feat: complete Task 7 - end-to-end integration and full test suite
This commit is contained in:
@@ -13,19 +13,19 @@ import (
|
||||
|
||||
// HandlerContext holds the dependencies for API handlers.
|
||||
type HandlerContext struct {
|
||||
Cache cache.Cache
|
||||
Redis *redis.Client
|
||||
Router *routing.Graph
|
||||
Yandex *yandex.Client
|
||||
Cache cache.Cache
|
||||
Redis *redis.Client
|
||||
Router *routing.Graph
|
||||
Yandex *yandex.Client
|
||||
}
|
||||
|
||||
// NewHandlerContext creates a new HandlerContext with initialized services.
|
||||
func NewHandlerContext(redisClient *redis.Client, router *routing.Graph, yandex *yandex.Client) *HandlerContext {
|
||||
return &HandlerContext{
|
||||
Cache: cache.NewCacheStore(redisClient),
|
||||
Redis: redisClient,
|
||||
Router: router,
|
||||
Yandex: yandex,
|
||||
Cache: cache.NewCacheStore(redisClient),
|
||||
Redis: redisClient,
|
||||
Router: router,
|
||||
Yandex: yandex,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,4 +345,4 @@ func splitBySlash(s string) []string {
|
||||
parts = append(parts, s[start:])
|
||||
}
|
||||
return parts
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,4 +154,111 @@ func TestHandlerStationStatus(t *testing.T) {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
t.Logf("station status response: %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandlerRouteSearchIntegration tests the route search handler with a fully built graph,
|
||||
// verifying the cache-aware flow: handler → graph → route search → response.
|
||||
func TestHandlerRouteSearchIntegration(t *testing.T) {
|
||||
h := newMockHandlerContext()
|
||||
|
||||
// Build a routing graph using the same pattern as TestFindRouteSuccess:
|
||||
// stations with real edges and one synthetic transfer edge, plus city hub.
|
||||
graph := routing.NewGraph()
|
||||
graph.AddNode(&routing.Node{ID: "c1", Type: routing.NodeTypeCity, Name: "City Hub"})
|
||||
graph.AddNode(&routing.Node{ID: "s1", Type: routing.NodeTypeStation, Name: "Moscow", CityCode: "c1"})
|
||||
graph.AddNode(&routing.Node{ID: "s2", Type: routing.NodeTypeStation, Name: "Tula", CityCode: "c1"})
|
||||
graph.AddNode(&routing.Node{ID: "s3", Type: routing.NodeTypeStation, Name: "Vladimir", CityCode: "c1"})
|
||||
|
||||
// Add synthetic edge: city hub <-> station Moscow (transfer)
|
||||
graph.AddEdge(&routing.Edge{
|
||||
From: graph.Nodes()[0], // c1 city hub
|
||||
To: graph.Nodes()[1], // s1 Moscow
|
||||
Kind: routing.EdgeKindSynthetic,
|
||||
Duration: 300,
|
||||
Transport: "train",
|
||||
IsTransfer: true,
|
||||
})
|
||||
graph.AddEdge(&routing.Edge{
|
||||
From: graph.Nodes()[1], // s1 Moscow
|
||||
To: graph.Nodes()[0], // c1 city hub
|
||||
Kind: routing.EdgeKindSynthetic,
|
||||
Duration: 300,
|
||||
Transport: "train",
|
||||
IsTransfer: true,
|
||||
})
|
||||
|
||||
// Add real edge: direct route Moscow → Tula
|
||||
graph.AddEdge(&routing.Edge{
|
||||
From: graph.Nodes()[1], // s1 Moscow
|
||||
To: graph.Nodes()[2], // s2 Tula
|
||||
Kind: routing.EdgeKindReal,
|
||||
Duration: 3600,
|
||||
Transport: "train",
|
||||
IsTransfer: false,
|
||||
})
|
||||
|
||||
// Add synthetic transfer edge: Tula → Vladimir (1 transfer)
|
||||
graph.AddEdge(&routing.Edge{
|
||||
From: graph.Nodes()[2], // s2 Tula
|
||||
To: graph.Nodes()[3], // s3 Vladimir
|
||||
Kind: routing.EdgeKindSynthetic,
|
||||
Duration: 1800,
|
||||
Transport: "train",
|
||||
IsTransfer: true,
|
||||
})
|
||||
|
||||
// Replace the router with our test graph
|
||||
h.Router = graph
|
||||
|
||||
// Create request: from city c1 (Moscow) to city c1 (same city code)
|
||||
req := httptest.NewRequest("POST", "/v1/routes/search", strings.NewReader(`{"from_city_id": "c1", "to_city_id": "c1", "date": "2026-08-15"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
RouteSearch(h, rr, req)
|
||||
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d", rr.Code)
|
||||
}
|
||||
|
||||
var resp routeSearchResponse
|
||||
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
t.Logf("route search response: routes=%+v, count=%d", resp.Routes, resp.Count)
|
||||
|
||||
// With this graph, we should find a route with 1 transfer
|
||||
if resp.Count == 0 {
|
||||
t.Error("expected at least 1 route, got 0")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandlerRouteSearchNoRoute tests route search when origin/destination not in graph.
|
||||
func TestHandlerRouteSearchNoRoute(t *testing.T) {
|
||||
h := newMockHandlerContext()
|
||||
|
||||
// Create graph with no relevant nodes, but add some so the handler can find
|
||||
// the city IDs (otherwise handler returns 404 before route search)
|
||||
graph := routing.NewGraph()
|
||||
graph.AddNode(&routing.Node{ID: "c999", Type: routing.NodeTypeCity, Name: "City 999"})
|
||||
graph.AddNode(&routing.Node{ID: "c888", Type: routing.NodeTypeCity, Name: "City 888"})
|
||||
h.Router = graph
|
||||
|
||||
req := httptest.NewRequest("POST", "/v1/routes/search", strings.NewReader(`{"from_city_id": "c999", "to_city_id": "c888", "date": "2026-08-15"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
RouteSearch(h, rr, req)
|
||||
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d", rr.Code)
|
||||
}
|
||||
|
||||
var resp routeSearchResponse
|
||||
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
if resp.Count != 0 {
|
||||
t.Errorf("expected 0 routes, got %d", resp.Count)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user