Integration test: idempotency cache survives replica restart (op-infra Step 12 follow-up)

Context

Op-infra Step 12 (MR pending) ships a PostgreSQL-backed IdempotencyCache::with_pool that persists cached responses across process restart and shares state across replicas. The unit tests in crates/canopy-api/src/idempotency.rs cover the in-memory backend only — the Backend::Postgres paths in check_cache and execute_and_cache (and the hourly TTL cleanup task) are not exercised by automated tests.

The original plan (Step 12.5) called for "tests for idempotency across simulated pod restarts", which was deferred from the implementation MR because the project does not have a multi-replica test harness yet.

Acceptance criteria

  1. New devstack-gated integration test under crates/canopy-api/tests/idempotency_persistence_test.rs:
    • Construct IdempotencyCache::with_pool against a live PgPool.
    • POST a request with an Idempotency-Key → cache it.
    • Drop the cache (simulate process restart) and reconstruct from the same pool.
    • Replay the same key → assert the cached response is returned with the x-idempotency-replay: true header.
  2. Cross-replica scenario: two IdempotencyCache::with_pool instances on the same pool — store on one, replay on the other.
  3. TTL test: insert a row with created_at = now() - interval '25 hours' directly via SQL → assert check_cache returns None (the created_at >= now() - interval '24 hours' filter is the safety net while cleanup is async).
  4. Test gated by infrastructure_available() and skipped on host runs without devstack — same pattern as canopy-mq reconnect tests (per ADR-015).

Out of scope

  • Redis backend coverage (still speculative; Postgres-only for phase 1 per plan §Step 12).
  • Performance benchmarks (separate concern).

Source

Op-infra plan Step 12, deferred sub-item (5).