// Copyright 2015-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
//	http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.

package service

import (
	"flag"
	"fmt"
	"strconv"
	"strings"
	"testing"

	"github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/compose/context"
	"github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/compose/entity"
	mock_ecs "github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients/aws/ecs/mock"
	"github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients/aws/tagging"
	mock_tagging "github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients/aws/tagging/mock"
	"github.com/aws/amazon-ecs-cli/ecs-cli/modules/commands/flags"
	"github.com/aws/amazon-ecs-cli/ecs-cli/modules/config"
	utils "github.com/aws/amazon-ecs-cli/ecs-cli/modules/utils/compose"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ecs"
	taggingSDK "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"
	"github.com/urfave/cli"
)

const (
	arnPrefix = "arn:aws:ecs:us-west-2:accountId:task-definition/"
)

// Values returned by the ECS Settings API
const (
	ecsSettingEnabled  = "enabled"
	ecsSettingDisabled = "disabled"
)

//////////////////////////
// Create Service tests //
/////////////////////////

func TestCreateWithDeploymentConfig(t *testing.T) {
	deploymentMaxPercent := 200
	deploymentMinPercent := 100

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.DeploymentMaxPercentFlag, strconv.Itoa(deploymentMaxPercent), "")
	flagSet.String(flags.DeploymentMinHealthyPercentFlag, strconv.Itoa(deploymentMinPercent), "")

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			actual := input.DeploymentConfiguration
			assert.Equal(t, int64(deploymentMaxPercent), aws.Int64Value(actual.MaximumPercent), "DeploymentConfig.MaxPercent should match")
			assert.Equal(t, int64(deploymentMinPercent), aws.Int64Value(actual.MinimumHealthyPercent), "DeploymentConfig.MinimumHealthyPercent should match")
		},
		ecsSettingDisabled,
	)
}

func TestCreateWithoutDeploymentConfig(t *testing.T) {
	flagSet := flag.NewFlagSet("ecs-cli-up", 0)

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			actual := input.DeploymentConfiguration
			assert.Nil(t, actual.MaximumPercent, "DeploymentConfig.MaximumPercent should be nil")
			assert.Nil(t, actual.MinimumHealthyPercent, "DeploymentConfig.MinimumHealthyPercent should be nil")
		},
		ecsSettingDisabled,
	)
}

func ecsParamsWithNetworkConfig() *utils.ECSParams {
	return &utils.ECSParams{
		TaskDefinition: utils.EcsTaskDef{
			NetworkMode: "awsvpc",
		},
		RunParams: utils.RunParams{
			NetworkConfiguration: utils.NetworkConfiguration{
				AwsVpcConfiguration: utils.AwsVpcConfiguration{
					Subnets: []string{"sg-bafff1ed", "sg-c0ffeefe"},
				},
			},
		},
	}
}

func TestCreateWithNetworkConfig(t *testing.T) {
	flagSet := flag.NewFlagSet("ecs-cli-up", 0)

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		ecsParamsWithNetworkConfig(),
		func(input *ecs.CreateServiceInput) {
			launchType := input.LaunchType
			assert.NotEqual(t, "FARGATE", launchType)

			networkConfig := input.NetworkConfiguration
			assert.NotNil(t, networkConfig, "NetworkConfiguration should not be nil")
			assert.Equal(t, 2, len(networkConfig.AwsvpcConfiguration.Subnets))
			assert.Nil(t, networkConfig.AwsvpcConfiguration.AssignPublicIp)
		},
		ecsSettingDisabled,
	)
}

func ecsParamsWithFargateNetworkConfig() *utils.ECSParams {
	return &utils.ECSParams{
		TaskDefinition: utils.EcsTaskDef{
			ExecutionRole: "arn:aws:iam::123456789012:role/fargate_role",
			NetworkMode:   "awsvpc",
			TaskSize: utils.TaskSize{
				Cpu:    "512",
				Memory: "1GB",
			},
		},
		RunParams: utils.RunParams{
			NetworkConfiguration: utils.NetworkConfiguration{
				AwsVpcConfiguration: utils.AwsVpcConfiguration{
					Subnets:        []string{"sg-bafff1ed", "sg-c0ffeefe"},
					AssignPublicIp: utils.Enabled,
				},
			},
		},
	}
}

func TestCreateWithEFS_EC2(t *testing.T) {
	flagSet := flag.NewFlagSet("ecs-cli-up", 0)

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{LaunchType: config.LaunchTypeEC2},
		ecsParamsWithEFSVolume(),
		func(input *ecs.CreateServiceInput) {
			launchType := input.LaunchType
			assert.Equal(t, config.LaunchTypeEC2, aws.StringValue(launchType), "launch type is not ec2")
			platformVersion := input.PlatformVersion

			assert.Nil(t, platformVersion)
		},
		ecsSettingDisabled,
	)
}

func TestCreateWithEFSFargate(t *testing.T) {
	flagSet := flag.NewFlagSet("ecs-cli-up", 0)

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{LaunchType: config.LaunchTypeFargate},
		ecsParamsWithEFSVolume(),
		func(input *ecs.CreateServiceInput) {
			launchType := input.LaunchType
			assert.Equal(t, config.LaunchTypeFargate, aws.StringValue(launchType), "launch type is not fargate")
			platformVersion := input.PlatformVersion
			assert.Equal(t, aws.String(config.PlatformVersion140), platformVersion)
		},
		ecsSettingDisabled,
	)
}

func ecsParamsWithEFSVolume() *utils.ECSParams {
	return &utils.ECSParams{
		TaskDefinition: utils.EcsTaskDef{
			ExecutionRole: "arn:aws:iam::123456789012:role/my_execution_role",
			NetworkMode:   "awsvpc",
			TaskSize: utils.TaskSize{
				Cpu:    "512",
				Memory: "1GB",
			},
			EFSVolumes: []utils.EFSVolume{
				{
					Name:         "myVolume",
					FileSystemID: aws.String("fs-1234"),
				},
			},
		},
		RunParams: utils.RunParams{
			NetworkConfiguration: utils.NetworkConfiguration{
				AwsVpcConfiguration: utils.AwsVpcConfiguration{
					Subnets:        []string{"sg-bafff1ed", "sg-c0ffeefe"},
					AssignPublicIp: utils.Enabled,
				},
			},
		},
	}
}

func TestCreateFargate(t *testing.T) {
	flagSet := flag.NewFlagSet("ecs-cli-up", 0)

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{LaunchType: "FARGATE"},
		ecsParamsWithFargateNetworkConfig(),
		func(input *ecs.CreateServiceInput) {
			launchType := input.LaunchType
			assert.Equal(t, "FARGATE", aws.StringValue(launchType))

			networkConfig := input.NetworkConfiguration
			assert.NotNil(t, networkConfig, "NetworkConfiguration should not be nil")
			assert.Equal(t, 2, len(networkConfig.AwsvpcConfiguration.Subnets))
			assert.Equal(t, string(utils.Enabled), aws.StringValue(networkConfig.AwsvpcConfiguration.AssignPublicIp))
		},
		ecsSettingDisabled,
	)
}

func TestCreateFargateNetworkModeNotAWSVPC(t *testing.T) {
	taskDefID := "taskDefinitionId"
	taskDefArn := "arn/" + taskDefID

	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	mockEcs := mock_ecs.NewMockECSClient(ctrl)

	taskDefinition := ecs.TaskDefinition{
		Family:               aws.String("family"),
		ContainerDefinitions: []*ecs.ContainerDefinition{},
		Volumes:              []*ecs.Volume{},
	}
	respTaskDef := taskDefinition
	respTaskDef.TaskDefinitionArn = aws.String(taskDefArn)

	gomock.InOrder(
		mockEcs.EXPECT().RegisterTaskDefinitionIfNeeded(gomock.Any(), gomock.Any()).Do(func(x, y interface{}) {
			// verify input fields
			req := x.(*ecs.RegisterTaskDefinitionInput)
			assert.Equal(t, aws.StringValue(taskDefinition.Family), aws.StringValue(req.Family), "Task Definition family should match")
		}).Return(&respTaskDef, nil),
	)

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	cliContext := cli.NewContext(nil, flagSet, nil)

	context := &context.ECSContext{
		ECSClient:     mockEcs,
		CommandConfig: &config.CommandConfig{LaunchType: "FARGATE"},
		CLIContext:    cliContext,
		ECSParams:     &utils.ECSParams{},
	}

	service := NewService(context)
	err := service.LoadContext()
	assert.NoError(t, err, "Unexpected error while loading context in create service test")

	service.SetTaskDefinition(&taskDefinition)
	err = service.Create()
	assert.Error(t, err, "Expected error creating service")
}

