diff --git a/server/test/medium/specs/repositories/asset.repository.spec.ts b/server/test/medium/specs/repositories/asset.repository.spec.ts new file mode 100644 index 0000000000..a7af66f872 --- /dev/null +++ b/server/test/medium/specs/repositories/asset.repository.spec.ts @@ -0,0 +1,90 @@ +import { Kysely } from 'kysely'; +import { AssetRepository } from 'src/repositories/asset.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { DB } from 'src/schema'; +import { BaseService } from 'src/services/base.service'; +import { newMediumService } from 'test/medium.factory'; +import { getKyselyDB } from 'test/utils'; + +let defaultDatabase: Kysely; + +const setup = (db?: Kysely) => { + const { ctx } = newMediumService(BaseService, { + database: db || defaultDatabase, + real: [], + mock: [LoggingRepository], + }); + return { ctx, sut: ctx.get(AssetRepository) }; +}; + +beforeAll(async () => { + defaultDatabase = await getKyselyDB(); +}); + +describe(AssetRepository.name, () => { + describe('upsertExif', () => { + it('should append to locked columns', async () => { + const { ctx, sut } = setup(); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + await ctx.newExif({ + assetId: asset.id, + dateTimeOriginal: '2023-11-19T18:11:00', + lockedProperties: ['dateTimeOriginal'], + }); + + await expect( + ctx.database + .selectFrom('asset_exif') + .select('lockedProperties') + .where('assetId', '=', asset.id) + .executeTakeFirstOrThrow(), + ).resolves.toEqual({ lockedProperties: ['dateTimeOriginal'] }); + + await sut.upsertExif( + { assetId: asset.id, lockedProperties: ['description'] }, + { lockedPropertiesBehavior: 'append' }, + ); + + await expect( + ctx.database + .selectFrom('asset_exif') + .select('lockedProperties') + .where('assetId', '=', asset.id) + .executeTakeFirstOrThrow(), + ).resolves.toEqual({ lockedProperties: ['description', 'dateTimeOriginal'] }); + }); + + it('should deduplicate locked columns', async () => { + const { ctx, sut } = setup(); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + await ctx.newExif({ + assetId: asset.id, + dateTimeOriginal: '2023-11-19T18:11:00', + lockedProperties: ['dateTimeOriginal', 'description'], + }); + + await expect( + ctx.database + .selectFrom('asset_exif') + .select('lockedProperties') + .where('assetId', '=', asset.id) + .executeTakeFirstOrThrow(), + ).resolves.toEqual({ lockedProperties: ['dateTimeOriginal', 'description'] }); + + await sut.upsertExif( + { assetId: asset.id, lockedProperties: ['description'] }, + { lockedPropertiesBehavior: 'append' }, + ); + + await expect( + ctx.database + .selectFrom('asset_exif') + .select('lockedProperties') + .where('assetId', '=', asset.id) + .executeTakeFirstOrThrow(), + ).resolves.toEqual({ lockedProperties: ['description', 'dateTimeOriginal'] }); + }); + }); +}); diff --git a/server/test/medium/specs/services/asset.service.spec.ts b/server/test/medium/specs/services/asset.service.spec.ts index a7c6470599..661c4f5cdb 100644 --- a/server/test/medium/specs/services/asset.service.spec.ts +++ b/server/test/medium/specs/services/asset.service.spec.ts @@ -270,13 +270,13 @@ describe(AssetService.name, () => { }); describe('update', () => { - it('should update dateTimeOriginal', async () => { + it('should automatically lock lockable columns', async () => { const { sut, ctx } = setup(); ctx.getMock(JobRepository).queue.mockResolvedValue(); const { user } = await ctx.newUser(); const auth = factory.auth({ user }); const { asset } = await ctx.newAsset({ ownerId: user.id }); - await ctx.newExif({ assetId: asset.id, description: 'test' }); + await ctx.newExif({ assetId: asset.id, dateTimeOriginal: '2023-11-19T18:11:00' }); await expect( ctx.database @@ -285,7 +285,14 @@ describe(AssetService.name, () => { .where('assetId', '=', asset.id) .executeTakeFirstOrThrow(), ).resolves.toEqual({ lockedProperties: null }); - await sut.update(auth, asset.id, { dateTimeOriginal: '2023-11-19T18:11:00' }); + + await sut.update(auth, asset.id, { + latitude: 42, + longitude: 42, + rating: 3, + description: 'foo', + dateTimeOriginal: '2023-11-19T18:11:00+01:00', + }); await expect( ctx.database @@ -293,7 +300,21 @@ describe(AssetService.name, () => { .select('lockedProperties') .where('assetId', '=', asset.id) .executeTakeFirstOrThrow(), - ).resolves.toEqual({ lockedProperties: ['dateTimeOriginal'] }); + ).resolves.toEqual({ + lockedProperties: ['timeZone', 'rating', 'description', 'latitude', 'longitude', 'dateTimeOriginal'], + }); + }); + + it('should update dateTimeOriginal', async () => { + const { sut, ctx } = setup(); + ctx.getMock(JobRepository).queue.mockResolvedValue(); + const { user } = await ctx.newUser(); + const auth = factory.auth({ user }); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + await ctx.newExif({ assetId: asset.id, description: 'test' }); + + await sut.update(auth, asset.id, { dateTimeOriginal: '2023-11-19T18:11:00' }); + await expect(ctx.get(AssetRepository).getById(asset.id, { exifInfo: true })).resolves.toEqual( expect.objectContaining({ exifInfo: expect.objectContaining({ dateTimeOriginal: '2023-11-19T18:11:00+00:00', timeZone: null }), @@ -309,22 +330,8 @@ describe(AssetService.name, () => { const { asset } = await ctx.newAsset({ ownerId: user.id }); await ctx.newExif({ assetId: asset.id, description: 'test' }); - await expect( - ctx.database - .selectFrom('asset_exif') - .select('lockedProperties') - .where('assetId', '=', asset.id) - .executeTakeFirstOrThrow(), - ).resolves.toEqual({ lockedProperties: null }); await sut.update(auth, asset.id, { dateTimeOriginal: '2023-11-19T18:11:00.000-07:00' }); - await expect( - ctx.database - .selectFrom('asset_exif') - .select('lockedProperties') - .where('assetId', '=', asset.id) - .executeTakeFirstOrThrow(), - ).resolves.toEqual({ lockedProperties: ['timeZone', 'dateTimeOriginal'] }); await expect(ctx.get(AssetRepository).getById(asset.id, { exifInfo: true })).resolves.toEqual( expect.objectContaining({ exifInfo: expect.objectContaining({ dateTimeOriginal: '2023-11-20T01:11:00+00:00', timeZone: 'UTC-7' }), @@ -334,6 +341,42 @@ describe(AssetService.name, () => { }); describe('updateAll', () => { + it('should automatically lock lockable columns', async () => { + const { sut, ctx } = setup(); + ctx.getMock(JobRepository).queueAll.mockResolvedValue(); + const { user } = await ctx.newUser(); + const auth = factory.auth({ user }); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + await ctx.newExif({ assetId: asset.id, dateTimeOriginal: '2023-11-19T18:11:00' }); + + await expect( + ctx.database + .selectFrom('asset_exif') + .select('lockedProperties') + .where('assetId', '=', asset.id) + .executeTakeFirstOrThrow(), + ).resolves.toEqual({ lockedProperties: null }); + + await sut.updateAll(auth, { + ids: [asset.id], + latitude: 42, + description: 'foo', + longitude: 42, + rating: 3, + dateTimeOriginal: '2023-11-19T18:11:00+01:00', + }); + + await expect( + ctx.database + .selectFrom('asset_exif') + .select('lockedProperties') + .where('assetId', '=', asset.id) + .executeTakeFirstOrThrow(), + ).resolves.toEqual({ + lockedProperties: ['timeZone', 'rating', 'description', 'latitude', 'longitude', 'dateTimeOriginal'], + }); + }); + it('should relatively update assets', async () => { const { sut, ctx } = setup(); ctx.getMock(JobRepository).queueAll.mockResolvedValue(); @@ -344,13 +387,6 @@ describe(AssetService.name, () => { await sut.updateAll(auth, { ids: [asset.id], dateTimeRelative: -11 }); - await expect( - ctx.database - .selectFrom('asset_exif') - .select('lockedProperties') - .where('assetId', '=', asset.id) - .executeTakeFirstOrThrow(), - ).resolves.toEqual({ lockedProperties: ['timeZone', 'dateTimeOriginal'] }); await expect(ctx.get(AssetRepository).getById(asset.id, { exifInfo: true })).resolves.toEqual( expect.objectContaining({ exifInfo: expect.objectContaining({ @@ -359,66 +395,39 @@ describe(AssetService.name, () => { }), ); }); - }); - it('should update dateTimeOriginal', async () => { - const { sut, ctx } = setup(); - ctx.getMock(JobRepository).queueAll.mockResolvedValue(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - const { asset } = await ctx.newAsset({ ownerId: user.id }); - await ctx.newExif({ assetId: asset.id, description: 'test' }); + it('should update dateTimeOriginal', async () => { + const { sut, ctx } = setup(); + ctx.getMock(JobRepository).queueAll.mockResolvedValue(); + const { user } = await ctx.newUser(); + const auth = factory.auth({ user }); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + await ctx.newExif({ assetId: asset.id, description: 'test' }); - await expect( - ctx.database - .selectFrom('asset_exif') - .select('lockedProperties') - .where('assetId', '=', asset.id) - .executeTakeFirstOrThrow(), - ).resolves.toEqual({ lockedProperties: null }); - await sut.updateAll(auth, { ids: [asset.id], dateTimeOriginal: '2023-11-19T18:11:00' }); + await sut.updateAll(auth, { ids: [asset.id], dateTimeOriginal: '2023-11-19T18:11:00' }); - await expect( - ctx.database - .selectFrom('asset_exif') - .select('lockedProperties') - .where('assetId', '=', asset.id) - .executeTakeFirstOrThrow(), - ).resolves.toEqual({ lockedProperties: ['dateTimeOriginal'] }); - await expect(ctx.get(AssetRepository).getById(asset.id, { exifInfo: true })).resolves.toEqual( - expect.objectContaining({ - exifInfo: expect.objectContaining({ dateTimeOriginal: '2023-11-19T18:11:00+00:00', timeZone: null }), - }), - ); - }); + await expect(ctx.get(AssetRepository).getById(asset.id, { exifInfo: true })).resolves.toEqual( + expect.objectContaining({ + exifInfo: expect.objectContaining({ dateTimeOriginal: '2023-11-19T18:11:00+00:00', timeZone: null }), + }), + ); + }); - it('should update dateTimeOriginal with time zone', async () => { - const { sut, ctx } = setup(); - ctx.getMock(JobRepository).queueAll.mockResolvedValue(); - const { user } = await ctx.newUser(); - const auth = factory.auth({ user }); - const { asset } = await ctx.newAsset({ ownerId: user.id }); - await ctx.newExif({ assetId: asset.id, description: 'test' }); + it('should update dateTimeOriginal with time zone', async () => { + const { sut, ctx } = setup(); + ctx.getMock(JobRepository).queueAll.mockResolvedValue(); + const { user } = await ctx.newUser(); + const auth = factory.auth({ user }); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + await ctx.newExif({ assetId: asset.id, description: 'test' }); - await expect( - ctx.database - .selectFrom('asset_exif') - .select('lockedProperties') - .where('assetId', '=', asset.id) - .executeTakeFirstOrThrow(), - ).resolves.toEqual({ lockedProperties: null }); - await sut.updateAll(auth, { ids: [asset.id], dateTimeOriginal: '2023-11-19T18:11:00.000-07:00' }); - await expect( - ctx.database - .selectFrom('asset_exif') - .select('lockedProperties') - .where('assetId', '=', asset.id) - .executeTakeFirstOrThrow(), - ).resolves.toEqual({ lockedProperties: ['timeZone', 'dateTimeOriginal'] }); - await expect(ctx.get(AssetRepository).getById(asset.id, { exifInfo: true })).resolves.toEqual( - expect.objectContaining({ - exifInfo: expect.objectContaining({ dateTimeOriginal: '2023-11-20T01:11:00+00:00', timeZone: 'UTC-7' }), - }), - ); + await sut.updateAll(auth, { ids: [asset.id], dateTimeOriginal: '2023-11-19T18:11:00.000-07:00' }); + + await expect(ctx.get(AssetRepository).getById(asset.id, { exifInfo: true })).resolves.toEqual( + expect.objectContaining({ + exifInfo: expect.objectContaining({ dateTimeOriginal: '2023-11-20T01:11:00+00:00', timeZone: 'UTC-7' }), + }), + ); + }); }); });