1133aade73
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NKSN3gSg4TKVBBqkwW2djR
173 lines
5.3 KiB
Go
173 lines
5.3 KiB
Go
package offsite
|
|
|
|
import (
|
|
"io"
|
|
"log"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
|
)
|
|
|
|
// R-70 detector tests — four states + the applied-wins precedence + the demo-felhom
|
|
// applied+stale-staged edge. Fixtures are the real store (t.TempDir SQLite), timestamps
|
|
// controlled via the test-only back-dater (no sleeps).
|
|
|
|
func newDeliveryStore(t *testing.T) *store.Store {
|
|
t.Helper()
|
|
st, err := store.New(filepath.Join(t.TempDir(), "d.db"), log.New(io.Discard, "", 0))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { st.Close() })
|
|
return st
|
|
}
|
|
|
|
const (
|
|
reportWithOffsite = `{"health":{"status":"ok"},"offsite":{"enabled":true,"escrow_state":"escrowed"}}`
|
|
reportNoOffsite = `{"health":{"status":"ok"}}`
|
|
)
|
|
|
|
func TestDeliveryState_Applied(t *testing.T) {
|
|
st := newDeliveryStore(t)
|
|
if err := st.SaveReport("c1", []byte(reportWithOffsite)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.SaveOneTimeSecret("c1", "x"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := st.ConsumeOneTimeSecret("c1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := DeliveryStateFor(st, "c1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.State != DeliveryApplied {
|
|
t.Fatalf("state = %s, want applied", got.State)
|
|
}
|
|
if !got.StaleStagedSince.IsZero() {
|
|
t.Fatalf("consumed secret must not flag stale-staged, got %v", got.StaleStagedSince)
|
|
}
|
|
if got.Since.IsZero() {
|
|
t.Fatal("applied state must carry the report timestamp (mióta)")
|
|
}
|
|
}
|
|
|
|
// The demo-felhom shape: applied via an earlier generation + an unconsumed secret staged since.
|
|
// Precedence: applied WINS, and the stale staged secret becomes a VISIBLE info flag — never a
|
|
// state downgrade, never a heal trigger.
|
|
func TestDeliveryState_AppliedWithStaleStaged(t *testing.T) {
|
|
st := newDeliveryStore(t)
|
|
if err := st.SaveReport("c1", []byte(reportWithOffsite)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.SaveOneTimeSecret("c1", "stale"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.SetOneTimeSecretTimesForTest("c1", "2026-07-21 08:29:29", ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := DeliveryStateFor(st, "c1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.State != DeliveryApplied {
|
|
t.Fatalf("precedence broken: state = %s, want applied (applied wins over staged)", got.State)
|
|
}
|
|
want := time.Date(2026, 7, 21, 8, 29, 29, 0, time.UTC)
|
|
if !got.StaleStagedSince.Equal(want) {
|
|
t.Fatalf("StaleStagedSince = %v, want %v (the invisible plumbing must be visible)", got.StaleStagedSince, want)
|
|
}
|
|
}
|
|
|
|
// The burned-credential shape (F10): consumed, and every report since carries no offbox.
|
|
func TestDeliveryState_ConsumedAwaitingApply(t *testing.T) {
|
|
st := newDeliveryStore(t)
|
|
if err := st.SaveOneTimeSecret("c1", "x"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// consumed 2h ago; the reports below (received "now") all come AFTER it
|
|
if err := st.SetOneTimeSecretTimesForTest("c1", "2026-01-01 00:00:00", "2026-01-01 00:03:00"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i := 0; i < 5; i++ {
|
|
if err := st.SaveReport("c1", []byte(reportNoOffsite)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
got, err := DeliveryStateFor(st, "c1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.State != DeliveryConsumedAwaitingApply {
|
|
t.Fatalf("state = %s, want consumed_awaiting_apply", got.State)
|
|
}
|
|
if got.ReportsSinceConsume != 5 || got.OffsiteReportsSinceConsume != 0 {
|
|
t.Fatalf("counts = %d/%d, want 5/0", got.ReportsSinceConsume, got.OffsiteReportsSinceConsume)
|
|
}
|
|
if want := time.Date(2026, 1, 1, 0, 3, 0, 0, time.UTC); !got.Since.Equal(want) {
|
|
t.Fatalf("Since = %v, want consumed_at %v", got.Since, want)
|
|
}
|
|
}
|
|
|
|
// The since-filter: reports received BEFORE consumed_at must not count toward the trigger.
|
|
func TestDeliveryState_ReportsBeforeConsumeNotCounted(t *testing.T) {
|
|
st := newDeliveryStore(t)
|
|
for i := 0; i < 3; i++ {
|
|
if err := st.SaveReport("c1", []byte(reportNoOffsite)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := st.SaveOneTimeSecret("c1", "x"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// consumed FAR in the future relative to the rows above → zero reports since
|
|
if err := st.SetOneTimeSecretTimesForTest("c1", "2099-01-01 00:00:00", "2099-01-01 00:00:01"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := DeliveryStateFor(st, "c1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.State != DeliveryConsumedAwaitingApply || got.ReportsSinceConsume != 0 {
|
|
t.Fatalf("state/count = %s/%d, want consumed_awaiting_apply/0 (pre-consume reports must not count)",
|
|
got.State, got.ReportsSinceConsume)
|
|
}
|
|
}
|
|
|
|
func TestDeliveryState_StagedAwaitingConsume(t *testing.T) {
|
|
st := newDeliveryStore(t)
|
|
if err := st.SaveReport("c1", []byte(reportNoOffsite)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.SaveOneTimeSecret("c1", "x"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := DeliveryStateFor(st, "c1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.State != DeliveryStagedAwaitingConsume {
|
|
t.Fatalf("state = %s, want staged_awaiting_consume", got.State)
|
|
}
|
|
if got.Since.IsZero() {
|
|
t.Fatal("staged state must carry created_at (mióta)")
|
|
}
|
|
}
|
|
|
|
func TestDeliveryState_NoSecret(t *testing.T) {
|
|
st := newDeliveryStore(t)
|
|
if err := st.SaveReport("c1", []byte(reportNoOffsite)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := DeliveryStateFor(st, "c1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.State != DeliveryNoSecret {
|
|
t.Fatalf("state = %s, want no_secret (mis-state must render needs-attention, never applied)", got.State)
|
|
}
|
|
}
|