func TestCreateEC2Explicitly(t *testing.T) {
	flagSet := flag.NewFlagSet("ecs-cli-up", 0)

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{LaunchType: "EC2"},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			launchType := input.LaunchType
			assert.Equal(t, "EC2", aws.StringValue(launchType))
			assert.Nil(t, input.PlatformVersion, "platform version is not nil")
			networkConfig := input.NetworkConfiguration
			assert.Nil(t, networkConfig, "NetworkConfiguration should be nil")
		},
		ecsSettingDisabled,
	)
}

func TestCreateWithTaskPlacement(t *testing.T) {
	flagSet := flag.NewFlagSet("ecs-cli-up", 0)

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		ecsParamsWithTaskPlacement(),
		func(input *ecs.CreateServiceInput) {
			placementConstraints := input.PlacementConstraints
			placementStrategy := input.PlacementStrategy
			expectedConstraints := []*ecs.PlacementConstraint{
				{
					Type: aws.String("distinctInstance"),
				}, {
					Expression: aws.String("attribute:ecs.instance-type =~ t2.*"),
					Type:       aws.String("memberOf"),
				},
			}
			expectedStrategy := []*ecs.PlacementStrategy{
				{
					Type: aws.String("random"),
				}, {
					Field: aws.String("instanceId"),
					Type:  aws.String("binpack"),
				},
			}
			assert.Nil(t, input.PlatformVersion, "platform version should be nil for ec2")
			assert.Nil(t, input.LaunchType, "Launch type should be nil for default case")
			assert.Len(t, placementConstraints, 2)
			assert.Equal(t, expectedConstraints, placementConstraints, "Expected Placement Constraints to match")
			assert.Len(t, placementStrategy, 2)
			assert.Equal(t, expectedStrategy, placementStrategy, "Expected Placement Strategy to match")
		},
		ecsSettingDisabled,
	)
}

func ecsParamsWithTaskPlacement() *utils.ECSParams {
	return &utils.ECSParams{
		RunParams: utils.RunParams{
			TaskPlacement: utils.TaskPlacement{
				Constraints: []utils.Constraint{
					utils.Constraint{
						Type: ecs.PlacementConstraintTypeDistinctInstance,
					},
					utils.Constraint{
						Expression: "attribute:ecs.instance-type =~ t2.*",
						Type:       ecs.PlacementConstraintTypeMemberOf,
					},
				},
				Strategies: []utils.Strategy{
					utils.Strategy{
						Type: ecs.PlacementStrategyTypeRandom,
					},
					utils.Strategy{
						Field: "instanceId",
						Type:  ecs.PlacementStrategyTypeBinpack,
					},
				},
			},
		},
	}
}

// Specifies TargetGroupArn to test ALB
func TestCreateWithALB(t *testing.T) {
	targetGroupArn := "targetGroupArn"
	containerName := "containerName"
	containerPort := 80
	role := "role"

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.TargetGroupArnFlag, targetGroupArn, "")
	flagSet.String(flags.ContainerNameFlag, containerName, "")
	flagSet.String(flags.ContainerPortFlag, strconv.Itoa(containerPort), "")
	flagSet.String(flags.RoleFlag, role, "")

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			loadBalancer := input.LoadBalancers[0]
			observedRole := input.Role

			assert.NotNil(t, loadBalancer, "LoadBalancer should not be nil")
			assert.Nil(t, loadBalancer.LoadBalancerName, "LoadBalancer.LoadBalancerName should be nil")
			assert.Equal(t, targetGroupArn, aws.StringValue(loadBalancer.TargetGroupArn), "LoadBalancer.TargetGroupArn should match")
			assert.Equal(t, containerName, aws.StringValue(loadBalancer.ContainerName), "LoadBalancer.ContainerName should match")
			assert.Equal(t, int64(containerPort), aws.Int64Value(loadBalancer.ContainerPort), "LoadBalancer.ContainerPort should match")
			assert.Equal(t, role, aws.StringValue(observedRole), "Role should match")
		},
		ecsSettingDisabled,
	)
}

func TestCreateWithHealthCheckGracePeriodAndALB(t *testing.T) {
	targetGroupArn := "targetGroupArn"
	containerName := "containerName"
	containerPort := 80
	role := "role"
	healthCheckGP := 60

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.TargetGroupArnFlag, targetGroupArn, "")
	flagSet.String(flags.ContainerNameFlag, containerName, "")
	flagSet.String(flags.ContainerPortFlag, strconv.Itoa(containerPort), "")
	flagSet.String(flags.RoleFlag, role, "")
	flagSet.String(flags.HealthCheckGracePeriodFlag, strconv.Itoa(healthCheckGP), "")

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			loadBalancer := input.LoadBalancers[0]
			observedRole := input.Role
			healthCheckGracePeriod := input.HealthCheckGracePeriodSeconds

			assert.NotNil(t, loadBalancer, "LoadBalancer should not be nil")
			assert.Nil(t, loadBalancer.LoadBalancerName, "LoadBalancer.LoadBalancerName should be nil")
			assert.Equal(t, targetGroupArn, aws.StringValue(loadBalancer.TargetGroupArn), "LoadBalancer.TargetGroupArn should match")
			assert.Equal(t, containerName, aws.StringValue(loadBalancer.ContainerName), "LoadBalancer.ContainerName should match")
			assert.Equal(t, int64(containerPort), aws.Int64Value(loadBalancer.ContainerPort), "LoadBalancer.ContainerPort should match")
			assert.Equal(t, role, aws.StringValue(observedRole), "Role should match")
			assert.Equal(t, int64(healthCheckGP), *healthCheckGracePeriod, "HealthCheckGracePeriod should match")
		},
		ecsSettingDisabled,
	)
}

