package caddy import ( "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" ) func TestNewImporter(t *testing.T) { importer := NewImporter("/usr/bin/caddy") assert.NotNil(t, importer) assert.Equal(t, "/usr/bin/caddy", importer.caddyBinaryPath) importerDefault := NewImporter("") assert.NotNil(t, importerDefault) assert.Equal(t, "caddy", importerDefault.caddyBinaryPath) } func TestImporter_ParseCaddyfile_NotFound(t *testing.T) { importer := NewImporter("caddy") _, err := importer.ParseCaddyfile("non-existent-file") assert.Error(t, err) assert.Contains(t, err.Error(), "caddyfile not found") } type MockExecutor struct { Output []byte Err error } func (m *MockExecutor) Execute(name string, args ...string) ([]byte, error) { return m.Output, m.Err } func TestImporter_ParseCaddyfile_Success(t *testing.T) { importer := NewImporter("caddy") mockExecutor := &MockExecutor{ Output: []byte(`{"apps": {"http": {"servers": {}}}}`), Err: nil, } importer.executor = mockExecutor // Create a dummy file to bypass os.Stat check tmpFile := filepath.Join(t.TempDir(), "Caddyfile") err := os.WriteFile(tmpFile, []byte("foo"), 0o644) assert.NoError(t, err) output, err := importer.ParseCaddyfile(tmpFile) assert.NoError(t, err) assert.JSONEq(t, `{"apps": {"http": {"servers": {}}}}`, string(output)) } func TestImporter_ParseCaddyfile_Failure(t *testing.T) { importer := NewImporter("caddy") mockExecutor := &MockExecutor{ Output: []byte("syntax error"), Err: assert.AnError, } importer.executor = mockExecutor // Create a dummy file tmpFile := filepath.Join(t.TempDir(), "Caddyfile") err := os.WriteFile(tmpFile, []byte("foo"), 0o644) assert.NoError(t, err) _, err = importer.ParseCaddyfile(tmpFile) assert.Error(t, err) assert.Contains(t, err.Error(), "caddy adapt failed") } func TestImporter_ExtractHosts(t *testing.T) { importer := NewImporter("caddy") // Test Case 1: Empty Config emptyJSON := []byte(`{}`) result, err := importer.ExtractHosts(emptyJSON) assert.NoError(t, err) assert.Empty(t, result.Hosts) // Test Case 2: Invalid JSON invalidJSON := []byte(`{invalid`) _, err = importer.ExtractHosts(invalidJSON) assert.Error(t, err) // Test Case 3: Valid Config with Reverse Proxy validJSON := []byte(`{ "apps": { "http": { "servers": { "srv0": { "routes": [ { "match": [{"host": ["example.com"]}], "handle": [ { "handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:8080"}] } ] } ] } } } } }`) result, err = importer.ExtractHosts(validJSON) assert.NoError(t, err) assert.Len(t, result.Hosts, 1) assert.Equal(t, "example.com", result.Hosts[0].DomainNames) assert.Equal(t, "127.0.0.1", result.Hosts[0].ForwardHost) assert.Equal(t, 8080, result.Hosts[0].ForwardPort) // Test Case 4: Duplicate Domain duplicateJSON := []byte(`{ "apps": { "http": { "servers": { "srv0": { "routes": [ { "match": [{"host": ["example.com"]}], "handle": [{"handler": "reverse_proxy"}] }, { "match": [{"host": ["example.com"]}], "handle": [{"handler": "reverse_proxy"}] } ] } } } } }`) result, err = importer.ExtractHosts(duplicateJSON) assert.NoError(t, err) assert.Len(t, result.Hosts, 1) assert.Len(t, result.Conflicts, 1) assert.Equal(t, "example.com", result.Conflicts[0]) // Test Case 5: Unsupported Features unsupportedJSON := []byte(`{ "apps": { "http": { "servers": { "srv0": { "routes": [ { "match": [{"host": ["files.example.com"]}], "handle": [ {"handler": "file_server"}, {"handler": "rewrite"} ] } ] } } } } }`) result, err = importer.ExtractHosts(unsupportedJSON) assert.NoError(t, err) assert.Len(t, result.Hosts, 1) assert.Len(t, result.Hosts[0].Warnings, 2) assert.Contains(t, result.Hosts[0].Warnings, "File server directives not supported") assert.Contains(t, result.Hosts[0].Warnings, "Rewrite rules not supported - manual configuration required") // Test Case 6: SSL Detection via Listen Address (:443) sslViaListenJSON := []byte(`{ "apps": { "http": { "servers": { "srv0": { "listen": [":443"], "routes": [ { "match": [{"host": ["secure.example.com"]}], "handle": [ { "handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:9000"}] } ] } ] } } } } }`) result, err = importer.ExtractHosts(sslViaListenJSON) assert.NoError(t, err) assert.Len(t, result.Hosts, 1) assert.Equal(t, "secure.example.com", result.Hosts[0].DomainNames) assert.True(t, result.Hosts[0].SSLForced, "SSLForced should be true when server listens on :443") } func TestImporter_ImportFile(t *testing.T) { importer := NewImporter("caddy") mockExecutor := &MockExecutor{ Output: []byte(`{ "apps": { "http": { "servers": { "srv0": { "routes": [ { "match": [{"host": ["example.com"]}], "handle": [ { "handler": "reverse_proxy", "upstreams": [{"dial": "127.0.0.1:8080"}] } ] } ] } } } } }`), Err: nil, } importer.executor = mockExecutor // Create a dummy file tmpFile := filepath.Join(t.TempDir(), "Caddyfile") err := os.WriteFile(tmpFile, []byte("foo"), 0o644) assert.NoError(t, err) result, err := importer.ImportFile(tmpFile) assert.NoError(t, err) assert.Len(t, result.Hosts, 1) assert.Equal(t, "example.com", result.Hosts[0].DomainNames) } func TestConvertToProxyHosts(t *testing.T) { parsedHosts := []ParsedHost{ { DomainNames: "example.com", ForwardScheme: "http", ForwardHost: "127.0.0.1", ForwardPort: 8080, SSLForced: true, WebsocketSupport: true, }, { DomainNames: "invalid.com", ForwardHost: "", // Invalid }, } hosts := ConvertToProxyHosts(parsedHosts) assert.Len(t, hosts, 1) assert.Equal(t, "example.com", hosts[0].DomainNames) assert.Equal(t, "127.0.0.1", hosts[0].ForwardHost) assert.Equal(t, 8080, hosts[0].ForwardPort) assert.True(t, hosts[0].SSLForced) assert.True(t, hosts[0].WebsocketSupport) } func TestImporter_ValidateCaddyBinary(t *testing.T) { importer := NewImporter("caddy") // Success importer.executor = &MockExecutor{Output: []byte("v2.0.0"), Err: nil} err := importer.ValidateCaddyBinary() assert.NoError(t, err) // Failure importer.executor = &MockExecutor{Output: nil, Err: assert.AnError} err = importer.ValidateCaddyBinary() assert.Error(t, err) assert.Equal(t, "caddy binary not found or not executable", err.Error()) } func TestBackupCaddyfile(t *testing.T) { tmpDir := t.TempDir() originalFile := filepath.Join(tmpDir, "Caddyfile") err := os.WriteFile(originalFile, []byte("original content"), 0o644) assert.NoError(t, err) backupDir := filepath.Join(tmpDir, "backups") // Success backupPath, err := BackupCaddyfile(originalFile, backupDir) assert.NoError(t, err) assert.FileExists(t, backupPath) content, err := os.ReadFile(backupPath) assert.NoError(t, err) assert.Equal(t, "original content", string(content)) // Failure - Source not found _, err = BackupCaddyfile("non-existent", backupDir) assert.Error(t, err) } func TestDefaultExecutor_Execute(t *testing.T) { executor := &DefaultExecutor{} output, err := executor.Execute("echo", "hello") assert.NoError(t, err) assert.Equal(t, "hello\n", string(output)) }