- a reaper started at Mount checks every local session against the store every min(idle_ttl/2, 1m) and closes each copy whose record is gone, logging the expired step (A-CR-01, A-IN-06) - the GET stream re-check ends the stream when the record is gone (A-IN-01) - tool calls served without a session on the stateful path log the session as ephemeral (A-IN-06) - TestPierReaperSmoke and the ReapNow, LocalSessions, StopReaper seams
67 lines
2.3 KiB
Go
67 lines
2.3 KiB
Go
package pier_test
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
)
|
|
|
|
// TestPierReaperSmoke is the tracer of phase 19.7 plan 06 (A-CR-01): on the
|
|
// memory store, a stateful session whose record expired is closed by the
|
|
// reaper (it leaves the local map, so fan-out no longer reaches it), its id
|
|
// answers 404 next, and a session kept active by a request survives a pass.
|
|
func TestPierReaperSmoke(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
in := startInstance(t, newApp(t, appSpec{
|
|
mcp: "enabled: false\nsessions:\n store: memory\n idle_ttl: 30m\n",
|
|
guards: []pact.MCPGuard{&acmeGuard{}},
|
|
}), acmePlugin{tools: []pact.MCPTool{echoTool()}})
|
|
p := in.pier()
|
|
t.Cleanup(p.StopReaper)
|
|
|
|
// 1. An SDK client opens a session on this instance.
|
|
abandoned := sdkClient(t, ctx, in.url(), "oas_at_test", clientOpts{})
|
|
if n := p.LocalSessions(); n != 1 {
|
|
t.Fatalf("local sessions after connect = %d, want 1", n)
|
|
}
|
|
|
|
// 2. The client goes away without DELETE; past the idle TTL its record
|
|
// is gone and one reaper pass closes the local copy.
|
|
if !p.AdvanceStoreClock(31 * time.Minute) {
|
|
t.Fatal("the instance has no memory store")
|
|
}
|
|
if n := p.ReapNow(ctx); n != 1 {
|
|
t.Fatalf("reaped = %d, want 1", n)
|
|
}
|
|
if n := p.LocalSessions(); n != 0 {
|
|
t.Fatalf("local sessions after the reap = %d, want 0", n)
|
|
}
|
|
|
|
// 3. The old session id answers 404, so the client re-initialises.
|
|
if gone := rpc(t, ctx, in.url(), "oas_at_test", abandoned.ID(), callBody); gone.status != http.StatusNotFound {
|
|
t.Fatalf("call with the reaped session = %d %s", gone.status, gone.body)
|
|
}
|
|
|
|
// 4. A session touched by a request within the TTL survives a pass.
|
|
active := openSession(t, ctx, in, "oas_at_test")
|
|
if !p.AdvanceStoreClock(20 * time.Minute) {
|
|
t.Fatal("the instance has no memory store")
|
|
}
|
|
if call := rpc(t, ctx, in.url(), "oas_at_test", active, callBody); call.status != http.StatusOK {
|
|
t.Fatalf("call on the active session = %d %s", call.status, call.body)
|
|
}
|
|
if !p.AdvanceStoreClock(20 * time.Minute) {
|
|
t.Fatal("the instance has no memory store")
|
|
}
|
|
if n := p.ReapNow(ctx); n != 0 {
|
|
t.Fatalf("reaped the active session (%d closed)", n)
|
|
}
|
|
if n := p.LocalSessions(); n != 1 {
|
|
t.Fatalf("local sessions after the second pass = %d, want 1", n)
|
|
}
|
|
}
|