Unit + integration coverage for POST /api/workouts/batch.
Unit (database-free, mocks the Prisma layer):
- 401 on unauthenticated calls
- 413 when Content-Length exceeds the 5 MB ceiling, with the
documented workout.batch.payload_too_large error code
- 429 when the rate-limit gate blocks the call
- 400 when the workouts array exceeds the 100-entry cap
- 400 when the Zod schema rejects the payload
- 400 when a route exceeds the 20 000-point cap
- Fresh entries marked inserted
- Pre-existing entries flagged duplicate without re-inserting
- Race-reconciliation downgrades inserted statuses to duplicate
when createMany.count is below the attempted-row count
- WorkoutRoute rows attached by FK after the workout createMany
Integration (real Postgres in a testcontainer):
- Happy-path single workout with nested route lands as a Workout
row + WorkoutRoute row + 1:1 FK + computed durationSec
- 100-workout batch with mixed routes lands all 100 workouts and
exactly the expected number of route rows
- Over-cap batch (101 workouts) returns 400 with
workout.batch.too_large
- Over-cap route (20 001 points) returns 400 with
workout.batch.invalid
- Re-posted batch surfaces as duplicates without inserting twice
- Idempotency-Key replays the cached envelope and sets the
X-Idempotent-Replay header
- Pre-seeded rate-limit row triggers 429 and writes nothing
- Narrow-scope Bearer token admitted on a route that declares no
required permission (the W10 fix-C correctness fix the iOS
narrow-scope sync depends on)