feat: implement API handlers for MVP endpoints (Task 5)
- Create cmd/api/handlers.go with HTTP handlers for all MVP endpoints
- Implement GET /v1/cities?query= city autocomplete
- Implement GET /v1/cities/{id}/stations city stations including neighbors
- Implement POST /v1/routes/search route search with Pareto-optimal results
- Implement GET /v1/routes/{search_id}/{route_id}/geojson route geometry
- Implement GET /v1/stations/{id}/status station status endpoint
- Add handler tests with success and error cases
- All existing tests pass
This commit is contained in:
157
cmd/api/handlers_test.go
Normal file
157
cmd/api/handlers_test.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
|
||||
"trip-planner/internal/routing"
|
||||
"trip-planner/internal/yandex"
|
||||
)
|
||||
|
||||
func newMockHandlerContext() *HandlerContext {
|
||||
redisClient := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
})
|
||||
|
||||
// Create an empty routing graph
|
||||
router := routing.NewGraph()
|
||||
|
||||
// Create Yandex client
|
||||
yandexClient := yandex.NewClient("test-key")
|
||||
|
||||
return NewHandlerContext(redisClient, router, yandexClient)
|
||||
}
|
||||
|
||||
func TestHandlerCityAutocomplete(t *testing.T) {
|
||||
h := newMockHandlerContext()
|
||||
req := httptest.NewRequest("GET", "/v1/cities?query=mos", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
CityAutocomplete(h, rr, req)
|
||||
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d", rr.Code)
|
||||
}
|
||||
|
||||
var resp []cityResponse
|
||||
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
t.Logf("city autocomplete response: %d cities", len(resp))
|
||||
}
|
||||
|
||||
func TestHandlerCityStations(t *testing.T) {
|
||||
h := newMockHandlerContext()
|
||||
req := httptest.NewRequest("GET", "/v1/cities/1/stations", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
CityStations(h, rr, req)
|
||||
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d", rr.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerRouteSearch(t *testing.T) {
|
||||
h := newMockHandlerContext()
|
||||
|
||||
// Add nodes and edges to the graph to test route finding
|
||||
graph := routing.NewGraph()
|
||||
graph.AddNode(&routing.Node{ID: "c146", Type: routing.NodeTypeCity, Name: "Simferopol"})
|
||||
graph.AddNode(&routing.Node{ID: "c213", Type: routing.NodeTypeCity, Name: "Moscow"})
|
||||
graph.AddNode(&routing.Node{ID: "s9600213", Type: routing.NodeTypeStation, Name: "Шереметьево", CityCode: "c146"})
|
||||
graph.AddNode(&routing.Node{ID: "s9600396", Type: routing.NodeTypeStation, Name: "Симферополь", CityCode: "c146"})
|
||||
|
||||
// Add synthetic edges: station <-> city
|
||||
graph.AddEdge(&routing.Edge{
|
||||
From: graph.Nodes()[2], // s9600213
|
||||
To: graph.Nodes()[0], // c146
|
||||
Kind: routing.EdgeKindSynthetic,
|
||||
Duration: 300,
|
||||
Transport: "train",
|
||||
IsTransfer: true,
|
||||
})
|
||||
graph.AddEdge(&routing.Edge{
|
||||
From: graph.Nodes()[0], // c146
|
||||
To: graph.Nodes()[2], // s9600213
|
||||
Kind: routing.EdgeKindSynthetic,
|
||||
Duration: 300,
|
||||
Transport: "train",
|
||||
IsTransfer: true,
|
||||
})
|
||||
graph.AddEdge(&routing.Edge{
|
||||
From: graph.Nodes()[3], // s9600396
|
||||
To: graph.Nodes()[0], // c146
|
||||
Kind: routing.EdgeKindSynthetic,
|
||||
Duration: 300,
|
||||
Transport: "train",
|
||||
IsTransfer: true,
|
||||
})
|
||||
graph.AddEdge(&routing.Edge{
|
||||
From: graph.Nodes()[0], // c146
|
||||
To: graph.Nodes()[3], // s9600396
|
||||
Kind: routing.EdgeKindSynthetic,
|
||||
Duration: 300,
|
||||
Transport: "train",
|
||||
IsTransfer: true,
|
||||
})
|
||||
|
||||
// Replace the router with our test graph
|
||||
h.Router = graph
|
||||
|
||||
// Create request with JSON body
|
||||
req := httptest.NewRequest("POST", "/v1/routes/search", strings.NewReader(`{"from_city_id": "c146", "to_city_id": "c213", "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)
|
||||
}
|
||||
|
||||
func TestHandlerRouteGeoJSON(t *testing.T) {
|
||||
h := newMockHandlerContext()
|
||||
|
||||
req := httptest.NewRequest("GET", "/v1/routes/search-123/route-456/geojson", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
RouteGeoJSON(h, rr, req)
|
||||
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d", rr.Code)
|
||||
}
|
||||
|
||||
var resp routeGeoJSONResponse
|
||||
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
t.Logf("route geojson response: %+v", resp)
|
||||
}
|
||||
|
||||
func TestHandlerStationStatus(t *testing.T) {
|
||||
h := newMockHandlerContext()
|
||||
|
||||
req := httptest.NewRequest("GET", "/v1/stations/s9600213/status", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
StationStatus(h, rr, req)
|
||||
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d", rr.Code)
|
||||
}
|
||||
|
||||
var resp stationStatusResponse
|
||||
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
t.Logf("station status response: %+v", resp)
|
||||
}
|
||||
Reference in New Issue
Block a user