func TestCreateWithTwoTargetGroupsFlag(t *testing.T) {
	role := "role"
	targetGroups := &cli.StringSlice{}
	targetGroups.Set("targetGroupArn=CS:GO,containerName=Bobs,containerPort=80")
	targetGroups.Set("targetGroupArn=Overwatch,containerName=Joes,containerPort=40")

	targetGroupArnList := []string{"CS:GO", "Overwatch"}
	containerNameList := []string{"Bobs", "Joes"}
	containerPostList := []int64{80, 40}

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.RoleFlag, role, "")
	flagSet.Var(targetGroups, flags.TargetGroupsFlag, "")

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			loadBalancer := input.LoadBalancers
			observedRole := input.Role
			for i := 0; i < len(loadBalancer); i++ {
				assert.Equal(t, *loadBalancer[i].TargetGroupArn, targetGroupArnList[i], "LoadBalancer.TargetGroupArn should match")
				assert.Equal(t, *loadBalancer[i].ContainerName, containerNameList[i], "LoadBalancer.ContainerName should match")
				assert.Equal(t, *loadBalancer[i].ContainerPort, containerPostList[i], "LoadBalancer.ContainerPort should match")
				assert.Equal(t, role, aws.StringValue(observedRole), "Role should match")
			}
		},
		ecsSettingDisabled,
	)
}
func TestCreateWithTargetGroupsFlagWithMultipleTargetGroupArn(t *testing.T) {
	role := "role"
	targetGroups := &cli.StringSlice{}
	targetGroups.Set("targetGroupArn=SuperMario,containerName=Bob,containerPort=80")
	targetGroups.Set("targetGroupArn=Overwatch,targetGroupArn=Overwatch2,containerName=Joes,containerPort=40")

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.RoleFlag, role, "")
	flagSet.Var(targetGroups, flags.TargetGroupsFlag, "")
	cliContext := cli.NewContext(nil, flagSet, nil)

	service := &Service{
		ecsContext: &context.ECSContext{CLIContext: cliContext},
	}

	err := service.LoadContext()
	assert.Error(t, err, "Cannot have multiple targetGroupArns in one --target-group flag")
}
func TestCreateWithTargetGroupsFlagWithMultipleLoadBalancerName(t *testing.T) {
	role := "role"
	targetGroups := &cli.StringSlice{}
	targetGroups.Set("targetGroupArn=SuperMario,containerName=Bob,containerPort=80")
	targetGroups.Set("loadBalancerName=Overwatch,loadBalancerName=Overwatch2,containerName=Joes,containerPort=40")

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.RoleFlag, role, "")
	flagSet.Var(targetGroups, flags.TargetGroupsFlag, "")
	cliContext := cli.NewContext(nil, flagSet, nil)

	service := &Service{
		ecsContext: &context.ECSContext{CLIContext: cliContext},
	}

	err := service.LoadContext()
	assert.Error(t, err, "Cannot have multiple loadBalancerName in one --target-group flag")
}
func TestCreateWithTargetGroupsFlagWithTargetGroupArnAndLoadBalancerName(t *testing.T) {
	role := "role"
	targetGroups := &cli.StringSlice{}
	targetGroups.Set("targetGroupArn=Overwatch,containerName=Joes,containerPort=40")
	targetGroups.Set("targetGroupArn=CS:GO,loadBalancerName=SuperMario,containerName=Bob,containerPort=80")

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.RoleFlag, role, "")
	flagSet.Var(targetGroups, flags.TargetGroupsFlag, "")
	cliContext := cli.NewContext(nil, flagSet, nil)

	service := &Service{
		ecsContext: &context.ECSContext{CLIContext: cliContext},
	}

	err := service.LoadContext()
	assert.Error(t, err, "Cannot have targetGroupArn and loadBalancerName together")
}
func TestCreateWithTargetGroupsFlagWithoutTargetGroupArnAndLoadBalancerName(t *testing.T) {
	role := "role"
	targetGroups := &cli.StringSlice{}
	targetGroups.Set("targetGroupArn=Doom,containerName=Joes,containerPort=40")
	targetGroups.Set("containerName=Bob,containerPort=80")

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.RoleFlag, role, "")
	flagSet.Var(targetGroups, flags.TargetGroupsFlag, "")
	cliContext := cli.NewContext(nil, flagSet, nil)

	service := &Service{
		ecsContext: &context.ECSContext{CLIContext: cliContext},
	}

	err := service.LoadContext()
	assert.Error(t, err, "Must have targetGroupArn OR loadBalancerName specified")
}
func TestCreateWithTargetGroupsFlagWithInvalidFlag(t *testing.T) {
	role := "role"
	targetGroups := &cli.StringSlice{}
	targetGroups.Set("targetGroupArnnnnn=CS:GO,containerName=Bobs,containerPort=80")
	targetGroups.Set("targetGroupArn=Overwatch,containerName=Joes,containerPort=40")

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.RoleFlag, role, "")
	flagSet.Var(targetGroups, flags.TargetGroupsFlag, "")
	cliContext := cli.NewContext(nil, flagSet, nil)

	service := &Service{
		ecsContext: &context.ECSContext{CLIContext: cliContext},
	}

	err := service.LoadContext()
	assert.Error(t, err, "targetGroupArnnnn is a invalid flag")
}
func TestCreateWithTargetGroupsFlagWithContainerNameMissing(t *testing.T) {
	role := "role"
	targetGroups := &cli.StringSlice{}
	targetGroups.Set("targetGroupArn=Overwatch,containerName=Joes,containerPort=40")
	targetGroups.Set("targetGroupArn=CS:GO,containerPort=80")

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.RoleFlag, role, "")
	flagSet.Var(targetGroups, flags.TargetGroupsFlag, "")
	cliContext := cli.NewContext(nil, flagSet, nil)

	service := &Service{
		ecsContext: &context.ECSContext{CLIContext: cliContext},
	}

	err := service.LoadContext()
	assert.Error(t, err, "containerName is Missing")
}

func TestCreateWithTargetGroupsFlagWithContainerPortInvalid(t *testing.T) {
	role := "role"
	targetGroups := &cli.StringSlice{}
	targetGroups.Set("targetGroupArn=Overwatch,containerName=Joes,containerPort=40")
	targetGroups.Set("targetGroupArn=CS:GO,containerName=Bob,containerPort=word")

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.RoleFlag, role, "")
	flagSet.Var(targetGroups, flags.TargetGroupsFlag, "")
	cliContext := cli.NewContext(nil, flagSet, nil)

	service := &Service{
		ecsContext: &context.ECSContext{CLIContext: cliContext},
	}

	err := service.LoadContext()
	assert.Error(t, err, "containerPort is not an interger")
}
func TestCreateWithTargetGroupFlagWithContainerPortMissing(t *testing.T) {
	role := "role"
	targetGroups := &cli.StringSlice{}
	targetGroups.Set("targetGroupArn=CS:GO,containerName=Bob,containerPort=80")
	targetGroups.Set("targetGroupArn=Overwatch,containerName=Joes")

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.RoleFlag, role, "")
	flagSet.Var(targetGroups, flags.TargetGroupsFlag, "")
	cliContext := cli.NewContext(nil, flagSet, nil)

	service := &Service{
		ecsContext: &context.ECSContext{CLIContext: cliContext},
	}

	err := service.LoadContext()
	assert.Error(t, err, "containerPort is missing")
}

func TestCreateWithTargetGroupsFlagWitDoubleEqualSigns(t *testing.T) {
	role := "role"
	targetGroups := &cli.StringSlice{}
	targetGroups.Set("targetGroupArn=CS:GO,containerName=Bob,containerPort=80")
	targetGroups.Set("targetGroupArn=Overwatch,containerName=Joes=Mikes,containerPort=40")

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.RoleFlag, role, "")
	flagSet.Var(targetGroups, flags.TargetGroupsFlag, "")
	cliContext := cli.NewContext(nil, flagSet, nil)

	service := &Service{
		ecsContext: &context.ECSContext{CLIContext: cliContext},
	}

	err := service.LoadContext()
	assert.Error(t, err, "Cannot have double equal")
}

func TestCreateWithTargetGroupArnAndTargetGroupsFlag(t *testing.T) {
	targetGroupArn := "targetGroupArnName"
	targetGroups := &cli.StringSlice{}
	targetGroups.Set("targetGroupArn=CS:GO,containerName=Bobs,containerPort=80")

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.TargetGroupArnFlag, targetGroupArn, "")
	flagSet.Var(targetGroups, flags.TargetGroupsFlag, "")
	cliContext := cli.NewContext(nil, flagSet, nil)

	service := &Service{
		ecsContext: &context.ECSContext{CLIContext: cliContext},
	}
	err := service.LoadContext()
	assert.Error(t, err, "Cannot have both --target-group-arn flag with --target-groups")
}

func TestCreateWithLoadBalancerNameAndTargetGroupsFlag(t *testing.T) {
	loadBalancerName := "loadBalancerName"
	targetGroups := &cli.StringSlice{}
	targetGroups.Set("targetGroupArn=CS:GO,containerName=Bobs,containerPort=80")

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.LoadBalancerNameFlag, loadBalancerName, "")
	flagSet.Var(targetGroups, flags.TargetGroupsFlag, "")
	cliContext := cli.NewContext(nil, flagSet, nil)

	service := &Service{
		ecsContext: &context.ECSContext{CLIContext: cliContext},
	}
	err := service.LoadContext()
	assert.Error(t, err, "Cannot have both --load-balancer-name flag with --target-groups")
}

func TestCreateWithContainerNameAndTargetGroupsFlag(t *testing.T) {
	containerName := "containerName"
	targetGroups := &cli.StringSlice{}
	targetGroups.Set("targetGroupArn=CS:GO,containerName=Bobs,containerPort=80")

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.ContainerNameFlag, containerName, "")
	flagSet.Var(targetGroups, flags.TargetGroupsFlag, "")
	cliContext := cli.NewContext(nil, flagSet, nil)

	service := &Service{
		ecsContext: &context.ECSContext{CLIContext: cliContext},
	}
	err := service.LoadContext()
	assert.Error(t, err, "Cannot have both --container-name flag with --target-groups")
}

func TestCreateWithContainerPortAndTargetGroupsFlag(t *testing.T) {
	containerPort := "containerPort"
	targetGroups := &cli.StringSlice{}
	targetGroups.Set("targetGroupArn=CS:GO,containerName=Bobs,containerPort=80")

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.ContainerPortFlag, containerPort, "")
	flagSet.Var(targetGroups, flags.TargetGroupsFlag, "")
	cliContext := cli.NewContext(nil, flagSet, nil)

	service := &Service{
		ecsContext: &context.ECSContext{CLIContext: cliContext},
	}
	err := service.LoadContext()
	assert.Error(t, err, "Cannot have both --container-port flag with --target-groups")
}

