package config import ( "encoding/json" "testing" "gopkg.in/yaml.v3" ) func TestExtraPortsJSONDecode_Legacy(t *testing.T) { // Legacy format: plain integer array input := `{"cloud":{"nftables":{"extraPorts":[22]}}}` var cfg State if err := json.Unmarshal([]byte(input), &cfg); err != nil { t.Fatalf("unmarshal: %v", err) } if len(cfg.Cloud.Nftables.ExtraPorts) != 1 || cfg.Cloud.Nftables.ExtraPorts[0].Port != 22 { t.Errorf("after JSON decode: expected [{Port:22}], got %v", cfg.Cloud.Nftables.ExtraPorts) } } func TestExtraPortsJSONDecode_Structured(t *testing.T) { // New structured format input := `{"cloud":{"nftables":{"extraPorts":[{"port":22,"protocol":"tcp","label":"SSH"},{"port":5353,"protocol":"udp"}]}}}` var cfg State if err := json.Unmarshal([]byte(input), &cfg); err != nil { t.Fatalf("unmarshal: %v", err) } if len(cfg.Cloud.Nftables.ExtraPorts) != 2 { t.Fatalf("expected 2 ports, got %d", len(cfg.Cloud.Nftables.ExtraPorts)) } if cfg.Cloud.Nftables.ExtraPorts[0].Port != 22 || cfg.Cloud.Nftables.ExtraPorts[0].Protocol != "tcp" || cfg.Cloud.Nftables.ExtraPorts[0].Label != "SSH" { t.Errorf("port 0: expected {22,tcp,SSH}, got %+v", cfg.Cloud.Nftables.ExtraPorts[0]) } if cfg.Cloud.Nftables.ExtraPorts[1].Port != 5353 || cfg.Cloud.Nftables.ExtraPorts[1].Protocol != "udp" { t.Errorf("port 1: expected {5353,udp}, got %+v", cfg.Cloud.Nftables.ExtraPorts[1]) } } func TestExtraPortsJSONEncode(t *testing.T) { var cfg State cfg.Cloud.Nftables.ExtraPorts = []ExtraPort{{Port: 22, Protocol: "tcp", Label: "SSH"}} out, err := json.Marshal(cfg) if err != nil { t.Fatalf("marshal: %v", err) } var decoded map[string]any json.Unmarshal(out, &decoded) cloud := decoded["cloud"].(map[string]any) nft := cloud["nftables"].(map[string]any) ports, ok := nft["extraPorts"] if !ok { t.Errorf("extraPorts missing from JSON output: %s", out) } arr := ports.([]any) if len(arr) != 1 { t.Fatalf("expected 1 port, got %d", len(arr)) } entry := arr[0].(map[string]any) if entry["port"].(float64) != 22 { t.Errorf("expected port 22, got %v", entry["port"]) } if entry["label"].(string) != "SSH" { t.Errorf("expected label SSH, got %v", entry["label"]) } } func TestExtraPortsYAML_LegacyRoundTrip(t *testing.T) { // Legacy YAML format with plain integers input := "cloud:\n nftables:\n extraPorts:\n - 22\n - 8080\n" var cfg State if err := yaml.Unmarshal([]byte(input), &cfg); err != nil { t.Fatalf("yaml unmarshal: %v", err) } if len(cfg.Cloud.Nftables.ExtraPorts) != 2 { t.Fatalf("expected 2 ports, got %d", len(cfg.Cloud.Nftables.ExtraPorts)) } if cfg.Cloud.Nftables.ExtraPorts[0].Port != 22 || cfg.Cloud.Nftables.ExtraPorts[1].Port != 8080 { t.Errorf("expected [22, 8080], got %v", cfg.Cloud.Nftables.ExtraPorts) } } func TestExtraPortsYAML_StructuredRoundTrip(t *testing.T) { input := "cloud:\n nftables:\n extraPorts:\n - port: 22\n protocol: tcp\n label: SSH\n - port: 5353\n protocol: udp\n" var cfg State if err := yaml.Unmarshal([]byte(input), &cfg); err != nil { t.Fatalf("yaml unmarshal: %v", err) } if len(cfg.Cloud.Nftables.ExtraPorts) != 2 { t.Fatalf("expected 2 ports, got %d", len(cfg.Cloud.Nftables.ExtraPorts)) } if cfg.Cloud.Nftables.ExtraPorts[0].Label != "SSH" { t.Errorf("expected label SSH, got %q", cfg.Cloud.Nftables.ExtraPorts[0].Label) } if cfg.Cloud.Nftables.ExtraPorts[1].Protocol != "udp" { t.Errorf("expected protocol udp, got %q", cfg.Cloud.Nftables.ExtraPorts[1].Protocol) } } func TestSplitExtraPorts(t *testing.T) { ports := []ExtraPort{ {Port: 22, Protocol: "tcp"}, {Port: 80}, // default = tcp {Port: 5353, Protocol: "udp"}, {Port: 123, Protocol: "UDP"}, // case-insensitive } tcp, udp := SplitExtraPorts(ports) if len(tcp) != 2 || tcp[0] != 22 || tcp[1] != 80 { t.Errorf("expected tcp=[22,80], got %v", tcp) } if len(udp) != 2 || udp[0] != 5353 || udp[1] != 123 { t.Errorf("expected udp=[5353,123], got %v", udp) } }