From e90ed065e56e15fd214fd2153087a44e87de0072 Mon Sep 17 00:00:00 2001 From: Maurice Date: Thu, 8 Oct 2026 18:37:16 +0200 Subject: [PATCH] test(server): clear and age fixture rows through the ORM in converted specs These specs left the raw SQL baseline but still emptied tables with `db.exec('DELETE FROM ...')` and aged a Dawarich connection with a raw UPDATE, which the ratchet cannot see because it only counts `.prepare(`. They now go through deleteRows and updateRows like the rest of the file. packing_item_recipients and packing_bag_members have no entity; both cascade from their item and bag, so the packing spec relies on that with foreign keys on in the snapshot database. --- server/tests/e2e/packing.e2e.test.ts | 28 +++++++++++-------- server/tests/e2e/share.e2e.test.ts | 12 ++++---- server/tests/e2e/storage-admin.e2e.test.ts | 8 +++--- .../DawarichConnections.repository.test.ts | 8 +++--- .../tests/unit/mcp/tools-feeds-invite.test.ts | 4 +-- .../unit/nest/dawarich-sync.service.test.ts | 4 +-- .../unit/nest/google-quota.service.test.ts | 2 +- .../place-photo-cache.service.test.ts | 8 ++++-- 8 files changed, 41 insertions(+), 33 deletions(-) diff --git a/server/tests/e2e/packing.e2e.test.ts b/server/tests/e2e/packing.e2e.test.ts index 8d4cb14f2..0b5b2a641 100644 --- a/server/tests/e2e/packing.e2e.test.ts +++ b/server/tests/e2e/packing.e2e.test.ts @@ -34,14 +34,17 @@ import { createTestMikroOrmModule, createTestOrm, type TestOrm } from '../helper import { db } from '../../src/db/database'; import { dbNow } from '../../src/db/types/db-timestamp.type'; import { PackingBags } from '../../src/db/entities/PackingBags.entity'; +import { PackingCategoryAssignees } from '../../src/db/entities/PackingCategoryAssignees.entity'; +import { PackingItemContributors } from '../../src/db/entities/PackingItemContributors.entity'; import { PackingItems } from '../../src/db/entities/PackingItems.entity'; import { PackingTemplateCategories } from '../../src/db/entities/PackingTemplateCategories.entity'; import { PackingTemplateItems } from '../../src/db/entities/PackingTemplateItems.entity'; import { PackingTemplates } from '../../src/db/entities/PackingTemplates.entity'; import { TripMembers } from '../../src/db/entities/TripMembers.entity'; +import { Trips } from '../../src/db/entities/Trips.entity'; import { makeAdmin, makeUser } from '../helpers/factories/users'; import { makeTrip } from '../helpers/factories/trips'; -import { countRows, findRow, findRows, insertRow } from '../helpers/factories/rows'; +import { countRows, deleteRows, findRow, findRows, insertRow } from '../helpers/factories/rows'; let orm: TestOrm; @@ -86,17 +89,18 @@ describe('Packing e2e (real auth guard + real SQL over migrated temp SQLite)', ( }); beforeEach(async () => { - db.exec('DELETE FROM packing_category_assignees'); - db.exec('DELETE FROM packing_template_items'); - db.exec('DELETE FROM packing_template_categories'); - db.exec('DELETE FROM packing_templates'); - db.exec('DELETE FROM packing_item_recipients'); - db.exec('DELETE FROM packing_item_contributors'); - db.exec('DELETE FROM packing_bag_members'); - db.exec('DELETE FROM packing_items'); - db.exec('DELETE FROM packing_bags'); - db.exec('DELETE FROM trip_members'); - db.exec('DELETE FROM trips'); + await deleteRows(orm, PackingCategoryAssignees); + await deleteRows(orm, PackingTemplateItems); + await deleteRows(orm, PackingTemplateCategories); + await deleteRows(orm, PackingTemplates); + await deleteRows(orm, PackingItemContributors); + // packing_item_recipients and packing_bag_members have no entity of their + // own; both cascade on delete from their item and bag, so the two deletes + // below clear them too (foreign keys are on in the snapshot database). + await deleteRows(orm, PackingItems); + await deleteRows(orm, PackingBags); + await deleteRows(orm, TripMembers); + await deleteRows(orm, Trips); tripId = await insertTrip('Trip'); checkPermission.mockReturnValue(true); }); diff --git a/server/tests/e2e/share.e2e.test.ts b/server/tests/e2e/share.e2e.test.ts index 35df0fa2e..96e705b30 100644 --- a/server/tests/e2e/share.e2e.test.ts +++ b/server/tests/e2e/share.e2e.test.ts @@ -48,9 +48,11 @@ import { TestUnitOfWorkModule } from '../helpers/test-uow'; import { createTestMikroOrmModule, createTestOrm, type TestOrm } from '../helpers/test-orm'; import { makeUser } from '../helpers/factories/users'; import { makeTrip } from '../helpers/factories/trips'; -import { findRow, insertRow } from '../helpers/factories/rows'; +import { deleteRows, findRow, insertRow } from '../helpers/factories/rows'; import { Places } from '../../src/db/entities/Places.entity'; import { ShareTokens } from '../../src/db/entities/ShareTokens.entity'; +import { TripMembers } from '../../src/db/entities/TripMembers.entity'; +import { Trips } from '../../src/db/entities/Trips.entity'; let orm: TestOrm; @@ -83,10 +85,10 @@ describe('Share-link e2e (real auth guard + real SQL over temp SQLite)', () => { }); beforeEach(async () => { - db.exec('DELETE FROM share_tokens'); - db.exec('DELETE FROM places'); - db.exec('DELETE FROM trip_members'); - db.exec('DELETE FROM trips'); + await deleteRows(orm, ShareTokens); + await deleteRows(orm, Places); + await deleteRows(orm, TripMembers); + await deleteRows(orm, Trips); tripId = (await makeTrip(orm, 1, { title: 'Trip' })).id; checkPermission.mockReturnValue(true); serveKey.mockReset(); diff --git a/server/tests/e2e/storage-admin.e2e.test.ts b/server/tests/e2e/storage-admin.e2e.test.ts index 4b61e9296..1f6b9efdd 100644 --- a/server/tests/e2e/storage-admin.e2e.test.ts +++ b/server/tests/e2e/storage-admin.e2e.test.ts @@ -45,7 +45,7 @@ import { db } from '../../src/db/database'; import { AppSettings } from '../../src/db/entities/AppSettings.entity'; import { AuditLog } from '../../src/db/entities/AuditLog.entity'; import { makeAdmin, makeUser } from '../helpers/factories/users'; -import { countRows, findRow } from '../helpers/factories/rows'; +import { countRows, deleteRows, findRow } from '../helpers/factories/rows'; let orm: TestOrm; @@ -86,9 +86,9 @@ describe('Storage admin e2e (real auth + admin guard + managed guard + migrated // STORE2E-004 that expects to see fresh defaults must call GET (which // renders the registry's live snapshot) rather than assume the deleted rows // reset it — the registry only re-reads app_settings on `reload()`/init. - beforeEach(() => { - db.exec("DELETE FROM app_settings WHERE key LIKE 'storage.%'"); - db.exec('DELETE FROM audit_log'); + beforeEach(async () => { + await deleteRows(orm, AppSettings, { key: { $like: 'storage.%' } }); + await deleteRows(orm, AuditLog); }); afterAll(async () => { diff --git a/server/tests/unit/db/repositories/DawarichConnections.repository.test.ts b/server/tests/unit/db/repositories/DawarichConnections.repository.test.ts index 034953ff8..0b0ace157 100644 --- a/server/tests/unit/db/repositories/DawarichConnections.repository.test.ts +++ b/server/tests/unit/db/repositories/DawarichConnections.repository.test.ts @@ -3,7 +3,7 @@ import { createSnapshotTestDb } from '../../../helpers/db-mock'; import { resetTestDb } from '../../../helpers/test-db'; import { createTestOrm, type TestOrm } from '../../../helpers/test-orm'; import { createUser } from '../../../helpers/factories'; -import { countRows, findRow, insertRow, upsertRow } from '../../../helpers/factories/rows'; +import { countRows, deleteRows, findRow, insertRow, updateRows, upsertRow } from '../../../helpers/factories/rows'; import { DawarichVisitSuggestions } from '../../../../src/db/entities/DawarichVisitSuggestions.entity'; import { DawarichConnections } from '../../../../src/db/entities/DawarichConnections.entity'; import type { DawarichConnectionsRepository } from '../../../../src/db/repositories/DawarichConnections.repository'; @@ -16,10 +16,10 @@ beforeAll(async () => { t = await createTestOrm(testDb); connections = t.repo(DawarichConnections); }); -beforeEach(() => { +beforeEach(async () => { resetTestDb(testDb); t.clear(); - testDb.exec('DELETE FROM dawarich_connections'); + await deleteRows(t, DawarichConnections); }); afterAll(async () => { await t.close(); @@ -147,7 +147,7 @@ describe('DawarichConnectionsRepository', () => { it('CONNREPO-032: updated_at is bumped unconditionally, insert or merge alike', async () => { const { user } = createUser(testDb); await connections.upsertConnection(user.id, { url: 'https://d.example', allowInsecureTls: false, syncEnabled: true }); - testDb.exec(`UPDATE dawarich_connections SET updated_at = '2000-01-01 00:00:00'`); + await updateRows(t, DawarichConnections, {}, { updated_at: '2000-01-01 00:00:00' }); await connections.upsertConnection(user.id, { url: 'https://d.example', allowInsecureTls: false, syncEnabled: false }); diff --git a/server/tests/unit/mcp/tools-feeds-invite.test.ts b/server/tests/unit/mcp/tools-feeds-invite.test.ts index 4dfaa9b81..8b1503e76 100644 --- a/server/tests/unit/mcp/tools-feeds-invite.test.ts +++ b/server/tests/unit/mcp/tools-feeds-invite.test.ts @@ -27,7 +27,7 @@ import { createUser, createTrip, addTripMember } from '../../helpers/factories'; import { invalidatePermissionsCache } from '../../../src/nest/permissions/permissions-cache'; import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness'; import { createTestOrm, type TestOrm } from '../../helpers/test-orm'; -import { countRows, findRow, insertRow, updateRows } from '../../helpers/factories/rows'; +import { countRows, deleteRows, findRow, insertRow, updateRows } from '../../helpers/factories/rows'; import { AppSettings } from '../../../src/db/entities/AppSettings.entity'; import { AuditLog } from '../../../src/db/entities/AuditLog.entity'; import { TripInviteTokens } from '../../../src/db/entities/TripInviteTokens.entity'; @@ -77,7 +77,7 @@ beforeEach(async () => { // trip_invite_tokens is not in the shared helper's RESET_TABLES, and trip ids // restart after a DELETE-based reset, so a leftover row would attach itself // to the next test's trip. - testDb.exec('DELETE FROM trip_invite_tokens'); + await deleteRows(orm, TripInviteTokens); // The permission levels are cached in module state that resetTestDb knows // nothing about, so the one test that lowers share_manage would otherwise // keep the lowered level alive for every test after it. diff --git a/server/tests/unit/nest/dawarich-sync.service.test.ts b/server/tests/unit/nest/dawarich-sync.service.test.ts index 9377beb63..55d051ede 100644 --- a/server/tests/unit/nest/dawarich-sync.service.test.ts +++ b/server/tests/unit/nest/dawarich-sync.service.test.ts @@ -252,8 +252,8 @@ beforeEach(async () => { // RESET_TABLES in tests/helpers/test-db.ts predates this domain and does not // list its two tables; foreign keys are off during the reset, so rows would // otherwise outlive their user and leak into the next case. - testDb.exec('DELETE FROM dawarich_visit_suggestions'); - testDb.exec('DELETE FROM dawarich_connections'); + await deleteRows(t, DawarichVisitSuggestions); + await deleteRows(t, DawarichConnections); t.clear(); vi.clearAllMocks(); probeCapabilities.mockResolvedValue(CAPABILITIES); diff --git a/server/tests/unit/nest/google-quota.service.test.ts b/server/tests/unit/nest/google-quota.service.test.ts index ea8e8c692..52aaaeab3 100644 --- a/server/tests/unit/nest/google-quota.service.test.ts +++ b/server/tests/unit/nest/google-quota.service.test.ts @@ -31,7 +31,7 @@ describe('GoogleQuotaService', () => { }); beforeEach(async () => { - db.exec('DELETE FROM google_api_usage'); + await deleteRows(await sharedTestOrm(db), GoogleApiUsage); await deleteRows(await sharedTestOrm(db), AppSettings, { key: GOOGLE_DAILY_LIMIT_SETTING }); // A fresh service per case, so the once-a-day warning state starts clean. quota = new GoogleQuotaService(appSettings, usage); diff --git a/server/tests/unit/nest/place-photos/place-photo-cache.service.test.ts b/server/tests/unit/nest/place-photos/place-photo-cache.service.test.ts index 557bc2f1d..c6af17ab8 100644 --- a/server/tests/unit/nest/place-photos/place-photo-cache.service.test.ts +++ b/server/tests/unit/nest/place-photos/place-photo-cache.service.test.ts @@ -44,7 +44,7 @@ import type { PlacesRepository } from '../../../../src/db/repositories/Places.re import { CollectionPlaces } from '../../../../src/db/entities/CollectionPlaces.entity'; import type { CollectionPlacesRepository } from '../../../../src/db/repositories/CollectionPlaces.repository'; import { makeStorageFixture, type StorageFixture } from '../../../helpers/storage-fixture'; -import { findRow, insertRow, updateRows } from '../../../helpers/factories/rows'; +import { deleteRows, findRow, insertRow, updateRows } from '../../../helpers/factories/rows'; import { makeUser } from '../../../helpers/factories/users'; import { makeTrip } from '../../../helpers/factories/trips'; import { makePlace } from '../../../helpers/factories/places'; @@ -122,8 +122,10 @@ describe.each([ cache = new PlacePhotoCacheService(fx.storage, metaRepo, placesRepo, collectionPlacesRepo); }); - beforeEach(() => { - testDb.exec('DELETE FROM places; DELETE FROM collection_places; DELETE FROM google_place_photo_meta;'); + beforeEach(async () => { + await deleteRows(t, Places); + await deleteRows(t, CollectionPlaces); + await deleteRows(t, GooglePlacePhotoMeta); t.clear(); for (const f of fs.readdirSync(fx.root)) { if (f === '.tmp') continue;