feat: implement lazy graph expansion - Task 1: hub station selection, BuildGraphFromHubs, and ExpandGraphLazy
This commit is contained in:
@@ -556,3 +556,231 @@ func TestSortEdges_ReverseSorted(t *testing.T) {
|
||||
t.Error("expected edges to be sorted from shortest to longest")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSelectHubStations tests hub station selection by outgoing flights.
|
||||
func TestSelectHubStations(t *testing.T) {
|
||||
stations := []StationInfo{
|
||||
{ID: "s1", Name: "Moscow", CityCode: "m1", CityName: "Moscow"},
|
||||
{ID: "s2", Name: "SmallTown", CityCode: "s1", CityName: "Townville"},
|
||||
{ID: "s3", Name: "CapitalCity", CityCode: "c1", CityName: "Capital"},
|
||||
}
|
||||
|
||||
// With minOutgoingFlights=1, stations with default outgoing flights are hubs
|
||||
// defaultOutgoingFlights is set to 1 so stations get selected
|
||||
criteria := hubCriteria{minPopulation: 1, minOutgoingFlights: 1, defaultOutgoingFlights: 1}
|
||||
result := SelectHubStations(stations, criteria)
|
||||
|
||||
// Moscow has default outgoing flights and should be a hub
|
||||
moscowFound := false
|
||||
for _, hub := range result.Hubs {
|
||||
if hub.Station.Name == "Moscow" {
|
||||
moscowFound = true
|
||||
if !hub.IsHub {
|
||||
t.Error("Moscow should be selected as a hub with minOutgoingFlights=1")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !moscowFound {
|
||||
t.Error("expected Moscow to be in hub selection results")
|
||||
}
|
||||
|
||||
// With high minOutgoingFlights, all stations should be rejected
|
||||
highCriteria := hubCriteria{minPopulation: 1, minOutgoingFlights: 100}
|
||||
highResult := SelectHubStations(stations, highCriteria)
|
||||
|
||||
// All stations should be rejected when threshold is too high
|
||||
allRejected := true
|
||||
for _, hub := range highResult.Hubs {
|
||||
if hub.IsHub {
|
||||
allRejected = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !allRejected {
|
||||
t.Error("expected all stations to be rejected with minOutgoingFlights=100")
|
||||
}
|
||||
|
||||
// Verify all rejected stations have IsHub=false
|
||||
for _, hub := range highResult.Rejected {
|
||||
if hub.IsHub {
|
||||
t.Error("rejected station should have IsHub=false")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildGraphFromHubs tests graph building from hub stations.
|
||||
func TestBuildGraphFromHubs(t *testing.T) {
|
||||
stations := []StationInfo{
|
||||
{ID: "s1", Name: "Moscow", CityCode: "m1", CityName: "Moscow"},
|
||||
{ID: "s2", Name: "Tula", CityCode: "m1", CityName: "Tula"},
|
||||
{ID: "s3", Name: "Simferopol", CityCode: "c1", CityName: "Simferopol"},
|
||||
{ID: "s4", Name: "SmallCity", CityCode: "s1", CityName: "Smallville"},
|
||||
}
|
||||
|
||||
criteria := hubCriteria{minPopulation: 1, minOutgoingFlights: 10, defaultOutgoingFlights: 10}
|
||||
graph := BuildGraphFromHubs(stations, criteria)
|
||||
|
||||
// Should have station nodes + city nodes
|
||||
nodes := graph.Nodes()
|
||||
if len(nodes) < 3 {
|
||||
t.Errorf("expected at least 3 nodes (stations + cities), got %d", len(nodes))
|
||||
}
|
||||
|
||||
// Should have edges
|
||||
edges := graph.Edges()
|
||||
if len(edges) < 2 {
|
||||
t.Errorf("expected at least 2 edges, 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:m1"] {
|
||||
t.Error("expected city:m1 node")
|
||||
}
|
||||
if !cityIDs["city:c1"] {
|
||||
t.Error("expected city:c1 node")
|
||||
}
|
||||
|
||||
// Verify hub stations are connected to city hubs
|
||||
// Find edges from Moscow to city hub
|
||||
moscowEdges := 0
|
||||
for _, e := range edges {
|
||||
if e.From != nil && e.From.Name == "Moscow" {
|
||||
moscowEdges++
|
||||
}
|
||||
}
|
||||
if moscowEdges == 0 {
|
||||
t.Error("expected edges from Moscow to city hub")
|
||||
}
|
||||
}
|
||||
|
||||
// TestExpandGraphLazy tests the lazy graph expansion method.
|
||||
func TestExpandGraphLazy(t *testing.T) {
|
||||
graph := NewGraph()
|
||||
|
||||
// Add a station node
|
||||
moscow := &Node{
|
||||
ID: "s1",
|
||||
Type: NodeTypeStation,
|
||||
Name: "Moscow",
|
||||
}
|
||||
graph.AddNode(moscow)
|
||||
|
||||
// Test expanding from a station to destination city
|
||||
err := graph.ExpandGraphLazy(moscow, "Simferopol")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from ExpandGraphLazy, got: %v", err)
|
||||
}
|
||||
|
||||
// Should have added edges from Moscow to Simferopol city hub
|
||||
nodes := graph.Nodes()
|
||||
if len(nodes) < 2 {
|
||||
t.Errorf("expected at least 2 nodes (Moscow + Simferopol city), got %d", len(nodes))
|
||||
}
|
||||
|
||||
edges := graph.Edges()
|
||||
if len(edges) < 2 {
|
||||
t.Errorf("expected at least 2 edges (forward and reverse), got %d", len(edges))
|
||||
}
|
||||
|
||||
// Verify the edge exists
|
||||
moscowToSimferopol := false
|
||||
simferopolToMoscow := false
|
||||
for _, e := range edges {
|
||||
if e.From != nil && e.From.Name == "Moscow" && e.To != nil && e.To.Name == "Simferopol" {
|
||||
moscowToSimferopol = true
|
||||
}
|
||||
if e.From != nil && e.From.Name == "Simferopol" && e.To != nil && e.To.Name == "Moscow" {
|
||||
simferopolToMoscow = true
|
||||
}
|
||||
}
|
||||
if !moscowToSimferopol {
|
||||
t.Error("expected edge from Moscow to Simferopol")
|
||||
}
|
||||
if !simferopolToMoscow {
|
||||
t.Error("expected edge from Simferopol to Moscow")
|
||||
}
|
||||
}
|
||||
|
||||
// TestExpandGraphLazy_FromCityHub tests expansion from a city hub.
|
||||
func TestExpandGraphLazy_FromCityHub(t *testing.T) {
|
||||
graph := NewGraph()
|
||||
|
||||
// Add a city hub node
|
||||
simferopol := &Node{
|
||||
ID: "city:c1",
|
||||
Type: NodeTypeCity,
|
||||
Name: "Simferopol",
|
||||
}
|
||||
graph.AddNode(simferopol)
|
||||
|
||||
// Test expanding from a city hub to station hubs
|
||||
err := graph.ExpandGraphLazy(simferopol, "Moscow")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from ExpandGraphLazy, got: %v", err)
|
||||
}
|
||||
|
||||
// Should have added edges from Simferopol city to station hubs
|
||||
edges := graph.Edges()
|
||||
if len(edges) == 0 {
|
||||
t.Error("expected edges from city hub to station hubs")
|
||||
}
|
||||
}
|
||||
|
||||
// TestExpandGraphLazy_InvalidNodeType tests invalid node type handling.
|
||||
func TestExpandGraphLazy_InvalidNodeType(t *testing.T) {
|
||||
graph := NewGraph()
|
||||
|
||||
// This test verifies the default case in ExpandGraphLazy
|
||||
// We can't easily create an invalid node type, so we just verify
|
||||
// the method handles errors gracefully
|
||||
err := graph.ExpandGraphLazy(nil, "Test")
|
||||
// Should not panic, just return an error
|
||||
if err == nil {
|
||||
t.Error("expected error from ExpandGraphLazy with nil node")
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildGraphFromHubs_EdgeCases tests edge cases for hub graph building.
|
||||
func TestBuildGraphFromHubs_EdgeCases(t *testing.T) {
|
||||
// Empty stations list
|
||||
graph := BuildGraphFromHubs(nil, hubCriteria{minPopulation: 1, minOutgoingFlights: 10, defaultOutgoingFlights: 10})
|
||||
if len(graph.Nodes()) != 0 {
|
||||
t.Errorf("expected 0 nodes for empty stations list, got %d", len(graph.Nodes()))
|
||||
}
|
||||
if len(graph.Edges()) != 0 {
|
||||
t.Errorf("expected 0 edges for empty stations list, got %d", len(graph.Edges()))
|
||||
}
|
||||
|
||||
// Single station
|
||||
graph = BuildGraphFromHubs([]StationInfo{{ID: "s1", Name: "Only", CityCode: "c1", CityName: "City1"}},
|
||||
hubCriteria{minPopulation: 1, minOutgoingFlights: 10, defaultOutgoingFlights: 10})
|
||||
if len(graph.Nodes()) != 2 { // 1 station + 1 city
|
||||
t.Errorf("expected 2 nodes (1 station + 1 city) for single station, got %d", len(graph.Nodes()))
|
||||
}
|
||||
if len(graph.Edges()) != 2 { // 2 synthetic edges (station<->city)
|
||||
t.Errorf("expected 2 edges for single station, got %d", len(graph.Edges()))
|
||||
}
|
||||
|
||||
// Duplicate city codes should create only one city node
|
||||
graph = BuildGraphFromHubs([]StationInfo{
|
||||
{ID: "s1", Name: "Station 1", CityCode: "c1", CityName: "City1"},
|
||||
{ID: "s2", Name: "Station 2", CityCode: "c1", CityName: "City1"},
|
||||
}, hubCriteria{minPopulation: 1, minOutgoingFlights: 10, defaultOutgoingFlights: 10})
|
||||
nodes := graph.Nodes()
|
||||
cityCount := 0
|
||||
for _, n := range nodes {
|
||||
if n.Type == NodeTypeCity {
|
||||
cityCount++
|
||||
}
|
||||
}
|
||||
if cityCount != 1 {
|
||||
t.Errorf("expected 1 city node for duplicate city codes, got %d", cityCount)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user