Add test for onConflict refresh updating refresh_state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
This commit is contained in:
Fredrik Adelöw
2026-03-09 16:52:05 +01:00
parent e8dc06d2e9
commit 05a3e13b88
2 changed files with 59 additions and 11 deletions
@@ -15,7 +15,10 @@
*/
import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils';
import { ANNOTATION_ORIGIN_LOCATION } from '@backstage/catalog-model';
import {
ANNOTATION_ORIGIN_LOCATION,
stringifyEntityRef,
} from '@backstage/catalog-model';
import { v4 as uuid } from 'uuid';
import { applyDatabaseMigrations } from '../database/migrations';
import {
@@ -25,6 +28,7 @@ import {
DbSearchRow,
} from '../database/tables';
import { DefaultLocationStore } from './DefaultLocationStore';
import { locationSpecToLocationEntity } from '../util/conversion';
import { CatalogScmEventsServiceSubscriber } from '@backstage/plugin-catalog-node/alpha';
import waitFor from 'wait-for-expect';
@@ -154,6 +158,48 @@ describe('DefaultLocationStore', () => {
});
},
);
it.each(databases.eachSupportedId())(
'updates refresh_state when onConflict is refresh, %p',
async databaseId => {
const { store, knex } = await createLocationStore(databaseId);
const spec = {
type: 'url',
target:
'https://github.com/backstage/demo/blob/master/catalog-info.yml',
};
// Create the location initially
await store.createLocation(spec);
// Seed a refresh_state row for the corresponding Location entity
const entity = locationSpecToLocationEntity({ location: spec });
const entityRef = stringifyEntityRef(entity);
const entityId = uuid();
const oldDate = new Date('2020-01-01T00:00:00Z');
await knex<DbRefreshStateRow>('refresh_state').insert({
entity_id: entityId,
entity_ref: entityRef,
unprocessed_entity: '{}',
errors: '[]',
next_update_at: oldDate,
last_discovery_at: oldDate,
result_hash: 'old-hash',
});
// Re-register the same location with onConflict: 'refresh'
await store.createLocation(spec, { onConflict: 'refresh' });
// Verify that the refresh_state row was updated
const [row] = await knex<DbRefreshStateRow>('refresh_state').where({
entity_ref: entityRef,
});
expect(row.result_hash).toBe('');
expect(new Date(row.next_update_at).getTime()).toBeGreaterThan(
oldDate.getTime(),
);
},
);
});
describe('deleteLocation', () => {
@@ -105,23 +105,25 @@ export class DefaultLocationStore implements LocationStore, EntityProvider {
return inner;
});
// Always upsert the entity, even if the location already existed, to
// recover from cases where the entity was inadvertently deleted.
const entity = locationSpecToLocationEntity({ location });
await this.connection.applyMutation({
type: 'delta',
added: [{ entity, locationKey: getEntityLocationRef(entity) }],
removed: [],
});
if (existed) {
const entityRef = stringifyEntityRef(
locationSpecToLocationEntity({ location }),
);
// This is the "onConflict refresh" case, where a re-registration safely
// tries to recover from a bad state.
const entityRef = stringifyEntityRef(entity);
await this.db<DbRefreshStateRow>('refresh_state')
.where({ entity_ref: entityRef })
.update({
next_update_at: this.db.fn.now(),
result_hash: '',
});
} else {
const entity = locationSpecToLocationEntity({ location });
await this.connection.applyMutation({
type: 'delta',
added: [{ entity, locationKey: getEntityLocationRef(entity) }],
removed: [],
});
}
return location;