// Copyright 2015-2018 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 docker

import (
	"errors"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"

	"github.com/aws/amazon-ecs-init/ecs-init/config"
	"github.com/aws/amazon-ecs-init/ecs-init/gpu"
	godocker "github.com/fsouza/go-dockerclient"
	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"
)

// expectedAgentBinds is the total number of agent host config binds.
// Note: Change this value every time when a new bind mount is added to
// agent for the tests to pass
const (
	testTempDirPrefix = "init-docker-test-"

	expectedAgentBindsUnspecifiedPlatform = 20
	expectedAgentBindsSuseUbuntuPlatform  = 18
)

var expectedAgentBinds = expectedAgentBindsUnspecifiedPlatform

func TestIsAgentImageLoadedListFailure(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	mockDocker := NewMockdockerclient(mockCtrl)

	mockDocker.EXPECT().ListImages(godocker.ListImagesOptions{All: true}).Return(nil, errors.New("test error"))

	client := &client{
		docker: mockDocker,
	}
	loaded, err := client.IsAgentImageLoaded()
	assert.Error(t, err, "error should be returned when list image fails")
	assert.False(t, loaded, "IsImageLoaded should return false if list image fails")
}

func TestIsAgentImageLoadedNoMatches(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	mockDocker := NewMockdockerclient(mockCtrl)

	mockDocker.EXPECT().ListImages(godocker.ListImagesOptions{All: true}).Return(
		append(make([]godocker.APIImages, 0), godocker.APIImages{
			RepoTags: append(make([]string, 0), ""),
		}), nil)

	client := &client{
		docker: mockDocker,
	}
	loaded, err := client.IsAgentImageLoaded()
	assert.NoError(t, err, "error should not be returned when no images match")
	assert.False(t, loaded, "IsImageLoaded should return false if there are no matches")
}

func TestIsImageLoadedMatches(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	mockDocker := NewMockdockerclient(mockCtrl)

	mockDocker.EXPECT().ListImages(godocker.ListImagesOptions{All: true}).Return(
		append(make([]godocker.APIImages, 0), godocker.APIImages{
			RepoTags: append(make([]string, 0), config.AgentImageName),
		}), nil)

	client := &client{
		docker: mockDocker,
	}
	loaded, err := client.IsAgentImageLoaded()
	assert.NoError(t, err, "error should not be returned when image match is found")
	assert.True(t, loaded, "IsImageLoaded should return true if there is a match")
}

func TestLoadImage(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	mockDocker := NewMockdockerclient(mockCtrl)

	mockDocker.EXPECT().LoadImage(godocker.LoadImageOptions{})

	client := &client{
		docker: mockDocker,
	}
	err := client.LoadImage(nil)
	assert.NoError(t, err, "no errors should be returned on load image with nil image")
}

func TestRemoveExistingAgentContainerListContainersFailure(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	mockDocker := NewMockdockerclient(mockCtrl)

	mockDocker.EXPECT().ListContainers(godocker.ListContainersOptions{
		All: true,
		Filters: map[string][]string{
			"status": []string{},
		},
	}).Return(nil, errors.New("test error"))

	client := &client{
		docker: mockDocker,
	}
	err := client.RemoveExistingAgentContainer()
	if err == nil {
		t.Error("Error should be returned")
	}
}

func TestRemoveExistingAgentContainerNoneFound(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	mockDocker := NewMockdockerclient(mockCtrl)

	mockDocker.EXPECT().ListContainers(godocker.ListContainersOptions{
		All: true,
		Filters: map[string][]string{
			"status": []string{},
		},
	})

	client := &client{
		docker: mockDocker,
	}
	err := client.RemoveExistingAgentContainer()
	if err != nil {
		t.Error("Error should not be returned")
	}
}

