# Movement test strategy (corruption prevention)

**Goal:** Tests that catch **silent corruption**, not only happy-path CRUD.  
**Invariants:** [`inventory-invariants.md`](inventory-invariants.md). **Validation:** [`movement-validation-rules.md`](movement-validation-rules.md).

---

## Part 5 — Required test themes

### Concurrent movement attempts

- **Two sessions** `stock_out` same SKU last unit: exactly **one** succeeds; other gets **409** / insufficient stock; final `on_hand == 0`.
- Use real DB transactions + threads/forks or parallel `Http::` / queue workers with same tenant context.
- Assert **no** intermediate state visible where `on_hand` went negative under strict policy.

### Duplicate requests

- Same `Idempotency-Key` + body **twice**: second returns **same** movement id(s); `COUNT(*)` movements unchanged.
- Same key **different** body: **reject** with explicit error (collision), no movement.

### Deadlock retries

- Contrived lock order inversion (if possible in test DB): engine **retries** bounded times; eventual success **or** single failure after max retries; **no** duplicate movements (idempotency + TX rollback).

### Rebuild verification

- Given known ledger fixture, run rebuild job → compare every projection field to **golden** expected array.
- Inject **wrong** projection row in fixture DB → rebuild **detects** mismatch vs replay and fails or overwrites per spec.

### Invariant validation

- After each test scenario, assert **I1–I4** on projection rows (query or domain `StockBalance::assertInvariants()` in test helper).
- Property-style test (optional): random sequence of **allowed** movements never violates invariants under sequential apply.

### Correction chains

- Post `stock_in` then compensating `adjustment` negative link via `reverses_movement_id` → replay net equals projection; **no** UPDATE on original movement row (assert DB row unchanged except immutable columns none).

---

## Test levels

| Level | Focus |
|-------|--------|
| **Unit** | Pure delta calculator, validation rules, idempotency key normalization |
| **Integration** | DB + engine + projection row + TX rollback |
| **Concurrency** | Parallel outbounds, deadlock, lock ordering per [`transaction-engine.md`](transaction-engine.md) |
| **Soak (CI nightly)** | Large replay batch, memory/time bounds |

---

## Explicit non-goals for test suite (Phase 1)

- Full procurement / sales / manufacturing flows.
- Multi-warehouse transfer until type exists.

---

## CI gate suggestion

- Merge blocked if invariant / concurrency / idempotency test files in `tests/Feature/Inventory/Stock*` are **red** (once implemented).
