Files
Immich/server/test/medium/specs/repositories/person.repository.spec.ts
T

246 lines
9.6 KiB
TypeScript

import { Kysely } from 'kysely';
import { AssetFileType } from 'src/enum.js';
import { LoggingRepository } from 'src/repositories/logging.repository.js';
import { PersonRepository } from 'src/repositories/person.repository.js';
import { DB } from 'src/schema/index.js';
import { BaseService } from 'src/services/base.service.js';
import { newMediumService } from 'test/medium.factory.js';
import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
const setup = (db?: Kysely<DB>) => {
const { ctx } = newMediumService(BaseService, {
database: db || defaultDatabase,
real: [],
mock: [LoggingRepository],
});
return { ctx, sut: ctx.get(PersonRepository) };
};
beforeAll(async () => {
defaultDatabase = await getKyselyDB();
});
describe(PersonRepository.name, () => {
describe('createAll', () => {
it('should create people in the groups they were given', async () => {
const { ctx, sut } = setup();
const [{ user: user1 }, { user: user2 }] = [await ctx.newUser(), await ctx.newUser()];
const [group1, group2] = await sut.createGroups([
{ clusterGroupId: user1.clusterGroupId },
{ clusterGroupId: user1.clusterGroupId },
]);
const group3 = await sut.createGroup(user2.id);
const people = await sut.createAll([
{ ownerId: user1.id, name: 'Alice', personGroupId: group1.id },
{ ownerId: user1.id, name: 'Bob', personGroupId: group2.id },
{ ownerId: user2.id, name: 'Carol', personGroupId: group3.id },
]);
expect(people.map(({ personGroupId }) => personGroupId)).toEqual([group1.id, group2.id, group3.id]);
const groups = await ctx.database
.selectFrom('person')
.innerJoin('person_group', 'person_group.id', 'person.personGroupId')
.innerJoin('user', 'user.id', 'person.ownerId')
.select(['person.name', 'person_group.clusterGroupId', 'user.clusterGroupId as ownerClusterGroupId'])
.where(
'person.personGroupId',
'in',
people.map(({ personGroupId }) => personGroupId),
)
.execute();
expect(groups).toHaveLength(3);
for (const group of groups) {
expect(group.clusterGroupId).toBe(group.ownerClusterGroupId);
}
});
});
describe('createGroup', () => {
it('should create a group in the owner cluster group', async () => {
const { ctx, sut } = setup();
const { user } = await ctx.newUser();
const group = await sut.createGroup(user.id);
const owner = await ctx.database
.selectFrom('person_group')
.innerJoin('user', 'user.clusterGroupId', 'person_group.clusterGroupId')
.select('user.id')
.where('person_group.id', '=', group.id)
.executeTakeFirstOrThrow();
expect(owner.id).toBe(user.id);
});
it('should put people created with the same group into that group', async () => {
const { ctx, sut } = setup(await getKyselyDB());
const [{ user: user1 }, { user: user2 }] = [await ctx.newUser(), await ctx.newUser()];
const group = await sut.createGroup(user1.id);
const person1 = await sut.create({ ownerId: user1.id, name: 'Alice', personGroupId: group.id });
const person2 = await sut.create({ ownerId: user2.id, name: 'Alice', personGroupId: group.id });
expect(person1.personGroupId).toBe(group.id);
expect(person2.personGroupId).toBe(group.id);
const groups = await ctx.database.selectFrom('person_group').select('person_group.id').execute();
expect(groups.map(({ id }) => id)).toEqual([group.id]);
});
});
describe('getByGroupId', () => {
it('should not return a person owned by another user', async () => {
const { ctx, sut } = setup();
const [{ user: user1 }, { user: user2 }] = [await ctx.newUser(), await ctx.newUser()];
const group = await sut.createGroup(user1.id);
const person1 = await sut.create({ ownerId: user1.id, name: 'Alice', personGroupId: group.id });
const person2 = await ctx.database
.insertInto('person')
.values({ ownerId: user2.id, name: 'Alice', personGroupId: person1.personGroupId })
.returningAll()
.executeTakeFirstOrThrow();
await expect(sut.getByGroupId({ ownerId: user1.id, personGroupId: person1.personGroupId })).resolves.toEqual(
expect.objectContaining({ personGroupId: person1.personGroupId, ownerId: user1.id }),
);
await expect(sut.getByGroupId({ ownerId: user2.id, personGroupId: person1.personGroupId })).resolves.toEqual(
expect.objectContaining({ personGroupId: person2.personGroupId, ownerId: user2.id }),
);
});
it('should return nothing when the group belongs to another user', async () => {
const { ctx, sut } = setup();
const [{ user: user1 }, { user: user2 }] = [await ctx.newUser(), await ctx.newUser()];
const group = await sut.createGroup(user1.id);
const person = await sut.create({ ownerId: user1.id, name: 'Alice', personGroupId: group.id });
await expect(
sut.getByGroupId({ ownerId: user2.id, personGroupId: person.personGroupId }),
).resolves.toBeUndefined();
});
});
describe('deleteEmptyGroups', () => {
it('should delete groups that no longer have any people', async () => {
const { ctx, sut } = setup(await getKyselyDB());
const { user } = await ctx.newUser();
const [keptGroup, emptiedGroup] = await sut.createGroups([
{ clusterGroupId: user.clusterGroupId },
{ clusterGroupId: user.clusterGroupId },
]);
const kept = await sut.create({ ownerId: user.id, name: 'Alice', personGroupId: keptGroup.id });
const emptied = await sut.create({ ownerId: user.id, name: 'Bob', personGroupId: emptiedGroup.id });
await ctx.database
.deleteFrom('person')
.where('person.ownerId', '=', emptied.ownerId)
.where('person.personGroupId', '=', emptied.personGroupId)
.execute();
await expect(sut.deleteEmptyGroups()).resolves.toBe(1);
const groups = await ctx.database.selectFrom('person_group').select('person_group.id').execute();
expect(groups.map(({ id }) => id)).toEqual([kept.personGroupId]);
});
});
describe('deleteOrphanedClusterGroups', () => {
it('should delete cluster groups that no longer belong to a user, along with their people', async () => {
const { ctx, sut } = setup(await getKyselyDB());
const [{ user: kept }, { user: removed }] = [await ctx.newUser(), await ctx.newUser()];
const keptGroup = await sut.createGroup(kept.id);
const removedGroup = await sut.createGroup(removed.id);
const keptPerson = await sut.create({ ownerId: kept.id, name: 'Alice', personGroupId: keptGroup.id });
await sut.create({ ownerId: removed.id, name: 'Bob', personGroupId: removedGroup.id });
const { clusterGroupId } = await ctx.database
.selectFrom('user')
.select('user.clusterGroupId')
.where('user.id', '=', kept.id)
.executeTakeFirstOrThrow();
await ctx.database.deleteFrom('user').where('user.id', '=', removed.id).execute();
await expect(sut.deleteOrphanedClusterGroups()).resolves.toBe(1);
const clusterGroups = await ctx.database.selectFrom('cluster_group').select('cluster_group.id').execute();
expect(clusterGroups.map(({ id }) => id)).toEqual([clusterGroupId]);
const groups = await ctx.database.selectFrom('person_group').select('person_group.id').execute();
expect(groups.map(({ id }) => id)).toEqual([keptPerson.personGroupId]);
});
});
describe('getDataForThumbnailGenerationJob', () => {
it('should not return the edited preview path', async () => {
const { ctx, sut } = setup();
const { user } = await ctx.newUser();
const { asset } = await ctx.newAsset({ ownerId: user.id });
const { person } = await ctx.newPerson({ ownerId: user.id });
const { assetFace } = await ctx.newAssetFace({
assetId: asset.id,
personGroupId: person.personGroupId,
boundingBoxX1: 10,
boundingBoxY1: 10,
boundingBoxX2: 90,
boundingBoxY2: 90,
});
// there's a circular dependency between assetFace and person, so we need to update the person after creating the assetFace
await ctx.database
.updateTable('person')
.set({ faceAssetId: assetFace.id })
.where('ownerId', '=', person.ownerId)
.where('personGroupId', '=', person.personGroupId)
.execute();
await ctx.newAssetFile({
assetId: asset.id,
type: AssetFileType.Preview,
path: 'preview_edited.jpg',
isEdited: true,
});
await ctx.newAssetFile({
assetId: asset.id,
type: AssetFileType.Preview,
path: 'preview_unedited.jpg',
isEdited: false,
});
const result = await sut.getDataForThumbnailGenerationJob({
ownerId: person.ownerId,
personGroupId: person.personGroupId,
});
expect(result).toEqual(
expect.objectContaining({
previewPath: 'preview_unedited.jpg',
}),
);
});
});
describe('getForFeatureFaceUpdate', () => {
it('should ignore soft deleted faces', async () => {
const { ctx, sut } = setup();
const { user } = await ctx.newUser();
const { asset } = await ctx.newAsset({ ownerId: user.id });
const { person } = await ctx.newPerson({ ownerId: user.id });
await ctx.newAssetFace({ assetId: asset.id, deletedAt: new Date(), personGroupId: person.personGroupId });
await expect(
sut.getForFeatureFaceUpdate({ personGroupId: person.personGroupId, assetId: asset.id }),
).resolves.toEqual(undefined);
});
});
});