package dynamicrouting import ( "os" "os/exec" "strings" "testing" ) func TestAllProtocolsBirdConfigGeneration(t *testing.T) { mgr := &Manager{ config: DynamicConfig{ BGPInstance: BGPInstance{ Enabled: true, AS: 65001, RouterID: "10.42.62.254", }, BGPPeers: []BGPPeer{ { ID: "peer-1", Name: "Uplink", RemoteAddress: "10.42.36.253", RemoteAS: 65002, UseBFD: true, }, }, OSPFInstances: []OSPFInstance{ { ID: "ospf-1", Name: "Default", Version: 2, RouterID: "10.42.62.254", Enabled: true, }, }, OSPFAreas: []OSPFArea{ { ID: "area-0", Name: "Default", AreaID: "0.0.0.0", AreaType: "default", }, }, OSPFInterfaces: []OSPFInterface{ { ID: "ospf-if-1", Interfaces: "eth0", Area: "Default", Cost: 10, UseBFD: true, }, }, BFDInstance: BFDInstance{ Enabled: true, }, BFDInterfaces: []BFDInterface{ { ID: "bfd-if-1", Interface: "eth0", MinRxInterval: 100, MinTxInterval: 100, Multiplier: 3, }, }, BFDNeighbors: []BFDNeighbor{ { ID: "bfd-nbr-1", Address: "10.42.36.253", Interface: "eth0", }, }, RIPInstances: []RIPInstance{ { ID: "rip-1", Name: "default", Version: 2, Enabled: true, RedistributeConnected: true, RedistributeStatic: true, }, }, RIPInterfaces: []RIPInterface{ { ID: "rip-if-1", Instance: "default", Interface: "eth0", Metric: 1, Mode: "multicast", UseBFD: true, }, }, BabelInstances: []BabelInstance{ { ID: "babel-1", Name: "default", RouterID: "10.42.62.254", Enabled: true, RedistributeConnected: true, }, }, BabelInterfaces: []BabelInterface{ { ID: "babel-if-1", Instance: "default", Interface: "eth0", Type: "wired", RxCost: 10, HelloInterval: 4, UpdateInterval: 16, }, }, RAdvInstances: []RAdvInstance{ { ID: "radv-1", Name: "default", Enabled: true, }, }, RAdvInterfaces: []RAdvInterface{ { ID: "radv-if-1", Instance: "default", Interface: "eth0", MinRAInterval: 200, MaxRAInterval: 600, DefaultLifetime: 1800, Prefixes: []string{"2001:db8:1::/64"}, RDNSS: []string{"2001:4860:4860::8888"}, }, }, }, } cfg := mgr.generateBirdConfig() // Verify all protocol blocks are generated if !strings.Contains(cfg, "protocol bfd bfd_main") { t.Errorf("Expected protocol bfd bfd_main in config, got:\n%s", cfg) } if !strings.Contains(cfg, "protocol rip rip_default") { t.Errorf("Expected protocol rip in config, got:\n%s", cfg) } if !strings.Contains(cfg, "protocol babel babel_default") { t.Errorf("Expected protocol babel in config, got:\n%s", cfg) } if !strings.Contains(cfg, "protocol radv radv_default") { t.Errorf("Expected protocol radv in config, got:\n%s", cfg) } if !strings.Contains(cfg, "bfd yes;") { t.Errorf("Expected bfd yes; in peer and interface, got:\n%s", cfg) } // Validate generated config directly with BIRD 2 compiler if bird binary is available if _, err := exec.LookPath("bird"); err == nil { tmpFile, err := os.CreateTemp("", "bird_val_*.conf") if err != nil { t.Fatalf("Failed to create temp file: %v", err) } defer os.Remove(tmpFile.Name()) if _, err := tmpFile.WriteString(cfg); err != nil { t.Fatalf("Failed to write temp file: %v", err) } tmpFile.Close() cmd := exec.Command("bird", "-p", "-c", tmpFile.Name()) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("bird -p validation failed: %s\nConfig:\n%s", string(out), cfg) } } } func TestBabelFallbackInterfaceAndInstanceManagement(t *testing.T) { tmpDir, err := os.MkdirTemp("", "debtik_babel_test_*") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer os.RemoveAll(tmpDir) mgr := &Manager{ configPath: tmpDir + "/dynamic.json", config: DynamicConfig{ BabelInstances: []BabelInstance{}, BabelInterfaces: []BabelInterface{}, }, } // 1. Save Babel instance without explicit enabled flag inst, err := mgr.SaveBabelInstance(BabelInstance{ Name: "internal-Maiden2", RouterID: "192.168.132.1", }) if err != nil { t.Fatalf("SaveBabelInstance failed: %v", err) } if !inst.Enabled { t.Errorf("Expected inst.Enabled to be true when Disabled is false, got false") } // 2. Generate BIRD config when no interfaces exist - should generate fallback interface "*" cfg := mgr.generateBirdConfig() if !strings.Contains(cfg, `protocol babel babel_internal_Maiden2_0`) { t.Errorf("Expected protocol babel in config, got:\n%s", cfg) } if !strings.Contains(cfg, `interface "*"`) { t.Errorf("Expected fallback interface \"*\" in config, got:\n%s", cfg) } // 3. Save Babel interface without explicit instance - should default to "internal-Maiden2" iface, err := mgr.SaveBabelInterface(BabelInterface{ Interface: "eth0", RxCost: 96, }) if err != nil { t.Fatalf("SaveBabelInterface failed: %v", err) } if iface.Instance != "internal-Maiden2" { t.Errorf("Expected iface.Instance to default to internal-Maiden2, got %q", iface.Instance) } // 4. Regenerate config - now should contain eth0 template cfg2 := mgr.generateBirdConfig() if !strings.Contains(cfg2, `interface "eth0"`) { t.Errorf("Expected interface \"eth0\" in config, got:\n%s", cfg2) } // 5. Validate with bird -p if bird is installed if _, err := exec.LookPath("bird"); err == nil { tmpFile, err := os.CreateTemp("", "bird_babel_val_*.conf") if err != nil { t.Fatalf("Failed to create temp file: %v", err) } defer os.Remove(tmpFile.Name()) if _, err := tmpFile.WriteString(cfg2); err != nil { t.Fatalf("Failed to write temp file: %v", err) } tmpFile.Close() cmd := exec.Command("bird", "-p", "-c", tmpFile.Name()) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("bird -p validation failed on babel config: %s\nConfig:\n%s", string(out), cfg2) } } }