test(integration): pre-clean fixture rows by stable marker, not in-memory ids - #2012
test(integration): pre-clean fixture rows by stable marker, not in-memory ids#2012jakebromberg wants to merge 1 commit into
Conversation
…mory ids Integration specs that only clean up in afterAll, keyed on an in-memory id array, lose that array if the process dies first — routine under jest.config.json's forceExit: true. The orphans aren't inert: a crashed enrichment-worker-streaming-reask run leaves library rows with a Various-Artists-shaped artist_name joined to album_metadata rows carrying a non-null apple_music_url, which satisfies jobs/va-apple-music-url-remediation's albumMetadataNet exactly, so a later --dry-run sizing of that job on a persistent dev database silently counts leftover test fixtures as production candidates. Add a beforeAll pre-clean to each affected describe block that deletes by the distinctive fixture marker already present in the seeded data (album_title LIKE 'bs1915-reask-test-%', artist_name LIKE 'ZZDEDUP %' / 'ZZMERGE%') instead of the in-memory array, so a prior crashed run's rows are found with no prior knowledge. The existing afterAll/afterEach cleanup is left in place; the pre-clean is a recovery path, not a replacement. Verified against real Postgres rather than assumed: album_metadata.album_id does cascade from library.id, so the streaming-reask pre-clean is one statement. artists is NOT uniformly cascaded, though — genre_artist_ crossreference.artist_id and library.artist_id are both NO ACTION despite schema.ts's genre_artist_crossreference declaration reading cascade, an apparent schema/migration drift caught by querying information_schema directly. The two artist-unicode-dedup pre-cleans delete children before parents explicitly, in the same order their own existing cleanup hooks already use, rather than relying on cascade. Swept tests/integration/ for the same shape (afterAll/afterEach cleanup keyed on an in-memory id array with no predicate-based pre-clean). Most don't correspond to a coarse, unwindowed candidate net the way this pattern does; flowsheet-ghost-row-sweep.spec.js looked like a candidate but turned out to already be immune (every test runs inside withRollback, so a crashed process loses the transaction along with the rows). artist-unicode-dedup.spec.js and artist-unicode-dedup-merge.spec.js are the one clear match found: both seed genuinely fold-duplicate artists rows for jobs/artist-unicode-dedup, a one-shot job with the identical dry-run-then-execute shape as va-apple-music-url-remediation.
|
One correction to the rationale in the PR description, so a cold reader doesn't inherit a wrong premise: the The change is still correct and worth landing — for a better reason than the issue gave. Both Nothing to change in the diff. |
Closes #2011
Problem
tests/integration/enrichment-worker-streaming-reask.spec.jscleans up its fixture rows only inafterAll, keyed on an in-memoryinsertedAlbumIdsarray. If the process dies beforeafterAllruns — routine underjest.config.json'sforceExit: true— the array is lost and the rows survive.The orphans aren't inert. They're
libraryrows with a Various-Artists-shapedartist_namejoined toalbum_metadatarows carrying a non-nullapple_music_url, which satisfiesjobs/va-apple-music-url-remediation'salbumMetadataNetexactly. On a persistent dev database, a later local--dry-runsizing of that job silently counts leftover test fixtures as production candidates — defeating the entire point of a dry-run.Fix
Added a
beforeAllpre-clean to each affecteddescribeblock that deletes by the distinctive fixture marker already present in the seeded data, instead of relying on the in-memory id array:enrichment-worker-streaming-reask.spec.js(bothdescribeblocks) —library.album_title LIKE 'bs1915-reask-test-%'.album_metadata.album_idcascades fromlibrary.id(verified againstinformation_schema, not assumed), so oneDELETE FROM librarycovers both tables.artist-unicode-dedup.spec.js—artists.artist_name LIKE 'ZZDEDUP %'artist-unicode-dedup-merge.spec.js—artists.artist_name LIKE 'ZZMERGE%'The existing
afterAll/afterEachcleanup is untouched — the pre-clean is a recovery path, not a replacement for cleaning up after yourself. No change to what any spec asserts.Why the two
artist-unicode-dedupspecsThe issue named
enrichment-worker-streaming-reask.spec.jsas the donor and asked for a sweep oftests/integration/for the same shape. MostafterAll/afterEach-only specs don't correspond to a coarse, unwindowed candidate net the way this pattern does. Two clear matches turned up:artist-unicode-dedup.spec.jsandartist-unicode-dedup-merge.spec.jsseed genuinely fold-duplicateartistsrows (that's the fixture under test) forjobs/artist-unicode-dedup— a one-shot job with the identical dry-run-then---executeshape asva-apple-music-url-remediation. A crashed run leaves real merge candidates behind for the next local--dry-runsizing to silently count.flowsheet-ghost-row-sweep.spec.jslooked like a candidate (same job shape, same table family) but turned out to already be immune: every test runs insidewithRollback, so a crashed process loses the whole transaction along with the rows. No change needed.The remaining ~65 files matching a bare
afterAll/afterEachgrep are almost all recurring-cron fixtures (concerts, catalog-popularity, flowsheet-metadata-backfill, etc.) whose production jobs run on a schedule rather than via an operator-eyeballed--dry-runcount, or already scope their own candidate queries with an explicit id/scope predicate. Left alone here rather than ballooning this diff; flagging as a candidate follow-up if the convention proves worth generalizing further.A finding along the way
The issue said to verify the
album_metadata-cascades-from-libraryclaim against the migration rather than assume it — that held. Applying the same discipline to theartists-referencing tables in the two dedup specs surfaced a real discrepancy:shared/database/src/schema.tsdeclaresgenre_artist_crossreference.artist_idasonDelete: 'cascade', but queryinginformation_schema.referential_constraintsagainst a real migrated database showsNO ACTION. I didn't chase that down further — the two new pre-cleans sidestep it entirely by deleting children before parents explicitly (mirroring the order each file's own existing cleanup hook already uses), the same defensive posture theartist-unicode-dedup-merge.spec.jsfile's existing comment already took for exactly this reason. Worth its own look if the schema/migration drift is unexpected.Verification
Ran directly against real Postgres (migrations applied, real FK constraints) rather than trusting the SQL by inspection: inserted fixture rows matching each new marker to simulate a crashed run, ran each new pre-clean statement, and confirmed the rows (and cascaded/child rows) are gone, the statements are idempotent on a second run, and unrelated seed data is untouched. Separately confirmed a naive parent-first delete on the dedup fixture genuinely fails with a
foreign_key_violation, so the child-then-parent ordering is load-bearing, not decorative.Could not run the actual Jest integration suite end to end.
npm run ci:env's Docker image build forapps/backend/apps/authfails atnpm install --omit=devwith401 Unauthorized … npm.pkg.github.com/@wxyc/shared— a stale localNPM_TOKEN, not a code problem, and it does not predict CI.jest'sglobalSetupgates every integration spec (including a pure-DB one) on backend:8081 + auth:8083 health, so there was no way to run the actual spec files without those images. The Postgres-only verification above is a real substitute for correctness of the SQL, but it is not the same as the specs passing, and I did not run the "twice in a row withafterAlldisabled" acceptance check against the real spec files.Checks run
npm run format:checknpm run lintnpm run typechecknode --checkon all three touched filesnpm run ci:testmock/ real Jest integration runNPM_TOKENauth failure blocks the Docker image build (see above)GitHub Actions is in a confirmed
major_outageas of 2026-08-06 ~11:30 PT — this PR will show zero checks and none were re-triggered or polled.Follow-up for PR #2008
#2008 adds
tests/integration/va-apple-music-url-remediation-invalidate.spec.js, which inherits the sameafterAll-only pattern from this donor. Left commented on that PR to adopt this convention on rebase, per the sequencing in #2011 — not stacked, since #2008 is already blocked on the CI outage.