package attesto import ( "errors" "os" "path/filepath" "testing" ) func receipt(seqNo int64, eventHash, prevEventHash string) EventReceipt { return EventReceipt{ StreamID: "str_demo", SeqNo: seqNo, EventHash: eventHash, PrevEventHash: prevEventHash, } } func TestMemoryHeadStoreInOrderAdvances(t *testing.T) { store := NewMemoryHeadStore() for _, r := range []EventReceipt{receipt(1, "h1", ""), receipt(2, "h2", "h1"), receipt(3, "h3", "h2")} { if err := checkAndAdvanceHead(store, r); err != nil { t.Fatalf("unexpected: %v", err) } } if seq, hash, ok := store.Get("str_demo"); !ok || seq != 3 || hash != "h3" { t.Errorf("head = (%d,%s,%v)", seq, hash, ok) } } func TestForgedRewoundReceiptIsFork(t *testing.T) { store := NewMemoryHeadStore() _ = checkAndAdvanceHead(store, receipt(1, "h1", "")) _ = checkAndAdvanceHead(store, receipt(2, "h2", "h1")) err := checkAndAdvanceHead(store, receipt(2, "h2-fork", "h1")) var fork *ForkDetectedError if !errors.As(err, &fork) { t.Fatalf("expected *ForkDetectedError, got %v", err) } if seq, hash, _ := store.Get("str_demo"); seq != 2 || hash != "h2" { t.Errorf("store advanced past fork: (%d,%s)", seq, hash) } } func TestDivergentNextEventIsFork(t *testing.T) { store := NewMemoryHeadStore() _ = checkAndAdvanceHead(store, receipt(1, "h1", "")) err := checkAndAdvanceHead(store, receipt(2, "h2", "WRONG")) var fork *ForkDetectedError if !errors.As(err, &fork) { t.Fatalf("expected *ForkDetectedError, got %v", err) } } func TestForwardGapAccepted(t *testing.T) { store := NewMemoryHeadStore() _ = checkAndAdvanceHead(store, receipt(1, "h1", "")) if err := checkAndAdvanceHead(store, receipt(5, "h5", "h4")); err != nil { t.Fatalf("forward gap should be accepted: %v", err) } } func TestFileHeadStorePersistsAndIs0600(t *testing.T) { path := filepath.Join(t.TempDir(), "heads.json") store := NewFileHeadStore(path) _ = checkAndAdvanceHead(store, receipt(1, "h1", "")) _ = checkAndAdvanceHead(store, receipt(2, "h2", "h1")) info, err := os.Stat(path) if err != nil { t.Fatal(err) } if info.Mode().Perm() != 0o600 { t.Errorf("mode = %o, want 600", info.Mode().Perm()) } reopened := NewFileHeadStore(path) if seq, hash, ok := reopened.Get("str_demo"); !ok || seq != 2 || hash != "h2" { t.Errorf("reopened head = (%d,%s,%v)", seq, hash, ok) } if err := checkAndAdvanceHead(reopened, receipt(2, "h2-fork", "h1")); err == nil { t.Error("expected fork on reopened store") } } func TestFileHeadStorePersistenceFailureReturned(t *testing.T) { path := t.TempDir() store := NewFileHeadStore(path) if err := checkAndAdvanceHead(store, receipt(1, "h1", "")); err == nil { t.Fatal("expected persistence failure when head-store path is a directory") } } func TestFileHeadStoreCorruptFileFailsClosed(t *testing.T) { path := filepath.Join(t.TempDir(), "heads.json") if err := os.WriteFile(path, []byte("{not-json"), 0o600); err != nil { t.Fatal(err) } store := NewFileHeadStore(path) if err := store.SetWithError("str_demo", 1, "h1"); err == nil { t.Fatal("expected corrupt head store to fail closed") } raw, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if string(raw) != "{not-json" { t.Fatal("corrupt head store must not be silently overwritten") } } func TestExactReplayOfStoredHeadIsBenign(t *testing.T) { // [P3.3 regression] A deduplicated resend returns the same receipt; the // head tracker must treat (same seqNo, same eventHash) as a no-op. store := NewMemoryHeadStore() first := EventReceipt{StreamID: "str_x", SeqNo: 1, EventHash: "h1"} if err := checkAndAdvanceHead(store, first); err != nil { t.Fatal(err) } if err := checkAndAdvanceHead(store, first); err != nil { t.Fatalf("exact replay must be benign, got %v", err) } fork := EventReceipt{StreamID: "str_x", SeqNo: 1, EventHash: "h2"} if err := checkAndAdvanceHead(store, fork); err == nil { t.Fatal("same seq with different hash must be a fork") } }