//go: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 serviceconnect

import (
	"testing"

	"github.com/aws/amazon-ecs-agent/ecs-agent/acs/model/ecsacs"
	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/stretchr/testify/assert"
)

var (
	testTaskContainers = []*ecsacs.Container{
		{
			Name: aws.String(testServiceConnectContainerName),
		},
		{
			Name: aws.String("testTaskFirstContainerName"),
		},
		{
			Name: aws.String("testTaskSecondContainerName"),
		},
	}
)

// getTestServiceConnectConfig returns *Config based on given parameters.
func getTestServiceConnectConfig(
	scContainerName string,
	scEgressConfig *EgressConfig,
	scDnsConfig []DNSConfigEntry,
	scIngressConfig []IngressConfigEntry) *Config {
	return &Config{
		ContainerName: scContainerName,
		IngressConfig: scIngressConfig,
		EgressConfig:  scEgressConfig,
		DNSConfig:     scDnsConfig,
	}
}

// getTestEgressConfig returns *EgressConfig based on given parameters.
func getTestEgressConfig(scListenerName, scIPv4Cidr, scIPv6Cidr string) *EgressConfig {
	return &EgressConfig{
		ListenerName: scListenerName,
		VIP: VIP{
			IPV4CIDR: scIPv4Cidr,
			IPV6CIDR: scIPv6Cidr,
		},
	}
}

// getTestDnsConfig returns DNSConfigEntry based on given parameters.
func getTestDnsConfigEntry(scHostname, scAddress string) DNSConfigEntry {
	return DNSConfigEntry{
		HostName: testHostName,
		Address:  scAddress,
	}
}