func TestCreateWithLoadBalancerNameFlagAndTargetGroupsFlag(t *testing.T) {
	targetGroups := &cli.StringSlice{}
	targetGroups.Set("loadBalancerName=CS:GO,containerName=Bobs,containerPort=80")
	targetGroups.Set("loadBalancerName=Overwatch,containerName=Joes,containerPort=40")

	loadBalancerNameList := []string{"CS:GO", "Overwatch"}
	containerNameList := []string{"Bobs", "Joes"}
	containerPostList := []int64{80, 40}

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.Var(targetGroups, flags.TargetGroupsFlag, "")

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			loadBalancer := input.LoadBalancers
			for i := 0; i < len(loadBalancer); i++ {
				assert.Equal(t, *loadBalancer[i].LoadBalancerName, loadBalancerNameList[i], "LoadBalancer.LoadBalancerName should match")
				assert.Equal(t, *loadBalancer[i].ContainerName, containerNameList[i], "LoadBalancer.ContainerName should match")
				assert.Equal(t, *loadBalancer[i].ContainerPort, containerPostList[i], "LoadBalancer.ContainerPort should match")
			}
		},
		ecsSettingDisabled,
	)
}

// Specifies LoadBalancerName to test ELB
func TestCreateWithELB(t *testing.T) {
	loadbalancerName := "loadbalancerName"
	containerName := "containerName"
	containerPort := 80
	role := "role"

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.LoadBalancerNameFlag, loadbalancerName, "")
	flagSet.String(flags.ContainerNameFlag, containerName, "")
	flagSet.String(flags.ContainerPortFlag, strconv.Itoa(containerPort), "")
	flagSet.String(flags.RoleFlag, role, "")

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			loadBalancer := input.LoadBalancers[0]
			observedRole := input.Role

			assert.NotNil(t, loadBalancer, "LoadBalancer should not be nil")
			assert.Nil(t, loadBalancer.TargetGroupArn, "LoadBalancer.TargetGroupArn should be nil")
			assert.Equal(t, loadbalancerName, aws.StringValue(loadBalancer.LoadBalancerName), "LoadBalancer.LoadBalancerName should match")
			assert.Equal(t, containerName, aws.StringValue(loadBalancer.ContainerName), "LoadBalancer.ContainerName should match")
			assert.Equal(t, int64(containerPort), aws.Int64Value(loadBalancer.ContainerPort), "LoadBalancer.ContainerPort should match")
			assert.Equal(t, role, aws.StringValue(observedRole), "Role should match")
		},
		ecsSettingDisabled,
	)
}

func TestCreateWithHealthCheckGracePeriodAndELB(t *testing.T) {
	loadbalancerName := "loadbalancerName"
	containerName := "containerName"
	containerPort := 80
	role := "role"
	healthCheckGP := 60

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.LoadBalancerNameFlag, loadbalancerName, "")
	flagSet.String(flags.ContainerNameFlag, containerName, "")
	flagSet.String(flags.ContainerPortFlag, strconv.Itoa(containerPort), "")
	flagSet.String(flags.RoleFlag, role, "")
	flagSet.String(flags.HealthCheckGracePeriodFlag, strconv.Itoa(healthCheckGP), "")

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			loadBalancer := input.LoadBalancers[0]
			observedRole := input.Role
			healthCheckGracePeriod := input.HealthCheckGracePeriodSeconds

			assert.NotNil(t, loadBalancer, "LoadBalancer should not be nil")
			assert.Nil(t, loadBalancer.TargetGroupArn, "LoadBalancer.TargetGroupArn should be nil")
			assert.Equal(t, loadbalancerName, aws.StringValue(loadBalancer.LoadBalancerName), "LoadBalancer.LoadBalancerName should match")
			assert.Equal(t, containerName, aws.StringValue(loadBalancer.ContainerName), "LoadBalancer.ContainerName should match")
			assert.Equal(t, int64(containerPort), aws.Int64Value(loadBalancer.ContainerPort), "LoadBalancer.ContainerPort should match")
			assert.Equal(t, role, aws.StringValue(observedRole), "Role should match")
			assert.Equal(t, int64(healthCheckGP), *healthCheckGracePeriod, "HealthCheckGracePeriod should match")
		},
		ecsSettingDisabled,
	)
}

func TestDelayedServiceCreate(t *testing.T) {
	// define test flag set
	timeoutFlagValue := 1

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.ComposeServiceTimeOutFlag, strconv.Itoa(timeoutFlagValue), "")
	cliContext := cli.NewContext(nil, flagSet, nil)

	// call tests
	createNewServiceWithDelay(t, cliContext, &config.CommandConfig{}, &utils.ECSParams{})
}

func TestCreateWithServiceDiscovery(t *testing.T) {
	sdsARN := "arn:aws:servicediscovery:eu-west-1:11111111111:service/srv-clydelovespudding"

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.Bool(flags.EnableServiceDiscoveryFlag, true, "")

	// Reset mockable function after test
	nonMockedServicediscoveryCreate := servicediscoveryCreate
	defer func() { servicediscoveryCreate = nonMockedServicediscoveryCreate }()

	servicediscoveryCreate = func(networkMode, serviceName string, c *context.ECSContext) (*ecs.ServiceRegistry, error) {
		return &ecs.ServiceRegistry{
			RegistryArn: aws.String(sdsARN),
		}, nil
	}

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			actualServiceRegistries := input.ServiceRegistries
			assert.Len(t, actualServiceRegistries, 1, "Expected a single Service Registry")
			assert.Equal(t, sdsARN, aws.StringValue(actualServiceRegistries[0].RegistryArn), "Service Registry should match")
		},
		ecsSettingDisabled,
	)
}

func TestCreateWithServiceDiscoveryWithContainerNameAndPort(t *testing.T) {
	sdsARN := "arn:aws:servicediscovery:eu-west-1:11111111111:service/srv-clydelovespudding"
	containerName := "nginx"

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.Bool(flags.EnableServiceDiscoveryFlag, true, "")

	// Reset mockable function after test
	nonMockedServicediscoveryCreate := servicediscoveryCreate
	defer func() { servicediscoveryCreate = nonMockedServicediscoveryCreate }()

	servicediscoveryCreate = func(networkMode, serviceName string, c *context.ECSContext) (*ecs.ServiceRegistry, error) {
		return &ecs.ServiceRegistry{
			RegistryArn:   aws.String(sdsARN),
			ContainerName: aws.String(containerName),
			ContainerPort: aws.Int64(80),
		}, nil
	}

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			actualServiceRegistries := input.ServiceRegistries
			assert.Len(t, actualServiceRegistries, 1, "Expected a single Service Registry")
			assert.Equal(t, int64(80), aws.Int64Value(actualServiceRegistries[0].ContainerPort), "Expected container port to be 80")
			assert.Equal(t, containerName, aws.StringValue(actualServiceRegistries[0].ContainerName), "Expected ContainerName to match")
			assert.Equal(t, sdsARN, aws.StringValue(actualServiceRegistries[0].RegistryArn), "Service Registry should match")
		},
		ecsSettingDisabled,
	)
}

func TestCreateWithSchedulingStrategyWithDaemon(t *testing.T) {
	schedulingStrategy := ecs.SchedulingStrategyDaemon

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.SchedulingStrategyFlag, schedulingStrategy, "")

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			actual := input
			assert.NotNil(t, actual.SchedulingStrategy, "SchedulingStrategy should not be nil")
			assert.Equal(t, schedulingStrategy, aws.StringValue(actual.SchedulingStrategy), "SchedulingStrategy should match")
			assert.Nil(t, actual.DesiredCount, "DesiredCount should be nil")
		},
		ecsSettingDisabled,
	)
}

func TestCreateWithSchedulingStrategyWithReplica(t *testing.T) {
	schedulingStrategy := ecs.SchedulingStrategyReplica

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.SchedulingStrategyFlag, schedulingStrategy, "")

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			actual := input
			assert.NotNil(t, actual.SchedulingStrategy, "SchedulingStrategy should not be nil")
			assert.Equal(t, schedulingStrategy, aws.StringValue(actual.SchedulingStrategy), "SchedulingStrategy should match")
			assert.NotNil(t, actual.DesiredCount, "DesiredCount should not be nil")
			assert.Equal(t, int64(0), aws.Int64Value(actual.DesiredCount), "DesiredCount should be zero")
		},
		ecsSettingDisabled,
	)
}

