From d806bc002cea5acd71b1d9061b59d40fffb64cdf Mon Sep 17 00:00:00 2001 From: "immich-push-o-matic[bot]" <179150890+immich-push-o-matic[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 08:38:24 +0000 Subject: [PATCH] fix: person without assets (#32117) fix: person without assets (#32114) (cherry picked from commit c0506fe723995808f4effd8d40d0235fcaf104b5) Co-authored-by: Jason Rasmussen Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> --- e2e/src/specs/server/api/person.e2e-spec.ts | 8 +- server/src/queries/person.repository.sql | 139 ++++++++++-------- server/src/repositories/person.repository.ts | 101 +++++-------- server/src/services/person.service.spec.ts | 4 + server/src/services/person.service.ts | 8 +- .../repositories/person.repository.spec.ts | 102 +++++++++++-- .../specs/services/person.service.spec.ts | 3 + 7 files changed, 217 insertions(+), 148 deletions(-) diff --git a/e2e/src/specs/server/api/person.e2e-spec.ts b/e2e/src/specs/server/api/person.e2e-spec.ts index 62286daf3..254364cb1 100644 --- a/e2e/src/specs/server/api/person.e2e-spec.ts +++ b/e2e/src/specs/server/api/person.e2e-spec.ts @@ -122,7 +122,7 @@ describe('/people', () => { expect(status).toBe(200); expect(body).toEqual({ hasNextPage: false, - total: 11, + total: 10, hidden: 1, people: [ expect.objectContaining({ name: 'Freddy' }), @@ -144,7 +144,7 @@ describe('/people', () => { expect(status).toBe(200); expect(body.hasNextPage).toBe(false); - expect(body.total).toBe(11); // All persons + expect(body.total).toBe(10); expect(body.hidden).toBe(1); // 'hidden_person' const people = body.people as PersonResponseDto[]; @@ -170,7 +170,7 @@ describe('/people', () => { expect(status).toBe(200); expect(body).toEqual({ hasNextPage: false, - total: 11, + total: 10, hidden: 1, people: [ expect.objectContaining({ name: 'Freddy' }), @@ -195,7 +195,7 @@ describe('/people', () => { expect(status).toBe(200); expect(body).toEqual({ hasNextPage: true, - total: 11, + total: 10, hidden: 1, people: [expect.objectContaining({ name: 'Alice' })], }); diff --git a/server/src/queries/person.repository.sql b/server/src/queries/person.repository.sql index 551622b72..4fc551f37 100644 --- a/server/src/queries/person.repository.sql +++ b/server/src/queries/person.repository.sql @@ -243,15 +243,29 @@ from "person_group" inner join "person" as "owned" on "owned"."personGroupId" = "person_group"."id" and "owned"."ownerId" = $6 - left join "asset_face" on "asset_face"."personGroupId" = "person_group"."id" + inner join "asset_face" on "asset_face"."personGroupId" = "person_group"."id" and "asset_face"."deletedAt" is null and "asset_face"."isVisible" is true - left join "asset" on "asset"."id" = "asset_face"."assetId" - and "asset"."ownerId" = $7 + inner join "asset" on "asset"."id" = "asset_face"."assetId" and "asset"."visibility" = 'timeline' and "asset"."deletedAt" is null + and ( + "asset"."ownerId" = any ($7::uuid[]) + or exists ( + select + 1 as "exists" + from + "album_asset" + inner join "album" on "album"."id" = "album_asset"."albumId" + and "album"."deletedAt" is null + inner join "album_user" on "album_user"."albumId" = "album"."id" + and "album_user"."userId" = $8::uuid + where + "album_asset"."assetId" = "asset"."id" + ) + ) where - "owned"."isHidden" = $8 + "owned"."isHidden" = $9 and 1 = 1 group by "person_group"."id", @@ -259,19 +273,7 @@ group by "owned"."personGroupId" having ( - exists ( - select - "person_user"."sharedWithId" - from - "person_user" - where - "person_user"."personGroupId" = "person_group"."id" - and "person_user"."sharedWithId" = $9 - ) - or ( - count("asset"."id") > $10 - and "owned"."name" != $11 - ) + "owned"."name" != $10 or count("asset"."id") >= COALESCE( ( SELECT @@ -279,7 +281,7 @@ having FROM user_metadata WHERE - "userId" = $12 + "userId" = $11 AND key = 'preferences' ), '3' @@ -293,9 +295,9 @@ order by NULLIF("owned"."name", '') asc nulls last, "owned"."createdAt" limit - $13 + $12 offset - $14 + $13 -- PersonRepository.getAllWithoutFaces select @@ -900,60 +902,73 @@ where ) -- PersonRepository.getNumberOfPeople -with - "similarity_threshold" as ( - select - set_config('pg_trgm.word_similarity_threshold', '0.5', true) as "thresh" - ) select coalesce(count(*), 0) as "total", coalesce( count(*) filter ( where - "owned"."isHidden" = $1 + "people"."isHidden" = $1 ), 0 ) as "hidden" from - "similarity_threshold", - "person_group" - left join "person" as "owned" on "owned"."personGroupId" = "person_group"."id" - and "owned"."ownerId" = $2 -where ( - ( - "owned"."ownerId" is not null - and exists ( + with + "similarity_threshold" as ( select - from - "asset_face" - where - "asset_face"."personGroupId" = "person_group"."id" - and "asset_face"."deletedAt" is null - and "asset_face"."isVisible" = $3 - and exists ( - select - from - "asset" - where - "asset"."id" = "asset_face"."assetId" - and "asset"."ownerId" = $4 - and "asset"."visibility" = 'timeline' - and "asset"."deletedAt" is null - ) + set_config('pg_trgm.word_similarity_threshold', '0.5', true) as "thresh" ) - ) - or exists ( - select - "person_user"."sharedById" - from - "person_user" - where - "person_user"."personGroupId" = "person_group"."id" - and "person_user"."sharedWithId" = $5 - ) - ) - and 1 = 1 + select + "owned"."isHidden" + from + "similarity_threshold", + "person_group" + inner join "person" as "owned" on "owned"."personGroupId" = "person_group"."id" + and "owned"."ownerId" = $2 + inner join "asset_face" on "asset_face"."personGroupId" = "person_group"."id" + and "asset_face"."deletedAt" is null + and "asset_face"."isVisible" is true + inner join "asset" on "asset"."id" = "asset_face"."assetId" + and "asset"."visibility" = 'timeline' + and "asset"."deletedAt" is null + and ( + "asset"."ownerId" = any ($3::uuid[]) + or exists ( + select + 1 as "exists" + from + "album_asset" + inner join "album" on "album"."id" = "album_asset"."albumId" + and "album"."deletedAt" is null + inner join "album_user" on "album_user"."albumId" = "album"."id" + and "album_user"."userId" = $4::uuid + where + "album_asset"."assetId" = "asset"."id" + ) + ) + where + 1 = 1 + group by + "person_group"."id", + "owned"."ownerId", + "owned"."personGroupId" + having + ( + "owned"."name" != $5 + or count("asset"."id") >= COALESCE( + ( + SELECT + value -> 'people' ->> 'minimumFaces' + FROM + user_metadata + WHERE + "userId" = $6 + AND key = 'preferences' + ), + '3' + )::int + ) + ) as "people" -- PersonRepository.createGroup insert into diff --git a/server/src/repositories/person.repository.ts b/server/src/repositories/person.repository.ts index 7b12b1f18..ba8844a8b 100644 --- a/server/src/repositories/person.repository.ts +++ b/server/src/repositories/person.repository.ts @@ -42,7 +42,11 @@ export interface PersonFilterOptions { name?: string; } -export interface PersonSearchOptions extends PersonFilterOptions { +export interface PersonCountOptions extends PersonFilterOptions { + partnerIds: string[]; +} + +export interface PersonSearchOptions extends PersonCountOptions { withHidden: boolean; closestFaceAssetId?: string; } @@ -436,9 +440,8 @@ export class PersonRepository { .execute(); } - @GenerateSql({ params: [{ take: 1, skip: 0 }, DummyValue.UUID] }) - async getAllForUser(pagination: PaginationOptions, userId: string, options?: PersonSearchOptions) { - const items = await this.db + private getVisiblePeopleQuery(userId: string, partnerIds: string[]) { + return this.db .with('similarity_threshold', (db) => db.selectNoFrom(sql`set_config('pg_trgm.word_similarity_threshold', '0.5', true)`.as('thresh')), ) @@ -446,33 +449,23 @@ export class PersonRepository { .innerJoin('person as owned', (join) => join.onRef('owned.personGroupId', '=', 'person_group.id').on('owned.ownerId', '=', userId), ) - .leftJoin('asset_face', (join) => + .innerJoin('asset_face', (join) => join .onRef('asset_face.personGroupId', '=', 'person_group.id') .on('asset_face.deletedAt', 'is', null) .on('asset_face.isVisible', 'is', true), ) - .leftJoin('asset', (join) => + .innerJoin('asset', (join) => join .onRef('asset.id', '=', 'asset_face.assetId') - .on('asset.ownerId', '=', userId) .on('asset.visibility', '=', sql.lit(AssetVisibility.Timeline)) - .on('asset.deletedAt', 'is', null), + .on('asset.deletedAt', 'is', null) + .on((eb) => eb.or([eb('asset.ownerId', '=', anyUuid([userId, ...partnerIds])), inSharedAlbum(eb, userId)])), ) - .select(withOwnedPerson(userId)) - .select(withOtherPeople(userId)) .groupBy(['person_group.id', 'owned.ownerId', 'owned.personGroupId']) .having((eb) => eb.or([ - // shared people are always included - eb.exists( - eb - .selectFrom('person_user') - .select('person_user.sharedWithId') - .whereRef('person_user.personGroupId', '=', 'person_group.id') - .where('person_user.sharedWithId', '=', userId), - ), - eb.and([eb(faceCount, '>', 0), eb('owned.name', '!=', '')]), + eb('owned.name', '!=', ''), eb( faceCount, '>=', @@ -485,10 +478,19 @@ export class PersonRepository { )::int `, ), ]), - ) + ); + } + + @GenerateSql({ + params: [{ take: 1, skip: 0 }, DummyValue.UUID, { withHidden: false, partnerIds: [DummyValue.UUID] }], + }) + async getAllForUser(pagination: PaginationOptions, userId: string, options: PersonSearchOptions) { + const items = await this.getVisiblePeopleQuery(userId, options.partnerIds) + .select(withOwnedPerson(userId)) + .select(withOtherPeople(userId)) .orderBy('owned.isHidden', 'asc') .orderBy('owned.isFavorite', 'desc') - .$if(!!options?.closestFaceAssetId, (qb) => + .$if(!!options.closestFaceAssetId, (qb) => qb.orderBy((eb) => eb( (eb) => @@ -501,11 +503,11 @@ export class PersonRepository { eb .selectFrom('face_search') .select('face_search.embedding') - .where('face_search.faceId', '=', options!.closestFaceAssetId!), + .where('face_search.faceId', '=', options.closestFaceAssetId!), ), ), ) - .$if(!options?.closestFaceAssetId, (qb) => + .$if(!options.closestFaceAssetId, (qb) => qb .orderBy(sql`NULLIF("owned"."name", '') is null`, 'asc') .orderBy(faceCount, 'desc') @@ -513,9 +515,9 @@ export class PersonRepository { .orderBy('owned.createdAt'), ) // an explicit isHidden filter takes precedence over withHidden - .$if(!options?.withHidden && options?.isHidden === undefined, (qb) => qb.where('owned.isHidden', '=', false)) + .$if(!options.withHidden && options.isHidden === undefined, (qb) => qb.where('owned.isHidden', '=', false)) .where(withFilters(userId, options)) - .$if(!!options?.name, (qb) => qb.orderBy(sql`f_unaccent("owned"."name") <->>> f_unaccent(${options!.name!})`)) + .$if(!!options.name, (qb) => qb.orderBy(sql`f_unaccent("owned"."name") <->>> f_unaccent(${options.name!})`)) .offset(pagination.skip ?? 0) .limit(pagination.take + 1) .execute(); @@ -736,52 +738,19 @@ export class PersonRepository { assets: result ? Number(result.count) : 0, }; } - @GenerateSql({ params: [DummyValue.UUID] }) - getNumberOfPeople(userId: string, options?: PersonFilterOptions) { + @GenerateSql({ params: [DummyValue.UUID, { partnerIds: [DummyValue.UUID] }] }) + getNumberOfPeople(userId: string, options: PersonCountOptions) { const zero = sql.lit(0); return this.db - .with('similarity_threshold', (db) => - db.selectNoFrom(sql`set_config('pg_trgm.word_similarity_threshold', '0.5', true)`.as('thresh')), + .selectFrom( + this.getVisiblePeopleQuery(userId, options.partnerIds) + .select('owned.isHidden') + .where(withFilters(userId, options)) + .as('people'), ) - .selectFrom(['similarity_threshold', 'person_group']) - .leftJoin('person as owned', (join) => - join.onRef('owned.personGroupId', '=', 'person_group.id').on('owned.ownerId', '=', userId), - ) - .where((eb) => - eb.or([ - eb.and([ - eb('owned.ownerId', 'is not', null), - eb.exists((eb) => - eb - .selectFrom('asset_face') - .whereRef('asset_face.personGroupId', '=', 'person_group.id') - .where('asset_face.deletedAt', 'is', null) - .where('asset_face.isVisible', '=', true) - .where((eb) => - eb.exists((eb) => - eb - .selectFrom('asset') - .whereRef('asset.id', '=', 'asset_face.assetId') - .where('asset.ownerId', '=', userId) - .where('asset.visibility', '=', sql.lit(AssetVisibility.Timeline)) - .where('asset.deletedAt', 'is', null), - ), - ), - ), - ]), - eb.exists((eb) => - eb - .selectFrom('person_user') - .select('person_user.sharedById') - .whereRef('person_user.personGroupId', '=', 'person_group.id') - .where('person_user.sharedWithId', '=', userId), - ), - ]), - ) - .where(withFilters(userId, options)) .select((eb) => eb.fn.coalesce(eb.fn.countAll(), zero).as('total')) .select((eb) => - eb.fn.coalesce(eb.fn.countAll().filterWhere('owned.isHidden', '=', true), zero).as('hidden'), + eb.fn.coalesce(eb.fn.countAll().filterWhere('people.isHidden', '=', true), zero).as('hidden'), ) .executeTakeFirstOrThrow(); } diff --git a/server/src/services/person.service.spec.ts b/server/src/services/person.service.spec.ts index ae8d35f5d..4584823d4 100644 --- a/server/src/services/person.service.spec.ts +++ b/server/src/services/person.service.spec.ts @@ -51,6 +51,7 @@ describe(PersonService.name, () => { hasNextPage: false, }); mocks.person.getNumberOfPeople.mockResolvedValue({ total: 2, hidden: 1 }); + mocks.partner.getAll.mockResolvedValue([]); await expect(sut.getAll(auth, { withHidden: true, page: 1, size: 10 })).resolves.toEqual({ hasNextPage: false, total: 2, @@ -65,6 +66,7 @@ describe(PersonService.name, () => { }); expect(mocks.person.getAllForUser).toHaveBeenCalledWith({ skip: 0, take: 10 }, auth.user.id, { withHidden: true, + partnerIds: [], }); }); @@ -77,6 +79,7 @@ describe(PersonService.name, () => { hasNextPage: false, }); mocks.person.getNumberOfPeople.mockResolvedValue({ total: 2, hidden: 1 }); + mocks.partner.getAll.mockResolvedValue([]); await expect(sut.getAll(auth, { withHidden: false, page: 1, size: 10 })).resolves.toEqual({ hasNextPage: false, total: 2, @@ -91,6 +94,7 @@ describe(PersonService.name, () => { }); expect(mocks.person.getAllForUser).toHaveBeenCalledWith({ skip: 0, take: 10 }, auth.user.id, { withHidden: false, + partnerIds: [], }); }); }); diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts index e5ea0cce5..52908b6d2 100644 --- a/server/src/services/person.service.ts +++ b/server/src/services/person.service.ts @@ -77,12 +77,18 @@ export class PersonService extends BaseService { } closestFaceAssetId = person.faceAssetId; } + const partnerIds = await getMyPartnerIds({ + userId: auth.user.id, + repository: this.partnerRepository, + timelineEnabled: true, + }); const { items, hasNextPage } = await this.personRepository.getAllForUser(pagination, auth.user.id, { withHidden, + partnerIds, closestFaceAssetId, ...filters, }); - const { total, hidden } = await this.personRepository.getNumberOfPeople(auth.user.id, filters); + const { total, hidden } = await this.personRepository.getNumberOfPeople(auth.user.id, { partnerIds, ...filters }); return { people: items.map((person) => mapPerson(person)), diff --git a/server/test/medium/specs/repositories/person.repository.spec.ts b/server/test/medium/specs/repositories/person.repository.spec.ts index 1fc4e12f0..b91e30ebf 100644 --- a/server/test/medium/specs/repositories/person.repository.spec.ts +++ b/server/test/medium/specs/repositories/person.repository.spec.ts @@ -3,7 +3,7 @@ import { PersonUserRole } from 'src/dtos/person.dto.js'; import { AssetFileType } from 'src/enum.js'; import { LoggingRepository } from 'src/repositories/logging.repository.js'; import { PersonUserRepository } from 'src/repositories/person-user.repository.js'; -import { PersonRepository } from 'src/repositories/person.repository.js'; +import { PersonRepository, type PersonSearchOptions } from 'src/repositories/person.repository.js'; import { DB } from 'src/schema/index.js'; import { BaseService } from 'src/services/base.service.js'; import { MediumTestContext, newMediumService } from 'test/medium.factory.js'; @@ -20,8 +20,12 @@ const setup = (db?: Kysely) => { return { ctx, sut: ctx.get(PersonRepository) }; }; -const listFor = async (sut: PersonRepository, userId: string, options?: { withHidden: boolean }) => { - const { items } = await sut.getAllForUser({ take: 100, skip: 0 }, userId, options); +const listFor = async (sut: PersonRepository, userId: string, options?: Partial) => { + const { items } = await sut.getAllForUser({ take: 100, skip: 0 }, userId, { + withHidden: false, + partnerIds: [], + ...options, + }); return items; }; @@ -33,8 +37,14 @@ const newNamedPerson = async (ctx: MediumTestContext, ownerId: string, name: str return person; }; -const otherPeopleOf = async (sut: PersonRepository, ctx: MediumTestContext, userId: string, groupId: string) => { - const listItems = await listFor(sut, userId); +const otherPeopleOf = async ( + sut: PersonRepository, + ctx: MediumTestContext, + userId: string, + groupId: string, + partnerIds: string[], +) => { + const listItems = await listFor(sut, userId, { partnerIds }); const listed = listItems.find((item) => item.personGroupId === groupId); const forUser = await sut.getForUser({ userId, personGroupId: groupId }); const byGroupId = await sut.getByGroupId({ ownerId: userId, personGroupId: groupId }); @@ -171,7 +181,7 @@ describe(PersonRepository.name, () => { ]); const expected = [{ sharedById: owner.id, role: PersonUserRole.Write, name: 'Alice', birthDate: null }]; - const results = await otherPeopleOf(sut, ctx, recipient.id, person.personGroupId); + const results = await otherPeopleOf(sut, ctx, recipient.id, person.personGroupId, [owner.id]); expect(results.getAllForUser).toEqual(expected); expect(results.getForUser).toEqual(expected); @@ -203,7 +213,7 @@ describe(PersonRepository.name, () => { ]); const expected = [{ sharedById: owner.id, role: PersonUserRole.Read, name: 'Alice', birthDate: null }]; - const results = await otherPeopleOf(sut, ctx, first.id, person.personGroupId); + const results = await otherPeopleOf(sut, ctx, first.id, person.personGroupId, [owner.id]); expect(results.getAllForUser).toEqual(expected); expect(results.getForUser).toEqual(expected); @@ -220,7 +230,7 @@ describe(PersonRepository.name, () => { .values({ ownerId: other.id, name: 'Alice', personGroupId: person.personGroupId }) .execute(); - const results = await otherPeopleOf(sut, ctx, other.id, person.personGroupId); + const results = await otherPeopleOf(sut, ctx, other.id, person.personGroupId, [owner.id]); expect(results.getForUser).toEqual([]); expect(results.getByGroupId).toEqual([]); @@ -464,7 +474,7 @@ describe(PersonRepository.name, () => { }, ]); - await expect(listFor(sut, sharedWith.id)).resolves.toEqual([ + await expect(listFor(sut, sharedWith.id, { partnerIds: [owner.id] })).resolves.toEqual([ expect.objectContaining({ ownerId: sharedWith.id, personGroupId: person.personGroupId, @@ -475,10 +485,9 @@ describe(PersonRepository.name, () => { ]); }); - it('should return a shared person even when it has no faces of its own', async () => { + it('should not return a shared person without any visible faces', async () => { const { ctx, sut } = setup(await getKyselyDB()); const [{ user: owner }, { user: sharedWith }] = [await ctx.newUser(), await ctx.newUser()]; - // no faces at all, so only the "shared people are always included" rule can let it through const { result: person } = await ctx.newPerson({ ownerId: owner.id, name: 'Alice' }); await ctx.get(PersonUserRepository).createAll([ @@ -490,9 +499,72 @@ describe(PersonRepository.name, () => { }, ]); - await expect(listFor(sut, sharedWith.id)).resolves.toEqual([ - expect.objectContaining({ ownerId: sharedWith.id, personGroupId: person.personGroupId }), + await expect(listFor(sut, sharedWith.id, { partnerIds: [owner.id] })).resolves.toEqual([]); + }); + + it('should not return a shared person whose faces are only on assets the user cannot see', async () => { + const { ctx, sut } = setup(await getKyselyDB()); + const [{ user: owner }, { user: sharedWith }] = [await ctx.newUser(), await ctx.newUser()]; + const person = await newNamedPerson(ctx, owner.id, 'Alice'); + + await ctx.get(PersonUserRepository).createAll([ + { + personGroupId: person.personGroupId, + sharedById: owner.id, + sharedWithId: sharedWith.id, + role: PersonUserRole.Read, + }, ]); + + await expect(listFor(sut, sharedWith.id)).resolves.toEqual([]); + }); + + it('should count faces on partner assets', async () => { + const { ctx, sut } = setup(await getKyselyDB()); + const [{ user: owner }, { user: partner }] = [await ctx.newUser(), await ctx.newUser()]; + const { result: person } = await ctx.newPerson({ ownerId: owner.id, name: '' }); + for (let i = 0; i < 3; i++) { + const { asset } = await ctx.newAsset({ ownerId: partner.id }); + await ctx.newAssetFace({ assetId: asset.id, personGroupId: person.personGroupId }); + } + + await expect(listFor(sut, owner.id)).resolves.toEqual([]); + await expect(listFor(sut, owner.id, { partnerIds: [partner.id] })).resolves.toEqual([ + expect.objectContaining({ ownerId: owner.id, personGroupId: person.personGroupId }), + ]); + }); + + it('should count people with the same rules as the list', async () => { + const { ctx, sut } = setup(await getKyselyDB()); + const [{ user: owner }, { user: partner }] = [await ctx.newUser(), await ctx.newUser()]; + const named = await newNamedPerson(ctx, owner.id, 'Alice'); + await ctx.database + .updateTable('person') + .set({ isHidden: true }) + .where('personGroupId', '=', named.personGroupId) + .execute(); + await ctx.newPerson({ ownerId: owner.id, name: '' }); + const { result: onPartnerAssets } = await ctx.newPerson({ ownerId: owner.id, name: '' }); + for (let i = 0; i < 3; i++) { + const { asset } = await ctx.newAsset({ ownerId: partner.id }); + await ctx.newAssetFace({ assetId: asset.id, personGroupId: onPartnerAssets.personGroupId }); + } + + await expect(sut.getNumberOfPeople(owner.id, { partnerIds: [] })).resolves.toEqual({ total: 1, hidden: 1 }); + await expect(sut.getNumberOfPeople(owner.id, { partnerIds: [partner.id] })).resolves.toEqual({ + total: 2, + hidden: 1, + }); + }); + + it('should require unnamed people to meet the minimum face count', async () => { + const { ctx, sut } = setup(await getKyselyDB()); + const { user } = await ctx.newUser(); + const { result: person } = await ctx.newPerson({ ownerId: user.id, name: '' }); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + await ctx.newAssetFace({ assetId: asset.id, personGroupId: person.personGroupId }); + + await expect(listFor(sut, user.id)).resolves.toEqual([]); }); it('should stop returning a shared person once the user deletes their copy', async () => { @@ -508,11 +580,11 @@ describe(PersonRepository.name, () => { role: PersonUserRole.Read, }, ]); - await expect(listFor(sut, sharedWith.id)).resolves.toHaveLength(1); + await expect(listFor(sut, sharedWith.id, { partnerIds: [owner.id] })).resolves.toHaveLength(1); await sut.delete([person.personGroupId], sharedWith.id); - await expect(listFor(sut, sharedWith.id)).resolves.toEqual([]); + await expect(listFor(sut, sharedWith.id, { partnerIds: [owner.id] })).resolves.toEqual([]); // the owner still has theirs await expect(listFor(sut, owner.id)).resolves.toHaveLength(1); }); diff --git a/server/test/medium/specs/services/person.service.spec.ts b/server/test/medium/specs/services/person.service.spec.ts index 2e2377981..7b124e091 100644 --- a/server/test/medium/specs/services/person.service.spec.ts +++ b/server/test/medium/specs/services/person.service.spec.ts @@ -14,6 +14,7 @@ import { DatabaseRepository } from 'src/repositories/database.repository.js'; import { JobRepository } from 'src/repositories/job.repository.js'; import { LoggingRepository } from 'src/repositories/logging.repository.js'; import { MachineLearningRepository } from 'src/repositories/machine-learning.repository.js'; +import { PartnerRepository } from 'src/repositories/partner.repository.js'; import { PersonUserRepository } from 'src/repositories/person-user.repository.js'; import { PersonRepository } from 'src/repositories/person.repository.js'; import { StorageRepository } from 'src/repositories/storage.repository.js'; @@ -42,6 +43,7 @@ const setup = (db?: Kysely) => { SystemMetadataRepository, UserRepository, ClusterGroupRepository, + PartnerRepository, ], mock: [JobRepository, LoggingRepository, StorageRepository, MachineLearningRepository], }); @@ -124,6 +126,7 @@ describe(PersonService.name, () => { const { person: sharedWithMe } = await ctx.newPerson({ ownerId: user3.id, name: 'Shared with me' }); await ctx.newAssetFace({ assetId: asset.id, personGroupId: sharedByMe.personGroupId }); await ctx.newAssetFace({ assetId: asset.id, personGroupId: notShared.personGroupId }); + await ctx.newAssetFace({ assetId: asset.id, personGroupId: sharedWithMe.personGroupId }); await ctx.newPersonUser({ personGroupId: sharedByMe.personGroupId, sharedById: user1.id,