// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package cli import ( "errors" "fmt" "testing" awscfn "github.com/aws/copilot-cli/internal/pkg/aws/cloudformation" "github.com/aws/copilot-cli/internal/pkg/manifest/manifestinfo" "github.com/aws/copilot-cli/internal/pkg/term/prompt" climocks "github.com/aws/copilot-cli/internal/pkg/cli/mocks" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) func TestInitOpts_Run(t *testing.T) { mockSchedule := "@hourly" var mockPort uint16 = 80 var mockAppName = "demo" testCases := map[string]struct { inShouldDeploy bool inPromptForShouldDeploy bool inAppName string inWlType string expect func(opts *initOpts) wantedError string }{ "returns prompt error for application": { inWlType: "Load Balanced Web Service", expect: func(opts *initOpts) { opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(errors.New("my error")) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Validate().Times(0) }, wantedError: "ask app init: my error", }, "returns validation error for application": { expect: func(opts *initOpts) { opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(errors.New("my error")) }, wantedError: "my error", }, "returns prompt error for service": { inWlType: "Backend Service", expect: func(opts *initOpts) { opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Validate().Times(1).Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(errors.New("my error")) }, wantedError: "ask Backend Service: my error", }, "returns validation error for service": { inWlType: "Load Balanced Web Service", expect: func(opts *initOpts) { opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(errors.New("my error")) }, wantedError: "validate Load Balanced Web Service: my error", }, "returns execute error for application": { expect: func(opts *initOpts) { opts.prompt.(*climocks.Mockprompter).EXPECT().SelectOption(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(errors.New("my error")) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Execute().Times(0) }, wantedError: "execute app init: my error", }, "returns execute error for service": { inWlType: "Load Balanced Web Service", expect: func(opts *initOpts) { opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(errors.New("my error")) }, wantedError: "execute Load Balanced Web Service init: my error", }, "fail to deploy an environment": { inPromptForShouldDeploy: true, expect: func(opts *initOpts) { opts.prompt.(*climocks.Mockprompter).EXPECT().SelectOption(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(manifestinfo.LoadBalancedWebServiceType, nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.prompt.(*climocks.Mockprompter).EXPECT().Confirm(initShouldDeployPrompt, initShouldDeployHelpPrompt, gomock.Any()). Return(true, nil) opts.initEnvCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.deployEnvCmd.(*climocks.Mockcmd).EXPECT().Execute().Return(errors.New("some error")) }, wantedError: "some error", }, "deploys environment": { inPromptForShouldDeploy: true, expect: func(opts *initOpts) { opts.prompt.(*climocks.Mockprompter).EXPECT().SelectOption(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(manifestinfo.LoadBalancedWebServiceType, nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.prompt.(*climocks.Mockprompter).EXPECT().Confirm(initShouldDeployPrompt, initShouldDeployHelpPrompt, gomock.Any()). Return(true, nil) opts.initEnvCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.deployEnvCmd.(*climocks.Mockcmd).EXPECT().Execute().Return(nil) opts.deploySvcCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.deploySvcCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.deploySvcCmd.(*climocks.MockactionCommand).EXPECT().RecommendActions().Return(nil) }, }, "should not error out if environment change set is empty": { inPromptForShouldDeploy: true, expect: func(opts *initOpts) { opts.prompt.(*climocks.Mockprompter).EXPECT().SelectOption(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(manifestinfo.LoadBalancedWebServiceType, nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.prompt.(*climocks.Mockprompter).EXPECT().Confirm(initShouldDeployPrompt, initShouldDeployHelpPrompt, gomock.Any()). Return(true, nil) opts.initEnvCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.deployEnvCmd.(*climocks.Mockcmd).EXPECT().Execute().Return(fmt.Errorf("wrap: %w", &awscfn.ErrChangeSetEmpty{})) opts.deploySvcCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.deploySvcCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.deploySvcCmd.(*climocks.MockactionCommand).EXPECT().RecommendActions().Return(nil) }, }, "deploy workload happy path": { inPromptForShouldDeploy: true, inShouldDeploy: true, expect: func(opts *initOpts) { opts.prompt.(*climocks.Mockprompter).EXPECT().SelectOption(gomock.Any(), gomock.Any(), []prompt.Option{ { Value: manifestinfo.RequestDrivenWebServiceType, Hint: "App Runner", }, { Value: manifestinfo.LoadBalancedWebServiceType, Hint: "Internet to ECS on Fargate", }, { Value: manifestinfo.BackendServiceType, Hint: "ECS on Fargate", }, { Value: manifestinfo.WorkerServiceType, Hint: "Events to SQS to ECS on Fargate", }, { Value: manifestinfo.StaticSiteType, Hint: "Internet to CDN to S3 bucket", }, { Value: manifestinfo.ScheduledJobType, Hint: "Scheduled event to State Machine to Fargate", }, }, gomock.Any()) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.prompt.(*climocks.Mockprompter).EXPECT().Confirm(initShouldDeployPrompt, initShouldDeployHelpPrompt, gomock.Any()). Return(true, nil) opts.initEnvCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.deployEnvCmd.(*climocks.Mockcmd).EXPECT().Execute().Return(nil) opts.deploySvcCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.deploySvcCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.deploySvcCmd.(*climocks.MockactionCommand).EXPECT().RecommendActions().Return(nil) }, }, "should not deploy the svc if shouldDeploy is false": { inPromptForShouldDeploy: true, inShouldDeploy: false, expect: func(opts *initOpts) { opts.prompt.(*climocks.Mockprompter).EXPECT().SelectOption(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(manifestinfo.LoadBalancedWebServiceType, nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Ask().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Validate().Return(nil) opts.initAppCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.initWlCmd.(*climocks.MockactionCommand).EXPECT().Execute().Return(nil) opts.prompt.(*climocks.Mockprompter).EXPECT().Confirm(initShouldDeployPrompt, initShouldDeployHelpPrompt, gomock.Any()). Return(false, nil) }, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { // GIVEN ctrl := gomock.NewController(t) defer ctrl.Finish() opts := &initOpts{ initVars: initVars{ appName: tc.inAppName, wkldType: tc.inWlType, }, ShouldDeploy: tc.inShouldDeploy, promptForShouldDeploy: tc.inPromptForShouldDeploy, initAppCmd: climocks.NewMockactionCommand(ctrl), initWlCmd: climocks.NewMockactionCommand(ctrl), initEnvCmd: climocks.NewMockactionCommand(ctrl), deployEnvCmd: climocks.NewMockcmd(ctrl), deploySvcCmd: climocks.NewMockactionCommand(ctrl), prompt: climocks.NewMockprompter(ctrl), // These fields are used for logging, the values are not important for tests. appName: &mockAppName, initWkldVars: &initWkldVars{}, schedule: &mockSchedule, port: &mockPort, setupWorkloadInit: func(*initOpts, string) error { return nil }, useExistingWorkspaceForCMDs: func(opts *initOpts) error { return nil }, } tc.expect(opts) // WHEN err := opts.Run() // THEN if tc.wantedError != "" { require.EqualError(t, err, tc.wantedError) } else { require.NoError(t, err) } }) } }