package config import ( "os" "path/filepath" "testing" ) const testJSON = `{ "networks": { "smarthost-loadbalancer": { "network_name": "smarthost-loadbalancer", "subnet": "172.18.103.0/24", "gateway": "172.18.103.1" }, "smarthost_backend-1": { "network_name": "smarthost_backend-1", "subnet": "172.18.104.0/24", "gateway": "172.18.104.1" } }, "ips": { "172.18.103.2": { "ip": "172.18.103.2", "container_name": "smarthostloadbalancer", "selector": "smarthostloadbalancer", "service_name": "smarthost-proxy" }, "172.18.104.2": { "ip": "172.18.104.2", "container_name": "smarthostbackend-1", "selector": "smarthostbackend-1", "service_name": "smarthost-proxy" } }, "policies": [ { "service_name": "smarthost-proxy", "container_name": "smarthost_loadbalancer", "selector": "smarthostloadbalancer", "from": "publicbackend", "port": 80, "proto": "tcp" }, { "service_name": "smarthost-proxy", "container_name": "smarthost_loadbalancer", "selector": "smarthostloadbalancer", "name": "wireguardproxy", "iface": "wg0", "nat": "dnat", "to": "smarthostloadbalancer", "port": 80, "proto": "tcp" } ] }` func TestLoad(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "networks.json") if err := os.WriteFile(path, []byte(testJSON), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } cfg, err := Load(path) if err != nil { t.Fatalf("Load() returned error: %v", err) } if len(cfg.Networks) != 2 { t.Errorf("expected 2 networks, got %d", len(cfg.Networks)) } if cfg.Networks["smarthost-loadbalancer"].Subnet != "172.18.103.0/24" { t.Errorf("unexpected subnet: %s", cfg.Networks["smarthost-loadbalancer"].Subnet) } if len(cfg.IPs) != 2 { t.Errorf("expected 2 IPs, got %d", len(cfg.IPs)) } if cfg.IPs["172.18.103.2"].ContainerName != "smarthostloadbalancer" { t.Errorf("unexpected container name: %s", cfg.IPs["172.18.103.2"].ContainerName) } if len(cfg.Policies) != 2 { t.Errorf("expected 2 policies, got %d", len(cfg.Policies)) } if cfg.Policies[0].From != "publicbackend" { t.Errorf("unexpected from: %s", cfg.Policies[0].From) } if cfg.Policies[1].Nat != "dnat" { t.Errorf("unexpected nat: %s", cfg.Policies[1].Nat) } } func TestLoadFileNotFound(t *testing.T) { _, err := Load("/nonexistent/path/networks.json") if err == nil { t.Error("expected error for nonexistent file, got nil") } } func TestLoadInvalidJSON(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "bad.json") if err := os.WriteFile(path, []byte("{invalid json"), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } _, err := Load(path) if err == nil { t.Error("expected error for invalid JSON, got nil") } } func TestToCIDR(t *testing.T) { tests := []struct { input string want string }{ {"172.18.103.0", "172.18.103.0/24"}, {"172.18.103.2", "172.18.103.2"}, {"172.18.103.0/24", "172.18.103.0/24"}, {"10.0.0.0", "10.0.0.0/24"}, {"invalid", "invalid"}, } for _, tt := range tests { got := ToCIDR(tt.input) if got != tt.want { t.Errorf("ToCIDR(%q) = %q, want %q", tt.input, got, tt.want) } } } func TestNetworkPrefix(t *testing.T) { tests := []struct { input string want string }{ {"172.18.103.2", "172.18.103.0/24"}, {"10.0.0.1", "10.0.0.0/24"}, {"invalid", "invalid"}, } for _, tt := range tests { got := NetworkPrefix(tt.input) if got != tt.want { t.Errorf("NetworkPrefix(%q) = %q, want %q", tt.input, got, tt.want) } } } func TestIsIP(t *testing.T) { tests := []struct { input string want bool }{ {"172.18.103.2", true}, {"10.0.0.0", true}, {"255.255.255.255", true}, {"publicbackend", false}, {"172.18.103.0/24", false}, {"", false}, } for _, tt := range tests { got := IsIP(tt.input) if got != tt.want { t.Errorf("IsIP(%q) = %v, want %v", tt.input, got, tt.want) } } } func TestNetworkConfigParseCIDR(t *testing.T) { nc := NetworkConfig{Subnet: "172.18.103.0/24"} ipNet, err := nc.ParseCIDR() if err != nil { t.Fatalf("ParseCIDR() returned error: %v", err) } if ipNet.String() != "172.18.103.0/24" { t.Errorf("ParseCIDR() = %s, want 172.18.103.0/24", ipNet.String()) } nc2 := NetworkConfig{Subnet: "invalid"} _, err = nc2.ParseCIDR() if err == nil { t.Error("expected error for invalid subnet, got nil") } } func TestLoadReproducible(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "networks.json") if err := os.WriteFile(path, []byte(testJSON), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } // Load twice and compare cfg1, err := Load(path) if err != nil { t.Fatalf("first Load() error: %v", err) } cfg2, err := Load(path) if err != nil { t.Fatalf("second Load() error: %v", err) } // Verify reproducibility if len(cfg1.Networks) != len(cfg2.Networks) { t.Errorf("reproducibility: network count mismatch %d vs %d", len(cfg1.Networks), len(cfg2.Networks)) } if len(cfg1.IPs) != len(cfg2.IPs) { t.Errorf("reproducibility: IP count mismatch %d vs %d", len(cfg1.IPs), len(cfg2.IPs)) } if len(cfg1.Policies) != len(cfg2.Policies) { t.Errorf("reproducibility: policy count mismatch %d vs %d", len(cfg1.Policies), len(cfg2.Policies)) } for name, net1 := range cfg1.Networks { net2 := cfg2.Networks[name] if net1.Subnet != net2.Subnet || net1.Gateway != net2.Gateway { t.Errorf("reproducibility: network %s mismatch", name) } } }