func TestRemoveExistingAgentContainer(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	mockDocker := NewMockdockerclient(mockCtrl)

	mockDocker.EXPECT().ListContainers(godocker.ListContainersOptions{
		All: true,
		Filters: map[string][]string{
			"status": []string{},
		},
	}).Return([]godocker.APIContainers{
		godocker.APIContainers{
			Names: []string{"/" + config.AgentContainerName},
			ID:    "id",
		},
	}, nil)
	mockDocker.EXPECT().RemoveContainer(godocker.RemoveContainerOptions{
		ID:    "id",
		Force: true,
	})

	client := &client{
		docker: mockDocker,
	}
	err := client.RemoveExistingAgentContainer()
	if err != nil {
		t.Error("Error should not be returned")
	}
}

func TestStartAgentNoEnvFile(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()
	isPathValid = func(path string, isDir bool) bool {
		return false
	}
	defer func() {
		isPathValid = defaultIsPathValid
	}()

	containerID := "container id"

	mockFS := NewMockfileSystem(mockCtrl)
	mockDocker := NewMockdockerclient(mockCtrl)

	mockFS.EXPECT().ReadFile(config.InstanceConfigFile()).Return(nil, errors.New("not found")).AnyTimes()
	mockFS.EXPECT().ReadFile(config.AgentConfigFile()).Return(nil, errors.New("test error")).AnyTimes()
	mockDocker.EXPECT().CreateContainer(gomock.Any()).Do(func(opts godocker.CreateContainerOptions) {
		validateCommonCreateContainerOptions(opts, t)
	}).Return(&godocker.Container{
		ID: containerID,
	}, nil)
	mockDocker.EXPECT().StartContainer(containerID, nil)
	mockDocker.EXPECT().WaitContainer(containerID)

	client := &client{
		docker: mockDocker,
		fs:     mockFS,
	}

	_, err := client.StartAgent()
	if err != nil {
		t.Error("Error should not be returned")
	}
}

