From 6127fd4c5c377559bda5e679022b6e8173ed7f05 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Tue, 14 Nov 2023 20:08:22 -0500 Subject: [PATCH] chore(server): improve e2e test speed (#5026) * feat: improve shared link (41s to 8s) * feat: improve activity (18s to 8s) * feat: improve partner (20s to 10s) * feat: improve server-info (10s to 6s) * feat: improve system-config * fix: e2e * chore: linting --- server/src/domain/partner/index.ts | 1 + server/test/api/asset-api.ts | 18 ++ server/test/api/index.ts | 2 + server/test/api/partner-api.ts | 10 + server/test/e2e/activity.e2e-spec.ts | 46 ++-- server/test/e2e/asset.e2e-spec.ts | 31 ++- server/test/e2e/oauth.e2e-spec.ts | 4 - server/test/e2e/partner.e2e-spec.ts | 72 +++--- server/test/e2e/server-info.e2e-spec.ts | 43 ++-- server/test/e2e/shared-link.e2e-spec.ts | 275 ++++++++++++---------- server/test/e2e/system-config.e2e-spec.ts | 27 ++- server/test/test-utils.ts | 4 +- 12 files changed, 307 insertions(+), 226 deletions(-) create mode 100644 server/test/api/partner-api.ts diff --git a/server/src/domain/partner/index.ts b/server/src/domain/partner/index.ts index f78ecb1cb9..b25925e896 100644 --- a/server/src/domain/partner/index.ts +++ b/server/src/domain/partner/index.ts @@ -1 +1,2 @@ +export * from './partner.dto'; export * from './partner.service'; diff --git a/server/test/api/asset-api.ts b/server/test/api/asset-api.ts index 0c83b8abbd..1ff44d5bab 100644 --- a/server/test/api/asset-api.ts +++ b/server/test/api/asset-api.ts @@ -7,6 +7,24 @@ import request from 'supertest'; type UploadDto = Partial & { content?: Buffer }; export const assetApi = { + create: async ( + server: any, + accessToken: string, + dto: Omit, + ): Promise => { + const { status, body } = await request(server) + .post(`/asset/upload`) + .field('deviceAssetId', dto.deviceAssetId) + .field('deviceId', dto.deviceId) + .field('fileCreatedAt', dto.fileCreatedAt.toISOString()) + .field('fileModifiedAt', dto.fileModifiedAt.toISOString()) + .attach('assetData', randomBytes(32), 'example.jpg') + .set('Authorization', `Bearer ${accessToken}`); + + expect([200, 201].includes(status)).toBe(true); + + return body as AssetResponseDto; + }, get: async (server: any, accessToken: string, id: string): Promise => { const { body, status } = await request(server) .get(`/asset/assetById/${id}`) diff --git a/server/test/api/index.ts b/server/test/api/index.ts index d9321df275..55cf2526d7 100644 --- a/server/test/api/index.ts +++ b/server/test/api/index.ts @@ -3,6 +3,7 @@ import { albumApi } from './album-api'; import { assetApi } from './asset-api'; import { authApi } from './auth-api'; import { libraryApi } from './library-api'; +import { partnerApi } from './partner-api'; import { sharedLinkApi } from './shared-link-api'; import { userApi } from './user-api'; @@ -14,4 +15,5 @@ export const api = { sharedLinkApi, albumApi, userApi, + partnerApi, }; diff --git a/server/test/api/partner-api.ts b/server/test/api/partner-api.ts new file mode 100644 index 0000000000..97a9558c5f --- /dev/null +++ b/server/test/api/partner-api.ts @@ -0,0 +1,10 @@ +import { PartnerResponseDto } from '@app/domain'; +import request from 'supertest'; + +export const partnerApi = { + create: async (server: any, accessToken: string, id: string) => { + const { status, body } = await request(server).post(`/partner/${id}`).set('Authorization', `Bearer ${accessToken}`); + expect(status).toBe(201); + return body as PartnerResponseDto; + }, +}; diff --git a/server/test/e2e/activity.e2e-spec.ts b/server/test/e2e/activity.e2e-spec.ts index 34799b1634..726dc64ed9 100644 --- a/server/test/e2e/activity.e2e-spec.ts +++ b/server/test/e2e/activity.e2e-spec.ts @@ -1,6 +1,7 @@ import { AlbumResponseDto, LoginResponseDto, ReactionType } from '@app/domain'; import { ActivityController } from '@app/immich'; import { AssetFileUploadResponseDto } from '@app/immich/api-v1/asset/response-dto/asset-file-upload-response.dto'; +import { ActivityEntity } from '@app/infra/entities'; import { api } from '@test/api'; import { db } from '@test/db'; import { errorStub, uuidStub } from '@test/fixtures'; @@ -12,9 +13,24 @@ describe(`${ActivityController.name} (e2e)`, () => { let admin: LoginResponseDto; let asset: AssetFileUploadResponseDto; let album: AlbumResponseDto; + let nonOwner: LoginResponseDto; beforeAll(async () => { [server] = await testApp.create(); + await db.reset(); + await api.authApi.adminSignUp(server); + admin = await api.authApi.adminLogin(server); + asset = await api.assetApi.upload(server, admin.accessToken, 'example'); + + const credentials = { email: 'user1@immich.app', password: 'Password123' }; + await api.userApi.create(server, admin.accessToken, { ...credentials, name: 'User 1' }); + nonOwner = await api.authApi.login(server, credentials); + + album = await api.albumApi.create(server, admin.accessToken, { + albumName: 'Album 1', + assetIds: [asset.id], + sharedWithUserIds: [nonOwner.userId], + }); }); afterAll(async () => { @@ -22,11 +38,7 @@ describe(`${ActivityController.name} (e2e)`, () => { }); beforeEach(async () => { - await db.reset(); - await api.authApi.adminSignUp(server); - admin = await api.authApi.adminLogin(server); - asset = await api.assetApi.upload(server, admin.accessToken, 'example'); - album = await api.albumApi.create(server, admin.accessToken, { albumName: 'Album 1', assetIds: [asset.id] }); + await testApp.reset({ entities: [ActivityEntity] }); }); describe('GET /activity', () => { @@ -347,13 +359,6 @@ describe(`${ActivityController.name} (e2e)`, () => { }); it('should let the owner remove a comment by another user', async () => { - const { id: userId } = await api.userApi.create(server, admin.accessToken, { - email: 'user1@immich.app', - password: 'Password123', - name: 'User 1', - }); - await api.albumApi.addUsers(server, admin.accessToken, album.id, { sharedUserIds: [userId] }); - const nonOwner = await api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' }); const reaction = await api.activityApi.create(server, nonOwner.accessToken, { albumId: album.id, type: ReactionType.COMMENT, @@ -363,17 +368,11 @@ describe(`${ActivityController.name} (e2e)`, () => { const { status } = await request(server) .delete(`/activity/${reaction.id}`) .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toEqual(204); }); it('should not let a user remove a comment by another user', async () => { - const { id: userId } = await api.userApi.create(server, admin.accessToken, { - email: 'user1@immich.app', - password: 'Password123', - name: 'User 1', - }); - await api.albumApi.addUsers(server, admin.accessToken, album.id, { sharedUserIds: [userId] }); - const nonOwner = await api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' }); const reaction = await api.activityApi.create(server, admin.accessToken, { albumId: album.id, type: ReactionType.COMMENT, @@ -383,18 +382,12 @@ describe(`${ActivityController.name} (e2e)`, () => { const { status, body } = await request(server) .delete(`/activity/${reaction.id}`) .set('Authorization', `Bearer ${nonOwner.accessToken}`); + expect(status).toBe(400); expect(body).toEqual(errorStub.badRequest('Not found or no activity.delete access')); }); it('should let a non-owner remove their own comment', async () => { - const { id: userId } = await api.userApi.create(server, admin.accessToken, { - email: 'user1@immich.app', - password: 'Password123', - name: 'User 1', - }); - await api.albumApi.addUsers(server, admin.accessToken, album.id, { sharedUserIds: [userId] }); - const nonOwner = await api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' }); const reaction = await api.activityApi.create(server, nonOwner.accessToken, { albumId: album.id, type: ReactionType.COMMENT, @@ -404,6 +397,7 @@ describe(`${ActivityController.name} (e2e)`, () => { const { status } = await request(server) .delete(`/activity/${reaction.id}`) .set('Authorization', `Bearer ${nonOwner.accessToken}`); + expect(status).toBe(204); }); }); diff --git a/server/test/e2e/asset.e2e-spec.ts b/server/test/e2e/asset.e2e-spec.ts index 197f40d54d..8fe56f2bc8 100644 --- a/server/test/e2e/asset.e2e-spec.ts +++ b/server/test/e2e/asset.e2e-spec.ts @@ -15,7 +15,7 @@ import { AssetRepository } from '@app/infra/repositories'; import { INestApplication } from '@nestjs/common'; import { api } from '@test/api'; import { errorStub, uuidStub } from '@test/fixtures'; -import { db, testApp } from '@test/test-utils'; +import { testApp } from '@test/test-utils'; import { randomBytes } from 'crypto'; import { DateTime } from 'luxon'; import request from 'supertest'; @@ -98,7 +98,7 @@ describe(`${AssetController.name} (e2e)`, () => { [server, app] = await testApp.create(); assetRepository = app.get(IAssetRepository); - await db.reset(); + await testApp.reset(); await api.authApi.adminSignUp(server); const admin = await api.authApi.adminLogin(server); @@ -122,7 +122,32 @@ describe(`${AssetController.name} (e2e)`, () => { }); beforeEach(async () => { - await db.reset({ entities: [AssetEntity] }); + await testApp.reset({ entities: [AssetEntity] }); + + [asset1, asset2, asset3, asset4, asset5] = await Promise.all([ + createAsset(user1, new Date('1970-01-01')), + createAsset(user1, new Date('1970-02-10')), + createAsset(user1, new Date('1970-02-11'), { + isFavorite: true, + isArchived: true, + isExternal: true, + isReadOnly: true, + type: AssetType.VIDEO, + fileCreatedAt: yesterday.toJSDate(), + fileModifiedAt: yesterday.toJSDate(), + createdAt: yesterday.toJSDate(), + updatedAt: yesterday.toJSDate(), + localDateTime: yesterday.toJSDate(), + }), + createAsset(user2, new Date('1970-01-01')), + createAsset(user1, new Date('1970-01-01'), { + deletedAt: yesterday.toJSDate(), + }), + ]); + }); + + beforeEach(async () => { + await testApp.reset({ entities: [AssetEntity] }); [asset1, asset2, asset3, asset4, asset5] = await Promise.all([ createAsset(user1, new Date('1970-01-01')), diff --git a/server/test/e2e/oauth.e2e-spec.ts b/server/test/e2e/oauth.e2e-spec.ts index 879d538152..d48cefdaa2 100644 --- a/server/test/e2e/oauth.e2e-spec.ts +++ b/server/test/e2e/oauth.e2e-spec.ts @@ -22,10 +22,6 @@ describe(`${OAuthController.name} (e2e)`, () => { }); describe('POST /oauth/authorize', () => { - beforeEach(async () => { - await db.reset(); - }); - it(`should throw an error if a redirect uri is not provided`, async () => { const { status, body } = await request(server).post('/oauth/authorize').send({}); expect(status).toBe(400); diff --git a/server/test/e2e/partner.e2e-spec.ts b/server/test/e2e/partner.e2e-spec.ts index 3eb48dd9e2..f4b69f1158 100644 --- a/server/test/e2e/partner.e2e-spec.ts +++ b/server/test/e2e/partner.e2e-spec.ts @@ -1,6 +1,5 @@ -import { IPartnerRepository, LoginResponseDto, PartnerDirection } from '@app/domain'; +import { LoginResponseDto, PartnerDirection } from '@app/domain'; import { PartnerController } from '@app/immich'; -import { INestApplication } from '@nestjs/common'; import { api } from '@test/api'; import { db } from '@test/db'; import { errorStub } from '@test/fixtures'; @@ -19,41 +18,47 @@ const user2Dto = { name: 'User 2', }; +const user3Dto = { + email: 'user3@immich.app', + password: 'Password123', + name: 'User 3', +}; + describe(`${PartnerController.name} (e2e)`, () => { - let app: INestApplication; let server: any; - let loginResponse: LoginResponseDto; - let accessToken: string; - let repository: IPartnerRepository; let user1: LoginResponseDto; let user2: LoginResponseDto; + let user3: LoginResponseDto; beforeAll(async () => { - [server, app] = await testApp.create(); - repository = app.get(IPartnerRepository); + [server] = await testApp.create(); + + await db.reset(); + await api.authApi.adminSignUp(server); + const admin = await api.authApi.adminLogin(server); + + await Promise.all([ + api.userApi.create(server, admin.accessToken, user1Dto), + api.userApi.create(server, admin.accessToken, user2Dto), + api.userApi.create(server, admin.accessToken, user3Dto), + ]); + + [user1, user2, user3] = await Promise.all([ + api.authApi.login(server, { email: user1Dto.email, password: user1Dto.password }), + api.authApi.login(server, { email: user2Dto.email, password: user2Dto.password }), + api.authApi.login(server, { email: user3Dto.email, password: user3Dto.password }), + ]); + + await Promise.all([ + api.partnerApi.create(server, user1.accessToken, user2.userId), + api.partnerApi.create(server, user2.accessToken, user1.userId), + ]); }); afterAll(async () => { await testApp.teardown(); }); - beforeEach(async () => { - await db.reset(); - await api.authApi.adminSignUp(server); - loginResponse = await api.authApi.adminLogin(server); - accessToken = loginResponse.accessToken; - - await Promise.all([ - api.userApi.create(server, accessToken, user1Dto), - api.userApi.create(server, accessToken, user2Dto), - ]); - - [user1, user2] = await Promise.all([ - api.authApi.login(server, { email: user1Dto.email, password: user1Dto.password }), - api.authApi.login(server, { email: user2Dto.email, password: user2Dto.password }), - ]); - }); - describe('GET /partner', () => { it('should require authentication', async () => { const { status, body } = await request(server).get('/partner'); @@ -63,7 +68,6 @@ describe(`${PartnerController.name} (e2e)`, () => { }); it('should get all partners shared by user', async () => { - await repository.create({ sharedById: user1.userId, sharedWithId: user2.userId }); const { status, body } = await request(server) .get('/partner') .set('Authorization', `Bearer ${user1.accessToken}`) @@ -74,7 +78,6 @@ describe(`${PartnerController.name} (e2e)`, () => { }); it('should get all partners that share with user', async () => { - await repository.create({ sharedById: user2.userId, sharedWithId: user1.userId }); const { status, body } = await request(server) .get('/partner') .set('Authorization', `Bearer ${user1.accessToken}`) @@ -87,7 +90,7 @@ describe(`${PartnerController.name} (e2e)`, () => { describe('POST /partner/:id', () => { it('should require authentication', async () => { - const { status, body } = await request(server).post(`/partner/${user2.userId}`); + const { status, body } = await request(server).post(`/partner/${user3.userId}`); expect(status).toBe(401); expect(body).toEqual(errorStub.unauthorized); @@ -95,15 +98,14 @@ describe(`${PartnerController.name} (e2e)`, () => { it('should share with new partner', async () => { const { status, body } = await request(server) - .post(`/partner/${user2.userId}`) + .post(`/partner/${user3.userId}`) .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(201); - expect(body).toEqual(expect.objectContaining({ id: user2.userId })); + expect(body).toEqual(expect.objectContaining({ id: user3.userId })); }); it('should not share with new partner if already sharing with this partner', async () => { - await repository.create({ sharedById: user1.userId, sharedWithId: user2.userId }); const { status, body } = await request(server) .post(`/partner/${user2.userId}`) .set('Authorization', `Bearer ${user1.accessToken}`); @@ -122,7 +124,6 @@ describe(`${PartnerController.name} (e2e)`, () => { }); it('should update partner', async () => { - await repository.create({ sharedById: user2.userId, sharedWithId: user1.userId }); const { status, body } = await request(server) .put(`/partner/${user2.userId}`) .set('Authorization', `Bearer ${user1.accessToken}`) @@ -135,16 +136,15 @@ describe(`${PartnerController.name} (e2e)`, () => { describe('DELETE /partner/:id', () => { it('should require authentication', async () => { - const { status, body } = await request(server).delete(`/partner/${user2.userId}`); + const { status, body } = await request(server).delete(`/partner/${user3.userId}`); expect(status).toBe(401); expect(body).toEqual(errorStub.unauthorized); }); it('should delete partner', async () => { - await repository.create({ sharedById: user1.userId, sharedWithId: user2.userId }); const { status } = await request(server) - .delete(`/partner/${user2.userId}`) + .delete(`/partner/${user3.userId}`) .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); @@ -152,7 +152,7 @@ describe(`${PartnerController.name} (e2e)`, () => { it('should throw a bad request if partner not found', async () => { const { status, body } = await request(server) - .delete(`/partner/${user2.userId}`) + .delete(`/partner/${user3.userId}`) .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(400); diff --git a/server/test/e2e/server-info.e2e-spec.ts b/server/test/e2e/server-info.e2e-spec.ts index be85e424f7..98fff52229 100644 --- a/server/test/e2e/server-info.e2e-spec.ts +++ b/server/test/e2e/server-info.e2e-spec.ts @@ -6,26 +6,31 @@ import { errorStub } from '@test/fixtures'; import { testApp } from '@test/test-utils'; import request from 'supertest'; +const user1Dto = { + email: 'user1@immich.app', + password: 'Password123', + name: 'User 1', +}; + describe(`${ServerInfoController.name} (e2e)`, () => { let server: any; - let accessToken: string; - let loginResponse: LoginResponseDto; + let admin: LoginResponseDto; + let nonAdmin: LoginResponseDto; beforeAll(async () => { [server] = await testApp.create(); + + await db.reset(); + await api.authApi.adminSignUp(server); + admin = await api.authApi.adminLogin(server); + await api.userApi.create(server, admin.accessToken, user1Dto); + nonAdmin = await api.authApi.login(server, user1Dto); }); afterAll(async () => { await testApp.teardown(); }); - beforeEach(async () => { - await db.reset(); - await api.authApi.adminSignUp(server); - loginResponse = await api.authApi.adminLogin(server); - accessToken = loginResponse.accessToken; - }); - describe('GET /server-info', () => { it('should require authentication', async () => { const { status, body } = await request(server).get('/server-info'); @@ -34,7 +39,9 @@ describe(`${ServerInfoController.name} (e2e)`, () => { }); it('should return the disk information', async () => { - const { status, body } = await request(server).get('/server-info').set('Authorization', `Bearer ${accessToken}`); + const { status, body } = await request(server) + .get('/server-info') + .set('Authorization', `Bearer ${admin.accessToken}`); expect(status).toBe(200); expect(body).toEqual({ diskAvailable: expect.any(String), @@ -110,12 +117,9 @@ describe(`${ServerInfoController.name} (e2e)`, () => { }); it('should only work for admins', async () => { - const loginDto = { email: 'test@immich.app', password: 'Immich123' }; - await api.userApi.create(server, accessToken, { ...loginDto, name: 'test' }); - const { accessToken: userAccessToken } = await api.authApi.login(server, loginDto); const { status, body } = await request(server) .get('/server-info/statistics') - .set('Authorization', `Bearer ${userAccessToken}`); + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); expect(status).toBe(403); expect(body).toEqual(errorStub.forbidden); }); @@ -123,7 +127,7 @@ describe(`${ServerInfoController.name} (e2e)`, () => { it('should return the server stats', async () => { const { status, body } = await request(server) .get('/server-info/statistics') - .set('Authorization', `Bearer ${accessToken}`); + .set('Authorization', `Bearer ${admin.accessToken}`); expect(status).toBe(200); expect(body).toEqual({ photos: 0, @@ -133,7 +137,14 @@ describe(`${ServerInfoController.name} (e2e)`, () => { photos: 0, usage: 0, userName: 'Immich Admin', - userId: loginResponse.userId, + userId: admin.userId, + videos: 0, + }, + { + photos: 0, + usage: 0, + userName: 'User 1', + userId: nonAdmin.userId, videos: 0, }, ], diff --git a/server/test/e2e/shared-link.e2e-spec.ts b/server/test/e2e/shared-link.e2e-spec.ts index ededc01384..368d7408d2 100644 --- a/server/test/e2e/shared-link.e2e-spec.ts +++ b/server/test/e2e/shared-link.e2e-spec.ts @@ -1,11 +1,11 @@ -import { AlbumResponseDto, LoginResponseDto, SharedLinkResponseDto } from '@app/domain'; -import { PartnerController } from '@app/immich'; -import { LibraryType, SharedLinkType } from '@app/infra/entities'; +import { AlbumResponseDto, IAssetRepository, LoginResponseDto, SharedLinkResponseDto } from '@app/domain'; +import { SharedLinkController } from '@app/immich'; +import { SharedLinkType } from '@app/infra/entities'; +import { INestApplication } from '@nestjs/common'; import { api } from '@test/api'; -import { db } from '@test/db'; import { errorStub, uuidStub } from '@test/fixtures'; -import { IMMICH_TEST_ASSET_PATH, IMMICH_TEST_ASSET_TEMP_PATH, restoreTempFolder, testApp } from '@test/test-utils'; -import { cp } from 'fs/promises'; +import { testApp } from '@test/test-utils'; +import { DateTime } from 'luxon'; import request from 'supertest'; const user1Dto = { @@ -14,35 +14,102 @@ const user1Dto = { name: 'User 1', }; -describe(`${PartnerController.name} (e2e)`, () => { +const user2Dto = { + email: 'user2@immich.app', + password: 'Password123', + name: 'User 2', +}; + +const today = new Date(); + +describe(`${SharedLinkController.name} (e2e)`, () => { let server: any; let admin: LoginResponseDto; let user1: LoginResponseDto; + let user2: LoginResponseDto; let album: AlbumResponseDto; + let metadataAlbum: AlbumResponseDto; + let deletedAlbum: AlbumResponseDto; let sharedLink: SharedLinkResponseDto; + let linkWithDeletedAlbum: SharedLinkResponseDto; + let linkWithPassword: SharedLinkResponseDto; + let linkWithMetadata: SharedLinkResponseDto; + let linkWithoutMetadata: SharedLinkResponseDto; + let app: INestApplication; beforeAll(async () => { - [server] = await testApp.create({ jobs: true }); + [server, app] = await testApp.create(); + const assetRepository = app.get(IAssetRepository); + + await testApp.reset(); + await api.authApi.adminSignUp(server); + admin = await api.authApi.adminLogin(server); + + await Promise.all([ + api.userApi.create(server, admin.accessToken, user1Dto), + api.userApi.create(server, admin.accessToken, user2Dto), + ]); + + [user1, user2] = await Promise.all([ + api.authApi.login(server, { email: user1Dto.email, password: user1Dto.password }), + api.authApi.login(server, { email: user2Dto.email, password: user2Dto.password }), + ]); + + const asset = await api.assetApi.create(server, user1.accessToken, { + fileCreatedAt: today, + fileModifiedAt: today, + deviceId: 'test', + deviceAssetId: 'test', + }); + + await assetRepository.upsertExif({ + assetId: asset.id, + longitude: -108.400968333333, + latitude: 39.115, + orientation: '1', + dateTimeOriginal: DateTime.fromISO('2022-01-10T19:15:44.310Z').toJSDate(), + timeZone: 'UTC-4', + state: 'Mesa County, Colorado', + country: 'United States of America', + }); + + [album, deletedAlbum, metadataAlbum] = await Promise.all([ + api.albumApi.create(server, user1.accessToken, { albumName: 'album' }), + api.albumApi.create(server, user2.accessToken, { albumName: 'deleted album' }), + api.albumApi.create(server, user1.accessToken, { albumName: 'metadata album', assetIds: [asset.id] }), + ]); + + [linkWithDeletedAlbum, sharedLink, linkWithPassword, linkWithMetadata, linkWithoutMetadata] = await Promise.all([ + api.sharedLinkApi.create(server, user2.accessToken, { + type: SharedLinkType.ALBUM, + albumId: deletedAlbum.id, + }), + api.sharedLinkApi.create(server, user1.accessToken, { + type: SharedLinkType.ALBUM, + albumId: album.id, + }), + api.sharedLinkApi.create(server, user1.accessToken, { + type: SharedLinkType.ALBUM, + albumId: album.id, + password: 'foo', + }), + api.sharedLinkApi.create(server, user1.accessToken, { + type: SharedLinkType.ALBUM, + albumId: metadataAlbum.id, + showMetadata: true, + }), + api.sharedLinkApi.create(server, user1.accessToken, { + type: SharedLinkType.ALBUM, + albumId: metadataAlbum.id, + showMetadata: false, + }), + ]); + + await api.userApi.delete(server, admin.accessToken, user2.userId); }); afterAll(async () => { await testApp.teardown(); - await restoreTempFolder(); - }); - - beforeEach(async () => { - await db.reset(); - await api.authApi.adminSignUp(server); - admin = await api.authApi.adminLogin(server); - - await api.userApi.create(server, admin.accessToken, user1Dto); - user1 = await api.authApi.login(server, { email: user1Dto.email, password: user1Dto.password }); - - album = await api.albumApi.create(server, user1.accessToken, { albumName: 'shared with link' }); - sharedLink = await api.sharedLinkApi.create(server, user1.accessToken, { - type: SharedLinkType.ALBUM, - albumId: album.id, - }); }); describe('GET /shared-link', () => { @@ -59,7 +126,15 @@ describe(`${PartnerController.name} (e2e)`, () => { .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); - expect(body).toEqual([expect.objectContaining({ album, userId: user1.userId, type: SharedLinkType.ALBUM })]); + expect(body).toHaveLength(4); + expect(body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: sharedLink.id }), + expect.objectContaining({ id: linkWithPassword.id }), + expect.objectContaining({ id: linkWithMetadata.id }), + expect.objectContaining({ id: linkWithoutMetadata.id }), + ]), + ); }); it('should not get shared links created by other users', async () => { @@ -85,7 +160,13 @@ describe(`${PartnerController.name} (e2e)`, () => { const { status, body } = await request(server).get('/shared-link/me').query({ key: sharedLink.key }); expect(status).toBe(200); - expect(body).toEqual(expect.objectContaining({ album, userId: user1.userId, type: SharedLinkType.ALBUM })); + expect(body).toEqual( + expect.objectContaining({ + album, + userId: user1.userId, + type: SharedLinkType.ALBUM, + }), + ); }); it('should return unauthorized for incorrect shared link', async () => { @@ -98,46 +179,65 @@ describe(`${PartnerController.name} (e2e)`, () => { }); it('should return unauthorized if target has been soft deleted', async () => { - const softDeletedAlbum = await api.albumApi.create(server, user1.accessToken, { albumName: 'shared with link' }); - const softDeletedAlbumLink = await api.sharedLinkApi.create(server, user1.accessToken, { - type: SharedLinkType.ALBUM, - albumId: softDeletedAlbum.id, - }); - await api.userApi.delete(server, admin.accessToken, user1.userId); - - const { status, body } = await request(server).get('/shared-link/me').query({ key: softDeletedAlbumLink.key }); + const { status, body } = await request(server).get('/shared-link/me').query({ key: linkWithDeletedAlbum.key }); expect(status).toBe(401); expect(body).toEqual(errorStub.invalidShareKey); }); it('should return unauthorized for password protected link', async () => { - const passwordProtectedLink = await api.sharedLinkApi.create(server, user1.accessToken, { - type: SharedLinkType.ALBUM, - albumId: album.id, - password: 'foo', - }); - - const { status, body } = await request(server).get('/shared-link/me').query({ key: passwordProtectedLink.key }); + const { status, body } = await request(server).get('/shared-link/me').query({ key: linkWithPassword.key }); expect(status).toBe(401); expect(body).toEqual(errorStub.invalidSharePassword); }); it('should get data for correct password protected link', async () => { - const passwordProtectedLink = await api.sharedLinkApi.create(server, user1.accessToken, { - type: SharedLinkType.ALBUM, - albumId: album.id, - password: 'foo', - }); - const { status, body } = await request(server) .get('/shared-link/me') - .query({ key: passwordProtectedLink.key, password: 'foo' }); + .query({ key: linkWithPassword.key, password: 'foo' }); expect(status).toBe(200); expect(body).toEqual(expect.objectContaining({ album, userId: user1.userId, type: SharedLinkType.ALBUM })); }); + + it('should return metadata for album shared link', async () => { + const { status, body } = await request(server).get('/shared-link/me').query({ key: linkWithMetadata.key }); + + expect(status).toBe(200); + expect(body.assets).toHaveLength(1); + expect(body.assets[0]).toEqual( + expect.objectContaining({ + originalFileName: 'example', + localDateTime: expect.any(String), + fileCreatedAt: expect.any(String), + exifInfo: expect.objectContaining({ + longitude: -108.400968333333, + latitude: 39.115, + orientation: '1', + dateTimeOriginal: expect.any(String), + timeZone: 'UTC-4', + state: 'Mesa County, Colorado', + country: 'United States of America', + }), + }), + ); + expect(body.album).toBeDefined(); + }); + + it('should not return metadata for album shared link without metadata', async () => { + const { status, body } = await request(server).get('/shared-link/me').query({ key: linkWithoutMetadata.key }); + + expect(status).toBe(200); + expect(body.assets).toHaveLength(1); + expect(body.album).toBeDefined(); + + const asset = body.assets[0]; + expect(asset).not.toHaveProperty('exifInfo'); + expect(asset).not.toHaveProperty('fileCreatedAt'); + expect(asset).not.toHaveProperty('originalFilename'); + expect(asset).not.toHaveProperty('originalPath'); + }); }); describe('GET /shared-link/:id', () => { @@ -267,7 +367,7 @@ describe(`${PartnerController.name} (e2e)`, () => { expect(body).toEqual(errorStub.badRequest()); }); - it('should update shared link', async () => { + it('should delete a shared link', async () => { const { status } = await request(server) .delete(`/shared-link/${sharedLink.id}`) .set('Authorization', `Bearer ${user1.accessToken}`); @@ -275,81 +375,4 @@ describe(`${PartnerController.name} (e2e)`, () => { expect(status).toBe(200); }); }); - - describe('Shared link metadata', () => { - beforeEach(async () => { - await restoreTempFolder(); - - await cp( - `${IMMICH_TEST_ASSET_PATH}/metadata/gps-position/thompson-springs.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/thompson-springs.jpg`, - ); - - await api.userApi.setExternalPath(server, admin.accessToken, admin.userId, '/'); - - const library = await api.libraryApi.create(server, admin.accessToken, { - type: LibraryType.EXTERNAL, - importPaths: [`${IMMICH_TEST_ASSET_TEMP_PATH}`], - }); - - await api.libraryApi.scanLibrary(server, admin.accessToken, library.id); - - const assets = await api.assetApi.getAllAssets(server, admin.accessToken); - - expect(assets).toHaveLength(1); - - album = await api.albumApi.create(server, admin.accessToken, { albumName: 'New album' }); - await api.albumApi.addAssets(server, admin.accessToken, album.id, { ids: [assets[0].id] }); - }); - - it('should return metadata for album shared link', async () => { - const sharedLink = await api.sharedLinkApi.create(server, admin.accessToken, { - type: SharedLinkType.ALBUM, - albumId: album.id, - }); - - const returnedLink = await api.sharedLinkApi.getMySharedLink(server, sharedLink.key); - - expect(returnedLink.assets).toHaveLength(1); - expect(returnedLink.album).toBeDefined(); - - const returnedAsset = returnedLink.assets[0]; - expect(returnedAsset).toEqual( - expect.objectContaining({ - originalFileName: 'thompson-springs', - resized: true, - localDateTime: '2022-01-10T15:15:44.310Z', - fileCreatedAt: '2022-01-10T19:15:44.310Z', - exifInfo: expect.objectContaining({ - longitude: -108.400968333333, - latitude: 39.115, - orientation: '1', - dateTimeOriginal: '2022-01-10T19:15:44.310Z', - timeZone: 'UTC-4', - state: 'Mesa County, Colorado', - country: 'United States of America', - }), - }), - ); - }); - - it('should not return metadata for album shared link without metadata', async () => { - const sharedLink = await api.sharedLinkApi.create(server, admin.accessToken, { - type: SharedLinkType.ALBUM, - albumId: album.id, - showMetadata: false, - }); - - const returnedLink = await api.sharedLinkApi.getMySharedLink(server, sharedLink.key); - - expect(returnedLink.assets).toHaveLength(1); - expect(returnedLink.album).toBeDefined(); - - const returnedAsset = returnedLink.assets[0]; - expect(returnedAsset).not.toHaveProperty('exifInfo'); - expect(returnedAsset).not.toHaveProperty('fileCreatedAt'); - expect(returnedAsset).not.toHaveProperty('originalFilename'); - expect(returnedAsset).not.toHaveProperty('originalPath'); - }); - }); }); diff --git a/server/test/e2e/system-config.e2e-spec.ts b/server/test/e2e/system-config.e2e-spec.ts index 0e505ca11a..d03fe06de9 100644 --- a/server/test/e2e/system-config.e2e-spec.ts +++ b/server/test/e2e/system-config.e2e-spec.ts @@ -6,24 +6,31 @@ import { errorStub } from '@test/fixtures'; import { testApp } from '@test/test-utils'; import request from 'supertest'; +const user1Dto = { + email: 'user1@immich.app', + password: 'Password123', + name: 'User 1', +}; + describe(`${SystemConfigController.name} (e2e)`, () => { let server: any; let admin: LoginResponseDto; + let nonAdmin: LoginResponseDto; beforeAll(async () => { [server] = await testApp.create(); + + await db.reset(); + await api.authApi.adminSignUp(server); + admin = await api.authApi.adminLogin(server); + await api.userApi.create(server, admin.accessToken, user1Dto); + nonAdmin = await api.authApi.login(server, user1Dto); }); afterAll(async () => { await testApp.teardown(); }); - beforeEach(async () => { - await db.reset(); - await api.authApi.adminSignUp(server); - admin = await api.authApi.adminLogin(server); - }); - describe('GET /system-config/map/style.json', () => { it('should require authentication', async () => { const { status, body } = await request(server).get('/system-config/map/style.json'); @@ -61,16 +68,10 @@ describe(`${SystemConfigController.name} (e2e)`, () => { }); it('should not require admin authentication', async () => { - const credentials = { email: 'user1@immich.app', password: 'Password123' }; - await api.userApi.create(server, admin.accessToken, { - ...credentials, - name: 'User 1', - }); - const { accessToken } = await api.authApi.login(server, credentials); const { status, body } = await request(server) .get('/system-config/map/style.json') .query({ theme: 'dark' }) - .set('Authorization', `Bearer ${accessToken}`); + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); expect(status).toBe(200); expect(body).toEqual(expect.objectContaining({ id: 'immich-map-dark' })); }); diff --git a/server/test/test-utils.ts b/server/test/test-utils.ts index 819d04556f..2cbd4f19a6 100644 --- a/server/test/test-utils.ts +++ b/server/test/test-utils.ts @@ -85,8 +85,8 @@ export const testApp = { return [app.getHttpServer(), app]; }, - reset: async () => { - await db.reset(); + reset: async (options?: ResetOptions) => { + await db.reset(options); }, teardown: async () => { await app.get(AppService).teardown();