Files
felhom.eu/hub/internal/store/r435_windows_between_test.go
T

76 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package store
import (
"testing"
"time"
)
// R-435: WindowCreditsBetween lists only windows closed inside the interval (or still open), and each
// window explains at most its hub-set max_remove — a box's count_after cannot widen it, and a window with
// no usable count_after explains exactly its cap. Only a window with no cap makes the answer unknown.
// RED-PROOF (security review 2026-10-08): drop the max_remove cap → „lying box" returns 69, not 34 → FAILS.
func TestR435_RemovedByWindowsBetween(t *testing.T) {
s := newTestStore(t)
sum := func(cust string, from, to time.Time) (int, bool) {
cs, unk := s.WindowCreditsBetween(cust, from, to)
n := 0
for _, c := range cs {
n += c.Explains
}
return n, unk
}
at := func(ts string) time.Time { v, _ := time.Parse("2006-01-02 15:04:05", ts); return v }
ins := func(cust, opened, closed string, before, after, maxRemove any) {
t.Helper()
var c any
if closed != "" {
c = closed
}
closesBy := "2026-10-01 23:00:00" // a window's deadline; an open one past it before `from` is stale
if _, err := s.db.Exec(`INSERT INTO offsite_windows (customer_id, opened_at, closes_by, closed_at, count_before, count_after, max_remove) VALUES (?, ?, ?, ?, ?, ?, ?)`,
cust, opened, closesBy, c, before, after, maxRemove); err != nil {
t.Fatal(err)
}
}
ins("a", "2026-10-01 03:00:00", "2026-10-01 03:10:00", 69, 60, 34) // inside → 9
ins("a", "2026-09-20 03:00:00", "2026-09-20 03:10:00", 80, 69, 40) // before the interval
ins("a", "2026-10-03 03:00:00", "2026-10-03 03:10:00", 60, 50, 30) // after the interval
ins("b", "2026-10-01 03:00:00", "2026-10-01 03:10:00", 50, 40, 25) // another customer
from, to := at("2026-09-30 00:00:00"), at("2026-10-02 00:00:00")
if n, unk := sum("a", from, to); n != 9 || unk {
t.Fatalf("a: want 9 known, got %d unknown=%v", n, unk)
}
if n, unk := sum("c", from, to); n != 0 || unk {
t.Fatalf("no window: want 0 known, got %d unknown=%v", n, unk)
}
// a box that claims it removed everything explains only the cap the hub granted
ins("l", "2026-10-01 03:00:00", "2026-10-01 03:10:00", 69, 0, 34)
if n, unk := sum("l", from, to); n != 34 || unk {
t.Fatalf("lying box: want the cap 34, got %d unknown=%v", n, unk)
}
// a timeout close (count_after −1) inside the interval → explains its cap
ins("d", "2026-10-01 03:00:00", "2026-10-01 03:40:00", 69, -1, 10)
if n, unk := sum("d", from, to); n != 10 || unk {
t.Fatalf("timeout-closed window: want its cap 10, got %d unknown=%v", n, unk)
}
// a window still open → explains its cap
ins("e", "2026-10-01 03:00:00", "", 69, nil, 10)
if n, unk := sum("e", from, to); n != 10 || unk {
t.Fatalf("open window: want its cap 10, got %d unknown=%v", n, unk)
}
// a window stuck open past its deadline before the interval explains nothing
ins("g", "2026-09-01 03:00:00", "", 69, nil, 10)
if _, err := s.db.Exec(`UPDATE offsite_windows SET closes_by = '2026-09-01 04:00:00' WHERE customer_id = 'g'`); err != nil {
t.Fatal(err)
}
if n, unk := sum("g", from, to); n != 0 || unk {
t.Fatalf("stale open window: want 0, got %d unknown=%v", n, unk)
}
// a window with no cap → unknown (the caller falls back to the half-rule)
ins("f", "2026-10-01 03:00:00", "2026-10-01 03:10:00", 69, 60, nil)
if _, unk := sum("f", from, to); !unk {
t.Fatal("a window with no cap must make the interval unknown")
}
}