func TestCreateWithSchedulingStrategyWithReplicaLowercase(t *testing.T) {
	schedulingStrategy := ecs.SchedulingStrategyReplica

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.SchedulingStrategyFlag, strings.ToLower(schedulingStrategy), "")

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			actual := input
			assert.NotNil(t, actual.SchedulingStrategy, "SchedulingStrategy should not be nil")
			assert.Equal(t, schedulingStrategy, aws.StringValue(actual.SchedulingStrategy), "SchedulingStrategy should match")
			assert.NotNil(t, actual.DesiredCount, "DesiredCount should not be nil")
			assert.Equal(t, int64(0), aws.Int64Value(actual.DesiredCount), "DesiredCount should be zero")
		},
		ecsSettingDisabled,
	)
}

func TestCreateWithoutSchedulingStrategy(t *testing.T) {
	flagSet := flag.NewFlagSet("ecs-cli-up", 0)

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			actual := input
			assert.Nil(t, actual.SchedulingStrategy, "SchedulingStrategy should be nil")
			assert.NotNil(t, actual.DesiredCount, "DesiredCount should not be nil")
			assert.Equal(t, int64(0), aws.Int64Value(actual.DesiredCount), "DesiredCount should be zero")
		},
		ecsSettingDisabled,
	)
}

func TestCreateWithResourceTags(t *testing.T) {
	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.ResourceTagsFlag, "amy=rory,smith=11", "")

	expectedTags := []*ecs.Tag{
		&ecs.Tag{
			Key:   aws.String("amy"),
			Value: aws.String("rory"),
		},
		&ecs.Tag{
			Key:   aws.String("smith"),
			Value: aws.String("11"),
		},
	}

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			actualTags := input.Tags
			assert.ElementsMatch(t, actualTags, expectedTags, "Expected resource tags to match")
		},
		ecsSettingDisabled,
	)
}

func TestCreateWithECSManagedTags(t *testing.T) {
	flagSet := flag.NewFlagSet("ecs-cli-up", 0)

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			// feature is enabled by default in the CLI when account settings allow tagging
			assert.True(t, aws.BoolValue(input.EnableECSManagedTags), "Expected ECS Managed Tags to be enabled")
		},
		ecsSettingEnabled,
	)
}

func TestCreateWithECSManagedTagsDisabled(t *testing.T) {
	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.Bool(flags.DisableECSManagedTagsFlag, true, "")

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			assert.False(t, aws.BoolValue(input.EnableECSManagedTags), "Expected ECS Managed Tags to be disabled")
		},
		ecsSettingEnabled,
	)
}

// test defaults are set correctly when long ARNs are enabled
func TestCreateWithLongARNsEnabled(t *testing.T) {
	flagSet := flag.NewFlagSet("ecs-cli-up", 0)

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			// features are enabled by default in the CLI when account settings allow tagging
			assert.True(t, aws.BoolValue(input.EnableECSManagedTags), "Expected ECS Managed Tags to be enabled")
			assert.Equal(t, aws.StringValue(input.PropagateTags), ecs.PropagateTagsTaskDefinition, "Expected propogate tags to be set")
		},
		ecsSettingEnabled,
	)
}

// test that values are set correctly when long ARNs are disabled
func TestCreateWithLongARNsDisabled(t *testing.T) {
	flagSet := flag.NewFlagSet("ecs-cli-up", 0)

	createServiceTest(
		t,
		flagSet,
		&config.CommandConfig{},
		&utils.ECSParams{},
		func(input *ecs.CreateServiceInput) {
			// features are enabled by default in the CLI when account settings allow tagging
			assert.False(t, aws.BoolValue(input.EnableECSManagedTags), "Expected ECS Managed Tags to be enabled")
			assert.Nil(t, input.PropagateTags, "Expected propogate tags to be set")
		},
		ecsSettingDisabled,
	)
}

//////////////////////////////////////
// Helpers for CreateService tests //
/////////////////////////////////////
type validateCreateServiceInputField func(*ecs.CreateServiceInput)

func createServiceTest(t *testing.T,
	flagSet *flag.FlagSet,
	commandConfig *config.CommandConfig,
	ecsParams *utils.ECSParams,
	validateInput validateCreateServiceInputField,
	settingsValue string) {

	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	taskDefID := "taskDefinitionId"
	taskDefArn, taskDefinition, registerTaskDefResponse := getTestTaskDef(taskDefID)

	// Mock ECS calls
	mockEcs := mock_ecs.NewMockECSClient(ctrl)
	gomock.InOrder(
		mockEcs.EXPECT().RegisterTaskDefinitionIfNeeded(
			gomock.Any(), // RegisterTaskDefinitionInput
			gomock.Any(), // taskDefinitionCache
		).Do(func(input, cache interface{}) {
			verifyTaskDefinitionInput(t, taskDefinition, input.(*ecs.RegisterTaskDefinitionInput))
		}).Return(&registerTaskDefResponse, nil),

		mockEcs.EXPECT().ListAccountSettings(gomock.Any()).Do(func(input interface{}) {
			req := input.(*ecs.ListAccountSettingsInput)
			assert.True(t, aws.BoolValue(req.EffectiveSettings), "Expected Effective settings to be true")
			assert.Equal(t, ecs.SettingNameTaskLongArnFormat, aws.StringValue(req.Name), "Expected setting name to be service long ARN")
		}).Return(&ecs.ListAccountSettingsOutput{
			Settings: []*ecs.Setting{
				&ecs.Setting{
					Value: aws.String(settingsValue),
					Name:  aws.String(ecs.SettingNameTaskLongArnFormat),
				},
			},
		}, nil),

		mockEcs.EXPECT().CreateService(
			gomock.Any(), // createServiceInput
		).Do(func(input interface{}) {
			req := input.(*ecs.CreateServiceInput)
			validateInput(req) // core test assertion
		}).Return(nil),
	)

	cliContext := cli.NewContext(nil, flagSet, nil)
	context := &context.ECSContext{
		ECSClient:     mockEcs,
		CommandConfig: commandConfig,
		CLIContext:    cliContext,
		ECSParams:     ecsParams,
	}

	service := NewService(context)
	err := service.LoadContext()
	assert.NoError(t, err, "Unexpected error while loading context in create service test")

	service.SetTaskDefinition(&taskDefinition)
	err = service.Create()
	assert.NoError(t, err, "Unexpected error while create")

	// task definition should be set
	assert.Equal(t, taskDefArn, aws.StringValue(service.TaskDefinition().TaskDefinitionArn), "TaskDefArn should match")
}

// helper for createNewServiceWithDelay
func getCreateServiceWithDelayMockClient(t *testing.T,
	ctrl *gomock.Controller,
	taskDefinition ecs.TaskDefinition,
	taskDefID string,
	registerTaskDefResponse ecs.TaskDefinition) *mock_ecs.MockECSClient {

	mockEcs := mock_ecs.NewMockECSClient(ctrl)

	createdService := &ecs.Service{
		TaskDefinition: aws.String("arn/" + taskDefID),
		Status:         aws.String("ACTIVE"),
		DesiredCount:   aws.Int64(0),
		RunningCount:   aws.Int64(0),
		ServiceName:    aws.String("test-created"),
	}
	stableService := &ecs.Service{
		TaskDefinition: aws.String("arn/" + taskDefID),
		Status:         aws.String("ACTIVE"),
		DesiredCount:   aws.Int64(1),
		RunningCount:   aws.Int64(1),
		ServiceName:    aws.String("test-created"),
		Deployments: []*ecs.Deployment{
			{
				DesiredCount: aws.Int64(1),
				RunningCount: aws.Int64(1),
			},
		},
	}
	gomock.InOrder(
		mockEcs.EXPECT().DescribeService(gomock.Any()).Return(getDescribeServiceTestResponse(nil), nil),

		mockEcs.EXPECT().RegisterTaskDefinitionIfNeeded(
			gomock.Any(), // RegisterTaskDefinitionInput
			gomock.Any(), // taskDefinitionCache
		).Do(func(input, cache interface{}) {
			verifyTaskDefinitionInput(t, taskDefinition, input.(*ecs.RegisterTaskDefinitionInput))
		}).Return(&registerTaskDefResponse, nil),

		mockEcs.EXPECT().ListAccountSettings(gomock.Any()).Do(func(input interface{}) {
			req := input.(*ecs.ListAccountSettingsInput)
			assert.True(t, aws.BoolValue(req.EffectiveSettings), "Expected Effective settings to be true")
			assert.Equal(t, ecs.SettingNameTaskLongArnFormat, aws.StringValue(req.Name), "Expected setting name to be service long ARN")
		}).Return(&ecs.ListAccountSettingsOutput{
			Settings: []*ecs.Setting{
				&ecs.Setting{
					Value: aws.String(ecsSettingDisabled),
				},
			},
		}, nil),

		mockEcs.EXPECT().CreateService(
			gomock.Any(), // createServiceInput
		).Do(func(input interface{}) {
			req := input.(*ecs.CreateServiceInput)
			observedTaskDefID := req.TaskDefinition
			assert.Equal(t, taskDefID, aws.StringValue(observedTaskDefID), "Task Definition name should match")
		}).Return(nil),

		mockEcs.EXPECT().DescribeService(gomock.Any()).Return(getDescribeServiceTestResponse(nil), nil),
		mockEcs.EXPECT().DescribeService(gomock.Any()).Return(getDescribeServiceTestResponse(createdService), nil).MaxTimes(2),
		mockEcs.EXPECT().DescribeService(gomock.Any()).Return(getDescribeServiceTestResponse(createdService.SetDeployments([]*ecs.Deployment{&ecs.Deployment{}}).SetDesiredCount(1).SetRunningCount(0)), nil).MaxTimes(2),
		mockEcs.EXPECT().DescribeService(gomock.Any()).Return(getDescribeServiceTestResponse(stableService), nil),
	)
	return mockEcs
}

