From c7531a36f9e98595fd5d769c64b5ad79288513d5 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Thu, 20 Aug 2026 17:32:51 -0400 Subject: [PATCH] refactor: partner e2e (#30895) --- e2e/src/specs/server/api/partner.e2e-spec.ts | 102 -------------- .../specs/services/partner.service.spec.ts | 124 ++++++++++++++++++ 2 files changed, 124 insertions(+), 102 deletions(-) delete mode 100644 e2e/src/specs/server/api/partner.e2e-spec.ts create mode 100644 server/test/medium/specs/services/partner.service.spec.ts diff --git a/e2e/src/specs/server/api/partner.e2e-spec.ts b/e2e/src/specs/server/api/partner.e2e-spec.ts deleted file mode 100644 index 0eb7d6db3..000000000 --- a/e2e/src/specs/server/api/partner.e2e-spec.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { LoginResponseDto, createPartner } from '@immich/sdk'; -import { createUserDto } from 'src/fixtures'; -import { app, asBearerAuth, utils } from 'src/utils'; -import request from 'supertest'; -import { beforeAll, describe, expect, it } from 'vitest'; - -describe('/partners', () => { - let admin: LoginResponseDto; - let user1: LoginResponseDto; - let user2: LoginResponseDto; - let user3: LoginResponseDto; - - beforeAll(async () => { - await utils.resetDatabase(); - - admin = await utils.adminSetup(); - - [user1, user2, user3] = await Promise.all([ - utils.userSetup(admin.accessToken, createUserDto.user1), - utils.userSetup(admin.accessToken, createUserDto.user2), - utils.userSetup(admin.accessToken, createUserDto.user3), - ]); - - await Promise.all([ - createPartner({ partnerCreateDto: { sharedWithId: user2.userId } }, { headers: asBearerAuth(user1.accessToken) }), - createPartner({ partnerCreateDto: { sharedWithId: user1.userId } }, { headers: asBearerAuth(user2.accessToken) }), - ]); - }); - - describe('GET /partners', () => { - it('should get all partners shared by user', async () => { - const { status, body } = await request(app) - .get('/partners') - .set('Authorization', `Bearer ${user1.accessToken}`) - .query({ direction: 'shared-by' }); - - expect(status).toBe(200); - expect(body).toEqual([expect.objectContaining({ id: user2.userId })]); - }); - - it('should get all partners that share with user', async () => { - const { status, body } = await request(app) - .get('/partners') - .set('Authorization', `Bearer ${user1.accessToken}`) - .query({ direction: 'shared-with' }); - - expect(status).toBe(200); - expect(body).toEqual([expect.objectContaining({ id: user2.userId })]); - }); - }); - - describe('POST /partners/:id', () => { - it('should share with new partner', async () => { - const { status, body } = await request(app) - .post(`/partners/${user3.userId}`) - .set('Authorization', `Bearer ${user1.accessToken}`); - - expect(status).toBe(201); - expect(body).toEqual(expect.objectContaining({ id: user3.userId })); - }); - - it('should not share with new partner if already sharing with this partner', async () => { - const { status, body } = await request(app) - .post(`/partners/${user2.userId}`) - .set('Authorization', `Bearer ${user1.accessToken}`); - - expect(status).toBe(400); - expect(body).toEqual(expect.objectContaining({ message: 'Partner already exists' })); - }); - }); - - describe('PUT /partners/:id', () => { - it('should update partner', async () => { - const { status, body } = await request(app) - .put(`/partners/${user2.userId}`) - .set('Authorization', `Bearer ${user1.accessToken}`) - .send({ inTimeline: false }); - - expect(status).toBe(200); - expect(body).toEqual(expect.objectContaining({ id: user2.userId, inTimeline: false })); - }); - }); - - describe('DELETE /partners/:id', () => { - it('should delete partner', async () => { - const { status } = await request(app) - .delete(`/partners/${user3.userId}`) - .set('Authorization', `Bearer ${user1.accessToken}`); - - expect(status).toBe(204); - }); - - it('should throw a bad request if partner not found', async () => { - const { status, body } = await request(app) - .delete(`/partners/${user3.userId}`) - .set('Authorization', `Bearer ${user1.accessToken}`); - - expect(status).toBe(400); - expect(body).toEqual(expect.objectContaining({ message: 'Partner not found' })); - }); - }); -}); diff --git a/server/test/medium/specs/services/partner.service.spec.ts b/server/test/medium/specs/services/partner.service.spec.ts new file mode 100644 index 000000000..addbd2bdb --- /dev/null +++ b/server/test/medium/specs/services/partner.service.spec.ts @@ -0,0 +1,124 @@ +import { Kysely } from 'kysely'; +import { AccessRepository } from 'src/repositories/access.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { PartnerDirection, PartnerRepository } from 'src/repositories/partner.repository'; +import { UserRepository } from 'src/repositories/user.repository'; +import { DB } from 'src/schema'; +import { PartnerService } from 'src/services/partner.service'; +import { newMediumService } from 'test/medium.factory'; +import { factory, newUuid } from 'test/small.factory'; +import { getKyselyDB } from 'test/utils'; + +let defaultDatabase: Kysely; + +const setup = (db?: Kysely) => { + return newMediumService(PartnerService, { + database: db || defaultDatabase, + real: [AccessRepository, PartnerRepository, UserRepository], + mock: [LoggingRepository], + }); +}; + +beforeAll(async () => { + defaultDatabase = await getKyselyDB(); +}); + +describe(PartnerService.name, () => { + describe('create', () => { + it('should share with a new partner', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const { user: partner } = await ctx.newUser(); + + await expect(sut.create(factory.auth({ user }), { sharedWithId: partner.id })).resolves.toEqual( + expect.objectContaining({ id: partner.id }), + ); + }); + + it('should not share with a partner that is already shared with', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const { user: partner } = await ctx.newUser(); + await ctx.newPartner({ sharedById: user.id, sharedWithId: partner.id }); + + await expect(sut.create(factory.auth({ user }), { sharedWithId: partner.id })).rejects.toThrow( + 'Partner already exists', + ); + }); + + it('should not share with an unknown user', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + + await expect(sut.create(factory.auth({ user }), { sharedWithId: newUuid() })).rejects.toThrow('Invalid user'); + }); + }); + + describe('search', () => { + it('should return partners shared by the user', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const { user: partner } = await ctx.newUser(); + await ctx.newPartner({ sharedById: user.id, sharedWithId: partner.id }); + + await expect(sut.search(factory.auth({ user }), { direction: PartnerDirection.SharedBy })).resolves.toEqual([ + expect.objectContaining({ id: partner.id }), + ]); + }); + + it('should return partners that share with the user', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const { user: partner } = await ctx.newUser(); + await ctx.newPartner({ sharedById: partner.id, sharedWithId: user.id }); + + await expect(sut.search(factory.auth({ user }), { direction: PartnerDirection.SharedWith })).resolves.toEqual([ + expect.objectContaining({ id: partner.id }), + ]); + }); + }); + + describe('update', () => { + it('should update a partner', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const { user: partner } = await ctx.newUser(); + await ctx.newPartner({ sharedById: partner.id, sharedWithId: user.id }); + + await expect(sut.update(factory.auth({ user }), partner.id, { inTimeline: false })).resolves.toEqual( + expect.objectContaining({ id: partner.id, inTimeline: false }), + ); + }); + + it('should not update a partner that did not share with the user', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const { user: partner } = await ctx.newUser(); + const { user: other } = await ctx.newUser(); + await ctx.newPartner({ sharedById: partner.id, sharedWithId: other.id }); + + await expect(sut.update(factory.auth({ user }), partner.id, { inTimeline: false })).rejects.toThrow( + 'Not found or no partner.update access', + ); + }); + }); + + describe('remove', () => { + it('should remove a partner', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const { user: partner } = await ctx.newUser(); + await ctx.newPartner({ sharedById: user.id, sharedWithId: partner.id }); + + await expect(sut.remove(factory.auth({ user }), partner.id)).resolves.toBeUndefined(); + await expect(sut.search(factory.auth({ user }), { direction: PartnerDirection.SharedBy })).resolves.toEqual([]); + }); + + it('should throw when the partner does not exist', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + + await expect(sut.remove(factory.auth({ user }), newUuid())).rejects.toThrow('Partner not found'); + }); + }); +});