func validateCommonCreateContainerOptions(opts godocker.CreateContainerOptions, t *testing.T) {
	if opts.Name != "ecs-agent" {
		t.Errorf("Expected container Name to be %s but was %s", "ecs-agent", opts.Name)
	}
	cfg := opts.Config

	if len(cfg.Env) < 3 {
		t.Errorf("Expected at least 3 elements to be in Env, but was %d", len(cfg.Env))
	}
	envVariables := make(map[string]struct{})
	for _, envVar := range cfg.Env {
		envVariables[envVar] = struct{}{}
	}
	expectKey("ECS_DATADIR=/data", envVariables, t)
	expectKey("ECS_LOGFILE=/log/"+config.AgentLogFile, envVariables, t)
	expectKey("ECS_AGENT_CONFIG_FILE_PATH="+config.AgentJSONConfigFile(), envVariables, t)
	expectKey("ECS_UPDATE_DOWNLOAD_DIR="+config.CacheDirectory(), envVariables, t)
	expectKey("ECS_UPDATES_ENABLED=true", envVariables, t)
	expectKey(`ECS_AVAILABLE_LOGGING_DRIVERS=["json-file","syslog","awslogs","fluentd","none"]`,
		envVariables, t)
	expectKey("ECS_ENABLE_TASK_IAM_ROLE=true", envVariables, t)
	expectKey("ECS_ENABLE_TASK_IAM_ROLE_NETWORK_HOST=true", envVariables, t)
	expectKey("ECS_ENABLE_TASK_ENI=true", envVariables, t)
	expectKey("ECS_ENABLE_AWSLOGS_EXECUTIONROLE_OVERRIDE=true", envVariables, t)
	expectKey(`ECS_VOLUME_PLUGIN_CAPABILITIES=["efsAuth"]`, envVariables, t)
	if cfg.Image != config.AgentImageName {
		t.Errorf("Expected image to be %s", config.AgentImageName)
	}

	hostCfg := opts.HostConfig

	// for hosts that do not have cert directories explicity mounted, ignore
	// host cert directory configuration.
	// TODO (adnxn): ideally, these should be behind build flags.
	// https://github.com/aws/amazon-ecs-init/issues/131
	if certDir := config.HostPKIDirPath(); certDir == "" {
		expectedAgentBinds = expectedAgentBindsSuseUbuntuPlatform
	}

	if len(hostCfg.Binds) != expectedAgentBinds {
		t.Errorf("Expected exactly %d elements to be in Binds, but was %d", expectedAgentBinds, len(hostCfg.Binds))
	}
	binds := make(map[string]struct{})
	for _, binding := range hostCfg.Binds {
		binds[binding] = struct{}{}
	}
	defaultDockerSocket, _ := config.DockerUnixSocket()
	expectKey(defaultDockerSocket+":"+defaultDockerSocket, binds, t)
	expectKey(config.LogDirectory()+":/log", binds, t)
	expectKey(config.AgentDataDirectory()+":/data", binds, t)
	expectKey(config.AgentConfigDirectory()+":"+config.AgentConfigDirectory(), binds, t)
	expectKey(config.CacheDirectory()+":"+config.CacheDirectory(), binds, t)
	expectKey(config.ProcFS+":"+hostProcDir+":ro", binds, t)
	expectKey(iptablesUsrLibDir+":"+iptablesUsrLibDir+":ro", binds, t)
	expectKey(iptablesLibDir+":"+iptablesLibDir+":ro", binds, t)
	expectKey(iptablesUsrLib64Dir+":"+iptablesUsrLib64Dir+":ro", binds, t)
	expectKey(iptablesLib64Dir+":"+iptablesLib64Dir+":ro", binds, t)
	expectKey(iptablesExecutableHostDir+":"+iptablesExecutableContainerDir+":ro", binds, t)
	expectKey(iptablesAltDir+":"+iptablesAltDir+":ro", binds, t)
	expectKey(iptablesLegacyDir+":"+iptablesLegacyDir+":ro", binds, t)
	expectKey(config.LogDirectory()+"/exec:/log/exec", binds, t)
	for _, pluginDir := range pluginDirs {
		expectKey(pluginDir+":"+pluginDir+readOnly, binds, t)
	}

	if hostCfg.NetworkMode != networkMode {
		t.Errorf("Expected network mode to be %s, got %s", networkMode, hostCfg.NetworkMode)
	}

	if len(hostCfg.CapAdd) != 3 {
		t.Error("Mismatch detected in added host config capabilities")
	}

	capNetAdminFound := false
	capSysAdminFound := false
	for _, cap := range hostCfg.CapAdd {
		if cap == CapNetAdmin {
			capNetAdminFound = true
		}
		if cap == CapSysAdmin {
			capSysAdminFound = true
		}
	}
	if !capNetAdminFound {
		t.Errorf("Missing %s from host config capabilities", CapNetAdmin)
	}
	if !capSysAdminFound {
		t.Errorf("Missing %s from host config capabilities", CapSysAdmin)
	}

	if hostCfg.Init != true {
		t.Error("Incorrect host config. Expected Init to be true")
	}
}

func expectKey(key string, input map[string]struct{}, t *testing.T) {
	if _, ok := input[key]; !ok {
		t.Errorf("Expected %s to be defined", key)
	}
}

