mirror of
https://github.com/immich-app/immich.git
synced 2024-11-30 09:47:31 +02:00
refactor: library e2e (#7538)
* refactor: library e2e * refactor: remove before each usages
This commit is contained in:
parent
efa6efd200
commit
3d25d91e77
@ -16,6 +16,7 @@ x-server-build: &server-common
|
|||||||
- IMMICH_MACHINE_LEARNING_ENABLED=false
|
- IMMICH_MACHINE_LEARNING_ENABLED=false
|
||||||
volumes:
|
volumes:
|
||||||
- upload:/usr/src/app/upload
|
- upload:/usr/src/app/upload
|
||||||
|
- ../server/test/assets:/data/assets
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
- database
|
- database
|
||||||
|
456
e2e/src/api/specs/library.e2e-spec.ts
Normal file
456
e2e/src/api/specs/library.e2e-spec.ts
Normal file
@ -0,0 +1,456 @@
|
|||||||
|
import { LibraryResponseDto, LibraryType, LoginResponseDto, getAllLibraries } from '@immich/sdk';
|
||||||
|
import { userDto, uuidDto } from 'src/fixtures';
|
||||||
|
import { errorDto } from 'src/responses';
|
||||||
|
import { apiUtils, app, asBearerAuth, dbUtils, testAssetDirInternal } from 'src/utils';
|
||||||
|
import request from 'supertest';
|
||||||
|
import { beforeAll, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
describe('/library', () => {
|
||||||
|
let admin: LoginResponseDto;
|
||||||
|
let user: LoginResponseDto;
|
||||||
|
let library: LibraryResponseDto;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
apiUtils.setup();
|
||||||
|
await dbUtils.reset();
|
||||||
|
admin = await apiUtils.adminSetup();
|
||||||
|
user = await apiUtils.userSetup(admin.accessToken, userDto.user1);
|
||||||
|
library = await apiUtils.createLibrary(admin.accessToken, { type: LibraryType.External });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /library', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).get('/library');
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should start with a default upload library', async () => {
|
||||||
|
const { status, body } = await request(app).get('/library').set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
ownerId: admin.userId,
|
||||||
|
type: LibraryType.Upload,
|
||||||
|
name: 'Default Library',
|
||||||
|
refreshedAt: null,
|
||||||
|
assetCount: 0,
|
||||||
|
importPaths: [],
|
||||||
|
exclusionPatterns: [],
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /library', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).post('/library').send({});
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require admin authentication', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/library')
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`)
|
||||||
|
.send({ type: LibraryType.External });
|
||||||
|
|
||||||
|
expect(status).toBe(403);
|
||||||
|
expect(body).toEqual(errorDto.forbidden);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create an external library with defaults', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/library')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ type: LibraryType.External });
|
||||||
|
|
||||||
|
expect(status).toBe(201);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
ownerId: admin.userId,
|
||||||
|
type: LibraryType.External,
|
||||||
|
name: 'New External Library',
|
||||||
|
refreshedAt: null,
|
||||||
|
assetCount: 0,
|
||||||
|
importPaths: [],
|
||||||
|
exclusionPatterns: [],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create an external library with options', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/library')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({
|
||||||
|
type: LibraryType.External,
|
||||||
|
name: 'My Awesome Library',
|
||||||
|
importPaths: ['/path/to/import'],
|
||||||
|
exclusionPatterns: ['**/Raw/**'],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(status).toBe(201);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: 'My Awesome Library',
|
||||||
|
importPaths: ['/path/to/import'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create an external library with duplicate import paths', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/library')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({
|
||||||
|
type: LibraryType.External,
|
||||||
|
name: 'My Awesome Library',
|
||||||
|
importPaths: ['/path', '/path'],
|
||||||
|
exclusionPatterns: ['**/Raw/**'],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(["All importPaths's elements must be unique"]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create an external library with duplicate exclusion patterns', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/library')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({
|
||||||
|
type: LibraryType.External,
|
||||||
|
name: 'My Awesome Library',
|
||||||
|
importPaths: ['/path/to/import'],
|
||||||
|
exclusionPatterns: ['**/Raw/**', '**/Raw/**'],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(["All exclusionPatterns's elements must be unique"]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create an upload library with defaults', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/library')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ type: LibraryType.Upload });
|
||||||
|
|
||||||
|
expect(status).toBe(201);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
ownerId: admin.userId,
|
||||||
|
type: LibraryType.Upload,
|
||||||
|
name: 'New Upload Library',
|
||||||
|
refreshedAt: null,
|
||||||
|
assetCount: 0,
|
||||||
|
importPaths: [],
|
||||||
|
exclusionPatterns: [],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create an upload library with options', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/library')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ type: LibraryType.Upload, name: 'My Awesome Library' });
|
||||||
|
|
||||||
|
expect(status).toBe(201);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: 'My Awesome Library',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow upload libraries to have import paths', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/library')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ type: LibraryType.Upload, importPaths: ['/path/to/import'] });
|
||||||
|
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest('Upload libraries cannot have import paths'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow upload libraries to have exclusion patterns', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/library')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ type: LibraryType.Upload, exclusionPatterns: ['**/Raw/**'] });
|
||||||
|
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest('Upload libraries cannot have exclusion patterns'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PUT /library/:id', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).put(`/library/${uuidDto.notFound}`).send({});
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change the library name', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/library/${library.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ name: 'New Library Name' });
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: 'New Library Name',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not set an empty name', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/library/${library.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ name: '' });
|
||||||
|
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(['name should not be empty']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change the import paths', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/library/${library.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ importPaths: [testAssetDirInternal] });
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
importPaths: [testAssetDirInternal],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject an empty import path', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/library/${library.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ importPaths: [''] });
|
||||||
|
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(['each value in importPaths should not be empty']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject duplicate import paths', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/library/${library.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ importPaths: ['/path', '/path'] });
|
||||||
|
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(["All importPaths's elements must be unique"]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change the exclusion pattern', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/library/${library.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ exclusionPatterns: ['**/Raw/**'] });
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
exclusionPatterns: ['**/Raw/**'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject duplicate exclusion patterns', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/library/${library.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ exclusionPatterns: ['**/*.jpg', '**/*.jpg'] });
|
||||||
|
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(["All exclusionPatterns's elements must be unique"]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject an empty exclusion pattern', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/library/${library.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ exclusionPatterns: [''] });
|
||||||
|
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(['each value in exclusionPatterns should not be empty']));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /library/:id', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).get(`/library/${uuidDto.notFound}`);
|
||||||
|
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require admin access', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get(`/library/${uuidDto.notFound}`)
|
||||||
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
|
expect(status).toBe(403);
|
||||||
|
expect(body).toEqual(errorDto.forbidden);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get library by id', async () => {
|
||||||
|
const library = await apiUtils.createLibrary(admin.accessToken, { type: LibraryType.External });
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get(`/library/${library.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
ownerId: admin.userId,
|
||||||
|
type: LibraryType.External,
|
||||||
|
name: 'New External Library',
|
||||||
|
refreshedAt: null,
|
||||||
|
assetCount: 0,
|
||||||
|
importPaths: [],
|
||||||
|
exclusionPatterns: [],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DELETE /library/:id', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).delete(`/library/${uuidDto.notFound}`);
|
||||||
|
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not delete the last upload library', async () => {
|
||||||
|
const libraries = await getAllLibraries(
|
||||||
|
{ $type: LibraryType.Upload },
|
||||||
|
{ headers: asBearerAuth(admin.accessToken) },
|
||||||
|
);
|
||||||
|
|
||||||
|
const adminLibraries = libraries.filter((library) => library.ownerId === admin.userId);
|
||||||
|
expect(adminLibraries.length).toBeGreaterThanOrEqual(1);
|
||||||
|
const lastLibrary = adminLibraries.pop() as LibraryResponseDto;
|
||||||
|
|
||||||
|
// delete all but the last upload library
|
||||||
|
for (const library of adminLibraries) {
|
||||||
|
const { status } = await request(app)
|
||||||
|
.delete(`/library/${library.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(204);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.delete(`/library/${lastLibrary.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
|
expect(body).toEqual(errorDto.noDeleteUploadLibrary);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete an external library', async () => {
|
||||||
|
const library = await apiUtils.createLibrary(admin.accessToken, { type: LibraryType.External });
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.delete(`/library/${library.id}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
|
expect(status).toBe(204);
|
||||||
|
expect(body).toEqual({});
|
||||||
|
|
||||||
|
const libraries = await getAllLibraries({}, { headers: asBearerAuth(admin.accessToken) });
|
||||||
|
expect(libraries).not.toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: library.id,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /library/:id/statistics', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).get(`/library/${uuidDto.notFound}/statistics`);
|
||||||
|
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /library/:id/scan', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).post(`/library/${uuidDto.notFound}/scan`).send({});
|
||||||
|
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /library/:id/removeOffline', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).post(`/library/${uuidDto.notFound}/removeOffline`).send({});
|
||||||
|
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /library/:id/validate', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).post(`/library/${uuidDto.notFound}/validate`).send({});
|
||||||
|
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass with no import paths', async () => {
|
||||||
|
const response = await apiUtils.validateLibrary(admin.accessToken, library.id, { importPaths: [] });
|
||||||
|
expect(response.importPaths).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail if path does not exist', async () => {
|
||||||
|
const pathToTest = `${testAssetDirInternal}/does/not/exist`;
|
||||||
|
|
||||||
|
const response = await apiUtils.validateLibrary(admin.accessToken, library.id, {
|
||||||
|
importPaths: [pathToTest],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.importPaths?.length).toEqual(1);
|
||||||
|
const pathResponse = response?.importPaths?.at(0);
|
||||||
|
|
||||||
|
expect(pathResponse).toEqual({
|
||||||
|
importPath: pathToTest,
|
||||||
|
isValid: false,
|
||||||
|
message: `Path does not exist (ENOENT)`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail if path is a file', async () => {
|
||||||
|
const pathToTest = `${testAssetDirInternal}/albums/nature/el_torcal_rocks.jpg`;
|
||||||
|
|
||||||
|
const response = await apiUtils.validateLibrary(admin.accessToken, library.id, {
|
||||||
|
importPaths: [pathToTest],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.importPaths?.length).toEqual(1);
|
||||||
|
const pathResponse = response?.importPaths?.at(0);
|
||||||
|
|
||||||
|
expect(pathResponse).toEqual({
|
||||||
|
importPath: pathToTest,
|
||||||
|
isValid: false,
|
||||||
|
message: `Not a directory`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -3,11 +3,14 @@ import {
|
|||||||
AssetResponseDto,
|
AssetResponseDto,
|
||||||
CreateAlbumDto,
|
CreateAlbumDto,
|
||||||
CreateAssetDto,
|
CreateAssetDto,
|
||||||
|
CreateLibraryDto,
|
||||||
CreateUserDto,
|
CreateUserDto,
|
||||||
PersonUpdateDto,
|
PersonUpdateDto,
|
||||||
SharedLinkCreateDto,
|
SharedLinkCreateDto,
|
||||||
|
ValidateLibraryDto,
|
||||||
createAlbum,
|
createAlbum,
|
||||||
createApiKey,
|
createApiKey,
|
||||||
|
createLibrary,
|
||||||
createPerson,
|
createPerson,
|
||||||
createSharedLink,
|
createSharedLink,
|
||||||
createUser,
|
createUser,
|
||||||
@ -18,6 +21,7 @@ import {
|
|||||||
setAdminOnboarding,
|
setAdminOnboarding,
|
||||||
signUpAdmin,
|
signUpAdmin,
|
||||||
updatePerson,
|
updatePerson,
|
||||||
|
validate,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { BrowserContext } from '@playwright/test';
|
import { BrowserContext } from '@playwright/test';
|
||||||
import { exec, spawn } from 'node:child_process';
|
import { exec, spawn } from 'node:child_process';
|
||||||
@ -42,6 +46,7 @@ const directoryExists = (directory: string) =>
|
|||||||
|
|
||||||
// TODO move test assets into e2e/assets
|
// TODO move test assets into e2e/assets
|
||||||
export const testAssetDir = path.resolve(`./../server/test/assets/`);
|
export const testAssetDir = path.resolve(`./../server/test/assets/`);
|
||||||
|
export const testAssetDirInternal = '/data/assets';
|
||||||
export const tempDir = tmpdir();
|
export const tempDir = tmpdir();
|
||||||
|
|
||||||
const serverContainerName = 'immich-e2e-server';
|
const serverContainerName = 'immich-e2e-server';
|
||||||
@ -103,6 +108,7 @@ export const dbUtils = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tables = tables || [
|
tables = tables || [
|
||||||
|
'libraries',
|
||||||
'shared_links',
|
'shared_links',
|
||||||
'person',
|
'person',
|
||||||
'albums',
|
'albums',
|
||||||
@ -313,6 +319,10 @@ export const apiUtils = {
|
|||||||
},
|
},
|
||||||
createSharedLink: (accessToken: string, dto: SharedLinkCreateDto) =>
|
createSharedLink: (accessToken: string, dto: SharedLinkCreateDto) =>
|
||||||
createSharedLink({ sharedLinkCreateDto: dto }, { headers: asBearerAuth(accessToken) }),
|
createSharedLink({ sharedLinkCreateDto: dto }, { headers: asBearerAuth(accessToken) }),
|
||||||
|
createLibrary: (accessToken: string, dto: CreateLibraryDto) =>
|
||||||
|
createLibrary({ createLibraryDto: dto }, { headers: asBearerAuth(accessToken) }),
|
||||||
|
validateLibrary: (accessToken: string, id: string, dto: ValidateLibraryDto) =>
|
||||||
|
validate({ id, validateLibraryDto: dto }, { headers: asBearerAuth(accessToken) }),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cliUtils = {
|
export const cliUtils = {
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
// skip `docker compose up` if `make e2e` was already run
|
||||||
|
const globalSetup: string[] = [];
|
||||||
|
try {
|
||||||
|
await fetch('http://127.0.0.1:2283/api/server-info/ping');
|
||||||
|
} catch {
|
||||||
|
globalSetup.push('src/setup.ts');
|
||||||
|
}
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
test: {
|
test: {
|
||||||
include: ['src/{api,cli}/specs/*.e2e-spec.ts'],
|
include: ['src/{api,cli}/specs/*.e2e-spec.ts'],
|
||||||
globalSetup: ['src/setup.ts'],
|
globalSetup,
|
||||||
poolOptions: {
|
poolOptions: {
|
||||||
threads: {
|
threads: {
|
||||||
singleThread: true,
|
singleThread: true,
|
||||||
|
@ -3396,7 +3396,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"204": {
|
||||||
"description": ""
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -3521,7 +3521,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"201": {
|
"204": {
|
||||||
"description": ""
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -3566,7 +3566,7 @@
|
|||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
"responses": {
|
"responses": {
|
||||||
"201": {
|
"204": {
|
||||||
"description": ""
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,475 +0,0 @@
|
|||||||
import { LibraryResponseDto, LoginResponseDto } from '@app/domain';
|
|
||||||
import { LibraryController } from '@app/immich';
|
|
||||||
import { LibraryType } from '@app/infra/entities';
|
|
||||||
import { errorStub, userDto, uuidStub } from '@test/fixtures';
|
|
||||||
import { IMMICH_TEST_ASSET_TEMP_PATH, restoreTempFolder } from 'src/test-utils/utils';
|
|
||||||
import request from 'supertest';
|
|
||||||
import { api } from '../../client';
|
|
||||||
import { testApp } from '../utils';
|
|
||||||
|
|
||||||
describe(`${LibraryController.name} (e2e)`, () => {
|
|
||||||
let server: any;
|
|
||||||
let admin: LoginResponseDto;
|
|
||||||
let user: LoginResponseDto;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
const app = await testApp.create();
|
|
||||||
server = app.getHttpServer();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await testApp.teardown();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await restoreTempFolder();
|
|
||||||
await testApp.reset();
|
|
||||||
await api.authApi.adminSignUp(server);
|
|
||||||
admin = await api.authApi.adminLogin(server);
|
|
||||||
|
|
||||||
await api.userApi.create(server, admin.accessToken, userDto.user1);
|
|
||||||
user = await api.authApi.login(server, userDto.user1);
|
|
||||||
});
|
|
||||||
|
|
||||||
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 ${admin.accessToken}`);
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
expect.objectContaining({
|
|
||||||
ownerId: admin.userId,
|
|
||||||
type: LibraryType.UPLOAD,
|
|
||||||
name: 'Default Library',
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require admin authentication', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.post('/library')
|
|
||||||
.set('Authorization', `Bearer ${user.accessToken}`)
|
|
||||||
.send({ type: LibraryType.EXTERNAL });
|
|
||||||
|
|
||||||
expect(status).toBe(403);
|
|
||||||
expect(body).toEqual(errorStub.forbidden);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create an external library with defaults', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.post('/library')
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send({ type: LibraryType.EXTERNAL });
|
|
||||||
|
|
||||||
expect(status).toBe(201);
|
|
||||||
expect(body).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
ownerId: admin.userId,
|
|
||||||
type: LibraryType.EXTERNAL,
|
|
||||||
name: 'New External Library',
|
|
||||||
refreshedAt: null,
|
|
||||||
assetCount: 0,
|
|
||||||
importPaths: [],
|
|
||||||
exclusionPatterns: [],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create an external library with options', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.post('/library')
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send({
|
|
||||||
type: LibraryType.EXTERNAL,
|
|
||||||
name: 'My Awesome Library',
|
|
||||||
importPaths: ['/path/to/import'],
|
|
||||||
exclusionPatterns: ['**/Raw/**'],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(status).toBe(201);
|
|
||||||
expect(body).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
name: 'My Awesome Library',
|
|
||||||
importPaths: ['/path/to/import'],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not create an external library with duplicate import paths', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.post('/library')
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send({
|
|
||||||
type: LibraryType.EXTERNAL,
|
|
||||||
name: 'My Awesome Library',
|
|
||||||
importPaths: ['/path', '/path'],
|
|
||||||
exclusionPatterns: ['**/Raw/**'],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorStub.badRequest(["All importPaths's elements must be unique"]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not create an external library with duplicate exclusion patterns', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.post('/library')
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send({
|
|
||||||
type: LibraryType.EXTERNAL,
|
|
||||||
name: 'My Awesome Library',
|
|
||||||
importPaths: ['/path/to/import'],
|
|
||||||
exclusionPatterns: ['**/Raw/**', '**/Raw/**'],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorStub.badRequest(["All exclusionPatterns's elements must be unique"]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create an upload library with defaults', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.post('/library')
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send({ type: LibraryType.UPLOAD });
|
|
||||||
|
|
||||||
expect(status).toBe(201);
|
|
||||||
expect(body).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
ownerId: admin.userId,
|
|
||||||
type: LibraryType.UPLOAD,
|
|
||||||
name: 'New Upload Library',
|
|
||||||
refreshedAt: null,
|
|
||||||
assetCount: 0,
|
|
||||||
importPaths: [],
|
|
||||||
exclusionPatterns: [],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create an upload library with options', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.post('/library')
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send({ type: LibraryType.UPLOAD, name: 'My Awesome Library' });
|
|
||||||
|
|
||||||
expect(status).toBe(201);
|
|
||||||
expect(body).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
name: 'My Awesome Library',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not allow upload libraries to have import paths', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.post('/library')
|
|
||||||
.set('Authorization', `Bearer ${admin.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('should not allow upload libraries to have exclusion patterns', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.post('/library')
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send({ type: LibraryType.UPLOAD, exclusionPatterns: ['**/Raw/**'] });
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorStub.badRequest('Upload libraries cannot have exclusion patterns'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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 library: LibraryResponseDto;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
// Create an external library with default settings
|
|
||||||
library = await api.libraryApi.create(server, admin.accessToken, { type: LibraryType.EXTERNAL });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should change the library name', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.put(`/library/${library.id}`)
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send({ name: 'New Library Name' });
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
name: 'New Library Name',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not set an empty name', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.put(`/library/${library.id}`)
|
|
||||||
.set('Authorization', `Bearer ${admin.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/${library.id}`)
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send({ importPaths: [IMMICH_TEST_ASSET_TEMP_PATH] });
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
importPaths: [IMMICH_TEST_ASSET_TEMP_PATH],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reject an empty import path', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.put(`/library/${library.id}`)
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send({ importPaths: [''] });
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorStub.badRequest(['each value in importPaths should not be empty']));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reject duplicate import paths', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.put(`/library/${library.id}`)
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send({ importPaths: ['/path', '/path'] });
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorStub.badRequest(["All importPaths's elements must be unique"]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should change the exclusion pattern', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.put(`/library/${library.id}`)
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send({ exclusionPatterns: ['**/Raw/**'] });
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
exclusionPatterns: ['**/Raw/**'],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reject duplicate exclusion patterns', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.put(`/library/${library.id}`)
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send({ exclusionPatterns: ['**/*.jpg', '**/*.jpg'] });
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorStub.badRequest(["All exclusionPatterns's elements must be unique"]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reject an empty exclusion pattern', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.put(`/library/${library.id}`)
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send({ exclusionPatterns: [''] });
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorStub.badRequest(['each value in exclusionPatterns 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 require admin access', async () => {
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.get(`/library/${uuidStub.notFound}`)
|
|
||||||
.set('Authorization', `Bearer ${user.accessToken}`);
|
|
||||||
expect(status).toBe(403);
|
|
||||||
expect(body).toEqual(errorStub.forbidden);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get library by id', async () => {
|
|
||||||
const library = await api.libraryApi.create(server, admin.accessToken, { type: LibraryType.EXTERNAL });
|
|
||||||
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.get(`/library/${library.id}`)
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
ownerId: admin.userId,
|
|
||||||
type: LibraryType.EXTERNAL,
|
|
||||||
name: 'New External Library',
|
|
||||||
refreshedAt: null,
|
|
||||||
assetCount: 0,
|
|
||||||
importPaths: [],
|
|
||||||
exclusionPatterns: [],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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, admin.accessToken);
|
|
||||||
expect(defaultLibrary).toBeDefined();
|
|
||||||
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.delete(`/library/${defaultLibrary.id}`)
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorStub.noDeleteUploadLibrary);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should delete an external library', async () => {
|
|
||||||
const library = await api.libraryApi.create(server, admin.accessToken, { type: LibraryType.EXTERNAL });
|
|
||||||
|
|
||||||
const { status, body } = await request(server)
|
|
||||||
.delete(`/library/${library.id}`)
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual({});
|
|
||||||
|
|
||||||
const libraries = await api.libraryApi.getAll(server, admin.accessToken);
|
|
||||||
expect(libraries).not.toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
expect.objectContaining({
|
|
||||||
id: library.id,
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /library/:id/validate', () => {
|
|
||||||
it('should require authentication', async () => {
|
|
||||||
const { status, body } = await request(server).post(`/library/${uuidStub.notFound}/validate`).send({});
|
|
||||||
|
|
||||||
expect(status).toBe(401);
|
|
||||||
expect(body).toEqual(errorStub.unauthorized);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Validate import path', () => {
|
|
||||||
let library: LibraryResponseDto;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
// Create an external library with default settings
|
|
||||||
library = await api.libraryApi.create(server, admin.accessToken, { type: LibraryType.EXTERNAL });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should pass with no import paths', async () => {
|
|
||||||
const response = await api.libraryApi.validate(server, admin.accessToken, library.id, { importPaths: [] });
|
|
||||||
expect(response.importPaths).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail if path does not exist', async () => {
|
|
||||||
const pathToTest = `${IMMICH_TEST_ASSET_TEMP_PATH}/does/not/exist`;
|
|
||||||
|
|
||||||
const response = await api.libraryApi.validate(server, admin.accessToken, library.id, {
|
|
||||||
importPaths: [pathToTest],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.importPaths?.length).toEqual(1);
|
|
||||||
const pathResponse = response?.importPaths?.at(0);
|
|
||||||
|
|
||||||
expect(pathResponse).toEqual({
|
|
||||||
importPath: pathToTest,
|
|
||||||
isValid: false,
|
|
||||||
message: `Path does not exist (ENOENT)`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail if path is a file', async () => {
|
|
||||||
const pathToTest = `${IMMICH_TEST_ASSET_TEMP_PATH}/does/not/exist`;
|
|
||||||
|
|
||||||
const response = await api.libraryApi.validate(server, admin.accessToken, library.id, {
|
|
||||||
importPaths: [pathToTest],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.importPaths?.length).toEqual(1);
|
|
||||||
const pathResponse = response?.importPaths?.at(0);
|
|
||||||
|
|
||||||
expect(pathResponse).toEqual({
|
|
||||||
importPath: pathToTest,
|
|
||||||
isValid: false,
|
|
||||||
message: `Path does not exist (ENOENT)`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -36,14 +36,14 @@ export const libraryApi = {
|
|||||||
.post(`/library/${id}/scan`)
|
.post(`/library/${id}/scan`)
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
.set('Authorization', `Bearer ${accessToken}`)
|
||||||
.send(dto);
|
.send(dto);
|
||||||
expect(status).toBe(201);
|
expect(status).toBe(204);
|
||||||
},
|
},
|
||||||
removeOfflineFiles: async (server: any, accessToken: string, id: string) => {
|
removeOfflineFiles: async (server: any, accessToken: string, id: string) => {
|
||||||
const { status } = await request(server)
|
const { status } = await request(server)
|
||||||
.post(`/library/${id}/removeOffline`)
|
.post(`/library/${id}/removeOffline`)
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
.set('Authorization', `Bearer ${accessToken}`)
|
||||||
.send();
|
.send();
|
||||||
expect(status).toBe(201);
|
expect(status).toBe(204);
|
||||||
},
|
},
|
||||||
getLibraryStatistics: async (server: any, accessToken: string, id: string): Promise<LibraryStatsResponseDto> => {
|
getLibraryStatistics: async (server: any, accessToken: string, id: string): Promise<LibraryStatsResponseDto> => {
|
||||||
const { body, status } = await request(server)
|
const { body, status } = await request(server)
|
||||||
|
@ -45,12 +45,11 @@ describe(`${LibraryController.name} (e2e)`, () => {
|
|||||||
const assets = await api.assetApi.getAllAssets(server, admin.accessToken);
|
const assets = await api.assetApi.getAllAssets(server, admin.accessToken);
|
||||||
expect(assets.length).toBeGreaterThan(2);
|
expect(assets.length).toBeGreaterThan(2);
|
||||||
|
|
||||||
const { status, body } = await request(server)
|
const { status } = await request(server)
|
||||||
.delete(`/library/${library.id}`)
|
.delete(`/library/${library.id}`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(204);
|
||||||
expect(body).toEqual({});
|
|
||||||
|
|
||||||
const libraries = await api.libraryApi.getAll(server, admin.accessToken);
|
const libraries = await api.libraryApi.getAll(server, admin.accessToken);
|
||||||
expect(libraries).toHaveLength(1);
|
expect(libraries).toHaveLength(1);
|
||||||
@ -392,7 +391,7 @@ describe(`${LibraryController.name} (e2e)`, () => {
|
|||||||
.post(`/library/${library.id}/removeOffline`)
|
.post(`/library/${library.id}/removeOffline`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send();
|
.send();
|
||||||
expect(status).toBe(201);
|
expect(status).toBe(204);
|
||||||
|
|
||||||
const assets = await api.assetApi.getAllAssets(server, admin.accessToken);
|
const assets = await api.assetApi.getAllAssets(server, admin.accessToken);
|
||||||
|
|
||||||
@ -416,7 +415,7 @@ describe(`${LibraryController.name} (e2e)`, () => {
|
|||||||
.post(`/library/${library.id}/removeOffline`)
|
.post(`/library/${library.id}/removeOffline`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send();
|
.send();
|
||||||
expect(status).toBe(201);
|
expect(status).toBe(204);
|
||||||
|
|
||||||
const assetsAfter = await api.assetApi.getAllAssets(server, admin.accessToken);
|
const assetsAfter = await api.assetApi.getAllAssets(server, admin.accessToken);
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
ValidateLibraryDto,
|
ValidateLibraryDto,
|
||||||
ValidateLibraryResponseDto,
|
ValidateLibraryResponseDto,
|
||||||
} from '@app/domain';
|
} from '@app/domain';
|
||||||
import { Body, Controller, Delete, Get, HttpCode, Param, Post, Put, Query } from '@nestjs/common';
|
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import { AdminRoute, Auth, Authenticated } from '../app.guard';
|
import { AdminRoute, Auth, Authenticated } from '../app.guard';
|
||||||
import { UseValidation } from '../app.utils';
|
import { UseValidation } from '../app.utils';
|
||||||
@ -55,6 +55,7 @@ export class LibraryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
deleteLibrary(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
deleteLibrary(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.delete(auth, id);
|
return this.service.delete(auth, id);
|
||||||
}
|
}
|
||||||
@ -65,11 +66,13 @@ export class LibraryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/scan')
|
@Post(':id/scan')
|
||||||
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
scanLibrary(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: ScanLibraryDto) {
|
scanLibrary(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: ScanLibraryDto) {
|
||||||
return this.service.queueScan(auth, id, dto);
|
return this.service.queueScan(auth, id, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/removeOffline')
|
@Post(':id/removeOffline')
|
||||||
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
removeOfflineFiles(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
|
removeOfflineFiles(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
|
||||||
return this.service.queueRemoveOffline(auth, id);
|
return this.service.queueRemoveOffline(auth, id);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user