Files
elmprodvpn/selective-vpn-api/app/refresh_coordinator_test.go

81 lines
2.3 KiB
Go

package app
import (
"testing"
"time"
)
func TestRefreshCoordinatorLifecycle(t *testing.T) {
now := time.Date(2026, time.March, 9, 21, 0, 0, 0, time.UTC)
rc := newRefreshCoordinator(10*time.Minute, 2*time.Second, 60*time.Second)
if !rc.shouldRefresh(now, false, false) {
t.Fatalf("expected refresh when cache is empty")
}
if !rc.beginRefresh(now, false, false) {
t.Fatalf("expected beginRefresh=true for empty cache")
}
if rc.shouldRefresh(now, false, false) {
t.Fatalf("must not refresh while refresh is in progress")
}
rc.finishSuccess(now)
snap := rc.snapshot(now)
if snap.RefreshInProgress {
t.Fatalf("refresh must be finished after success")
}
if snap.Stale {
t.Fatalf("fresh snapshot must not be stale")
}
if snap.LastError != "" || snap.NextRetryAt != "" {
t.Fatalf("success must clear error and retry metadata: %#v", snap)
}
if rc.shouldRefresh(now.Add(5*time.Minute), false, true) {
t.Fatalf("fresh cache should not refresh yet")
}
if !rc.shouldRefresh(now.Add(11*time.Minute), false, true) {
t.Fatalf("stale cache should refresh")
}
}
func TestRefreshCoordinatorBackoffAndReset(t *testing.T) {
start := time.Date(2026, time.March, 9, 21, 5, 0, 0, time.UTC)
rc := newRefreshCoordinator(10*time.Minute, 2*time.Second, 60*time.Second)
type step struct {
at time.Time
expected time.Duration
}
steps := []step{
{at: start, expected: 2 * time.Second},
{at: start.Add(2 * time.Second), expected: 4 * time.Second},
{at: start.Add(6 * time.Second), expected: 8 * time.Second},
{at: start.Add(14 * time.Second), expected: 16 * time.Second},
{at: start.Add(30 * time.Second), expected: 32 * time.Second},
{at: start.Add(62 * time.Second), expected: 60 * time.Second},
}
for i, st := range steps {
rc.finishError("probe failed", st.at)
got := rc.nextRetryAt().Sub(st.at)
if got != st.expected {
t.Fatalf("step=%d unexpected backoff: got=%s want=%s", i+1, got, st.expected)
}
if rc.lastError() == "" {
t.Fatalf("step=%d expected non-empty lastError", i+1)
}
}
rc.finishSuccess(start.Add(2 * time.Minute))
if rc.consecutiveErrors() != 0 {
t.Fatalf("expected consecutiveErrors reset, got %d", rc.consecutiveErrors())
}
if !rc.nextRetryAt().IsZero() {
t.Fatalf("expected nextRetryAt reset")
}
if rc.lastError() != "" {
t.Fatalf("expected lastError reset, got %q", rc.lastError())
}
}