mirror of
https://github.com/immich-app/immich.git
synced 2026-02-18 01:00:15 -05:00
760 lines
24 KiB
TypeScript
760 lines
24 KiB
TypeScript
import { Kysely } from 'kysely';
|
|
import { AssetEditAction, MirrorAxis } from 'src/dtos/editing.dto';
|
|
import { AssetFaceCreateDto } from 'src/dtos/person.dto';
|
|
import { AccessRepository } from 'src/repositories/access.repository';
|
|
import { AssetEditRepository } from 'src/repositories/asset-edit.repository';
|
|
import { AssetRepository } from 'src/repositories/asset.repository';
|
|
import { DatabaseRepository } from 'src/repositories/database.repository';
|
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
|
import { PersonRepository } from 'src/repositories/person.repository';
|
|
import { StorageRepository } from 'src/repositories/storage.repository';
|
|
import { DB } from 'src/schema';
|
|
import { PersonService } from 'src/services/person.service';
|
|
import { newMediumService } from 'test/medium.factory';
|
|
import { factory } from 'test/small.factory';
|
|
import { getKyselyDB } from 'test/utils';
|
|
|
|
let defaultDatabase: Kysely<DB>;
|
|
|
|
const setup = (db?: Kysely<DB>) => {
|
|
return newMediumService(PersonService, {
|
|
database: db || defaultDatabase,
|
|
real: [AccessRepository, DatabaseRepository, PersonRepository, AssetRepository, AssetEditRepository],
|
|
mock: [LoggingRepository, StorageRepository],
|
|
});
|
|
};
|
|
|
|
beforeAll(async () => {
|
|
defaultDatabase = await getKyselyDB();
|
|
});
|
|
|
|
describe(PersonService.name, () => {
|
|
describe('delete', () => {
|
|
it('should throw an error when there is no access', async () => {
|
|
const { sut } = setup();
|
|
const auth = factory.auth();
|
|
const personId = factory.uuid();
|
|
await expect(sut.delete(auth, personId)).rejects.toThrow('Not found or no person.delete access');
|
|
});
|
|
|
|
it('should delete the person', async () => {
|
|
const { sut, ctx } = setup();
|
|
const personRepo = ctx.get(PersonRepository);
|
|
const storageMock = ctx.getMock(StorageRepository);
|
|
const { user } = await ctx.newUser();
|
|
const { person } = await ctx.newPerson({ ownerId: user.id });
|
|
const auth = factory.auth({ user });
|
|
storageMock.unlink.mockResolvedValue();
|
|
|
|
await expect(personRepo.getById(person.id)).resolves.toEqual(expect.objectContaining({ id: person.id }));
|
|
await expect(sut.delete(auth, person.id)).resolves.toBeUndefined();
|
|
await expect(personRepo.getById(person.id)).resolves.toBeUndefined();
|
|
|
|
expect(storageMock.unlink).toHaveBeenCalledWith(person.thumbnailPath);
|
|
});
|
|
});
|
|
|
|
describe('deleteAll', () => {
|
|
it('should throw an error when there is no access', async () => {
|
|
const { sut } = setup();
|
|
const auth = factory.auth();
|
|
const personId = factory.uuid();
|
|
await expect(sut.deleteAll(auth, { ids: [personId] })).rejects.toThrow('Not found or no person.delete access');
|
|
});
|
|
|
|
it('should delete the person', async () => {
|
|
const { sut, ctx } = setup();
|
|
const storageMock = ctx.getMock(StorageRepository);
|
|
const personRepo = ctx.get(PersonRepository);
|
|
const { user } = await ctx.newUser();
|
|
const { person: person1 } = await ctx.newPerson({ ownerId: user.id });
|
|
const { person: person2 } = await ctx.newPerson({ ownerId: user.id });
|
|
const auth = factory.auth({ user });
|
|
storageMock.unlink.mockResolvedValue();
|
|
|
|
await expect(sut.deleteAll(auth, { ids: [person1.id, person2.id] })).resolves.toBeUndefined();
|
|
await expect(personRepo.getById(person1.id)).resolves.toBeUndefined();
|
|
await expect(personRepo.getById(person2.id)).resolves.toBeUndefined();
|
|
|
|
expect(storageMock.unlink).toHaveBeenCalledTimes(2);
|
|
expect(storageMock.unlink).toHaveBeenCalledWith(person1.thumbnailPath);
|
|
expect(storageMock.unlink).toHaveBeenCalledWith(person2.thumbnailPath);
|
|
});
|
|
});
|
|
|
|
describe('createFace', () => {
|
|
it('should store and retrieve the face as-is when there are no edits', async () => {
|
|
const { sut, ctx } = setup();
|
|
const { user } = await ctx.newUser();
|
|
const { person } = await ctx.newPerson({ ownerId: user.id });
|
|
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 200, height: 200 });
|
|
await ctx.newExif({ assetId: asset.id, exifImageHeight: 200, exifImageWidth: 200 });
|
|
|
|
const auth = factory.auth({ user });
|
|
|
|
const dto: AssetFaceCreateDto = {
|
|
imageWidth: 200,
|
|
imageHeight: 200,
|
|
x: 50,
|
|
y: 50,
|
|
width: 150,
|
|
height: 150,
|
|
personId: person.id,
|
|
assetId: asset.id,
|
|
};
|
|
|
|
await sut.createFace(auth, dto);
|
|
|
|
// retrieve an asset's faces
|
|
const faces = sut.getFacesById(auth, { id: asset.id });
|
|
|
|
await expect(faces).resolves.toHaveLength(1);
|
|
await expect(faces).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: 50,
|
|
boundingBoxY1: 50,
|
|
boundingBoxX2: 200,
|
|
boundingBoxY2: 200,
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('should properly transform the coordinates when the asset is edited (Crop)', async () => {
|
|
const { sut, ctx } = setup();
|
|
const { user } = await ctx.newUser();
|
|
const { person } = await ctx.newPerson({ ownerId: user.id });
|
|
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 150, height: 200 });
|
|
await ctx.newExif({ assetId: asset.id, exifImageHeight: 200, exifImageWidth: 200 });
|
|
|
|
await ctx.newEdits(asset.id, {
|
|
edits: [
|
|
{
|
|
action: AssetEditAction.Crop,
|
|
parameters: {
|
|
x: 50,
|
|
y: 50,
|
|
width: 150,
|
|
height: 200,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const auth = factory.auth({ user });
|
|
|
|
const dto: AssetFaceCreateDto = {
|
|
imageWidth: 150,
|
|
imageHeight: 200,
|
|
x: 0,
|
|
y: 0,
|
|
width: 100,
|
|
height: 100,
|
|
personId: person.id,
|
|
assetId: asset.id,
|
|
};
|
|
|
|
await sut.createFace(auth, dto);
|
|
|
|
// retrieve an asset's faces
|
|
const faces = sut.getFacesById(auth, { id: asset.id });
|
|
|
|
await expect(faces).resolves.toHaveLength(1);
|
|
await expect(faces).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: 0,
|
|
boundingBoxY1: 0,
|
|
boundingBoxX2: 100,
|
|
boundingBoxY2: 100,
|
|
}),
|
|
]),
|
|
);
|
|
|
|
// remove edits and verify the stored coordinates map to the original image
|
|
await ctx.newEdits(asset.id, { edits: [] });
|
|
|
|
const facesAfterRemovingEdits = sut.getFacesById(auth, { id: asset.id });
|
|
|
|
await expect(facesAfterRemovingEdits).resolves.toHaveLength(1);
|
|
await expect(facesAfterRemovingEdits).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: 50,
|
|
boundingBoxY1: 50,
|
|
boundingBoxX2: 150,
|
|
boundingBoxY2: 150,
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('should properly transform the coordinates when the asset is edited (Rotate 90)', async () => {
|
|
const { sut, ctx } = setup();
|
|
const { user } = await ctx.newUser();
|
|
const { person } = await ctx.newPerson({ ownerId: user.id });
|
|
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 100, height: 200 });
|
|
await ctx.newExif({ assetId: asset.id, exifImageWidth: 200, exifImageHeight: 100 });
|
|
|
|
await ctx.newEdits(asset.id, {
|
|
edits: [
|
|
{
|
|
action: AssetEditAction.Rotate,
|
|
parameters: {
|
|
angle: 90,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const auth = factory.auth({ user });
|
|
|
|
const dto: AssetFaceCreateDto = {
|
|
imageWidth: 100,
|
|
imageHeight: 200,
|
|
x: 25,
|
|
y: 50,
|
|
width: 10,
|
|
height: 10,
|
|
personId: person.id,
|
|
assetId: asset.id,
|
|
};
|
|
|
|
await sut.createFace(auth, dto);
|
|
|
|
const faces = sut.getFacesById(auth, { id: asset.id });
|
|
await expect(faces).resolves.toHaveLength(1);
|
|
await expect(faces).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: expect.closeTo(25, 1),
|
|
boundingBoxY1: expect.closeTo(50, 1),
|
|
boundingBoxX2: expect.closeTo(35, 1),
|
|
boundingBoxY2: expect.closeTo(60, 1),
|
|
}),
|
|
]),
|
|
);
|
|
|
|
// remove edits and verify the stored coordinates map to the original image
|
|
await ctx.newEdits(asset.id, { edits: [] });
|
|
const facesAfterRemovingEdits = sut.getFacesById(auth, { id: asset.id });
|
|
|
|
await expect(facesAfterRemovingEdits).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: 50,
|
|
boundingBoxY1: 65,
|
|
boundingBoxX2: 60,
|
|
boundingBoxY2: 75,
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('should properly transform the coordinates when the asset is edited (Mirror Horizontal)', async () => {
|
|
const { sut, ctx } = setup();
|
|
const { user } = await ctx.newUser();
|
|
const { person } = await ctx.newPerson({ ownerId: user.id });
|
|
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 200, height: 100 });
|
|
await ctx.newExif({ assetId: asset.id, exifImageHeight: 100, exifImageWidth: 200 });
|
|
|
|
await ctx.newEdits(asset.id, {
|
|
edits: [
|
|
{
|
|
action: AssetEditAction.Mirror,
|
|
parameters: {
|
|
axis: MirrorAxis.Horizontal,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const auth = factory.auth({ user });
|
|
|
|
const dto: AssetFaceCreateDto = {
|
|
imageWidth: 200,
|
|
imageHeight: 100,
|
|
x: 50,
|
|
y: 25,
|
|
width: 100,
|
|
height: 50,
|
|
personId: person.id,
|
|
assetId: asset.id,
|
|
};
|
|
|
|
await sut.createFace(auth, dto);
|
|
|
|
const faces = sut.getFacesById(auth, { id: asset.id });
|
|
await expect(faces).resolves.toHaveLength(1);
|
|
await expect(faces).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: 50,
|
|
boundingBoxY1: 25,
|
|
boundingBoxX2: 150,
|
|
boundingBoxY2: 75,
|
|
}),
|
|
]),
|
|
);
|
|
|
|
// remove edits and verify the stored coordinates map to the original image
|
|
await ctx.newEdits(asset.id, { edits: [] });
|
|
const facesAfterRemovingEdits = sut.getFacesById(auth, { id: asset.id });
|
|
|
|
await expect(facesAfterRemovingEdits).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: 50,
|
|
boundingBoxY1: 25,
|
|
boundingBoxX2: 150,
|
|
boundingBoxY2: 75,
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('should properly transform the coordinates when the asset is edited (Crop + Rotate)', async () => {
|
|
const { sut, ctx } = setup();
|
|
const { user } = await ctx.newUser();
|
|
const { person } = await ctx.newPerson({ ownerId: user.id });
|
|
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 200, height: 150 });
|
|
await ctx.newExif({ assetId: asset.id, exifImageHeight: 200, exifImageWidth: 200 });
|
|
|
|
await ctx.newEdits(asset.id, {
|
|
edits: [
|
|
{
|
|
action: AssetEditAction.Crop,
|
|
parameters: {
|
|
x: 50,
|
|
y: 0,
|
|
width: 150,
|
|
height: 200,
|
|
},
|
|
},
|
|
{
|
|
action: AssetEditAction.Rotate,
|
|
parameters: {
|
|
angle: 90,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const auth = factory.auth({ user });
|
|
|
|
const dto: AssetFaceCreateDto = {
|
|
imageWidth: 200,
|
|
imageHeight: 150,
|
|
x: 50,
|
|
y: 25,
|
|
width: 10,
|
|
height: 20,
|
|
personId: person.id,
|
|
assetId: asset.id,
|
|
};
|
|
|
|
await sut.createFace(auth, dto);
|
|
|
|
const faces = sut.getFacesById(auth, { id: asset.id });
|
|
await expect(faces).resolves.toHaveLength(1);
|
|
await expect(faces).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: expect.closeTo(50, 1),
|
|
boundingBoxY1: expect.closeTo(25, 1),
|
|
boundingBoxX2: expect.closeTo(60, 1),
|
|
boundingBoxY2: expect.closeTo(45, 1),
|
|
}),
|
|
]),
|
|
);
|
|
|
|
// remove edits and verify the stored coordinates map to the original image
|
|
await ctx.newEdits(asset.id, { edits: [] });
|
|
const facesAfterRemovingEdits = sut.getFacesById(auth, { id: asset.id });
|
|
|
|
await expect(facesAfterRemovingEdits).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: 75,
|
|
boundingBoxY1: 140,
|
|
boundingBoxX2: 95,
|
|
boundingBoxY2: 150,
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('should properly transform the coordinates when the asset is edited (Crop + Mirror)', async () => {
|
|
const { sut, ctx } = setup();
|
|
const { user } = await ctx.newUser();
|
|
const { person } = await ctx.newPerson({ ownerId: user.id });
|
|
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 150, height: 100 });
|
|
await ctx.newExif({ assetId: asset.id, exifImageHeight: 100, exifImageWidth: 200 });
|
|
|
|
await ctx.newEdits(asset.id, {
|
|
edits: [
|
|
{
|
|
action: AssetEditAction.Crop,
|
|
parameters: {
|
|
x: 50,
|
|
y: 0,
|
|
width: 150,
|
|
height: 100,
|
|
},
|
|
},
|
|
{
|
|
action: AssetEditAction.Mirror,
|
|
parameters: {
|
|
axis: MirrorAxis.Horizontal,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const auth = factory.auth({ user });
|
|
|
|
const dto: AssetFaceCreateDto = {
|
|
imageWidth: 150,
|
|
imageHeight: 100,
|
|
x: 25,
|
|
y: 25,
|
|
width: 75,
|
|
height: 50,
|
|
personId: person.id,
|
|
assetId: asset.id,
|
|
};
|
|
|
|
await sut.createFace(auth, dto);
|
|
|
|
const faces = sut.getFacesById(auth, { id: asset.id });
|
|
await expect(faces).resolves.toHaveLength(1);
|
|
await expect(faces).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: 25,
|
|
boundingBoxY1: 25,
|
|
boundingBoxX2: 100,
|
|
boundingBoxY2: 75,
|
|
}),
|
|
]),
|
|
);
|
|
|
|
// remove edits and verify the stored coordinates map to the original image
|
|
await ctx.newEdits(asset.id, { edits: [] });
|
|
const facesAfterRemovingEdits = sut.getFacesById(auth, { id: asset.id });
|
|
|
|
await expect(facesAfterRemovingEdits).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: 100,
|
|
boundingBoxY1: 25,
|
|
boundingBoxX2: 175,
|
|
boundingBoxY2: 75,
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('should properly transform the coordinates when the asset is edited (Rotate + Mirror)', async () => {
|
|
const { sut, ctx } = setup();
|
|
const { user } = await ctx.newUser();
|
|
const { person } = await ctx.newPerson({ ownerId: user.id });
|
|
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 200, height: 150 });
|
|
await ctx.newExif({ assetId: asset.id, exifImageHeight: 200, exifImageWidth: 150 });
|
|
|
|
await ctx.newEdits(asset.id, {
|
|
edits: [
|
|
{
|
|
action: AssetEditAction.Rotate,
|
|
parameters: {
|
|
angle: 90,
|
|
},
|
|
},
|
|
{
|
|
action: AssetEditAction.Mirror,
|
|
parameters: {
|
|
axis: MirrorAxis.Horizontal,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const auth = factory.auth({ user });
|
|
|
|
const dto: AssetFaceCreateDto = {
|
|
imageWidth: 200,
|
|
imageHeight: 150,
|
|
x: 50,
|
|
y: 25,
|
|
width: 15,
|
|
height: 20,
|
|
personId: person.id,
|
|
assetId: asset.id,
|
|
};
|
|
|
|
await sut.createFace(auth, dto);
|
|
|
|
const faces = sut.getFacesById(auth, { id: asset.id });
|
|
await expect(faces).resolves.toHaveLength(1);
|
|
await expect(faces).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: expect.closeTo(50, 1),
|
|
boundingBoxY1: expect.closeTo(25, 1),
|
|
boundingBoxX2: expect.closeTo(65, 1),
|
|
boundingBoxY2: expect.closeTo(45, 1),
|
|
}),
|
|
]),
|
|
);
|
|
|
|
// remove edits and verify the stored coordinates map to the original image
|
|
await ctx.newEdits(asset.id, { edits: [] });
|
|
const facesAfterRemovingEdits = sut.getFacesById(auth, { id: asset.id });
|
|
|
|
await expect(facesAfterRemovingEdits).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: 25,
|
|
boundingBoxY1: 50,
|
|
boundingBoxX2: 45,
|
|
boundingBoxY2: 65,
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('should properly transform the coordinates when the asset is edited (Crop + Rotate + Mirror)', async () => {
|
|
const { sut, ctx } = setup();
|
|
const { user } = await ctx.newUser();
|
|
const { person } = await ctx.newPerson({ ownerId: user.id });
|
|
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 150, height: 100 });
|
|
await ctx.newExif({ assetId: asset.id, exifImageHeight: 200, exifImageWidth: 200 });
|
|
|
|
await ctx.newEdits(asset.id, {
|
|
edits: [
|
|
{
|
|
action: AssetEditAction.Crop,
|
|
parameters: {
|
|
x: 50,
|
|
y: 25,
|
|
width: 100,
|
|
height: 150,
|
|
},
|
|
},
|
|
{
|
|
action: AssetEditAction.Rotate,
|
|
parameters: {
|
|
angle: 270,
|
|
},
|
|
},
|
|
{
|
|
action: AssetEditAction.Mirror,
|
|
parameters: {
|
|
axis: MirrorAxis.Horizontal,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const auth = factory.auth({ user });
|
|
|
|
const dto: AssetFaceCreateDto = {
|
|
imageWidth: 150,
|
|
imageHeight: 150,
|
|
x: 25,
|
|
y: 50,
|
|
width: 75,
|
|
height: 50,
|
|
personId: person.id,
|
|
assetId: asset.id,
|
|
};
|
|
|
|
await sut.createFace(auth, dto);
|
|
|
|
const faces = sut.getFacesById(auth, { id: asset.id });
|
|
await expect(faces).resolves.toHaveLength(1);
|
|
await expect(faces).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: expect.closeTo(25, 1),
|
|
boundingBoxY1: expect.closeTo(50, 1),
|
|
boundingBoxX2: expect.closeTo(100, 1),
|
|
boundingBoxY2: expect.closeTo(100, 1),
|
|
}),
|
|
]),
|
|
);
|
|
|
|
// remove edits and verify the stored coordinates map to the original image
|
|
await ctx.newEdits(asset.id, { edits: [] });
|
|
const facesAfterRemovingEdits = sut.getFacesById(auth, { id: asset.id });
|
|
|
|
await expect(facesAfterRemovingEdits).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: 50,
|
|
boundingBoxY1: 75,
|
|
boundingBoxX2: 100,
|
|
boundingBoxY2: 150,
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('should properly transform the coordinates with multiple mirrors in sequence', async () => {
|
|
const { sut, ctx } = setup();
|
|
const { user } = await ctx.newUser();
|
|
const { person } = await ctx.newPerson({ ownerId: user.id });
|
|
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 100, height: 100 });
|
|
await ctx.newExif({ assetId: asset.id, exifImageHeight: 100, exifImageWidth: 100 });
|
|
|
|
await ctx.newEdits(asset.id, {
|
|
edits: [
|
|
{
|
|
action: AssetEditAction.Mirror,
|
|
parameters: {
|
|
axis: MirrorAxis.Horizontal,
|
|
},
|
|
},
|
|
{
|
|
action: AssetEditAction.Mirror,
|
|
parameters: {
|
|
axis: MirrorAxis.Vertical,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const auth = factory.auth({ user });
|
|
|
|
const dto: AssetFaceCreateDto = {
|
|
imageWidth: 100,
|
|
imageHeight: 100,
|
|
x: 10,
|
|
y: 10,
|
|
width: 80,
|
|
height: 80,
|
|
personId: person.id,
|
|
assetId: asset.id,
|
|
};
|
|
|
|
await sut.createFace(auth, dto);
|
|
|
|
const faces = sut.getFacesById(auth, { id: asset.id });
|
|
await expect(faces).resolves.toHaveLength(1);
|
|
await expect(faces).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: 10,
|
|
boundingBoxY1: 10,
|
|
boundingBoxX2: 90,
|
|
boundingBoxY2: 90,
|
|
}),
|
|
]),
|
|
);
|
|
|
|
// remove edits and verify the stored coordinates map to the original image
|
|
await ctx.newEdits(asset.id, { edits: [] });
|
|
const facesAfterRemovingEdits = sut.getFacesById(auth, { id: asset.id });
|
|
|
|
await expect(facesAfterRemovingEdits).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: 10,
|
|
boundingBoxY1: 10,
|
|
boundingBoxX2: 90,
|
|
boundingBoxY2: 90,
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('should properly handle exif orientation when creating a face on an edited asset', async () => {
|
|
const { sut, ctx } = setup();
|
|
const { user } = await ctx.newUser();
|
|
const { person } = await ctx.newPerson({ ownerId: user.id });
|
|
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 100, height: 100 });
|
|
await ctx.newExif({ assetId: asset.id, exifImageHeight: 200, exifImageWidth: 100, orientation: '6' });
|
|
|
|
await ctx.newEdits(asset.id, {
|
|
edits: [
|
|
{
|
|
action: AssetEditAction.Mirror,
|
|
parameters: {
|
|
axis: MirrorAxis.Horizontal,
|
|
},
|
|
},
|
|
{
|
|
action: AssetEditAction.Mirror,
|
|
parameters: {
|
|
axis: MirrorAxis.Vertical,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const auth = factory.auth({ user });
|
|
|
|
const dto: AssetFaceCreateDto = {
|
|
imageWidth: 100,
|
|
imageHeight: 100,
|
|
x: 10,
|
|
y: 10,
|
|
width: 80,
|
|
height: 80,
|
|
personId: person.id,
|
|
assetId: asset.id,
|
|
};
|
|
|
|
await sut.createFace(auth, dto);
|
|
|
|
const faces = sut.getFacesById(auth, { id: asset.id });
|
|
await expect(faces).resolves.toHaveLength(1);
|
|
await expect(faces).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: 110,
|
|
boundingBoxY1: 10,
|
|
boundingBoxX2: 190,
|
|
boundingBoxY2: 90,
|
|
}),
|
|
]),
|
|
);
|
|
|
|
// remove edits and verify the stored coordinates map to the original image
|
|
await ctx.newEdits(asset.id, { edits: [] });
|
|
const facesAfterRemovingEdits = sut.getFacesById(auth, { id: asset.id });
|
|
|
|
await expect(facesAfterRemovingEdits).resolves.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
person: expect.objectContaining({ id: person.id }),
|
|
boundingBoxX1: 10,
|
|
boundingBoxY1: 10,
|
|
boundingBoxX2: 90,
|
|
boundingBoxY2: 90,
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
});
|
|
});
|