Files
immich/server/src/controllers/person.controller.spec.ts
T
Jason Rasmussen aec7c0d97b feat: auto-sync name changes to connected users (#31923)
* feat: auto-sync name changes to connected users

* fix: rename to faceId

* fix: pr feedback

* fix: mobile build
2026-09-29 18:12:25 -04:00

199 lines
7.6 KiB
TypeScript

import request from 'supertest';
import { PersonController } from 'src/controllers/person.controller.js';
import { PersonUserRole } from 'src/dtos/person.dto.js';
import { LoggingRepository } from 'src/repositories/logging.repository.js';
import { PersonService } from 'src/services/person.service.js';
import { errorDto } from 'test/medium/responses.js';
import { factory, newUuid } from 'test/small.factory.js';
import { ControllerContext, automock, controllerSetup, mockBaseService } from 'test/utils.js';
describe(PersonController.name, () => {
let ctx: ControllerContext;
const service = mockBaseService(PersonService);
beforeAll(async () => {
ctx = await controllerSetup(PersonController, [
{ provide: PersonService, useValue: service },
{ provide: LoggingRepository, useValue: automock(LoggingRepository, { strict: false }) },
]);
return () => ctx.close();
});
beforeEach(() => {
service.resetAllMocks();
ctx.reset();
});
describe('GET /people', () => {
it(`should require closestPersonId to be a uuid`, async () => {
const { status, body } = await request(ctx.getHttpServer())
.get(`/people`)
.query({ closestPersonId: 'invalid' })
.set('Authorization', `Bearer token`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.validationError([{ path: ['closestPersonId'], message: 'Invalid UUID' }]));
});
it(`should require closestAssetId to be a uuid`, async () => {
const { status, body } = await request(ctx.getHttpServer())
.get(`/people`)
.query({ closestAssetId: 'invalid' })
.set('Authorization', `Bearer token`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.validationError([{ path: ['closestAssetId'], message: 'Invalid UUID' }]));
});
});
describe('DELETE /people', () => {
it('should require uuids in the body', async () => {
const { status, body } = await request(ctx.getHttpServer())
.delete('/people')
.send({ ids: ['invalid'] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.validationError([{ path: ['ids', 0], message: 'Invalid UUID' }]));
});
it('should respond with 204', async () => {
const { status } = await request(ctx.getHttpServer())
.delete(`/people`)
.send({ ids: [factory.uuid()] });
expect(status).toBe(204);
expect(service.deleteAll).toHaveBeenCalled();
});
});
describe('PUT /people/:id', () => {
it('should require a valid uuid', async () => {
const { status, body } = await request(ctx.getHttpServer()).put(`/people/123`);
expect(status).toBe(400);
expect(body).toEqual(
errorDto.validationError([{ path: [], message: 'Invalid input: expected object, received undefined' }]),
);
});
it(`should not allow a null name`, async () => {
const { status, body } = await request(ctx.getHttpServer())
.post(`/people`)
.send({ name: null })
.set('Authorization', `Bearer token`);
expect(status).toBe(400);
expect(body).toEqual(
errorDto.validationError([{ path: ['name'], message: 'Invalid input: expected string, received null' }]),
);
});
it(`should require featureFaceAssetId to be a uuid`, async () => {
const { status, body } = await request(ctx.getHttpServer())
.put(`/people/${factory.uuid()}`)
.send({ featureFaceAssetId: 'invalid' })
.set('Authorization', `Bearer token`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.validationError([{ path: ['featureFaceAssetId'], message: 'Invalid UUID' }]));
});
it('should require at least one property to update', async () => {
const { status, body } = await request(ctx.getHttpServer())
.put(`/people/${factory.uuid()}`)
.send({ userId: factory.uuid() })
.set('Authorization', `Bearer token`);
expect(status).toBe(400);
expect(body).toEqual(
errorDto.validationError([
{
path: [],
message:
'At least one of the following fields is required: name, birthDate, isHidden, isFavorite, color, featureFaceAssetId',
},
]),
);
});
it(`should require isFavorite to be a boolean`, async () => {
const { status, body } = await request(ctx.getHttpServer())
.put(`/people/${factory.uuid()}`)
.send({ isFavorite: 'invalid' })
.set('Authorization', `Bearer token`);
expect(status).toBe(400);
expect(body).toEqual(
errorDto.validationError([
{ path: ['isFavorite'], message: 'Invalid input: expected boolean, received string' },
]),
);
});
it(`should require isHidden to be a boolean`, async () => {
const { status, body } = await request(ctx.getHttpServer())
.put(`/people/${factory.uuid()}`)
.send({ isHidden: 'invalid' })
.set('Authorization', `Bearer token`);
expect(status).toBe(400);
expect(body).toEqual(
errorDto.validationError([{ path: ['isHidden'], message: 'Invalid input: expected boolean, received string' }]),
);
});
it('should not accept an invalid birth date (false)', async () => {
const { status, body } = await request(ctx.getHttpServer())
.put(`/people/${factory.uuid()}`)
.send({ birthDate: false });
expect(status).toBe(400);
expect(body).toEqual(
errorDto.validationError([
{ path: ['birthDate'], message: 'Invalid input: expected string, received boolean' },
]),
);
});
it('should not accept an invalid birth date (number)', async () => {
const { status, body } = await request(ctx.getHttpServer())
.put(`/people/${factory.uuid()}`)
.send({ birthDate: 123_456 });
expect(status).toBe(400);
expect(body).toEqual(
errorDto.validationError([{ path: ['birthDate'], message: 'Invalid input: expected string, received number' }]),
);
});
it('should not accept a birth date in the future)', async () => {
const { status, body } = await request(ctx.getHttpServer())
.put(`/people/${factory.uuid()}`)
.send({ birthDate: '9999-01-01' });
expect(status).toBe(400);
expect(body).toEqual(
errorDto.validationError([{ path: ['birthDate'], message: 'Birth date cannot be in the future' }]),
);
});
});
describe('DELETE /people/:id', () => {
it('should require a valid uuid', async () => {
const { status, body } = await request(ctx.getHttpServer()).delete(`/people/invalid`).send({});
expect(status).toBe(400);
expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }]));
});
it('should respond with 204', async () => {
const { status } = await request(ctx.getHttpServer()).delete(`/people/${factory.uuid()}`).send({});
expect(status).toBe(204);
expect(service.delete).toHaveBeenCalled();
});
});
describe('PUT /people/users', () => {
it('should reject duplicate personIds as well as duplicate userIds', async () => {
const userId = newUuid();
const personId = newUuid();
const { status, body } = await request(ctx.getHttpServer())
.put('/people/users')
.send({ personIds: [personId, personId], sharedWithIds: [userId, userId], role: PersonUserRole.Write });
expect(status).toBe(400);
expect(body.errors).toEqual(
expect.arrayContaining([
expect.objectContaining({ path: ['personIds'], message: 'Items must be unique' }),
expect.objectContaining({ path: ['sharedWithIds'], message: 'Items must be unique' }),
]),
);
});
});
});