diff --git a/server/test/medium/specs/repositories/asset-job.repository.spec.ts b/server/test/medium/specs/repositories/asset-job.repository.spec.ts new file mode 100644 index 0000000000..6af3aa778f --- /dev/null +++ b/server/test/medium/specs/repositories/asset-job.repository.spec.ts @@ -0,0 +1,118 @@ +import { Kysely } from 'kysely'; +import { AssetFileType } from 'src/enum'; +import { AssetJobRepository } from 'src/repositories/asset-job.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'; + +const consume = async (generator: AsyncIterableIterator) => { + const values: T[] = []; + + for await (const value of generator) { + values.push(value); + } + + return values; +}; + +let defaultDatabase: Kysely; + +const setup = (db?: Kysely) => { + const { ctx } = newMediumService(BaseService, { + database: db || defaultDatabase, + real: [], + mock: [LoggingRepository], + }); + return { ctx, sut: ctx.get(AssetJobRepository) }; +}; + +beforeAll(async () => { + defaultDatabase = await getKyselyDB(); +}); + +describe(AssetJobRepository.name, () => { + describe('streamForThumbnailJob', () => { + it('should work', async () => { + const { sut } = setup(); + const stream = sut.streamForThumbnailJob({ force: false, fullsizeEnabled: false }); + await expect(stream.next()).resolves.toEqual({ done: true, value: undefined }); + }); + + it('should queue an asset with missing thumbnails', async () => { + const { ctx, sut } = setup(); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + await ctx.newJobStatus({ assetId: asset.id, metadataExtractedAt: new Date() }); + + const stream = sut.streamForThumbnailJob({ force: false, fullsizeEnabled: false }); + await expect(consume(stream)).resolves.toEqual([expect.objectContaining({ id: asset.id })]); + }); + + it('should skip assets without missing thumbnails', async () => { + const { ctx, sut } = setup(); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id, thumbhash: Buffer.from('fake-thumbhash-buffer') }); + await ctx.newJobStatus({ assetId: asset.id, metadataExtractedAt: new Date() }); + await ctx.newAssetFile({ assetId: asset.id, type: AssetFileType.Thumbnail, path: 'thumbnail.jpg' }); + await ctx.newAssetFile({ assetId: asset.id, type: AssetFileType.Preview, path: 'preview.jpg' }); + + const stream = sut.streamForThumbnailJob({ force: false, fullsizeEnabled: false }); + await expect(consume(stream)).resolves.not.toEqual( + expect.arrayContaining([expect.objectContaining({ id: asset.id })]), + ); + }); + + it('should queue assets with a missing full size', async () => { + const { ctx, sut } = setup(); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ + ownerId: user.id, + thumbhash: Buffer.from('fake-thumbhash-buffer'), + originalFileName: 'photo.cr2', + }); + await ctx.newJobStatus({ assetId: asset.id, metadataExtractedAt: new Date() }); + await ctx.newAssetFile({ assetId: asset.id, type: AssetFileType.Thumbnail, path: 'thumbnail.jpg' }); + await ctx.newAssetFile({ assetId: asset.id, type: AssetFileType.Preview, path: 'preview.jpg' }); + + const stream = sut.streamForThumbnailJob({ force: false, fullsizeEnabled: true }); + await expect(consume(stream)).resolves.toEqual( + expect.arrayContaining([expect.objectContaining({ id: asset.id })]), + ); + }); + + it('should skip assets with after they have full size previews', async () => { + const { ctx, sut } = setup(); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id, thumbhash: Buffer.from('fake-thumbhash-buffer') }); + await ctx.newJobStatus({ assetId: asset.id, metadataExtractedAt: new Date() }); + await ctx.newAssetFile({ assetId: asset.id, type: AssetFileType.Thumbnail, path: 'thumbnail.jpg' }); + await ctx.newAssetFile({ assetId: asset.id, type: AssetFileType.Preview, path: 'preview.jpg' }); + await ctx.newAssetFile({ assetId: asset.id, type: AssetFileType.FullSize, path: 'fullsize.jpg' }); + + const stream = sut.streamForThumbnailJob({ force: false, fullsizeEnabled: true }); + await expect(consume(stream)).resolves.not.toEqual( + expect.arrayContaining([expect.objectContaining({ id: asset.id })]), + ); + }); + + it('should skip assets with web-compatible originals', async () => { + const { ctx, sut } = setup(); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ + ownerId: user.id, + thumbhash: Buffer.from('fake-thumbhash-buffer'), + originalFileName: 'photo.jpg', + }); + await ctx.newJobStatus({ assetId: asset.id, metadataExtractedAt: new Date() }); + await ctx.newAssetFile({ assetId: asset.id, type: AssetFileType.Thumbnail, path: 'thumbnail.jpg' }); + await ctx.newAssetFile({ assetId: asset.id, type: AssetFileType.Preview, path: 'preview.jpg' }); + + const stream = sut.streamForThumbnailJob({ force: false, fullsizeEnabled: true }); + await expect(consume(stream)).resolves.not.toEqual( + expect.arrayContaining([expect.objectContaining({ id: asset.id })]), + ); + }); + }); +});