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 0c7ac6f95a..9733f4361b 100644 --- a/plugins/catalog-backend/src/database/operations/stitcher/syncSearchRows.test.ts +++ b/plugins/catalog-backend/src/database/operations/stitcher/syncSearchRows.test.ts @@ -244,6 +244,24 @@ describe.each(databases.eachSupportedId())('syncSearchRows, %p', databaseId => { ); }); + it('overwrites original_value on conflict via ON CONFLICT DO UPDATE', async () => { + await syncSearchRows(knex, 'e1', [row('a', 'x', 'X')]); + + // Simulate a concurrent stitcher inserting the same (entity_id, key, value) + // with a different original_value casing. Unlike DO NOTHING, DO UPDATE + // requires an explicit conflict target — the column list is not optional. + await knex('search') + .insert({ entity_id: 'e1', key: 'a', value: 'x', original_value: 'x' }) + .onConflict(['entity_id', 'key', 'value']) + .merge(['original_value']); + + const rows = await getSearchRows(); + expect(rows).toHaveLength(1); + expect(rows[0]).toEqual( + expect.objectContaining({ key: 'a', value: 'x', original_value: 'x' }), + ); + }); + it('simulates the typical steady-state case with one changed row', async () => { // Build a realistic-ish set of search rows const initial = [