func TestStartAgentEnvFile(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()
	isPathValid = func(path string, isDir bool) bool {
		return false
	}
	defer func() {
		isPathValid = defaultIsPathValid
	}()

	envFile := "\nAGENT_TEST_VAR=val\nAGENT_TEST_VAR2=val2\n"
	containerID := "container id"

	mockFS := NewMockfileSystem(mockCtrl)
	mockDocker := NewMockdockerclient(mockCtrl)

	mockFS.EXPECT().ReadFile(config.InstanceConfigFile()).Return(nil, errors.New("not found")).AnyTimes()
	mockFS.EXPECT().ReadFile(config.AgentConfigFile()).Return([]byte(envFile), nil).AnyTimes()
	mockDocker.EXPECT().CreateContainer(gomock.Any()).Do(func(opts godocker.CreateContainerOptions) {
		validateCommonCreateContainerOptions(opts, t)
		cfg := opts.Config

		envVariables := make(map[string]struct{})
		for _, envVar := range cfg.Env {
			envVariables[envVar] = struct{}{}
		}
		expectKey("AGENT_TEST_VAR=val", envVariables, t)
		expectKey("AGENT_TEST_VAR2=val2", envVariables, t)
	}).Return(&godocker.Container{
		ID: containerID,
	}, nil)
	mockDocker.EXPECT().StartContainer(containerID, nil)
	mockDocker.EXPECT().WaitContainer(containerID)

	client := &client{
		docker: mockDocker,
		fs:     mockFS,
	}

	_, err := client.StartAgent()
	if err != nil {
		t.Error("Error should not be returned")
	}
}
func TestStartAgentWithGPUConfig(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()
	isPathValid = func(path string, isDir bool) bool {
		return false
	}
	defer func() {
		isPathValid = defaultIsPathValid
	}()

	envFile := "\nECS_ENABLE_GPU_SUPPORT=true\n"
	containerID := "container id"
	expectedAgentBinds += 1

	defer func() {
		MatchFilePatternForGPU = FilePatternMatchForGPU
		expectedAgentBinds = expectedAgentBindsUnspecifiedPlatform
	}()
	MatchFilePatternForGPU = func(pattern string) ([]string, error) {
		return []string{"/dev/nvidia0", "/dev/nvidia1"}, nil
	}

	mockFS := NewMockfileSystem(mockCtrl)
	mockDocker := NewMockdockerclient(mockCtrl)

	mockFS.EXPECT().ReadFile(config.InstanceConfigFile()).Return([]byte(envFile), nil).AnyTimes()
	mockFS.EXPECT().ReadFile(config.AgentConfigFile()).Return(nil, errors.New("not found")).AnyTimes()
	mockDocker.EXPECT().CreateContainer(gomock.Any()).Do(func(opts godocker.CreateContainerOptions) {
		validateCommonCreateContainerOptions(opts, t)
		var found bool
		for _, bind := range opts.HostConfig.Binds {
			if bind == gpu.GPUInfoDirPath+":"+gpu.GPUInfoDirPath {
				found = true
				break
			}
		}
		assert.True(t, found)

		cfg := opts.Config

		envVariables := make(map[string]struct{})
		for _, envVar := range cfg.Env {
			envVariables[envVar] = struct{}{}
		}
	}).Return(&godocker.Container{
		ID: containerID,
	}, nil)
	mockDocker.EXPECT().StartContainer(containerID, nil)
	mockDocker.EXPECT().WaitContainer(containerID)

	client := &client{
		docker: mockDocker,
		fs:     mockFS,
	}

	_, err := client.StartAgent()
	assert.NoError(t, err)
}

func TestStartAgentWithGPUConfigNoDevices(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()
	isPathValid = func(path string, isDir bool) bool {
		return false
	}
	defer func() {
		isPathValid = defaultIsPathValid
	}()

	envFile := "\nECS_ENABLE_GPU_SUPPORT=true\n"
	containerID := "container id"

	defer func() {
		MatchFilePatternForGPU = FilePatternMatchForGPU
	}()
	MatchFilePatternForGPU = func(pattern string) ([]string, error) {
		// matches is nil
		return nil, nil
	}

	mockFS := NewMockfileSystem(mockCtrl)
	mockDocker := NewMockdockerclient(mockCtrl)

	mockFS.EXPECT().ReadFile(config.InstanceConfigFile()).Return([]byte(envFile), nil).AnyTimes()
	mockFS.EXPECT().ReadFile(config.AgentConfigFile()).Return(nil, errors.New("not found")).AnyTimes()
	mockDocker.EXPECT().CreateContainer(gomock.Any()).Do(func(opts godocker.CreateContainerOptions) {
		validateCommonCreateContainerOptions(opts, t)
		cfg := opts.Config

		envVariables := make(map[string]struct{})
		for _, envVar := range cfg.Env {
			envVariables[envVar] = struct{}{}
		}
	}).Return(&godocker.Container{
		ID: containerID,
	}, nil)
	mockDocker.EXPECT().StartContainer(containerID, nil)
	mockDocker.EXPECT().WaitContainer(containerID)

	client := &client{
		docker: mockDocker,
		fs:     mockFS,
	}

	_, err := client.StartAgent()
	assert.NoError(t, err)
}