// getTestIngressConfigEntry returns IngressConfigEntry based on given parameters.
func getTestIngressConfigEntry(networkMode, scListenerName string,
	override bool,
	scListenerPort uint16,
	scInterceptPort, scHostPort *uint16) IngressConfigEntry {
	var entry IngressConfigEntry
	switch networkMode {
	case AWSVPCNetworkMode:
		if override {
			// awsvpc override case has listener port(s) in the ingress config
			entry = IngressConfigEntry{
				ListenerPort: scListenerPort,
			}
		} else {
			// awsvpc default case has intercept port(s) and listener name(s) in the ingress config
			entry = IngressConfigEntry{
				ListenerName:  scListenerName,
				InterceptPort: scInterceptPort,
			}
		}
	case BridgeNetworkMode:
		if override {
			// bridge override case has listener port(s) and host port(s) in the ingress config
			entry = IngressConfigEntry{
				ListenerPort: scListenerPort,
				HostPort:     scHostPort,
			}
		} else {
			// bridge default case has listener port(s) in the ingress config
			entry = IngressConfigEntry{
				ListenerPort: scListenerPort,
			}
		}
	}
	return entry
}
func TestValidateServiceConnectConfig(t *testing.T) {
	tt := []struct {
		testName               string
		testNetworkMode        string
		testIsIPv6Enabled      bool
		testEgressConfig       *EgressConfig
		testDnsConfigEntry     DNSConfigEntry
		testIngressConfigEntry IngressConfigEntry
	}{
		{
			testName:               "AWSVPC default case",
			testNetworkMode:        AWSVPCNetworkMode,
			testIsIPv6Enabled:      false,
			testEgressConfig:       getTestEgressConfig(testOutboundListenerName, testIPv4Cidr, ""),
			testDnsConfigEntry:     getTestDnsConfigEntry(testHostName, testIPv4Address),
			testIngressConfigEntry: getTestIngressConfigEntry(AWSVPCNetworkMode, testInboundListenerName, false, uint16(0), testAwsVpcDefaultInterceptPort, aws.Uint16(0)),
		},
		{
			testName:               "AWSVPC default case with IPv6 enabled",
			testNetworkMode:        AWSVPCNetworkMode,
			testIsIPv6Enabled:      true,
			testEgressConfig:       getTestEgressConfig(testOutboundListenerName, testIPv4Cidr, testIPv6Cidr),
			testDnsConfigEntry:     getTestDnsConfigEntry(testHostName, testIPv6Address),
			testIngressConfigEntry: getTestIngressConfigEntry(AWSVPCNetworkMode, testInboundListenerName, false, uint16(0), testAwsVpcDefaultInterceptPort, aws.Uint16(0)),
		},
		{
			testName:               "AWSVPC override case",
			testNetworkMode:        AWSVPCNetworkMode,
			testIsIPv6Enabled:      false,
			testEgressConfig:       getTestEgressConfig(testOutboundListenerName, testIPv4Cidr, ""),
			testDnsConfigEntry:     getTestDnsConfigEntry(testHostName, testIPv4Address),
			testIngressConfigEntry: getTestIngressConfigEntry(AWSVPCNetworkMode, "", true, testListenerPort, aws.Uint16(0), aws.Uint16(0)),
		},
		{
			testName:               "AWSVPC override case with IPv6 enabled",
			testNetworkMode:        AWSVPCNetworkMode,
			testIsIPv6Enabled:      true,
			testEgressConfig:       getTestEgressConfig(testOutboundListenerName, testIPv4Cidr, testIPv6Cidr),
			testDnsConfigEntry:     getTestDnsConfigEntry(testHostName, testIPv6Address),
			testIngressConfigEntry: getTestIngressConfigEntry(AWSVPCNetworkMode, "", true, testListenerPort, aws.Uint16(0), aws.Uint16(0)),
		},
		{
			testName:               "Bridge default case",
			testNetworkMode:        BridgeNetworkMode,
			testIsIPv6Enabled:      false,
			testEgressConfig:       getTestEgressConfig(testOutboundListenerName, testIPv4Cidr, ""),
			testDnsConfigEntry:     getTestDnsConfigEntry(testHostName, testIPv4Address),
			testIngressConfigEntry: getTestIngressConfigEntry(BridgeNetworkMode, "", false, testBridgeDefaultListenerPort, aws.Uint16(0), aws.Uint16(0)),
		},
		{
			testName:               "Bridge default case with IPv6 enabled",
			testNetworkMode:        BridgeNetworkMode,
			testIsIPv6Enabled:      true,
			testEgressConfig:       getTestEgressConfig(testOutboundListenerName, testIPv4Cidr, testIPv6Cidr),
			testDnsConfigEntry:     getTestDnsConfigEntry(testHostName, testIPv6Address),
			testIngressConfigEntry: getTestIngressConfigEntry(BridgeNetworkMode, "", false, testBridgeDefaultListenerPort, aws.Uint16(0), aws.Uint16(0)),
		},
		{
			testName:               "Bridge override case",
			testNetworkMode:        BridgeNetworkMode,
			testIsIPv6Enabled:      false,
			testEgressConfig:       getTestEgressConfig(testOutboundListenerName, testIPv4Cidr, ""),
			testDnsConfigEntry:     getTestDnsConfigEntry(testHostName, testIPv4Address),
			testIngressConfigEntry: getTestIngressConfigEntry(BridgeNetworkMode, "", true, testListenerPort, aws.Uint16(0), testBridgeOverrideHostPort),
		},
		{
			testName:               "Bridge override case with IPv6 enabled",
			testNetworkMode:        BridgeNetworkMode,
			testIsIPv6Enabled:      true,
			testEgressConfig:       getTestEgressConfig(testOutboundListenerName, testIPv4Cidr, testIPv6Cidr),
			testDnsConfigEntry:     getTestDnsConfigEntry(testHostName, testIPv6Address),
			testIngressConfigEntry: getTestIngressConfigEntry(BridgeNetworkMode, "", true, testListenerPort, aws.Uint16(0), testBridgeOverrideHostPort),
		},
	}

	for _, tc := range tt {
		t.Run(tc.testName, func(t *testing.T) {
			dnsConfig := []DNSConfigEntry{}
			dnsConfig = append(dnsConfig, tc.testDnsConfigEntry)
			ingressConfig := []IngressConfigEntry{}
			ingressConfig = append(ingressConfig, tc.testIngressConfigEntry)
			testServiceConnectConfig := getTestServiceConnectConfig(
				testServiceConnectContainerName,
				tc.testEgressConfig,
				dnsConfig,
				ingressConfig,
			)
			err := ValidateServiceConnectConfig(testServiceConnectConfig, testTaskContainers, tc.testNetworkMode, tc.testIsIPv6Enabled)
			assert.NoError(t, err)
		})
	}
}

