| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695 |
- package dynamicrouting
- import (
- "encoding/json"
- "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)
- }
- }
- }
- func TestBabelFiltersAndValidation(t *testing.T) {
- tmpDir, err := os.MkdirTemp("", "debtik_babel_filter_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{
- RoutingFilters: []RoutingFilterRule{
- {Chain: "babel-in", RuleOrder: 10, Rule: "if (dst in 192.168.100.0/24) { accept; }"},
- {Chain: "babel-out", RuleOrder: 10, Rule: "if (dst in 10.0.0.0/8) { accept; }"},
- },
- BabelInstances: []BabelInstance{
- {
- Name: "test_babel",
- RouterID: "1.1.1.1",
- InFilter: "babel-in",
- OutFilter: "babel-out",
- RedistributeStatic: true,
- },
- },
- BabelInterfaces: []BabelInterface{
- {Interface: "eth0", Instance: "test_babel"},
- },
- },
- }
- cfg := mgr.generateBirdConfig()
- if !strings.Contains(cfg, "import filter filter_babel_in;") {
- t.Fatalf("expected import filter filter_babel_in in config:\n%s", cfg)
- }
- if !strings.Contains(cfg, "fn_filter_babel_out();") {
- t.Fatalf("expected fn_filter_babel_out() in export filter:\n%s", cfg)
- }
- if _, err := exec.LookPath("bird"); err == nil {
- tmpFile, err := os.CreateTemp("", "bird_babel_filter_*.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 on babel filter config: %s\nConfig:\n%s", string(out), cfg)
- }
- }
- }
- func TestBFDAndRIPAndRAdvManagement(t *testing.T) {
- tmpDir, err := os.MkdirTemp("", "debtik_protocols_test_*")
- if err != nil {
- t.Fatalf("Failed to create temp dir: %v", err)
- }
- defer os.RemoveAll(tmpDir)
- mgr := &Manager{
- configPath: tmpDir + "/dynamic.json",
- }
- // 1. Save BFD instance
- err = mgr.SaveBFDInstance(BFDInstance{Enabled: true})
- if err != nil {
- t.Fatalf("SaveBFDInstance failed: %v", err)
- }
- bfdInst := mgr.GetBFDInstance()
- if !bfdInst.Enabled {
- t.Errorf("Expected bfdInst.Enabled to be true")
- }
- // 2. Check BFD config generation with 0 interfaces - should contain fallback interface "*"
- cfg := mgr.generateBirdConfig()
- 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, `interface "*"`) {
- t.Errorf("Expected fallback interface \"*\" in BFD config, got:\n%s", cfg)
- }
- // 3. Save BFD multihop neighbor with local address
- _, err = mgr.SaveBFDNeighbor(BFDNeighbor{
- Address: "10.0.0.1",
- LocalAddress: "10.0.0.2",
- Multihop: true,
- })
- if err != nil {
- t.Fatalf("SaveBFDNeighbor failed: %v", err)
- }
- // 4. Save RIP instance without explicit enabled flag
- ripInst, err := mgr.SaveRIPInstance(RIPInstance{
- Name: "rip1",
- Version: 2,
- })
- if err != nil {
- t.Fatalf("SaveRIPInstance failed: %v", err)
- }
- if !ripInst.Enabled {
- t.Errorf("Expected ripInst.Enabled to be true, got false")
- }
- // 5. Save RAdv instance without explicit enabled flag
- radvInst, err := mgr.SaveRAdvInstance(RAdvInstance{
- Name: "radv1",
- })
- if err != nil {
- t.Fatalf("SaveRAdvInstance failed: %v", err)
- }
- if !radvInst.Enabled {
- t.Errorf("Expected radvInst.Enabled to be true, got false")
- }
- // 6. Verify config with BIRD compiler
- cfgAll := mgr.generateBirdConfig()
- if !strings.Contains(cfgAll, "neighbor 10.0.0.1 local 10.0.0.2 multihop;") {
- t.Errorf("Expected multihop neighbor with local address, got:\n%s", cfgAll)
- }
- if !strings.Contains(cfgAll, `protocol rip rip_rip1_0`) {
- t.Errorf("Expected protocol rip in config, got:\n%s", cfgAll)
- }
- if !strings.Contains(cfgAll, `protocol radv radv_radv1_0`) {
- t.Errorf("Expected protocol radv in config, got:\n%s", cfgAll)
- }
- // 7. Verify OSPF fallback interface when no templates exist
- mgr.config.OSPFInterfaces = nil
- cfgOspfFallback := mgr.generateBirdConfig()
- if !strings.Contains(cfgOspfFallback, `interface "*"`) {
- t.Errorf("Expected fallback wildcard interface for OSPF area, got:\n%s", cfgOspfFallback)
- }
- if _, err := exec.LookPath("bird"); err == nil {
- tmpFile, err := os.CreateTemp("", "bird_all_val_*.conf")
- if err != nil {
- t.Fatalf("Failed to create temp file: %v", err)
- }
- defer os.Remove(tmpFile.Name())
- if _, err := tmpFile.WriteString(cfgOspfFallback); 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 all protocols config: %s\nConfig:\n%s", string(out), cfgOspfFallback)
- }
- }
- }
- func TestRIPAndOSPFInstanceJSONUnmarshal(t *testing.T) {
- testCasesRIP := []struct {
- jsonStr string
- expectedVersion int
- }{
- {`{"name":"rip1","version":2}`, 2},
- {`{"name":"rip1","version":"2"}`, 2},
- {`{"name":"rip1","version":"rip2"}`, 2},
- {`{"name":"rip1","version":"ripv2"}`, 2},
- {`{"name":"ripng1","version":1}`, 1},
- {`{"name":"ripng1","version":"1"}`, 1},
- {`{"name":"ripng1","version":"ripng"}`, 1},
- {`{"name":"ripng1","version":"ng"}`, 1},
- {`{"name":"ripng1","version":"ipv6"}`, 1},
- {`{"name":"rip_default"}`, 2},
- }
- for idx, tc := range testCasesRIP {
- var inst RIPInstance
- if err := json.Unmarshal([]byte(tc.jsonStr), &inst); err != nil {
- t.Fatalf("Case %d failed to unmarshal: %v", idx, err)
- }
- if inst.Version != tc.expectedVersion {
- t.Errorf("Case %d: expected version %d, got %d", idx, tc.expectedVersion, inst.Version)
- }
- }
- testCasesOSPF := []struct {
- jsonStr string
- expectedVersion int
- }{
- {`{"name":"ospf1","version":2}`, 2},
- {`{"name":"ospf1","version":"2"}`, 2},
- {`{"name":"ospf1","version":"ospfv2"}`, 2},
- {`{"name":"ospf1","version":"v2"}`, 2},
- {`{"name":"ospf3","version":3}`, 3},
- {`{"name":"ospf3","version":"3"}`, 3},
- {`{"name":"ospf3","version":"ospfv3"}`, 3},
- {`{"name":"ospf3","version":"v3"}`, 3},
- {`{"name":"ospf3","version":"ipv6"}`, 3},
- {`{"name":"ospf_default"}`, 2},
- }
- for idx, tc := range testCasesOSPF {
- var inst OSPFInstance
- if err := json.Unmarshal([]byte(tc.jsonStr), &inst); err != nil {
- t.Fatalf("OSPF Case %d failed to unmarshal: %v", idx, err)
- }
- if inst.Version != tc.expectedVersion {
- t.Errorf("OSPF Case %d: expected version %d, got %d", idx, tc.expectedVersion, inst.Version)
- }
- }
- }
- func TestDynamicRoutingFiltersAllProtocols(t *testing.T) {
- mgr := &Manager{
- config: DynamicConfig{
- RoutingFilters: []RoutingFilterRule{
- {
- ID: "rule-in-1",
- Chain: "filter-in",
- Rule: "dst in 10.0.0.0/8",
- Action: "accept",
- Disabled: false,
- },
- {
- ID: "rule-out-1",
- Chain: "filter-out",
- Rule: "dst in 192.168.0.0/16",
- Action: "accept",
- Disabled: false,
- },
- },
- BGPInstance: BGPInstance{
- Enabled: true,
- AS: 65001,
- RouterID: "10.42.62.254",
- InFilter: "filter-in",
- OutFilter: "filter-out",
- },
- BGPPeers: []BGPPeer{
- {
- ID: "peer-1",
- Name: "DefaultPeer",
- RemoteAddress: "10.42.36.253",
- RemoteAS: 65002,
- },
- },
- OSPFInstances: []OSPFInstance{
- {
- ID: "ospf-1",
- Name: "Default",
- Version: 2,
- RouterID: "10.42.62.254",
- Enabled: true,
- InFilter: "filter-in",
- OutFilter: "filter-out",
- RedistributeStatic: true,
- },
- },
- OSPFAreas: []OSPFArea{
- {
- ID: "area-0",
- Name: "backbone",
- AreaID: "0.0.0.0",
- AreaType: "default",
- },
- },
- OSPFInterfaces: []OSPFInterface{
- {
- ID: "ospf-if-1",
- Interfaces: "eth0",
- Area: "backbone",
- },
- },
- RIPInstances: []RIPInstance{
- {
- ID: "rip-1",
- Name: "default",
- Version: 2,
- Enabled: true,
- InFilter: "filter-in",
- OutFilter: "filter-out",
- RedistributeConnected: true,
- },
- },
- RIPInterfaces: []RIPInterface{
- {
- ID: "rip-if-1",
- Instance: "default",
- Interface: "eth0",
- },
- },
- BabelInstances: []BabelInstance{
- {
- ID: "babel-1",
- Name: "default",
- RouterID: "10.42.62.254",
- Enabled: true,
- InFilter: "filter-in",
- OutFilter: "filter-out",
- RedistributeConnected: true,
- },
- },
- BabelInterfaces: []BabelInterface{
- {
- ID: "babel-if-1",
- Instance: "default",
- Interface: "eth0",
- },
- },
- EIGRPInstances: []EIGRPInstance{
- {
- ID: "eigrp-1",
- Name: "default",
- AS: 100,
- RouterID: "10.42.62.254",
- Enabled: true,
- InFilter: "filter-in",
- OutFilter: "filter-out",
- RedistributeConnected: true,
- },
- },
- },
- }
- // 1. Test BIRD Config Generation
- birdCfg := mgr.generateBirdConfig()
- // Verify BGP uses global fallback filter
- if !strings.Contains(birdCfg, "import filter filter_filter_in;") {
- t.Errorf("BGP config missing import filter filter_filter_in;\nConfig:\n%s", birdCfg)
- }
- if !strings.Contains(birdCfg, "fn_filter_filter_out();") {
- t.Errorf("BGP config missing fn_filter_filter_out();\nConfig:\n%s", birdCfg)
- }
- // Verify OSPF in_filter & out_filter
- if !strings.Contains(birdCfg, "protocol ospf v2 ospf_Default") || !strings.Contains(birdCfg, "import filter filter_filter_in;") {
- t.Errorf("OSPF config missing import filter filter_filter_in;\nConfig:\n%s", birdCfg)
- }
- // Verify RIP in_filter & out_filter
- if !strings.Contains(birdCfg, "protocol rip rip_default") || !strings.Contains(birdCfg, "fn_filter_filter_out();") {
- t.Errorf("RIP config missing fn_filter_filter_out();\nConfig:\n%s", birdCfg)
- }
- // Verify Babel in_filter & out_filter
- if !strings.Contains(birdCfg, "protocol babel babel_default") || !strings.Contains(birdCfg, "fn_filter_filter_out();") {
- t.Errorf("Babel config missing fn_filter_filter_out();\nConfig:\n%s", birdCfg)
- }
- // Verify BIRD syntax with bird -p if installed
- if _, err := exec.LookPath("bird"); err == nil {
- tmpFile, err := os.CreateTemp("", "bird_filters_val_*.conf")
- if err != nil {
- t.Fatalf("Failed to create temp file: %v", err)
- }
- defer os.Remove(tmpFile.Name())
- if _, err := tmpFile.WriteString(birdCfg); 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 all protocol filters config: %s\nConfig:\n%s", string(out), birdCfg)
- }
- }
- // 2. Test FRR EIGRP Config Generation
- frrCfg := mgr.generateFRREigrpConfig()
- if !strings.Contains(frrCfg, "ip prefix-list pl_in_filter_in seq 10 permit 10.0.0.0/8") {
- t.Errorf("FRR config missing in-filter prefix list;\nConfig:\n%s", frrCfg)
- }
- if !strings.Contains(frrCfg, "ip prefix-list pl_out_filter_out seq 10 permit 192.168.0.0/16") {
- t.Errorf("FRR config missing out-filter prefix list;\nConfig:\n%s", frrCfg)
- }
- if !strings.Contains(frrCfg, "distribute-list prefix pl_in_filter_in in") {
- t.Errorf("FRR config missing distribute-list prefix in;\nConfig:\n%s", frrCfg)
- }
- if !strings.Contains(frrCfg, "distribute-list prefix pl_out_filter_out out") {
- t.Errorf("FRR config missing distribute-list prefix out;\nConfig:\n%s", frrCfg)
- }
- }
- func TestMoveRoutingFilter(t *testing.T) {
- mgr := &Manager{
- config: DynamicConfig{
- RoutingFilters: []RoutingFilterRule{
- {ID: "r1", Chain: "chain-A", RuleOrder: 10, Rule: "dst in 10.1.0.0/16"},
- {ID: "r2", Chain: "chain-A", RuleOrder: 20, Rule: "dst in 10.2.0.0/16"},
- {ID: "r3", Chain: "chain-A", RuleOrder: 30, Rule: "dst in 10.3.0.0/16"},
- {ID: "r-other", Chain: "chain-B", RuleOrder: 10, Rule: "dst in 172.16.0.0/12"},
- },
- },
- }
- // 1. Move r1 "up" (already at top) -> should succeed with no order change
- if err := mgr.MoveRoutingFilter("r1", "up"); err != nil {
- t.Fatalf("Move r1 up failed: %v", err)
- }
- filters := mgr.ListRoutingFilters()
- if filters[0].ID != "r1" || filters[0].RuleOrder != 10 {
- t.Errorf("Expected r1 still at order 10, got %d", filters[0].RuleOrder)
- }
- // 2. Move r1 "down" -> r2 should become order 10, r1 should become order 20
- if err := mgr.MoveRoutingFilter("r1", "down"); err != nil {
- t.Fatalf("Move r1 down failed: %v", err)
- }
- filters = mgr.ListRoutingFilters()
- var r1Order, r2Order int
- for _, f := range filters {
- if f.ID == "r1" {
- r1Order = f.RuleOrder
- }
- if f.ID == "r2" {
- r2Order = f.RuleOrder
- }
- }
- if r2Order != 10 || r1Order != 20 {
- t.Errorf("Expected r2 order 10 and r1 order 20, got r2=%d, r1=%d", r2Order, r1Order)
- }
- // 3. Chain B should be unaffected
- for _, f := range filters {
- if f.ID == "r-other" && f.RuleOrder != 10 {
- t.Errorf("Chain B rule modified unexpectedly: %d", f.RuleOrder)
- }
- }
- // 4. Move r3 (now at index 2 in chain-A) "up" -> should swap with r1 (at order 20)
- if err := mgr.MoveRoutingFilter("r3", "up"); err != nil {
- t.Fatalf("Move r3 up failed: %v", err)
- }
- filters = mgr.ListRoutingFilters()
- var r3Order int
- for _, f := range filters {
- if f.ID == "r1" {
- r1Order = f.RuleOrder
- }
- if f.ID == "r3" {
- r3Order = f.RuleOrder
- }
- }
- if r3Order != 20 || r1Order != 30 {
- t.Errorf("Expected r3 order 20 and r1 order 30, got r3=%d, r1=%d", r3Order, r1Order)
- }
- }
|