mirror of
https://github.com/immich-app/immich.git
synced 2024-12-29 07:01:59 +00:00
refactor(server): e2e (#7265)
* refactor: activity e2e * refactor: person e2e * refactor: shared link e2e
This commit is contained in:
parent
a1bc74cdd6
commit
f798e037d5
5 changed files with 573 additions and 459 deletions
|
@ -1,79 +1,94 @@
|
||||||
import { AlbumResponseDto, LoginResponseDto, ReactionType } from '@app/domain';
|
import {
|
||||||
import { ActivityController } from '@app/immich';
|
ActivityCreateDto,
|
||||||
import { AssetFileUploadResponseDto } from '@app/immich/api-v1/asset/response-dto/asset-file-upload-response.dto';
|
AlbumResponseDto,
|
||||||
import { ActivityEntity } from '@app/infra/entities';
|
AssetResponseDto,
|
||||||
import { errorStub, userDto, uuidStub } from '@test/fixtures';
|
LoginResponseDto,
|
||||||
|
ReactionType,
|
||||||
|
createActivity as create,
|
||||||
|
createAlbum,
|
||||||
|
} from '@immich/sdk';
|
||||||
|
import { createUserDto, uuidDto } from 'src/fixtures';
|
||||||
|
import { errorDto } from 'src/responses';
|
||||||
|
import { apiUtils, app, asBearerAuth, dbUtils } from 'src/utils';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import { api } from '../../client';
|
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||||
import { testApp } from '../utils';
|
|
||||||
|
|
||||||
describe(`${ActivityController.name} (e2e)`, () => {
|
describe('/activity', () => {
|
||||||
let server: any;
|
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
let asset: AssetFileUploadResponseDto;
|
|
||||||
let album: AlbumResponseDto;
|
|
||||||
let nonOwner: LoginResponseDto;
|
let nonOwner: LoginResponseDto;
|
||||||
|
let asset: AssetResponseDto;
|
||||||
|
let album: AlbumResponseDto;
|
||||||
|
|
||||||
|
const createActivity = (dto: ActivityCreateDto, accessToken?: string) =>
|
||||||
|
create(
|
||||||
|
{ activityCreateDto: dto },
|
||||||
|
{ headers: asBearerAuth(accessToken || admin.accessToken) }
|
||||||
|
);
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = (await testApp.create()).getHttpServer();
|
apiUtils.setup();
|
||||||
await testApp.reset();
|
await dbUtils.reset();
|
||||||
await api.authApi.adminSignUp(server);
|
|
||||||
admin = await api.authApi.adminLogin(server);
|
|
||||||
asset = await api.assetApi.upload(server, admin.accessToken, 'example');
|
|
||||||
|
|
||||||
await api.userApi.create(server, admin.accessToken, userDto.user1);
|
admin = await apiUtils.adminSetup();
|
||||||
nonOwner = await api.authApi.login(server, userDto.user1);
|
nonOwner = await apiUtils.userSetup(admin.accessToken, createUserDto.user1);
|
||||||
|
asset = await apiUtils.createAsset(admin.accessToken);
|
||||||
album = await api.albumApi.create(server, admin.accessToken, {
|
album = await createAlbum(
|
||||||
albumName: 'Album 1',
|
{
|
||||||
assetIds: [asset.id],
|
createAlbumDto: {
|
||||||
sharedWithUserIds: [nonOwner.userId],
|
albumName: 'Album 1',
|
||||||
});
|
assetIds: [asset.id],
|
||||||
});
|
sharedWithUserIds: [nonOwner.userId],
|
||||||
|
},
|
||||||
afterAll(async () => {
|
},
|
||||||
await testApp.teardown();
|
{ headers: asBearerAuth(admin.accessToken) }
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await testApp.reset({ entities: [ActivityEntity] });
|
await dbUtils.reset(['activity']);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /activity', () => {
|
describe('GET /activity', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(server).get('/activity');
|
const { status, body } = await request(app).get('/activity');
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
expect(body).toEqual(errorStub.unauthorized);
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require an albumId', async () => {
|
it('should require an albumId', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.get('/activity')
|
.get('/activity')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toEqual(400);
|
expect(status).toEqual(400);
|
||||||
expect(body).toEqual(errorStub.badRequest(expect.arrayContaining(['albumId must be a UUID'])));
|
expect(body).toEqual(
|
||||||
|
errorDto.badRequest(expect.arrayContaining(['albumId must be a UUID']))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject an invalid albumId', async () => {
|
it('should reject an invalid albumId', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.get('/activity')
|
.get('/activity')
|
||||||
.query({ albumId: uuidStub.invalid })
|
.query({ albumId: uuidDto.invalid })
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toEqual(400);
|
expect(status).toEqual(400);
|
||||||
expect(body).toEqual(errorStub.badRequest(expect.arrayContaining(['albumId must be a UUID'])));
|
expect(body).toEqual(
|
||||||
|
errorDto.badRequest(expect.arrayContaining(['albumId must be a UUID']))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject an invalid assetId', async () => {
|
it('should reject an invalid assetId', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.get('/activity')
|
.get('/activity')
|
||||||
.query({ albumId: uuidStub.notFound, assetId: uuidStub.invalid })
|
.query({ albumId: uuidDto.notFound, assetId: uuidDto.invalid })
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toEqual(400);
|
expect(status).toEqual(400);
|
||||||
expect(body).toEqual(errorStub.badRequest(expect.arrayContaining(['assetId must be a UUID'])));
|
expect(body).toEqual(
|
||||||
|
errorDto.badRequest(expect.arrayContaining(['assetId must be a UUID']))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should start off empty', async () => {
|
it('should start off empty', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.get('/activity')
|
.get('/activity')
|
||||||
.query({ albumId: album.id })
|
.query({ albumId: album.id })
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
@ -82,22 +97,22 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should filter by album id', async () => {
|
it('should filter by album id', async () => {
|
||||||
const album2 = await api.albumApi.create(server, admin.accessToken, {
|
const album2 = await createAlbum(
|
||||||
albumName: 'Album 2',
|
{
|
||||||
assetIds: [asset.id],
|
createAlbumDto: {
|
||||||
});
|
albumName: 'Album 2',
|
||||||
|
assetIds: [asset.id],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ headers: asBearerAuth(admin.accessToken) }
|
||||||
|
);
|
||||||
|
|
||||||
const [reaction] = await Promise.all([
|
const [reaction] = await Promise.all([
|
||||||
api.activityApi.create(server, admin.accessToken, {
|
createActivity({ albumId: album.id, type: ReactionType.Like }),
|
||||||
albumId: album.id,
|
createActivity({ albumId: album2.id, type: ReactionType.Like }),
|
||||||
type: ReactionType.LIKE,
|
|
||||||
}),
|
|
||||||
api.activityApi.create(server, admin.accessToken, {
|
|
||||||
albumId: album2.id,
|
|
||||||
type: ReactionType.LIKE,
|
|
||||||
}),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.get('/activity')
|
.get('/activity')
|
||||||
.query({ albumId: album.id })
|
.query({ albumId: album.id })
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
@ -108,15 +123,15 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
||||||
|
|
||||||
it('should filter by type=comment', async () => {
|
it('should filter by type=comment', async () => {
|
||||||
const [reaction] = await Promise.all([
|
const [reaction] = await Promise.all([
|
||||||
api.activityApi.create(server, admin.accessToken, {
|
createActivity({
|
||||||
albumId: album.id,
|
albumId: album.id,
|
||||||
type: ReactionType.COMMENT,
|
type: ReactionType.Comment,
|
||||||
comment: 'comment',
|
comment: 'comment',
|
||||||
}),
|
}),
|
||||||
api.activityApi.create(server, admin.accessToken, { albumId: album.id, type: ReactionType.LIKE }),
|
createActivity({ albumId: album.id, type: ReactionType.Like }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.get('/activity')
|
.get('/activity')
|
||||||
.query({ albumId: album.id, type: 'comment' })
|
.query({ albumId: album.id, type: 'comment' })
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
@ -127,15 +142,15 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
||||||
|
|
||||||
it('should filter by type=like', async () => {
|
it('should filter by type=like', async () => {
|
||||||
const [reaction] = await Promise.all([
|
const [reaction] = await Promise.all([
|
||||||
api.activityApi.create(server, admin.accessToken, { albumId: album.id, type: ReactionType.LIKE }),
|
createActivity({ albumId: album.id, type: ReactionType.Like }),
|
||||||
api.activityApi.create(server, admin.accessToken, {
|
createActivity({
|
||||||
albumId: album.id,
|
albumId: album.id,
|
||||||
type: ReactionType.COMMENT,
|
type: ReactionType.Comment,
|
||||||
comment: 'comment',
|
comment: 'comment',
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.get('/activity')
|
.get('/activity')
|
||||||
.query({ albumId: album.id, type: 'like' })
|
.query({ albumId: album.id, type: 'like' })
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
@ -146,18 +161,18 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
||||||
|
|
||||||
it('should filter by userId', async () => {
|
it('should filter by userId', async () => {
|
||||||
const [reaction] = await Promise.all([
|
const [reaction] = await Promise.all([
|
||||||
api.activityApi.create(server, admin.accessToken, { albumId: album.id, type: ReactionType.LIKE }),
|
createActivity({ albumId: album.id, type: ReactionType.Like }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const response1 = await request(server)
|
const response1 = await request(app)
|
||||||
.get('/activity')
|
.get('/activity')
|
||||||
.query({ albumId: album.id, userId: uuidStub.notFound })
|
.query({ albumId: album.id, userId: uuidDto.notFound })
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
expect(response1.status).toEqual(200);
|
expect(response1.status).toEqual(200);
|
||||||
expect(response1.body.length).toBe(0);
|
expect(response1.body.length).toBe(0);
|
||||||
|
|
||||||
const response2 = await request(server)
|
const response2 = await request(app)
|
||||||
.get('/activity')
|
.get('/activity')
|
||||||
.query({ albumId: album.id, userId: admin.userId })
|
.query({ albumId: album.id, userId: admin.userId })
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
@ -169,15 +184,15 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
||||||
|
|
||||||
it('should filter by assetId', async () => {
|
it('should filter by assetId', async () => {
|
||||||
const [reaction] = await Promise.all([
|
const [reaction] = await Promise.all([
|
||||||
api.activityApi.create(server, admin.accessToken, {
|
createActivity({
|
||||||
albumId: album.id,
|
albumId: album.id,
|
||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
type: ReactionType.LIKE,
|
type: ReactionType.Like,
|
||||||
}),
|
}),
|
||||||
api.activityApi.create(server, admin.accessToken, { albumId: album.id, type: ReactionType.LIKE }),
|
createActivity({ albumId: album.id, type: ReactionType.Like }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.get('/activity')
|
.get('/activity')
|
||||||
.query({ albumId: album.id, assetId: asset.id })
|
.query({ albumId: album.id, assetId: asset.id })
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
@ -189,34 +204,45 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
||||||
|
|
||||||
describe('POST /activity', () => {
|
describe('POST /activity', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(server).post('/activity');
|
const { status, body } = await request(app).post('/activity');
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
expect(body).toEqual(errorStub.unauthorized);
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require an albumId', async () => {
|
it('should require an albumId', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.post('/activity')
|
.post('/activity')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send({ albumId: uuidStub.invalid });
|
.send({ albumId: uuidDto.invalid });
|
||||||
expect(status).toEqual(400);
|
expect(status).toEqual(400);
|
||||||
expect(body).toEqual(errorStub.badRequest(expect.arrayContaining(['albumId must be a UUID'])));
|
expect(body).toEqual(
|
||||||
|
errorDto.badRequest(expect.arrayContaining(['albumId must be a UUID']))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require a comment when type is comment', async () => {
|
it('should require a comment when type is comment', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.post('/activity')
|
.post('/activity')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send({ albumId: uuidStub.notFound, type: 'comment', comment: null });
|
.send({ albumId: uuidDto.notFound, type: 'comment', comment: null });
|
||||||
expect(status).toEqual(400);
|
expect(status).toEqual(400);
|
||||||
expect(body).toEqual(errorStub.badRequest(['comment must be a string', 'comment should not be empty']));
|
expect(body).toEqual(
|
||||||
|
errorDto.badRequest([
|
||||||
|
'comment must be a string',
|
||||||
|
'comment should not be empty',
|
||||||
|
])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add a comment to an album', async () => {
|
it('should add a comment to an album', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.post('/activity')
|
.post('/activity')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send({ albumId: album.id, type: 'comment', comment: 'This is my first comment' });
|
.send({
|
||||||
|
albumId: album.id,
|
||||||
|
type: 'comment',
|
||||||
|
comment: 'This is my first comment',
|
||||||
|
});
|
||||||
expect(status).toEqual(201);
|
expect(status).toEqual(201);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
|
@ -229,7 +255,7 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add a like to an album', async () => {
|
it('should add a like to an album', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.post('/activity')
|
.post('/activity')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send({ albumId: album.id, type: 'like' });
|
.send({ albumId: album.id, type: 'like' });
|
||||||
|
@ -245,11 +271,11 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a 200 for a duplicate like on the album', async () => {
|
it('should return a 200 for a duplicate like on the album', async () => {
|
||||||
const reaction = await api.activityApi.create(server, admin.accessToken, {
|
const [reaction] = await Promise.all([
|
||||||
albumId: album.id,
|
createActivity({ albumId: album.id, type: ReactionType.Like }),
|
||||||
type: ReactionType.LIKE,
|
]);
|
||||||
});
|
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.post('/activity')
|
.post('/activity')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send({ albumId: album.id, type: 'like' });
|
.send({ albumId: album.id, type: 'like' });
|
||||||
|
@ -258,12 +284,14 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not confuse an album like with an asset like', async () => {
|
it('should not confuse an album like with an asset like', async () => {
|
||||||
const reaction = await api.activityApi.create(server, admin.accessToken, {
|
const [reaction] = await Promise.all([
|
||||||
albumId: album.id,
|
createActivity({
|
||||||
assetId: asset.id,
|
albumId: album.id,
|
||||||
type: ReactionType.LIKE,
|
assetId: asset.id,
|
||||||
});
|
type: ReactionType.Like,
|
||||||
const { status, body } = await request(server)
|
}),
|
||||||
|
]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
.post('/activity')
|
.post('/activity')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send({ albumId: album.id, type: 'like' });
|
.send({ albumId: album.id, type: 'like' });
|
||||||
|
@ -272,10 +300,15 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add a comment to an asset', async () => {
|
it('should add a comment to an asset', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.post('/activity')
|
.post('/activity')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send({ albumId: album.id, assetId: asset.id, type: 'comment', comment: 'This is my first comment' });
|
.send({
|
||||||
|
albumId: album.id,
|
||||||
|
assetId: asset.id,
|
||||||
|
type: 'comment',
|
||||||
|
comment: 'This is my first comment',
|
||||||
|
});
|
||||||
expect(status).toEqual(201);
|
expect(status).toEqual(201);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
|
@ -288,7 +321,7 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add a like to an asset', async () => {
|
it('should add a like to an asset', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.post('/activity')
|
.post('/activity')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send({ albumId: album.id, assetId: asset.id, type: 'like' });
|
.send({ albumId: album.id, assetId: asset.id, type: 'like' });
|
||||||
|
@ -304,12 +337,15 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a 200 for a duplicate like on an asset', async () => {
|
it('should return a 200 for a duplicate like on an asset', async () => {
|
||||||
const reaction = await api.activityApi.create(server, admin.accessToken, {
|
const [reaction] = await Promise.all([
|
||||||
albumId: album.id,
|
createActivity({
|
||||||
assetId: asset.id,
|
albumId: album.id,
|
||||||
type: ReactionType.LIKE,
|
assetId: asset.id,
|
||||||
});
|
type: ReactionType.Like,
|
||||||
const { status, body } = await request(server)
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
.post('/activity')
|
.post('/activity')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send({ albumId: album.id, assetId: asset.id, type: 'like' });
|
.send({ albumId: album.id, assetId: asset.id, type: 'like' });
|
||||||
|
@ -320,50 +356,52 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
||||||
|
|
||||||
describe('DELETE /activity/:id', () => {
|
describe('DELETE /activity/:id', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(server).delete(`/activity/${uuidStub.notFound}`);
|
const { status, body } = await request(app).delete(
|
||||||
|
`/activity/${uuidDto.notFound}`
|
||||||
|
);
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
expect(body).toEqual(errorStub.unauthorized);
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require a valid uuid', async () => {
|
it('should require a valid uuid', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.delete(`/activity/${uuidStub.invalid}`)
|
.delete(`/activity/${uuidDto.invalid}`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorStub.badRequest(['id must be a UUID']));
|
expect(body).toEqual(errorDto.badRequest(['id must be a UUID']));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove a comment from an album', async () => {
|
it('should remove a comment from an album', async () => {
|
||||||
const reaction = await api.activityApi.create(server, admin.accessToken, {
|
const reaction = await createActivity({
|
||||||
albumId: album.id,
|
albumId: album.id,
|
||||||
type: ReactionType.COMMENT,
|
type: ReactionType.Comment,
|
||||||
comment: 'This is a test comment',
|
comment: 'This is a test comment',
|
||||||
});
|
});
|
||||||
const { status } = await request(server)
|
const { status } = await request(app)
|
||||||
.delete(`/activity/${reaction.id}`)
|
.delete(`/activity/${reaction.id}`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toEqual(204);
|
expect(status).toEqual(204);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove a like from an album', async () => {
|
it('should remove a like from an album', async () => {
|
||||||
const reaction = await api.activityApi.create(server, admin.accessToken, {
|
const reaction = await createActivity({
|
||||||
albumId: album.id,
|
albumId: album.id,
|
||||||
type: ReactionType.LIKE,
|
type: ReactionType.Like,
|
||||||
});
|
});
|
||||||
const { status } = await request(server)
|
const { status } = await request(app)
|
||||||
.delete(`/activity/${reaction.id}`)
|
.delete(`/activity/${reaction.id}`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toEqual(204);
|
expect(status).toEqual(204);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should let the owner remove a comment by another user', async () => {
|
it('should let the owner remove a comment by another user', async () => {
|
||||||
const reaction = await api.activityApi.create(server, nonOwner.accessToken, {
|
const reaction = await createActivity({
|
||||||
albumId: album.id,
|
albumId: album.id,
|
||||||
type: ReactionType.COMMENT,
|
type: ReactionType.Comment,
|
||||||
comment: 'This is a test comment',
|
comment: 'This is a test comment',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { status } = await request(server)
|
const { status } = await request(app)
|
||||||
.delete(`/activity/${reaction.id}`)
|
.delete(`/activity/${reaction.id}`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
|
@ -371,28 +409,33 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not let a user remove a comment by another user', async () => {
|
it('should not let a user remove a comment by another user', async () => {
|
||||||
const reaction = await api.activityApi.create(server, admin.accessToken, {
|
const reaction = await createActivity({
|
||||||
albumId: album.id,
|
albumId: album.id,
|
||||||
type: ReactionType.COMMENT,
|
type: ReactionType.Comment,
|
||||||
comment: 'This is a test comment',
|
comment: 'This is a test comment',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.delete(`/activity/${reaction.id}`)
|
.delete(`/activity/${reaction.id}`)
|
||||||
.set('Authorization', `Bearer ${nonOwner.accessToken}`);
|
.set('Authorization', `Bearer ${nonOwner.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorStub.badRequest('Not found or no activity.delete access'));
|
expect(body).toEqual(
|
||||||
|
errorDto.badRequest('Not found or no activity.delete access')
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should let a non-owner remove their own comment', async () => {
|
it('should let a non-owner remove their own comment', async () => {
|
||||||
const reaction = await api.activityApi.create(server, nonOwner.accessToken, {
|
const reaction = await createActivity(
|
||||||
albumId: album.id,
|
{
|
||||||
type: ReactionType.COMMENT,
|
albumId: album.id,
|
||||||
comment: 'This is a test comment',
|
type: ReactionType.Comment,
|
||||||
});
|
comment: 'This is a test comment',
|
||||||
|
},
|
||||||
|
nonOwner.accessToken
|
||||||
|
);
|
||||||
|
|
||||||
const { status } = await request(server)
|
const { status } = await request(app)
|
||||||
.delete(`/activity/${reaction.id}`)
|
.delete(`/activity/${reaction.id}`)
|
||||||
.set('Authorization', `Bearer ${nonOwner.accessToken}`);
|
.set('Authorization', `Bearer ${nonOwner.accessToken}`);
|
||||||
|
|
176
e2e/src/api/specs/person.e2e-spec.ts
Normal file
176
e2e/src/api/specs/person.e2e-spec.ts
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
import { LoginResponseDto, PersonResponseDto } from '@immich/sdk';
|
||||||
|
import { uuidDto } from 'src/fixtures';
|
||||||
|
import { errorDto } from 'src/responses';
|
||||||
|
import { apiUtils, app, dbUtils } from 'src/utils';
|
||||||
|
import request from 'supertest';
|
||||||
|
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
describe('/activity', () => {
|
||||||
|
let admin: LoginResponseDto;
|
||||||
|
let visiblePerson: PersonResponseDto;
|
||||||
|
let hiddenPerson: PersonResponseDto;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
apiUtils.setup();
|
||||||
|
await dbUtils.reset();
|
||||||
|
admin = await apiUtils.adminSetup();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await dbUtils.reset(['person']);
|
||||||
|
|
||||||
|
[visiblePerson, hiddenPerson] = await Promise.all([
|
||||||
|
apiUtils.createPerson(admin.accessToken, {
|
||||||
|
name: 'visible_person',
|
||||||
|
}),
|
||||||
|
apiUtils.createPerson(admin.accessToken, {
|
||||||
|
name: 'hidden_person',
|
||||||
|
isHidden: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const asset = await apiUtils.createAsset(admin.accessToken);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
dbUtils.createFace({ assetId: asset.id, personId: visiblePerson.id }),
|
||||||
|
dbUtils.createFace({ assetId: asset.id, personId: hiddenPerson.id }),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /person', () => {
|
||||||
|
beforeEach(async () => {});
|
||||||
|
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).get('/person');
|
||||||
|
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return all people (including hidden)', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/person')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.query({ withHidden: true });
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({
|
||||||
|
total: 2,
|
||||||
|
people: [
|
||||||
|
expect.objectContaining({ name: 'visible_person' }),
|
||||||
|
expect.objectContaining({ name: 'hidden_person' }),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return only visible people', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/person')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({
|
||||||
|
total: 2,
|
||||||
|
people: [expect.objectContaining({ name: 'visible_person' })],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /person/:id', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).get(
|
||||||
|
`/person/${uuidDto.notFound}`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if person with id does not exist', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get(`/person/${uuidDto.notFound}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return person information', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get(`/person/${visiblePerson.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(expect.objectContaining({ id: visiblePerson.id }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PUT /person/:id', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).put(
|
||||||
|
`/person/${uuidDto.notFound}`
|
||||||
|
);
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const { key, type } of [
|
||||||
|
{ key: 'name', type: 'string' },
|
||||||
|
{ key: 'featureFaceAssetId', type: 'string' },
|
||||||
|
{ key: 'isHidden', type: 'boolean value' },
|
||||||
|
]) {
|
||||||
|
it(`should not allow null ${key}`, async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/person/${visiblePerson.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ [key]: null });
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest([`${key} must be a ${type}`]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should not accept invalid birth dates', async () => {
|
||||||
|
for (const { birthDate, response } of [
|
||||||
|
{ birthDate: false, response: 'Not found or no person.write access' },
|
||||||
|
{ birthDate: 'false', response: ['birthDate must be a Date instance'] },
|
||||||
|
{
|
||||||
|
birthDate: '123567',
|
||||||
|
response: 'Not found or no person.write access',
|
||||||
|
},
|
||||||
|
{ birthDate: 123567, response: 'Not found or no person.write access' },
|
||||||
|
]) {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/person/${uuidDto.notFound}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ birthDate });
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(response));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update a date of birth', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/person/${visiblePerson.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ birthDate: '1990-01-01T05:00:00.000Z' });
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toMatchObject({ birthDate: '1990-01-01' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear a date of birth', async () => {
|
||||||
|
// TODO ironically this uses the update endpoint to create the person
|
||||||
|
const person = await apiUtils.createPerson(admin.accessToken, {
|
||||||
|
birthDate: new Date('1990-01-01').toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(person.birthDate).toBeDefined();
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/person/${person.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ birthDate: null });
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toMatchObject({ birthDate: null });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,21 +1,24 @@
|
||||||
import {
|
import {
|
||||||
AlbumResponseDto,
|
AlbumResponseDto,
|
||||||
AssetResponseDto,
|
AssetResponseDto,
|
||||||
IAssetRepository,
|
|
||||||
LoginResponseDto,
|
LoginResponseDto,
|
||||||
|
SharedLinkCreateDto,
|
||||||
SharedLinkResponseDto,
|
SharedLinkResponseDto,
|
||||||
} from '@app/domain';
|
SharedLinkType,
|
||||||
import { SharedLinkController } from '@app/immich';
|
createSharedLink as create,
|
||||||
import { SharedLinkType } from '@app/infra/entities';
|
createAlbum,
|
||||||
import { INestApplication } from '@nestjs/common';
|
deleteUser,
|
||||||
import { errorStub, userDto, uuidStub } from '@test/fixtures';
|
} from '@immich/sdk';
|
||||||
import { DateTime } from 'luxon';
|
import { createUserDto, uuidDto } from 'src/fixtures';
|
||||||
|
import { errorDto } from 'src/responses';
|
||||||
|
import { apiUtils, app, asBearerAuth, dbUtils } from 'src/utils';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import { api } from '../../client';
|
import { beforeAll, describe, expect, it } from 'vitest';
|
||||||
import { testApp } from '../utils';
|
|
||||||
|
|
||||||
describe(`${SharedLinkController.name} (e2e)`, () => {
|
const createSharedLink = (dto: SharedLinkCreateDto, accessToken: string) =>
|
||||||
let server: any;
|
create({ sharedLinkCreateDto: dto }, { headers: asBearerAuth(accessToken) });
|
||||||
|
|
||||||
|
describe('/shared-link', () => {
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
let asset1: AssetResponseDto;
|
let asset1: AssetResponseDto;
|
||||||
let asset2: AssetResponseDto;
|
let asset2: AssetResponseDto;
|
||||||
|
@ -30,97 +33,101 @@ describe(`${SharedLinkController.name} (e2e)`, () => {
|
||||||
let linkWithAssets: SharedLinkResponseDto;
|
let linkWithAssets: SharedLinkResponseDto;
|
||||||
let linkWithMetadata: SharedLinkResponseDto;
|
let linkWithMetadata: SharedLinkResponseDto;
|
||||||
let linkWithoutMetadata: SharedLinkResponseDto;
|
let linkWithoutMetadata: SharedLinkResponseDto;
|
||||||
let app: INestApplication<any>;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await testApp.create();
|
apiUtils.setup();
|
||||||
server = app.getHttpServer();
|
await dbUtils.reset();
|
||||||
const assetRepository = app.get<IAssetRepository>(IAssetRepository);
|
|
||||||
|
|
||||||
await testApp.reset();
|
admin = await apiUtils.adminSetup();
|
||||||
await api.authApi.adminSignUp(server);
|
|
||||||
admin = await api.authApi.adminLogin(server);
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
api.userApi.create(server, admin.accessToken, userDto.user1),
|
|
||||||
api.userApi.create(server, admin.accessToken, userDto.user2),
|
|
||||||
]);
|
|
||||||
|
|
||||||
[user1, user2] = await Promise.all([
|
[user1, user2] = await Promise.all([
|
||||||
api.authApi.login(server, userDto.user1),
|
apiUtils.userSetup(admin.accessToken, createUserDto.user1),
|
||||||
api.authApi.login(server, userDto.user2),
|
apiUtils.userSetup(admin.accessToken, createUserDto.user2),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
[asset1, asset2] = await Promise.all([
|
[asset1, asset2] = await Promise.all([
|
||||||
api.assetApi.create(server, user1.accessToken),
|
apiUtils.createAsset(user1.accessToken),
|
||||||
api.assetApi.create(server, user1.accessToken),
|
apiUtils.createAsset(user1.accessToken),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await assetRepository.upsertExif({
|
|
||||||
assetId: asset1.id,
|
|
||||||
longitude: -108.400968333333,
|
|
||||||
latitude: 39.115,
|
|
||||||
orientation: '1',
|
|
||||||
dateTimeOriginal: DateTime.fromISO('2022-01-10T19:15:44.310Z').toJSDate(),
|
|
||||||
timeZone: 'UTC-4',
|
|
||||||
state: 'Mesa County, Colorado',
|
|
||||||
country: 'United States of America',
|
|
||||||
});
|
|
||||||
|
|
||||||
[album, deletedAlbum, metadataAlbum] = await Promise.all([
|
[album, deletedAlbum, metadataAlbum] = await Promise.all([
|
||||||
api.albumApi.create(server, user1.accessToken, { albumName: 'album' }),
|
createAlbum(
|
||||||
api.albumApi.create(server, user2.accessToken, { albumName: 'deleted album' }),
|
{ createAlbumDto: { albumName: 'album' } },
|
||||||
api.albumApi.create(server, user1.accessToken, { albumName: 'metadata album', assetIds: [asset1.id] }),
|
{ headers: asBearerAuth(user1.accessToken) }
|
||||||
|
),
|
||||||
|
createAlbum(
|
||||||
|
{ createAlbumDto: { albumName: 'deleted album' } },
|
||||||
|
{ headers: asBearerAuth(user2.accessToken) }
|
||||||
|
),
|
||||||
|
createAlbum(
|
||||||
|
{
|
||||||
|
createAlbumDto: {
|
||||||
|
albumName: 'metadata album',
|
||||||
|
assetIds: [asset1.id],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ headers: asBearerAuth(user1.accessToken) }
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
[linkWithDeletedAlbum, linkWithAlbum, linkWithAssets, linkWithPassword, linkWithMetadata, linkWithoutMetadata] =
|
[
|
||||||
await Promise.all([
|
linkWithDeletedAlbum,
|
||||||
api.sharedLinkApi.create(server, user2.accessToken, {
|
linkWithAlbum,
|
||||||
type: SharedLinkType.ALBUM,
|
linkWithAssets,
|
||||||
albumId: deletedAlbum.id,
|
linkWithPassword,
|
||||||
}),
|
linkWithMetadata,
|
||||||
api.sharedLinkApi.create(server, user1.accessToken, {
|
linkWithoutMetadata,
|
||||||
type: SharedLinkType.ALBUM,
|
] = await Promise.all([
|
||||||
albumId: album.id,
|
createSharedLink(
|
||||||
}),
|
{ type: SharedLinkType.Album, albumId: deletedAlbum.id },
|
||||||
api.sharedLinkApi.create(server, user1.accessToken, {
|
user2.accessToken
|
||||||
type: SharedLinkType.INDIVIDUAL,
|
),
|
||||||
assetIds: [asset1.id],
|
createSharedLink(
|
||||||
}),
|
{ type: SharedLinkType.Album, albumId: album.id },
|
||||||
api.sharedLinkApi.create(server, user1.accessToken, {
|
user1.accessToken
|
||||||
type: SharedLinkType.ALBUM,
|
),
|
||||||
albumId: album.id,
|
createSharedLink(
|
||||||
password: 'foo',
|
{ type: SharedLinkType.Individual, assetIds: [asset1.id] },
|
||||||
}),
|
user1.accessToken
|
||||||
api.sharedLinkApi.create(server, user1.accessToken, {
|
),
|
||||||
type: SharedLinkType.ALBUM,
|
createSharedLink(
|
||||||
|
{ type: SharedLinkType.Album, albumId: album.id, password: 'foo' },
|
||||||
|
user1.accessToken
|
||||||
|
),
|
||||||
|
createSharedLink(
|
||||||
|
{
|
||||||
|
type: SharedLinkType.Album,
|
||||||
albumId: metadataAlbum.id,
|
albumId: metadataAlbum.id,
|
||||||
showMetadata: true,
|
showMetadata: true,
|
||||||
}),
|
},
|
||||||
api.sharedLinkApi.create(server, user1.accessToken, {
|
user1.accessToken
|
||||||
type: SharedLinkType.ALBUM,
|
),
|
||||||
|
createSharedLink(
|
||||||
|
{
|
||||||
|
type: SharedLinkType.Album,
|
||||||
albumId: metadataAlbum.id,
|
albumId: metadataAlbum.id,
|
||||||
showMetadata: false,
|
showMetadata: false,
|
||||||
}),
|
},
|
||||||
]);
|
user1.accessToken
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
await api.userApi.delete(server, admin.accessToken, user2.userId);
|
await deleteUser(
|
||||||
});
|
{ id: user2.userId },
|
||||||
|
{ headers: asBearerAuth(admin.accessToken) }
|
||||||
afterAll(async () => {
|
);
|
||||||
await testApp.teardown();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /shared-link', () => {
|
describe('GET /shared-link', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(server).get('/shared-link');
|
const { status, body } = await request(app).get('/shared-link');
|
||||||
|
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
expect(body).toEqual(errorStub.unauthorized);
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get all shared links created by user', async () => {
|
it('should get all shared links created by user', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.get('/shared-link')
|
.get('/shared-link')
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
|
@ -133,12 +140,12 @@ describe(`${SharedLinkController.name} (e2e)`, () => {
|
||||||
expect.objectContaining({ id: linkWithPassword.id }),
|
expect.objectContaining({ id: linkWithPassword.id }),
|
||||||
expect.objectContaining({ id: linkWithMetadata.id }),
|
expect.objectContaining({ id: linkWithMetadata.id }),
|
||||||
expect.objectContaining({ id: linkWithoutMetadata.id }),
|
expect.objectContaining({ id: linkWithoutMetadata.id }),
|
||||||
]),
|
])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not get shared links created by other users', async () => {
|
it('should not get shared links created by other users', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.get('/shared-link')
|
.get('/shared-link')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
|
@ -149,7 +156,7 @@ describe(`${SharedLinkController.name} (e2e)`, () => {
|
||||||
|
|
||||||
describe('GET /shared-link/me', () => {
|
describe('GET /shared-link/me', () => {
|
||||||
it('should not require admin authentication', async () => {
|
it('should not require admin authentication', async () => {
|
||||||
const { status } = await request(server)
|
const { status } = await request(app)
|
||||||
.get('/shared-link/me')
|
.get('/shared-link/me')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
|
@ -157,52 +164,66 @@ describe(`${SharedLinkController.name} (e2e)`, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get data for correct shared link', async () => {
|
it('should get data for correct shared link', async () => {
|
||||||
const { status, body } = await request(server).get('/shared-link/me').query({ key: linkWithAlbum.key });
|
const { status, body } = await request(app)
|
||||||
|
.get('/shared-link/me')
|
||||||
|
.query({ key: linkWithAlbum.key });
|
||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
album,
|
album,
|
||||||
userId: user1.userId,
|
userId: user1.userId,
|
||||||
type: SharedLinkType.ALBUM,
|
type: SharedLinkType.Album,
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return unauthorized for incorrect shared link', async () => {
|
it('should return unauthorized for incorrect shared link', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.get('/shared-link/me')
|
.get('/shared-link/me')
|
||||||
.query({ key: linkWithAlbum.key + 'foo' });
|
.query({ key: linkWithAlbum.key + 'foo' });
|
||||||
|
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
expect(body).toEqual(errorStub.invalidShareKey);
|
expect(body).toEqual(errorDto.invalidShareKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return unauthorized if target has been soft deleted', async () => {
|
it('should return unauthorized if target has been soft deleted', async () => {
|
||||||
const { status, body } = await request(server).get('/shared-link/me').query({ key: linkWithDeletedAlbum.key });
|
const { status, body } = await request(app)
|
||||||
|
.get('/shared-link/me')
|
||||||
|
.query({ key: linkWithDeletedAlbum.key });
|
||||||
|
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
expect(body).toEqual(errorStub.invalidShareKey);
|
expect(body).toEqual(errorDto.invalidShareKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return unauthorized for password protected link', async () => {
|
it('should return unauthorized for password protected link', async () => {
|
||||||
const { status, body } = await request(server).get('/shared-link/me').query({ key: linkWithPassword.key });
|
const { status, body } = await request(app)
|
||||||
|
.get('/shared-link/me')
|
||||||
|
.query({ key: linkWithPassword.key });
|
||||||
|
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
expect(body).toEqual(errorStub.invalidSharePassword);
|
expect(body).toEqual(errorDto.invalidSharePassword);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get data for correct password protected link', async () => {
|
it('should get data for correct password protected link', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.get('/shared-link/me')
|
.get('/shared-link/me')
|
||||||
.query({ key: linkWithPassword.key, password: 'foo' });
|
.query({ key: linkWithPassword.key, password: 'foo' });
|
||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual(expect.objectContaining({ album, userId: user1.userId, type: SharedLinkType.ALBUM }));
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
album,
|
||||||
|
userId: user1.userId,
|
||||||
|
type: SharedLinkType.Album,
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return metadata for album shared link', async () => {
|
it('should return metadata for album shared link', async () => {
|
||||||
const { status, body } = await request(server).get('/shared-link/me').query({ key: linkWithMetadata.key });
|
const { status, body } = await request(app)
|
||||||
|
.get('/shared-link/me')
|
||||||
|
.query({ key: linkWithMetadata.key });
|
||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body.assets).toHaveLength(1);
|
expect(body.assets).toHaveLength(1);
|
||||||
|
@ -211,22 +232,16 @@ describe(`${SharedLinkController.name} (e2e)`, () => {
|
||||||
originalFileName: 'example',
|
originalFileName: 'example',
|
||||||
localDateTime: expect.any(String),
|
localDateTime: expect.any(String),
|
||||||
fileCreatedAt: expect.any(String),
|
fileCreatedAt: expect.any(String),
|
||||||
exifInfo: expect.objectContaining({
|
exifInfo: expect.any(Object),
|
||||||
longitude: -108.400968333333,
|
})
|
||||||
latitude: 39.115,
|
|
||||||
orientation: '1',
|
|
||||||
dateTimeOriginal: expect.any(String),
|
|
||||||
timeZone: 'UTC-4',
|
|
||||||
state: 'Mesa County, Colorado',
|
|
||||||
country: 'United States of America',
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
expect(body.album).toBeDefined();
|
expect(body.album).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not return metadata for album shared link without metadata', async () => {
|
it('should not return metadata for album shared link without metadata', async () => {
|
||||||
const { status, body } = await request(server).get('/shared-link/me').query({ key: linkWithoutMetadata.key });
|
const { status, body } = await request(app)
|
||||||
|
.get('/shared-link/me')
|
||||||
|
.query({ key: linkWithoutMetadata.key });
|
||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body.assets).toHaveLength(1);
|
expect(body.assets).toHaveLength(1);
|
||||||
|
@ -242,127 +257,150 @@ describe(`${SharedLinkController.name} (e2e)`, () => {
|
||||||
|
|
||||||
describe('GET /shared-link/:id', () => {
|
describe('GET /shared-link/:id', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(server).get(`/shared-link/${linkWithAlbum.id}`);
|
const { status, body } = await request(app).get(
|
||||||
|
`/shared-link/${linkWithAlbum.id}`
|
||||||
|
);
|
||||||
|
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
expect(body).toEqual(errorStub.unauthorized);
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get shared link by id', async () => {
|
it('should get shared link by id', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.get(`/shared-link/${linkWithAlbum.id}`)
|
.get(`/shared-link/${linkWithAlbum.id}`)
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual(expect.objectContaining({ album, userId: user1.userId, type: SharedLinkType.ALBUM }));
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
album,
|
||||||
|
userId: user1.userId,
|
||||||
|
type: SharedLinkType.Album,
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not get shared link by id if user has not created the link or it does not exist', async () => {
|
it('should not get shared link by id if user has not created the link or it does not exist', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.get(`/shared-link/${linkWithAlbum.id}`)
|
.get(`/shared-link/${linkWithAlbum.id}`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(expect.objectContaining({ message: 'Shared link not found' }));
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({ message: 'Shared link not found' })
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /shared-link', () => {
|
describe('POST /shared-link', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.post('/shared-link')
|
.post('/shared-link')
|
||||||
.send({ type: SharedLinkType.ALBUM, albumId: uuidStub.notFound });
|
.send({ type: SharedLinkType.Album, albumId: uuidDto.notFound });
|
||||||
|
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
expect(body).toEqual(errorStub.unauthorized);
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require a type and the correspondent asset/album id', async () => {
|
it('should require a type and the correspondent asset/album id', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.post('/shared-link')
|
.post('/shared-link')
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorStub.badRequest());
|
expect(body).toEqual(errorDto.badRequest());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require an asset/album id', async () => {
|
it('should require an asset/album id', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.post('/shared-link')
|
.post('/shared-link')
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
.send({ type: SharedLinkType.ALBUM });
|
.send({ type: SharedLinkType.Album });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(expect.objectContaining({ message: 'Invalid albumId' }));
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({ message: 'Invalid albumId' })
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require a valid asset id', async () => {
|
it('should require a valid asset id', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.post('/shared-link')
|
.post('/shared-link')
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
.send({ type: SharedLinkType.INDIVIDUAL, assetId: uuidStub.notFound });
|
.send({ type: SharedLinkType.Individual, assetId: uuidDto.notFound });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(expect.objectContaining({ message: 'Invalid assetIds' }));
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({ message: 'Invalid assetIds' })
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a shared link', async () => {
|
it('should create a shared link', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.post('/shared-link')
|
.post('/shared-link')
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
.send({ type: SharedLinkType.ALBUM, albumId: album.id });
|
.send({ type: SharedLinkType.Album, albumId: album.id });
|
||||||
|
|
||||||
expect(status).toBe(201);
|
expect(status).toBe(201);
|
||||||
expect(body).toEqual(expect.objectContaining({ type: SharedLinkType.ALBUM, userId: user1.userId }));
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
type: SharedLinkType.Album,
|
||||||
|
userId: user1.userId,
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('PATCH /shared-link/:id', () => {
|
describe('PATCH /shared-link/:id', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.patch(`/shared-link/${linkWithAlbum.id}`)
|
.patch(`/shared-link/${linkWithAlbum.id}`)
|
||||||
.send({ description: 'foo' });
|
.send({ description: 'foo' });
|
||||||
|
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
expect(body).toEqual(errorStub.unauthorized);
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail if invalid link', async () => {
|
it('should fail if invalid link', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.patch(`/shared-link/${uuidStub.notFound}`)
|
.patch(`/shared-link/${uuidDto.notFound}`)
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
.send({ description: 'foo' });
|
.send({ description: 'foo' });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorStub.badRequest());
|
expect(body).toEqual(errorDto.badRequest());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update shared link', async () => {
|
it('should update shared link', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.patch(`/shared-link/${linkWithAlbum.id}`)
|
.patch(`/shared-link/${linkWithAlbum.id}`)
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
.send({ description: 'foo' });
|
.send({ description: 'foo' });
|
||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.objectContaining({ type: SharedLinkType.ALBUM, userId: user1.userId, description: 'foo' }),
|
expect.objectContaining({
|
||||||
|
type: SharedLinkType.Album,
|
||||||
|
userId: user1.userId,
|
||||||
|
description: 'foo',
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('PUT /shared-link/:id/assets', () => {
|
describe('PUT /shared-link/:id/assets', () => {
|
||||||
it('should not add assets to shared link (album)', async () => {
|
it('should not add assets to shared link (album)', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.put(`/shared-link/${linkWithAlbum.id}/assets`)
|
.put(`/shared-link/${linkWithAlbum.id}/assets`)
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
.send({ assetIds: [asset2.id] });
|
.send({ assetIds: [asset2.id] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorStub.badRequest('Invalid shared link type'));
|
expect(body).toEqual(errorDto.badRequest('Invalid shared link type'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add an assets to a shared link (individual)', async () => {
|
it('should add an assets to a shared link (individual)', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.put(`/shared-link/${linkWithAssets.id}/assets`)
|
.put(`/shared-link/${linkWithAssets.id}/assets`)
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
.send({ assetIds: [asset2.id] });
|
.send({ assetIds: [asset2.id] });
|
||||||
|
@ -374,17 +412,17 @@ describe(`${SharedLinkController.name} (e2e)`, () => {
|
||||||
|
|
||||||
describe('DELETE /shared-link/:id/assets', () => {
|
describe('DELETE /shared-link/:id/assets', () => {
|
||||||
it('should not remove assets from a shared link (album)', async () => {
|
it('should not remove assets from a shared link (album)', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.delete(`/shared-link/${linkWithAlbum.id}/assets`)
|
.delete(`/shared-link/${linkWithAlbum.id}/assets`)
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
.send({ assetIds: [asset2.id] });
|
.send({ assetIds: [asset2.id] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorStub.badRequest('Invalid shared link type'));
|
expect(body).toEqual(errorDto.badRequest('Invalid shared link type'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove assets from a shared link (individual)', async () => {
|
it('should remove assets from a shared link (individual)', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.delete(`/shared-link/${linkWithAssets.id}/assets`)
|
.delete(`/shared-link/${linkWithAssets.id}/assets`)
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
.send({ assetIds: [asset2.id] });
|
.send({ assetIds: [asset2.id] });
|
||||||
|
@ -396,23 +434,25 @@ describe(`${SharedLinkController.name} (e2e)`, () => {
|
||||||
|
|
||||||
describe('DELETE /shared-link/:id', () => {
|
describe('DELETE /shared-link/:id', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(server).delete(`/shared-link/${linkWithAlbum.id}`);
|
const { status, body } = await request(app).delete(
|
||||||
|
`/shared-link/${linkWithAlbum.id}`
|
||||||
|
);
|
||||||
|
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
expect(body).toEqual(errorStub.unauthorized);
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail if invalid link', async () => {
|
it('should fail if invalid link', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(app)
|
||||||
.delete(`/shared-link/${uuidStub.notFound}`)
|
.delete(`/shared-link/${uuidDto.notFound}`)
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorStub.badRequest());
|
expect(body).toEqual(errorDto.badRequest());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should delete a shared link', async () => {
|
it('should delete a shared link', async () => {
|
||||||
const { status } = await request(server)
|
const { status } = await request(app)
|
||||||
.delete(`/shared-link/${linkWithAlbum.id}`)
|
.delete(`/shared-link/${linkWithAlbum.id}`)
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
|
@ -2,13 +2,15 @@ import {
|
||||||
AssetResponseDto,
|
AssetResponseDto,
|
||||||
CreateAssetDto,
|
CreateAssetDto,
|
||||||
CreateUserDto,
|
CreateUserDto,
|
||||||
LoginResponseDto,
|
PersonUpdateDto,
|
||||||
createApiKey,
|
createApiKey,
|
||||||
|
createPerson,
|
||||||
createUser,
|
createUser,
|
||||||
defaults,
|
defaults,
|
||||||
login,
|
login,
|
||||||
setAdminOnboarding,
|
setAdminOnboarding,
|
||||||
signUpAdmin,
|
signUpAdmin,
|
||||||
|
updatePerson,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { BrowserContext } from '@playwright/test';
|
import { BrowserContext } from '@playwright/test';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
|
@ -45,7 +47,36 @@ export const asKeyAuth = (key: string) => ({ 'x-api-key': key });
|
||||||
let client: pg.Client | null = null;
|
let client: pg.Client | null = null;
|
||||||
|
|
||||||
export const dbUtils = {
|
export const dbUtils = {
|
||||||
reset: async () => {
|
createFace: async ({
|
||||||
|
assetId,
|
||||||
|
personId,
|
||||||
|
}: {
|
||||||
|
assetId: string;
|
||||||
|
personId: string;
|
||||||
|
}) => {
|
||||||
|
if (!client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const vector = Array.from({ length: 512 }, Math.random);
|
||||||
|
const embedding = `[${vector.join(',')}]`;
|
||||||
|
|
||||||
|
await client.query(
|
||||||
|
'INSERT INTO asset_faces ("assetId", "personId", "embedding") VALUES ($1, $2, $3)',
|
||||||
|
[assetId, personId, embedding]
|
||||||
|
);
|
||||||
|
},
|
||||||
|
setPersonThumbnail: async (personId: string) => {
|
||||||
|
if (!client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.query(
|
||||||
|
`UPDATE "person" set "thumbnailPath" = '/my/awesome/thumbnail.jpg' where "id" = $1`,
|
||||||
|
[personId]
|
||||||
|
);
|
||||||
|
},
|
||||||
|
reset: async (tables?: string[]) => {
|
||||||
try {
|
try {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
client = new pg.Client(
|
client = new pg.Client(
|
||||||
|
@ -54,14 +85,20 @@ export const dbUtils = {
|
||||||
await client.connect();
|
await client.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const table of [
|
tables = tables || [
|
||||||
|
'shared_links',
|
||||||
|
'person',
|
||||||
'albums',
|
'albums',
|
||||||
'assets',
|
'assets',
|
||||||
|
'asset_faces',
|
||||||
|
'activity',
|
||||||
'api_keys',
|
'api_keys',
|
||||||
'user_token',
|
'user_token',
|
||||||
'users',
|
'users',
|
||||||
'system_metadata',
|
'system_metadata',
|
||||||
]) {
|
];
|
||||||
|
|
||||||
|
for (const table of tables) {
|
||||||
await client.query(`DELETE FROM ${table} CASCADE;`);
|
await client.query(`DELETE FROM ${table} CASCADE;`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -165,6 +202,15 @@ export const apiUtils = {
|
||||||
|
|
||||||
return body as AssetResponseDto;
|
return body as AssetResponseDto;
|
||||||
},
|
},
|
||||||
|
createPerson: async (accessToken: string, dto: PersonUpdateDto) => {
|
||||||
|
// TODO fix createPerson to accept a body
|
||||||
|
const { id } = await createPerson({ headers: asBearerAuth(accessToken) });
|
||||||
|
await dbUtils.setPersonThumbnail(id);
|
||||||
|
return updatePerson(
|
||||||
|
{ id, personUpdateDto: dto },
|
||||||
|
{ headers: asBearerAuth(accessToken) }
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cliUtils = {
|
export const cliUtils = {
|
||||||
|
|
|
@ -1,191 +0,0 @@
|
||||||
import { IPersonRepository, LoginResponseDto } from '@app/domain';
|
|
||||||
import { PersonController } from '@app/immich';
|
|
||||||
import { PersonEntity } from '@app/infra/entities';
|
|
||||||
import { INestApplication } from '@nestjs/common';
|
|
||||||
import { errorStub, uuidStub } from '@test/fixtures';
|
|
||||||
import request from 'supertest';
|
|
||||||
import { api } from '../../client';
|
|
||||||
import { testApp } from '../utils';
|
|
||||||
|
|
||||||
describe(`${PersonController.name}`, () => {
|
|
||||||
let app: INestApplication;
|
|
||||||
let server: any;
|
|
||||||
let loginResponse: LoginResponseDto;
|
|
||||||
let accessToken: string;
|
|
||||||
let personRepository: IPersonRepository;
|
|
||||||
let visiblePerson: PersonEntity;
|
|
||||||
let hiddenPerson: PersonEntity;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
app = await testApp.create();
|
|
||||||
server = app.getHttpServer();
|
|
||||||
personRepository = app.get<IPersonRepository>(IPersonRepository);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await testApp.teardown();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await testApp.reset();
|
|
||||||
await api.authApi.adminSignUp(server);
|
|
||||||
loginResponse = await api.authApi.adminLogin(server);
|
|
||||||
accessToken = loginResponse.accessToken;
|
|
||||||
|
|
||||||
const faceAsset = await api.assetApi.upload(server, accessToken, 'face_asset');
|
|
||||||
visiblePerson = await personRepository.create({
|
|
||||||
ownerId: loginResponse.userId,
|
|
||||||
name: 'visible_person',
|
|
||||||
thumbnailPath: '/thumbnail/face_asset',
|
|
||||||
});
|
|
||||||
await personRepository.createFaces([
|
|
||||||
{
|
|
||||||
assetId: faceAsset.id,
|
|
||||||
personId: visiblePerson.id,
|
|
||||||
embedding: Array.from({ length: 512 }, Math.random),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
hiddenPerson = await personRepository.create({
|
|
||||||
ownerId: loginResponse.userId,
|
|
||||||
name: 'hidden_person',
|
|
||||||
isHidden: true,
|
|
||||||
thumbnailPath: '/thumbnail/face_asset',
|
|
||||||
});
|
|
||||||
await personRepository.createFaces([
|
|
||||||
{
|
|
||||||
assetId: faceAsset.id,
|
|
||||||
personId: hiddenPerson.id,
|
|
||||||
embedding: Array.from({ length: 512 }, Math.random),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /person', () => {
|
|
||||||
beforeEach(async () => {});
|
|
||||||
|
|
||||||
it('should require authentication', async () => {
|
|
||||||
const { status, body } = await request(server).get('/person');
|
|
||||||
|
|
||||||
expect(status).toBe(401);
|
|
||||||
expect(body).toEqual(errorStub.unauthorized);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return all people (including hidden)', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.get('/person')
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.query({ withHidden: true });
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual({
|
|
||||||
total: 2,
|
|
||||||
people: [
|
|
||||||
expect.objectContaining({ name: 'visible_person' }),
|
|
||||||
expect.objectContaining({ name: 'hidden_person' }),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return only visible people', async () => {
|
|
||||||
const { status, body } = await request(server).get('/person').set('Authorization', `Bearer ${accessToken}`);
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual({
|
|
||||||
total: 2,
|
|
||||||
people: [expect.objectContaining({ name: 'visible_person' })],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /person/:id', () => {
|
|
||||||
it('should require authentication', async () => {
|
|
||||||
const { status, body } = await request(server).get(`/person/${uuidStub.notFound}`);
|
|
||||||
|
|
||||||
expect(status).toBe(401);
|
|
||||||
expect(body).toEqual(errorStub.unauthorized);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw error if person with id does not exist', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.get(`/person/${uuidStub.notFound}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`);
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorStub.badRequest());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return person information', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.get(`/person/${visiblePerson.id}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`);
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual(expect.objectContaining({ id: visiblePerson.id }));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('PUT /person/:id', () => {
|
|
||||||
it('should require authentication', async () => {
|
|
||||||
const { status, body } = await request(server).put(`/person/${uuidStub.notFound}`);
|
|
||||||
expect(status).toBe(401);
|
|
||||||
expect(body).toEqual(errorStub.unauthorized);
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const { key, type } of [
|
|
||||||
{ key: 'name', type: 'string' },
|
|
||||||
{ key: 'featureFaceAssetId', type: 'string' },
|
|
||||||
{ key: 'isHidden', type: 'boolean value' },
|
|
||||||
]) {
|
|
||||||
it(`should not allow null ${key}`, async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.put(`/person/${visiblePerson.id}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.send({ [key]: null });
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorStub.badRequest([`${key} must be a ${type}`]));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should not accept invalid birth dates', async () => {
|
|
||||||
for (const { birthDate, response } of [
|
|
||||||
{ birthDate: false, response: 'Not found or no person.write access' },
|
|
||||||
{ birthDate: 'false', response: ['birthDate must be a Date instance'] },
|
|
||||||
{ birthDate: '123567', response: 'Not found or no person.write access' },
|
|
||||||
{ birthDate: 123567, response: 'Not found or no person.write access' },
|
|
||||||
]) {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.put(`/person/${uuidStub.notFound}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.send({ birthDate });
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorStub.badRequest(response));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update a date of birth', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.put(`/person/${visiblePerson.id}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.send({ birthDate: '1990-01-01T05:00:00.000Z' });
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toMatchObject({ birthDate: '1990-01-01' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should clear a date of birth', async () => {
|
|
||||||
const person = await personRepository.create({
|
|
||||||
birthDate: new Date('1990-01-01'),
|
|
||||||
ownerId: loginResponse.userId,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(person.birthDate).toBeDefined();
|
|
||||||
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.put(`/person/${person.id}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.send({ birthDate: null });
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toMatchObject({ birthDate: null });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
Reference in a new issue