func createNewServiceWithDelay(t *testing.T,
	cliContext *cli.Context,
	commandConfig *config.CommandConfig,
	ecsParams *utils.ECSParams) {

	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	taskDefID := "newTaskDefinitionId"
	taskDefArn, taskDefinition, registerTaskDefResponse := getTestTaskDef(taskDefID)

	mockEcs := getCreateServiceWithDelayMockClient(t, ctrl, taskDefinition, taskDefID, registerTaskDefResponse)

	ecsContext := &context.ECSContext{
		ECSClient:     mockEcs,
		CommandConfig: commandConfig,
		CLIContext:    cliContext,
		ECSParams:     ecsParams,
	}
	ecsContext.SetProjectName()
	service := NewService(ecsContext)
	err := service.LoadContext()
	assert.NoError(t, err, "Unexpected error while loading context in update service with new task def test")

	service.SetTaskDefinition(&taskDefinition)
	err = service.Up()
	assert.NoError(t, err, "Unexpected error on service up with new task def")

	// task definition should be set
	assert.Equal(t, taskDefArn, aws.StringValue(service.TaskDefinition().TaskDefinitionArn), "TaskDefArn should match")
}

////////////////////////
// LoadContext tests //
///////////////////////
func TestLoadContext(t *testing.T) {
	deploymentMaxPercent := 150

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.DeploymentMaxPercentFlag, strconv.Itoa(deploymentMaxPercent), "")
	cliContext := cli.NewContext(nil, flagSet, nil)
	service := &Service{
		ecsContext: &context.ECSContext{CLIContext: cliContext},
	}

	err := service.LoadContext()
	assert.NoError(t, err, "Unexpected error while loading context in load context test")

	observedDeploymentConfig := service.DeploymentConfig()

	assert.Equal(t, int64(deploymentMaxPercent),
		aws.Int64Value(observedDeploymentConfig.MaximumPercent),
		"DeploymentConfig.MaxPercent should match")
	assert.Nil(t, observedDeploymentConfig.MinimumHealthyPercent,
		"DeploymentConfig.MinimumHealthyPercent should be nil")
}

func TestLoadContextForIncorrectInput(t *testing.T) {
	deploymentMaxPercent := "string"

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.DeploymentMaxPercentFlag, deploymentMaxPercent, "")
	cliContext := cli.NewContext(nil, flagSet, nil)
	service := &Service{
		ecsContext: &context.ECSContext{CLIContext: cliContext},
	}

	err := service.LoadContext()
	assert.Error(t, err, "Expected error to load context when flag is a string but got done")
}

func TestLoadContextForLoadBalancerInputError(t *testing.T) {
	targetGroupArn := "targetGroupArn"
	loadBalancerName := "loadBalancerName"

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.TargetGroupArnFlag, targetGroupArn, "")
	flagSet.String(flags.LoadBalancerNameFlag, loadBalancerName, "")
	cliContext := cli.NewContext(nil, flagSet, nil)
	service := &Service{
		ecsContext: &context.ECSContext{CLIContext: cliContext},
	}

	err := service.LoadContext()
	assert.Error(t, err, "Expected error to load context when flag is a string but got done")
}

/////////////////
// Info tests //
////////////////

func TestServiceInfo(t *testing.T) {
	entity.TestInfo(func(context *context.ECSContext) entity.ProjectEntity {
		return NewService(context)
	}, func(req *ecs.ListTasksInput, projectName string, t *testing.T) {
		assert.Contains(t, aws.StringValue(req.ServiceName), projectName, "ServiceName should contain ProjectName")
		assert.Nil(t, req.StartedBy, "StartedBy should be nil")
	}, t, true, "")
}

////////////////
// Run tests //
///////////////

func TestServiceRun(t *testing.T) {
	service := NewService(&context.ECSContext{})
	err := service.Run(map[string][]string{})
	assert.Error(t, err, "Expected unsupported error")
}

///////////////////////
// Up Service tests //
//////////////////////

type UpdateServiceParams struct {
	serviceName            string
	taskDefinition         string
	count                  *int64
	deploymentConfig       *ecs.DeploymentConfiguration
	networkConfig          *ecs.NetworkConfiguration
	healthCheckGracePeriod *int64
	forceDeployment        bool
}

// For an existing service
func TestUpdateExistingServiceWithForceFlag(t *testing.T) {
	// define test flag set
	forceFlagValue := true

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.Bool(flags.ForceDeploymentFlag, forceFlagValue, "")

	// define existing service
	serviceName := "test-service"
	existingService := &ecs.Service{
		TaskDefinition: aws.String("arn/test-task-def"),
		Status:         aws.String("ACTIVE"),
		DesiredCount:   aws.Int64(0),
		ServiceName:    aws.String(serviceName),
	}

	// define expected client input given the above info
	expectedInput := getDefaultUpdateInput()
	expectedInput.serviceName = serviceName
	expectedInput.forceDeployment = forceFlagValue

	// call tests
	updateServiceTest(t, flagSet, &config.CommandConfig{}, &utils.ECSParams{}, expectedInput, existingService, true)
}

func TestUpdateExistingServiceWithTags(t *testing.T) {
	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.ResourceTagsFlag, "everybodysworkingforthe=weekend", "")

	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	taggingMock := mock_tagging.NewMockClient(ctrl)

	newTaggingClient = func(commandConfig *config.CommandConfig) tagging.Client {
		return taggingMock
	}

	serviceARN := "arn:aws:ecs:region:account-id:service/cluster-name/service-name"

	expectedTags := map[string]*string{
		"everybodysworkingforthe": aws.String("weekend"),
	}

	// define existing service
	serviceName := "test-service"
	existingService := &ecs.Service{
		TaskDefinition: aws.String("arn/test-task-def"),
		Status:         aws.String("ACTIVE"),
		DesiredCount:   aws.Int64(0),
		ServiceName:    aws.String(serviceName),
		ServiceArn:     aws.String(serviceARN),
	}

	gomock.InOrder(
		taggingMock.EXPECT().TagResources(gomock.Any()).Do(func(x interface{}) {
			input := x.(*taggingSDK.TagResourcesInput)
			assert.Equal(t, expectedTags, input.Tags, "Expected tags to match")
			assert.Equal(t, serviceARN, aws.StringValue(input.ResourceARNList[0]))
		}).Return(&taggingSDK.TagResourcesOutput{}, nil),
	)

	// define expected client input given the above info
	expectedInput := getDefaultUpdateInput()
	expectedInput.serviceName = serviceName

	// call tests
	updateServiceTest(t, flagSet, &config.CommandConfig{}, &utils.ECSParams{}, expectedInput, existingService, true)
}

