FED-M1-08 — End-to-end integration test that seeds a temp PGlite, runs mosaic storage migrate-tier, and verifies row counts + spot-checks against a live federated Postgres+pgvector profile.
Real bug surfaced
The integration test caught a P0 regression in M1-05 (#474) that all 32 unit tests missed because they mocked both source and target:
PostgresMigrationTarget.upsertBatch() used those keys verbatim as SQL identifiers, producing column "emailVerified" does not exist against the real federated DB.
Fix: normaliseSourceRow now applies a toSnakeCase conversion that is idempotent on already-snake_case keys (/[A-Z]/g → _<lowercase>). All 85 prior unit tests still pass; the integration test now passes against a live stack.
Test design
Seeds: users (2), teams (1), team_members (2), conversations (2), messages (5). All rows namespaced with fed-m1-08- (text PKs) or f0000xxx- UUIDs, deleted in afterAll even on test panic. No global state mutation. Test-prefix DELETE means --allow-non-empty is safe to use.
Test-utils placement
createPgliteDbWithVector and runPgliteMigrations were initially added to @mosaicstack/db public exports. Code review (sonnet) flagged this as polluting production consumers with a WASM bundle dependency. Moved to packages/storage/src/test-utils/pglite-with-vector.ts — co-located with the only consumer.
Independent review + verification
Code review (sonnet) — VERDICT: B → both items addressed in same PR
Surface verification (haiku) — claims-trustworthy across all 9 worker claims
85 storage unit tests still pass; 1 integration test gated by env var
## Summary
FED-M1-08 — End-to-end integration test that seeds a temp PGlite, runs `mosaic storage migrate-tier`, and verifies row counts + spot-checks against a live federated Postgres+pgvector profile.
## Real bug surfaced
The integration test caught a P0 regression in M1-05 (#474) that all 32 unit tests missed because they mocked both source and target:
- `DrizzleMigrationSource.readTable()` returns Drizzle's selected camelCase keys (`emailVerified`, `userId`, `createdAt`).
- `PostgresMigrationTarget.upsertBatch()` used those keys verbatim as SQL identifiers, producing `column "emailVerified" does not exist` against the real federated DB.
Fix: `normaliseSourceRow` now applies a `toSnakeCase` conversion that is idempotent on already-snake_case keys (`/[A-Z]/g` → `_<lowercase>`). All 85 prior unit tests still pass; the integration test now passes against a live stack.
## Test design
Seeds: `users` (2), `teams` (1), `team_members` (2), `conversations` (2), `messages` (5). All rows namespaced with `fed-m1-08-` (text PKs) or `f0000xxx-` UUIDs, deleted in `afterAll` even on test panic. No global state mutation. Test-prefix DELETE means `--allow-non-empty` is safe to use.
## Test-utils placement
`createPgliteDbWithVector` and `runPgliteMigrations` were initially added to `@mosaicstack/db` public exports. Code review (sonnet) flagged this as polluting production consumers with a WASM bundle dependency. Moved to `packages/storage/src/test-utils/pglite-with-vector.ts` — co-located with the only consumer.
## Independent review + verification
- Code review (sonnet) — VERDICT: B → both items addressed in same PR
- Surface verification (haiku) — claims-trustworthy across all 9 worker claims
- 85 storage unit tests still pass; 1 integration test gated by env var
## Verification
```
$ pnpm --filter @mosaicstack/storage test
Test Files 5 passed | 1 skipped (6)
Tests 85 passed | 1 skipped (86)
$ FEDERATED_INTEGRATION=1 pnpm --filter @mosaicstack/storage test src/migrate-tier.integration.test.ts
✓ migrate-tier — PGlite → federated PG (1 test) 4690ms
```
## Test plan
- [ ] CI green
- [ ] Manual smoke: rerun integration test against fresh federated compose
Refs #460
Seeds a temp PGlite with representative cross-table data (users, teams,
team_members, conversations, messages), runs mosaic storage migrate-tier
against a live federated PG+pgvector profile, asserts exact row counts and
spot-checks key columns. Gated by FEDERATED_INTEGRATION=1.
Caught and fixed P0 bug in migrate-tier:
PostgresMigrationTarget passed Drizzle-selected camelCase keys
(emailVerified, userId, ...) verbatim as SQL identifiers, failing on
Postgres which expects snake_case. The 32 unit tests missed this because
both source and target were mocked. normaliseSourceRow now applies
toSnakeCase conversion that is idempotent on already-snake_case keys.
Test infrastructure:
- packages/storage/src/test-utils/pglite-with-vector.ts: PGlite +
@electric-sql/pglite/vector (JS-native pgvector) and migration runner.
Co-located with storage tests rather than exposed on @mosaicstack/db
public surface (would have polluted prod consumers with WASM bundle).
- packages/storage now declared "type": "module" (codebase convention,
required for import.meta.url in test-utils).
afterAll cleanup deletes prefix-namespaced rows even on test panic.
Refs #460
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
FED-M1-08 — End-to-end integration test that seeds a temp PGlite, runs
mosaic storage migrate-tier, and verifies row counts + spot-checks against a live federated Postgres+pgvector profile.Real bug surfaced
The integration test caught a P0 regression in M1-05 (#474) that all 32 unit tests missed because they mocked both source and target:
DrizzleMigrationSource.readTable()returns Drizzle's selected camelCase keys (emailVerified,userId,createdAt).PostgresMigrationTarget.upsertBatch()used those keys verbatim as SQL identifiers, producingcolumn "emailVerified" does not existagainst the real federated DB.Fix:
normaliseSourceRownow applies atoSnakeCaseconversion that is idempotent on already-snake_case keys (/[A-Z]/g→_<lowercase>). All 85 prior unit tests still pass; the integration test now passes against a live stack.Test design
Seeds:
users(2),teams(1),team_members(2),conversations(2),messages(5). All rows namespaced withfed-m1-08-(text PKs) orf0000xxx-UUIDs, deleted inafterAlleven on test panic. No global state mutation. Test-prefix DELETE means--allow-non-emptyis safe to use.Test-utils placement
createPgliteDbWithVectorandrunPgliteMigrationswere initially added to@mosaicstack/dbpublic exports. Code review (sonnet) flagged this as polluting production consumers with a WASM bundle dependency. Moved topackages/storage/src/test-utils/pglite-with-vector.ts— co-located with the only consumer.Independent review + verification
Verification
Test plan
Refs #460