storetest

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 7, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package storetest provides a reusable Protocol-driven contract test suite for ledger.Store implementations. Each backend (mem, postgres) supplies a Factory and runs Run with the same Protocol; the suite derives test cases from the Protocol configuration so every backend is proved to honor the same protocol decisions.

Helpers (NewTestProtocol / NewEntryFixture) are exported so future PG store integration tests reuse the same fixture surface; the path runtime/audit/ledger/storetest/ is in the AUDIT-LEDGER-PROTOCOL-COMPOSITION-ROOT-01 archtest allowlist so calls to ledger.NewProtocol from this package are permitted.

All test backends share NewTestProtocol so they prove parity on the same protocol decisions; backends differ only in their Factory implementation.

MemStore restart simulation note: MemStore is ephemeral (no cross-factory persistence). The Restart_Recovery case documents the Tail-consistency invariant by replaying entries from storeA into storeB via GetBySeq/Append. A PG-backed store would restore state from DB on construction; the suite exercises the same Tail contract regardless of backend.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AssertEntryRoundTrip

func AssertEntryRoundTrip(t *testing.T, src, got *ledger.Entry)

AssertEntryRoundTrip verifies that all caller-supplied (non-store-assigned) fields of src round-trip byte-for-byte through Append + GetBySeq, comparing every exported field via reflect. When a new caller-supplied field is added to ledger.Entry (the canonical HMAC input expands), this helper picks it up automatically — no edit needed here.

Store-assigned fields (SeqNo, ID, PrevHash, Hash) are skipped: Append writes them, so the persisted view legitimately differs from the source literal. Chain-level Hash parity is asserted separately by hash-parity helpers (Protocol_HashParity, PrincipalFields_RoundTrip).

Drift between the Entry field set and AUDIT-HASH-INPUT-FROZEN-01 A1 is caught by that archtest first; this helper makes the runtime round-trip behavior visible against the same surface, so an add-field-but-drop-from-store regression fails here too.

func EpochAnchor

func EpochAnchor() time.Time

EpochAnchor returns the deterministic clock anchor used by storetest cases; backends constructing FakeClock from outside the suite (per-test setup hooks) should use this exact value so case timestamps line up.

func NewEntryFixture

func NewEntryFixture(t *testing.T, eventID, eventType, actorID string, now time.Time) *ledger.Entry

NewEntryFixture constructs an Entry with deterministic fields. eventID must be non-empty; other fields are set to reasonable defaults.

All 5 Principal/Correlation/OccurredAt fields are populated with non-zero fixture values so the default storetest suite exercises the full 12-field HMAC chain rather than the empty-string degenerate case. Callers that want to exercise the zero-value path explicitly (e.g. a PR-A1 transition test) should construct the Entry literal directly instead of going through this helper.

When the Entry shape grows (e.g. PR-A2 wiring more producer-supplied metadata), update this fixture AND the referenceHashInput mirror struct above in the same change — drift between either side and runtime/audit/ledger.auditHashInput is caught by archtest AUDIT-HASH-INPUT-FROZEN-01, but only after the test fixture has been updated to populate the new fields.

func NewTestProtocol

func NewTestProtocol(t testing.TB) *ledger.Protocol

NewTestProtocol constructs the canonical ledger protocol shape: RestartRecoveryStrictTailVerify + IdempotencyContentFingerprint + auditcore namespace. This call routes through ledger.NewProtocol; the archtest AUDIT-LEDGER-PROTOCOL-COMPOSITION-ROOT-01 allowlist must include runtime/audit/ledger/storetest/ for this to compile-link cleanly.

Accepts testing.TB (not *testing.T) so fuzz targets can construct the protocol in their setup phase, where only a *testing.F is in scope.

func ReferenceComputeHash

func ReferenceComputeHash(t testing.TB, key []byte, ns ledger.NamespaceID, prevHash string, e *ledger.Entry) string

ReferenceComputeHash recomputes the canonical HMAC for an Entry without touching Protocol.ComputeHash, serving as the single independent oracle for every audit-ledger hash-parity test (both the storetest suite and the ledger-package white-box tests call it — there is no second copy). Callers supply the HMAC key, the namespace, and the expected prev_hash; the function marshals the mirror struct and returns the hex digest. The namespace is the first signed field, mirroring the production cross-namespace HMAC domain separation (ADR-1042 §A).

The marshal cannot fail for referenceHashInput (only string/int64/[]byte fields), but the error is surfaced via t.Fatalf rather than discarded — the project forbids silently dropping errors, and a future field of an unmarshalable type must fail loudly.