func TestGetContainerConfigWithFileOverrides(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	envFile := "\nECS_UPDATES_ENABLED=false\nAGENT_TEST_VAR2=\"val2\"=val2\n"

	mockFS := NewMockfileSystem(mockCtrl)

	mockFS.EXPECT().ReadFile(config.InstanceConfigFile()).Return(nil, errors.New("not found"))
	mockFS.EXPECT().ReadFile(config.AgentConfigFile()).Return([]byte(envFile), nil)

	client := &client{
		fs: mockFS,
	}
	envVarsFromFiles := client.LoadEnvVars()
	cfg := client.getContainerConfig(envVarsFromFiles)

	envVariables := make(map[string]struct{})
	for _, envVar := range cfg.Env {
		envVariables[envVar] = struct{}{}
	}
	expectKey("ECS_UPDATES_ENABLED=false", envVariables, t)
	expectKey("AGENT_TEST_VAR2=\"val2\"=val2", envVariables, t)
	if _, ok := envVariables["ECS_UPDATES_ENABLED=true"]; ok {
		t.Errorf("Did not expect ECS_UPDATES_ENABLED=true to be defined")
	}

}

func TestGetContainerConfigExternal(t *testing.T) {
	os.Setenv(config.ExternalEnvVar, "true")
	defer os.Unsetenv(config.ExternalEnvVar)

	client := &client{}
	cfg := client.getContainerConfig(map[string]string{})
	assert.Contains(t, cfg.Env, "ECS_ENABLE_TASK_ENI=false")
}

func TestGetInstanceConfig(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	envFile := "\nECS_ENABLE_GPU_SUPPORT=true\n"
	defer func() {
		MatchFilePatternForGPU = FilePatternMatchForGPU
	}()
	MatchFilePatternForGPU = func(pattern string) ([]string, error) {
		// GPU device present
		return []string{"/dev/nvidia0"}, nil
	}

	mockFS := NewMockfileSystem(mockCtrl)

	mockFS.EXPECT().ReadFile(config.InstanceConfigFile()).Return([]byte(envFile), nil)
	mockFS.EXPECT().ReadFile(config.AgentConfigFile()).Return(nil, errors.New("not found"))

	client := &client{
		fs: mockFS,
	}
	envVarsFromFiles := client.LoadEnvVars()
	cfg := client.getContainerConfig(envVarsFromFiles)

	envVariables := make(map[string]struct{})
	for _, envVar := range cfg.Env {
		envVariables[envVar] = struct{}{}
	}
	expectKey("ECS_ENABLE_GPU_SUPPORT=true", envVariables, t)
}

func TestGetNonGPUInstanceConfig(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	envFile := "\nECS_ENABLE_GPU_SUPPORT=true\n"
	defer func() {
		MatchFilePatternForGPU = FilePatternMatchForGPU
	}()
	MatchFilePatternForGPU = func(pattern string) ([]string, error) {
		// matches is nil
		return nil, nil
	}

	mockFS := NewMockfileSystem(mockCtrl)

	mockFS.EXPECT().ReadFile(config.InstanceConfigFile()).Return([]byte(envFile), nil)
	mockFS.EXPECT().ReadFile(config.AgentConfigFile()).Return(nil, errors.New("not found"))

	client := &client{
		fs: mockFS,
	}
	envVarsFromFiles := client.LoadEnvVars()
	cfg := client.getContainerConfig(envVarsFromFiles)

	envVariables := make(map[string]struct{})
	for _, envVar := range cfg.Env {
		envVariables[envVar] = struct{}{}
	}
	if _, ok := envVariables["ECS_ENABLE_GPU_SUPPORT=true"]; ok {
		t.Errorf("Expected ECS_ENABLE_GPU_SUPPORT=true to be not present")
	}
}

