diff --git a/server/src/services/tag.service.spec.ts b/server/src/services/tag.service.spec.ts index ff706552a9..a80e6d508b 100644 --- a/server/src/services/tag.service.spec.ts +++ b/server/src/services/tag.service.spec.ts @@ -206,15 +206,15 @@ describe(TagService.name, () => { count: 6, }); expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - { assetId: 'asset-1', tags: ['tag-1', 'tag-2'] }, + { assetId: 'asset-1', lockedProperties: ['tags'], tags: ['tag-1', 'tag-2'] }, { lockedPropertiesBehavior: 'append' }, ); expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - { assetId: 'asset-2', tags: ['tag-1', 'tag-2'] }, + { assetId: 'asset-2', lockedProperties: ['tags'], tags: ['tag-1', 'tag-2'] }, { lockedPropertiesBehavior: 'append' }, ); expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - { assetId: 'asset-3', tags: ['tag-1', 'tag-2'] }, + { assetId: 'asset-3', lockedProperties: ['tags'], tags: ['tag-1', 'tag-2'] }, { lockedPropertiesBehavior: 'append' }, ); expect(mocks.tag.upsertAssetIds).toHaveBeenCalledWith([ @@ -255,11 +255,11 @@ describe(TagService.name, () => { ]); expect(mocks.asset.upsertExif).not.toHaveBeenCalledWith( - { assetId: 'asset-1', tags: ['tag-1'] }, + { assetId: 'asset-1', lockedProperties: ['tags'], tags: ['tag-1'] }, { lockedPropertiesBehavior: 'append' }, ); expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - { assetId: 'asset-2', tags: ['tag-1'] }, + { assetId: 'asset-2', lockedProperties: ['tags'], tags: ['tag-1'] }, { lockedPropertiesBehavior: 'append' }, ); expect(mocks.tag.getAssetIds).toHaveBeenCalledWith('tag-1', ['asset-1', 'asset-2']); diff --git a/server/src/services/tag.service.ts b/server/src/services/tag.service.ts index 3b3000f759..20303421c1 100644 --- a/server/src/services/tag.service.ts +++ b/server/src/services/tag.service.ts @@ -16,6 +16,7 @@ import { JobName, JobStatus, Permission, QueueName } from 'src/enum'; import { TagAssetTable } from 'src/schema/tables/tag-asset.table'; import { BaseService } from 'src/services/base.service'; import { addAssets, removeAssets } from 'src/utils/asset.util'; +import { updateLockedColumns } from 'src/utils/database'; import { upsertTags } from 'src/utils/tag'; @Injectable() @@ -152,7 +153,7 @@ export class TagService extends BaseService { private async updateTags(assetId: string) { const asset = await this.assetRepository.getById(assetId, { tags: true }); await this.assetRepository.upsertExif( - { assetId, tags: asset?.tags?.map(({ value }) => value) ?? [] }, + updateLockedColumns({ assetId, tags: asset?.tags?.map(({ value }) => value) ?? [] }), { lockedPropertiesBehavior: 'append' }, ); } diff --git a/server/test/medium/specs/services/tag.service.spec.ts b/server/test/medium/specs/services/tag.service.spec.ts index 2ec498e56d..989e4f535f 100644 --- a/server/test/medium/specs/services/tag.service.spec.ts +++ b/server/test/medium/specs/services/tag.service.spec.ts @@ -1,12 +1,15 @@ import { Kysely } from 'kysely'; import { JobStatus } from 'src/enum'; import { AccessRepository } from 'src/repositories/access.repository'; +import { AssetRepository } from 'src/repositories/asset.repository'; +import { EventRepository } from 'src/repositories/event.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { TagRepository } from 'src/repositories/tag.repository'; import { DB } from 'src/schema'; import { TagService } from 'src/services/tag.service'; import { upsertTags } from 'src/utils/tag'; import { newMediumService } from 'test/medium.factory'; +import { factory } from 'test/small.factory'; import { getKyselyDB } from 'test/utils'; let defaultDatabase: Kysely; @@ -14,8 +17,8 @@ let defaultDatabase: Kysely; const setup = (db?: Kysely) => { return newMediumService(TagService, { database: db || defaultDatabase, - real: [TagRepository, AccessRepository], - mock: [LoggingRepository], + real: [AssetRepository, TagRepository, AccessRepository], + mock: [EventRepository, LoggingRepository], }); }; @@ -24,6 +27,32 @@ beforeAll(async () => { }); describe(TagService.name, () => { + describe('addAssets', () => { + it('should lock exif column', async () => { + const { sut, ctx } = setup(); + ctx.getMock(EventRepository).emit.mockResolvedValue(); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + const [tag] = await upsertTags(ctx.get(TagRepository), { userId: user.id, tags: ['tag-1'] }); + const authDto = factory.auth({ user }); + + await sut.addAssets(authDto, tag.id, { ids: [asset.id] }); + await expect( + ctx.database + .selectFrom('asset_exif') + .select(['lockedProperties', 'tags']) + .where('assetId', '=', asset.id) + .executeTakeFirstOrThrow(), + ).resolves.toEqual({ + lockedProperties: ['tags'], + tags: ['tag-1'], + }); + await expect(ctx.get(TagRepository).getByValue(user.id, 'tag-1')).resolves.toEqual( + expect.objectContaining({ id: tag.id }), + ); + await expect(ctx.get(TagRepository).getAssetIds(tag.id, [asset.id])).resolves.toContain(asset.id); + }); + }); describe('deleteEmptyTags', () => { it('single tag exists, not connected to any assets, and is deleted', async () => { const { sut, ctx } = setup();