Genace

generation refund idempotency tests: verify ledger amounts and rollback boundaries

·

Refund reliability is an accounting invariant, not merely a helper call count. A useful test checks the balance, refund transactions, remaining spendable credit, and job settlement fields after repeated attempts and injected failures. Keep these tests isolated from production data and from paid provider calls.

Define the expected ledger invariant

For one job with an amount due, repeated settlement must not increase the returned total beyond that amount. Different jobs should contribute their own increments. A transaction failure must leave both balance and refund record unchanged, and a later successful retry must complete the same settlement.

Genace uses the deterministic identifier job-refund:{id} and a transaction that locks the balance row, inserts the REFUND record, and atomically increments balance. The returned record has positive remainingAmount so later FIFO consumption can spend it. Verify those effects together rather than assuming a nonthrowing function means the ledger is correct.

case A: same job, repeated failed settlement -> one total amount due
case B: same job, failed then canceled reason -> no second refund
case C: two jobs -> sum of their independent amounts
case D: balance update constraint fails -> no refund row remains
case E: matching legacy partial records -> add only outstanding difference
case F: later consumption -> returned remaining amount decreases correctly

This is an internal accounting test matrix. It does not describe a public partial-refund endpoint. Legacy partial records are considered when calculating how much remains to return, not an invitation for API clients to request arbitrary partial credits.

Use transaction-capable fixtures

The repository’s focused ledger tests execute SQL with PGlite and a reduced schema. They cover real constraint failure and rollback behavior within that test environment. They do not establish every production foreign-key invariant or multi-connection PostgreSQL locking behavior, so report those limits explicitly.

For deployment-critical concurrency guarantees, add a separate isolated PostgreSQL integration environment with independent connections and the relevant production constraints. Never point a test containing truncation or injected constraints at a production database. The test configuration should reject that possibility by construction.

Test settlement retries through the job boundary

A submission failure with submissionOutcome set to rejected can be failed while creditsActual remains unsettled. A legacy failure code without that evidence must not enter automatic recovery. Simulate refund failure, then observe the job again and verify settlement succeeds without another provider submission. If refund succeeded but saving zero accounting failed, retry should recognize the existing ledger entry rather than pay twice.

Also test failure to persist the submission marker. That case cannot be resolved by pretending every old queued job should be refunded. Preserve the distinction between a known marker and an unknown external acceptance outcome.

Verify assertions and exclusions

Check exact amounts, not only transaction count. Include matching legacy full refunds, several partial records whose sum is below the amount due, and records belonging to another job or user. Assert unrelated records do not satisfy the settlement.

Finally, verify the tests never invoke a paid model and do not claim creation idempotency. An idempotent refund prevents duplicate repayment; repeating a generation POST can still create another job.

See failed job accounting, fault injection, and controlled timing.

Unknown submission outcomes need reconciliation

Transport failures and malformed acceptance responses use UPSTREAM_SUBMISSION_UNCERTAIN, preserve the queued reservation, and do not trigger another creation request. Nonterminal jobs with an uncertain marker or either missing upstream handle cannot be canceled into a refund: the route returns HTTP 409 SUBMISSION_RECONCILIATION_REQUIRED, including when saving the uncertainty marker failed. Legacy failure codes without rejection evidence need manual investigation, not bulk balance changes. Read the unknown-submission reference.

Continue with production operations

Where these facts come from

  • codebase: src/ai/providers/fal-client.ts
  • Editorial keyword hypothesis; search volume not measured
  • codebase: src/ai/api/credits.ts
  • codebase: src/ai/jobs/submission-ledger.test.ts
  • codebase: src/test/job-ledger-db.ts
  • codebase: src/credits/credits.ts