func TestGetConfigOverrides(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	instanceEnvFile := "\nECS_ENABLE_GPU_SUPPORT=true\n"
	userEnvFile := "\nECS_ENABLE_GPU_SUPPORT=false\n"

	mockFS := NewMockfileSystem(mockCtrl)

	mockFS.EXPECT().ReadFile(config.InstanceConfigFile()).Return([]byte(instanceEnvFile), nil)
	mockFS.EXPECT().ReadFile(config.AgentConfigFile()).Return([]byte(userEnvFile), nil)

	client := &client{
		fs: mockFS,
	}
	envVarsFromFiles := client.LoadEnvVars()
	cfg := client.getContainerConfig(envVarsFromFiles)

	envVariables := make(map[string]struct{})
	for _, envVar := range cfg.Env {
		envVariables[envVar] = struct{}{}
	}
	expectKey("ECS_ENABLE_GPU_SUPPORT=false", envVariables, t)
}

func TestStopAgent(t *testing.T) {
	testCases := []struct {
		name                 string
		listFailed           bool
		listEmpty            bool
		stopFailedNotRunning bool
		stopFailedOther      bool
		expectedError        bool
	}{
		{
			name: "List containers succeeded, stop agent succeeded",
		},
		{
			name:       "List containers failed",
			listFailed: true,
		},
		{
			name:      "List containers no agent present",
			listEmpty: true,
		},
		{
			name:                 "List containers succeeded, stop agent failed on container not running",
			stopFailedNotRunning: true,
		},
		{
			name:            "List containers succeeded, stop agent failed on error other than not running",
			stopFailedOther: true,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()

			mockDocker := NewMockdockerclient(mockCtrl)
			client := &client{
				docker: mockDocker,
			}

			var listInput godocker.ListContainersOptions
			var listOutput []godocker.APIContainers
			var listErr error
			listInput = godocker.ListContainersOptions{
				All: true,
				Filters: map[string][]string{
					"status": {},
				},
			}
			if tc.listEmpty || tc.listFailed {
				listOutput = []godocker.APIContainers{{}}
			} else {
				listOutput = []godocker.APIContainers{
					{
						Names: []string{"/" + config.AgentContainerName},
						ID:    "id",
					},
				}
			}

			if tc.listFailed {
				listErr = errors.New("test error")
			}

			mockDocker.EXPECT().ListContainers(listInput).Return(listOutput, listErr)

			var stopErr error
			if tc.stopFailedNotRunning {
				stopErr = &godocker.ContainerNotRunning{ID: "id"}
			} else if tc.stopFailedOther {
				stopErr = errors.New("test error")
			}

			if !tc.listEmpty && !tc.listFailed {
				mockDocker.EXPECT().StopContainer("id", uint(10)).Return(stopErr)
			}

			if tc.listFailed || tc.stopFailedOther {
				assert.Error(t, client.StopAgent())
			} else {
				assert.NoError(t, client.StopAgent())
			}
		})
	}
}

func TestContainerLabels(t *testing.T) {
	testData := `{"test.label.1":"value1","test.label.2":"value2"}`
	out, err := generateLabelMap(testData)
	if err != nil {
		t.Logf("Got an error while decoding labels, error: %s", err)
		t.Fail()
	}
	if out["test.label.1"] != "value1" {
		t.Logf("Label did not give the correct value out.")
		t.Fail()
	}

	for key, value := range out {
		t.Logf("Key: %s %T | Value: %s %T", key, key, value, value)
	}
}

func TestContainerLabelsNoData(t *testing.T) {
	tests := []struct {
		name     string
		testData string
	}{
		{
			name:     "Blank",
			testData: "",
		},
		{
			name:     "Empty JSON",
			testData: `{}`,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			cfg := &godocker.Config{}

			setLabels(cfg, test.testData)

			if cfg.Labels != nil {
				t.Logf("Labels are set but we didn't give any. Current labels: %s", cfg.Labels)
				t.Fail()
			}
		})
	}
}

func TestContainerLabelsBadData(t *testing.T) {
	testData := `{"something":[{"test.label.1":"value1"},{"test.label.2":"value2"}]}`
	_, err := generateLabelMap(testData)
	if err == nil {
		t.Logf("Didn't get a error while getting lables on badly formatted data, error: %s", err)
		t.Fail()
	}
}

