Skip to content

Commit 7c3358c

Browse files
authored
test(e2e): add pipeline test (#2435)
The test flow for pipelines: 1. Create and clone a CodeCommit repo 2. Create an application with a single service "frontend" on two environments ("test" and "prod") in different AWS acc and regions. 3. Create a pipeline and validate with `pipeline show` 4. Push the artifacts to the repo and validate that the service is deployed with `pipeline status` 5. Finally validate that the endpoints are live with a GET request from `svc show` By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent df657c5 commit 7c3358c

File tree

3 files changed

+174
-25
lines changed

3 files changed

+174
-25
lines changed

e2e/internal/client/cli.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -643,10 +643,15 @@ func (cli *CLI) PipelineInit(app, url, branch string, envs []string) (string, er
643643
"-e", strings.Join(envs, ",")))
644644
}
645645

646+
// PipelineUpdate runs "copilot pipeline update".
647+
func (cli *CLI) PipelineUpdate(app string) (string, error) {
648+
return cli.exec(exec.Command(cli.path, "pipeline", "update", "-a", app, "--yes"))
649+
}
650+
646651
// PipelineShow runs "copilot pipeline show --json"
647-
func (cli *CLI) PipelineShow() (*PipelineShowOutput, error) {
652+
func (cli *CLI) PipelineShow(app string) (*PipelineShowOutput, error) {
648653
text, err := cli.exec(
649-
exec.Command(cli.path, "pipeline", "show", "--json"))
654+
exec.Command(cli.path, "pipeline", "show", "-a", app, "--json"))
650655
if err != nil {
651656
return nil, err
652657
}
@@ -658,9 +663,9 @@ func (cli *CLI) PipelineShow() (*PipelineShowOutput, error) {
658663
}
659664

660665
// PipelineStatus runs "copilot pipeline status --json"
661-
func (cli *CLI) PipelineStatus() (*PipelineStatusOutput, error) {
666+
func (cli *CLI) PipelineStatus(app string) (*PipelineStatusOutput, error) {
662667
text, err := cli.exec(
663-
exec.Command(cli.path, "pipeline", "status", "--json"))
668+
exec.Command(cli.path, "pipeline", "status", "-a", app, "--json"))
664669
if err != nil {
665670
return nil, err
666671
}

e2e/internal/client/outputs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ type PipelineShowOutput struct {
220220

221221
// PipelineStatusOutput represents the JSON output of the "pipeline status" command.
222222
type PipelineStatusOutput struct {
223-
States struct {
223+
States []struct {
224224
Name string `json:"stageName"`
225225
Actions []struct {
226226
Name string `json:"name"`

e2e/pipeline/pipeline_test.go

Lines changed: 164 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package pipeline_test
55

66
import (
77
"fmt"
8+
"net/http"
89
"net/url"
910
"os"
1011
"os/exec"
@@ -43,26 +44,6 @@ var _ = Describe("pipeline flow", func() {
4344
cmd.Stderr = os.Stderr
4445
Expect(cmd.Run()).NotTo(HaveOccurred())
4546
})
46-
47-
It("should push upstream", func() {
48-
cmd := exec.Command("git", "add", ".")
49-
cmd.Stdout = os.Stdout
50-
cmd.Stderr = os.Stderr
51-
cmd.Dir = repoName
52-
Expect(cmd.Run()).NotTo(HaveOccurred())
53-
54-
cmd = exec.Command("git", "commit", "-m", "first commit")
55-
cmd.Stdout = os.Stdout
56-
cmd.Stderr = os.Stderr
57-
cmd.Dir = repoName
58-
Expect(cmd.Run()).NotTo(HaveOccurred())
59-
60-
cmd = exec.Command("git", "push")
61-
cmd.Stdout = os.Stdout
62-
cmd.Stderr = os.Stderr
63-
cmd.Dir = repoName
64-
Expect(cmd.Run()).NotTo(HaveOccurred())
65-
})
6647
})
6748

6849
Context("create a new app", func() {
@@ -148,9 +129,172 @@ var _ = Describe("pipeline flow", func() {
148129
_, err := copilot.PipelineInit(appName, repoURL, "master", []string{"test", "prod"})
149130
Expect(err).NotTo(HaveOccurred())
150131
})
132+
151133
It("should generate pipeline artifacts", func() {
152134
Expect(filepath.Join(repoName, "copilot", "pipeline.yml")).Should(BeAnExistingFile())
153135
Expect(filepath.Join(repoName, "copilot", "buildspec.yml")).Should(BeAnExistingFile())
154136
})
137+
138+
It("should push repo changes upstream", func() {
139+
cmd := exec.Command("git", "config", "user.email", "e2etest@amazon.com")
140+
cmd.Stdout = os.Stdout
141+
cmd.Stderr = os.Stderr
142+
cmd.Dir = repoName
143+
Expect(cmd.Run()).NotTo(HaveOccurred())
144+
145+
cmd = exec.Command("git", "config", "user.name", "e2etest")
146+
cmd.Stdout = os.Stdout
147+
cmd.Stderr = os.Stderr
148+
cmd.Dir = repoName
149+
Expect(cmd.Run()).NotTo(HaveOccurred())
150+
151+
cmd = exec.Command("git", "add", ".")
152+
cmd.Stdout = os.Stdout
153+
cmd.Stderr = os.Stderr
154+
cmd.Dir = repoName
155+
Expect(cmd.Run()).NotTo(HaveOccurred())
156+
157+
cmd = exec.Command("git", "commit", "-m", "first commit")
158+
cmd.Stdout = os.Stdout
159+
cmd.Stderr = os.Stderr
160+
cmd.Dir = repoName
161+
Expect(cmd.Run()).NotTo(HaveOccurred())
162+
163+
cmd = exec.Command("git", "push")
164+
cmd.Stdout = os.Stdout
165+
cmd.Stderr = os.Stderr
166+
cmd.Dir = repoName
167+
Expect(cmd.Run()).NotTo(HaveOccurred())
168+
})
169+
})
170+
171+
Context("when creating the pipeline stack", func() {
172+
It("should start creating the pipeline stack", func() {
173+
_, err := copilot.PipelineUpdate(appName)
174+
Expect(err).NotTo(HaveOccurred())
175+
})
176+
177+
It("should show pipeline details once the stack is created", func() {
178+
type stage struct {
179+
Name string
180+
Category string
181+
}
182+
wantedStages := []stage{
183+
{
184+
Name: "Source",
185+
Category: "Source",
186+
},
187+
{
188+
Name: "Build",
189+
Category: "Build",
190+
},
191+
{
192+
Name: "DeployTo-test",
193+
Category: "Deploy",
194+
},
195+
{
196+
Name: "DeployTo-prod",
197+
Category: "Deploy",
198+
},
199+
}
200+
201+
Eventually(func() error {
202+
out, err := copilot.PipelineShow(appName)
203+
if err != nil {
204+
return err
205+
}
206+
if out.Name == "" {
207+
return fmt.Errorf("pipeline name is empty: %v", out)
208+
}
209+
if len(wantedStages) != len(out.Stages) {
210+
return fmt.Errorf("pipeline stages do not match: %v", out.Stages)
211+
}
212+
for idx, actualStage := range out.Stages {
213+
if wantedStages[idx].Name != actualStage.Name {
214+
return fmt.Errorf("stage name %s at index %d does not match", actualStage.Name, idx)
215+
}
216+
if wantedStages[idx].Category != actualStage.Category {
217+
return fmt.Errorf("stage category %s at index %d does not match", actualStage.Category, idx)
218+
}
219+
}
220+
return nil
221+
}, "600s", "10s").Should(BeNil())
222+
})
223+
224+
It("should deploy the services to both environments", func() {
225+
type state struct {
226+
Name string
227+
ActionName string
228+
ActionStatus string
229+
}
230+
wantedStates := []state{
231+
{
232+
Name: "Source",
233+
ActionName: fmt.Sprintf("SourceCodeFor-%s", appName),
234+
ActionStatus: "Succeeded",
235+
},
236+
{
237+
Name: "Build",
238+
ActionName: "Build",
239+
ActionStatus: "Succeeded",
240+
},
241+
{
242+
Name: "DeployTo-test",
243+
ActionName: "CreateOrUpdate-frontend-test",
244+
ActionStatus: "Succeeded",
245+
},
246+
{
247+
Name: "DeployTo-prod",
248+
ActionName: "CreateOrUpdate-frontend-prod",
249+
ActionStatus: "Succeeded",
250+
},
251+
}
252+
253+
Eventually(func() error {
254+
out, err := copilot.PipelineStatus(appName)
255+
if err != nil {
256+
return err
257+
}
258+
if len(wantedStates) != len(out.States) {
259+
return fmt.Errorf("len of pipeline states do not match: %v", out.States)
260+
}
261+
for idx, actualState := range out.States {
262+
if wantedStates[idx].Name != actualState.Name {
263+
return fmt.Errorf("state name %s at index %d does not match", actualState.Name, idx)
264+
}
265+
if len(actualState.Actions) != 1 {
266+
return fmt.Errorf("no action yet for state name %s", actualState.Name)
267+
}
268+
if wantedStates[idx].ActionName != actualState.Actions[0].Name {
269+
return fmt.Errorf("action name %v for state %s does not match at index %d", actualState.Actions[0], actualState.Name, idx)
270+
}
271+
if wantedStates[idx].ActionStatus != actualState.Actions[0].Status {
272+
return fmt.Errorf("action status %v for state %s does not match at index %d", actualState.Actions[0], actualState.Name, idx)
273+
}
274+
}
275+
return nil
276+
}, "1200s", "15s").Should(BeNil())
277+
})
278+
})
279+
280+
Context("services should be queryable post-release", func() {
281+
It("services should include a valid URL", func() {
282+
out, err := copilot.SvcShow(&client.SvcShowRequest{
283+
AppName: appName,
284+
Name: "frontend",
285+
})
286+
Expect(err).NotTo(HaveOccurred())
287+
288+
routes := make(map[string]string)
289+
for _, route := range out.Routes {
290+
routes[route.Environment] = route.URL
291+
}
292+
for _, env := range []string{"test", "prod"} {
293+
Eventually(func() (int, error) {
294+
resp, fetchErr := http.Get(routes[env])
295+
return resp.StatusCode, fetchErr
296+
}, "30s", "1s").Should(Equal(200))
297+
}
298+
})
155299
})
156300
})

0 commit comments

Comments
 (0)