From 930c575e5838d8b4c0f9372b91846d8408e11523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 11 May 2026 12:55:10 +0200 Subject: [PATCH] test(catalog-backend): simplify onConflict().ignore() and add UNIQUE constraint test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the explicit column list from .onConflict() calls in test setup — ON CONFLICT DO NOTHING without a target is equivalent since (entity_id, key, value) is the only unique constraint on the search table. Add a dedicated test in syncSearchRows that directly inserts a duplicate row and verifies it is silently rejected via the UNIQUE constraint. Signed-off-by: Fredrik Adelöw Co-authored-by: Cursor --- .../stitcher/syncSearchRows.test.ts | 20 +++++++++++++++++++ .../service/DefaultEntitiesCatalog.test.ts | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-backend/src/database/operations/stitcher/syncSearchRows.test.ts b/plugins/catalog-backend/src/database/operations/stitcher/syncSearchRows.test.ts index 49e67be736..0c7ac6f95a 100644 --- a/plugins/catalog-backend/src/database/operations/stitcher/syncSearchRows.test.ts +++ b/plugins/catalog-backend/src/database/operations/stitcher/syncSearchRows.test.ts @@ -224,6 +224,26 @@ describe.each(databases.eachSupportedId())('syncSearchRows, %p', databaseId => { expect(rows.map(r => r.value).sort()).toEqual(['java', 'python', 'rust']); }); + it('silently rejects a direct duplicate insert via the UNIQUE constraint', async () => { + await syncSearchRows(knex, 'e1', [row('a', 'x'), row('b', 'y')]); + + // Simulate a concurrent process inserting a duplicate directly — + // the UNIQUE constraint on (entity_id, key, value) should reject it + // silently. ON CONFLICT without a column list covers any unique + // constraint on the table, which is safe since (entity_id, key, value) + // is the only one. + await knex('search') + .insert({ entity_id: 'e1', key: 'a', value: 'x', original_value: 'x' }) + .onConflict() + .ignore(); + + const rows = await getSearchRows(); + expect(rows).toHaveLength(2); + expect(rows.find(r => r.key === 'a')).toEqual( + expect.objectContaining({ key: 'a', value: 'x' }), + ); + }); + it('simulates the typical steady-state case with one changed row', async () => { // Build a realistic-ish set of search rows const initial = [ diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts index 7abb3ca8a3..427a9c8ce0 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts @@ -2031,7 +2031,7 @@ describe('DefaultEntitiesCatalog', () => { original_value: 'B Test Entity', }, ]) - .onConflict(['entity_id', 'key', 'value']) + .onConflict() .ignore(); const catalog = new DefaultEntitiesCatalog({ @@ -2422,7 +2422,7 @@ describe('DefaultEntitiesCatalog', () => { original_value: 'one', }, ]) - .onConflict(['entity_id', 'key', 'value']) + .onConflict() .ignore(); const catalog = new DefaultEntitiesCatalog({