func Run

func Run(t *testing.T, factory Factory, protocol *ledger.Protocol)

Run executes the Protocol-driven contract suite against factory. All backends share NewTestProtocol to prove parity on the same protocol decisions.

The protocol parameter is the same instance the Factory uses internally; the Protocol_HashParity subtest re-computes ComputeHash on appended entries to prove the store's persisted hash matches the protocol's output byte-for-byte (the core single-source contract between Store implementations and Protocol). The Verify_Tampered* cases previously accepted via this parameter have been relocated to mem_store_tamper_test.go (A-05 refactor).

func RunEntryRoundTripFuzz

func RunEntryRoundTripFuzz(f *testing.F, store ledger.Store, protocol *ledger.Protocol)

RunEntryRoundTripFuzz is the property-based complement to the example-based Run suite: it generates random 12-field ledger.Entry values plus a binary payload corpus and asserts, for every accepted Append, that

  • every caller-supplied field round-trips byte-for-byte through Append → GetBySeq (via AssertEntryRoundTrip, reflect-driven so new Entry fields are covered automatically), and
  • the store-persisted Hash equals an INDEPENDENT canonical HMAC recomputed by ReferenceComputeHash (the external mirror, never Protocol.ComputeHash) over an independently reconstructed prev_hash, so a silently dropped field in ComputeHash or a mis-linked chain entry is caught.

store and protocol MUST be same-source: protocol must be the very protocol the store was constructed with, and it must use TestHMACKey() as its HMAC key (i.e. built via NewTestProtocol) — the parity assertion recomputes the reference digest with TestHMACKey() and protocol.Namespace(), so a mismatched key or namespace would surface as a spurious parity failure, not a store bug.

store and protocol are constructed once by the caller and reused across all fuzz iterations — PG cannot afford a fresh migrated database per iteration. The chain therefore grows unbounded over a long run (every accepted Append adds an entry); bound it with -fuzztime rather than expecting per-iteration reset. The same exported helper is wired into both the MemStore fuzz (suite_test.go) and the PG fuzz (adapters/postgres, integration tag) with a shared seed corpus, so mem-vs-PG round-trip + HMAC parity is exercised in fuzz form: both backends must satisfy the same independent reference.

The HMAC canonical-input invariant itself is statically frozen Hard by archtest AUDIT-HASH-INPUT-FROZEN-01 (sealed auditHashInput + locked hmac.New callsite); this fuzz adds the runtime behavior that a static archtest cannot express — µs precision handling, payload binary safety, and namespace domain separation under arbitrary inputs.

func RunPrincipalFieldsRoundTrip

func RunPrincipalFieldsRoundTrip(t *testing.T, factory Factory, protocol *ledger.Protocol)

RunPrincipalFieldsRoundTrip asserts that the five canonical Principal / Correlation / OccurredAt fields added in migration 043_audit_entries_v2 round trip through Append → GetBySeq with byte-equal values, and that the persisted Hash matches protocol.ComputeHash on the populated entry. Combined with Protocol_HashParity it forms the single-source HMAC parity contract: any Store implementation must (a) persist all 11 input fields losslessly and (b) compute Hash via Protocol.ComputeHash so identical inputs map to identical hashes across MemStore / PG Store / future backends.

Wired into Run() as the "PrincipalFields_RoundTrip" subtest; also exported for direct invocation from per-backend tests that wish to call it outside the full Run() suite.

func TestHMACKey

func TestHMACKey() []byte

TestHMACKey returns the deterministic 32-byte HMAC key NewTestProtocol uses. Exposed so cross-implementation parity tests (RunPrincipalFieldsRoundTrip) can recompute the canonical HMAC byte-for-byte via ReferenceComputeHash without going through Protocol.ComputeHash. The returned slice is a fresh copy each call so callers may zero or mutate it (e.g. before passing into WithChainHMAC, which itself wipes the input slice after a defensive copy).

TEST ONLY. This is a fixed, public, low-entropy key (bytes 1..32) for deterministic test parity — it provides no secrecy and must never be used to seal a production audit chain.

Types

type Factory

type Factory func(t *testing.T) (store ledger.Store, fakeClock *clockmock.FakeClock, cleanup func())

Factory constructs a fresh Store with a deterministic clock. Backends with per-test setup (e.g. PG schema reset) do it inside Factory; cleanup is the returned func and must be safe to call exactly once.

The fakeClock return type is the concrete *clockmock.FakeClock rather than the clock.Clock interface — suite cases call fc.Advance() and fc.Now() directly, methods that only the concrete type carries.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL