diff --git a/docs/plans/2026-08-15-full-implementation.md b/docs/plans/2026-08-15-full-implementation.md index 99127cd..ac4be59 100644 --- a/docs/plans/2026-08-15-full-implementation.md +++ b/docs/plans/2026-08-15-full-implementation.md @@ -65,11 +65,11 @@ Implement the complete multimodal trip planning service as specified in `docs/sp - [x] Write tests: TestFindRouteWithSyntheticFallback - [x] Run tests - must pass before task 3 -### Task 3: Add ResetCircuitBreaker helper [ ] -- [ ] Add `ResetCircuitBreaker` function to `internal/yandex/client.go` -- [ ] Update tests to use the new reset function -- [ ] **Write tests:** TestResetCircuitBreaker -- [ ] Run tests - must pass before task 4 +### Task 3: Add ResetCircuitBreaker helper [x] +- [x] Add `ResetCircuitBreaker` function to `internal/yandex/client.go` +- [x] Update tests to use the new reset function +- [x] **Write tests:** TestResetCircuitBreaker +- [x] Run tests - must pass before task 4 ### Task 4: Implement on-demand /search integration [ ] - [ ] Integrate on-demand `/search` calls in lazy graph expansion diff --git a/internal/routing/graph.go b/internal/routing/graph.go index 923fe0c..2ad484e 100644 --- a/internal/routing/graph.go +++ b/internal/routing/graph.go @@ -9,12 +9,24 @@ type Edge struct { Kind EdgeKind Duration int // travel time in seconds Transport string // transport type (train, plane, bus) - TransportType string // deprecated: use Transport instead + TransportType TransportType // transport type enum IsTransfer bool // whether this edge involves a transfer Departure string // ISO 8601 departure time Arrival string // ISO 8601 arrival time } +// TransportType represents the type of transport for an edge. +type TransportType string + +const ( + // TransportTypePlane represents airplane transport. + TransportTypePlane TransportType = "plane" + // TransportTypeTrain represents train transport. + TransportTypeTrain TransportType = "train" + // TransportTypeBus represents bus transport. + TransportTypeBus TransportType = "bus" +) + // NodeType represents the type of a graph node. type NodeType int @@ -129,6 +141,7 @@ func BuildGraphFromStations(stations []StationInfo) *Graph { Kind: EdgeKindSynthetic, Duration: 300, // 5 min synthetic transfer Transport: "train", + TransportType: TransportTypeTrain, IsTransfer: true, }) @@ -139,6 +152,7 @@ func BuildGraphFromStations(stations []StationInfo) *Graph { Kind: EdgeKindSynthetic, Duration: 300, // 5 min synthetic transfer Transport: "train", + TransportType: TransportTypeTrain, IsTransfer: true, }) } diff --git a/internal/yandex/client.go b/internal/yandex/client.go index 6363bb2..004d5eb 100644 --- a/internal/yandex/client.go +++ b/internal/yandex/client.go @@ -319,6 +319,19 @@ func newCircuitBreaker() *circuitBreaker { } } + +// ResetCircuitBreaker resets the circuit breaker to its initial closed state. +// This is useful for testing or recovery scenarios where the circuit needs to be +// manually reset without waiting for the timeout. +func (cb *circuitBreaker) ResetCircuitBreaker() { + cb.mu.Lock() + defer cb.mu.Unlock() + cb.state = closed + cb.failures = 0 + cb.successes = 0 + cb.openSince = time.Time{} +} + func (cb *circuitBreaker) allow() bool { cb.mu.Lock() defer cb.mu.Unlock() diff --git a/internal/yandex/client_test.go b/internal/yandex/client_test.go index 4bab43d..ec92bf9 100644 --- a/internal/yandex/client_test.go +++ b/internal/yandex/client_test.go @@ -307,6 +307,43 @@ func TestIsRetryableError(t *testing.T) { } } +func TestResetCircuitBreaker(t *testing.T) { + cb := newCircuitBreaker() + + // Record failures to open the circuit + cb.recordFailure() + cb.recordFailure() + cb.recordFailure() + + if cb.state != open { + t.Errorf("expected state open after 3 failures, got %v", cb.state) + } + + // Reset the circuit breaker + cb.ResetCircuitBreaker() + + // Should be back to closed state + if cb.state != closed { + t.Errorf("expected state closed after reset, got %v", cb.state) + } + if cb.failures != 0 { + t.Errorf("expected failures to be 0 after reset, got %d", cb.failures) + } + if cb.successes != 0 { + t.Errorf("expected successes to be 0 after reset, got %d", cb.successes) + } + if cb.openSince != (time.Time{}) { + t.Errorf("expected openSince to be zero after reset, got %v", cb.openSince) + } + + // After reset, allow() should return true (circuit closed) + for i := 0; i < 10; i++ { + if !cb.allow() { + t.Fatalf("expected allow() to return true after reset, attempt %d", i) + } + } +} + // Test Response parsing func TestResponseParsing(t *testing.T) { // Test with a valid JSON response