import { BadRequestException } from '@nestjs/common'; import { Kysely } from 'kysely'; import { AssetVisibility } from 'src/enum'; import { AccessRepository } from 'src/repositories/access.repository'; import { AssetRepository } from 'src/repositories/asset.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { DB } from 'src/schema'; import { TimelineService } from 'src/services/timeline.service'; import { newMediumService } from 'test/medium.factory'; import { factory } from 'test/small.factory'; import { getKyselyDB } from 'test/utils'; let defaultDatabase: Kysely; const setup = (db?: Kysely) => { return newMediumService(TimelineService, { database: db || defaultDatabase, real: [AssetRepository, AccessRepository], mock: [LoggingRepository], }); }; beforeAll(async () => { defaultDatabase = await getKyselyDB(); }); describe(TimelineService.name, () => { describe('getTimeBuckets', () => { it('should get time buckets by month', async () => { const { sut, ctx } = setup(); const { user } = await ctx.newUser(); const auth = factory.auth({ user }); const dates = [new Date('1970-01-01'), new Date('1970-02-10'), new Date('1970-02-11'), new Date('1970-02-11')]; for (const localDateTime of dates) { const { asset } = await ctx.newAsset({ ownerId: user.id, localDateTime }); await ctx.newExif({ assetId: asset.id, make: 'Canon' }); } const response = sut.getTimeBuckets(auth, {}); await expect(response).resolves.toEqual([ { count: 3, timeBucket: '1970-02-01' }, { count: 1, timeBucket: '1970-01-01' }, ]); }); it('should return error if time bucket is requested with partners asset and archived', async () => { const { sut } = setup(); const auth = factory.auth(); const response1 = sut.getTimeBuckets(auth, { withPartners: true, visibility: AssetVisibility.ARCHIVE }); await expect(response1).rejects.toBeInstanceOf(BadRequestException); await expect(response1).rejects.toThrow( 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', ); const response2 = sut.getTimeBuckets(auth, { withPartners: true }); await expect(response2).rejects.toBeInstanceOf(BadRequestException); await expect(response2).rejects.toThrow( 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', ); }); it('should return error if time bucket is requested with partners asset and favorite', async () => { const { sut } = setup(); const auth = factory.auth(); const response1 = sut.getTimeBuckets(auth, { withPartners: true, isFavorite: false }); await expect(response1).rejects.toBeInstanceOf(BadRequestException); await expect(response1).rejects.toThrow( 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', ); const response2 = sut.getTimeBuckets(auth, { withPartners: true, isFavorite: true }); await expect(response2).rejects.toBeInstanceOf(BadRequestException); await expect(response2).rejects.toThrow( 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', ); }); it('should return error if time bucket is requested with partners asset and trash', async () => { const { sut } = setup(); const auth = factory.auth(); const response = sut.getTimeBuckets(auth, { withPartners: true, isTrashed: true }); await expect(response).rejects.toBeInstanceOf(BadRequestException); await expect(response).rejects.toThrow( 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', ); }); it('should not allow access for unrelated shared links', async () => { const { sut } = setup(); const auth = factory.auth({ sharedLink: {} }); const response = sut.getTimeBuckets(auth, {}); await expect(response).rejects.toBeInstanceOf(BadRequestException); await expect(response).rejects.toThrow('Not found or no timeline.read access'); }); }); describe('getTimeBucket', () => { it('should return time bucket', async () => { const { sut, ctx } = setup(); const { user } = await ctx.newUser(); const { asset } = await ctx.newAsset({ ownerId: user.id, localDateTime: new Date('1970-02-12'), deletedAt: new Date(), }); await ctx.newExif({ assetId: asset.id, make: 'Canon' }); const auth = factory.auth({ user: { id: user.id } }); const rawResponse = await sut.getTimeBucket(auth, { timeBucket: '1970-02-01', isTrashed: true }); const response = JSON.parse(rawResponse); expect(response).toEqual(expect.objectContaining({ isTrashed: [true] })); }); it('should handle a bucket without any assets', async () => { const { sut } = setup(); const rawResponse = await sut.getTimeBucket(factory.auth(), { timeBucket: '1970-02-01' }); const response = JSON.parse(rawResponse); expect(response).toEqual({ city: [], country: [], duration: [], id: [], visibility: [], isFavorite: [], isImage: [], isTrashed: [], livePhotoVideoId: [], fileCreatedAt: [], localOffsetHours: [], ownerId: [], projectionType: [], ratio: [], status: [], thumbhash: [], }); }); it('should handle 5 digit years', async () => { const { sut } = setup(); const rawResponse = await sut.getTimeBucket(factory.auth(), { timeBucket: '012345-01-01' }); const response = JSON.parse(rawResponse); expect(response).toEqual(expect.objectContaining({ id: [] })); }); it('should return time bucket in trash', async () => { const { sut, ctx } = setup(); const { user } = await ctx.newUser(); const { asset } = await ctx.newAsset({ ownerId: user.id, localDateTime: new Date('1970-02-12'), deletedAt: new Date(), }); await ctx.newExif({ assetId: asset.id, make: 'Canon' }); const auth = factory.auth({ user: { id: user.id } }); const rawResponse = await sut.getTimeBucket(auth, { timeBucket: '1970-02-01', isTrashed: true }); const response = JSON.parse(rawResponse); expect(response).toEqual(expect.objectContaining({ isTrashed: [true] })); }); }); });