Skip to content

Commit c9ee2ed

Browse files
committed
Add tests for the CI subcommand
1 parent 91144af commit c9ee2ed

File tree

2 files changed

+252
-11
lines changed

2 files changed

+252
-11
lines changed

cmd/wait-for-github/ci.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ var (
4444
commitRegexp = regexp.MustCompile(`.*github\.com/(?P<owner>[^/]+)/(?P<repo>[^/]+)/commit/(?P<commit>[abcdef\d]+)/?.*`)
4545
)
4646

47+
type ErrInvalidCommitURL struct {
48+
url string
49+
}
50+
51+
func (e ErrInvalidCommitURL) Error() string {
52+
return fmt.Sprintf("invalid commit URL: %s", e.url)
53+
}
54+
4755
func parseCIArguments(c *cli.Context) (ciConfig, error) {
4856
var owner, repo, ref string
4957

@@ -53,7 +61,7 @@ func parseCIArguments(c *cli.Context) (ciConfig, error) {
5361
url := c.Args().Get(0)
5462
match := commitRegexp.FindStringSubmatch(url)
5563
if match == nil {
56-
return ciConfig{}, fmt.Errorf("invalid commit URL: %s", url)
64+
return ciConfig{}, ErrInvalidCommitURL{url}
5765
}
5866

5967
owner = match[1]
@@ -71,10 +79,12 @@ func parseCIArguments(c *cli.Context) (ciConfig, error) {
7179
// but it doesn't work, says "unknown command ci". So we go through the parent command.
7280
lineage := c.Lineage()
7381
parent := lineage[1]
74-
cli.ShowCommandHelpAndExit(parent, "ci", 1)
82+
err := cli.ShowCommandHelp(parent, "ci")
83+
if err != nil {
84+
return ciConfig{}, err
85+
}
7586

76-
// shouldn't get here, the previous line should exit
77-
return ciConfig{}, nil
87+
return ciConfig{}, cli.Exit("invalid number of arguments", 1)
7888
}
7989

8090
return ciConfig{
@@ -143,12 +153,7 @@ func (ci checkSpecificCI) Check(ctx context.Context, recheckInterval time.Durati
143153
return handleCIStatus(status, recheckInterval)
144154
}
145155

146-
func checkCIStatus(timeoutCtx context.Context, cfg *config, ciConf *ciConfig) error {
147-
githubClient, err := github.NewGithubClient(timeoutCtx, cfg.AuthInfo)
148-
if err != nil {
149-
return err
150-
}
151-
156+
func checkCIStatus(timeoutCtx context.Context, githubClient github.CheckCIStatus, cfg *config, ciConf *ciConfig) error {
152157
log.Infof("Checking CI status on %s/%s@%s", ciConf.owner, ciConf.repo, ciConf.ref)
153158

154159
all := checkAllCI{
@@ -184,7 +189,14 @@ func ciCommand(cfg *config) *cli.Command {
184189

185190
return err
186191
},
187-
Action: func(c *cli.Context) error { return checkCIStatus(c.Context, cfg, &ciConf) },
192+
Action: func(c *cli.Context) error {
193+
githubClient, err := github.NewGithubClient(c.Context, cfg.AuthInfo)
194+
if err != nil {
195+
return err
196+
}
197+
198+
return checkCIStatus(c.Context, githubClient, cfg, &ciConf)
199+
},
188200
Flags: []cli.Flag{
189201
&cli.StringSliceFlag{
190202
Name: "check",

cmd/wait-for-github/ci_test.go

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
// wait-for-github
2+
// Copyright (C) 2023, Grafana Labs
3+
4+
// This program is free software: you can redistribute it and/or modify it under
5+
// the terms of the GNU Affero General Public License as published by the Free
6+
// Software Foundation, either version 3 of the License, or (at your option) any
7+
// later version.
8+
9+
// This program is distributed in the hope that it will be useful, but WITHOUT
10+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11+
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
12+
// details.
13+
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package main
18+
19+
import (
20+
"context"
21+
"flag"
22+
"testing"
23+
"time"
24+
25+
"github.com/grafana/wait-for-github/internal/github"
26+
"github.com/stretchr/testify/require"
27+
"github.com/urfave/cli/v2"
28+
)
29+
30+
// FakeCIStatusChecker implements the CheckCIStatus interface.
31+
type FakeCIStatusChecker struct {
32+
status github.CIStatus
33+
err error
34+
}
35+
36+
func (c *FakeCIStatusChecker) GetCIStatus(ctx context.Context, owner, repo string, commitHash string) (github.CIStatus, error) {
37+
return c.status, c.err
38+
}
39+
40+
func (c *FakeCIStatusChecker) GetCIStatusForChecks(ctx context.Context, owner, repo string, commitHash string, checkNames []string) (github.CIStatus, []string, error) {
41+
return c.status, checkNames, c.err
42+
}
43+
44+
var (
45+
zero = 0
46+
one = 1
47+
)
48+
49+
func TestHandleCIStatus(t *testing.T) {
50+
t.Parallel()
51+
52+
tests := []struct {
53+
name string
54+
status github.CIStatus
55+
expectedExitCode *int
56+
}{
57+
{
58+
name: "passed",
59+
status: github.CIStatusPassed,
60+
expectedExitCode: &zero,
61+
},
62+
{
63+
name: "failed",
64+
status: github.CIStatusFailed,
65+
expectedExitCode: &one,
66+
},
67+
{
68+
name: "pending",
69+
status: github.CIStatusPending,
70+
expectedExitCode: nil,
71+
},
72+
{
73+
name: "unknown",
74+
status: github.CIStatusUnknown,
75+
expectedExitCode: nil,
76+
},
77+
}
78+
79+
for _, tt := range tests {
80+
tt := tt
81+
82+
t.Run(tt.name, func(t *testing.T) {
83+
output := handleCIStatus(tt.status, 1)
84+
if tt.expectedExitCode == nil {
85+
require.Nil(t, output)
86+
} else {
87+
require.NotNil(t, output)
88+
require.Equal(t, *tt.expectedExitCode, output.ExitCode())
89+
}
90+
})
91+
}
92+
}
93+
func TestCheckCIStatus(t *testing.T) {
94+
t.Parallel()
95+
96+
tests := []struct {
97+
name string
98+
checks []string
99+
status github.CIStatus
100+
err error
101+
recheckInterval time.Duration
102+
expectedExitCode *int
103+
}{
104+
{
105+
name: "All checks pass",
106+
checks: []string{},
107+
status: github.CIStatusPassed,
108+
err: cli.Exit("CI successful", 0),
109+
recheckInterval: time.Second * 2,
110+
expectedExitCode: &zero,
111+
},
112+
{
113+
name: "Specific checks pass",
114+
checks: []string{"check1", "check2"},
115+
status: github.CIStatusPassed,
116+
err: cli.Exit("CI successful", 0),
117+
expectedExitCode: &zero,
118+
},
119+
{
120+
name: "All checks fail",
121+
checks: []string{},
122+
status: github.CIStatusFailed,
123+
err: cli.Exit("CI failed", 1),
124+
expectedExitCode: &one,
125+
},
126+
{
127+
name: "Specific checks fail",
128+
checks: []string{"check1", "check2"},
129+
status: github.CIStatusFailed,
130+
err: cli.Exit("CI failed", 1),
131+
expectedExitCode: &one,
132+
},
133+
{
134+
name: "All checks pending",
135+
checks: []string{},
136+
status: github.CIStatusPending,
137+
err: nil,
138+
recheckInterval: 1,
139+
expectedExitCode: &one,
140+
},
141+
{
142+
name: "Specific checks pending",
143+
checks: []string{"check1", "check2"},
144+
status: github.CIStatusPending,
145+
err: nil,
146+
recheckInterval: 1,
147+
expectedExitCode: &one,
148+
},
149+
}
150+
151+
for _, tt := range tests {
152+
t.Run(tt.name, func(t *testing.T) {
153+
fakeCIStatusChecker := &FakeCIStatusChecker{status: tt.status, err: tt.err}
154+
cfg := &config{
155+
recheckInterval: 1,
156+
}
157+
ciConf := &ciConfig{
158+
owner: "owner",
159+
repo: "repo",
160+
ref: "ref",
161+
checks: tt.checks,
162+
}
163+
164+
ctx, cancel := context.WithCancel(context.Background())
165+
cancel()
166+
167+
err := checkCIStatus(ctx, fakeCIStatusChecker, cfg, ciConf)
168+
169+
var exitErr cli.ExitCoder
170+
require.ErrorAs(t, err, &exitErr)
171+
require.Equal(t, *tt.expectedExitCode, exitErr.ExitCode())
172+
})
173+
}
174+
}
175+
176+
func TestParseCIArguments(t *testing.T) {
177+
tests := []struct {
178+
name string
179+
args []string
180+
want ciConfig
181+
wantErr error
182+
}{
183+
{
184+
name: "Valid commit URL",
185+
args: []string{"https://github.com/owner/repo/commit/abc123"},
186+
want: ciConfig{
187+
owner: "owner",
188+
repo: "repo",
189+
ref: "abc123",
190+
},
191+
},
192+
{
193+
name: "Valid arguments owner, repo, ref",
194+
args: []string{"owner", "repo", "abc123"},
195+
want: ciConfig{
196+
owner: "owner",
197+
repo: "repo",
198+
ref: "abc123",
199+
},
200+
},
201+
{
202+
name: "Invalid commit URL",
203+
args: []string{"https://invalid_url"},
204+
wantErr: ErrInvalidCommitURL{},
205+
},
206+
{
207+
name: "Invalid number of arguments",
208+
args: []string{"owner", "repo"},
209+
wantErr: cli.Exit("invalid number of arguments", 1),
210+
},
211+
}
212+
213+
for _, tt := range tests {
214+
t.Run(tt.name, func(t *testing.T) {
215+
flagSet := flag.NewFlagSet("test", flag.ContinueOnError)
216+
err := flagSet.Parse(tt.args)
217+
require.NoError(t, err)
218+
parentCliContext := cli.NewContext(nil, nil, nil)
219+
parentCliContext.App = cli.NewApp()
220+
cliContext := cli.NewContext(nil, flagSet, parentCliContext)
221+
222+
got, err := parseCIArguments(cliContext)
223+
if tt.wantErr != nil {
224+
require.ErrorAs(t, err, &tt.wantErr)
225+
}
226+
require.Equal(t, tt.want, got)
227+
})
228+
}
229+
}

0 commit comments

Comments
 (0)