605 lines
13 KiB
Go
605 lines
13 KiB
Go
// ABOUTME: Comprehensive tests for EdgeConnect configuration parser with validation scenarios
|
|
// ABOUTME: Tests all validation rules, error conditions, and successful parsing cases
|
|
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNewParser(t *testing.T) {
|
|
parser := NewParser()
|
|
assert.NotNil(t, parser)
|
|
assert.IsType(t, &ConfigParser{}, parser)
|
|
}
|
|
|
|
func TestConfigParser_ParseBytes(t *testing.T) {
|
|
parser := NewParser()
|
|
|
|
tests := []struct {
|
|
name string
|
|
yaml string
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "valid k8s config",
|
|
yaml: `
|
|
kind: edgeconnect-deployment
|
|
metadata:
|
|
name: "test-app"
|
|
appVersion: "1.0.0"
|
|
organization: "testorg"
|
|
spec:
|
|
k8sApp:
|
|
manifestFile: "./test-manifest.yaml"
|
|
infraTemplate:
|
|
- region: "US"
|
|
cloudletOrg: "TestOP"
|
|
cloudletName: "TestCloudlet"
|
|
flavorName: "small"
|
|
`,
|
|
wantErr: true, // Will fail because manifest file doesn't exist
|
|
errMsg: "manifestFile does not exist",
|
|
},
|
|
{
|
|
name: "valid docker config",
|
|
yaml: `
|
|
kind: edgeconnect-deployment
|
|
metadata:
|
|
name: "test-app"
|
|
appVersion: "1.0.0"
|
|
organization: "testorg"
|
|
spec:
|
|
dockerApp:
|
|
image: "nginx:latest"
|
|
infraTemplate:
|
|
- region: "US"
|
|
cloudletOrg: "TestOP"
|
|
cloudletName: "TestCloudlet"
|
|
flavorName: "small"
|
|
`,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "missing kind",
|
|
yaml: `
|
|
metadata:
|
|
name: "test-app"
|
|
appVersion: "1.0.0"
|
|
organization: "testorg"
|
|
spec:
|
|
k8sApp:
|
|
manifestFile: "./test-manifest.yaml"
|
|
infraTemplate:
|
|
- region: "US"
|
|
cloudletOrg: "TestOP"
|
|
cloudletName: "TestCloudlet"
|
|
flavorName: "small"
|
|
`,
|
|
wantErr: true,
|
|
errMsg: "kind is required",
|
|
},
|
|
{
|
|
name: "invalid kind",
|
|
yaml: `
|
|
kind: invalid-kind
|
|
metadata:
|
|
name: "test-app"
|
|
appVersion: "1.0.0"
|
|
organization: "testorg"
|
|
spec:
|
|
dockerApp:
|
|
image: "nginx:latest"
|
|
infraTemplate:
|
|
- region: "US"
|
|
cloudletOrg: "TestOP"
|
|
cloudletName: "TestCloudlet"
|
|
flavorName: "small"
|
|
`,
|
|
wantErr: true,
|
|
errMsg: "unsupported kind: invalid-kind",
|
|
},
|
|
{
|
|
name: "missing app definition",
|
|
yaml: `
|
|
kind: edgeconnect-deployment
|
|
metadata:
|
|
name: "test-app"
|
|
appVersion: "1.0.0"
|
|
organization: "testorg"
|
|
spec:
|
|
infraTemplate:
|
|
- region: "US"
|
|
cloudletOrg: "TestOP"
|
|
cloudletName: "TestCloudlet"
|
|
flavorName: "small"
|
|
`,
|
|
wantErr: true,
|
|
errMsg: "spec must define either k8sApp or dockerApp",
|
|
},
|
|
{
|
|
name: "both k8s and docker apps",
|
|
yaml: `
|
|
kind: edgeconnect-deployment
|
|
metadata:
|
|
name: "test-app"
|
|
appVersion: "1.0.0"
|
|
organization: "testorg"
|
|
spec:
|
|
k8sApp:
|
|
manifestFile: "./test-manifest.yaml"
|
|
dockerApp:
|
|
appName: "test-app"
|
|
image: "nginx:latest"
|
|
infraTemplate:
|
|
- region: "US"
|
|
cloudletOrg: "TestOP"
|
|
cloudletName: "TestCloudlet"
|
|
flavorName: "small"
|
|
`,
|
|
wantErr: true,
|
|
errMsg: "spec cannot define both k8sApp and dockerApp",
|
|
},
|
|
{
|
|
name: "empty infrastructure template",
|
|
yaml: `
|
|
kind: edgeconnect-deployment
|
|
metadata:
|
|
name: "test-app"
|
|
appVersion: "1.0.0"
|
|
spec:
|
|
dockerApp:
|
|
image: "nginx:latest"
|
|
infraTemplate: []
|
|
`,
|
|
wantErr: true,
|
|
errMsg: "infraTemplate is required and must contain at least one target",
|
|
},
|
|
{
|
|
name: "with network config",
|
|
yaml: `
|
|
kind: edgeconnect-deployment
|
|
metadata:
|
|
name: "test-app"
|
|
appVersion: "1.0.0"
|
|
organization: "testorg"
|
|
spec:
|
|
dockerApp:
|
|
image: "nginx:latest"
|
|
infraTemplate:
|
|
- region: "US"
|
|
cloudletOrg: "TestOP"
|
|
cloudletName: "TestCloudlet"
|
|
flavorName: "small"
|
|
network:
|
|
outboundConnections:
|
|
- protocol: "tcp"
|
|
portRangeMin: 80
|
|
portRangeMax: 80
|
|
remoteCIDR: "0.0.0.0/0"
|
|
`,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty data",
|
|
yaml: "",
|
|
wantErr: true,
|
|
errMsg: "configuration data cannot be empty",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
config, err := parser.ParseBytes([]byte(tt.yaml))
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
if tt.errMsg != "" {
|
|
assert.Contains(t, err.Error(), tt.errMsg)
|
|
}
|
|
assert.Nil(t, config)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, config)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestConfigParser_ParseFile(t *testing.T) {
|
|
parser := NewParser()
|
|
|
|
// Create temporary directory for test files
|
|
tempDir := t.TempDir()
|
|
|
|
// Create a valid config file
|
|
validConfig := `
|
|
kind: edgeconnect-deployment
|
|
metadata:
|
|
name: "test-app"
|
|
appVersion: "1.0.0"
|
|
organization: "testorg"
|
|
spec:
|
|
dockerApp:
|
|
image: "nginx:latest"
|
|
infraTemplate:
|
|
- region: "US"
|
|
cloudletOrg: "TestOP"
|
|
cloudletName: "TestCloudlet"
|
|
flavorName: "small"
|
|
`
|
|
|
|
validFile := filepath.Join(tempDir, "valid.yaml")
|
|
err := os.WriteFile(validFile, []byte(validConfig), 0644)
|
|
require.NoError(t, err)
|
|
|
|
// Test valid file parsing
|
|
config, _, err := parser.ParseFile(validFile)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, config)
|
|
assert.Equal(t, "edgeconnect-deployment", config.Kind)
|
|
assert.Equal(t, "test-app", config.Metadata.Name)
|
|
|
|
// Test non-existent file
|
|
nonExistentFile := filepath.Join(tempDir, "nonexistent.yaml")
|
|
config, _, err = parser.ParseFile(nonExistentFile)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "does not exist")
|
|
assert.Nil(t, config)
|
|
|
|
// Test empty filename
|
|
config, _, err = parser.ParseFile("")
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "filename cannot be empty")
|
|
assert.Nil(t, config)
|
|
|
|
// Test invalid YAML
|
|
invalidFile := filepath.Join(tempDir, "invalid.yaml")
|
|
err = os.WriteFile(invalidFile, []byte("invalid: yaml: content: ["), 0644)
|
|
require.NoError(t, err)
|
|
|
|
config, _, err = parser.ParseFile(invalidFile)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "YAML parsing failed")
|
|
assert.Nil(t, config)
|
|
}
|
|
|
|
func TestConfigParser_RelativePathResolution(t *testing.T) {
|
|
parser := NewParser()
|
|
tempDir := t.TempDir()
|
|
|
|
// Create a manifest file
|
|
manifestContent := "apiVersion: v1\nkind: Pod\nmetadata:\n name: test\n"
|
|
manifestFile := filepath.Join(tempDir, "manifest.yaml")
|
|
err := os.WriteFile(manifestFile, []byte(manifestContent), 0644)
|
|
require.NoError(t, err)
|
|
|
|
// Create config with relative path
|
|
configContent := `
|
|
kind: edgeconnect-deployment
|
|
metadata:
|
|
name: "test-app"
|
|
appVersion: "1.0.0"
|
|
organization: "testorg"
|
|
spec:
|
|
k8sApp:
|
|
manifestFile: "./manifest.yaml"
|
|
infraTemplate:
|
|
- region: "US"
|
|
cloudletOrg: "TestOP"
|
|
cloudletName: "TestCloudlet"
|
|
flavorName: "small"
|
|
`
|
|
|
|
configFile := filepath.Join(tempDir, "config.yaml")
|
|
err = os.WriteFile(configFile, []byte(configContent), 0644)
|
|
require.NoError(t, err)
|
|
|
|
config, _, err := parser.ParseFile(configFile)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, config)
|
|
|
|
// Check that relative path was resolved to absolute
|
|
expectedPath := filepath.Join(tempDir, "manifest.yaml")
|
|
assert.Equal(t, expectedPath, config.Spec.K8sApp.ManifestFile)
|
|
}
|
|
|
|
func TestEdgeConnectConfig_Validate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
config EdgeConnectConfig
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "valid config",
|
|
config: EdgeConnectConfig{
|
|
Kind: "edgeconnect-deployment",
|
|
Metadata: Metadata{
|
|
Name: "test-app",
|
|
AppVersion: "1.0.0",
|
|
Organization: "testorg",
|
|
},
|
|
Spec: Spec{
|
|
DockerApp: &DockerApp{
|
|
Image: "nginx:latest",
|
|
},
|
|
InfraTemplate: []InfraTemplate{
|
|
{
|
|
Region: "US",
|
|
CloudletOrg: "TestOP",
|
|
CloudletName: "TestCloudlet",
|
|
FlavorName: "small",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "missing kind",
|
|
config: EdgeConnectConfig{
|
|
Metadata: Metadata{Name: "test"},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "kind is required",
|
|
},
|
|
{
|
|
name: "invalid kind",
|
|
config: EdgeConnectConfig{
|
|
Kind: "invalid",
|
|
Metadata: Metadata{Name: "test"},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "unsupported kind",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.config.Validate()
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
if tt.errMsg != "" {
|
|
assert.Contains(t, err.Error(), tt.errMsg)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMetadata_Validate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
metadata Metadata
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "valid metadata",
|
|
metadata: Metadata{Name: "test-app", AppVersion: "1.0.0"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty name",
|
|
metadata: Metadata{Name: "", AppVersion: "1.0.0"},
|
|
wantErr: true,
|
|
errMsg: "metadata.name is required",
|
|
},
|
|
{
|
|
name: "name with leading whitespace",
|
|
metadata: Metadata{Name: " test-app", AppVersion: "1.0.0"},
|
|
wantErr: true,
|
|
errMsg: "cannot have leading/trailing whitespace",
|
|
},
|
|
{
|
|
name: "name with trailing whitespace",
|
|
metadata: Metadata{Name: "test-app ", AppVersion: "1.0.0"},
|
|
wantErr: true,
|
|
errMsg: "cannot have leading/trailing whitespace",
|
|
},
|
|
{
|
|
name: "empty app version",
|
|
metadata: Metadata{Name: "test-app", AppVersion: ""},
|
|
wantErr: true,
|
|
errMsg: "metadata.appVersion is required",
|
|
},
|
|
{
|
|
name: "app version with leading whitespace",
|
|
metadata: Metadata{Name: "test-app", AppVersion: " 1.0.0"},
|
|
wantErr: true,
|
|
errMsg: "cannot have leading/trailing whitespace",
|
|
},
|
|
{
|
|
name: "app version with trailing whitespace",
|
|
metadata: Metadata{Name: "test-app", AppVersion: "1.0.0 "},
|
|
wantErr: true,
|
|
errMsg: "cannot have leading/trailing whitespace",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.metadata.Validate()
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
if tt.errMsg != "" {
|
|
assert.Contains(t, err.Error(), tt.errMsg)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOutboundConnection_Validate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
connection OutboundConnection
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "valid connection",
|
|
connection: OutboundConnection{
|
|
Protocol: "tcp",
|
|
PortRangeMin: 80,
|
|
PortRangeMax: 80,
|
|
RemoteCIDR: "0.0.0.0/0",
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "missing protocol",
|
|
connection: OutboundConnection{
|
|
PortRangeMin: 80,
|
|
PortRangeMax: 80,
|
|
RemoteCIDR: "0.0.0.0/0",
|
|
},
|
|
wantErr: true,
|
|
errMsg: "protocol is required",
|
|
},
|
|
{
|
|
name: "invalid protocol",
|
|
connection: OutboundConnection{
|
|
Protocol: "invalid",
|
|
PortRangeMin: 80,
|
|
PortRangeMax: 80,
|
|
RemoteCIDR: "0.0.0.0/0",
|
|
},
|
|
wantErr: true,
|
|
errMsg: "protocol must be one of: tcp, udp, icmp",
|
|
},
|
|
{
|
|
name: "invalid port range min",
|
|
connection: OutboundConnection{
|
|
Protocol: "tcp",
|
|
PortRangeMin: 0,
|
|
PortRangeMax: 80,
|
|
RemoteCIDR: "0.0.0.0/0",
|
|
},
|
|
wantErr: true,
|
|
errMsg: "portRangeMin must be between 1 and 65535",
|
|
},
|
|
{
|
|
name: "invalid port range max",
|
|
connection: OutboundConnection{
|
|
Protocol: "tcp",
|
|
PortRangeMin: 80,
|
|
PortRangeMax: 65536,
|
|
RemoteCIDR: "0.0.0.0/0",
|
|
},
|
|
wantErr: true,
|
|
errMsg: "portRangeMax must be between 1 and 65535",
|
|
},
|
|
{
|
|
name: "min greater than max",
|
|
connection: OutboundConnection{
|
|
Protocol: "tcp",
|
|
PortRangeMin: 443,
|
|
PortRangeMax: 80,
|
|
RemoteCIDR: "0.0.0.0/0",
|
|
},
|
|
wantErr: true,
|
|
errMsg: "portRangeMin (443) cannot be greater than portRangeMax (80)",
|
|
},
|
|
{
|
|
name: "missing remote CIDR",
|
|
connection: OutboundConnection{
|
|
Protocol: "tcp",
|
|
PortRangeMin: 80,
|
|
PortRangeMax: 80,
|
|
},
|
|
wantErr: true,
|
|
errMsg: "remoteCIDR is required",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.connection.Validate()
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
if tt.errMsg != "" {
|
|
assert.Contains(t, err.Error(), tt.errMsg)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSpec_GetMethods(t *testing.T) {
|
|
k8sSpec := &Spec{
|
|
K8sApp: &K8sApp{
|
|
ManifestFile: "k8s.yaml",
|
|
},
|
|
}
|
|
|
|
dockerSpec := &Spec{
|
|
DockerApp: &DockerApp{
|
|
ManifestFile: "docker.yaml",
|
|
},
|
|
}
|
|
|
|
assert.Equal(t, "k8s.yaml", k8sSpec.GetManifestFile())
|
|
assert.True(t, k8sSpec.IsK8sApp())
|
|
assert.False(t, k8sSpec.IsDockerApp())
|
|
|
|
assert.Equal(t, "docker.yaml", dockerSpec.GetManifestFile())
|
|
assert.False(t, dockerSpec.IsK8sApp())
|
|
assert.True(t, dockerSpec.IsDockerApp())
|
|
}
|
|
|
|
func TestReadManifestFile(t *testing.T) {
|
|
parser := NewParser()
|
|
tempDir := t.TempDir()
|
|
|
|
// Create a manifest file
|
|
manifestContent := "apiVersion: v1\nkind: Pod\nmetadata:\n name: test\n"
|
|
manifestFile := filepath.Join(tempDir, "manifest.yaml")
|
|
err := os.WriteFile(manifestFile, []byte(manifestContent), 0644)
|
|
require.NoError(t, err)
|
|
|
|
// Create config with relative path
|
|
configContent := `
|
|
kind: edgeconnect-deployment
|
|
metadata:
|
|
name: "test-app"
|
|
appVersion: "1.0.0"
|
|
organization: "testorg"
|
|
spec:
|
|
k8sApp:
|
|
manifestFile: "./manifest.yaml"
|
|
infraTemplate:
|
|
- region: "US"
|
|
cloudletOrg: "TestOP"
|
|
cloudletName: "TestCloudlet"
|
|
flavorName: "small"
|
|
`
|
|
|
|
configFile := filepath.Join(tempDir, "config.yaml")
|
|
err = os.WriteFile(configFile, []byte(configContent), 0644)
|
|
require.NoError(t, err)
|
|
|
|
config, parsedManifestContent, err := parser.ParseFile(configFile)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, manifestContent, parsedManifestContent)
|
|
assert.NotNil(t, config)
|
|
|
|
// Check that relative path was resolved to absolute
|
|
expectedPath := filepath.Join(tempDir, "manifest.yaml")
|
|
assert.Equal(t, expectedPath, config.Spec.K8sApp.ManifestFile)
|
|
}
|