package routing import ( "testing" ) func TestGraphNodeCreation(t *testing.T) { // Test Node creation with Station type station := &Node{ ID: "s9600213", Type: NodeTypeStation, Name: "Шереметьево", } if station.ID != "s9600213" { t.Errorf("expected node ID s9600213, got %s", station.ID) } if station.Type != NodeTypeStation { t.Errorf("expected NodeTypeStation, got %v", station.Type) } if station.Name != "Шереметьево" { t.Errorf("expected name Шереметьево, got %s", station.Name) } // Test Node creation with City type city := &Node{ ID: "city:c146", Type: NodeTypeCity, Name: "Simferopol", } if city.ID != "city:c146" { t.Errorf("expected node ID city:c146, got %s", city.ID) } if city.Type != NodeTypeCity { t.Errorf("expected NodeTypeCity, got %v", city.Type) } if city.Name != "Simferopol" { t.Errorf("expected name Simferopol, got %s", city.Name) } } func TestGraphEdgeCreation(t *testing.T) { // Test Real edge realEdge := &Edge{ Kind: EdgeKindReal, Duration: 3600, TransportType: "train", IsTransfer: false, } if realEdge.Kind != EdgeKindReal { t.Errorf("expected EdgeKindReal, got %v", realEdge.Kind) } if realEdge.Duration != 3600 { t.Errorf("expected duration 3600, got %d", realEdge.Duration) } if realEdge.TransportType != "train" { t.Errorf("expected transport_type train, got %s", realEdge.TransportType) } if realEdge.IsTransfer { t.Errorf("expected IsTransfer false for real edge") } // Test Synthetic edge syntheticEdge := &Edge{ Kind: EdgeKindSynthetic, Duration: 1800, TransportType: "bus", IsTransfer: true, } if syntheticEdge.Kind != EdgeKindSynthetic { t.Errorf("expected EdgeKindSynthetic, got %v", syntheticEdge.Kind) } if syntheticEdge.IsTransfer != true { t.Errorf("expected IsTransfer true for synthetic edge") } } func TestGraphAddNodeAndEdge(t *testing.T) { graph := NewGraph() node := &Node{ID: "n1", Type: NodeTypeStation, Name: "Test Station"} graph.AddNode(node) if len(graph.Nodes()) != 1 { t.Errorf("expected 1 node, got %d", len(graph.Nodes())) } if graph.Nodes()[0].ID != "n1" { t.Errorf("expected node n1, got %s", graph.Nodes()[0].ID) } edge := &Edge{From: node, To: node, Kind: EdgeKindReal, Duration: 100} graph.AddEdge(edge) if len(graph.Edges()) != 1 { t.Errorf("expected 1 edge, got %d", len(graph.Edges())) } if graph.Edges()[0].Duration != 100 { t.Errorf("expected duration 100, got %d", graph.Edges()[0].Duration) } } func TestGraphSortEdges(t *testing.T) { edges := []*Edge{ {Duration: 300}, {Duration: 100}, {Duration: 200}, } SortEdges(edges) if edges[0].Duration != 100 { t.Errorf("expected first edge duration 100, got %d", edges[0].Duration) } if edges[1].Duration != 200 { t.Errorf("expected second edge duration 200, got %d", edges[1].Duration) } if edges[2].Duration != 300 { t.Errorf("expected third edge duration 300, got %d", edges[2].Duration) } } func TestBuildGraphFromStations(t *testing.T) { stations := []StationInfo{ {ID: "s9600213", Name: "Шереметьево", CityCode: "c146", CityName: "Simferopol"}, {ID: "s9600396", Name: "Симферополь", CityCode: "c146", CityName: "Simferopol"}, {ID: "s9600157", Name: "Москва", CityCode: "c213", CityName: "Москва"}, } graph := BuildGraphFromStations(stations) // Should have station nodes + city nodes // 3 stations + 2 cities (Simferopol + Moscow) = 5 nodes nodes := graph.Nodes() if len(nodes) != 5 { t.Errorf("expected 5 nodes (3 stations + 2 cities), got %d", len(nodes)) } // Should have edges edges := graph.Edges() if len(edges) < 3 { t.Errorf("expected at least 3 edges (synthetic city↔station), got %d", len(edges)) } // Verify city nodes exist cityIDs := make(map[string]bool) for _, n := range nodes { if n.Type == NodeTypeCity { cityIDs[n.ID] = true } } if !cityIDs["city:c146"] { t.Error("expected city:c146 node") } if !cityIDs["city:c213"] { t.Error("expected city:c213 node") } } func TestGraphNodesAndEdges(t *testing.T) { graph := NewGraph() // Add nodes graph.AddNode(&Node{ID: "n1", Type: NodeTypeStation, Name: "Station 1"}) graph.AddNode(&Node{ID: "n2", Type: NodeTypeStation, Name: "Station 2"}) graph.AddNode(&Node{ID: "city:c1", Type: NodeTypeCity, Name: "City 1"}) if len(graph.Nodes()) != 3 { t.Errorf("expected 3 nodes, got %d", len(graph.Nodes())) } // Add edges graph.AddEdge(&Edge{From: graph.Nodes()[0], To: graph.Nodes()[1], Kind: EdgeKindReal, Duration: 100}) graph.AddEdge(&Edge{From: graph.Nodes()[1], To: graph.Nodes()[2], Kind: EdgeKindSynthetic, Duration: 200}) if len(graph.Edges()) != 2 { t.Errorf("expected 2 edges, got %d", len(graph.Edges())) } } func TestFindRouteSuccess(t *testing.T) { graph := NewGraph() // Add stations graph.AddNode(&Node{ID: "s1", Type: NodeTypeStation, Name: "Moscow", CityCode: "c1"}) graph.AddNode(&Node{ID: "s2", Type: NodeTypeStation, Name: "Tula", CityCode: "c1"}) graph.AddNode(&Node{ID: "s3", Type: NodeTypeStation, Name: "Clinic", CityCode: "c1"}) // Add real edges (direct route) graph.AddEdge(&Edge{From: graph.Nodes()[0], To: graph.Nodes()[1], Kind: EdgeKindReal, Duration: 3600, Transport: "train", IsTransfer: false}) // Add synthetic transfer edge graph.AddEdge(&Edge{From: graph.Nodes()[1], To: graph.Nodes()[2], Kind: EdgeKindSynthetic, Duration: 1800, Transport: "train", IsTransfer: true}) // Search for route with max 1 transfer opts := SearchOptions{MaxTransfers: 1, MCT: 300} result := graph.FindRoute("s1", "s3", opts) if result == nil { t.Error("expected a route to be found") } if result.TotalTransfers > 1 { t.Errorf("expected at most 1 transfer, got %d", result.TotalTransfers) } if result.TotalDuration <= 0 { t.Errorf("expected positive duration, got %d", result.TotalDuration) } } func TestFindRouteNoRoute(t *testing.T) { graph := NewGraph() // Add isolated nodes with no connections graph.AddNode(&Node{ID: "s1", Type: NodeTypeStation, Name: "Station 1", CityCode: "c1"}) graph.AddNode(&Node{ID: "s2", Type: NodeTypeStation, Name: "Station 2", CityCode: "c2"}) // Search with no edges - should return nil opts := SearchOptions{MaxTransfers: 1, MCT: 300} result := graph.FindRoute("s1", "s2", opts) if result != nil { t.Error("expected nil route when no edges exist, got result") } } func TestFindRouteExceedsTransferLimit(t *testing.T) { graph := NewGraph() // Add a chain of stations with synthetic transfer edges (would require 4 transfers) graph.AddNode(&Node{ID: "s1", Type: NodeTypeStation, Name: "Moscow", CityCode: "c1"}) graph.AddNode(&Node{ID: "s2", Type: NodeTypeCity, Name: "City Hub 1", CityCode: "c1"}) graph.AddNode(&Node{ID: "s3", Type: NodeTypeCity, Name: "City Hub 2", CityCode: "c1"}) graph.AddNode(&Node{ID: "s4", Type: NodeTypeCity, Name: "City Hub 3", CityCode: "c1"}) graph.AddNode(&Node{ID: "s5", Type: NodeTypeStation, Name: "Vladimir", CityCode: "c1"}) // Add synthetic transfer edges between consecutive nodes (IsTransfer: true) graph.AddEdge(&Edge{From: graph.Nodes()[0], To: graph.Nodes()[1], Kind: EdgeKindSynthetic, Duration: 300, Transport: "train", IsTransfer: true}) graph.AddEdge(&Edge{From: graph.Nodes()[1], To: graph.Nodes()[2], Kind: EdgeKindSynthetic, Duration: 300, Transport: "train", IsTransfer: true}) graph.AddEdge(&Edge{From: graph.Nodes()[2], To: graph.Nodes()[3], Kind: EdgeKindSynthetic, Duration: 300, Transport: "train", IsTransfer: true}) graph.AddEdge(&Edge{From: graph.Nodes()[3], To: graph.Nodes()[4], Kind: EdgeKindSynthetic, Duration: 300, Transport: "train", IsTransfer: true}) // Search with max 1 transfer - should not find route requiring 4 transfers opts := SearchOptions{MaxTransfers: 1, MCT: 300} result := graph.FindRoute("s1", "s5", opts) if result != nil { t.Error("expected nil route when transfers exceed limit, got result") } } func TestApplyMCT_CityHubReducesMCT(t *testing.T) { graph := NewGraph() // Create legs with city hub transfers graph.AddNode(&Node{ID: "s1", Type: NodeTypeStation, Name: "Moscow", CityCode: "c1"}) graph.AddNode(&Node{ID: "s2", Type: NodeTypeCity, Name: "City Hub", CityCode: "c1"}) graph.AddNode(&Node{ID: "s3", Type: NodeTypeStation, Name: "Tula", CityCode: "c1"}) // Add real edges graph.AddEdge(&Edge{From: graph.Nodes()[0], To: graph.Nodes()[1], Kind: EdgeKindReal, Duration: 3600, Transport: "train", IsTransfer: false}) graph.AddEdge(&Edge{From: graph.Nodes()[1], To: graph.Nodes()[2], Kind: EdgeKindReal, Duration: 3600, Transport: "train", IsTransfer: false}) itinerary := &Itinerary{ Legs: []RouteLeg{ {From: graph.Nodes()[0], To: graph.Nodes()[1], Duration: 3600, Transport: "train", IsTransfer: false}, {From: graph.Nodes()[1], To: graph.Nodes()[2], Duration: 3600, Transport: "train", IsTransfer: false}, }, TotalDuration: 0, TotalTransfers: 0, } result := graph.ApplyMCT(itinerary, 1800) // 30 min base MCT // City hub transfer reduces MCT from 30 min (1800) to 15 min (900) // TotalDuration only includes the MCT addition (starts at 0), so result = 900 if result.TotalDuration != 900 { t.Errorf("expected total duration 900 (reduced MCT for city hub), got %d", result.TotalDuration) } } func TestApplyMCT_ModeChangeIncreasesMCT(t *testing.T) { graph := NewGraph() // Create legs with mode change graph.AddNode(&Node{ID: "s1", Type: NodeTypeStation, Name: "Moscow", CityCode: "c1"}) graph.AddNode(&Node{ID: "s2", Type: NodeTypeStation, Name: "Tula", CityCode: "c1"}) // Add first leg (train) graph.AddEdge(&Edge{From: graph.Nodes()[0], To: graph.Nodes()[1], Kind: EdgeKindReal, Duration: 3600, Transport: "train", IsTransfer: false}) itinerary := &Itinerary{ Legs: []RouteLeg{ {From: graph.Nodes()[0], To: graph.Nodes()[1], Duration: 3600, Transport: "train", IsTransfer: false}, }, TotalDuration: 0, TotalTransfers: 0, } // With only 1 leg, ApplyMCT returns early - no transfers needed result := graph.ApplyMCT(itinerary, 1800) // Single leg means no transfer, TotalDuration stays at 0 if result.TotalDuration != 0 { t.Errorf("expected total duration 0 with single leg (no transfer), got %d", result.TotalDuration) } } func TestApplyMCT_ModeChangeBetweenLegs(t *testing.T) { graph := NewGraph() // Create 2 stations for 2 legs with mode change (train then bus) graph.AddNode(&Node{ID: "s1", Type: NodeTypeStation, Name: "Moscow", CityCode: "c1"}) graph.AddNode(&Node{ID: "s2", Type: NodeTypeStation, Name: "Tula", CityCode: "c1"}) graph.AddNode(&Node{ID: "s3", Type: NodeTypeStation, Name: "Vladimir", CityCode: "c1"}) // Add real edges - train then bus (mode change) graph.AddEdge(&Edge{From: graph.Nodes()[0], To: graph.Nodes()[1], Kind: EdgeKindReal, Duration: 3600, Transport: "train", IsTransfer: false}) graph.AddEdge(&Edge{From: graph.Nodes()[1], To: graph.Nodes()[2], Kind: EdgeKindReal, Duration: 3600, Transport: "bus", IsTransfer: false}) itinerary := &Itinerary{ Legs: []RouteLeg{ {From: graph.Nodes()[0], To: graph.Nodes()[1], Duration: 3600, Transport: "train", IsTransfer: false}, {From: graph.Nodes()[1], To: graph.Nodes()[2], Duration: 3600, Transport: "bus", IsTransfer: false}, }, TotalDuration: 0, TotalTransfers: 0, } result := graph.ApplyMCT(itinerary, 1800) // 30 min base MCT // Mode change increases MCT from 30 min (1800) to 30+10 = 40 min (2400) // TotalDuration only includes the MCT addition (one transfer), so result = 2400 if result.TotalDuration != 2400 { t.Errorf("expected total duration 2400 (mode change MCT), got %d", result.TotalDuration) } }