latest: report each Poll Lane's last pass to the owner's page (#102)
A Lane's pace, its refusal backoff and whether its Site needs the browser were only ever visible in the log. The admin page (issue #102) has to state them, so the Poller keeps one snapshot per Lane. - LaneState/Status (status.go) is the page's view of a Lane: due, gap, clamped, refusing, browser. - runOnce records a snapshot on every return path, including the pass that refuses, so a cooling Lane does not read as one that never ran. - Refusing is derived at snapshot read time from the backoff, not stored: a Lane that cooled down between passes must report false without a new pass. - LaneStatus reports only Sites that have completed a pass, so a fresh restart renders "no data yet" instead of confident zeroes.
This commit is contained in:
@@ -1580,3 +1580,70 @@ func TestRunOnceClampWarningNamesTheSite(t *testing.T) {
|
||||
t.Fatalf("clamp warning = %q, want it to name asura and 3601", got)
|
||||
}
|
||||
}
|
||||
|
||||
// The owner's admin page (issue #102) reads Lane state out of the poller.
|
||||
// Before any pass the snapshot is empty — a restart must render "no data
|
||||
// yet", not zeroes — and each pass records what it saw: the frozen clock,
|
||||
// the due count and the pace, with refusal backoff derived at snapshot time.
|
||||
func TestLaneStatus(t *testing.T) {
|
||||
s, _ := newTestStore(t)
|
||||
now := time.UnixMilli(5_000_000)
|
||||
p := newTestPoller(t, s, &fakeFetcher{body: asuraSeriesFixture, status: 200}, now)
|
||||
|
||||
if st := p.LaneStatus(); len(st.Lanes) != 0 {
|
||||
t.Fatalf("lanes before any pass = %d, want 0 (nothing has run)", len(st.Lanes))
|
||||
} else if st.BrowserConfigured || st.BrowserReachable {
|
||||
t.Fatalf("browser before any pass = configured=%v reachable=%v, want false without a browser fetcher", st.BrowserConfigured, st.BrowserReachable)
|
||||
}
|
||||
|
||||
seedForCheck(t, s, "asura:chronicles", "https://asurascans.com/series/chronicles", 0)
|
||||
// Two refusals put kagane's Lane into backoff; asura sits on its own Lane.
|
||||
browser := &fakeFetcher{status: 403}
|
||||
p.BrowserFetch = browser
|
||||
for i := range 2 {
|
||||
key := fmt.Sprintf("kagane:s%d", i)
|
||||
seedForCheck(t, s, key, "https://kagane.to/series/"+key[7:], 0)
|
||||
}
|
||||
p.runOnce(context.Background())
|
||||
|
||||
st := p.LaneStatus()
|
||||
var asura, kagane LaneState
|
||||
for _, lane := range st.Lanes {
|
||||
switch lane.Site {
|
||||
case "asura":
|
||||
asura = lane
|
||||
case "kagane":
|
||||
kagane = lane
|
||||
}
|
||||
}
|
||||
if st.Lanes[0].Site != "asura" {
|
||||
t.Fatalf("first lane = %q, want asura (snapshot sorted by Site)", st.Lanes[0].Site)
|
||||
}
|
||||
if asura.Site == "" {
|
||||
t.Fatalf("asura missing from snapshot: %+v", st.Lanes)
|
||||
}
|
||||
if !asura.LastRun.Equal(now) {
|
||||
t.Fatalf("asura LastRun = %s, want the frozen clock %s", asura.LastRun, now)
|
||||
}
|
||||
if asura.Due != 1 {
|
||||
t.Fatalf("asura Due = %d, want 1", asura.Due)
|
||||
}
|
||||
if asura.Gap == 0 {
|
||||
t.Fatal("asura Gap = 0, want the Lane's pace")
|
||||
}
|
||||
if asura.Browser || asura.Refusing {
|
||||
t.Fatalf("asura = %+v, want a TLS Lane that is not refusing", asura)
|
||||
}
|
||||
if !kagane.Browser || !kagane.Refusing {
|
||||
t.Fatalf("kagane = %+v, want a browser Lane in refusal backoff", kagane)
|
||||
}
|
||||
if !st.BrowserConfigured || !st.BrowserReachable {
|
||||
t.Fatalf("browser after round = configured=%v reachable=%v, want true/true (sidecar never lost)", st.BrowserConfigured, st.BrowserReachable)
|
||||
}
|
||||
|
||||
// A lost sidecar reads as unreachable for the same window the Lanes skip.
|
||||
p.setBrowserDown(now)
|
||||
if st := p.LaneStatus(); !st.BrowserConfigured || st.BrowserReachable {
|
||||
t.Fatalf("browser after loss = configured=%v reachable=%v, want true/false", st.BrowserConfigured, st.BrowserReachable)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user