func TestUpdateExistingServiceWithTagsError(t *testing.T) {
	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.ResourceTagsFlag, "everybodysworkingforthe=weekend", "")

	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	taggingMock := mock_tagging.NewMockClient(ctrl)

	newTaggingClient = func(commandConfig *config.CommandConfig) tagging.Client {
		return taggingMock
	}

	serviceARN := "arn:aws:ecs:region:account-id:service/cluster-name/service-name"

	expectedTags := map[string]*string{
		"everybodysworkingforthe": aws.String("weekend"),
	}

	// define existing service
	serviceName := "test-service"
	existingService := &ecs.Service{
		TaskDefinition: aws.String("arn/test-task-def"),
		Status:         aws.String("ACTIVE"),
		DesiredCount:   aws.Int64(0),
		ServiceName:    aws.String(serviceName),
		ServiceArn:     aws.String(serviceARN),
	}

	gomock.InOrder(
		taggingMock.EXPECT().TagResources(gomock.Any()).Do(func(x interface{}) {
			input := x.(*taggingSDK.TagResourcesInput)
			assert.Equal(t, expectedTags, input.Tags, "Expected tags to match")
			assert.Equal(t, serviceARN, aws.StringValue(input.ResourceARNList[0]))
		}).Return(nil, fmt.Errorf("API Error")),
	)

	// define expected client input given the above info
	expectedInput := getDefaultUpdateInput()
	expectedInput.serviceName = serviceName

	// call tests
	updateServiceTest(t, flagSet, &config.CommandConfig{}, &utils.ECSParams{}, expectedInput, existingService, false)
}

func TestUpdateExistingServiceWithTagsFailedResource(t *testing.T) {
	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.ResourceTagsFlag, "everybodysworkingforthe=weekend", "")

	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	taggingMock := mock_tagging.NewMockClient(ctrl)

	newTaggingClient = func(commandConfig *config.CommandConfig) tagging.Client {
		return taggingMock
	}

	serviceARN := "arn:aws:ecs:region:account-id:service/cluster-name/service-name"

	expectedTags := map[string]*string{
		"everybodysworkingforthe": aws.String("weekend"),
	}

	// define existing service
	serviceName := "test-service"
	existingService := &ecs.Service{
		TaskDefinition: aws.String("arn/test-task-def"),
		Status:         aws.String("ACTIVE"),
		DesiredCount:   aws.Int64(0),
		ServiceName:    aws.String(serviceName),
		ServiceArn:     aws.String(serviceARN),
	}

	gomock.InOrder(
		taggingMock.EXPECT().TagResources(gomock.Any()).Do(func(x interface{}) {
			input := x.(*taggingSDK.TagResourcesInput)
			assert.Equal(t, expectedTags, input.Tags, "Expected tags to match")
			assert.Equal(t, serviceARN, aws.StringValue(input.ResourceARNList[0]))
		}).Return(&taggingSDK.TagResourcesOutput{
			FailedResourcesMap: map[string]*taggingSDK.FailureInfo{
				"arn:aws:ecs:region:account-id:service/cluster-name/service-name": &taggingSDK.FailureInfo{
					ErrorMessage: aws.String("Some reason"),
				},
			},
		}, nil),
	)

	// define expected client input given the above info
	expectedInput := getDefaultUpdateInput()
	expectedInput.serviceName = serviceName

	// call tests
	updateServiceTest(t, flagSet, &config.CommandConfig{}, &utils.ECSParams{}, expectedInput, existingService, false)
}

func TestUpdateExistingServiceWithNewDeploymentConfig(t *testing.T) {
	// define test flag set
	deploymentMaxPercent := 200
	deploymentMinPercent := 100

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.DeploymentMaxPercentFlag, strconv.Itoa(deploymentMaxPercent), "")
	flagSet.String(flags.DeploymentMinHealthyPercentFlag, strconv.Itoa(deploymentMinPercent), "")

	// define existing service
	serviceName := "test-service"
	existingService := &ecs.Service{
		TaskDefinition: aws.String("arn/test-task-def"),
		Status:         aws.String("ACTIVE"),
		DesiredCount:   aws.Int64(0),
		ServiceName:    aws.String(serviceName),
	}

	// define expected client input given the above info
	expectedInput := getDefaultUpdateInput()
	expectedInput.serviceName = serviceName
	expectedInput.deploymentConfig = &ecs.DeploymentConfiguration{
		MaximumPercent:        aws.Int64(int64(deploymentMaxPercent)),
		MinimumHealthyPercent: aws.Int64(int64(deploymentMinPercent)),
	}

	// call tests
	updateServiceTest(t, flagSet, &config.CommandConfig{}, &utils.ECSParams{}, expectedInput, existingService, true)
}

func TestUpdateExistingServiceWithNewHCGP(t *testing.T) {
	// define test flag set
	healthCheckGracePeriod := 200

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.HealthCheckGracePeriodFlag, strconv.Itoa(healthCheckGracePeriod), "")

	// define existing service
	serviceName := "test-service"
	existingService := &ecs.Service{
		TaskDefinition: aws.String("arn/test-task-def"),
		Status:         aws.String("ACTIVE"),
		DesiredCount:   aws.Int64(0),
		ServiceName:    aws.String(serviceName),
		LoadBalancers:  []*ecs.LoadBalancer{}, // LB required for HCGP, but not verified before calling client
	}

	// define expected client input given the above info
	expectedInput := getDefaultUpdateInput()
	expectedInput.serviceName = serviceName
	expectedInput.healthCheckGracePeriod = aws.Int64(int64(healthCheckGracePeriod))

	// call tests
	updateServiceTest(t, flagSet, &config.CommandConfig{}, &utils.ECSParams{}, expectedInput, existingService, true)
}

func TestUpdateExistingServiceWithDesiredCountOverOne(t *testing.T) {
	// define test values
	existingDesiredCount := 2

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)

	// define existing service
	serviceName := "test-service"
	existingService := &ecs.Service{
		TaskDefinition: aws.String("arn/test-task-def"),
		Status:         aws.String("ACTIVE"),
		DesiredCount:   aws.Int64(int64(existingDesiredCount)), // existing count > 1
		ServiceName:    aws.String(serviceName),
	}

	// define expected client input given the above info
	expectedInput := getDefaultUpdateInput()
	expectedInput.serviceName = serviceName
	expectedInput.count = aws.Int64(int64(existingDesiredCount))

	// call tests
	updateServiceTest(t, flagSet, &config.CommandConfig{}, &utils.ECSParams{}, expectedInput, existingService, true)
}

func TestUpdateExistingServiceWithDaemonSchedulingStrategy(t *testing.T) {
	// define test values
	schedulingStrategy := ecs.SchedulingStrategyDaemon

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.SchedulingStrategyFlag, strings.ToLower(schedulingStrategy), "")

	// define existing service
	serviceName := "test-service"
	existingService := &ecs.Service{
		TaskDefinition:     aws.String("arn/test-task-def"),
		Status:             aws.String("ACTIVE"),
		SchedulingStrategy: aws.String(schedulingStrategy),
		ServiceName:        aws.String(serviceName),
	}

	// define expected client input given the above info
	expectedInput := getDefaultUpdateInput()
	expectedInput.serviceName = serviceName
	expectedInput.count = nil
	expectedInput.taskDefinition = ""

	// call tests
	updateServiceTest(t, flagSet, &config.CommandConfig{}, &utils.ECSParams{}, expectedInput, existingService, true)
	startServiceTest(t, flagSet, &config.CommandConfig{}, &utils.ECSParams{}, existingService)
}

func TestUpdateExistingServiceWithDaemonSchedulingStrategyFlag(t *testing.T) {
	// define test values
	schedulingStrategy := ecs.SchedulingStrategyDaemon

	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.String(flags.SchedulingStrategyFlag, schedulingStrategy, "")

	// define existing service
	serviceName := "test-service"
	existingService := &ecs.Service{
		TaskDefinition:     aws.String("arn/test-task-def"),
		Status:             aws.String("ACTIVE"),
		SchedulingStrategy: aws.String(ecs.SchedulingStrategyReplica),
		ServiceName:        aws.String(serviceName),
	}

	// call tests
	updateServiceExceptionTest(t, flagSet, &config.CommandConfig{}, &utils.ECSParams{}, existingService)
}

