Files
felhom.eu/hub/internal/offsite/delivery_test.go
T

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)
}
}