Currency — EditMode Tests¶
These tests live under:
Assets/RevFramework/Tests/EditMode/Currency/
They are Editor‑only (EditMode) tests and are split into two explicit categories:
- Hostile — public contract tests
- InternalTruth — implementation‑truth tests
This split is intentional and enforced via separate asmdefs.
If a behaviour is asserted in Hostile, it is a behaviour we are willing to stand behind as a system guarantee (or an explicitly documented best‑effort truth).
If a behaviour is asserted in InternalTruth, it reflects the current implementation and may change during refactors without constituting a breaking change.
Folder structure¶
Currency/
├─ Hostile/
│ ├─ CurrencyAuthorityTests.cs
│ ├─ CurrencyBatchEventTests.cs
│ ├─ CurrencyIdempotencyTests.cs
│ ├─ CurrencyRequireEscrowTests.cs
│ ├─ CurrencyHoldRequireEscrowTests.cs
│ ├─ CurrencyHoldTxnTests.cs
│ └─ CurrencyTxnTests.cs
│
├─ InternalTruth/
│ ├─ CurrencyPolicyPreviewTests.cs
│ ├─ CurrencyAuditDecoratorChainTests.cs
│ └─ CurrencyPolicyTestBuilder.cs
│
└─ README.md ← (this file)
Each folder has its own README describing intent and rules in more detail.
How to run¶
Unity Test Runner:
Window → General → Test Runner- Select EditMode
- Run tests under:
RevFramework.Currency.Tests.EditMode.HostileRevFramework.Currency.Tests.EditMode.InternalTruth
These tests compile into Editor‑only test assemblies and do not ship to Player builds.
Test categories¶
1) Hostile — public contract tests¶
Hostile tests validate the observable guarantees of the Currency system using only public APIs.
Rules:
- No reflection
- No access to private fields or internal methods
- No dependency on InternalTruth helpers
- Editor authoring (SerializedObject) is allowed for setting up test data
If a Hostile test fails, it means one of these happened: - A public contract was broken - Composition assumptions changed - Documentation and behaviour are out of sync
These tests represent what downstream users can rely on.
Key Hostile coverage¶
CurrencyHoldTxnTests — escrow‑first (HARD‑HOLD)¶
These tests exercise the real composed stack:
SceneCurrencyService(wallet)Caps(policy)AuditEscrow(hard‑hold)
They validate escrow‑first transaction guarantees:
- Partial hold failure releases previously acquired holds → net‑zero
- Preflight failure after holds releases holds → net‑zero
- One test intentionally composes a no‑caps stack (
wallet → audit → escrow) so that transaction preflight is the failure source - Successful transfer applies destination credit and commits holds
- Owner destroyed drops refunds safely (no resurrection, no crash)
- TTL expiry refunds when owner exists, drops when missing
- Destination clamp may result in
paid ≥ received(overflow discarded) when destination caps clamp the credit after the source has already paid
Time‑based behaviour is tested deterministically using ManualTimeProvider.
CurrencyTxnTests — live ops + best‑effort rollback¶
These tests validate CurrencyTxn’s intent and its truth:
- Applies staged operations in order
- On first failure, attempts rollback in reverse order
- Rollback is best‑effort, not atomic
Important truths asserted:
- In a friendly stack, rollback restores net‑zero for simple failure cases
- A truth test proves rollback is not guaranteed if the owner disappears mid‑transaction
- Under destination caps:
CappedCurrencyService.Transferreduces the effective transfer amount up‑front- Therefore paid == received for transfer clamping
- (This intentionally contrasts with
CurrencyHoldTxn, where clamping can occur after payment)
Additional Hostile coverage¶
- Authority gating (
CurrencyAuthorityTests) - Batch event semantics (
CurrencyBatchEventTests) - Idempotency behaviour (
CurrencyIdempotencyTests) - RequireEscrow guards (
CurrencyRequireEscrowTests,CurrencyHoldRequireEscrowTests) - Public audit exposure (
CurrencyAuditPublicTests)
2) InternalTruth — implementation truth tests¶
InternalTruth tests intentionally break encapsulation to pin down current behaviour.
Rules: - Reflection is allowed - Private fields and internal methods may be accessed - Tests may break during refactors without constituting a public breaking change
These tests exist to: - Prevent accidental semantic drift - Make implicit behaviour explicit - Support confident refactoring
InternalTruth coverage¶
CurrencyPolicyPreviewTests- Reflects
CurrencyTxn.TryPreview(...) - Proves what preview does and does not model
-
Explicitly documents divergence between preview and commit
-
CurrencyAuditDecoratorChainTests - Walks decorator chains via private fields
-
Proves audit is present but not forwarded through certain wrappers (eg. Escrow)
-
CurrencyPolicyTestBuilder - Reflection‑based helper for authoring policies
- Writes to private serialized fields and forces rebuilds
- Used only by InternalTruth tests
Notes on composition¶
Tests publish composed services via:
CurrencyBootstrap.Publish(ICurrencyService service)
This ensures any resolution paths (CurrencyResolve, pumps, etc.) observe the same instance during the test run.
Each test disposes the publish scope during teardown to prevent cross‑test leakage.
If a test fails¶
Do not “fix” failures by weakening assertions.
Instead:
- If the failure is due to composition, fix composition
- If the failure is due to contract drift, update docs and Hostile tests together
- If the failure reveals a new edge case, add coverage and decide the intended guarantee
These tests exist to keep the Currency system honest, explicit, and refactor‑safe.