529 lines
18 KiB
Go
529 lines
18 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/go-redis/redis/v8"
|
|
|
|
"trip-planner/internal/airports"
|
|
"trip-planner/internal/routing"
|
|
"trip-planner/internal/storage"
|
|
"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 TestHandlerCityStationsWithNeighbors(t *testing.T) {
|
|
h := newMockHandlerContext()
|
|
|
|
// Test: City 1 with closed station should include neighbors
|
|
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)
|
|
}
|
|
|
|
var resp cityStationResponse
|
|
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
// Should have stations
|
|
if len(resp.Stations) == 0 {
|
|
t.Error("expected at least one station")
|
|
}
|
|
t.Logf("City 1 stations: %+v", resp.Stations)
|
|
|
|
// Should have neighbors when station is closed (city 1 has closed stations)
|
|
if len(resp.Neighbors) == 0 {
|
|
t.Error("expected neighboring stations for closed main station")
|
|
}
|
|
t.Logf("City 1 neighbors: %+v", resp.Neighbors)
|
|
|
|
// Verify neighbor has source field
|
|
for _, n := range resp.Neighbors {
|
|
if n.Source != "manual" && n.Source != "geo" {
|
|
t.Errorf("expected neighbor source to be 'manual' or 'geo', got %s", n.Source)
|
|
}
|
|
t.Logf(" Neighbor: %s (source=%s, isExcluded=%v)", n.Name, n.Source, n.IsExcluded)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func TestAdminAuth(t *testing.T) {
|
|
h := newMockHandlerContext()
|
|
|
|
// Test 1: Request without API key should be unauthorized
|
|
req := httptest.NewRequest("POST", "/internal/admin/stations/s9600213/status", strings.NewReader(`{"status": "closed", "source": "manual"}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rr := httptest.NewRecorder()
|
|
AdminStationStatus(h, rr, req)
|
|
|
|
if rr.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected status 401 (unauthorized) without API key, got %d", rr.Code)
|
|
}
|
|
t.Logf("Test admin auth (no key): got status %d (expected 401)", rr.Code)
|
|
|
|
// Test 2: Request with correct API key should be authorized
|
|
req2 := httptest.NewRequest("POST", "/internal/admin/stations/s9600213/status", strings.NewReader(`{"status": "closed", "source": "manual"}`))
|
|
req2.Header.Set("Content-Type", "application/json")
|
|
req2.Header.Set("X-Admin-Api-Key", "trip-planner-admin-key")
|
|
rr2 := httptest.NewRecorder()
|
|
AdminStationStatus(h, rr2, req2)
|
|
|
|
if rr2.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 with valid API key, got %d", rr2.Code)
|
|
}
|
|
t.Logf("Test admin auth (valid key): got status %d (expected 200)", rr2.Code)
|
|
|
|
// Test 3: Request with wrong API key should be unauthorized
|
|
req3 := httptest.NewRequest("POST", "/internal/admin/stations/s9600213/status", strings.NewReader(`{"status": "closed", "source": "manual"}`))
|
|
req3.Header.Set("Content-Type", "application/json")
|
|
req3.Header.Set("X-Admin-Api-Key", "wrong-key")
|
|
rr3 := httptest.NewRecorder()
|
|
AdminStationStatus(h, rr3, req3)
|
|
|
|
if rr3.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected status 401 (unauthorized) with wrong API key, got %d", rr3.Code)
|
|
}
|
|
t.Logf("Test admin auth (wrong key): got status %d (expected 401)", rr3.Code)
|
|
}
|
|
|
|
func TestAdminStationStatus(t *testing.T) {
|
|
h := newMockHandlerContext()
|
|
|
|
// Set up a station in the graph
|
|
graph := routing.NewGraph()
|
|
graph.AddNode(&routing.Node{ID: "s9600213", Type: routing.NodeTypeStation, Name: "Sheremetyevo", CityCode: "c146"})
|
|
h.Router = graph
|
|
|
|
// Test 1: Set station status to "closed" with manual source
|
|
req := httptest.NewRequest("POST", "/internal/admin/stations/s9600213/status", strings.NewReader(`{"status": "closed", "source": "manual"}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("X-Admin-Api-Key", "trip-planner-admin-key")
|
|
rr := httptest.NewRecorder()
|
|
AdminStationStatus(h, rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", rr.Code)
|
|
}
|
|
|
|
var resp map[string]interface{}
|
|
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
if resp["status"] != "closed" {
|
|
t.Errorf("expected status 'closed', got '%v'", resp["status"])
|
|
}
|
|
if resp["source"] != "manual" {
|
|
t.Errorf("expected source 'manual', got '%v'", resp["source"])
|
|
}
|
|
if resp["id"] != "s9600213" {
|
|
t.Errorf("expected id 's9600213', got '%v'", resp["id"])
|
|
}
|
|
t.Logf("Test admin station status (closed): %+v", resp)
|
|
|
|
// Test 2: Set station status to "active" with manual source
|
|
req2 := httptest.NewRequest("POST", "/internal/admin/stations/s9600213/status", strings.NewReader(`{"status": "active", "source": "manual"}`))
|
|
req2.Header.Set("Content-Type", "application/json")
|
|
req2.Header.Set("X-Admin-Api-Key", "trip-planner-admin-key")
|
|
rr2 := httptest.NewRecorder()
|
|
AdminStationStatus(h, rr2, req2)
|
|
|
|
if rr2.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", rr2.Code)
|
|
}
|
|
|
|
var resp2 map[string]interface{}
|
|
if err := json.Unmarshal(rr2.Body.Bytes(), &resp2); err != nil {
|
|
t.Fatalf("failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
if resp2["status"] != "active" {
|
|
t.Errorf("expected status 'active', got '%v'", resp2["status"])
|
|
}
|
|
t.Logf("Test admin station status (active): %+v", resp2)
|
|
|
|
// Test 3: Invalid status value should return 400
|
|
req3 := httptest.NewRequest("POST", "/internal/admin/stations/s9600213/status", strings.NewReader(`{"status": "invalid", "source": "manual"}`))
|
|
req3.Header.Set("Content-Type", "application/json")
|
|
req3.Header.Set("X-Admin-Api-Key", "trip-planner-admin-key")
|
|
rr3 := httptest.NewRecorder()
|
|
AdminStationStatus(h, rr3, req3)
|
|
|
|
if rr3.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for invalid status, got %d", rr3.Code)
|
|
}
|
|
t.Logf("Test admin station status (invalid status): got status %d (expected 400)", rr3.Code)
|
|
|
|
// Test 4: Invalid source should return 400
|
|
req4 := httptest.NewRequest("POST", "/internal/admin/stations/s9600213/status", strings.NewReader(`{"status": "closed", "source": "geo"}`))
|
|
req4.Header.Set("Content-Type", "application/json")
|
|
req4.Header.Set("X-Admin-Api-Key", "trip-planner-admin-key")
|
|
rr4 := httptest.NewRecorder()
|
|
AdminStationStatus(h, rr4, req4)
|
|
|
|
if rr4.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for invalid source, got %d", rr4.Code)
|
|
}
|
|
t.Logf("Test admin station status (invalid source): got status %d (expected 400)", rr4.Code)
|
|
|
|
// Test 5: Missing body should return 400
|
|
req5 := httptest.NewRequest("POST", "/internal/admin/stations/s9600213/status", strings.NewReader(""))
|
|
req5.Header.Set("Content-Type", "application/json")
|
|
req5.Header.Set("X-Admin-Api-Key", "trip-planner-admin-key")
|
|
rr5 := httptest.NewRecorder()
|
|
AdminStationStatus(h, rr5, req5)
|
|
|
|
if rr5.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for missing body, got %d", rr5.Code)
|
|
}
|
|
t.Logf("Test admin station status (missing body): got status %d (expected 400)", rr5.Code)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
// TestNeighboringStations tests the StationNeighbor type from the airports package.
|
|
func TestNeighboringStations(t *testing.T) {
|
|
// Test creating neighbors with different sources
|
|
n := airports.NewStationNeighbors("c1")
|
|
n.Add("s1", "Station One", "geo")
|
|
n.Add("s2", "Station Two", "manual")
|
|
|
|
if n.Len() != 2 {
|
|
t.Errorf("expected 2 neighbors, got %d", n.Len())
|
|
}
|
|
|
|
// Test marking as excluded
|
|
n.MarkExcluded("s1")
|
|
isExcluded, found := n.IsExcluded("s1")
|
|
if !found {
|
|
t.Error("expected s1 to be found in neighbors")
|
|
}
|
|
if !isExcluded {
|
|
t.Error("expected s1 to be excluded")
|
|
}
|
|
|
|
// Test getting non-excluded neighbors
|
|
nonExcluded := n.GetNonExcluded()
|
|
if len(nonExcluded) != 1 {
|
|
t.Errorf("expected 1 non-excluded neighbor, got %d", len(nonExcluded))
|
|
}
|
|
if nonExcluded[0].Name != "Station Two" {
|
|
t.Errorf("expected 'Station Two' as non-excluded, got %s", nonExcluded[0].Name)
|
|
}
|
|
|
|
// Test getting neighbor by ID
|
|
neighbor, found := n.Get("s2")
|
|
if !found {
|
|
t.Error("expected s2 to be found")
|
|
}
|
|
if neighbor.Name != "Station Two" {
|
|
t.Errorf("expected 'Station Two', got %s", neighbor.Name)
|
|
}
|
|
|
|
// Test sorting
|
|
// Note: ASCII order has 'O' < 'T' < 'Z', so "Station One" < "Station Two" < "Station Zero"
|
|
n.Add("s0", "Station Zero", "geo")
|
|
n.Sort()
|
|
if n.Neighbors[0].Name != "Station One" {
|
|
t.Errorf("expected 'Station One' first after sort (alphabetical), got %s", n.Neighbors[0].Name)
|
|
}
|
|
if n.Neighbors[1].Name != "Station Two" {
|
|
t.Errorf("expected 'Station Two' second after sort, got %s", n.Neighbors[1].Name)
|
|
}
|
|
if n.Neighbors[2].Name != "Station Zero" {
|
|
t.Errorf("expected 'Station Zero' third after sort, got %s", n.Neighbors[2].Name)
|
|
}
|
|
}
|
|
|
|
// TestStationNeighbors tests the storage.StationNeighborsTable type.
|
|
func TestStationNeighbors(t *testing.T) {
|
|
// Test adding neighbors for a city
|
|
table := storage.NewStationNeighborsTable()
|
|
table.Add("c1", "s1", "Moscow Station", "manual")
|
|
table.Add("c1", "s2", "Tula Station", "manual")
|
|
table.Add("c1", "s3", "Kursk Station", "geo")
|
|
|
|
// Get all neighbors for city c1
|
|
neighbors := table.GetByCity("c1")
|
|
if len(neighbors) != 3 {
|
|
t.Errorf("expected 3 neighbors for city c1, got %d", len(neighbors))
|
|
}
|
|
|
|
// Get non-excluded neighbors
|
|
nonExcluded := table.GetNonExcluded("c1")
|
|
if len(nonExcluded) != 3 {
|
|
t.Errorf("expected 3 non-excluded neighbors for city c1, got %d", len(nonExcluded))
|
|
}
|
|
|
|
// Mark one as excluded
|
|
table.MarkExcluded("c1", "s2")
|
|
nonExcludedAfter := table.GetNonExcluded("c1")
|
|
if len(nonExcludedAfter) != 2 {
|
|
t.Errorf("expected 2 non-excluded neighbors after marking s2 excluded, got %d", len(nonExcludedAfter))
|
|
}
|
|
|
|
// Verify the excluded one is not in the list
|
|
foundS2 := false
|
|
for _, n := range nonExcludedAfter {
|
|
if n.StationID == "s2" {
|
|
foundS2 = true
|
|
}
|
|
}
|
|
if foundS2 {
|
|
t.Error("expected s2 to be excluded from non-excluded list")
|
|
}
|
|
} |