diff --git a/server/e2e/api/specs/album.e2e-spec.ts b/e2e/src/api/specs/album.e2e-spec.ts similarity index 52% rename from server/e2e/api/specs/album.e2e-spec.ts rename to e2e/src/api/specs/album.e2e-spec.ts index 312816035c..c131edc49c 100644 --- a/server/e2e/api/specs/album.e2e-spec.ts +++ b/e2e/src/api/specs/album.e2e-spec.ts @@ -1,11 +1,15 @@ -import { AlbumResponseDto, LoginResponseDto } from '@app/domain'; -import { AlbumController } from '@app/immich'; -import { AssetFileUploadResponseDto } from '@app/immich/api-v1/asset/response-dto/asset-file-upload-response.dto'; -import { SharedLinkType } from '@app/infra/entities'; -import { errorStub, userDto, uuidStub } from '@test/fixtures'; +import { + AlbumResponseDto, + AssetResponseDto, + LoginResponseDto, + SharedLinkType, + deleteUser, +} from '@immich/sdk'; +import { createUserDto, uuidDto } from 'src/fixtures'; +import { errorDto } from 'src/responses'; +import { apiUtils, app, asBearerAuth, dbUtils } from 'src/utils'; import request from 'supertest'; -import { api } from '../../client'; -import { testApp } from '../utils'; +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; const user1SharedUser = 'user1SharedUser'; const user1SharedLink = 'user1SharedLink'; @@ -14,193 +18,327 @@ const user2SharedUser = 'user2SharedUser'; const user2SharedLink = 'user2SharedLink'; const user2NotShared = 'user2NotShared'; -describe(`${AlbumController.name} (e2e)`, () => { - let server: any; +describe('/album', () => { let admin: LoginResponseDto; let user1: LoginResponseDto; - let user1Asset: AssetFileUploadResponseDto; + let user1Asset1: AssetResponseDto; + let user1Asset2: AssetResponseDto; let user1Albums: AlbumResponseDto[]; let user2: LoginResponseDto; let user2Albums: AlbumResponseDto[]; + let user3: LoginResponseDto; // deleted beforeAll(async () => { - server = (await testApp.create()).getHttpServer(); - }); + apiUtils.setup(); + await dbUtils.reset(); - afterAll(async () => { - await testApp.teardown(); - }); + admin = await apiUtils.adminSetup(); - beforeEach(async () => { - await testApp.reset(); - await api.authApi.adminSignUp(server); - admin = await api.authApi.adminLogin(server); - - await Promise.all([ - api.userApi.create(server, admin.accessToken, userDto.user1), - api.userApi.create(server, admin.accessToken, userDto.user2), + [user1, user2, user3] = await Promise.all([ + apiUtils.userSetup(admin.accessToken, createUserDto.user1), + apiUtils.userSetup(admin.accessToken, createUserDto.user2), + apiUtils.userSetup(admin.accessToken, createUserDto.user3), ]); - [user1, user2] = await Promise.all([ - api.authApi.login(server, userDto.user1), - api.authApi.login(server, userDto.user2), + [user1Asset1, user1Asset2] = await Promise.all([ + apiUtils.createAsset(user1.accessToken), + apiUtils.createAsset(user1.accessToken), ]); - user1Asset = await api.assetApi.upload(server, user1.accessToken, 'example'); - const albums = await Promise.all([ // user 1 - api.albumApi.create(server, user1.accessToken, { + apiUtils.createAlbum(user1.accessToken, { albumName: user1SharedUser, sharedWithUserIds: [user2.userId], - assetIds: [user1Asset.id], + assetIds: [user1Asset1.id], + }), + apiUtils.createAlbum(user1.accessToken, { + albumName: user1SharedLink, + assetIds: [user1Asset1.id], + }), + apiUtils.createAlbum(user1.accessToken, { + albumName: user1NotShared, + assetIds: [user1Asset1.id, user1Asset2.id], }), - api.albumApi.create(server, user1.accessToken, { albumName: user1SharedLink, assetIds: [user1Asset.id] }), - api.albumApi.create(server, user1.accessToken, { albumName: user1NotShared, assetIds: [user1Asset.id] }), // user 2 - api.albumApi.create(server, user2.accessToken, { + apiUtils.createAlbum(user2.accessToken, { albumName: user2SharedUser, sharedWithUserIds: [user1.userId], - assetIds: [user1Asset.id], + assetIds: [user1Asset1.id], + }), + apiUtils.createAlbum(user2.accessToken, { albumName: user2SharedLink }), + apiUtils.createAlbum(user2.accessToken, { albumName: user2NotShared }), + + // user 3 + apiUtils.createAlbum(user3.accessToken, { + albumName: 'Deleted', + sharedWithUserIds: [user1.userId], }), - api.albumApi.create(server, user2.accessToken, { albumName: user2SharedLink }), - api.albumApi.create(server, user2.accessToken, { albumName: user2NotShared }), ]); user1Albums = albums.slice(0, 3); - user2Albums = albums.slice(3); + user2Albums = albums.slice(3, 6); await Promise.all([ // add shared link to user1SharedLink album - api.sharedLinkApi.create(server, user1.accessToken, { - type: SharedLinkType.ALBUM, + apiUtils.createSharedLink(user1.accessToken, { + type: SharedLinkType.Album, albumId: user1Albums[1].id, }), - // add shared link to user2SharedLink album - api.sharedLinkApi.create(server, user2.accessToken, { - type: SharedLinkType.ALBUM, + apiUtils.createSharedLink(user2.accessToken, { + type: SharedLinkType.Album, albumId: user2Albums[1].id, }), ]); + + await deleteUser( + { id: user3.userId }, + { headers: asBearerAuth(admin.accessToken) } + ); }); describe('GET /album', () => { it('should require authentication', async () => { - const { status, body } = await request(server).get('/album'); + const { status, body } = await request(app).get('/album'); expect(status).toBe(401); - expect(body).toEqual(errorStub.unauthorized); + expect(body).toEqual(errorDto.unauthorized); }); it('should reject an invalid shared param', async () => { - const { status, body } = await request(server) + const { status, body } = await request(app) .get('/album?shared=invalid') .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toEqual(400); - expect(body).toEqual(errorStub.badRequest(['shared must be a boolean value'])); + expect(body).toEqual( + errorDto.badRequest(['shared must be a boolean value']) + ); }); it('should reject an invalid assetId param', async () => { - const { status, body } = await request(server) + const { status, body } = await request(app) .get('/album?assetId=invalid') .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toEqual(400); - expect(body).toEqual(errorStub.badRequest(['assetId must be a UUID'])); + expect(body).toEqual(errorDto.badRequest(['assetId must be a UUID'])); }); it('should not return shared albums with a deleted owner', async () => { - await api.userApi.delete(server, admin.accessToken, user1.userId); - const { status, body } = await request(server) + const { status, body } = await request(app) .get('/album?shared=true') - .set('Authorization', `Bearer ${user2.accessToken}`); + .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); - expect(body).toHaveLength(1); + expect(body).toHaveLength(3); expect(body).toEqual( expect.arrayContaining([ - expect.objectContaining({ ownerId: user2.userId, albumName: user2SharedLink, shared: true }), - ]), + expect.objectContaining({ + ownerId: user1.userId, + albumName: user1SharedLink, + shared: true, + }), + expect.objectContaining({ + ownerId: user1.userId, + albumName: user1SharedUser, + shared: true, + }), + expect.objectContaining({ + ownerId: user2.userId, + albumName: user2SharedUser, + shared: true, + }), + ]) ); }); it('should return the album collection including owned and shared', async () => { - const { status, body } = await request(server).get('/album').set('Authorization', `Bearer ${user1.accessToken}`); + const { status, body } = await request(app) + .get('/album') + .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(3); expect(body).toEqual( expect.arrayContaining([ - expect.objectContaining({ ownerId: user1.userId, albumName: user1SharedUser, shared: true }), - expect.objectContaining({ ownerId: user1.userId, albumName: user1SharedLink, shared: true }), - expect.objectContaining({ ownerId: user1.userId, albumName: user1NotShared, shared: false }), - ]), + expect.objectContaining({ + ownerId: user1.userId, + albumName: user1SharedUser, + shared: true, + }), + expect.objectContaining({ + ownerId: user1.userId, + albumName: user1SharedLink, + shared: true, + }), + expect.objectContaining({ + ownerId: user1.userId, + albumName: user1NotShared, + shared: false, + }), + ]) ); }); it('should return the album collection filtered by shared', async () => { - const { status, body } = await request(server) + const { status, body } = await request(app) .get('/album?shared=true') .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(3); expect(body).toEqual( expect.arrayContaining([ - expect.objectContaining({ ownerId: user1.userId, albumName: user1SharedUser, shared: true }), - expect.objectContaining({ ownerId: user1.userId, albumName: user1SharedLink, shared: true }), - expect.objectContaining({ ownerId: user2.userId, albumName: user2SharedUser, shared: true }), - ]), + expect.objectContaining({ + ownerId: user1.userId, + albumName: user1SharedUser, + shared: true, + }), + expect.objectContaining({ + ownerId: user1.userId, + albumName: user1SharedLink, + shared: true, + }), + expect.objectContaining({ + ownerId: user2.userId, + albumName: user2SharedUser, + shared: true, + }), + ]) ); }); it('should return the album collection filtered by NOT shared', async () => { - const { status, body } = await request(server) + const { status, body } = await request(app) .get('/album?shared=false') .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(1); expect(body).toEqual( expect.arrayContaining([ - expect.objectContaining({ ownerId: user1.userId, albumName: user1NotShared, shared: false }), - ]), + expect.objectContaining({ + ownerId: user1.userId, + albumName: user1NotShared, + shared: false, + }), + ]) ); }); it('should return the album collection filtered by assetId', async () => { - const asset = await api.assetApi.upload(server, user1.accessToken, 'example2'); - await api.albumApi.addAssets(server, user1.accessToken, user1Albums[0].id, { ids: [asset.id] }); - const { status, body } = await request(server) - .get(`/album?assetId=${asset.id}`) + const { status, body } = await request(app) + .get(`/album?assetId=${user1Asset2.id}`) .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(1); }); it('should return the album collection filtered by assetId and ignores shared=true', async () => { - const { status, body } = await request(server) - .get(`/album?shared=true&assetId=${user1Asset.id}`) + const { status, body } = await request(app) + .get(`/album?shared=true&assetId=${user1Asset1.id}`) .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(4); }); it('should return the album collection filtered by assetId and ignores shared=false', async () => { - const { status, body } = await request(server) - .get(`/album?shared=false&assetId=${user1Asset.id}`) + const { status, body } = await request(app) + .get(`/album?shared=false&assetId=${user1Asset1.id}`) .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(4); }); }); + describe('GET /album/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get( + `/album/${user1Albums[0].id}` + ); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should return album info for own album', async () => { + const { status, body } = await request(app) + .get(`/album/${user1Albums[0].id}?withoutAssets=false`) + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(200); + expect(body).toEqual({ + ...user1Albums[0], + assets: [expect.objectContaining(user1Albums[0].assets[0])], + }); + }); + + it('should return album info for shared album', async () => { + const { status, body } = await request(app) + .get(`/album/${user2Albums[0].id}?withoutAssets=false`) + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(200); + expect(body).toEqual({ + ...user2Albums[0], + assets: [expect.objectContaining(user2Albums[0].assets[0])], + }); + }); + + it('should return album info with assets when withoutAssets is undefined', async () => { + const { status, body } = await request(app) + .get(`/album/${user1Albums[0].id}`) + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(200); + expect(body).toEqual({ + ...user1Albums[0], + assets: [expect.objectContaining(user1Albums[0].assets[0])], + }); + }); + + it('should return album info without assets when withoutAssets is true', async () => { + const { status, body } = await request(app) + .get(`/album/${user1Albums[0].id}?withoutAssets=true`) + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(200); + expect(body).toEqual({ + ...user1Albums[0], + assets: [], + assetCount: 1, + }); + }); + }); + + describe('GET /album/count', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get('/album/count'); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should return total count of albums the user has access to', async () => { + const { status, body } = await request(app) + .get('/album/count') + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(200); + expect(body).toEqual({ owned: 3, shared: 3, notShared: 1 }); + }); + }); + describe('POST /album', () => { it('should require authentication', async () => { - const { status, body } = await request(server).post('/album').send({ albumName: 'New album' }); + const { status, body } = await request(app) + .post('/album') + .send({ albumName: 'New album' }); expect(status).toBe(401); - expect(body).toEqual(errorStub.unauthorized); + expect(body).toEqual(errorDto.unauthorized); }); it('should create an album', async () => { - const body = await api.albumApi.create(server, user1.accessToken, { albumName: 'New album' }); + const { status, body } = await request(app) + .post('/album') + .send({ albumName: 'New album' }) + .set('Authorization', `Bearer ${user1.accessToken}`); + expect(status).toBe(201); expect(body).toEqual({ id: expect.any(String), createdAt: expect.any(String), @@ -220,113 +358,56 @@ describe(`${AlbumController.name} (e2e)`, () => { }); }); - describe('GET /album/count', () => { - it('should require authentication', async () => { - const { status, body } = await request(server).get('/album/count'); - expect(status).toBe(401); - expect(body).toEqual(errorStub.unauthorized); - }); - - it('should return total count of albums the user has access to', async () => { - const { status, body } = await request(server) - .get('/album/count') - .set('Authorization', `Bearer ${user1.accessToken}`); - - expect(status).toBe(200); - expect(body).toEqual({ owned: 3, shared: 3, notShared: 1 }); - }); - }); - - describe('GET /album/:id', () => { - it('should require authentication', async () => { - const { status, body } = await request(server).get(`/album/${user1Albums[0].id}`); - expect(status).toBe(401); - expect(body).toEqual(errorStub.unauthorized); - }); - - it('should return album info for own album', async () => { - const { status, body } = await request(server) - .get(`/album/${user1Albums[0].id}?withoutAssets=false`) - .set('Authorization', `Bearer ${user1.accessToken}`); - - expect(status).toBe(200); - expect(body).toEqual({ ...user1Albums[0], assets: [expect.objectContaining(user1Albums[0].assets[0])] }); - }); - - it('should return album info for shared album', async () => { - const { status, body } = await request(server) - .get(`/album/${user2Albums[0].id}?withoutAssets=false`) - .set('Authorization', `Bearer ${user1.accessToken}`); - - expect(status).toBe(200); - expect(body).toEqual({ ...user2Albums[0], assets: [expect.objectContaining(user2Albums[0].assets[0])] }); - }); - - it('should return album info with assets when withoutAssets is undefined', async () => { - const { status, body } = await request(server) - .get(`/album/${user1Albums[0].id}`) - .set('Authorization', `Bearer ${user1.accessToken}`); - - expect(status).toBe(200); - expect(body).toEqual({ ...user1Albums[0], assets: [expect.objectContaining(user1Albums[0].assets[0])] }); - }); - - it('should return album info without assets when withoutAssets is true', async () => { - const { status, body } = await request(server) - .get(`/album/${user1Albums[0].id}?withoutAssets=true`) - .set('Authorization', `Bearer ${user1.accessToken}`); - - expect(status).toBe(200); - expect(body).toEqual({ - ...user1Albums[0], - assets: [], - assetCount: 1, - }); - }); - }); - describe('PUT /album/:id/assets', () => { it('should require authentication', async () => { - const { status, body } = await request(server).put(`/album/${user1Albums[0].id}/assets`); + const { status, body } = await request(app).put( + `/album/${user1Albums[0].id}/assets` + ); expect(status).toBe(401); - expect(body).toEqual(errorStub.unauthorized); + expect(body).toEqual(errorDto.unauthorized); }); it('should be able to add own asset to own album', async () => { - const asset = await api.assetApi.upload(server, user1.accessToken, 'example1'); - const { status, body } = await request(server) + const asset = await apiUtils.createAsset(user1.accessToken); + const { status, body } = await request(app) .put(`/album/${user1Albums[0].id}/assets`) .set('Authorization', `Bearer ${user1.accessToken}`) .send({ ids: [asset.id] }); expect(status).toBe(200); - expect(body).toEqual([expect.objectContaining({ id: asset.id, success: true })]); + expect(body).toEqual([ + expect.objectContaining({ id: asset.id, success: true }), + ]); }); it('should be able to add own asset to shared album', async () => { - const asset = await api.assetApi.upload(server, user1.accessToken, 'example1'); - const { status, body } = await request(server) + const asset = await apiUtils.createAsset(user1.accessToken); + const { status, body } = await request(app) .put(`/album/${user2Albums[0].id}/assets`) .set('Authorization', `Bearer ${user1.accessToken}`) .send({ ids: [asset.id] }); expect(status).toBe(200); - expect(body).toEqual([expect.objectContaining({ id: asset.id, success: true })]); + expect(body).toEqual([ + expect.objectContaining({ id: asset.id, success: true }), + ]); }); }); describe('PATCH /album/:id', () => { it('should require authentication', async () => { - const { status, body } = await request(server) - .patch(`/album/${uuidStub.notFound}`) + const { status, body } = await request(app) + .patch(`/album/${uuidDto.notFound}`) .send({ albumName: 'New album name' }); expect(status).toBe(401); - expect(body).toEqual(errorStub.unauthorized); + expect(body).toEqual(errorDto.unauthorized); }); it('should update an album', async () => { - const album = await api.albumApi.create(server, user1.accessToken, { albumName: 'New album' }); - const { status, body } = await request(server) + const album = await apiUtils.createAlbum(user1.accessToken, { + albumName: 'New album', + }); + const { status, body } = await request(app) .patch(`/album/${album.id}`) .set('Authorization', `Bearer ${user1.accessToken}`) .send({ @@ -345,52 +426,68 @@ describe(`${AlbumController.name} (e2e)`, () => { describe('DELETE /album/:id/assets', () => { it('should require authentication', async () => { - const { status, body } = await request(server) + const { status, body } = await request(app) .delete(`/album/${user1Albums[0].id}/assets`) - .send({ ids: [user1Asset.id] }); + .send({ ids: [user1Asset1.id] }); expect(status).toBe(401); - expect(body).toEqual(errorStub.unauthorized); - }); - - it('should be able to remove own asset from own album', async () => { - const { status, body } = await request(server) - .delete(`/album/${user1Albums[0].id}/assets`) - .set('Authorization', `Bearer ${user1.accessToken}`) - .send({ ids: [user1Asset.id] }); - - expect(status).toBe(200); - expect(body).toEqual([expect.objectContaining({ id: user1Asset.id, success: true })]); - }); - - it('should be able to remove own asset from shared album', async () => { - const { status, body } = await request(server) - .delete(`/album/${user2Albums[0].id}/assets`) - .set('Authorization', `Bearer ${user1.accessToken}`) - .send({ ids: [user1Asset.id] }); - - expect(status).toBe(200); - expect(body).toEqual([expect.objectContaining({ id: user1Asset.id, success: true })]); + expect(body).toEqual(errorDto.unauthorized); }); it('should not be able to remove foreign asset from own album', async () => { - const { status, body } = await request(server) + const { status, body } = await request(app) .delete(`/album/${user2Albums[0].id}/assets`) .set('Authorization', `Bearer ${user2.accessToken}`) - .send({ ids: [user1Asset.id] }); + .send({ ids: [user1Asset1.id] }); expect(status).toBe(200); - expect(body).toEqual([expect.objectContaining({ id: user1Asset.id, success: false, error: 'no_permission' })]); + expect(body).toEqual([ + expect.objectContaining({ + id: user1Asset1.id, + success: false, + error: 'no_permission', + }), + ]); }); it('should not be able to remove foreign asset from foreign album', async () => { - const { status, body } = await request(server) + const { status, body } = await request(app) .delete(`/album/${user1Albums[0].id}/assets`) .set('Authorization', `Bearer ${user2.accessToken}`) - .send({ ids: [user1Asset.id] }); + .send({ ids: [user1Asset1.id] }); expect(status).toBe(200); - expect(body).toEqual([expect.objectContaining({ id: user1Asset.id, success: false, error: 'no_permission' })]); + expect(body).toEqual([ + expect.objectContaining({ + id: user1Asset1.id, + success: false, + error: 'no_permission', + }), + ]); + }); + + it('should be able to remove own asset from own album', async () => { + const { status, body } = await request(app) + .delete(`/album/${user1Albums[0].id}/assets`) + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ ids: [user1Asset1.id] }); + + expect(status).toBe(200); + expect(body).toEqual([ + expect.objectContaining({ id: user1Asset1.id, success: true }), + ]); + }); + + it('should be able to remove own asset from shared album', async () => { + const { status, body } = await request(app) + .delete(`/album/${user2Albums[0].id}/assets`) + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ ids: [user1Asset1.id] }); + + expect(status).toBe(200); + expect(body).toEqual([ + expect.objectContaining({ id: user1Asset1.id, success: true }), + ]); }); }); @@ -398,51 +495,57 @@ describe(`${AlbumController.name} (e2e)`, () => { let album: AlbumResponseDto; beforeEach(async () => { - album = await api.albumApi.create(server, user1.accessToken, { albumName: 'testAlbum' }); + album = await apiUtils.createAlbum(user1.accessToken, { + albumName: 'testAlbum', + }); }); it('should require authentication', async () => { - const { status, body } = await request(server) + const { status, body } = await request(app) .put(`/album/${user1Albums[0].id}/users`) .send({ sharedUserIds: [] }); expect(status).toBe(401); - expect(body).toEqual(errorStub.unauthorized); + expect(body).toEqual(errorDto.unauthorized); }); it('should be able to add user to own album', async () => { - const { status, body } = await request(server) + const { status, body } = await request(app) .put(`/album/${album.id}/users`) .set('Authorization', `Bearer ${user1.accessToken}`) .send({ sharedUserIds: [user2.userId] }); expect(status).toBe(200); - expect(body).toEqual(expect.objectContaining({ sharedUsers: [expect.objectContaining({ id: user2.userId })] })); + expect(body).toEqual( + expect.objectContaining({ + sharedUsers: [expect.objectContaining({ id: user2.userId })], + }) + ); }); it('should not be able to share album with owner', async () => { - const { status, body } = await request(server) + const { status, body } = await request(app) .put(`/album/${album.id}/users`) .set('Authorization', `Bearer ${user1.accessToken}`) .send({ sharedUserIds: [user1.userId] }); expect(status).toBe(400); - expect(body).toEqual(errorStub.badRequest('Cannot be shared with owner')); + expect(body).toEqual(errorDto.badRequest('Cannot be shared with owner')); }); it('should not be able to add existing user to shared album', async () => { - await request(server) + await request(app) .put(`/album/${album.id}/users`) .set('Authorization', `Bearer ${user1.accessToken}`) .send({ sharedUserIds: [user2.userId] }); - const { status, body } = await request(server) + const { status, body } = await request(app) .put(`/album/${album.id}/users`) .set('Authorization', `Bearer ${user1.accessToken}`) .send({ sharedUserIds: [user2.userId] }); expect(status).toBe(400); - expect(body).toEqual(errorStub.badRequest('User already added')); + expect(body).toEqual(errorDto.badRequest('User already added')); }); }); }); diff --git a/e2e/src/api/specs/shared-link.e2e-spec.ts b/e2e/src/api/specs/shared-link.e2e-spec.ts index df57c57137..e791c447ac 100644 --- a/e2e/src/api/specs/shared-link.e2e-spec.ts +++ b/e2e/src/api/specs/shared-link.e2e-spec.ts @@ -15,9 +15,6 @@ import { apiUtils, app, asBearerAuth, dbUtils } from 'src/utils'; import request from 'supertest'; import { beforeAll, describe, expect, it } from 'vitest'; -const createSharedLink = (dto: SharedLinkCreateDto, accessToken: string) => - create({ sharedLinkCreateDto: dto }, { headers: asBearerAuth(accessToken) }); - describe('/shared-link', () => { let admin: LoginResponseDto; let asset1: AssetResponseDto; @@ -78,38 +75,33 @@ describe('/shared-link', () => { linkWithMetadata, linkWithoutMetadata, ] = await Promise.all([ - createSharedLink( - { type: SharedLinkType.Album, albumId: deletedAlbum.id }, - user2.accessToken - ), - createSharedLink( - { type: SharedLinkType.Album, albumId: album.id }, - user1.accessToken - ), - createSharedLink( - { type: SharedLinkType.Individual, assetIds: [asset1.id] }, - user1.accessToken - ), - createSharedLink( - { type: SharedLinkType.Album, albumId: album.id, password: 'foo' }, - user1.accessToken - ), - createSharedLink( - { - type: SharedLinkType.Album, - albumId: metadataAlbum.id, - showMetadata: true, - }, - user1.accessToken - ), - createSharedLink( - { - type: SharedLinkType.Album, - albumId: metadataAlbum.id, - showMetadata: false, - }, - user1.accessToken - ), + apiUtils.createSharedLink(user2.accessToken, { + type: SharedLinkType.Album, + albumId: deletedAlbum.id, + }), + apiUtils.createSharedLink(user1.accessToken, { + type: SharedLinkType.Album, + albumId: album.id, + }), + apiUtils.createSharedLink(user1.accessToken, { + type: SharedLinkType.Individual, + assetIds: [asset1.id], + }), + apiUtils.createSharedLink(user1.accessToken, { + type: SharedLinkType.Album, + albumId: album.id, + password: 'foo', + }), + apiUtils.createSharedLink(user1.accessToken, { + type: SharedLinkType.Album, + albumId: metadataAlbum.id, + showMetadata: true, + }), + apiUtils.createSharedLink(user1.accessToken, { + type: SharedLinkType.Album, + albumId: metadataAlbum.id, + showMetadata: false, + }), ]); await deleteUser( diff --git a/e2e/src/api/specs/user.e2e-spec.ts b/e2e/src/api/specs/user.e2e-spec.ts index 74e1646802..9bfb47284a 100644 --- a/e2e/src/api/specs/user.e2e-spec.ts +++ b/e2e/src/api/specs/user.e2e-spec.ts @@ -1,26 +1,31 @@ -import { - LoginResponseDto, - UserResponseDto, - createUser, - deleteUser, - getUserById, -} from '@immich/sdk'; +import { LoginResponseDto, deleteUser, getUserById } from '@immich/sdk'; import { createUserDto, userDto } from 'src/fixtures'; import { errorDto } from 'src/responses'; import { apiUtils, app, asBearerAuth, dbUtils } from 'src/utils'; import request from 'supertest'; -import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; +import { beforeAll, describe, expect, it } from 'vitest'; describe('/server-info', () => { let admin: LoginResponseDto; + let deletedUser: LoginResponseDto; + let userToDelete: LoginResponseDto; + let nonAdmin: LoginResponseDto; beforeAll(async () => { apiUtils.setup(); - }); - - beforeEach(async () => { await dbUtils.reset(); admin = await apiUtils.adminSetup({ onboarding: false }); + + [deletedUser, nonAdmin, userToDelete] = await Promise.all([ + apiUtils.userSetup(admin.accessToken, createUserDto.user1), + apiUtils.userSetup(admin.accessToken, createUserDto.user2), + apiUtils.userSetup(admin.accessToken, createUserDto.user3), + ]); + + await deleteUser( + { id: deletedUser.userId }, + { headers: asBearerAuth(admin.accessToken) } + ); }); describe('GET /user', () => { @@ -30,60 +35,54 @@ describe('/server-info', () => { expect(body).toEqual(errorDto.unauthorized); }); - it('should start with the admin', async () => { + it('should get users', async () => { const { status, body } = await request(app) .get('/user') .set('Authorization', `Bearer ${admin.accessToken}`); expect(status).toEqual(200); - expect(body).toHaveLength(1); - expect(body[0]).toMatchObject({ email: 'admin@immich.cloud' }); + expect(body).toHaveLength(4); + expect(body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ email: 'admin@immich.cloud' }), + expect.objectContaining({ email: 'user1@immich.cloud' }), + expect.objectContaining({ email: 'user2@immich.cloud' }), + expect.objectContaining({ email: 'user3@immich.cloud' }), + ]) + ); }); it('should hide deleted users', async () => { - const user1 = await apiUtils.userSetup( - admin.accessToken, - createUserDto.user1 - ); - await deleteUser( - { id: user1.userId }, - { headers: asBearerAuth(admin.accessToken) } - ); - const { status, body } = await request(app) .get(`/user`) .query({ isAll: true }) .set('Authorization', `Bearer ${admin.accessToken}`); expect(status).toBe(200); - expect(body).toHaveLength(1); - expect(body[0]).toMatchObject({ email: 'admin@immich.cloud' }); + expect(body).toHaveLength(3); + expect(body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ email: 'admin@immich.cloud' }), + expect.objectContaining({ email: 'user2@immich.cloud' }), + expect.objectContaining({ email: 'user3@immich.cloud' }), + ]) + ); }); it('should include deleted users', async () => { - const user1 = await apiUtils.userSetup( - admin.accessToken, - createUserDto.user1 - ); - await deleteUser( - { id: user1.userId }, - { headers: asBearerAuth(admin.accessToken) } - ); - const { status, body } = await request(app) .get(`/user`) .query({ isAll: false }) .set('Authorization', `Bearer ${admin.accessToken}`); expect(status).toBe(200); - expect(body).toHaveLength(2); - expect(body[0]).toMatchObject({ - id: user1.userId, - email: 'user1@immich.cloud', - deletedAt: expect.any(String), - }); - expect(body[1]).toMatchObject({ - id: admin.userId, - email: 'admin@immich.cloud', - }); + expect(body).toHaveLength(4); + expect(body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ email: 'admin@immich.cloud' }), + expect.objectContaining({ email: 'user1@immich.cloud' }), + expect.objectContaining({ email: 'user2@immich.cloud' }), + expect.objectContaining({ email: 'user3@immich.cloud' }), + ]) + ); }); }); @@ -149,13 +148,13 @@ describe('/server-info', () => { .post(`/user`) .send({ isAdmin: true, - email: 'user1@immich.cloud', - password: 'Password123', + email: 'user4@immich.cloud', + password: 'password123', name: 'Immich', }) .set('Authorization', `Bearer ${admin.accessToken}`); expect(body).toMatchObject({ - email: 'user1@immich.cloud', + email: 'user4@immich.cloud', isAdmin: false, shouldChangePassword: true, }); @@ -181,18 +180,9 @@ describe('/server-info', () => { }); describe('DELETE /user/:id', () => { - let userToDelete: UserResponseDto; - - beforeEach(async () => { - userToDelete = await createUser( - { createUserDto: createUserDto.user1 }, - { headers: asBearerAuth(admin.accessToken) } - ); - }); - it('should require authentication', async () => { const { status, body } = await request(app).delete( - `/user/${userToDelete.id}` + `/user/${userToDelete.userId}` ); expect(status).toBe(401); expect(body).toEqual(errorDto.unauthorized); @@ -200,12 +190,12 @@ describe('/server-info', () => { it('should delete user', async () => { const { status, body } = await request(app) - .delete(`/user/${userToDelete.id}`) + .delete(`/user/${userToDelete.userId}`) .set('Authorization', `Bearer ${admin.accessToken}`); expect(status).toBe(200); - expect(body).toEqual({ - ...userToDelete, + expect(body).toMatchObject({ + id: userToDelete.userId, updatedAt: expect.any(String), deletedAt: expect.any(String), }); @@ -231,14 +221,9 @@ describe('/server-info', () => { } it('should not allow a non-admin to become an admin', async () => { - const user = await apiUtils.userSetup( - admin.accessToken, - createUserDto.user1 - ); - const { status, body } = await request(app) .put(`/user`) - .send({ isAdmin: true, id: user.userId }) + .send({ isAdmin: true, id: nonAdmin.userId }) .set('Authorization', `Bearer ${admin.accessToken}`); expect(status).toBe(400); diff --git a/e2e/src/utils.ts b/e2e/src/utils.ts index 1c7382879d..a6374aff52 100644 --- a/e2e/src/utils.ts +++ b/e2e/src/utils.ts @@ -1,10 +1,14 @@ import { AssetResponseDto, + CreateAlbumDto, CreateAssetDto, CreateUserDto, PersonUpdateDto, + SharedLinkCreateDto, + createAlbum, createApiKey, createPerson, + createSharedLink, createUser, defaults, login, @@ -181,6 +185,11 @@ export const apiUtils = { { headers: asBearerAuth(accessToken) } ); }, + createAlbum: (accessToken: string, dto: CreateAlbumDto) => + createAlbum( + { createAlbumDto: dto }, + { headers: asBearerAuth(accessToken) } + ), createAsset: async ( accessToken: string, dto?: Omit @@ -211,6 +220,11 @@ export const apiUtils = { { headers: asBearerAuth(accessToken) } ); }, + createSharedLink: (accessToken: string, dto: SharedLinkCreateDto) => + createSharedLink( + { sharedLinkCreateDto: dto }, + { headers: asBearerAuth(accessToken) } + ), }; export const cliUtils = {