feat(server,web): libraries (#3124)
* feat: libraries Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
committed by
GitHub
parent
816db700e1
commit
acdc66413c
@@ -1,12 +1,14 @@
|
||||
import { albumApi } from './album-api';
|
||||
import { assetApi } from './asset-api';
|
||||
import { authApi } from './auth-api';
|
||||
import { libraryApi } from './library-api';
|
||||
import { sharedLinkApi } from './shared-link-api';
|
||||
import { userApi } from './user-api';
|
||||
|
||||
export const api = {
|
||||
authApi,
|
||||
assetApi,
|
||||
libraryApi,
|
||||
sharedLinkApi,
|
||||
albumApi,
|
||||
userApi,
|
||||
|
||||
10
server/test/api/library-api.ts
Normal file
10
server/test/api/library-api.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { LibraryResponseDto } from '@app/domain';
|
||||
import request from 'supertest';
|
||||
|
||||
export const libraryApi = {
|
||||
getAll: async (server: any, accessToken: string) => {
|
||||
const { body, status } = await request(server).get(`/library/`).set('Authorization', `Bearer ${accessToken}`);
|
||||
expect(status).toBe(200);
|
||||
return body as LibraryResponseDto[];
|
||||
},
|
||||
};
|
||||
@@ -106,16 +106,16 @@ describe(`${AlbumController.name} (e2e)`, () => {
|
||||
const { status, body } = await request(server)
|
||||
.get('/album?shared=invalid')
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest);
|
||||
expect(status).toEqual(400);
|
||||
expect(body).toEqual(errorStub.badRequest(['shared must be a boolean value']));
|
||||
});
|
||||
|
||||
it('should reject an invalid assetId param', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.get('/album?assetId=invalid')
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest);
|
||||
expect(status).toEqual(400);
|
||||
expect(body).toEqual(errorStub.badRequest(['assetId must be a UUID']));
|
||||
});
|
||||
|
||||
it('should not return shared albums with a deleted owner', async () => {
|
||||
@@ -413,7 +413,7 @@ describe(`${AlbumController.name} (e2e)`, () => {
|
||||
.send({ sharedUserIds: [user1.userId] });
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual({ ...errorStub.badRequest, message: 'Cannot be shared with owner' });
|
||||
expect(body).toEqual(errorStub.badRequest('Cannot be shared with owner'));
|
||||
});
|
||||
|
||||
it('should not be able to add existing user to shared album', async () => {
|
||||
@@ -428,7 +428,7 @@ describe(`${AlbumController.name} (e2e)`, () => {
|
||||
.send({ sharedUserIds: [user2.userId] });
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual({ ...errorStub.badRequest, message: 'User already added' });
|
||||
expect(body).toEqual(errorStub.badRequest('User already added'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,6 +45,7 @@ let assetCount = 0;
|
||||
const createAsset = (
|
||||
repository: IAssetRepository,
|
||||
loginResponse: LoginResponseDto,
|
||||
libraryId: string,
|
||||
createdAt: Date,
|
||||
): Promise<AssetEntity> => {
|
||||
const id = assetCount++;
|
||||
@@ -54,6 +55,7 @@ const createAsset = (
|
||||
originalPath: `/tests/test_${id}`,
|
||||
deviceAssetId: `test_${id}`,
|
||||
deviceId: 'e2e-test',
|
||||
libraryId,
|
||||
fileCreatedAt: createdAt,
|
||||
fileModifiedAt: new Date(),
|
||||
type: AssetType.IMAGE,
|
||||
@@ -87,15 +89,19 @@ describe(`${AssetController.name} (e2e)`, () => {
|
||||
await api.authApi.adminSignUp(server);
|
||||
const admin = await api.authApi.adminLogin(server);
|
||||
|
||||
const libraries = await api.libraryApi.getAll(server, admin.accessToken);
|
||||
const defaultLibrary = libraries[0];
|
||||
|
||||
await api.userApi.create(server, admin.accessToken, user1Dto);
|
||||
user1 = await api.authApi.login(server, { email: user1Dto.email, password: user1Dto.password });
|
||||
asset1 = await createAsset(assetRepository, user1, new Date('1970-01-01'));
|
||||
asset2 = await createAsset(assetRepository, user1, new Date('1970-01-02'));
|
||||
asset3 = await createAsset(assetRepository, user1, new Date('1970-02-01'));
|
||||
|
||||
asset1 = await createAsset(assetRepository, user1, defaultLibrary.id, new Date('1970-01-01'));
|
||||
asset2 = await createAsset(assetRepository, user1, defaultLibrary.id, new Date('1970-01-02'));
|
||||
asset3 = await createAsset(assetRepository, user1, defaultLibrary.id, new Date('1970-02-01'));
|
||||
|
||||
await api.userApi.create(server, admin.accessToken, user2Dto);
|
||||
user2 = await api.authApi.login(server, { email: user2Dto.email, password: user2Dto.password });
|
||||
asset4 = await createAsset(assetRepository, user2, new Date('1970-01-01'));
|
||||
asset4 = await createAsset(assetRepository, user2, defaultLibrary.id, new Date('1970-01-01'));
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -139,7 +145,7 @@ describe(`${AssetController.name} (e2e)`, () => {
|
||||
.attach('assetData', randomBytes(32), 'example.jpg')
|
||||
.field(dto);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest);
|
||||
expect(body).toEqual(errorStub.badRequest());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -192,7 +198,7 @@ describe(`${AssetController.name} (e2e)`, () => {
|
||||
.put(`/asset/${uuidStub.invalid}`)
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest);
|
||||
expect(body).toEqual(errorStub.badRequest(['id must be a UUID']));
|
||||
});
|
||||
|
||||
it('should require access', async () => {
|
||||
|
||||
@@ -52,18 +52,33 @@ describe(`${AuthController.name} (e2e)`, () => {
|
||||
});
|
||||
|
||||
const invalid = [
|
||||
{ should: 'require an email address', data: { firstName, lastName, password } },
|
||||
{ should: 'require a password', data: { firstName, lastName, email } },
|
||||
{ should: 'require a first name ', data: { lastName, email, password } },
|
||||
{ should: 'require a last name ', data: { firstName, email, password } },
|
||||
{ should: 'require a valid email', data: { firstName, lastName, email: 'immich', password } },
|
||||
{
|
||||
should: 'require an email address',
|
||||
data: { firstName, lastName, password },
|
||||
},
|
||||
{
|
||||
should: 'require a password',
|
||||
data: { firstName, lastName, email },
|
||||
},
|
||||
{
|
||||
should: 'require a first name ',
|
||||
data: { lastName, email, password },
|
||||
},
|
||||
{
|
||||
should: 'require a last name ',
|
||||
data: { firstName, email, password },
|
||||
},
|
||||
{
|
||||
should: 'require a valid email',
|
||||
data: { firstName, lastName, email: 'immich', password },
|
||||
},
|
||||
];
|
||||
|
||||
for (const { should, data } of invalid) {
|
||||
it(`should ${should}`, async () => {
|
||||
const { status, body } = await request(server).post('/auth/admin-sign-up').send(data);
|
||||
expect(status).toEqual(400);
|
||||
expect(body).toEqual(errorStub.badRequest);
|
||||
expect(body).toEqual(errorStub.badRequest());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -102,7 +117,7 @@ describe(`${AuthController.name} (e2e)`, () => {
|
||||
.post('/auth/admin-sign-up')
|
||||
.send({ ...adminSignupStub, [key]: null });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest);
|
||||
expect(body).toEqual(errorStub.badRequest());
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -120,7 +135,7 @@ describe(`${AuthController.name} (e2e)`, () => {
|
||||
.post('/auth/login')
|
||||
.send({ ...loginStub.admin, [key]: null });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest);
|
||||
expect(body).toEqual(errorStub.badRequest());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -225,7 +240,7 @@ describe(`${AuthController.name} (e2e)`, () => {
|
||||
.send({ ...changePasswordStub, [key]: null })
|
||||
.set('Authorization', `Bearer ${accessToken}`);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest);
|
||||
expect(body).toEqual(errorStub.badRequest());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
494
server/test/e2e/library.e2e-spec.ts
Normal file
494
server/test/e2e/library.e2e-spec.ts
Normal file
@@ -0,0 +1,494 @@
|
||||
import { LoginResponseDto } from '@app/domain';
|
||||
import { AppModule, LibraryController } from '@app/immich';
|
||||
import { LibraryType } from '@app/infra/entities';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import request from 'supertest';
|
||||
import { errorStub, userStub, uuidStub } from '../fixtures';
|
||||
import { api, db } from '../test-utils';
|
||||
|
||||
describe(`${LibraryController.name} (e2e)`, () => {
|
||||
let app: INestApplication;
|
||||
let server: any;
|
||||
let loginResponse: LoginResponseDto;
|
||||
let accessToken: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = await moduleFixture.createNestApplication().init();
|
||||
server = app.getHttpServer();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.reset();
|
||||
await api.adminSignUp(server);
|
||||
loginResponse = await api.adminLogin(server);
|
||||
accessToken = loginResponse.accessToken;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await db.disconnect();
|
||||
await app.close();
|
||||
});
|
||||
|
||||
describe('GET /library', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(server).get('/library');
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorStub.unauthorized);
|
||||
});
|
||||
|
||||
it('should start with a default upload library', async () => {
|
||||
const { status, body } = await request(server).get('/library').set('Authorization', `Bearer ${accessToken}`);
|
||||
expect(status).toBe(200);
|
||||
expect(body).toHaveLength(1);
|
||||
expect(body).toEqual([
|
||||
{
|
||||
id: expect.any(String),
|
||||
ownerId: loginResponse.userId,
|
||||
type: LibraryType.UPLOAD,
|
||||
name: 'Default Library',
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
refreshedAt: null,
|
||||
assetCount: 0,
|
||||
importPaths: [],
|
||||
exclusionPatterns: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /library', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(server).post('/library').send({});
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorStub.unauthorized);
|
||||
});
|
||||
|
||||
describe('external library', () => {
|
||||
it('with default settings', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.post('/library')
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ type: LibraryType.EXTERNAL });
|
||||
expect(status).toBe(201);
|
||||
|
||||
expect(body).toEqual({
|
||||
id: expect.any(String),
|
||||
ownerId: loginResponse.userId,
|
||||
type: LibraryType.EXTERNAL,
|
||||
name: 'New External Library',
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
refreshedAt: null,
|
||||
assetCount: 0,
|
||||
importPaths: [],
|
||||
exclusionPatterns: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('with name', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.post('/library')
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ type: LibraryType.EXTERNAL, name: 'My Awesome Library' });
|
||||
expect(status).toBe(201);
|
||||
|
||||
expect(body).toEqual({
|
||||
id: expect.any(String),
|
||||
ownerId: loginResponse.userId,
|
||||
type: LibraryType.EXTERNAL,
|
||||
name: 'My Awesome Library',
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
refreshedAt: null,
|
||||
assetCount: 0,
|
||||
importPaths: [],
|
||||
exclusionPatterns: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('with import paths', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.post('/library')
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ type: LibraryType.EXTERNAL, importPaths: ['/path/to/import'] });
|
||||
expect(status).toBe(201);
|
||||
|
||||
expect(body).toEqual({
|
||||
id: expect.any(String),
|
||||
ownerId: loginResponse.userId,
|
||||
type: LibraryType.EXTERNAL,
|
||||
name: 'New External Library',
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
refreshedAt: null,
|
||||
assetCount: 0,
|
||||
importPaths: ['/path/to/import'],
|
||||
exclusionPatterns: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('with exclusion patterns', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.post('/library')
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ type: LibraryType.EXTERNAL, exclusionPatterns: ['**/Raw/**'] });
|
||||
expect(status).toBe(201);
|
||||
|
||||
expect(body).toEqual({
|
||||
id: expect.any(String),
|
||||
ownerId: loginResponse.userId,
|
||||
type: LibraryType.EXTERNAL,
|
||||
name: 'New External Library',
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
refreshedAt: null,
|
||||
assetCount: 0,
|
||||
importPaths: [],
|
||||
exclusionPatterns: ['**/Raw/**'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('upload library', () => {
|
||||
it('with default settings', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.post('/library')
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ type: LibraryType.UPLOAD });
|
||||
expect(status).toBe(201);
|
||||
|
||||
expect(body).toEqual({
|
||||
id: expect.any(String),
|
||||
ownerId: loginResponse.userId,
|
||||
type: LibraryType.UPLOAD,
|
||||
name: 'New Upload Library',
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
refreshedAt: null,
|
||||
assetCount: 0,
|
||||
importPaths: [],
|
||||
exclusionPatterns: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('with name', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.post('/library')
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ type: LibraryType.UPLOAD, name: 'My Awesome Library' });
|
||||
expect(status).toBe(201);
|
||||
|
||||
expect(body).toEqual({
|
||||
id: expect.any(String),
|
||||
ownerId: loginResponse.userId,
|
||||
type: LibraryType.UPLOAD,
|
||||
name: 'My Awesome Library',
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
refreshedAt: null,
|
||||
assetCount: 0,
|
||||
importPaths: [],
|
||||
exclusionPatterns: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('with import paths should fail', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.post('/library')
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ type: LibraryType.UPLOAD, importPaths: ['/path/to/import'] });
|
||||
expect(status).toBe(400);
|
||||
|
||||
expect(body).toEqual(errorStub.badRequest('Upload libraries cannot have import paths'));
|
||||
});
|
||||
|
||||
it('with exclusion patterns should fail', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.post('/library')
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ type: LibraryType.UPLOAD, exclusionPatterns: ['**/Raw/**'] });
|
||||
expect(status).toBe(400);
|
||||
|
||||
expect(body).toEqual(errorStub.badRequest('Upload libraries cannot have exclusion patterns'));
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow a user to create a library', async () => {
|
||||
await api.userCreate(server, accessToken, userStub.user1);
|
||||
|
||||
const loginResponse = await api.login(server, {
|
||||
email: userStub.user1.email,
|
||||
password: userStub.user1.password ?? '',
|
||||
});
|
||||
|
||||
const { status, body } = await request(server)
|
||||
.post('/library')
|
||||
.set('Authorization', `Bearer ${loginResponse.accessToken}`)
|
||||
.send({ type: LibraryType.EXTERNAL });
|
||||
|
||||
expect(status).toBe(201);
|
||||
expect(body).toEqual({
|
||||
id: expect.any(String),
|
||||
ownerId: loginResponse.userId,
|
||||
type: LibraryType.EXTERNAL,
|
||||
name: 'New External Library',
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
refreshedAt: null,
|
||||
assetCount: 0,
|
||||
importPaths: [],
|
||||
exclusionPatterns: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /library/:id', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(server).put(`/library/${uuidStub.notFound}`).send({});
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorStub.unauthorized);
|
||||
});
|
||||
|
||||
describe('external library', () => {
|
||||
let libraryId: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Create an external library with default settings
|
||||
const { status, body } = await request(server)
|
||||
.post('/library')
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ type: LibraryType.EXTERNAL });
|
||||
|
||||
expect(status).toBe(201);
|
||||
|
||||
libraryId = body.id;
|
||||
});
|
||||
|
||||
it('should change the library name', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.put(`/library/${libraryId}`)
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ name: 'New Library Name' });
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual({
|
||||
id: expect.any(String),
|
||||
ownerId: loginResponse.userId,
|
||||
type: LibraryType.EXTERNAL,
|
||||
name: 'New Library Name',
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
refreshedAt: null,
|
||||
assetCount: 0,
|
||||
importPaths: [],
|
||||
exclusionPatterns: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should not set an empty name', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.put(`/library/${libraryId}`)
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ name: '' });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest(['name should not be empty']));
|
||||
});
|
||||
|
||||
it('should change the import paths', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.put(`/library/${libraryId}`)
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ importPaths: ['/path/to/import'] });
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual({
|
||||
id: expect.any(String),
|
||||
ownerId: loginResponse.userId,
|
||||
type: LibraryType.EXTERNAL,
|
||||
name: 'New External Library',
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
refreshedAt: null,
|
||||
assetCount: 0,
|
||||
importPaths: ['/path/to/import'],
|
||||
exclusionPatterns: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow an empty import path', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.put(`/library/${libraryId}`)
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ importPaths: [''] });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest(['each value in importPaths should not be empty']));
|
||||
});
|
||||
|
||||
it('should change the exclusion pattern', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.put(`/library/${libraryId}`)
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ exclusionPatterns: [''] });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest(['each value in exclusionPatterns should not be empty']));
|
||||
});
|
||||
|
||||
it('should not allow an empty exclusion pattern', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.put(`/library/${libraryId}`)
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ importPaths: [''] });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest(['each value in importPaths should not be empty']));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /library/:id', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(server).get(`/library/${uuidStub.notFound}`);
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorStub.unauthorized);
|
||||
});
|
||||
|
||||
it('should get library by id', async () => {
|
||||
let libraryId: string;
|
||||
{
|
||||
const { status, body } = await request(server)
|
||||
.post('/library')
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ type: LibraryType.EXTERNAL });
|
||||
expect(status).toBe(201);
|
||||
libraryId = body.id;
|
||||
}
|
||||
const { status, body } = await request(server)
|
||||
.get(`/library/${libraryId}`)
|
||||
.set('Authorization', `Bearer ${accessToken}`);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual({
|
||||
id: expect.any(String),
|
||||
ownerId: loginResponse.userId,
|
||||
type: LibraryType.EXTERNAL,
|
||||
name: 'New External Library',
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
refreshedAt: null,
|
||||
assetCount: 0,
|
||||
importPaths: [],
|
||||
exclusionPatterns: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("should not allow getting another user's library", async () => {
|
||||
await api.userCreate(server, accessToken, userStub.user1);
|
||||
|
||||
const loginResponse = await api.login(server, {
|
||||
email: userStub.user1.email,
|
||||
password: userStub.user1.password ?? '',
|
||||
});
|
||||
|
||||
let libraryId: string;
|
||||
{
|
||||
const { status, body } = await request(server)
|
||||
.post('/library')
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ type: LibraryType.EXTERNAL });
|
||||
expect(status).toBe(201);
|
||||
libraryId = body.id;
|
||||
}
|
||||
|
||||
const { status, body } = await request(server)
|
||||
.get(`/library/${libraryId}`)
|
||||
.set('Authorization', `Bearer ${loginResponse.accessToken}`);
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest('Not found or no library.read access'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /library/:id', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(server).delete(`/library/${uuidStub.notFound}`);
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorStub.unauthorized);
|
||||
});
|
||||
|
||||
it('should not delete the last upload library', async () => {
|
||||
const [defaultLibrary] = await api.libraryApi.getAll(server, accessToken);
|
||||
expect(defaultLibrary).toBeDefined();
|
||||
|
||||
const { status, body } = await request(server)
|
||||
.delete(`/library/${defaultLibrary.id}`)
|
||||
.set('Authorization', `Bearer ${accessToken}`);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.noDeleteUploadLibrary);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /library/:id/statistics', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(server).get(`/library/${uuidStub.notFound}/statistics`);
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorStub.unauthorized);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /library/:id/scan', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(server).post(`/library/${uuidStub.notFound}/scan`).send({});
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorStub.unauthorized);
|
||||
});
|
||||
|
||||
it('should scan external library', async () => {
|
||||
let libraryId: string;
|
||||
{
|
||||
const { status, body } = await request(server)
|
||||
.post('/library')
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ type: LibraryType.EXTERNAL });
|
||||
expect(status).toBe(201);
|
||||
libraryId = body.id;
|
||||
}
|
||||
|
||||
const { status, body } = await request(server)
|
||||
.post(`/library/${libraryId}/scan`)
|
||||
.set('Authorization', `Bearer ${accessToken}`);
|
||||
|
||||
expect(status).toBe(201);
|
||||
expect(body).toEqual({});
|
||||
});
|
||||
|
||||
it('should not scan an upload library', async () => {
|
||||
let libraryId: string;
|
||||
{
|
||||
const { status, body } = await request(server)
|
||||
.post('/library')
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ type: LibraryType.UPLOAD });
|
||||
expect(status).toBe(201);
|
||||
libraryId = body.id;
|
||||
}
|
||||
|
||||
const { status, body } = await request(server)
|
||||
.post(`/library/${libraryId}/scan`)
|
||||
.set('Authorization', `Bearer ${accessToken}`);
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest('Can only refresh external libraries'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /library/:id/removeOffline', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(server).post(`/library/${uuidStub.notFound}/removeOffline`).send({});
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorStub.unauthorized);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -37,7 +37,7 @@ describe(`${OAuthController.name} (e2e)`, () => {
|
||||
it(`should throw an error if a redirect uri is not provided`, async () => {
|
||||
const { status, body } = await request(server).post('/oauth/authorize').send({});
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest);
|
||||
expect(body).toEqual(errorStub.badRequest(['redirectUri must be a string', 'redirectUri should not be empty']));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,7 +110,7 @@ describe(`${PersonController.name}`, () => {
|
||||
.set('Authorization', `Bearer ${accessToken}`);
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest);
|
||||
expect(body).toEqual(errorStub.badRequest());
|
||||
});
|
||||
|
||||
it('should return person information', async () => {
|
||||
@@ -130,25 +130,34 @@ describe(`${PersonController.name}`, () => {
|
||||
expect(body).toEqual(errorStub.unauthorized);
|
||||
});
|
||||
|
||||
for (const key of ['name', 'featureFaceAssetId', 'isHidden']) {
|
||||
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);
|
||||
expect(body).toEqual(errorStub.badRequest([`${key} must be a ${type}`]));
|
||||
});
|
||||
}
|
||||
|
||||
it('should not accept invalid birth dates', async () => {
|
||||
for (const birthDate of [false, 'false', '123567', 123456]) {
|
||||
for (const { birthDate, response } of [
|
||||
{ birthDate: false, response: ['id must be a UUID'] },
|
||||
{ birthDate: 'false', response: ['birthDate must be a Date instance'] },
|
||||
{ birthDate: '123567', response: ['id must be a UUID'] },
|
||||
{ birthDate: 123456, response: ['id must be a UUID'] },
|
||||
]) {
|
||||
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);
|
||||
expect(body).toEqual(errorStub.badRequest(response));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest);
|
||||
expect(body).toEqual(errorStub.badRequest());
|
||||
});
|
||||
|
||||
it('should require an asset/album id', async () => {
|
||||
@@ -211,7 +211,7 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
||||
.send({ description: 'foo' });
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest);
|
||||
expect(body).toEqual(errorStub.badRequest());
|
||||
});
|
||||
|
||||
it('should update shared link', async () => {
|
||||
@@ -241,7 +241,7 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest);
|
||||
expect(body).toEqual(errorStub.badRequest());
|
||||
});
|
||||
|
||||
it('should update shared link', async () => {
|
||||
|
||||
@@ -138,7 +138,7 @@ describe(`${UserController.name}`, () => {
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ ...userSignupStub, [key]: null });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest);
|
||||
expect(body).toEqual(errorStub.badRequest());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ describe(`${UserController.name}`, () => {
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send({ ...userStub.admin, [key]: null });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest);
|
||||
expect(body).toEqual(errorStub.badRequest());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
111
server/test/fixtures/asset.stub.ts
vendored
111
server/test/fixtures/asset.stub.ts
vendored
@@ -1,6 +1,7 @@
|
||||
import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities';
|
||||
import { authStub } from './auth.stub';
|
||||
import { fileStub } from './file.stub';
|
||||
import { libraryStub } from './library.stub';
|
||||
import { userStub } from './user.stub';
|
||||
|
||||
export const assetStub = {
|
||||
@@ -33,6 +34,10 @@ export const assetStub = {
|
||||
faces: [],
|
||||
sidecarPath: null,
|
||||
isReadOnly: false,
|
||||
isOffline: false,
|
||||
isExternal: false,
|
||||
libraryId: 'library-id',
|
||||
library: libraryStub.uploadLibrary1,
|
||||
}),
|
||||
noWebpPath: Object.freeze<AssetEntity>({
|
||||
id: 'asset-id',
|
||||
@@ -63,6 +68,10 @@ export const assetStub = {
|
||||
faces: [],
|
||||
sidecarPath: null,
|
||||
isReadOnly: false,
|
||||
isOffline: false,
|
||||
isExternal: false,
|
||||
libraryId: 'library-id',
|
||||
library: libraryStub.uploadLibrary1,
|
||||
exifInfo: {
|
||||
fileSizeInByte: 123_000,
|
||||
} as ExifEntity,
|
||||
@@ -87,8 +96,12 @@ export const assetStub = {
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
isOffline: false,
|
||||
libraryId: 'library-id',
|
||||
library: libraryStub.uploadLibrary1,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
isExternal: false,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
tags: [],
|
||||
@@ -119,8 +132,86 @@ export const assetStub = {
|
||||
isReadOnly: false,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
isExternal: false,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
isOffline: false,
|
||||
libraryId: 'library-id',
|
||||
library: libraryStub.uploadLibrary1,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.jpg',
|
||||
faces: [],
|
||||
sidecarPath: null,
|
||||
exifInfo: {
|
||||
fileSizeInByte: 5_000,
|
||||
} as ExifEntity,
|
||||
}),
|
||||
external: Object.freeze<AssetEntity>({
|
||||
id: 'asset-id',
|
||||
deviceAssetId: 'device-asset-id',
|
||||
fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
owner: userStub.user1,
|
||||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/data/user1/photo.jpg',
|
||||
resizePath: '/uploads/user-id/thumbs/path.jpg',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
webpPath: '/uploads/user-id/webp/path.ext',
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
isExternal: true,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
isOffline: false,
|
||||
libraryId: 'library-id',
|
||||
library: libraryStub.uploadLibrary1,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.jpg',
|
||||
faces: [],
|
||||
sidecarPath: null,
|
||||
exifInfo: {
|
||||
fileSizeInByte: 5_000,
|
||||
} as ExifEntity,
|
||||
}),
|
||||
offline: Object.freeze<AssetEntity>({
|
||||
id: 'asset-id',
|
||||
deviceAssetId: 'device-asset-id',
|
||||
fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
owner: userStub.user1,
|
||||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.jpg',
|
||||
resizePath: '/uploads/user-id/thumbs/path.jpg',
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.IMAGE,
|
||||
webpPath: '/uploads/user-id/webp/path.ext',
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
isExternal: false,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
isOffline: true,
|
||||
libraryId: 'library-id',
|
||||
library: libraryStub.uploadLibrary1,
|
||||
tags: [],
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.jpg',
|
||||
@@ -149,7 +240,11 @@ export const assetStub = {
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isExternal: false,
|
||||
isReadOnly: false,
|
||||
isOffline: false,
|
||||
libraryId: 'library-id',
|
||||
library: libraryStub.uploadLibrary1,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
livePhotoVideo: null,
|
||||
@@ -184,6 +279,10 @@ export const assetStub = {
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
isExternal: false,
|
||||
isOffline: false,
|
||||
libraryId: 'library-id',
|
||||
library: libraryStub.uploadLibrary1,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
livePhotoVideo: null,
|
||||
@@ -204,6 +303,8 @@ export const assetStub = {
|
||||
isVisible: false,
|
||||
fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
libraryId: 'library-id',
|
||||
library: libraryStub.uploadLibrary1,
|
||||
exifInfo: {
|
||||
fileSizeInByte: 100_000,
|
||||
},
|
||||
@@ -218,6 +319,8 @@ export const assetStub = {
|
||||
isVisible: true,
|
||||
fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
libraryId: 'library-id',
|
||||
library: libraryStub.uploadLibrary1,
|
||||
exifInfo: {
|
||||
fileSizeInByte: 25_000,
|
||||
},
|
||||
@@ -244,6 +347,10 @@ export const assetStub = {
|
||||
isFavorite: false,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
isExternal: false,
|
||||
isOffline: false,
|
||||
libraryId: 'library-id',
|
||||
library: libraryStub.uploadLibrary1,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
livePhotoVideo: null,
|
||||
@@ -278,6 +385,10 @@ export const assetStub = {
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
isExternal: false,
|
||||
isOffline: false,
|
||||
libraryId: 'library-id',
|
||||
library: libraryStub.uploadLibrary1,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
livePhotoVideo: null,
|
||||
|
||||
11
server/test/fixtures/error.stub.ts
vendored
11
server/test/fixtures/error.stub.ts
vendored
@@ -24,11 +24,11 @@ export const errorStub = {
|
||||
statusCode: 401,
|
||||
message: 'Invalid share key',
|
||||
},
|
||||
badRequest: {
|
||||
badRequest: (message: any = null) => ({
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: expect.any(Array),
|
||||
},
|
||||
message: message ?? expect.anything(),
|
||||
}),
|
||||
noPermission: {
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
@@ -44,4 +44,9 @@ export const errorStub = {
|
||||
statusCode: 400,
|
||||
message: 'The server already has an admin',
|
||||
},
|
||||
noDeleteUploadLibrary: {
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: 'Cannot delete the last upload library',
|
||||
},
|
||||
};
|
||||
|
||||
1
server/test/fixtures/index.ts
vendored
1
server/test/fixtures/index.ts
vendored
@@ -7,6 +7,7 @@ export * from './device.stub';
|
||||
export * from './error.stub';
|
||||
export * from './face.stub';
|
||||
export * from './file.stub';
|
||||
export * from './library.stub';
|
||||
export * from './media.stub';
|
||||
export * from './partner.stub';
|
||||
export * from './person.stub';
|
||||
|
||||
33
server/test/fixtures/library.stub.ts
vendored
Normal file
33
server/test/fixtures/library.stub.ts
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
import { LibraryEntity, LibraryType } from '@app/infra/entities';
|
||||
import { userStub } from './user.stub';
|
||||
|
||||
export const libraryStub = {
|
||||
uploadLibrary1: Object.freeze<LibraryEntity>({
|
||||
id: 'library-id',
|
||||
name: 'test_library',
|
||||
assets: [],
|
||||
owner: userStub.user1,
|
||||
ownerId: 'user-id',
|
||||
type: LibraryType.UPLOAD,
|
||||
importPaths: [],
|
||||
createdAt: new Date('2022-01-01'),
|
||||
updatedAt: new Date('2022-01-01'),
|
||||
refreshedAt: null,
|
||||
isVisible: true,
|
||||
exclusionPatterns: [],
|
||||
}),
|
||||
externalLibrary1: Object.freeze<LibraryEntity>({
|
||||
id: 'library-id',
|
||||
name: 'test_library',
|
||||
assets: [],
|
||||
owner: userStub.externalPath1,
|
||||
ownerId: 'user-id',
|
||||
type: LibraryType.EXTERNAL,
|
||||
importPaths: [],
|
||||
createdAt: new Date('2023-01-01'),
|
||||
updatedAt: new Date('2023-01-01'),
|
||||
refreshedAt: null,
|
||||
isVisible: true,
|
||||
exclusionPatterns: [],
|
||||
}),
|
||||
};
|
||||
9
server/test/fixtures/shared-link.stub.ts
vendored
9
server/test/fixtures/shared-link.stub.ts
vendored
@@ -2,6 +2,7 @@ import { AlbumResponseDto, AssetResponseDto, ExifResponseDto, mapUser, SharedLin
|
||||
import { AssetType, SharedLinkEntity, SharedLinkType, UserEntity } from '@app/infra/entities';
|
||||
import { assetStub } from './asset.stub';
|
||||
import { authStub } from './auth.stub';
|
||||
import { libraryStub } from './library.stub';
|
||||
import { userStub } from './user.stub';
|
||||
|
||||
const today = new Date();
|
||||
@@ -50,6 +51,9 @@ const assetResponse: AssetResponseDto = {
|
||||
resized: false,
|
||||
thumbhash: null,
|
||||
fileModifiedAt: today,
|
||||
isExternal: false,
|
||||
isReadOnly: false,
|
||||
isOffline: false,
|
||||
fileCreatedAt: today,
|
||||
updatedAt: today,
|
||||
isFavorite: false,
|
||||
@@ -64,6 +68,7 @@ const assetResponse: AssetResponseDto = {
|
||||
tags: [],
|
||||
people: [],
|
||||
checksum: 'ZmlsZSBoYXNo',
|
||||
libraryId: 'library-id',
|
||||
};
|
||||
|
||||
const albumResponse: AlbumResponseDto = {
|
||||
@@ -173,7 +178,11 @@ export const sharedLinkStub = {
|
||||
updatedAt: today,
|
||||
isFavorite: false,
|
||||
isArchived: false,
|
||||
isExternal: false,
|
||||
isReadOnly: false,
|
||||
isOffline: false,
|
||||
libraryId: 'library-id',
|
||||
library: libraryStub.uploadLibrary1,
|
||||
smartInfo: {
|
||||
assetId: 'id_1',
|
||||
tags: [],
|
||||
|
||||
34
server/test/fixtures/user.stub.ts
vendored
34
server/test/fixtures/user.stub.ts
vendored
@@ -70,4 +70,38 @@ export const userStub = {
|
||||
assets: [],
|
||||
memoriesEnabled: true,
|
||||
}),
|
||||
externalPath1: Object.freeze<UserEntity>({
|
||||
...authStub.user1,
|
||||
password: 'immich_password',
|
||||
firstName: 'immich_first_name',
|
||||
lastName: 'immich_last_name',
|
||||
storageLabel: 'label-1',
|
||||
externalPath: '/data/user1',
|
||||
oauthId: '',
|
||||
shouldChangePassword: false,
|
||||
profileImagePath: '',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
tags: [],
|
||||
assets: [],
|
||||
memoriesEnabled: true,
|
||||
}),
|
||||
externalPath2: Object.freeze<UserEntity>({
|
||||
...authStub.user1,
|
||||
password: 'immich_password',
|
||||
firstName: 'immich_first_name',
|
||||
lastName: 'immich_last_name',
|
||||
storageLabel: 'label-1',
|
||||
externalPath: '/data/user2',
|
||||
oauthId: '',
|
||||
shouldChangePassword: false,
|
||||
profileImagePath: '',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
tags: [],
|
||||
assets: [],
|
||||
memoriesEnabled: true,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ export interface IAccessRepositoryMock {
|
||||
asset: jest.Mocked<IAccessRepository['asset']>;
|
||||
album: jest.Mocked<IAccessRepository['album']>;
|
||||
library: jest.Mocked<IAccessRepository['library']>;
|
||||
timeline: jest.Mocked<IAccessRepository['timeline']>;
|
||||
person: jest.Mocked<IAccessRepository['person']>;
|
||||
}
|
||||
|
||||
@@ -23,6 +24,11 @@ export const newAccessRepositoryMock = (): IAccessRepositoryMock => {
|
||||
},
|
||||
|
||||
library: {
|
||||
hasOwnerAccess: jest.fn(),
|
||||
hasPartnerAccess: jest.fn(),
|
||||
},
|
||||
|
||||
timeline: {
|
||||
hasPartnerAccess: jest.fn(),
|
||||
},
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { IAssetRepository } from '@app/domain';
|
||||
|
||||
export const newAssetRepositoryMock = (): jest.Mocked<IAssetRepository> => {
|
||||
return {
|
||||
create: jest.fn(),
|
||||
upsertExif: jest.fn(),
|
||||
getByDate: jest.fn(),
|
||||
getByIds: jest.fn().mockResolvedValue([]),
|
||||
@@ -14,6 +15,9 @@ export const newAssetRepositoryMock = (): jest.Mocked<IAssetRepository> => {
|
||||
getLastUpdatedAssetForAlbumId: jest.fn(),
|
||||
getAll: jest.fn().mockResolvedValue({ items: [], hasNextPage: false }),
|
||||
updateAll: jest.fn(),
|
||||
getByLibraryId: jest.fn(),
|
||||
getById: jest.fn(),
|
||||
getByLibraryIdAndOriginalPath: jest.fn(),
|
||||
deleteAll: jest.fn(),
|
||||
save: jest.fn(),
|
||||
findLivePhotoMatch: jest.fn(),
|
||||
@@ -21,5 +25,6 @@ export const newAssetRepositoryMock = (): jest.Mocked<IAssetRepository> => {
|
||||
getStatistics: jest.fn(),
|
||||
getByTimeBucket: jest.fn(),
|
||||
getTimeBuckets: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ export * from './communication.repository.mock';
|
||||
export * from './crypto.repository.mock';
|
||||
export * from './face.repository.mock';
|
||||
export * from './job.repository.mock';
|
||||
export * from './library.repository.mock';
|
||||
export * from './machine-learning.repository.mock';
|
||||
export * from './media.repository.mock';
|
||||
export * from './partner.repository.mock';
|
||||
|
||||
21
server/test/repositories/library.repository.mock.ts
Normal file
21
server/test/repositories/library.repository.mock.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ILibraryRepository } from '@app/domain';
|
||||
|
||||
export const newLibraryRepositoryMock = (): jest.Mocked<ILibraryRepository> => {
|
||||
return {
|
||||
get: jest.fn(),
|
||||
getCountForUser: jest.fn(),
|
||||
getAllByUserId: jest.fn(),
|
||||
create: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
softDelete: jest.fn(),
|
||||
update: jest.fn(),
|
||||
getStatistics: jest.fn(),
|
||||
getDefaultUploadLibrary: jest.fn(),
|
||||
getUploadLibraryCount: jest.fn(),
|
||||
getOnlineAssetPaths: jest.fn(),
|
||||
getAssetIds: jest.fn(),
|
||||
existsByName: jest.fn(),
|
||||
getAllDeleted: jest.fn(),
|
||||
getAll: jest.fn(),
|
||||
};
|
||||
};
|
||||
@@ -12,5 +12,7 @@ export const newStorageRepositoryMock = (): jest.Mocked<IStorageRepository> => {
|
||||
mkdirSync: jest.fn(),
|
||||
checkDiskUsage: jest.fn(),
|
||||
readdir: jest.fn(),
|
||||
stat: jest.fn(),
|
||||
crawl: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
175
server/test/test-utils.ts
Normal file
175
server/test/test-utils.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import {
|
||||
AdminSignupResponseDto,
|
||||
AlbumResponseDto,
|
||||
AuthDeviceResponseDto,
|
||||
AuthUserDto,
|
||||
CreateUserDto,
|
||||
LibraryResponseDto,
|
||||
LoginCredentialDto,
|
||||
LoginResponseDto,
|
||||
SharedLinkCreateDto,
|
||||
SharedLinkResponseDto,
|
||||
UpdateUserDto,
|
||||
UserResponseDto,
|
||||
} from '@app/domain';
|
||||
import { CreateAlbumDto } from '@app/domain/album/dto/album-create.dto';
|
||||
import { dataSource } from '@app/infra';
|
||||
import { UserEntity } from '@app/infra/entities';
|
||||
import request from 'supertest';
|
||||
import { adminSignupStub, loginResponseStub, loginStub, signupResponseStub } from './fixtures';
|
||||
|
||||
export const db = {
|
||||
reset: async () => {
|
||||
if (!dataSource.isInitialized) {
|
||||
await dataSource.initialize();
|
||||
}
|
||||
|
||||
await dataSource.transaction(async (em) => {
|
||||
for (const entity of dataSource.entityMetadatas) {
|
||||
if (entity.tableName === 'users') {
|
||||
continue;
|
||||
}
|
||||
await em.query(`DELETE FROM ${entity.tableName} CASCADE;`);
|
||||
}
|
||||
await em.query(`DELETE FROM "users" CASCADE;`);
|
||||
});
|
||||
},
|
||||
disconnect: async () => {
|
||||
if (dataSource.isInitialized) {
|
||||
await dataSource.destroy();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export function getAuthUser(): AuthUserDto {
|
||||
return {
|
||||
id: '3108ac14-8afb-4b7e-87fd-39ebb6b79750',
|
||||
email: 'test@email.com',
|
||||
isAdmin: false,
|
||||
};
|
||||
}
|
||||
|
||||
export const api = {
|
||||
adminSignUp: async (server: any) => {
|
||||
const { status, body } = await request(server).post('/auth/admin-sign-up').send(adminSignupStub);
|
||||
|
||||
expect(status).toBe(201);
|
||||
expect(body).toEqual(signupResponseStub);
|
||||
|
||||
return body as AdminSignupResponseDto;
|
||||
},
|
||||
adminLogin: async (server: any) => {
|
||||
const { status, body } = await request(server).post('/auth/login').send(loginStub.admin);
|
||||
|
||||
expect(body).toEqual(loginResponseStub.admin.response);
|
||||
expect(body).toMatchObject({ accessToken: expect.any(String) });
|
||||
expect(status).toBe(201);
|
||||
|
||||
return body as LoginResponseDto;
|
||||
},
|
||||
userCreate: async (server: any, accessToken: string, user: Partial<UserEntity>) => {
|
||||
const { status, body } = await request(server)
|
||||
.post('/user')
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send(user);
|
||||
|
||||
expect(status).toBe(201);
|
||||
|
||||
return body as UserResponseDto;
|
||||
},
|
||||
login: async (server: any, dto: LoginCredentialDto) => {
|
||||
const { status, body } = await request(server).post('/auth/login').send(dto);
|
||||
|
||||
expect(status).toEqual(201);
|
||||
expect(body).toMatchObject({ accessToken: expect.any(String) });
|
||||
|
||||
return body as LoginResponseDto;
|
||||
},
|
||||
getAuthDevices: async (server: any, accessToken: string) => {
|
||||
const { status, body } = await request(server).get('/auth/devices').set('Authorization', `Bearer ${accessToken}`);
|
||||
|
||||
expect(body).toEqual(expect.any(Array));
|
||||
expect(status).toBe(200);
|
||||
|
||||
return body as AuthDeviceResponseDto[];
|
||||
},
|
||||
validateToken: async (server: any, accessToken: string) => {
|
||||
const response = await request(server).post('/auth/validateToken').set('Authorization', `Bearer ${accessToken}`);
|
||||
expect(response.body).toEqual({ authStatus: true });
|
||||
expect(response.status).toBe(200);
|
||||
},
|
||||
albumApi: {
|
||||
create: async (server: any, accessToken: string, dto: CreateAlbumDto) => {
|
||||
const res = await request(server).post('/album').set('Authorization', `Bearer ${accessToken}`).send(dto);
|
||||
expect(res.status).toEqual(201);
|
||||
return res.body as AlbumResponseDto;
|
||||
},
|
||||
},
|
||||
libraryApi: {
|
||||
getAll: async (server: any, accessToken: string) => {
|
||||
const res = await request(server).get('/library').set('Authorization', `Bearer ${accessToken}`);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(Array.isArray(res.body)).toBe(true);
|
||||
return res.body as LibraryResponseDto[];
|
||||
},
|
||||
},
|
||||
sharedLinkApi: {
|
||||
create: async (server: any, accessToken: string, dto: SharedLinkCreateDto) => {
|
||||
const { status, body } = await request(server)
|
||||
.post('/shared-link')
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send(dto);
|
||||
expect(status).toBe(201);
|
||||
return body as SharedLinkResponseDto;
|
||||
},
|
||||
},
|
||||
userApi: {
|
||||
create: async (server: any, accessToken: string, dto: CreateUserDto) => {
|
||||
const { status, body } = await request(server)
|
||||
.post('/user')
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send(dto);
|
||||
|
||||
expect(status).toBe(201);
|
||||
expect(body).toMatchObject({
|
||||
id: expect.any(String),
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
email: dto.email,
|
||||
});
|
||||
|
||||
return body as UserResponseDto;
|
||||
},
|
||||
get: async (server: any, accessToken: string, id: string) => {
|
||||
const { status, body } = await request(server)
|
||||
.get(`/user/info/${id}`)
|
||||
.set('Authorization', `Bearer ${accessToken}`);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body).toMatchObject({ id });
|
||||
|
||||
return body as UserResponseDto;
|
||||
},
|
||||
update: async (server: any, accessToken: string, dto: UpdateUserDto) => {
|
||||
const { status, body } = await request(server)
|
||||
.put('/user')
|
||||
.set('Authorization', `Bearer ${accessToken}`)
|
||||
.send(dto);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body).toMatchObject({ id: dto.id });
|
||||
|
||||
return body as UserResponseDto;
|
||||
},
|
||||
delete: async (server: any, accessToken: string, id: string) => {
|
||||
const { status, body } = await request(server)
|
||||
.delete(`/user/${id}`)
|
||||
.set('Authorization', `Bearer ${accessToken}`);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body).toMatchObject({ id, deletedAt: expect.any(String) });
|
||||
|
||||
return body as UserResponseDto;
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
Reference in New Issue
Block a user