diff --git a/server/tests/helpers/factories.ts b/server/tests/helpers/factories.ts index d2d3f2f6..a4bd57af 100644 --- a/server/tests/helpers/factories.ts +++ b/server/tests/helpers/factories.ts @@ -558,10 +558,23 @@ export function addTripPhoto( provider: string, opts: { shared?: boolean; albumLinkId?: number } = {} ): TestTripPhoto { + // Insert into trek_photos first (central registry) + db.prepare( + 'INSERT OR IGNORE INTO trek_photos (provider, asset_id, owner_id) VALUES (?, ?, ?)' + ).run(provider, assetId, userId); + const trekPhoto = db.prepare( + 'SELECT id FROM trek_photos WHERE provider = ? AND asset_id = ? AND owner_id = ?' + ).get(provider, assetId, userId) as { id: number }; + const result = db.prepare( - 'INSERT OR IGNORE INTO trip_photos (trip_id, user_id, asset_id, provider, shared, album_link_id) VALUES (?, ?, ?, ?, ?, ?)' - ).run(tripId, userId, assetId, provider, opts.shared ? 1 : 0, opts.albumLinkId ?? null); - return db.prepare('SELECT * FROM trip_photos WHERE id = ?').get(result.lastInsertRowid) as TestTripPhoto; + 'INSERT OR IGNORE INTO trip_photos (trip_id, user_id, photo_id, shared, album_link_id) VALUES (?, ?, ?, ?, ?)' + ).run(tripId, userId, trekPhoto.id, opts.shared ? 1 : 0, opts.albumLinkId ?? null); + return db.prepare(` + SELECT tp.id, tp.trip_id, tp.user_id, tkp.asset_id, tkp.provider, tp.shared, tp.album_link_id + FROM trip_photos tp + JOIN trek_photos tkp ON tkp.id = tp.photo_id + WHERE tp.id = ? + `).get(result.lastInsertRowid) as TestTripPhoto; } export interface TestAlbumLink { diff --git a/server/tests/integration/immich.test.ts b/server/tests/integration/immich.test.ts index 6a4c40f8..4ddd82f3 100644 --- a/server/tests/integration/immich.test.ts +++ b/server/tests/integration/immich.test.ts @@ -190,11 +190,16 @@ describe('Immich album links', () => { .get(trip.id, user.id, 'album-xyz', 'Album XYZ', 'immich') as any; // Insert photos synced from the album - testDb.prepare('INSERT INTO trip_photos (trip_id, user_id, asset_id, provider, shared, album_link_id) VALUES (?, ?, ?, ?, 1, ?)').run(trip.id, user.id, 'asset-001', 'immich', linkResult.id); - testDb.prepare('INSERT INTO trip_photos (trip_id, user_id, asset_id, provider, shared, album_link_id) VALUES (?, ?, ?, ?, 1, ?)').run(trip.id, user.id, 'asset-002', 'immich', linkResult.id); + for (const assetId of ['asset-001', 'asset-002']) { + testDb.prepare('INSERT OR IGNORE INTO trek_photos (provider, asset_id, owner_id) VALUES (?, ?, ?)').run('immich', assetId, user.id); + const tkp = testDb.prepare('SELECT id FROM trek_photos WHERE provider = ? AND asset_id = ? AND owner_id = ?').get('immich', assetId, user.id) as any; + testDb.prepare('INSERT INTO trip_photos (trip_id, user_id, photo_id, shared, album_link_id) VALUES (?, ?, ?, 1, ?)').run(trip.id, user.id, tkp.id, linkResult.id); + } // Insert an individually-added photo (no album_link_id) - testDb.prepare('INSERT INTO trip_photos (trip_id, user_id, asset_id, provider, shared) VALUES (?, ?, ?, ?, 1)').run(trip.id, user.id, 'asset-manual', 'immich'); + testDb.prepare('INSERT OR IGNORE INTO trek_photos (provider, asset_id, owner_id) VALUES (?, ?, ?)').run('immich', 'asset-manual', user.id); + const tkpManual = testDb.prepare('SELECT id FROM trek_photos WHERE provider = ? AND asset_id = ? AND owner_id = ?').get('immich', 'asset-manual', user.id) as any; + testDb.prepare('INSERT INTO trip_photos (trip_id, user_id, photo_id, shared) VALUES (?, ?, ?, 1)').run(trip.id, user.id, tkpManual.id); const res = await request(app) .delete(`/api/integrations/memories/unified/trips/${trip.id}/album-links/${linkResult.id}`) @@ -204,7 +209,11 @@ describe('Immich album links', () => { expect(res.body.success).toBe(true); // Album-linked photos should be gone - const remainingPhotos = testDb.prepare('SELECT * FROM trip_photos WHERE trip_id = ?').all(trip.id) as any[]; + const remainingPhotos = testDb.prepare(` + SELECT tp.*, tkp.asset_id FROM trip_photos tp + JOIN trek_photos tkp ON tkp.id = tp.photo_id + WHERE tp.trip_id = ? + `).all(trip.id) as any[]; expect(remainingPhotos.length).toBe(1); expect(remainingPhotos[0].asset_id).toBe('asset-manual'); @@ -220,7 +229,9 @@ describe('Immich album links', () => { const linkResult = testDb.prepare('INSERT INTO trip_album_links (trip_id, user_id, album_id, album_name, provider) VALUES (?, ?, ?, ?, ?) RETURNING *') .get(trip.id, owner.id, 'album-secret', 'Secret Album', 'immich') as any; - testDb.prepare('INSERT INTO trip_photos (trip_id, user_id, asset_id, provider, shared, album_link_id) VALUES (?, ?, ?, ?, 1, ?)').run(trip.id, owner.id, 'asset-owned', 'immich', linkResult.id); + testDb.prepare('INSERT OR IGNORE INTO trek_photos (provider, asset_id, owner_id) VALUES (?, ?, ?)').run('immich', 'asset-owned', owner.id); + const tkpOwned = testDb.prepare('SELECT id FROM trek_photos WHERE provider = ? AND asset_id = ? AND owner_id = ?').get('immich', 'asset-owned', owner.id) as any; + testDb.prepare('INSERT INTO trip_photos (trip_id, user_id, photo_id, shared, album_link_id) VALUES (?, ?, ?, 1, ?)').run(trip.id, owner.id, tkpOwned.id, linkResult.id); // Non-member tries to delete owner's album link — should be denied const res = await request(app) @@ -232,7 +243,11 @@ describe('Immich album links', () => { // Link and photos should still exist const link = testDb.prepare('SELECT * FROM trip_album_links WHERE id = ?').get(linkResult.id); expect(link).toBeDefined(); - const photo = testDb.prepare('SELECT * FROM trip_photos WHERE asset_id = ?').get('asset-owned'); + const photo = testDb.prepare(` + SELECT tp.* FROM trip_photos tp + JOIN trek_photos tkp ON tkp.id = tp.photo_id + WHERE tkp.asset_id = ? + `).get('asset-owned'); expect(photo).toBeDefined(); }); diff --git a/server/tests/integration/memories-immich.test.ts b/server/tests/integration/memories-immich.test.ts index 876e5d99..0a27b45f 100644 --- a/server/tests/integration/memories-immich.test.ts +++ b/server/tests/integration/memories-immich.test.ts @@ -531,7 +531,11 @@ describe('Immich syncAlbumAssets', () => { expect(typeof res.body.added).toBe('number'); // Verify photos were inserted into the DB - const photos = testDb.prepare('SELECT * FROM trip_photos WHERE trip_id = ? AND user_id = ?').all(trip.id, user.id) as any[]; + const photos = testDb.prepare(` + SELECT tp.*, tkp.provider FROM trip_photos tp + JOIN trek_photos tkp ON tkp.id = tp.photo_id + WHERE tp.trip_id = ? AND tp.user_id = ? + `).all(trip.id, user.id) as any[]; expect(photos.length).toBeGreaterThan(0); expect(photos[0].provider).toBe('immich'); }); diff --git a/server/tests/integration/memories-synology.test.ts b/server/tests/integration/memories-synology.test.ts index 11371bea..b8afc049 100644 --- a/server/tests/integration/memories-synology.test.ts +++ b/server/tests/integration/memories-synology.test.ts @@ -470,9 +470,11 @@ describe('Synology asset access', () => { const { user: member } = createUser(testDb); // Insert a shared photo referencing a trip that doesn't exist (FK disabled temporarily) testDb.exec('PRAGMA foreign_keys = OFF'); + testDb.prepare('INSERT OR IGNORE INTO trek_photos (provider, asset_id, owner_id) VALUES (?, ?, ?)').run('synologyphotos', '101_cachekey', owner.id); + const tkpSyno35 = testDb.prepare('SELECT id FROM trek_photos WHERE provider = ? AND asset_id = ? AND owner_id = ?').get('synologyphotos', '101_cachekey', owner.id) as any; testDb.prepare( - 'INSERT INTO trip_photos (trip_id, user_id, asset_id, provider, shared) VALUES (?, ?, ?, ?, ?)' - ).run(9999, owner.id, '101_cachekey', 'synologyphotos', 1); + 'INSERT INTO trip_photos (trip_id, user_id, photo_id, shared) VALUES (?, ?, ?, ?)' + ).run(9999, owner.id, tkpSyno35.id, 1); testDb.exec('PRAGMA foreign_keys = ON'); const res = await request(app) @@ -568,7 +570,11 @@ describe('Synology syncSynologyAlbumLink', () => { expect(typeof res.body.total).toBe('number'); // Verify photos were inserted into the DB - const photos = testDb.prepare('SELECT * FROM trip_photos WHERE trip_id = ? AND user_id = ?').all(trip.id, user.id) as any[]; + const photos = testDb.prepare(` + SELECT tp.*, tkp.provider FROM trip_photos tp + JOIN trek_photos tkp ON tkp.id = tp.photo_id + WHERE tp.trip_id = ? AND tp.user_id = ? + `).all(trip.id, user.id) as any[]; expect(photos.length).toBeGreaterThan(0); expect(photos[0].provider).toBe('synologyphotos'); }); diff --git a/server/tests/integration/memories-unified.test.ts b/server/tests/integration/memories-unified.test.ts index 2d10e8f2..2d856201 100644 --- a/server/tests/integration/memories-unified.test.ts +++ b/server/tests/integration/memories-unified.test.ts @@ -146,7 +146,11 @@ describe('Unified photo management', () => { expect(res.status).toBe(200); expect(res.body.added).toBe(2); - const rows = testDb.prepare('SELECT asset_id FROM trip_photos WHERE trip_id = ?').all(trip.id) as any[]; + const rows = testDb.prepare(` + SELECT tkp.asset_id FROM trip_photos tp + JOIN trek_photos tkp ON tkp.id = tp.photo_id + WHERE tp.trip_id = ? + `).all(trip.id) as any[]; expect(rows.map((r: any) => r.asset_id)).toEqual(expect.arrayContaining(['asset-a', 'asset-b'])); }); @@ -178,14 +182,23 @@ describe('Unified photo management', () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id); addTripPhoto(testDb, trip.id, user.id, 'asset-tog', 'immich', { shared: false }); + const trekRef = testDb.prepare(` + SELECT tp.photo_id FROM trip_photos tp + JOIN trek_photos tkp ON tkp.id = tp.photo_id + WHERE tp.trip_id = ? AND tkp.asset_id = ? + `).get(trip.id, 'asset-tog') as any; const res = await request(app) .put(`${photosUrl(trip.id)}/sharing`) .set('Cookie', authCookie(user.id)) - .send({ provider: 'immich', asset_id: 'asset-tog', shared: true }); + .send({ photo_id: trekRef.photo_id, shared: true }); expect(res.status).toBe(200); - const row = testDb.prepare('SELECT shared FROM trip_photos WHERE asset_id = ?').get('asset-tog') as any; + const row = testDb.prepare(` + SELECT tp.shared FROM trip_photos tp + JOIN trek_photos tkp ON tkp.id = tp.photo_id + WHERE tkp.asset_id = ? + `).get('asset-tog') as any; expect(row.shared).toBe(1); }); @@ -206,14 +219,23 @@ describe('Unified photo management', () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id); addTripPhoto(testDb, trip.id, user.id, 'asset-del', 'immich'); + const trekRef = testDb.prepare(` + SELECT tp.photo_id FROM trip_photos tp + JOIN trek_photos tkp ON tkp.id = tp.photo_id + WHERE tp.trip_id = ? AND tkp.asset_id = ? + `).get(trip.id, 'asset-del') as any; const res = await request(app) .delete(photosUrl(trip.id)) .set('Cookie', authCookie(user.id)) - .send({ provider: 'immich', asset_id: 'asset-del' }); + .send({ photo_id: trekRef.photo_id }); expect(res.status).toBe(200); - const row = testDb.prepare('SELECT * FROM trip_photos WHERE asset_id = ?').get('asset-del'); + const row = testDb.prepare(` + SELECT tp.* FROM trip_photos tp + JOIN trek_photos tkp ON tkp.id = tp.photo_id + WHERE tkp.asset_id = ? + `).get('asset-del'); expect(row).toBeUndefined(); }); diff --git a/server/tests/unit/services/atlasService.test.ts b/server/tests/unit/services/atlasService.test.ts index 6248cf44..83282b3d 100644 --- a/server/tests/unit/services/atlasService.test.ts +++ b/server/tests/unit/services/atlasService.test.ts @@ -473,10 +473,12 @@ describe('getVisitedRegions', () => { const trip = createTrip(testDb, user.id, { title: 'Paris Trip' }); insertPlaceWithCoords(testDb, trip.id, 'Paris Hotel', 48.85, 2.35); - const resultPromise = getVisitedRegions(user.id); + // First call triggers the background geocoding fire-and-forget + await getVisitedRegions(user.id); // Advance all pending timers (including the 1100ms Nominatim rate-limit delay) await vi.runAllTimersAsync(); - const result = await resultPromise; + // Second call returns now-cached data + const result = await getVisitedRegions(user.id); expect(result.regions['FR']).toBeDefined(); diff --git a/server/tests/unit/services/journeyService.test.ts b/server/tests/unit/services/journeyService.test.ts index db3fa985..50d3ea4b 100644 --- a/server/tests/unit/services/journeyService.test.ts +++ b/server/tests/unit/services/journeyService.test.ts @@ -1132,7 +1132,11 @@ describe('setPhotoProvider', () => { setPhotoProvider(photo!.id, 'immich', 'immich-asset-789', user.id); - const updated = testDb.prepare('SELECT * FROM journey_photos WHERE id = ?').get(photo!.id) as any; + const updated = testDb.prepare(` + SELECT jp.*, tkp.provider, tkp.asset_id, tkp.owner_id + FROM journey_photos jp JOIN trek_photos tkp ON tkp.id = jp.photo_id + WHERE jp.id = ? + `).get(photo!.id) as any; expect(updated.provider).toBe('immich'); expect(updated.asset_id).toBe('immich-asset-789'); expect(updated.owner_id).toBe(user.id); @@ -1321,9 +1325,11 @@ describe('Edge cases', () => { ).get(journey.id) as any; expect(photoEntry).toBeDefined(); - const photos = testDb.prepare( - 'SELECT * FROM journey_photos WHERE entry_id = ?' - ).all(photoEntry.id); + const photos = testDb.prepare(` + SELECT jp.*, tkp.asset_id FROM journey_photos jp + JOIN trek_photos tkp ON tkp.id = jp.photo_id + WHERE jp.entry_id = ? + `).all(photoEntry.id); expect(photos.length).toBe(1); expect((photos[0] as any).asset_id).toBe('immich-photo-1'); }); diff --git a/server/tests/unit/services/journeyShareService.test.ts b/server/tests/unit/services/journeyShareService.test.ts index e62c06b7..371e170e 100644 --- a/server/tests/unit/services/journeyShareService.test.ts +++ b/server/tests/unit/services/journeyShareService.test.ts @@ -63,10 +63,17 @@ function insertJourneyPhoto( entryId: number, opts: { filePath?: string; assetId?: string; ownerId?: number } = {} ): number { + const provider = opts.assetId ? 'immich' : 'local'; + const filePath = !opts.assetId ? (opts.filePath ?? '/photos/test.jpg') : null; + const trekResult = testDb.prepare(` + INSERT INTO trek_photos (provider, asset_id, file_path, owner_id, created_at) + VALUES (?, ?, ?, ?, ?) + `).run(provider, opts.assetId ?? null, filePath, opts.ownerId ?? null, Date.now()); + const trekId = trekResult.lastInsertRowid as number; const result = testDb.prepare(` - INSERT INTO journey_photos (entry_id, file_path, caption, sort_order, created_at, asset_id, owner_id) - VALUES (?, ?, NULL, 0, ?, ?, ?) - `).run(entryId, opts.filePath ?? '/photos/test.jpg', Date.now(), opts.assetId ?? null, opts.ownerId ?? null); + INSERT INTO journey_photos (entry_id, photo_id, caption, sort_order, created_at) + VALUES (?, ?, NULL, 0, ?) + `).run(entryId, trekId, Date.now()); return result.lastInsertRowid as number; }