fix(test): add missing os import to test files chore(deps): update dependencies from go mod tidy
394 lines
11 KiB
Go
394 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/go-redis/redis/v8"
|
|
|
|
"trip-planner/internal/cache"
|
|
"trip-planner/internal/metrics"
|
|
"trip-planner/internal/yandex"
|
|
)
|
|
|
|
func newMockMonitor(id string, tripCount int, scheduleFunc func(context.Context, string) (int, error)) *StationMonitor {
|
|
// Read Redis address from environment, default to localhost:6379
|
|
redisAddr := os.Getenv("REDIS_ADDR")
|
|
if redisAddr == "" {
|
|
redisAddr = "localhost:6379"
|
|
}
|
|
rc := redis.NewClient(&redis.Options{
|
|
Addr: redisAddr,
|
|
Password: "",
|
|
DB: 0,
|
|
})
|
|
yc := yandex.NewClient("test-key")
|
|
|
|
monitor := &StationMonitor{
|
|
ID: id,
|
|
Yandex: yc,
|
|
Cache: cache.NewCacheStore(rc, metrics.New()),
|
|
ScheduleFunc: scheduleFunc,
|
|
}
|
|
|
|
// If no ScheduleFunc provided, set up default that returns tripCount
|
|
if monitor.ScheduleFunc == nil {
|
|
monitor.ScheduleFunc = func(ctx context.Context, stationID string) (int, error) {
|
|
return tripCount, nil
|
|
}
|
|
}
|
|
|
|
return monitor
|
|
}
|
|
|
|
const testMonitorID = "test-station"
|
|
|
|
// TestProcessStation_WithTrips verifies that a station with trips today
|
|
// gets status "active" and zero-trip day count resets to 0.
|
|
func TestProcessStation_WithTrips(t *testing.T) {
|
|
t.Helper()
|
|
// Read Redis address from environment, default to localhost:6379
|
|
redisAddr := os.Getenv("REDIS_ADDR")
|
|
if redisAddr == "" {
|
|
redisAddr = "localhost:6379"
|
|
}
|
|
rc := redis.NewClient(&redis.Options{
|
|
Addr: redisAddr,
|
|
Password: "",
|
|
DB: 0,
|
|
})
|
|
defer rc.Close()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Flush Redis database for test isolation
|
|
rc.FlushDB(ctx)
|
|
|
|
monitor := newMockMonitor(testMonitorID, 2, nil)
|
|
|
|
// Process the station - should have trips and status should be active
|
|
err := ProcessStation(ctx, monitor)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// Check that status was set to active
|
|
statusData, err := monitor.Cache.Get(ctx, stationStatusKey(testMonitorID))
|
|
if err != nil {
|
|
t.Fatalf("cache get error: %v", err)
|
|
}
|
|
if string(statusData) != string(StatusActive) {
|
|
t.Errorf("expected status active, got %s", string(statusData))
|
|
}
|
|
|
|
// Check that zero days was reset to 0
|
|
zeroDaysData, err := monitor.Cache.Get(ctx, zeroDaysKey(testMonitorID))
|
|
if err != nil {
|
|
t.Fatalf("cache get zero days error: %v", err)
|
|
}
|
|
var zeroDays int
|
|
_, err = fmt.Sscanf(string(zeroDaysData), "%d", &zeroDays)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse zero days: %v", err)
|
|
}
|
|
if zeroDays != 0 {
|
|
t.Errorf("expected zero days 0, got %d", zeroDays)
|
|
}
|
|
}
|
|
|
|
// TestProcessStation_ZeroTrips_IncrementsCount verifies that a station
|
|
// with 0 trips increments the zero-trip day count.
|
|
func TestProcessStation_ZeroTrips_IncrementsCount(t *testing.T) {
|
|
t.Helper()
|
|
// Read Redis address from environment, default to localhost:6379
|
|
redisAddr := os.Getenv("REDIS_ADDR")
|
|
if redisAddr == "" {
|
|
redisAddr = "localhost:6379"
|
|
}
|
|
rc := redis.NewClient(&redis.Options{
|
|
Addr: redisAddr,
|
|
Password: "",
|
|
DB: 0,
|
|
})
|
|
defer rc.Close()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Flush Redis database for test isolation
|
|
rc.FlushDB(ctx)
|
|
|
|
// Monitor with 0 trips (schedule func returns 0)
|
|
monitor := newMockMonitor(testMonitorID, 0, nil)
|
|
|
|
// First call: 0 trips, status should remain active (zero days = 1)
|
|
err := ProcessStation(ctx, monitor)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// Check that zero days was incremented to 1
|
|
statusData, err := monitor.Cache.Get(ctx, stationStatusKey(testMonitorID))
|
|
if err != nil {
|
|
t.Fatalf("cache get error: %v", err)
|
|
}
|
|
if string(statusData) != string(StatusActive) {
|
|
t.Errorf("expected status active after first call, got %s", string(statusData))
|
|
}
|
|
|
|
zeroDaysData, err := monitor.Cache.Get(ctx, zeroDaysKey(testMonitorID))
|
|
if err != nil {
|
|
t.Fatalf("cache get zero days error: %v", err)
|
|
}
|
|
var zeroDays int
|
|
_, err = fmt.Sscanf(string(zeroDaysData), "%d", &zeroDays)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse zero days: %v", err)
|
|
}
|
|
if zeroDays != 1 {
|
|
t.Errorf("expected zero days 1 after first call, got %d", zeroDays)
|
|
}
|
|
}
|
|
|
|
// TestProcessStation_ZeroTrips_3Days_Closes verifies that a station
|
|
// with 3 consecutive days of zero trips gets status "closed".
|
|
func TestProcessStation_ZeroTrips_3Days_Closes(t *testing.T) {
|
|
t.Helper()
|
|
// Read Redis address from environment, default to localhost:6379
|
|
redisAddr := os.Getenv("REDIS_ADDR")
|
|
if redisAddr == "" {
|
|
redisAddr = "localhost:6379"
|
|
}
|
|
rc := redis.NewClient(&redis.Options{
|
|
Addr: redisAddr,
|
|
Password: "",
|
|
DB: 0,
|
|
})
|
|
defer rc.Close()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Flush Redis database for test isolation
|
|
rc.FlushDB(ctx)
|
|
|
|
// Monitor with 0 trips each day
|
|
monitor := newMockMonitor(testMonitorID, 0, nil)
|
|
|
|
// Day 1: 0 trips - ProcessStation reads 0 (no prior data), increments to 1
|
|
err := ProcessStation(ctx, monitor)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error day 1: %v", err)
|
|
}
|
|
zeroDaysData, _ := monitor.Cache.Get(ctx, zeroDaysKey(testMonitorID))
|
|
var zeroDays int
|
|
fmt.Sscanf(string(zeroDaysData), "%d", &zeroDays)
|
|
// ProcessStation starts at 0 (no prior data), increments to 1
|
|
if zeroDays != 1 {
|
|
t.Errorf("day 1: expected zero days 1, got %d", zeroDays)
|
|
}
|
|
|
|
// Day 2: 0 trips - ProcessStation reads 1 (from day 1), increments to 2
|
|
err = ProcessStation(ctx, monitor)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error day 2: %v", err)
|
|
}
|
|
zeroDaysData, _ = monitor.Cache.Get(ctx, zeroDaysKey(testMonitorID))
|
|
fmt.Sscanf(string(zeroDaysData), "%d", &zeroDays)
|
|
// ProcessStation incremented from 1 to 2
|
|
if zeroDays != 2 {
|
|
t.Errorf("day 2: expected zero days 2, got %d", zeroDays)
|
|
}
|
|
|
|
// Day 3: 0 trips - ProcessStation reads 2 (from day 2), increments to 3, closes station
|
|
err = ProcessStation(ctx, monitor)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error day 3: %v", err)
|
|
}
|
|
zeroDaysData, _ = monitor.Cache.Get(ctx, zeroDaysKey(testMonitorID))
|
|
fmt.Sscanf(string(zeroDaysData), "%d", &zeroDays)
|
|
// ProcessStation incremented from 2 to 3
|
|
if zeroDays != 3 {
|
|
t.Errorf("day 3: expected zero days 3, got %d", zeroDays)
|
|
}
|
|
|
|
// Status should be closed after 3 consecutive days of zero trips
|
|
statusData, err := monitor.Cache.Get(ctx, stationStatusKey(testMonitorID))
|
|
if err != nil {
|
|
t.Fatalf("cache get error: %v", err)
|
|
}
|
|
if string(statusData) != string(StatusClosed) {
|
|
t.Errorf("expected status closed after 3 days, got %s", string(statusData))
|
|
}
|
|
}
|
|
|
|
// TestProcessStation_Reactivation_AfterClosure verifies that a station
|
|
// closed due to 3 zero-trip days gets reactivated when trips resume.
|
|
func TestProcessStation_Reactivation_AfterClosure(t *testing.T) {
|
|
t.Helper()
|
|
// Read Redis address from environment, default to localhost:6379
|
|
redisAddr := os.Getenv("REDIS_ADDR")
|
|
if redisAddr == "" {
|
|
redisAddr = "localhost:6379"
|
|
}
|
|
rc := redis.NewClient(&redis.Options{
|
|
Addr: redisAddr,
|
|
Password: "",
|
|
DB: 0,
|
|
})
|
|
defer rc.Close()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Flush Redis database for test isolation
|
|
rc.FlushDB(ctx)
|
|
|
|
// Monitor that will return 1 trip on reactivation
|
|
monitor := newMockMonitor(testMonitorID, 1, nil)
|
|
|
|
// First, close the station by setting zero days to 3 and status to closed
|
|
_ = rc.Set(ctx, "station:zero_days:"+testMonitorID, "3", 24*time.Hour)
|
|
_ = rc.Set(ctx, "station:status:"+testMonitorID, string(StatusClosed), 24*time.Hour)
|
|
|
|
// Day 4: trips resume - should reactivate
|
|
err := ProcessStation(ctx, monitor)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error on reactivation: %v", err)
|
|
}
|
|
|
|
// Status should be active again
|
|
statusData, err := monitor.Cache.Get(ctx, stationStatusKey(testMonitorID))
|
|
if err != nil {
|
|
t.Fatalf("cache get error: %v", err)
|
|
}
|
|
if string(statusData) != string(StatusActive) {
|
|
t.Errorf("expected status active after reactivation, got %s", string(statusData))
|
|
}
|
|
|
|
// Zero days should be reset to 0
|
|
zeroDaysData, err := monitor.Cache.Get(ctx, zeroDaysKey(testMonitorID))
|
|
if err != nil {
|
|
t.Fatalf("cache get zero days error: %v", err)
|
|
}
|
|
var zeroDays int
|
|
_, err = fmt.Sscanf(string(zeroDaysData), "%d", &zeroDays)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse zero days: %v", err)
|
|
}
|
|
if zeroDays != 0 {
|
|
t.Errorf("expected zero days 0 after reactivation, got %d", zeroDays)
|
|
}
|
|
}
|
|
|
|
// TestAutoClosureChronology verifies the chronology of auto-closure detection.
|
|
// It tests that a station closes after exactly N=3 consecutive zero-trip days,
|
|
// and that it reactivates when trips resume.
|
|
func TestAutoClosureChronology(t *testing.T) {
|
|
t.Helper()
|
|
// Read Redis address from environment, default to localhost:6379
|
|
redisAddr := os.Getenv("REDIS_ADDR")
|
|
if redisAddr == "" {
|
|
redisAddr = "localhost:6379"
|
|
}
|
|
rc := redis.NewClient(&redis.Options{
|
|
Addr: redisAddr,
|
|
Password: "",
|
|
DB: 0,
|
|
})
|
|
defer rc.Close()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Flush Redis database for test isolation
|
|
rc.FlushDB(ctx)
|
|
|
|
// Monitor that returns 0 trips
|
|
monitor := newMockMonitor("test-cha", 0, nil)
|
|
|
|
// Day 1: 0 trips - err declared with :=
|
|
err := ProcessStation(ctx, monitor)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error day 1: %v", err)
|
|
}
|
|
|
|
var zeroDays1 int
|
|
zeroDaysData, _ := monitor.Cache.Get(ctx, zeroDaysKey("test-cha"))
|
|
_, err = fmt.Sscanf(string(zeroDaysData), "%d", &zeroDays1)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse zero days: %v", err)
|
|
}
|
|
if zeroDays1 != 1 {
|
|
t.Errorf("day 1: expected zero days 1, got %d", zeroDays1)
|
|
}
|
|
|
|
// Day 2: 0 trips - assign to err (already declared)
|
|
err = ProcessStation(ctx, monitor)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error day 2: %v", err)
|
|
}
|
|
|
|
var zeroDays2 int
|
|
zeroDaysData, _ = monitor.Cache.Get(ctx, zeroDaysKey("test-cha"))
|
|
_, err = fmt.Sscanf(string(zeroDaysData), "%d", &zeroDays2)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse zero days: %v", err)
|
|
}
|
|
if zeroDays2 != 2 {
|
|
t.Errorf("day 2: expected zero days 2, got %d", zeroDays2)
|
|
}
|
|
|
|
// Day 3: 0 trips - assign to err (already declared), station closes
|
|
err = ProcessStation(ctx, monitor)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error day 3: %v", err)
|
|
}
|
|
|
|
var zeroDays3 int
|
|
zeroDaysData, _ = monitor.Cache.Get(ctx, zeroDaysKey("test-cha"))
|
|
_, err = fmt.Sscanf(string(zeroDaysData), "%d", &zeroDays3)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse zero days: %v", err)
|
|
}
|
|
if zeroDays3 != 3 {
|
|
t.Errorf("day 3: expected zero days 3, got %d", zeroDays3)
|
|
}
|
|
|
|
// Status should be closed
|
|
statusData, err := monitor.Cache.Get(ctx, stationStatusKey("test-cha"))
|
|
if err != nil {
|
|
t.Fatalf("cache get status error: %v", err)
|
|
}
|
|
if string(statusData) != string(StatusClosed) {
|
|
t.Errorf("expected status closed, got %s", string(statusData))
|
|
}
|
|
|
|
// Day 4: trips resume - should reactivate
|
|
monitor.ScheduleFunc = func(ctx context.Context, stationID string) (int, error) {
|
|
return 1, nil
|
|
}
|
|
err = ProcessStation(ctx, monitor)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error reactivation: %v", err)
|
|
}
|
|
|
|
// Status should be active again
|
|
statusData, err = monitor.Cache.Get(ctx, stationStatusKey("test-cha"))
|
|
if err != nil {
|
|
t.Fatalf("cache get status error: %v", err)
|
|
}
|
|
if string(statusData) != string(StatusActive) {
|
|
t.Errorf("expected status active after reactivation, got %s", string(statusData))
|
|
}
|
|
|
|
var zeroDays4 int
|
|
zeroDaysData, _ = monitor.Cache.Get(ctx, zeroDaysKey("test-cha"))
|
|
_, err = fmt.Sscanf(string(zeroDaysData), "%d", &zeroDays4)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse zero days: %v", err)
|
|
}
|
|
if zeroDays4 != 0 {
|
|
t.Errorf("expected zero days 0 after reactivation, got %d", zeroDays4)
|
|
}
|
|
}
|