diff --git a/tests/integration/artist-unicode-dedup-merge.spec.js b/tests/integration/artist-unicode-dedup-merge.spec.js index 977982d1c..a9230012b 100644 --- a/tests/integration/artist-unicode-dedup-merge.spec.js +++ b/tests/integration/artist-unicode-dedup-merge.spec.js @@ -60,8 +60,37 @@ describe('artist-unicode-dedup mergeGroup — REAL functions (real PG, BS#1897 M let sql; const artistIds = []; - beforeAll(() => { + beforeAll(async () => { sql = getTestDb(); + // Recovery path (BS#2011): `afterEach` below narrows the crash window to + // one in-flight test, but a process death still skips it — routine under + // `jest.config.json`'s `forceExit: true`. A crashed prior run leaves + // genuinely fold-duplicate `artists` rows (the fixture shape under test), + // which `jobs/artist-unicode-dedup`'s `--dry-run` sizing would then + // silently count as real merge candidates on a persistent dev DB. Delete + // by the stable `ZZMERGE` marker every seeded name in this file carries + // (`forms()` always prefixes with a `ZZMERGE...` test id), with no prior + // knowledge required. Same child-then-parent order as `afterEach`. + const marker = 'ZZMERGE%'; + await sql` + DELETE FROM ${sql(SCHEMA)}.artist_crossreference + WHERE source_artist_id IN (SELECT id FROM ${sql(SCHEMA)}.artists WHERE artist_name LIKE ${marker}) + OR target_artist_id IN (SELECT id FROM ${sql(SCHEMA)}.artists WHERE artist_name LIKE ${marker}) + `; + await sql` + DELETE FROM ${sql(SCHEMA)}.artist_search_alias + WHERE artist_id IN (SELECT id FROM ${sql(SCHEMA)}.artists WHERE artist_name LIKE ${marker}) + OR related_artist_id IN (SELECT id FROM ${sql(SCHEMA)}.artists WHERE artist_name LIKE ${marker}) + `; + await sql` + DELETE FROM ${sql(SCHEMA)}.genre_artist_crossreference + WHERE artist_id IN (SELECT id FROM ${sql(SCHEMA)}.artists WHERE artist_name LIKE ${marker}) + `; + await sql` + DELETE FROM ${sql(SCHEMA)}.library + WHERE artist_id IN (SELECT id FROM ${sql(SCHEMA)}.artists WHERE artist_name LIKE ${marker}) + `; + await sql`DELETE FROM ${sql(SCHEMA)}.artists WHERE artist_name LIKE ${marker}`; }); afterEach(async () => { diff --git a/tests/integration/artist-unicode-dedup.spec.js b/tests/integration/artist-unicode-dedup.spec.js index 520e808db..7b2c331f9 100644 --- a/tests/integration/artist-unicode-dedup.spec.js +++ b/tests/integration/artist-unicode-dedup.spec.js @@ -108,8 +108,29 @@ describe('Unicode-form artist matcher + dedup (real PG, BS#1897)', () => { let sql; const artistIds = []; - beforeAll(() => { + beforeAll(async () => { sql = getTestDb(); + // Recovery path (BS#2011): `afterAll` below only knows about the + // in-memory `artistIds`, lost if the process dies before it runs — + // routine under `jest.config.json`'s `forceExit: true`. A crashed prior + // run of this file leaves genuinely fold-duplicate `artists` rows (that's + // the fixture shape under test), which `jobs/artist-unicode-dedup`'s + // `--dry-run` sizing would then silently count as real merge candidates + // on a persistent dev DB. Delete by the stable `ZZDEDUP ` marker every + // seeded name here carries, with no prior knowledge required. + // `library.artist_id` has no `onDelete: 'cascade'` in this schema (the + // upstream tubafrenzy-derived NOT NULL FK — see the `library` table + // comment), so children go first — same order as the `afterAll` below. + const marker = 'ZZDEDUP %'; + await sql` + DELETE FROM ${sql(SCHEMA)}.library + WHERE artist_id IN (SELECT id FROM ${sql(SCHEMA)}.artists WHERE artist_name LIKE ${marker}) + `; + await sql` + DELETE FROM ${sql(SCHEMA)}.genre_artist_crossreference + WHERE artist_id IN (SELECT id FROM ${sql(SCHEMA)}.artists WHERE artist_name LIKE ${marker}) + `; + await sql`DELETE FROM ${sql(SCHEMA)}.artists WHERE artist_name LIKE ${marker}`; }); afterAll(async () => { diff --git a/tests/integration/enrichment-worker-streaming-reask.spec.js b/tests/integration/enrichment-worker-streaming-reask.spec.js index 2c6e7de55..b56d55e6c 100644 --- a/tests/integration/enrichment-worker-streaming-reask.spec.js +++ b/tests/integration/enrichment-worker-streaming-reask.spec.js @@ -143,8 +143,16 @@ describe('enrichment-worker streaming self-heal — BS#1915 candidate query + wr let sql; const insertedAlbumIds = []; - beforeAll(() => { + beforeAll(async () => { sql = getTestDb(); + // Recovery path (BS#2011): `afterAll` below only knows about + // `insertedAlbumIds`, an in-memory array lost if the process dies before + // it runs — routine under `jest.config.json`'s `forceExit: true`. Delete + // by the stable `bs1915-reask-test-` marker `insertLibraryAlbum` stamps + // into `library.album_title` instead, so a prior crashed run's orphans + // are found with no prior knowledge. Cascades to `album_metadata` via its + // `album_id` FK (`onDelete: 'cascade'`, `shared/database/src/schema.ts`). + await sql`DELETE FROM ${sql(SCHEMA)}.library WHERE album_title LIKE ${'bs1915-reask-test-%'}`; }); afterAll(async () => { @@ -321,8 +329,12 @@ describe('enrichment-worker streaming self-heal — Bandcamp de-freeze candidate const insertedAlbumIds = []; const priorFlag = process.env.ENRICHMENT_BANDCAMP_REASK; - beforeAll(() => { + beforeAll(async () => { sql = getTestDb(); + // Same recovery path as the describe block above — `insertFrozenBandcampRow` + // routes through the same `insertLibraryAlbum` helper and marker prefix. + // Pre-cleaning here too keeps this block self-sufficient (e.g. `.only`). + await sql`DELETE FROM ${sql(SCHEMA)}.library WHERE album_title LIKE ${'bs1915-reask-test-%'}`; }); afterAll(async () => {