manager_test.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. package routing
  2. import (
  3. "encoding/json"
  4. "os"
  5. "path/filepath"
  6. "testing"
  7. )
  8. func TestDeduplicationOnLoad(t *testing.T) {
  9. tmpDir := t.TempDir()
  10. storePath := filepath.Join(tmpDir, "static_routes.json")
  11. // Create test JSON with duplicate routes
  12. routes := []RouteItem{
  13. {
  14. ID: "route-1",
  15. DstAddress: "10.0.0.0/8",
  16. Gateway: "10.42.36.254",
  17. Interface: "AWMN-Internal",
  18. RoutingTable: "main",
  19. Distance: 1,
  20. Comment: "Original",
  21. },
  22. {
  23. ID: "route-2",
  24. DstAddress: "10.0.0.0/8",
  25. Gateway: "10.42.36.254",
  26. Interface: "AWMN-Internal",
  27. RoutingTable: "main",
  28. Distance: 1,
  29. Comment: "Duplicate",
  30. },
  31. {
  32. ID: "route-3",
  33. DstAddress: "192.168.1.0/24",
  34. Gateway: "192.168.1.1",
  35. RoutingTable: "main",
  36. Distance: 1,
  37. },
  38. }
  39. data, err := json.Marshal(routes)
  40. if err != nil {
  41. t.Fatalf("failed to marshal routes: %v", err)
  42. }
  43. if err := os.WriteFile(storePath, data, 0600); err != nil {
  44. t.Fatalf("failed to write test file: %v", err)
  45. }
  46. mgr := &RoutingManager{
  47. staticRoutes: make([]RouteItem, 0),
  48. }
  49. // Read and deduplicate using the exact logic from load()
  50. seenIDs := make(map[string]bool)
  51. seenKeys := make(map[string]bool)
  52. var dedupedRev []RouteItem
  53. hasDups := false
  54. var loadedRoutes []RouteItem
  55. _ = json.Unmarshal(data, &loadedRoutes)
  56. for i := len(loadedRoutes) - 1; i >= 0; i-- {
  57. r := loadedRoutes[i]
  58. normDst := normalizeDst(r.DstAddress)
  59. tbl := r.RoutingTable
  60. if tbl == "" {
  61. tbl = "main"
  62. }
  63. routeKey := normDst + "|" + tbl + "|" + r.Gateway + "|" + r.Interface + "|1"
  64. if seenIDs[r.ID] || seenKeys[routeKey] {
  65. hasDups = true
  66. continue
  67. }
  68. seenIDs[r.ID] = true
  69. seenKeys[routeKey] = true
  70. dedupedRev = append(dedupedRev, r)
  71. }
  72. var deduped []RouteItem
  73. for i := len(dedupedRev) - 1; i >= 0; i-- {
  74. deduped = append(deduped, dedupedRev[i])
  75. }
  76. mgr.staticRoutes = deduped
  77. if !hasDups {
  78. t.Errorf("expected duplicates to be detected")
  79. }
  80. if len(mgr.staticRoutes) != 2 {
  81. t.Fatalf("expected 2 routes after dedup, got %d", len(mgr.staticRoutes))
  82. }
  83. // The latest entry for 10.0.0.0/8 should have been preserved
  84. if mgr.staticRoutes[0].ID != "route-2" || mgr.staticRoutes[0].Comment != "Duplicate" {
  85. t.Errorf("expected route-2 (latest) to be kept, got %+v", mgr.staticRoutes[0])
  86. }
  87. }
  88. func TestSaveRouteUpdateNoDuplicates(t *testing.T) {
  89. mgr := &RoutingManager{
  90. staticRoutes: []RouteItem{
  91. {
  92. ID: "route-100",
  93. DstAddress: "10.0.0.0/8",
  94. Gateway: "10.42.36.254",
  95. Interface: "AWMN-Internal",
  96. RoutingTable: "main",
  97. Distance: 1,
  98. Comment: "Original AWMN Route",
  99. },
  100. },
  101. }
  102. // 1. Edit using matching ID
  103. editReq1 := RouteItem{
  104. ID: "route-100",
  105. DstAddress: "10.0.0.0/8",
  106. Gateway: "10.42.36.253",
  107. Interface: "AWMN-Internal",
  108. RoutingTable: "main",
  109. Distance: 1,
  110. Comment: "Updated Gateway",
  111. }
  112. // Test findIndex logic
  113. foundIndex := -1
  114. for i, existing := range mgr.staticRoutes {
  115. if existing.ID == editReq1.ID {
  116. foundIndex = i
  117. break
  118. }
  119. }
  120. if foundIndex != 0 {
  121. t.Fatalf("expected foundIndex 0, got %d", foundIndex)
  122. }
  123. mgr.staticRoutes[foundIndex] = editReq1
  124. if len(mgr.staticRoutes) != 1 {
  125. t.Fatalf("expected 1 route, got %d (duplicate created!)", len(mgr.staticRoutes))
  126. }
  127. if mgr.staticRoutes[0].Gateway != "10.42.36.253" {
  128. t.Errorf("expected gateway 10.42.36.253, got %s", mgr.staticRoutes[0].Gateway)
  129. }
  130. // 2. Edit where UI sent kr-... ID (kernel route adoption/edit) but with OldDstAddress and OldGateway
  131. editReq2 := RouteItem{
  132. ID: "kr-254-10.0.0.0_8-10.42.36.253",
  133. OldDstAddress: "10.0.0.0/8",
  134. OldGateway: "10.42.36.253",
  135. OldRoutingTable: "main",
  136. OldInterface: "AWMN-Internal",
  137. DstAddress: "10.0.0.0/8",
  138. Gateway: "10.42.36.252",
  139. Interface: "AWMN-Internal",
  140. RoutingTable: "main",
  141. Distance: 1,
  142. CheckGateway: "internet",
  143. CheckTarget: "10.22.11.4",
  144. }
  145. foundIndex = -1
  146. // Match by OldDstAddress / OldGateway
  147. normOldDst := normalizeDst(editReq2.OldDstAddress)
  148. for i, existing := range mgr.staticRoutes {
  149. if normalizeDst(existing.DstAddress) == normOldDst && existing.RoutingTable == editReq2.OldRoutingTable {
  150. if existing.Gateway == editReq2.OldGateway || existing.Interface == editReq2.OldGateway {
  151. foundIndex = i
  152. break
  153. }
  154. }
  155. }
  156. if foundIndex != 0 {
  157. t.Fatalf("expected foundIndex 0 from OldDst/OldGw match, got %d", foundIndex)
  158. }
  159. // Preserve ID
  160. editReq2.ID = mgr.staticRoutes[foundIndex].ID
  161. mgr.staticRoutes[foundIndex] = editReq2
  162. if len(mgr.staticRoutes) != 1 {
  163. t.Fatalf("expected 1 route, got %d (duplicate created!)", len(mgr.staticRoutes))
  164. }
  165. if mgr.staticRoutes[0].ID != "route-100" {
  166. t.Errorf("expected ID route-100 preserved, got %s", mgr.staticRoutes[0].ID)
  167. }
  168. if mgr.staticRoutes[0].Gateway != "10.42.36.252" {
  169. t.Errorf("expected gateway 10.42.36.252, got %s", mgr.staticRoutes[0].Gateway)
  170. }
  171. if mgr.staticRoutes[0].CheckTarget != "10.22.11.4" {
  172. t.Errorf("expected check_target 10.22.11.4, got %s", mgr.staticRoutes[0].CheckTarget)
  173. }
  174. }
  175. func TestDeleteRouteMatchingKrID(t *testing.T) {
  176. mgr := &RoutingManager{
  177. staticRoutes: []RouteItem{
  178. {
  179. ID: "route-100",
  180. DstAddress: "10.0.0.0/8",
  181. Gateway: "10.42.36.254",
  182. Interface: "AWMN-Internal",
  183. RoutingTable: "main",
  184. Distance: 1,
  185. },
  186. {
  187. ID: "route-200",
  188. DstAddress: "0.0.0.0/0",
  189. Gateway: "sdwan0",
  190. Interface: "sdwan0",
  191. RoutingTable: "main",
  192. Distance: 1,
  193. },
  194. },
  195. }
  196. // Simulating a live route from kernel corresponding to route-100
  197. liveRoute := RouteItem{
  198. ID: "kr-254-10.0.0.0_8-10.42.36.254",
  199. DstAddress: "10.0.0.0/8",
  200. Gateway: "10.42.36.254",
  201. Interface: "AWMN-Internal",
  202. RoutingTable: "main",
  203. Distance: 1,
  204. }
  205. // If user clicks delete on kr-254-10.0.0.0_8-10.42.36.254
  206. idToDelete := liveRoute.ID
  207. targetIdx := -1
  208. for i, r := range mgr.staticRoutes {
  209. if r.ID == idToDelete {
  210. targetIdx = i
  211. break
  212. }
  213. }
  214. if targetIdx == -1 {
  215. // Fallback to matching live route against static routes
  216. normLrDst := normalizeDst(liveRoute.DstAddress)
  217. for i, sr := range mgr.staticRoutes {
  218. gwMatches := sr.Gateway == "" || liveRoute.Gateway == sr.Gateway || liveRoute.Interface == sr.Gateway
  219. if normalizeDst(sr.DstAddress) == normLrDst && sr.RoutingTable == liveRoute.RoutingTable && gwMatches {
  220. targetIdx = i
  221. break
  222. }
  223. }
  224. }
  225. if targetIdx != 0 {
  226. t.Fatalf("expected targetIdx 0, got %d", targetIdx)
  227. }
  228. // Remove target
  229. mgr.staticRoutes = append(mgr.staticRoutes[:targetIdx], mgr.staticRoutes[targetIdx+1:]...)
  230. if len(mgr.staticRoutes) != 1 {
  231. t.Fatalf("expected 1 route remaining, got %d", len(mgr.staticRoutes))
  232. }
  233. if mgr.staticRoutes[0].ID != "route-200" {
  234. t.Errorf("expected route-200 to remain, got %s", mgr.staticRoutes[0].ID)
  235. }
  236. }
  237. func TestNormalizeDst(t *testing.T) {
  238. if normalizeDst("default") != "default" {
  239. t.Errorf("expected default, got %s", normalizeDst("default"))
  240. }
  241. if normalizeDst("0.0.0.0/0") != "default" {
  242. t.Errorf("expected default, got %s", normalizeDst("0.0.0.0/0"))
  243. }
  244. if normalizeDst("") != "default" {
  245. t.Errorf("expected default, got %s", normalizeDst(""))
  246. }
  247. if normalizeDst("10.0.0.0/8") != "10.0.0.0/8" {
  248. t.Errorf("expected 10.0.0.0/8, got %s", normalizeDst("10.0.0.0/8"))
  249. }
  250. }