func TestValidateServiceConnectConfigWithWarning(t *testing.T) {
	tt := []struct {
		testName                     string
		testNetworkMode              string
		testEgressConfig             *EgressConfig
		testDnsConfigEntry           DNSConfigEntry
		testFirstIngressConfigEntry  IngressConfigEntry
		testSecondIngressConfigEntry IngressConfigEntry
	}{
		{
			testName:                     "AWSVPC default case with both intercept port and host port in the ingress config",
			testNetworkMode:              AWSVPCNetworkMode,
			testEgressConfig:             getTestEgressConfig(testOutboundListenerName, testIPv4Cidr, ""),
			testDnsConfigEntry:           getTestDnsConfigEntry(testHostName, testIPv4Address),
			testFirstIngressConfigEntry:  getTestIngressConfigEntry(AWSVPCNetworkMode, testInboundListenerName, false, uint16(0), testAwsVpcDefaultInterceptPort, aws.Uint16(0)),
			testSecondIngressConfigEntry: getTestIngressConfigEntry(BridgeNetworkMode, "", true, testListenerPort, aws.Uint16(0), testBridgeOverrideHostPort),
		},
		{
			testName:                     "Bridge override case with both host port and intercept port in the ingress config",
			testNetworkMode:              BridgeNetworkMode,
			testEgressConfig:             getTestEgressConfig(testOutboundListenerName, testIPv4Cidr, ""),
			testDnsConfigEntry:           getTestDnsConfigEntry(testHostName, testIPv4Address),
			testFirstIngressConfigEntry:  getTestIngressConfigEntry(BridgeNetworkMode, "", true, testListenerPort, aws.Uint16(0), testBridgeOverrideHostPort),
			testSecondIngressConfigEntry: getTestIngressConfigEntry(AWSVPCNetworkMode, testInboundListenerName, false, uint16(0), testAwsVpcDefaultInterceptPort, aws.Uint16(0)),
		},
	}

	for _, tc := range tt {
		t.Run(tc.testName, func(t *testing.T) {
			dnsConfig := []DNSConfigEntry{}
			dnsConfig = append(dnsConfig, tc.testDnsConfigEntry)
			ingressConfig := []IngressConfigEntry{}
			ingressConfig = append(ingressConfig, tc.testFirstIngressConfigEntry)
			ingressConfig = append(ingressConfig, tc.testSecondIngressConfigEntry)
			testServiceConnectConfig := getTestServiceConnectConfig(
				testServiceConnectContainerName,
				tc.testEgressConfig,
				dnsConfig,
				ingressConfig,
			)
			err := ValidateServiceConnectConfig(testServiceConnectConfig, testTaskContainers, tc.testNetworkMode, false)
			assert.NoError(t, err)
		})
	}
}

func TestValidateServiceConnectConfigWithEmptyConfig(t *testing.T) {
	var testEgressConfig *EgressConfig
	var testDnsConfig []DNSConfigEntry
	var testIngressConfig []IngressConfigEntry
	tt := []struct {
		testName                 string
		testEgressConfigIsEmpty  bool
		testIngressConfigIsEmpty bool
	}{
		{
			testName:                 "AWSVPC default case with the empty egress config and dns config",
			testEgressConfigIsEmpty:  true,
			testIngressConfigIsEmpty: false,
		},
		{
			testName:                 "AWSVPC default case with the empty ingress config",
			testEgressConfigIsEmpty:  false,
			testIngressConfigIsEmpty: true,
		},
	}

	for _, tc := range tt {
		t.Run(tc.testName, func(t *testing.T) {
			if tc.testEgressConfigIsEmpty {
				testEgressConfig = nil
				testDnsConfig = []DNSConfigEntry{}
			} else {
				testEgressConfig = getTestEgressConfig(testOutboundListenerName, testIPv4Cidr, "")
				testDnsConfig = append(testDnsConfig, getTestDnsConfigEntry(testHostName, testIPv4Address))
			}

			if tc.testIngressConfigIsEmpty {
				testIngressConfig = []IngressConfigEntry{}
			} else {
				testIngressConfig = append(testIngressConfig, getTestIngressConfigEntry(AWSVPCNetworkMode, testInboundListenerName, false, uint16(0), testAwsVpcDefaultInterceptPort, aws.Uint16(0)))
			}

			testServiceConnectConfig := getTestServiceConnectConfig(
				testServiceConnectContainerName,
				testEgressConfig,
				testDnsConfig,
				testIngressConfig,
			)
			err := ValidateServiceConnectConfig(testServiceConnectConfig, testTaskContainers, AWSVPCNetworkMode, false)
			assert.NoError(t, err)
		})
	}
}