func TestUpdateExistingServiceWithServiceDiscoveryFlag(t *testing.T) {
	flagSet := flag.NewFlagSet("ecs-cli-up", 0)
	flagSet.Bool(flags.EnableServiceDiscoveryFlag, true, "")

	// define existing service
	serviceName := "test-service"
	existingService := &ecs.Service{
		TaskDefinition: aws.String("arn/test-task-def"),
		Status:         aws.String("ACTIVE"),
		ServiceName:    aws.String(serviceName),
	}

	expectedInput := getDefaultUpdateInput()
	expectedInput.serviceName = serviceName
	// call tests
	updateServiceTest(t, flagSet, &config.CommandConfig{}, &utils.ECSParams{}, expectedInput, existingService, true)
}

///////////////////////////////////////
//  Update Service Helper functions  //
///////////////////////////////////////

func getDefaultUpdateInput() UpdateServiceParams {
	return UpdateServiceParams{
		deploymentConfig: &ecs.DeploymentConfiguration{},
		count:            aws.Int64(1),
	}
}

// Tests only existing services, e.g. updateService private method rather than
// the public Up method, which would potentially create a new service if it
// does not already exist.
func updateServiceTest(t *testing.T,
	flagSet *flag.FlagSet,
	commandConfig *config.CommandConfig,
	ecsParams *utils.ECSParams,
	expectedInput UpdateServiceParams,
	existingService *ecs.Service,
	expectSuccess bool) {

	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	taskDefID := entity.GetIdFromArn(existingService.TaskDefinition)
	taskDefArn, taskDefinition, registerTaskDefResponse := getTestTaskDef(taskDefID)

	// Mock ECS calls
	mockEcs := mock_ecs.NewMockECSClient(ctrl)
	describeServiceResponse := getDescribeServiceTestResponse(existingService)
	gomock.InOrder(
		mockEcs.EXPECT().DescribeService(gomock.Any()).Return(describeServiceResponse, nil),

		mockEcs.EXPECT().RegisterTaskDefinitionIfNeeded(
			gomock.Any(), // RegisterTaskDefinitionInput
			gomock.Any(), // taskDefinitionCache
		).Do(func(input, cache interface{}) {
			verifyTaskDefinitionInput(t, taskDefinition, input.(*ecs.RegisterTaskDefinitionInput))
		}).Return(&registerTaskDefResponse, nil),

		mockEcs.EXPECT().UpdateService(
			gomock.Any(), // updateServiceInput
		).Do(func(input interface{}) {
			req := input.(*ecs.UpdateServiceInput)
			observedInput := UpdateServiceParams{
				serviceName:            aws.StringValue(req.Service),
				taskDefinition:         aws.StringValue(req.TaskDefinition),
				count:                  req.DesiredCount,
				deploymentConfig:       req.DeploymentConfiguration,
				networkConfig:          req.NetworkConfiguration,
				healthCheckGracePeriod: req.HealthCheckGracePeriodSeconds,
				forceDeployment:        aws.BoolValue(req.ForceNewDeployment),
			}
			assert.Equal(t, expectedInput, observedInput)

		}).Return(nil),
	)

	cliContext := cli.NewContext(nil, flagSet, nil)
	ecsContext := &context.ECSContext{
		ECSClient:     mockEcs,
		CommandConfig: commandConfig,
		CLIContext:    cliContext,
		ECSParams:     ecsParams,
	}

	ecsContext.ProjectName = *existingService.ServiceName
	service := NewService(ecsContext)
	err := service.LoadContext()
	assert.NoError(t, err, "Unexpected error while loading context in update service with current task def test")

	service.SetTaskDefinition(&taskDefinition)
	err = service.Up()
	if expectSuccess {
		assert.NoError(t, err, "Unexpected error on service up with current task def")
	} else {
		assert.Error(t, err, "Expected error on service up with current task def")
	}

	// task definition should be set
	assert.Equal(t, taskDefArn, aws.StringValue(service.TaskDefinition().TaskDefinitionArn), "TaskDefArn should match")
}

func updateServiceExceptionTest(t *testing.T,
	flagSet *flag.FlagSet,
	commandConfig *config.CommandConfig,
	ecsParams *utils.ECSParams,
	existingService *ecs.Service) {

	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	taskDefID := entity.GetIdFromArn(existingService.TaskDefinition)
	_, taskDefinition, registerTaskDefResponse := getTestTaskDef(taskDefID)

	// Mock ECS calls
	mockEcs := mock_ecs.NewMockECSClient(ctrl)
	describeServiceResponse := getDescribeServiceTestResponse(existingService)
	gomock.InOrder(
		mockEcs.EXPECT().DescribeService(gomock.Any()).Return(describeServiceResponse, nil),
		mockEcs.EXPECT().RegisterTaskDefinitionIfNeeded(
			gomock.Any(), // RegisterTaskDefinitionInput
			gomock.Any(), // taskDefinitionCache
		).Do(func(input, cache interface{}) {
			verifyTaskDefinitionInput(t, taskDefinition, input.(*ecs.RegisterTaskDefinitionInput))
		}).Return(&registerTaskDefResponse, nil),
	)

	cliContext := cli.NewContext(nil, flagSet, nil)
	ecsContext := &context.ECSContext{
		ECSClient:     mockEcs,
		CommandConfig: commandConfig,
		CLIContext:    cliContext,
		ECSParams:     ecsParams,
	}

	ecsContext.ProjectName = *existingService.ServiceName
	service := NewService(ecsContext)
	err := service.LoadContext()
	assert.NoError(t, err, "Unexpected error while loading context in update service with current task def test")

	service.SetTaskDefinition(&taskDefinition)
	err = service.Up()
	assert.Error(t, err, "Expected error when updating service")
}

func startServiceTest(t *testing.T,
	flagSet *flag.FlagSet,
	commandConfig *config.CommandConfig,
	ecsParams *utils.ECSParams,
	existingService *ecs.Service) {

	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	taskDefID := entity.GetIdFromArn(existingService.TaskDefinition)
	_, taskDefinition, _ := getTestTaskDef(taskDefID)

	// Mock ECS calls
	mockEcs := mock_ecs.NewMockECSClient(ctrl)
	describeServiceResponse := getDescribeServiceTestResponse(existingService)
	gomock.InOrder(
		mockEcs.EXPECT().DescribeService(gomock.Any()).Return(describeServiceResponse, nil),
	)

	cliContext := cli.NewContext(nil, flagSet, nil)
	ecsContext := &context.ECSContext{
		ECSClient:     mockEcs,
		CommandConfig: commandConfig,
		CLIContext:    cliContext,
		ECSParams:     ecsParams,
	}

	ecsContext.ProjectName = *existingService.ServiceName
	service := NewService(ecsContext)
	err := service.LoadContext()
	assert.NoError(t, err, "Unexpected error while loading context in update service with current task def test")

	service.SetTaskDefinition(&taskDefinition)
	err = service.Start()
	assert.NoError(t, err, "Unexpected error on service start with current task def")

	assert.Equal(t, "", aws.StringValue(service.TaskDefinition().TaskDefinitionArn), "TaskDefArn should be blank")
}

func getDescribeServiceTestResponse(existingService *ecs.Service) *ecs.DescribeServicesOutput {
	if existingService != nil {
		return &ecs.DescribeServicesOutput{
			Failures: []*ecs.Failure{},
			Services: []*ecs.Service{existingService},
		}
	}

	// otherwise service does not exist; return empty response
	return &ecs.DescribeServicesOutput{
		Failures: []*ecs.Failure{
			&ecs.Failure{
				Reason: aws.String("MISSING"),
				Arn:    aws.String("arn:missing-service"),
			},
		},
		Services: []*ecs.Service{},
	}
}

func getTestTaskDef(taskDefID string) (taskDefArn string, taskDefinition, registerTaskDefResponse ecs.TaskDefinition) {
	taskDefArn = arnPrefix + taskDefID
	taskDefinition = ecs.TaskDefinition{
		Family:               aws.String(taskDefID),
		ContainerDefinitions: []*ecs.ContainerDefinition{},
		Volumes:              []*ecs.Volume{},
	}
	registerTaskDefResponse = taskDefinition
	registerTaskDefResponse.TaskDefinitionArn = aws.String(taskDefArn)

	return
}

func verifyTaskDefinitionInput(t *testing.T,
	taskDef ecs.TaskDefinition,
	regInput *ecs.RegisterTaskDefinitionInput) {
	assert.Equal(t, aws.StringValue(taskDef.Family), aws.StringValue(regInput.Family), "Task Definition family should match")
}