func TestGetDockerSocketBind(t *testing.T) {
	testCases := []struct {
		name                     string
		dockerHostFromEnv        string
		dockerHostFromConfigFile string
		expectedBind             string
	}{
		{
			name:                     "No Docker host from env",
			dockerHostFromEnv:        "",
			dockerHostFromConfigFile: "dummy",
			expectedBind:             "/var/run:/var/run",
		},
		{
			name:                     "Invalid Docker host from env",
			dockerHostFromEnv:        "invalid",
			dockerHostFromConfigFile: "dummy",
			expectedBind:             "/var/run:/var/run",
		},
		{
			name:                     "Docker host from env, no Docker host from config file",
			dockerHostFromEnv:        "unix:///var/run/docker.sock",
			dockerHostFromConfigFile: "",
			expectedBind:             "/var/run/docker.sock:/var/run/docker.sock",
		},
		{
			name:                     "Docker host from env, invalid Docker host from config file",
			dockerHostFromEnv:        "unix:///var/run/docker.sock",
			dockerHostFromConfigFile: "invalid",
			expectedBind:             "/var/run/docker.sock:/var/run/docker.sock",
		},
		{
			name:                     "Docker host from env, Docker host from config file",
			dockerHostFromEnv:        "unix:///var/run/docker.sock.1",
			dockerHostFromConfigFile: "unix:///var/run/docker.sock.1",
			expectedBind:             "/var/run/docker.sock.1:/var/run/docker.sock.1",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			os.Setenv("DOCKER_HOST", tc.dockerHostFromEnv)
			defer os.Unsetenv("DOCKER_HOST")

			bind := getDockerSocketBind(map[string]string{"DOCKER_HOST": tc.dockerHostFromConfigFile})
			assert.Equal(t, tc.expectedBind, bind)
		})
	}
}

func TestGetHostConfigExternal(t *testing.T) {
	os.Setenv(config.ExternalEnvVar, "true")
	defer os.Unsetenv(config.ExternalEnvVar)

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

	mockFS := NewMockfileSystem(mockCtrl)
	mockFS.EXPECT().ReadFile(config.InstanceConfigFile()).Return(nil, errors.New("not found")).AnyTimes()
	mockFS.EXPECT().ReadFile(config.AgentConfigFile()).Return(nil, errors.New("test error")).AnyTimes()

	credsBind := "/root/.aws:/rotatingcreds:ro"

	client := &client{
		fs: mockFS,
	}
	hostConfig := client.getHostConfig(map[string]string{})
	assert.Contains(t, hostConfig.Binds, credsBind)
	assert.Empty(t, hostConfig.CapAdd)

	os.Setenv(config.ExternalEnvVar, "false")
	hostConfig = client.getHostConfig(map[string]string{})
	assert.NotContains(t, hostConfig.Binds, credsBind)
	assert.NotEmpty(t, hostConfig.CapAdd)
}

func TestStartAgentWithExecBinds(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	containerID := "container id"
	isPathValid = func(path string, isDir bool) bool {
		return true
	}
	hostCapabilityExecResourcesDir := filepath.Join(hostResourcesRootDir, execCapabilityName)
	containerCapabilityExecResourcesDir := filepath.Join(containerResourcesRootDir, execCapabilityName)

	// config
	hostConfigDir := filepath.Join(hostCapabilityExecResourcesDir, execConfigRelativePath)
	containerConfigDir := filepath.Join(containerCapabilityExecResourcesDir, execConfigRelativePath)

	expectedExecBinds := []string{
		hostResourcesRootDir + ":" + containerResourcesRootDir + readOnly,
		hostConfigDir + ":" + containerConfigDir,
	}
	expectedAgentBinds += len(expectedExecBinds)

	// bind mount for the config folder is already included in expectedAgentBinds since it's always added
	expectedExecBinds = append(expectedExecBinds, hostConfigDir+":"+containerConfigDir)
	defer func() {
		expectedAgentBinds = expectedAgentBindsUnspecifiedPlatform
		isPathValid = defaultIsPathValid
	}()

	mockFS := NewMockfileSystem(mockCtrl)
	mockDocker := NewMockdockerclient(mockCtrl)

	mockFS.EXPECT().ReadFile(config.InstanceConfigFile()).Return(nil, errors.New("not found")).AnyTimes()
	mockFS.EXPECT().ReadFile(config.AgentConfigFile()).Return(nil, errors.New("not found")).AnyTimes()
	mockDocker.EXPECT().CreateContainer(gomock.Any()).Do(func(opts godocker.CreateContainerOptions) {
		validateCommonCreateContainerOptions(opts, t)

		// verify that exec binds are added
		assert.Subset(t, opts.HostConfig.Binds, expectedExecBinds)
	}).Return(&godocker.Container{
		ID: containerID,
	}, nil)
	mockDocker.EXPECT().StartContainer(containerID, nil)
	mockDocker.EXPECT().WaitContainer(containerID)

	client := &client{
		docker: mockDocker,
		fs:     mockFS,
	}

	_, err := client.StartAgent()
	assert.NoError(t, err)
}

