| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- package routing
- import (
- "encoding/json"
- "os"
- "path/filepath"
- "testing"
- )
- func TestDeduplicationOnLoad(t *testing.T) {
- tmpDir := t.TempDir()
- storePath := filepath.Join(tmpDir, "static_routes.json")
- // Create test JSON with duplicate routes
- routes := []RouteItem{
- {
- ID: "route-1",
- DstAddress: "10.0.0.0/8",
- Gateway: "10.42.36.254",
- Interface: "AWMN-Internal",
- RoutingTable: "main",
- Distance: 1,
- Comment: "Original",
- },
- {
- ID: "route-2",
- DstAddress: "10.0.0.0/8",
- Gateway: "10.42.36.254",
- Interface: "AWMN-Internal",
- RoutingTable: "main",
- Distance: 1,
- Comment: "Duplicate",
- },
- {
- ID: "route-3",
- DstAddress: "192.168.1.0/24",
- Gateway: "192.168.1.1",
- RoutingTable: "main",
- Distance: 1,
- },
- }
- data, err := json.Marshal(routes)
- if err != nil {
- t.Fatalf("failed to marshal routes: %v", err)
- }
- if err := os.WriteFile(storePath, data, 0600); err != nil {
- t.Fatalf("failed to write test file: %v", err)
- }
- mgr := &RoutingManager{
- staticRoutes: make([]RouteItem, 0),
- }
- // Read and deduplicate using the exact logic from load()
- seenIDs := make(map[string]bool)
- seenKeys := make(map[string]bool)
- var dedupedRev []RouteItem
- hasDups := false
- var loadedRoutes []RouteItem
- _ = json.Unmarshal(data, &loadedRoutes)
- for i := len(loadedRoutes) - 1; i >= 0; i-- {
- r := loadedRoutes[i]
- normDst := normalizeDst(r.DstAddress)
- tbl := r.RoutingTable
- if tbl == "" {
- tbl = "main"
- }
- routeKey := normDst + "|" + tbl + "|" + r.Gateway + "|" + r.Interface + "|1"
- if seenIDs[r.ID] || seenKeys[routeKey] {
- hasDups = true
- continue
- }
- seenIDs[r.ID] = true
- seenKeys[routeKey] = true
- dedupedRev = append(dedupedRev, r)
- }
- var deduped []RouteItem
- for i := len(dedupedRev) - 1; i >= 0; i-- {
- deduped = append(deduped, dedupedRev[i])
- }
- mgr.staticRoutes = deduped
- if !hasDups {
- t.Errorf("expected duplicates to be detected")
- }
- if len(mgr.staticRoutes) != 2 {
- t.Fatalf("expected 2 routes after dedup, got %d", len(mgr.staticRoutes))
- }
- // The latest entry for 10.0.0.0/8 should have been preserved
- if mgr.staticRoutes[0].ID != "route-2" || mgr.staticRoutes[0].Comment != "Duplicate" {
- t.Errorf("expected route-2 (latest) to be kept, got %+v", mgr.staticRoutes[0])
- }
- }
- func TestSaveRouteUpdateNoDuplicates(t *testing.T) {
- mgr := &RoutingManager{
- staticRoutes: []RouteItem{
- {
- ID: "route-100",
- DstAddress: "10.0.0.0/8",
- Gateway: "10.42.36.254",
- Interface: "AWMN-Internal",
- RoutingTable: "main",
- Distance: 1,
- Comment: "Original AWMN Route",
- },
- },
- }
- // 1. Edit using matching ID
- editReq1 := RouteItem{
- ID: "route-100",
- DstAddress: "10.0.0.0/8",
- Gateway: "10.42.36.253",
- Interface: "AWMN-Internal",
- RoutingTable: "main",
- Distance: 1,
- Comment: "Updated Gateway",
- }
- // Test findIndex logic
- foundIndex := -1
- for i, existing := range mgr.staticRoutes {
- if existing.ID == editReq1.ID {
- foundIndex = i
- break
- }
- }
- if foundIndex != 0 {
- t.Fatalf("expected foundIndex 0, got %d", foundIndex)
- }
- mgr.staticRoutes[foundIndex] = editReq1
- if len(mgr.staticRoutes) != 1 {
- t.Fatalf("expected 1 route, got %d (duplicate created!)", len(mgr.staticRoutes))
- }
- if mgr.staticRoutes[0].Gateway != "10.42.36.253" {
- t.Errorf("expected gateway 10.42.36.253, got %s", mgr.staticRoutes[0].Gateway)
- }
- // 2. Edit where UI sent kr-... ID (kernel route adoption/edit) but with OldDstAddress and OldGateway
- editReq2 := RouteItem{
- ID: "kr-254-10.0.0.0_8-10.42.36.253",
- OldDstAddress: "10.0.0.0/8",
- OldGateway: "10.42.36.253",
- OldRoutingTable: "main",
- OldInterface: "AWMN-Internal",
- DstAddress: "10.0.0.0/8",
- Gateway: "10.42.36.252",
- Interface: "AWMN-Internal",
- RoutingTable: "main",
- Distance: 1,
- CheckGateway: "internet",
- CheckTarget: "10.22.11.4",
- }
- foundIndex = -1
- // Match by OldDstAddress / OldGateway
- normOldDst := normalizeDst(editReq2.OldDstAddress)
- for i, existing := range mgr.staticRoutes {
- if normalizeDst(existing.DstAddress) == normOldDst && existing.RoutingTable == editReq2.OldRoutingTable {
- if existing.Gateway == editReq2.OldGateway || existing.Interface == editReq2.OldGateway {
- foundIndex = i
- break
- }
- }
- }
- if foundIndex != 0 {
- t.Fatalf("expected foundIndex 0 from OldDst/OldGw match, got %d", foundIndex)
- }
- // Preserve ID
- editReq2.ID = mgr.staticRoutes[foundIndex].ID
- mgr.staticRoutes[foundIndex] = editReq2
- if len(mgr.staticRoutes) != 1 {
- t.Fatalf("expected 1 route, got %d (duplicate created!)", len(mgr.staticRoutes))
- }
- if mgr.staticRoutes[0].ID != "route-100" {
- t.Errorf("expected ID route-100 preserved, got %s", mgr.staticRoutes[0].ID)
- }
- if mgr.staticRoutes[0].Gateway != "10.42.36.252" {
- t.Errorf("expected gateway 10.42.36.252, got %s", mgr.staticRoutes[0].Gateway)
- }
- if mgr.staticRoutes[0].CheckTarget != "10.22.11.4" {
- t.Errorf("expected check_target 10.22.11.4, got %s", mgr.staticRoutes[0].CheckTarget)
- }
- }
- func TestDeleteRouteMatchingKrID(t *testing.T) {
- mgr := &RoutingManager{
- staticRoutes: []RouteItem{
- {
- ID: "route-100",
- DstAddress: "10.0.0.0/8",
- Gateway: "10.42.36.254",
- Interface: "AWMN-Internal",
- RoutingTable: "main",
- Distance: 1,
- },
- {
- ID: "route-200",
- DstAddress: "0.0.0.0/0",
- Gateway: "sdwan0",
- Interface: "sdwan0",
- RoutingTable: "main",
- Distance: 1,
- },
- },
- }
- // Simulating a live route from kernel corresponding to route-100
- liveRoute := RouteItem{
- ID: "kr-254-10.0.0.0_8-10.42.36.254",
- DstAddress: "10.0.0.0/8",
- Gateway: "10.42.36.254",
- Interface: "AWMN-Internal",
- RoutingTable: "main",
- Distance: 1,
- }
- // If user clicks delete on kr-254-10.0.0.0_8-10.42.36.254
- idToDelete := liveRoute.ID
- targetIdx := -1
- for i, r := range mgr.staticRoutes {
- if r.ID == idToDelete {
- targetIdx = i
- break
- }
- }
- if targetIdx == -1 {
- // Fallback to matching live route against static routes
- normLrDst := normalizeDst(liveRoute.DstAddress)
- for i, sr := range mgr.staticRoutes {
- gwMatches := sr.Gateway == "" || liveRoute.Gateway == sr.Gateway || liveRoute.Interface == sr.Gateway
- if normalizeDst(sr.DstAddress) == normLrDst && sr.RoutingTable == liveRoute.RoutingTable && gwMatches {
- targetIdx = i
- break
- }
- }
- }
- if targetIdx != 0 {
- t.Fatalf("expected targetIdx 0, got %d", targetIdx)
- }
- // Remove target
- mgr.staticRoutes = append(mgr.staticRoutes[:targetIdx], mgr.staticRoutes[targetIdx+1:]...)
- if len(mgr.staticRoutes) != 1 {
- t.Fatalf("expected 1 route remaining, got %d", len(mgr.staticRoutes))
- }
- if mgr.staticRoutes[0].ID != "route-200" {
- t.Errorf("expected route-200 to remain, got %s", mgr.staticRoutes[0].ID)
- }
- }
- func TestNormalizeDst(t *testing.T) {
- if normalizeDst("default") != "default" {
- t.Errorf("expected default, got %s", normalizeDst("default"))
- }
- if normalizeDst("0.0.0.0/0") != "default" {
- t.Errorf("expected default, got %s", normalizeDst("0.0.0.0/0"))
- }
- if normalizeDst("") != "default" {
- t.Errorf("expected default, got %s", normalizeDst(""))
- }
- if normalizeDst("10.0.0.0/8") != "10.0.0.0/8" {
- t.Errorf("expected 10.0.0.0/8, got %s", normalizeDst("10.0.0.0/8"))
- }
- }
|