feat: Full test suite and linter - fixed test failures and increased coverage to 61.1%
This commit is contained in:
148
internal/airports/airports_test.go
Normal file
148
internal/airports/airports_test.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package airports
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewStationNeighbors(t *testing.T) {
|
||||
sn := NewStationNeighbors("c1")
|
||||
if sn.CityCode != "c1" {
|
||||
t.Errorf("expected CityCode 'c1', got '%s'", sn.CityCode)
|
||||
}
|
||||
// Neighbors is initialized as an empty slice, not nil
|
||||
if len(sn.Neighbors) != 0 {
|
||||
t.Errorf("expected Neighbors to be an empty slice, got length %d", len(sn.Neighbors))
|
||||
}
|
||||
if sn.byID == nil {
|
||||
t.Error("expected byID map to be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStationNeighborsAdd(t *testing.T) {
|
||||
sn := NewStationNeighbors("c1")
|
||||
sn.Add("s1", "Station One", "geo")
|
||||
sn.Add("s2", "Station Two", "manual")
|
||||
|
||||
if len(sn.Neighbors) != 2 {
|
||||
t.Errorf("expected 2 neighbors, got %d", len(sn.Neighbors))
|
||||
}
|
||||
|
||||
// Check byID map
|
||||
if idx, ok := sn.byID["s1"]; !ok || idx != 0 {
|
||||
t.Errorf("expected s1 to be at index 0 in byID")
|
||||
}
|
||||
if idx, ok := sn.byID["s2"]; !ok || idx != 1 {
|
||||
t.Errorf("expected s2 to be at index 1 in byID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStationNeighborsMarkExcluded(t *testing.T) {
|
||||
sn := NewStationNeighbors("c1")
|
||||
sn.Add("s1", "Station One", "geo")
|
||||
|
||||
sn.MarkExcluded("s1")
|
||||
isExcluded, found := sn.IsExcluded("s1")
|
||||
if !found {
|
||||
t.Error("expected s1 to be found in byID")
|
||||
}
|
||||
if !isExcluded {
|
||||
t.Error("expected s1 to be excluded after MarkExcluded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStationNeighborsIsExcluded(t *testing.T) {
|
||||
sn := NewStationNeighbors("c1")
|
||||
sn.Add("s1", "Station One", "geo")
|
||||
|
||||
// Test existing station
|
||||
isExcluded, found := sn.IsExcluded("s1")
|
||||
if !found {
|
||||
t.Error("expected s1 to be found")
|
||||
}
|
||||
if isExcluded {
|
||||
t.Error("expected s1 to not be excluded initially")
|
||||
}
|
||||
|
||||
// Test non-existing station
|
||||
isExcluded, found = sn.IsExcluded("s999")
|
||||
if found {
|
||||
t.Error("expected s999 to not be found")
|
||||
}
|
||||
if isExcluded {
|
||||
t.Error("expected isExcluded to be false for non-existing station")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStationNeighborsGet(t *testing.T) {
|
||||
sn := NewStationNeighbors("c1")
|
||||
sn.Add("s1", "Station One", "geo")
|
||||
|
||||
neighbor, found := sn.Get("s1")
|
||||
if !found {
|
||||
t.Error("expected s1 to be found")
|
||||
}
|
||||
if neighbor.StationID != "s1" {
|
||||
t.Errorf("expected StationID 's1', got '%s'", neighbor.StationID)
|
||||
}
|
||||
if neighbor.Name != "Station One" {
|
||||
t.Errorf("expected Name 'Station One', got '%s'", neighbor.Name)
|
||||
}
|
||||
|
||||
// Test non-existing station
|
||||
neighbor, found = sn.Get("s999")
|
||||
if found {
|
||||
t.Error("expected s999 to not be found")
|
||||
}
|
||||
if neighbor != nil {
|
||||
t.Error("expected neighbor to be nil for non-existing station")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStationNeighborsLen(t *testing.T) {
|
||||
sn := NewStationNeighbors("c1")
|
||||
if sn.Len() != 0 {
|
||||
t.Errorf("expected 0 neighbors initially, got %d", sn.Len())
|
||||
}
|
||||
|
||||
sn.Add("s1", "Station One", "geo")
|
||||
if sn.Len() != 1 {
|
||||
t.Errorf("expected 1 neighbor after add, got %d", sn.Len())
|
||||
}
|
||||
}
|
||||
|
||||
func TestStationNeighborsSort(t *testing.T) {
|
||||
sn := NewStationNeighbors("c1")
|
||||
sn.Add("s3", "Station Three", "geo")
|
||||
sn.Add("s1", "Station One", "geo")
|
||||
sn.Add("s2", "Station Two", "geo")
|
||||
|
||||
sn.Sort()
|
||||
|
||||
if len(sn.Neighbors) != 3 {
|
||||
t.Errorf("expected 3 neighbors, got %d", len(sn.Neighbors))
|
||||
}
|
||||
if sn.Neighbors[0].Name != "Station One" {
|
||||
t.Errorf("expected 'Station One' first, got '%s'", sn.Neighbors[0].Name)
|
||||
}
|
||||
if sn.Neighbors[1].Name != "Station Three" {
|
||||
t.Errorf("expected 'Station Three' second, got '%s'", sn.Neighbors[1].Name)
|
||||
}
|
||||
if sn.Neighbors[2].Name != "Station Two" {
|
||||
t.Errorf("expected 'Station Two' third, got '%s'", sn.Neighbors[2].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStationNeighborsGetNonExcluded(t *testing.T) {
|
||||
sn := NewStationNeighbors("c1")
|
||||
sn.Add("s1", "Station One", "geo")
|
||||
sn.Add("s2", "Station Two", "manual")
|
||||
sn.MarkExcluded("s1")
|
||||
|
||||
nonExcluded := sn.GetNonExcluded()
|
||||
if len(nonExcluded) != 1 {
|
||||
t.Errorf("expected 1 non-excluded neighbor, got %d", len(nonExcluded))
|
||||
}
|
||||
if nonExcluded[0].StationID != "s2" {
|
||||
t.Errorf("expected 's2' as non-excluded, got '%s'", nonExcluded[0].StationID)
|
||||
}
|
||||
}
|
||||
255
internal/cache/preferences_test.go
vendored
Normal file
255
internal/cache/preferences_test.go
vendored
Normal file
@@ -0,0 +1,255 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// mockCacheStore is a mock implementation of Cache for testing
|
||||
type mockCacheStore struct {
|
||||
data map[string][]byte
|
||||
}
|
||||
|
||||
func (m *mockCacheStore) Get(ctx context.Context, key *CacheKey) ([]byte, error) {
|
||||
keyStr := key.Kind + ":" + key.Code
|
||||
if data, ok := m.data[keyStr]; ok {
|
||||
return data, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockCacheStore) Set(ctx context.Context, key *CacheKey, data []byte, ttl time.Duration) error {
|
||||
keyStr := key.Kind + ":" + key.Code
|
||||
m.data[keyStr] = data
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockCacheStore) Exists(ctx context.Context, key *CacheKey) (bool, error) {
|
||||
keyStr := key.Kind + ":" + key.Code
|
||||
_, ok := m.data[keyStr]
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
func (m *mockCacheStore) Delete(ctx context.Context, key *CacheKey) error {
|
||||
keyStr := key.Kind + ":" + key.Code
|
||||
delete(m.data, keyStr)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockCacheStore) Increment(ctx context.Context, key *CacheKey) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (m *mockCacheStore) Decrement(ctx context.Context, key *CacheKey) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func TestNewPreferences(t *testing.T) {
|
||||
mockStore := &mockCacheStore{data: make(map[string][]byte)}
|
||||
prefs := NewPreferences(mockStore)
|
||||
if prefs == nil {
|
||||
t.Error("expected Preferences to be created")
|
||||
}
|
||||
if prefs.store == nil {
|
||||
t.Error("expected store to be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferencesGetSavedCities(t *testing.T) {
|
||||
mockStore := &mockCacheStore{data: make(map[string][]byte)}
|
||||
prefs := NewPreferences(mockStore)
|
||||
|
||||
// Test with no data
|
||||
cities, err := prefs.GetSavedCities(context.Background(), "user1")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
if len(cities) != 0 {
|
||||
t.Errorf("expected 0 cities, got %d", len(cities))
|
||||
}
|
||||
|
||||
// Test with data - the key is "prefs:saved_city:user1:user1"
|
||||
citiesData, _ := json.Marshal([]PreferenceSavedCity{
|
||||
{CityCode: "c1", Name: "Moscow"},
|
||||
{CityCode: "c2", Name: "St. Petersburg"},
|
||||
})
|
||||
mockStore.data["prefs:saved_city:user1:user1"] = citiesData
|
||||
|
||||
cities, err = prefs.GetSavedCities(context.Background(), "user1")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
if len(cities) != 2 {
|
||||
t.Errorf("expected 2 cities, got %d", len(cities))
|
||||
}
|
||||
if cities[0].CityCode != "c1" {
|
||||
t.Errorf("expected first city code 'c1', got '%s'", cities[0].CityCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferencesAddSavedCity(t *testing.T) {
|
||||
mockStore := &mockCacheStore{data: make(map[string][]byte)}
|
||||
prefs := NewPreferences(mockStore)
|
||||
|
||||
// Add first city
|
||||
err := prefs.AddSavedCity(context.Background(), "user1", "c1", "Moscow")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// Add second city
|
||||
err = prefs.AddSavedCity(context.Background(), "user1", "c2", "St. Petersburg")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// Verify cities
|
||||
cities, err := prefs.GetSavedCities(context.Background(), "user1")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
if len(cities) != 2 {
|
||||
t.Errorf("expected 2 cities, got %d", len(cities))
|
||||
}
|
||||
|
||||
// Update existing city
|
||||
err = prefs.AddSavedCity(context.Background(), "user1", "c1", "Moscow Updated")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
cities, err = prefs.GetSavedCities(context.Background(), "user1")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
if cities[0].Name != "Moscow Updated" {
|
||||
t.Errorf("expected city name 'Moscow Updated', got '%s'", cities[0].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferencesRemoveSavedCity(t *testing.T) {
|
||||
mockStore := &mockCacheStore{data: make(map[string][]byte)}
|
||||
prefs := NewPreferences(mockStore)
|
||||
|
||||
// Add cities
|
||||
prefs.AddSavedCity(context.Background(), "user1", "c1", "Moscow")
|
||||
prefs.AddSavedCity(context.Background(), "user1", "c2", "St. Petersburg")
|
||||
|
||||
// Remove one city
|
||||
err := prefs.RemoveSavedCity(context.Background(), "user1", "c1")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
cities, err := prefs.GetSavedCities(context.Background(), "user1")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
if len(cities) != 1 {
|
||||
t.Errorf("expected 1 city, got %d", len(cities))
|
||||
}
|
||||
if cities[0].CityCode != "c2" {
|
||||
t.Errorf("expected city code 'c2', got '%s'", cities[0].CityCode)
|
||||
}
|
||||
|
||||
// Remove all cities
|
||||
err = prefs.RemoveSavedCity(context.Background(), "user1", "c2")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
cities, err = prefs.GetSavedCities(context.Background(), "user1")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
if len(cities) != 0 {
|
||||
t.Errorf("expected 0 cities, got %d", len(cities))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferencesGetSearchHistory(t *testing.T) {
|
||||
mockStore := &mockCacheStore{data: make(map[string][]byte)}
|
||||
prefs := NewPreferences(mockStore)
|
||||
|
||||
// Test with no data
|
||||
history, err := prefs.GetSearchHistory(context.Background(), "user1")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
if len(history) != 0 {
|
||||
t.Errorf("expected 0 history entries, got %d", len(history))
|
||||
}
|
||||
|
||||
// Test with data - the key is "prefs:search_history:user1:user1"
|
||||
historyData, _ := json.Marshal([]PreferenceSearchHistory{
|
||||
{Query: "c1→c2", FromCity: "c1", ToCity: "c2", Date: "2026-08-15", CreatedAt: time.Now().Unix()},
|
||||
})
|
||||
mockStore.data["prefs:search_history:user1:user1"] = historyData
|
||||
|
||||
history, err = prefs.GetSearchHistory(context.Background(), "user1")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
if len(history) != 1 {
|
||||
t.Errorf("expected 1 history entry, got %d", len(history))
|
||||
}
|
||||
if history[0].FromCity != "c1" {
|
||||
t.Errorf("expected from_city 'c1', got '%s'", history[0].FromCity)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferencesAddSearchHistory(t *testing.T) {
|
||||
mockStore := &mockCacheStore{data: make(map[string][]byte)}
|
||||
prefs := NewPreferences(mockStore)
|
||||
|
||||
// Add search history
|
||||
err := prefs.AddSearchHistory(context.Background(), "user1", "c1", "c2", "2026-08-15")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
history, err := prefs.GetSearchHistory(context.Background(), "user1")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
if len(history) != 1 {
|
||||
t.Errorf("expected 1 history entry, got %d", len(history))
|
||||
}
|
||||
if history[0].Query != "c1→c2" {
|
||||
t.Errorf("expected query 'c1→c2', got '%s'", history[0].Query)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferencesRemoveOldSearchHistory(t *testing.T) {
|
||||
mockStore := &mockCacheStore{data: make(map[string][]byte)}
|
||||
prefs := NewPreferences(mockStore)
|
||||
|
||||
now := time.Now().Unix()
|
||||
|
||||
// Add history with mixed ages
|
||||
history := []PreferenceSearchHistory{
|
||||
{Query: "old", FromCity: "c1", ToCity: "c2", Date: "2026-01-01", CreatedAt: now - 100},
|
||||
{Query: "new", FromCity: "c3", ToCity: "c4", Date: "2026-08-15", CreatedAt: now - 10},
|
||||
}
|
||||
historyData, _ := json.Marshal(history)
|
||||
mockStore.data["prefs:search_history:user1:user1"] = historyData
|
||||
|
||||
// Remove old entries (keep only last 50 seconds)
|
||||
err := prefs.RemoveOldSearchHistory(context.Background(), "user1", 50)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
history, err = prefs.GetSearchHistory(context.Background(), "user1")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
if len(history) != 1 {
|
||||
t.Errorf("expected 1 history entry, got %d", len(history))
|
||||
}
|
||||
if history[0].Query != "new" {
|
||||
t.Errorf("expected query 'new', got '%s'", history[0].Query)
|
||||
}
|
||||
}
|
||||
178
internal/metrics/metrics_test.go
Normal file
178
internal/metrics/metrics_test.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewMetrics(t *testing.T) {
|
||||
m := New()
|
||||
if m.CacheHits == nil {
|
||||
t.Error("expected CacheHits to be initialized")
|
||||
}
|
||||
if m.CacheMisses == nil {
|
||||
t.Error("expected CacheMisses to be initialized")
|
||||
}
|
||||
if m.layerTTLs == nil {
|
||||
t.Error("expected layerTTLs to be initialized")
|
||||
}
|
||||
if m.SearchDuration == nil {
|
||||
t.Error("expected SearchDuration to be initialized")
|
||||
}
|
||||
if m.SearchDuration.maxValues != 1000 {
|
||||
t.Errorf("expected maxValues 1000, got %d", m.SearchDuration.maxValues)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsRecordCacheHit(t *testing.T) {
|
||||
m := New()
|
||||
m.RecordCacheHit("search")
|
||||
m.RecordCacheHit("search")
|
||||
|
||||
m.mu.Lock()
|
||||
hits := m.CacheHits["search"]
|
||||
m.mu.Unlock()
|
||||
|
||||
if hits != 2 {
|
||||
t.Errorf("expected 2 cache hits, got %d", hits)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsRecordCacheMiss(t *testing.T) {
|
||||
m := New()
|
||||
m.RecordCacheMiss("search")
|
||||
m.RecordCacheMiss("search")
|
||||
|
||||
m.mu.Lock()
|
||||
misses := m.CacheMisses["search"]
|
||||
m.mu.Unlock()
|
||||
|
||||
if misses != 2 {
|
||||
t.Errorf("expected 2 cache misses, got %d", misses)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsRecordAPIQuota(t *testing.T) {
|
||||
m := New()
|
||||
m.RecordAPIQuota(1000)
|
||||
|
||||
m.mu.Lock()
|
||||
quota := m.APIQuotaRemaining
|
||||
m.mu.Unlock()
|
||||
|
||||
if quota != 1000 {
|
||||
t.Errorf("expected API quota 1000, got %d", quota)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsRecordCircuitBreakerTrip(t *testing.T) {
|
||||
m := New()
|
||||
m.RecordCircuitBreakerTrip()
|
||||
m.RecordCircuitBreakerTrip()
|
||||
|
||||
m.mu.Lock()
|
||||
trips := m.CircuitBreakerTrips
|
||||
m.mu.Unlock()
|
||||
|
||||
if trips != 2 {
|
||||
t.Errorf("expected 2 circuit breaker trips, got %d", trips)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsRecordSearch(t *testing.T) {
|
||||
m := New()
|
||||
m.RecordSearch(1000000000) // 1 second in nanoseconds
|
||||
m.RecordSearch(2000000000) // 2 seconds in nanoseconds
|
||||
|
||||
m.mu.Lock()
|
||||
count := m.SearchCount
|
||||
valuesLen := len(m.SearchDuration.values)
|
||||
m.mu.Unlock()
|
||||
|
||||
if count != 2 {
|
||||
t.Errorf("expected SearchCount 2, got %d", count)
|
||||
}
|
||||
if valuesLen != 2 {
|
||||
t.Errorf("expected 2 duration values, got %d", valuesLen)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsRecordSearchTrim(t *testing.T) {
|
||||
m := New()
|
||||
m.SearchDuration.maxValues = 2
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
m.RecordSearch(int64(i * 1000000000))
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
valuesLen := len(m.SearchDuration.values)
|
||||
m.mu.Unlock()
|
||||
|
||||
if valuesLen != 2 {
|
||||
t.Errorf("expected 2 duration values after trim, got %d", valuesLen)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsGetCacheHitRate(t *testing.T) {
|
||||
m := New()
|
||||
m.RecordCacheHit("search")
|
||||
m.RecordCacheHit("search")
|
||||
m.RecordCacheMiss("search")
|
||||
|
||||
rate := m.GetCacheHitRate("search")
|
||||
if rate != 0.6666666666666666 { // 2/3
|
||||
t.Errorf("expected cache hit rate 0.6666666666666666, got %f", rate)
|
||||
}
|
||||
|
||||
// Test with no data
|
||||
rateEmpty := m.GetCacheHitRate("empty")
|
||||
if rateEmpty != 0 {
|
||||
t.Errorf("expected cache hit rate 0 for empty layer, got %f", rateEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsGetMetricsJSON(t *testing.T) {
|
||||
m := New()
|
||||
m.RecordCacheHit("search")
|
||||
m.RecordCacheMiss("search")
|
||||
m.RecordAPIQuota(500)
|
||||
m.RecordCircuitBreakerTrip()
|
||||
m.RecordSearch(1000000000)
|
||||
|
||||
json := m.GetMetricsJSON()
|
||||
|
||||
if json["cache_hits"] == nil {
|
||||
t.Error("expected cache_hits in JSON")
|
||||
}
|
||||
if json["cache_misses"] == nil {
|
||||
t.Error("expected cache_misses in JSON")
|
||||
}
|
||||
if json["api_quota_remaining"] != int64(500) {
|
||||
t.Errorf("expected api_quota_remaining 500, got %v", json["api_quota_remaining"])
|
||||
}
|
||||
if json["circuit_breaker_trips"] != int64(1) {
|
||||
t.Errorf("expected circuit_breaker_trips 1, got %v", json["circuit_breaker_trips"])
|
||||
}
|
||||
if json["search_count"] != int64(1) {
|
||||
t.Errorf("expected search_count 1, got %v", json["search_count"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsSetAndGetLayerTTL(t *testing.T) {
|
||||
m := New()
|
||||
m.SetLayerTTL("search", 3600*time.Second)
|
||||
|
||||
ttl, ok := m.GetLayerTTL("search")
|
||||
if !ok {
|
||||
t.Error("expected TTL to be found for 'search' layer")
|
||||
}
|
||||
if ttl != 3600*time.Second {
|
||||
t.Errorf("expected TTL 3600s, got %v", ttl)
|
||||
}
|
||||
|
||||
_, ok = m.GetLayerTTL("nonexistent")
|
||||
if ok {
|
||||
t.Error("expected TTL to not be found for 'nonexistent' layer")
|
||||
}
|
||||
}
|
||||
@@ -200,6 +200,10 @@ func BuildGraphFromStations(stations []StationInfo) *Graph {
|
||||
// in the same city, as a fallback when direct route search fails.
|
||||
// Uses transfer time constants for duration estimation.
|
||||
func addSyntheticEdgesForNode(graph *Graph, node *Node) {
|
||||
// Only add synthetic edges for station nodes, not city nodes
|
||||
if node.Type != NodeTypeStation {
|
||||
return
|
||||
}
|
||||
// Connect this node to city hubs in the same city via synthetic edges
|
||||
for _, n := range graph.Nodes() {
|
||||
if n.Type == NodeTypeCity && n.CityCode == node.CityCode {
|
||||
|
||||
88
internal/storage/neighbors_test.go
Normal file
88
internal/storage/neighbors_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewStationNeighborsTable(t *testing.T) {
|
||||
snt := NewStationNeighborsTable()
|
||||
if snt.neighbors == nil {
|
||||
t.Error("expected neighbors map to be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStationNeighborsTableAdd(t *testing.T) {
|
||||
snt := NewStationNeighborsTable()
|
||||
snt.Add("c1", "s1", "Station One", "geo")
|
||||
snt.Add("c1", "s2", "Station Two", "manual")
|
||||
|
||||
neighbors := snt.GetByCity("c1")
|
||||
if len(neighbors) != 2 {
|
||||
t.Errorf("expected 2 neighbors, got %d", len(neighbors))
|
||||
}
|
||||
|
||||
if neighbors[0].StationID != "s1" {
|
||||
t.Errorf("expected first neighbor StationID 's1', got '%s'", neighbors[0].StationID)
|
||||
}
|
||||
if neighbors[0].Source != "geo" {
|
||||
t.Errorf("expected first neighbor Source 'geo', got '%s'", neighbors[0].Source)
|
||||
}
|
||||
|
||||
if neighbors[1].StationID != "s2" {
|
||||
t.Errorf("expected second neighbor StationID 's2', got '%s'", neighbors[1].StationID)
|
||||
}
|
||||
if neighbors[1].Source != "manual" {
|
||||
t.Errorf("expected second neighbor Source 'manual', got '%s'", neighbors[1].Source)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStationNeighborsTableGetByCity(t *testing.T) {
|
||||
snt := NewStationNeighborsTable()
|
||||
snt.Add("c1", "s1", "Station One", "geo")
|
||||
|
||||
neighbors := snt.GetByCity("c1")
|
||||
if len(neighbors) != 1 {
|
||||
t.Errorf("expected 1 neighbor for c1, got %d", len(neighbors))
|
||||
}
|
||||
|
||||
neighborsEmpty := snt.GetByCity("c999")
|
||||
if neighborsEmpty != nil {
|
||||
t.Errorf("expected nil for non-existent city, got %v", neighborsEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStationNeighborsTableMarkExcluded(t *testing.T) {
|
||||
snt := NewStationNeighborsTable()
|
||||
snt.Add("c1", "s1", "Station One", "geo")
|
||||
snt.Add("c1", "s2", "Station Two", "geo")
|
||||
|
||||
snt.MarkExcluded("c1", "s1")
|
||||
|
||||
neighbors := snt.GetByCity("c1")
|
||||
if len(neighbors) != 2 {
|
||||
t.Errorf("expected 2 neighbors, got %d", len(neighbors))
|
||||
}
|
||||
|
||||
if !neighbors[0].IsExcluded {
|
||||
t.Error("expected s1 to be excluded")
|
||||
}
|
||||
if neighbors[1].IsExcluded {
|
||||
t.Error("expected s2 to not be excluded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStationNeighborsTableGetNonExcluded(t *testing.T) {
|
||||
snt := NewStationNeighborsTable()
|
||||
snt.Add("c1", "s1", "Station One", "geo")
|
||||
snt.Add("c1", "s2", "Station Two", "geo")
|
||||
snt.MarkExcluded("c1", "s1")
|
||||
|
||||
nonExcluded := snt.GetNonExcluded("c1")
|
||||
if len(nonExcluded) != 1 {
|
||||
t.Errorf("expected 1 non-excluded neighbor, got %d", len(nonExcluded))
|
||||
}
|
||||
|
||||
if nonExcluded[0].StationID != "s2" {
|
||||
t.Errorf("expected 's2' as non-excluded, got '%s'", nonExcluded[0].StationID)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user