func TestValidateServiceConnectConfigWithError(t *testing.T) {
	tt := []struct {
		testName               string
		testNetworkMode        string
		testIsIPv6Enabled      bool
		testContainerName      string
		testEgressConfig       *EgressConfig
		testDnsConfigEntry     DNSConfigEntry
		testIngressConfigEntry IngressConfigEntry
	}{
		{
			testName:               "AWSVPC default case with no service connect container name",
			testNetworkMode:        AWSVPCNetworkMode,
			testIsIPv6Enabled:      false,
			testContainerName:      "",
			testEgressConfig:       getTestEgressConfig(testOutboundListenerName, testIPv4Cidr, ""),
			testDnsConfigEntry:     getTestDnsConfigEntry(testHostName, testIPv4Address),
			testIngressConfigEntry: getTestIngressConfigEntry(AWSVPCNetworkMode, testInboundListenerName, false, uint16(0), testAwsVpcDefaultInterceptPort, aws.Uint16(0)),
		},
		{
			testName:               "AWSVPC default case with the service connect container name not exists in the task",
			testNetworkMode:        AWSVPCNetworkMode,
			testIsIPv6Enabled:      false,
			testContainerName:      "helloworld",
			testEgressConfig:       getTestEgressConfig(testOutboundListenerName, testIPv4Cidr, ""),
			testDnsConfigEntry:     getTestDnsConfigEntry(testHostName, testIPv4Address),
			testIngressConfigEntry: getTestIngressConfigEntry(AWSVPCNetworkMode, testInboundListenerName, false, uint16(0), testAwsVpcDefaultInterceptPort, aws.Uint16(0)),
		},
		{
			testName:               "AWSVPC default case with no listener name in the egress config",
			testNetworkMode:        AWSVPCNetworkMode,
			testIsIPv6Enabled:      false,
			testContainerName:      testServiceConnectContainerName,
			testEgressConfig:       getTestEgressConfig("", testIPv4Cidr, ""),
			testDnsConfigEntry:     getTestDnsConfigEntry(testHostName, testIPv4Address),
			testIngressConfigEntry: getTestIngressConfigEntry(AWSVPCNetworkMode, testInboundListenerName, false, uint16(0), testAwsVpcDefaultInterceptPort, aws.Uint16(0)),
		},
		{
			testName:               "AWSVPC override case with the invalid IPv4 CIDR in the egress config",
			testNetworkMode:        AWSVPCNetworkMode,
			testIsIPv6Enabled:      false,
			testContainerName:      testServiceConnectContainerName,
			testEgressConfig:       getTestEgressConfig(testOutboundListenerName, "999999999", ""),
			testDnsConfigEntry:     getTestDnsConfigEntry(testHostName, testIPv4Address),
			testIngressConfigEntry: getTestIngressConfigEntry(AWSVPCNetworkMode, "", true, testListenerPort, aws.Uint16(0), aws.Uint16(0)),
		},
		{
			testName:               "AWSVPC override case with the invalid IPv6 CIDR in the egress config when IPv6 is enabled",
			testNetworkMode:        AWSVPCNetworkMode,
			testIsIPv6Enabled:      true,
			testContainerName:      testServiceConnectContainerName,
			testEgressConfig:       getTestEgressConfig(testOutboundListenerName, testIPv4Cidr, "999999999"),
			testDnsConfigEntry:     getTestDnsConfigEntry(testHostName, testIPv6Address),
			testIngressConfigEntry: getTestIngressConfigEntry(AWSVPCNetworkMode, "", true, testListenerPort, aws.Uint16(0), aws.Uint16(0)),
		},
		{
			testName:               "Bridge override case with no the invalid address in dns config when IPv6 is enabled",
			testNetworkMode:        BridgeNetworkMode,
			testIsIPv6Enabled:      true,
			testContainerName:      testServiceConnectContainerName,
			testEgressConfig:       getTestEgressConfig(testOutboundListenerName, testIPv4Cidr, testIPv6Cidr),
			testDnsConfigEntry:     getTestDnsConfigEntry(testHostName, "999999999"),
			testIngressConfigEntry: getTestIngressConfigEntry(BridgeNetworkMode, "", true, testListenerPort, aws.Uint16(0), testBridgeOverrideHostPort),
		},
	}

	for _, tc := range tt {
		t.Run(tc.testName, func(t *testing.T) {
			dnsConfig := []DNSConfigEntry{}
			dnsConfig = append(dnsConfig, tc.testDnsConfigEntry)
			ingressConfig := []IngressConfigEntry{}
			ingressConfig = append(ingressConfig, tc.testIngressConfigEntry)

			testServiceConnectConfig := getTestServiceConnectConfig(
				tc.testContainerName,
				tc.testEgressConfig,
				dnsConfig,
				ingressConfig,
			)
			err := ValidateServiceConnectConfig(testServiceConnectConfig, testTaskContainers, tc.testNetworkMode, tc.testIsIPv6Enabled)
			assert.Error(t, err)
		})
	}
}

