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.