//go:build unit
// +build unit

// Copyright 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 task

import (
	"encoding/json"
	"fmt"
	"runtime"
	"testing"

	apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container"
	"github.com/aws/amazon-ecs-agent/agent/config"
	"github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi"
	mock_dockerapi "github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi/mocks"
	"github.com/aws/amazon-ecs-agent/agent/taskresource"
	taskresourcevolume "github.com/aws/amazon-ecs-agent/agent/taskresource/volume"
	apiresource "github.com/aws/amazon-ecs-agent/ecs-agent/api/attachment/resource"
	apicontainerstatus "github.com/aws/amazon-ecs-agent/ecs-agent/api/container/status"

	"github.com/docker/docker/api/types/volume"
	"github.com/golang/mock/gomock"
	"github.com/pkg/errors"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestMarshalUnmarshalOldTaskVolumes(t *testing.T) {
	taskData := `{"volumes":[{"host":{"sourcePath":"/path"},"name":"1"},{"host":{"hostpath":""}, "name":"2"}]}`

	var out Task
	err := json.Unmarshal([]byte(taskData), &out)
	require.NoError(t, err, "Could not unmarshal task")
	require.Len(t, out.Volumes, 2, "Incorrect number of volumes")

	var v1, v2 TaskVolume

	for _, v := range out.Volumes {
		switch v.Name {
		case "1":
			v1 = v
		case "2":
			v2 = v
		}
	}

	_, ok := v1.Volume.(*taskresourcevolume.FSHostVolume)
	assert.True(t, ok, "Expected v1 to be host volume")
	assert.Equal(t, "/path", v1.Volume.(*taskresourcevolume.FSHostVolume).FSSourcePath, "Unmarshaled v2 didn't match marshalled v2")
	_, ok = v2.Volume.(*taskresourcevolume.LocalDockerVolume)
	assert.True(t, ok, "Expected v2 to be local empty volume")
	assert.Equal(t, "", v2.Volume.Source(), "Expected v2 to have 'sourcepath' work correctly")
}

func TestMarshalTaskVolumesEFS(t *testing.T) {
	task := &Task{
		Arn: "test",
		Volumes: []TaskVolume{
			{
				Name: "1",
				Type: EFSVolumeType,
				Volume: &taskresourcevolume.EFSVolumeConfig{
					AuthConfig: taskresourcevolume.EFSAuthConfig{
						AccessPointId: "fsap-123",
						Iam:           "ENABLED",
					},
					FileSystemID:          "fs-12345",
					RootDirectory:         "/tmp",
					TransitEncryption:     "ENABLED",
					TransitEncryptionPort: 23456,
				},
			},
		},
	}

	marshal, err := json.Marshal(task)
	require.NoError(t, err, "Could not marshal task")
	expectedTaskDef := `{
		"Arn": "test",
		"Family": "",
		"Version": "",
		"ServiceName": "",
		"Containers": null,
		"associations": null,
		"resources": null,
		"volumes": [
		  {
			"efsVolumeConfiguration": {
				"authorizationConfig": {
					"accessPointId": "fsap-123",
					"iam": "ENABLED"
				},
				"fileSystemId": "fs-12345",
				"rootDirectory": "/tmp",
				"transitEncryption": "ENABLED",
				"transitEncryptionPort": 23456,
			 	"dockerVolumeName": ""
			},
			"name": "1",
			"type": "efs"
		  }
		],
		"DesiredStatus": "NONE",
		"KnownStatus": "NONE",
		"KnownTime": "0001-01-01T00:00:00Z",
		"PullStartedAt": "0001-01-01T00:00:00Z",
		"PullStoppedAt": "0001-01-01T00:00:00Z",
		"ExecutionStoppedAt": "0001-01-01T00:00:00Z",
		"SentStatus": "NONE",
		"executionCredentialsID": "",
		"credentialsID": "",
		"ENI": null,
		"AppMesh": null,
		"PlatformFields": %s
	  }`
	if runtime.GOOS == "windows" {
		// windows task defs have a special 'cpu/memory unbounded' field added.
		// see https://github.com/aws/amazon-ecs-agent/pull/1227
		expectedTaskDef = fmt.Sprintf(expectedTaskDef, `{"cpuUnbounded": null, "memoryUnbounded": null}`)
	} else {
		expectedTaskDef = fmt.Sprintf(expectedTaskDef, "{}")
	}
	require.JSONEq(t, expectedTaskDef, string(marshal))
}

func TestUnmarshalTaskVolumesEFS(t *testing.T) {
	taskDef := []byte(`{
		"Arn": "test",
		"Family": "",
		"Version": "",
		"Containers": null,
		"associations": null,
		"resources": null,
		"volumes": [
		  {
			"efsVolumeConfiguration": {
			  "authorizationConfig": {
					"accessPointId": "fsap-123",
					"iam": "ENABLED"
				},
				"fileSystemId": "fs-12345",
				"rootDirectory": "/tmp",
				"transitEncryption": "ENABLED",
				"transitEncryptionPort": 23456,
				"dockerVolumeName": ""
			},
			"name": "1",
			"type": "efs"
		  }
		],
		"DesiredStatus": "NONE",
		"KnownStatus": "NONE",
		"KnownTime": "0001-01-01T00:00:00Z",
		"PullStartedAt": "0001-01-01T00:00:00Z",
		"PullStoppedAt": "0001-01-01T00:00:00Z",
		"ExecutionStoppedAt": "0001-01-01T00:00:00Z",
		"SentStatus": "NONE",
		"executionCredentialsID": "",
		"credentialsID": "",
		"ENI": null,
		"AppMesh": null,
		"PlatformFields": {}
	  }`)
	var task Task
	err := json.Unmarshal(taskDef, &task)
	require.NoError(t, err, "Could not unmarshal task")

	require.Len(t, task.Volumes, 1)
	assert.Equal(t, "efs", task.Volumes[0].Type)
	assert.Equal(t, "1", task.Volumes[0].Name)
	efsConfig, ok := task.Volumes[0].Volume.(*taskresourcevolume.EFSVolumeConfig)
	require.True(t, ok)
	assert.Equal(t, "fsap-123", efsConfig.AuthConfig.AccessPointId)
	assert.Equal(t, "ENABLED", efsConfig.AuthConfig.Iam)
	assert.Equal(t, "fs-12345", efsConfig.FileSystemID)
	assert.Equal(t, "/tmp", efsConfig.RootDirectory)
	assert.Equal(t, "ENABLED", efsConfig.TransitEncryption)
	assert.Equal(t, int64(23456), efsConfig.TransitEncryptionPort)
}

func TestMarshalEBSVolumes(t *testing.T) {
	task := &Task{
		Arn: "test",
		Volumes: []TaskVolume{
			{
				Name: "1",
				Type: apiresource.EBSTaskAttach,
				Volume: &taskresourcevolume.EBSTaskVolumeConfig{
					VolumeId:             "vol-12345",
					VolumeName:           "test-volume",
					VolumeSizeGib:        "10",
					SourceVolumeHostPath: "taskarn_vol-12345",
					DeviceName:           "/dev/nvme1n1",
					FileSystem:           "ext4",
				},
			},
		},
	}

	marshal, err := json.Marshal(task)
	require.NoError(t, err, "could not marshal task")
	expectedTaskDef := `{
		"Arn": "test",
		"Family": "",
		"Version": "",
		"ServiceName": "",
		"Containers": null,
		"associations": null,
		"resources": null,
		"volumes": [
		  {
			"ebsVolumeConfiguration": {
				"volumeId": "vol-12345",
				"volumeName": "test-volume",
				"volumeSizeGib": "10",
				"sourceVolumeHostPath": "taskarn_vol-12345",
				"deviceName": "/dev/nvme1n1",
			 	"fileSystem": "ext4",
				"dockerVolumeName": ""
			},
			"name": "1",
			"type": "amazonebs"
		  }
		],
		"DesiredStatus": "NONE",
		"KnownStatus": "NONE",
		"KnownTime": "0001-01-01T00:00:00Z",
		"PullStartedAt": "0001-01-01T00:00:00Z",
		"PullStoppedAt": "0001-01-01T00:00:00Z",
		"ExecutionStoppedAt": "0001-01-01T00:00:00Z",
		"SentStatus": "NONE",
		"executionCredentialsID": "",
		"credentialsID": "",
		"ENI": null,
		"AppMesh": null,
		"PlatformFields": %s
	  }`
	if runtime.GOOS == "windows" {
		// windows task defs have a special 'cpu/memory unbounded' field added.
		// see https://github.com/aws/amazon-ecs-agent/pull/1227
		expectedTaskDef = fmt.Sprintf(expectedTaskDef, `{"cpuUnbounded": null, "memoryUnbounded": null}`)
	} else {
		expectedTaskDef = fmt.Sprintf(expectedTaskDef, "{}")
	}
	require.JSONEq(t, expectedTaskDef, string(marshal))
}

func TestUnmarshalEBSVolumes(t *testing.T) {
	taskDef := []byte(`{
		"Arn": "test",
		"Family": "",
		"Version": "",
		"Containers": null,
		"associations": null,
		"resources": null,
		"volumes": [
		  {
			"ebsVolumeConfiguration": {
				"volumeId": "vol-12345",
				"volumeName": "test-volume",
				"volumeSizeGib": "10",
				"sourceVolumeHostPath": "taskarn_vol-12345",
				"deviceName": "/dev/nvme1n1",
			 	"fileSystem": "ext4",
				"dockerVolumeName": ""
			},
			"name": "1",
			"type": "amazonebs"
		  }
		],
		"DesiredStatus": "NONE",
		"KnownStatus": "NONE",
		"KnownTime": "0001-01-01T00:00:00Z",
		"PullStartedAt": "0001-01-01T00:00:00Z",
		"PullStoppedAt": "0001-01-01T00:00:00Z",
		"ExecutionStoppedAt": "0001-01-01T00:00:00Z",
		"SentStatus": "NONE",
		"executionCredentialsID": "",
		"credentialsID": "",
		"ENI": null,
		"AppMesh": null,
		"PlatformFields": {}
	  }`)

	testExpectedEBSCfg := &taskresourcevolume.EBSTaskVolumeConfig{
		VolumeId:             "vol-12345",
		VolumeName:           "test-volume",
		VolumeSizeGib:        "10",
		SourceVolumeHostPath: "taskarn_vol-12345",
		DeviceName:           "/dev/nvme1n1",
		FileSystem:           "ext4",
	}

	var task Task

	err := json.Unmarshal(taskDef, &task)
	require.NoError(t, err, "Could not unmarshal task")

	require.Len(t, task.Volumes, 1)
	assert.Equal(t, apiresource.EBSTaskAttach, task.Volumes[0].Type)
	assert.Equal(t, "1", task.Volumes[0].Name)
	ebsConfig, ok := task.Volumes[0].Volume.(*taskresourcevolume.EBSTaskVolumeConfig)
	require.True(t, ok)
	assert.Equal(t, testExpectedEBSCfg, ebsConfig)
}

func TestMarshalUnmarshalTaskVolumes(t *testing.T) {
	task := &Task{
		Arn: "test",
		Volumes: []TaskVolume{
			{Name: "1", Type: HostVolumeType, Volume: &taskresourcevolume.LocalDockerVolume{}},
			{Name: "2", Type: HostVolumeType, Volume: &taskresourcevolume.FSHostVolume{FSSourcePath: "/path"}},
			{Name: "3", Type: DockerVolumeType, Volume: &taskresourcevolume.DockerVolumeConfig{Scope: "task", Driver: "local"}},
			{Name: "4", Type: EFSVolumeType, Volume: &taskresourcevolume.EFSVolumeConfig{FileSystemID: "fs-12345", RootDirectory: "/tmp"}},
		},
	}

	marshal, err := json.Marshal(task)
	require.NoError(t, err, "Could not marshal task")

	var out Task
	err = json.Unmarshal(marshal, &out)
	require.NoError(t, err, "Could not unmarshal task")
	require.Len(t, out.Volumes, 4, "Incorrect number of volumes")

	var v1, v2, v3, v4 TaskVolume

	for _, v := range out.Volumes {
		switch v.Name {
		case "1":
			v1 = v
		case "2":
			v2 = v
		case "3":
			v3 = v
		case "4":
			v4 = v
		}
	}

	_, ok := v1.Volume.(*taskresourcevolume.LocalDockerVolume)
	assert.True(t, ok, "Expected v1 to be local empty volume")
	assert.Equal(t, "", v1.Volume.Source(), "Expected v2 to have 'sourcepath' work correctly")
	_, ok = v2.Volume.(*taskresourcevolume.FSHostVolume)
	assert.True(t, ok, "Expected v2 to be host volume")
	assert.Equal(t, "/path", v2.Volume.(*taskresourcevolume.FSHostVolume).FSSourcePath, "Unmarshaled v2 didn't match marshalled v2")

	dockerVolume, ok := v3.Volume.(*taskresourcevolume.DockerVolumeConfig)
	assert.True(t, ok, "incorrect DockerVolumeConfig type")
	assert.Equal(t, "task", dockerVolume.Scope)
	assert.Equal(t, "local", dockerVolume.Driver)

	efsVolume, ok := v4.Volume.(*taskresourcevolume.EFSVolumeConfig)
	assert.True(t, ok, "incorrect EFSVolumeConfig type")
	assert.Equal(t, "fs-12345", efsVolume.FileSystemID)
	assert.Equal(t, "/tmp", efsVolume.RootDirectory)
}

func TestInitializeLocalDockerVolume(t *testing.T) {
	testTask := &Task{
		ResourcesMapUnsafe: make(map[string][]taskresource.TaskResource),
		Containers: []*apicontainer.Container{
			{
				MountPoints: []apicontainer.MountPoint{
					{
						SourceVolume:  "empty-volume-test",
						ContainerPath: "/ecs",
					},
				},
				TransitionDependenciesMap: make(map[apicontainerstatus.ContainerStatus]apicontainer.TransitionDependencySet),
			},
		},
		Volumes: []TaskVolume{
			{
				Name:   "empty-volume-test",
				Type:   "docker",
				Volume: &taskresourcevolume.LocalDockerVolume{},
			},
		},
	}

	testTask.initializeDockerLocalVolumes(nil, nil)

	assert.Len(t, testTask.ResourcesMapUnsafe, 1, "expect the resource map has an empty volume resource")
	assert.Len(t, testTask.Containers[0].TransitionDependenciesMap, 1, "expect a volume resource as the container dependency")
}

func TestInitializeSharedProvisionedVolume(t *testing.T) {
	sharedVolumeMatchFullConfig := config.BooleanDefaultFalse{Value: config.ExplicitlyEnabled}
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	dockerClient := mock_dockerapi.NewMockDockerClient(ctrl)

	testTask := &Task{
		ResourcesMapUnsafe: make(map[string][]taskresource.TaskResource),
		Containers: []*apicontainer.Container{
			{
				MountPoints: []apicontainer.MountPoint{
					{
						SourceVolume:  "shared-volume-test",
						ContainerPath: "/ecs",
					},
				},
				TransitionDependenciesMap: make(map[apicontainerstatus.ContainerStatus]apicontainer.TransitionDependencySet),
			},
		},
		Volumes: []TaskVolume{
			{
				Name: "shared-volume-test",
				Type: "docker",
				Volume: &taskresourcevolume.DockerVolumeConfig{
					Scope:         "shared",
					Autoprovision: false,
				},
			},
		},
	}

	// Expect the volume already exists on the instance
	dockerClient.EXPECT().InspectVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(dockerapi.SDKVolumeResponse{})
	err := testTask.initializeDockerVolumes(sharedVolumeMatchFullConfig.Enabled(), dockerClient, nil)

	assert.NoError(t, err)
	assert.Len(t, testTask.ResourcesMapUnsafe, 0, "no volume resource should be provisioned by agent")
	assert.Len(t, testTask.Containers[0].TransitionDependenciesMap, 0, "resource already exists")
}

func TestInitializeEFSVolume_UseLocalVolumeDriver(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	dockerClient := mock_dockerapi.NewMockDockerClient(ctrl)

	testTask := getEFSTask()

	cfg := &config.Config{
		AWSRegion: "us-west-1",
	}
	err := testTask.initializeEFSVolumes(cfg, dockerClient, nil)

	assert.NoError(t, err)
	assert.Len(t, testTask.Volumes, 1)

	dockervol, ok := testTask.Volumes[0].Volume.(*taskresourcevolume.DockerVolumeConfig)
	assert.True(t, ok)
	assert.Equal(t, "local", dockervol.Driver)
	assert.Equal(t, "nfs", dockervol.DriverOpts["type"])
	assert.Equal(t, "addr=fs-12345.efs.us-west-1.amazonaws.com,nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport", dockervol.DriverOpts["o"])
	assert.Equal(t, ":/my/root/dir", dockervol.DriverOpts["device"])
}

func TestInitializeEFSVolume_UseECSVolumePlugin(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	dockerClient := mock_dockerapi.NewMockDockerClient(ctrl)

	testTask := getEFSTask()
	testTask.credentialsRelativeURIUnsafe = "/v2/creds-id"

	cfg := &config.Config{
		VolumePluginCapabilities: []string{"efsAuth"},
	}
	err := testTask.initializeEFSVolumes(cfg, dockerClient, nil)

	require.NoError(t, err)
	assert.Len(t, testTask.Volumes, 1)

	dockervol, ok := testTask.Volumes[0].Volume.(*taskresourcevolume.DockerVolumeConfig)
	require.True(t, ok)
	assert.Equal(t, "efs", dockervol.DriverOpts["type"])
	assert.Equal(t, "tls,tlsport=12345,iam,awscredsuri=/v2/creds-id,accesspoint=fsap-123", dockervol.DriverOpts["o"])
	assert.Equal(t, "fs-12345:/my/root/dir", dockervol.DriverOpts["device"])
}

func TestInitializeEFSVolume_WrongVolumeConfig(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	dockerClient := mock_dockerapi.NewMockDockerClient(ctrl)

	testTask := getEFSTask()
	testTask.Volumes[0].Volume = &taskresourcevolume.DockerVolumeConfig{}

	cfg := &config.Config{
		AWSRegion: "us-west-1",
	}
	err := testTask.initializeEFSVolumes(cfg, dockerClient, nil)

	assert.Error(t, err)
}

func TestInitializeEFSVolume_WrongVolumeType(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	dockerClient := mock_dockerapi.NewMockDockerClient(ctrl)

	testTask := getEFSTask()
	testTask.Volumes[0].Type = "docker"

	cfg := &config.Config{
		AWSRegion: "us-west-1",
	}
	err := testTask.initializeEFSVolumes(cfg, dockerClient, nil)

	assert.NoError(t, err)
	err = testTask.initializeDockerVolumes(true, dockerClient, nil)
	assert.Error(t, err)
}

func TestInitializeSharedProvisionedVolumeError(t *testing.T) {
	sharedVolumeMatchFullConfig := config.BooleanDefaultFalse{Value: config.ExplicitlyEnabled}
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	dockerClient := mock_dockerapi.NewMockDockerClient(ctrl)

	testTask := &Task{
		ResourcesMapUnsafe: make(map[string][]taskresource.TaskResource),
		Containers: []*apicontainer.Container{
			{
				MountPoints: []apicontainer.MountPoint{
					{
						SourceVolume:  "shared-volume-test",
						ContainerPath: "/ecs",
					},
				},
				TransitionDependenciesMap: make(map[apicontainerstatus.ContainerStatus]apicontainer.TransitionDependencySet),
			},
		},
		Volumes: []TaskVolume{
			{
				Name: "shared-volume-test",
				Type: "docker",
				Volume: &taskresourcevolume.DockerVolumeConfig{
					Scope:         "shared",
					Autoprovision: false,
				},
			},
		},
	}

	// Expect the volume does not exists on the instance
	dockerClient.EXPECT().InspectVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(dockerapi.SDKVolumeResponse{Error: errors.New("volume not exist")})
	err := testTask.initializeDockerVolumes(sharedVolumeMatchFullConfig.Enabled(), dockerClient, nil)
	assert.Error(t, err, "volume not found for auto-provisioned resource should cause task to fail")
}

func TestInitializeSharedNonProvisionedVolume(t *testing.T) {
	sharedVolumeMatchFullConfig := config.BooleanDefaultFalse{Value: config.ExplicitlyEnabled}
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	dockerClient := mock_dockerapi.NewMockDockerClient(ctrl)

	testTask := &Task{
		ResourcesMapUnsafe: make(map[string][]taskresource.TaskResource),
		Containers: []*apicontainer.Container{
			{
				MountPoints: []apicontainer.MountPoint{
					{
						SourceVolume:  "shared-volume-test",
						ContainerPath: "/ecs",
					},
				},
				TransitionDependenciesMap: make(map[apicontainerstatus.ContainerStatus]apicontainer.TransitionDependencySet),
			},
		},
		Volumes: []TaskVolume{
			{
				Name: "shared-volume-test",
				Type: "docker",
				Volume: &taskresourcevolume.DockerVolumeConfig{
					Scope:         "shared",
					Autoprovision: false,
					Labels:        map[string]string{"test": "test"},
				},
			},
		},
	}

	// Expect the volume already exists on the instance
	dockerClient.EXPECT().InspectVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(dockerapi.SDKVolumeResponse{
		DockerVolume: &volume.Volume{
			Labels: map[string]string{"test": "test"},
		},
	})
	err := testTask.initializeDockerVolumes(sharedVolumeMatchFullConfig.Enabled(), dockerClient, nil)

	assert.NoError(t, err)
	assert.Len(t, testTask.ResourcesMapUnsafe, 0, "no volume resource should be provisioned by agent")
	assert.Len(t, testTask.Containers[0].TransitionDependenciesMap, 0, "resource already exists")
}

func TestInitializeSharedNonProvisionedVolumeValidateNameOnly(t *testing.T) {
	sharedVolumeMatchFullConfig := config.BooleanDefaultFalse{Value: config.ExplicitlyDisabled}

	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	dockerClient := mock_dockerapi.NewMockDockerClient(ctrl)

	testTask := &Task{
		ResourcesMapUnsafe: make(map[string][]taskresource.TaskResource),
		Containers: []*apicontainer.Container{
			{
				MountPoints: []apicontainer.MountPoint{
					{
						SourceVolume:  "shared-volume-test",
						ContainerPath: "/ecs",
					},
				},
				TransitionDependenciesMap: make(map[apicontainerstatus.ContainerStatus]apicontainer.TransitionDependencySet),
			},
		},
		Volumes: []TaskVolume{
			{
				Name: "shared-volume-test",
				Type: "docker",
				Volume: &taskresourcevolume.DockerVolumeConfig{
					Scope:         "shared",
					Autoprovision: true,
					DriverOpts:    map[string]string{"type": "tmpfs"},
					Labels:        map[string]string{"domain": "test"},
				},
			},
		},
	}

	// Expect the volume already exists on the instance
	dockerClient.EXPECT().InspectVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(dockerapi.SDKVolumeResponse{
		DockerVolume: &volume.Volume{
			Options: map[string]string{},
			Labels:  nil,
		},
	})
	err := testTask.initializeDockerVolumes(sharedVolumeMatchFullConfig.Enabled(), dockerClient, nil)

	assert.NoError(t, err)
	assert.Len(t, testTask.ResourcesMapUnsafe, 0, "no volume resource should be provisioned by agent")
	assert.Len(t, testTask.Containers[0].TransitionDependenciesMap, 0, "resource already exists")
}

func TestInitializeSharedAutoprovisionVolumeNotFoundError(t *testing.T) {
	sharedVolumeMatchFullConfig := config.BooleanDefaultFalse{Value: config.ExplicitlyEnabled}
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	dockerClient := mock_dockerapi.NewMockDockerClient(ctrl)

	testTask := &Task{
		ResourcesMapUnsafe: make(map[string][]taskresource.TaskResource),
		Containers: []*apicontainer.Container{
			{
				MountPoints: []apicontainer.MountPoint{
					{
						SourceVolume:  "shared-volume-test",
						ContainerPath: "/ecs",
					},
				},
				TransitionDependenciesMap: make(map[apicontainerstatus.ContainerStatus]apicontainer.TransitionDependencySet),
			},
		},
		Volumes: []TaskVolume{
			{
				Name: "shared-volume-test",
				Type: "docker",
				Volume: &taskresourcevolume.DockerVolumeConfig{
					Scope:         "shared",
					Autoprovision: true,
				},
			},
		},
	}

	dockerClient.EXPECT().InspectVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(dockerapi.SDKVolumeResponse{Error: errors.New("not found")})
	err := testTask.initializeDockerVolumes(sharedVolumeMatchFullConfig.Enabled(), dockerClient, nil)
	assert.NoError(t, err)
	assert.Len(t, testTask.ResourcesMapUnsafe, 1, "volume resource should be provisioned by agent")
	assert.Len(t, testTask.Containers[0].TransitionDependenciesMap, 1, "volume resource should be in the container dependency map")
}

func TestInitializeSharedAutoprovisionVolumeNotMatchError(t *testing.T) {
	sharedVolumeMatchFullConfig := config.BooleanDefaultFalse{Value: config.ExplicitlyEnabled}
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	dockerClient := mock_dockerapi.NewMockDockerClient(ctrl)

	testTask := &Task{
		ResourcesMapUnsafe: make(map[string][]taskresource.TaskResource),
		Containers: []*apicontainer.Container{
			{
				MountPoints: []apicontainer.MountPoint{
					{
						SourceVolume:  "shared-volume-test",
						ContainerPath: "/ecs",
					},
				},
				TransitionDependenciesMap: make(map[apicontainerstatus.ContainerStatus]apicontainer.TransitionDependencySet),
			},
		},
		Volumes: []TaskVolume{
			{
				Name: "shared-volume-test",
				Type: "docker",
				Volume: &taskresourcevolume.DockerVolumeConfig{
					Scope:         "shared",
					Autoprovision: true,
				},
			},
		},
	}

	dockerClient.EXPECT().InspectVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(dockerapi.SDKVolumeResponse{
		DockerVolume: &volume.Volume{
			Labels: map[string]string{"test": "test"},
		},
	})
	err := testTask.initializeDockerVolumes(sharedVolumeMatchFullConfig.Enabled(), dockerClient, nil)
	assert.Error(t, err, "volume resource details not match should cause task fail")
}

func TestInitializeSharedAutoprovisionVolumeTimeout(t *testing.T) {
	sharedVolumeMatchFullConfig := config.BooleanDefaultFalse{Value: config.ExplicitlyEnabled}
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	dockerClient := mock_dockerapi.NewMockDockerClient(ctrl)

	testTask := &Task{
		ResourcesMapUnsafe: make(map[string][]taskresource.TaskResource),
		Containers: []*apicontainer.Container{
			{
				MountPoints: []apicontainer.MountPoint{
					{
						SourceVolume:  "shared-volume-test",
						ContainerPath: "/ecs",
					},
				},
				TransitionDependenciesMap: make(map[apicontainerstatus.ContainerStatus]apicontainer.TransitionDependencySet),
			},
		},
		Volumes: []TaskVolume{
			{
				Name: "shared-volume-test",
				Type: "docker",
				Volume: &taskresourcevolume.DockerVolumeConfig{
					Scope:         "shared",
					Autoprovision: true,
				},
			},
		},
	}

	dockerClient.EXPECT().InspectVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(dockerapi.SDKVolumeResponse{
		Error: &dockerapi.DockerTimeoutError{},
	})
	err := testTask.initializeDockerVolumes(sharedVolumeMatchFullConfig.Enabled(), dockerClient, nil)
	assert.Error(t, err, "volume resource details not match should cause task fail")
}

func TestInitializeTaskVolume(t *testing.T) {
	sharedVolumeMatchFullConfig := config.BooleanDefaultFalse{Value: config.ExplicitlyEnabled}
	testTask := &Task{
		ResourcesMapUnsafe: make(map[string][]taskresource.TaskResource),
		Containers: []*apicontainer.Container{
			{
				MountPoints: []apicontainer.MountPoint{
					{
						SourceVolume:  "task-volume-test",
						ContainerPath: "/ecs",
					},
				},
				TransitionDependenciesMap: make(map[apicontainerstatus.ContainerStatus]apicontainer.TransitionDependencySet),
			},
		},
		Volumes: []TaskVolume{
			{
				Name: "task-volume-test",
				Type: "docker",
				Volume: &taskresourcevolume.DockerVolumeConfig{
					Scope: "task",
				},
			},
		},
	}

	err := testTask.initializeDockerVolumes(sharedVolumeMatchFullConfig.Enabled(), nil, nil)
	assert.NoError(t, err)
	assert.Len(t, testTask.ResourcesMapUnsafe, 1, "expect the resource map has an empty volume resource")
	assert.Len(t, testTask.Containers[0].TransitionDependenciesMap, 1, "expect a volume resource as the container dependency")
}

func TestGetEFSVolumeDriverName(t *testing.T) {
	cfg := &config.Config{
		VolumePluginCapabilities: []string{"efsAuth"},
	}
	assert.Equal(t, "amazon-ecs-volume-plugin", getEFSVolumeDriverName(cfg))
	assert.Equal(t, "local", getEFSVolumeDriverName(&config.Config{}))
}

func TestSetPausePIDInVolumeResources(t *testing.T) {
	task := getEFSTask()
	volRes := &taskresourcevolume.VolumeResource{}
	task.AddResource("dockerVolume", volRes)
	task.SetPausePIDInVolumeResources("pid")
	assert.Equal(t, "pid", volRes.GetPauseContainerPID())
}