func TestGetCapabilityExecBinds(t *testing.T) {
	defer func() {
		isPathValid = defaultIsPathValid
	}()
	hostCapabilityExecResourcesDir := filepath.Join(hostResourcesRootDir, execCapabilityName)
	containerCapabilityExecResourcesDir := filepath.Join(containerResourcesRootDir, execCapabilityName)

	// config
	hostConfigDir := filepath.Join(hostCapabilityExecResourcesDir, execConfigRelativePath)
	containerConfigDir := filepath.Join(containerCapabilityExecResourcesDir, execConfigRelativePath)

	testCases := []struct {
		name            string
		testIsPathValid func(string, bool) bool
		expectedBinds   []string
	}{
		{
			name: "all paths valid",
			testIsPathValid: func(path string, isDir bool) bool {
				return true
			},
			expectedBinds: []string{
				hostResourcesRootDir + ":" + containerResourcesRootDir + readOnly,
				hostConfigDir + ":" + containerConfigDir,
			},
		},
		{
			name: "managed-agents path valid, no execute-command",
			testIsPathValid: func(path string, isDir bool) bool {
				return path == hostResourcesRootDir
			},
			expectedBinds: []string{
				hostResourcesRootDir + ":" + containerResourcesRootDir + readOnly,
			},
		},
		{
			name: "no path valid",
			testIsPathValid: func(path string, isDir bool) bool {
				return false
			},
			expectedBinds: []string{},
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			isPathValid = tc.testIsPathValid
			binds := getCapabilityBinds()
			assert.Equal(t, tc.expectedBinds, binds)
		})
	}
}

func TestDefaultIsPathValid(t *testing.T) {
	rootDir, err := ioutil.TempDir(os.TempDir(), testTempDirPrefix)
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(rootDir)

	file, err := ioutil.TempFile(rootDir, "file")
	if err != nil {
		t.Fatal(err)
	}
	notExistingPath := filepath.Join(rootDir, "not-existing")
	testCases := []struct {
		name              string
		path              string
		shouldBeDirectory bool
		expected          bool
	}{
		{
			name:              "return false if directory does not exist",
			path:              notExistingPath,
			shouldBeDirectory: true,
			expected:          false,
		},
		{
			name:              "return false if false does not exist",
			path:              notExistingPath,
			shouldBeDirectory: false,
			expected:          false,
		},
		{
			name:              "if directory exists, return shouldBeDirectory",
			path:              rootDir,
			shouldBeDirectory: true,
			expected:          true,
		},
		{
			name:              "if directory exists, return shouldBeDirectory",
			path:              rootDir,
			shouldBeDirectory: false,
			expected:          false,
		},
		{
			name:              "if file exists, return !shouldBeDirectory",
			path:              file.Name(),
			shouldBeDirectory: false,
			expected:          true,
		},
		{
			name:              "if file exists, return !shouldBeDirectory",
			path:              file.Name(),
			shouldBeDirectory: true,
			expected:          false,
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result := defaultIsPathValid(tc.path, tc.shouldBeDirectory)
			assert.Equal(t, result, tc.expected)
		})
	}
}
