Skip to content

Currency — Hostile Tests

These tests live under:

Assets/RevFramework/Tests/EditMode/Currency/Hostile/

They are Editor‑only (EditMode) tests and represent the public behavioural contract of the Currency system.

If a behaviour is asserted here, it is something downstream code is allowed to rely on. If it changes, it is considered a contract change, not an implementation detail.


Purpose

Hostile tests answer one question:

“What does the Currency system actually guarantee to a hostile consumer?”

These tests: - Use only public APIs - Exercise real composed stacks - Avoid reflection and private field access - Assert both guarantees and explicit non‑guarantees (best‑effort behaviour)

They are written from the perspective of an untrusted caller who only has access to documented surfaces.


Rules (strict)

Hostile tests must obey the following rules:

  • ❌ No System.Reflection
  • ❌ No access to private or internal members
  • ❌ No dependency on InternalTruth helpers
  • ❌ No assumptions about decorator internals
  • ✅ Public APIs only
  • ✅ Editor authoring (SerializedObject) is allowed for setup

If a test needs to break any of these rules, it does not belong here.


What these tests cover

Authority gating

CurrencyAuthorityTests

  • No authority present ⇒ mutations fail cleanly
  • Authority denies ⇒ no mutation, no events, no audit
  • Authority allows ⇒ mutations succeed

These tests prove that authority is a hard gate, not advisory.


RequireEscrow guards

CurrencyRequireEscrowTests
CurrencyHoldRequireEscrowTests

  • Debit / transfer fail when escrow is required but missing
  • Hold transactions fail fast when escrow is absent
  • Operations succeed once escrow is present

These tests prove that RequireEscrow is a capability gate, not a policy hint.


Batch event semantics

CurrencyBatchEventTests

  • Per‑op wallet events are not suppressed by batching
  • Batch events emit exactly once on Emit()
  • Cancel() prevents batch emission but does not undo mutations
  • Empty batches emit nothing
  • HoldTxn respects the same batching contract

These tests lock in event semantics relied on by UI and observers.


Idempotency

CurrencyIdempotencyTests

  • Replay detection via |rid: suffix
  • Replay short‑circuits without mutating
  • Replay emits no wallet events
  • Different RIDs apply independently
  • Missing RID ⇒ no idempotency

These tests prove idempotency is: - opt‑in - audit‑aware - non‑destructive


Audit (public exposure)

CurrencyAuditPublicTests

  • Audit metadata is recorded when audit is present
  • Reason + sourceId propagate through direct ops and transactions
  • Non‑audited stacks expose no audit data

These tests cover only publicly exposed audit behaviour.

(Decorator‑chain spelunking lives in InternalTruth.)


CurrencyHoldTxn — escrow‑first transactions

CurrencyHoldTxnTests

These tests exercise the real composed stack:

SceneCurrencyService → Caps → Audit → Escrow

They validate:

  • Partial hold failure ⇒ prior holds released (net‑zero)
  • Preflight failure after holds ⇒ net‑zero
  • Successful hold transfer applies destination credit
  • Owner destruction drops refunds safely
  • TTL expiry refunds when possible, drops when not
  • Destination clamping may result in paid ≥ received

Time‑based behaviour is tested deterministically via ManualTimeProvider.


CurrencyTxn — live ops + best‑effort rollback

CurrencyTxnTests

These tests assert what CurrencyTxn does and does not guarantee:

  • Operations apply in order
  • Rollback is attempted on failure
  • Rollback is best‑effort, not atomic

Truths proven:

  • Friendly stacks restore net‑zero for simple failures
  • Rollback can be blocked by real‑world conditions (eg. owner destroyed)
  • Under destination caps, transfer clamping reduces the effective amount up‑front
  • Therefore paid == received

This explicitly contrasts with CurrencyHoldTxn, where clamping may occur after payment.


Composition model

Hostile tests compose real services using public factories, then publish them via:

CurrencyBootstrap.Publish(ICurrencyService service)

This ensures all resolution paths observe the same instance during a test run.

Each test disposes the publish scope during teardown to prevent cross‑test leakage.


If a Hostile test fails

Do not weaken assertions to make the test pass.

Instead:

  • Fix composition if the stack is incorrect
  • Update documentation if a guarantee has changed
  • Promote or demote behaviour explicitly (guarantee ↔ best‑effort)

Hostile tests exist to keep the Currency system honest, predictable, and defensible.