func TestValidateServiceConnectConfigWithPortCollision(t *testing.T) {
	testEgressConfig := getTestEgressConfig(testOutboundListenerName, testIPv4Cidr, "")
	testDnsConfigEntry := getTestDnsConfigEntry(testHostName, testIPv4Address)
	tt := []struct {
		testName                     string
		testNetworkMode              string
		testFirstIngressConfigEntry  IngressConfigEntry
		testSecondIngressConfigEntry IngressConfigEntry
	}{
		{
			testName:                     "AWSVPC default case with the intercept port collision in the ingress config",
			testNetworkMode:              AWSVPCNetworkMode,
			testFirstIngressConfigEntry:  getTestIngressConfigEntry(AWSVPCNetworkMode, testInboundListenerName, false, uint16(0), testAwsVpcDefaultInterceptPort, aws.Uint16(0)),
			testSecondIngressConfigEntry: getTestIngressConfigEntry(AWSVPCNetworkMode, testInboundListenerName, false, uint16(0), testAwsVpcDefaultInterceptPort, aws.Uint16(0)),
		},
		{
			testName:                     "AWSVPC override case with the listener port collision in the ingress config",
			testNetworkMode:              AWSVPCNetworkMode,
			testFirstIngressConfigEntry:  getTestIngressConfigEntry(AWSVPCNetworkMode, "", true, testListenerPort, aws.Uint16(0), aws.Uint16(0)),
			testSecondIngressConfigEntry: getTestIngressConfigEntry(AWSVPCNetworkMode, "", true, testListenerPort, aws.Uint16(0), aws.Uint16(0)),
		},
		{
			testName:                     "Bridge default case with the listener port collision in the ingress config",
			testNetworkMode:              BridgeNetworkMode,
			testFirstIngressConfigEntry:  getTestIngressConfigEntry(BridgeNetworkMode, "", false, testBridgeDefaultListenerPort, aws.Uint16(0), aws.Uint16(0)),
			testSecondIngressConfigEntry: getTestIngressConfigEntry(BridgeNetworkMode, "", false, testBridgeDefaultListenerPort, aws.Uint16(0), aws.Uint16(0)),
		},
		{
			testName:                     "Bridge override case with the host port collision in the ingress config",
			testNetworkMode:              BridgeNetworkMode,
			testFirstIngressConfigEntry:  getTestIngressConfigEntry(BridgeNetworkMode, "", true, testListenerPort, aws.Uint16(0), testBridgeOverrideHostPort),
			testSecondIngressConfigEntry: getTestIngressConfigEntry(BridgeNetworkMode, "", true, testListenerPort, aws.Uint16(0), testBridgeOverrideHostPort),
		},
	}

	for _, tc := range tt {
		t.Run(tc.testName, func(t *testing.T) {
			dnsConfig := []DNSConfigEntry{testDnsConfigEntry}
			ingressConfig := []IngressConfigEntry{}
			ingressConfig = append(ingressConfig, tc.testFirstIngressConfigEntry)
			ingressConfig = append(ingressConfig, tc.testSecondIngressConfigEntry)
			testServiceConnectConfig := getTestServiceConnectConfig(
				testServiceConnectContainerName,
				testEgressConfig,
				dnsConfig,
				ingressConfig,
			)
			err := ValidateServiceConnectConfig(testServiceConnectConfig, testTaskContainers, tc.testNetworkMode, false)
			assert.Error(t, err)
		})
	}
}
