package routingtable import ( "sync" "testing" "time" "git.eeqj.de/sneak/routewatch/internal/config" "git.eeqj.de/sneak/routewatch/internal/logger" "github.com/google/uuid" ) func TestRoutingTable(t *testing.T) { // Create a test logger logger := logger.New() // Create test config with empty state dir (no snapshot loading) cfg := &config.Config{ StateDir: "", } rt := New(cfg, logger) // Test data prefixID1 := uuid.New() prefixID2 := uuid.New() originASNID1 := uuid.New() originASNID2 := uuid.New() route1 := &Route{ PrefixID: prefixID1, Prefix: "10.0.0.0/8", OriginASNID: originASNID1, OriginASN: 64512, PeerASN: 64513, ASPath: []int{64513, 64512}, NextHop: "192.168.1.1", AnnouncedAt: time.Now(), } route2 := &Route{ PrefixID: prefixID2, Prefix: "192.168.0.0/16", OriginASNID: originASNID2, OriginASN: 64514, PeerASN: 64513, ASPath: []int{64513, 64514}, NextHop: "192.168.1.1", AnnouncedAt: time.Now(), } // Test AddRoute rt.AddRoute(route1) rt.AddRoute(route2) if rt.Size() != 2 { t.Errorf("Expected 2 routes, got %d", rt.Size()) } // Test GetRoute retrievedRoute, exists := rt.GetRoute(prefixID1, originASNID1, 64513) if !exists { t.Error("Route 1 should exist") } if retrievedRoute.Prefix != "10.0.0.0/8" { t.Errorf("Expected prefix 10.0.0.0/8, got %s", retrievedRoute.Prefix) } // Test GetRoutesByPrefix prefixRoutes := rt.GetRoutesByPrefix(prefixID1) if len(prefixRoutes) != 1 { t.Errorf("Expected 1 route for prefix, got %d", len(prefixRoutes)) } // Test GetRoutesByPeerASN peerRoutes := rt.GetRoutesByPeerASN(64513) if len(peerRoutes) != 2 { t.Errorf("Expected 2 routes from peer 64513, got %d", len(peerRoutes)) } // Test RemoveRoute removed := rt.RemoveRoute(prefixID1, originASNID1, 64513) if !removed { t.Error("Route should have been removed") } if rt.Size() != 1 { t.Errorf("Expected 1 route after removal, got %d", rt.Size()) } // Test WithdrawRoutesByPrefixAndPeer // Add the route back first rt.AddRoute(route1) // Add another route for the same prefix from the same peer route3 := &Route{ PrefixID: prefixID1, Prefix: "10.0.0.0/8", OriginASNID: originASNID2, // Different origin OriginASN: 64515, PeerASN: 64513, ASPath: []int{64513, 64515}, NextHop: "192.168.1.1", AnnouncedAt: time.Now(), } rt.AddRoute(route3) count := rt.WithdrawRoutesByPrefixAndPeer(prefixID1, 64513) if count != 2 { t.Errorf("Expected to withdraw 2 routes, withdrew %d", count) } // Should only have route2 left if rt.Size() != 1 { t.Errorf("Expected 1 route after withdrawal, got %d", rt.Size()) } // Test Stats stats := rt.Stats() if stats["total_routes"] != 1 { t.Errorf("Expected 1 total route in stats, got %d", stats["total_routes"]) } // Test Clear rt.Clear() if rt.Size() != 0 { t.Errorf("Expected 0 routes after clear, got %d", rt.Size()) } } func TestRoutingTableConcurrency(t *testing.T) { // Create a test logger logger := logger.New() // Create test config with empty state dir (no snapshot loading) cfg := &config.Config{ StateDir: "", } rt := New(cfg, logger) // Test concurrent access var wg sync.WaitGroup numGoroutines := 10 numOperations := 100 // Start multiple goroutines that add/remove routes for i := 0; i < numGoroutines; i++ { wg.Add(1) go func(id int) { defer wg.Done() for j := 0; j < numOperations; j++ { prefixID := uuid.New() originASNID := uuid.New() route := &Route{ PrefixID: prefixID, Prefix: "10.0.0.0/8", OriginASNID: originASNID, OriginASN: 64512 + id, PeerASN: 64500, ASPath: []int{64500, 64512 + id}, NextHop: "192.168.1.1", AnnouncedAt: time.Now(), } // Add route rt.AddRoute(route) // Try to get it _, _ = rt.GetRoute(prefixID, originASNID, 64500) // Get stats _ = rt.Stats() // Remove it rt.RemoveRoute(prefixID, originASNID, 64500) } }(i) } wg.Wait() // Table should be empty after all operations if rt.Size() != 0 { t.Errorf("Expected empty table after concurrent operations, got %d routes", rt.Size()) } } func TestRouteUpdate(t *testing.T) { // Create a test logger logger := logger.New() // Create test config with empty state dir (no snapshot loading) cfg := &config.Config{ StateDir: "", } rt := New(cfg, logger) prefixID := uuid.New() originASNID := uuid.New() route1 := &Route{ PrefixID: prefixID, Prefix: "10.0.0.0/8", OriginASNID: originASNID, OriginASN: 64512, PeerASN: 64513, ASPath: []int{64513, 64512}, NextHop: "192.168.1.1", AnnouncedAt: time.Now(), } // Add initial route rt.AddRoute(route1) // Update the same route with new next hop route2 := &Route{ PrefixID: prefixID, Prefix: "10.0.0.0/8", OriginASNID: originASNID, OriginASN: 64512, PeerASN: 64513, ASPath: []int{64513, 64512}, NextHop: "192.168.1.2", // Changed AnnouncedAt: time.Now().Add(1 * time.Minute), } rt.AddRoute(route2) // Should still have only 1 route if rt.Size() != 1 { t.Errorf("Expected 1 route after update, got %d", rt.Size()) } // Check that the route was updated retrievedRoute, exists := rt.GetRoute(prefixID, originASNID, 64513) if !exists { t.Error("Route should exist after update") } if retrievedRoute.NextHop != "192.168.1.2" { t.Errorf("Expected updated next hop 192.168.1.2, got %s", retrievedRoute.NextHop) } }