mirror of
https://github.com/immich-app/immich.git
synced 2026-09-30 13:23:21 +08:00
* feat: auto-sync name changes to connected users * fix: rename to faceId * fix: pr feedback * fix: mobile build
199 lines
7.6 KiB
TypeScript
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' }),
|
|
]),
|
|
);
|
|
});
|
|
});
|
|
});
|