mirror of
https://github.com/immich-app/immich.git
synced 2025-12-23 17:25:11 +03:00
refactor(server): library service (#8050)
* refactor: library service * chore: open api * fix: checks
This commit is contained in:
@@ -706,7 +706,7 @@ describe(LibraryService.name, () => {
|
||||
libraryMock.getUploadLibraryCount.mockResolvedValue(2);
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
|
||||
await sut.delete(authStub.admin, libraryStub.externalLibrary1.id);
|
||||
await sut.delete(libraryStub.externalLibrary1.id);
|
||||
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({
|
||||
name: JobName.LIBRARY_DELETE,
|
||||
@@ -721,9 +721,7 @@ describe(LibraryService.name, () => {
|
||||
libraryMock.getUploadLibraryCount.mockResolvedValue(1);
|
||||
libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
|
||||
await expect(sut.delete(authStub.admin, libraryStub.uploadLibrary1.id)).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
);
|
||||
await expect(sut.delete(libraryStub.uploadLibrary1.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(jobMock.queue).not.toHaveBeenCalled();
|
||||
expect(jobMock.queueAll).not.toHaveBeenCalled();
|
||||
@@ -735,7 +733,7 @@ describe(LibraryService.name, () => {
|
||||
libraryMock.getUploadLibraryCount.mockResolvedValue(1);
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
|
||||
await sut.delete(authStub.admin, libraryStub.externalLibrary1.id);
|
||||
await sut.delete(libraryStub.externalLibrary1.id);
|
||||
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({
|
||||
name: JobName.LIBRARY_DELETE,
|
||||
@@ -757,26 +755,16 @@ describe(LibraryService.name, () => {
|
||||
storageMock.watch.mockImplementation(makeMockWatcher({ close: mockClose }));
|
||||
|
||||
await sut.init();
|
||||
await sut.delete(authStub.admin, libraryStub.externalLibraryWithImportPaths1.id);
|
||||
await sut.delete(libraryStub.externalLibraryWithImportPaths1.id);
|
||||
|
||||
expect(mockClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCount', () => {
|
||||
it('should call the repository', async () => {
|
||||
libraryMock.getCountForUser.mockResolvedValue(17);
|
||||
|
||||
await expect(sut.getCount(authStub.admin)).resolves.toBe(17);
|
||||
|
||||
expect(libraryMock.getCountForUser).toHaveBeenCalledWith(authStub.admin.user.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
it('should return a library', async () => {
|
||||
libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
await expect(sut.get(authStub.admin, libraryStub.uploadLibrary1.id)).resolves.toEqual(
|
||||
await expect(sut.get(libraryStub.uploadLibrary1.id)).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: libraryStub.uploadLibrary1.id,
|
||||
name: libraryStub.uploadLibrary1.name,
|
||||
@@ -789,15 +777,16 @@ describe(LibraryService.name, () => {
|
||||
|
||||
it('should throw an error when a library is not found', async () => {
|
||||
libraryMock.get.mockResolvedValue(null);
|
||||
await expect(sut.get(authStub.admin, libraryStub.uploadLibrary1.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||
await expect(sut.get(libraryStub.uploadLibrary1.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(libraryMock.get).toHaveBeenCalledWith(libraryStub.uploadLibrary1.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStatistics', () => {
|
||||
it('should return library statistics', async () => {
|
||||
libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
libraryMock.getStatistics.mockResolvedValue({ photos: 10, videos: 0, total: 10, usage: 1337 });
|
||||
await expect(sut.getStatistics(authStub.admin, libraryStub.uploadLibrary1.id)).resolves.toEqual({
|
||||
await expect(sut.getStatistics(libraryStub.uploadLibrary1.id)).resolves.toEqual({
|
||||
photos: 10,
|
||||
videos: 0,
|
||||
total: 10,
|
||||
@@ -812,11 +801,7 @@ describe(LibraryService.name, () => {
|
||||
describe('external library', () => {
|
||||
it('should create with default settings', async () => {
|
||||
libraryMock.create.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
await expect(
|
||||
sut.create(authStub.admin, {
|
||||
type: LibraryType.EXTERNAL,
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
await expect(sut.create({ ownerId: authStub.admin.user.id, type: LibraryType.EXTERNAL })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: libraryStub.externalLibrary1.id,
|
||||
type: LibraryType.EXTERNAL,
|
||||
@@ -845,10 +830,7 @@ describe(LibraryService.name, () => {
|
||||
it('should create with name', async () => {
|
||||
libraryMock.create.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
await expect(
|
||||
sut.create(authStub.admin, {
|
||||
type: LibraryType.EXTERNAL,
|
||||
name: 'My Awesome Library',
|
||||
}),
|
||||
sut.create({ ownerId: authStub.admin.user.id, type: LibraryType.EXTERNAL, name: 'My Awesome Library' }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: libraryStub.externalLibrary1.id,
|
||||
@@ -878,10 +860,7 @@ describe(LibraryService.name, () => {
|
||||
it('should create invisible', async () => {
|
||||
libraryMock.create.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
await expect(
|
||||
sut.create(authStub.admin, {
|
||||
type: LibraryType.EXTERNAL,
|
||||
isVisible: false,
|
||||
}),
|
||||
sut.create({ ownerId: authStub.admin.user.id, type: LibraryType.EXTERNAL, isVisible: false }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: libraryStub.externalLibrary1.id,
|
||||
@@ -911,7 +890,8 @@ describe(LibraryService.name, () => {
|
||||
it('should create with import paths', async () => {
|
||||
libraryMock.create.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
await expect(
|
||||
sut.create(authStub.admin, {
|
||||
sut.create({
|
||||
ownerId: authStub.admin.user.id,
|
||||
type: LibraryType.EXTERNAL,
|
||||
importPaths: ['/data/images', '/data/videos'],
|
||||
}),
|
||||
@@ -948,7 +928,8 @@ describe(LibraryService.name, () => {
|
||||
libraryMock.getAll.mockResolvedValue([]);
|
||||
|
||||
await sut.init();
|
||||
await sut.create(authStub.admin, {
|
||||
await sut.create({
|
||||
ownerId: authStub.admin.user.id,
|
||||
type: LibraryType.EXTERNAL,
|
||||
importPaths: libraryStub.externalLibraryWithImportPaths1.importPaths,
|
||||
});
|
||||
@@ -963,7 +944,8 @@ describe(LibraryService.name, () => {
|
||||
it('should create with exclusion patterns', async () => {
|
||||
libraryMock.create.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
await expect(
|
||||
sut.create(authStub.admin, {
|
||||
sut.create({
|
||||
ownerId: authStub.admin.user.id,
|
||||
type: LibraryType.EXTERNAL,
|
||||
exclusionPatterns: ['*.tmp', '*.bak'],
|
||||
}),
|
||||
@@ -997,11 +979,7 @@ describe(LibraryService.name, () => {
|
||||
describe('upload library', () => {
|
||||
it('should create with default settings', async () => {
|
||||
libraryMock.create.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
await expect(
|
||||
sut.create(authStub.admin, {
|
||||
type: LibraryType.UPLOAD,
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
await expect(sut.create({ ownerId: authStub.admin.user.id, type: LibraryType.UPLOAD })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: libraryStub.uploadLibrary1.id,
|
||||
type: LibraryType.UPLOAD,
|
||||
@@ -1030,10 +1008,7 @@ describe(LibraryService.name, () => {
|
||||
it('should create with name', async () => {
|
||||
libraryMock.create.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
await expect(
|
||||
sut.create(authStub.admin, {
|
||||
type: LibraryType.UPLOAD,
|
||||
name: 'My Awesome Library',
|
||||
}),
|
||||
sut.create({ ownerId: authStub.admin.user.id, type: LibraryType.UPLOAD, name: 'My Awesome Library' }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: libraryStub.uploadLibrary1.id,
|
||||
@@ -1062,7 +1037,8 @@ describe(LibraryService.name, () => {
|
||||
|
||||
it('should not create with import paths', async () => {
|
||||
await expect(
|
||||
sut.create(authStub.admin, {
|
||||
sut.create({
|
||||
ownerId: authStub.admin.user.id,
|
||||
type: LibraryType.UPLOAD,
|
||||
importPaths: ['/data/images', '/data/videos'],
|
||||
}),
|
||||
@@ -1073,7 +1049,8 @@ describe(LibraryService.name, () => {
|
||||
|
||||
it('should not create with exclusion patterns', async () => {
|
||||
await expect(
|
||||
sut.create(authStub.admin, {
|
||||
sut.create({
|
||||
ownerId: authStub.admin.user.id,
|
||||
type: LibraryType.UPLOAD,
|
||||
exclusionPatterns: ['*.tmp', '*.bak'],
|
||||
}),
|
||||
@@ -1084,10 +1061,7 @@ describe(LibraryService.name, () => {
|
||||
|
||||
it('should not create watched', async () => {
|
||||
await expect(
|
||||
sut.create(authStub.admin, {
|
||||
type: LibraryType.UPLOAD,
|
||||
isWatched: true,
|
||||
}),
|
||||
sut.create({ ownerId: authStub.admin.user.id, type: LibraryType.UPLOAD, isWatched: true }),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(storageMock.watch).not.toHaveBeenCalled();
|
||||
@@ -1117,14 +1091,9 @@ describe(LibraryService.name, () => {
|
||||
|
||||
it('should update library', async () => {
|
||||
libraryMock.update.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
await expect(sut.update(authStub.admin, authStub.admin.user.id, {})).resolves.toEqual(
|
||||
mapLibrary(libraryStub.uploadLibrary1),
|
||||
);
|
||||
expect(libraryMock.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: authStub.admin.user.id,
|
||||
}),
|
||||
);
|
||||
libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
await expect(sut.update('library-id', {})).resolves.toEqual(mapLibrary(libraryStub.uploadLibrary1));
|
||||
expect(libraryMock.update).toHaveBeenCalledWith(expect.objectContaining({ id: 'library-id' }));
|
||||
});
|
||||
|
||||
it('should re-watch library when updating import paths', async () => {
|
||||
@@ -1137,15 +1106,11 @@ describe(LibraryService.name, () => {
|
||||
|
||||
storageMock.checkFileExists.mockResolvedValue(true);
|
||||
|
||||
await expect(
|
||||
sut.update(authStub.admin, authStub.admin.user.id, { importPaths: ['/data/user1/foo'] }),
|
||||
).resolves.toEqual(mapLibrary(libraryStub.externalLibraryWithImportPaths1));
|
||||
|
||||
expect(libraryMock.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: authStub.admin.user.id,
|
||||
}),
|
||||
await expect(sut.update('library-id', { importPaths: ['/data/user1/foo'] })).resolves.toEqual(
|
||||
mapLibrary(libraryStub.externalLibraryWithImportPaths1),
|
||||
);
|
||||
|
||||
expect(libraryMock.update).toHaveBeenCalledWith(expect.objectContaining({ id: 'library-id' }));
|
||||
expect(storageMock.watch).toHaveBeenCalledWith(
|
||||
libraryStub.externalLibraryWithImportPaths1.importPaths,
|
||||
expect.anything(),
|
||||
@@ -1158,15 +1123,11 @@ describe(LibraryService.name, () => {
|
||||
configMock.load.mockResolvedValue(systemConfigStub.libraryWatchEnabled);
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1);
|
||||
|
||||
await expect(sut.update(authStub.admin, authStub.admin.user.id, { exclusionPatterns: ['bar'] })).resolves.toEqual(
|
||||
await expect(sut.update('library-id', { exclusionPatterns: ['bar'] })).resolves.toEqual(
|
||||
mapLibrary(libraryStub.externalLibraryWithImportPaths1),
|
||||
);
|
||||
|
||||
expect(libraryMock.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: authStub.admin.user.id,
|
||||
}),
|
||||
);
|
||||
expect(libraryMock.update).toHaveBeenCalledWith(expect.objectContaining({ id: 'library-id' }));
|
||||
expect(storageMock.watch).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([expect.any(String)]),
|
||||
expect.anything(),
|
||||
@@ -1411,7 +1372,7 @@ describe(LibraryService.name, () => {
|
||||
it('should queue a library scan of external library', async () => {
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
|
||||
await sut.queueScan(authStub.admin, libraryStub.externalLibrary1.id, {});
|
||||
await sut.queueScan(libraryStub.externalLibrary1.id, {});
|
||||
|
||||
expect(jobMock.queue.mock.calls).toEqual([
|
||||
[
|
||||
@@ -1430,9 +1391,7 @@ describe(LibraryService.name, () => {
|
||||
it('should not queue a library scan of upload library', async () => {
|
||||
libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
|
||||
await expect(sut.queueScan(authStub.admin, libraryStub.uploadLibrary1.id, {})).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
);
|
||||
await expect(sut.queueScan(libraryStub.uploadLibrary1.id, {})).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(jobMock.queue).not.toBeCalled();
|
||||
});
|
||||
@@ -1440,7 +1399,7 @@ describe(LibraryService.name, () => {
|
||||
it('should queue a library scan of all modified assets', async () => {
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
|
||||
await sut.queueScan(authStub.admin, libraryStub.externalLibrary1.id, { refreshModifiedFiles: true });
|
||||
await sut.queueScan(libraryStub.externalLibrary1.id, { refreshModifiedFiles: true });
|
||||
|
||||
expect(jobMock.queue.mock.calls).toEqual([
|
||||
[
|
||||
@@ -1459,7 +1418,7 @@ describe(LibraryService.name, () => {
|
||||
it('should queue a forced library scan', async () => {
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
|
||||
await sut.queueScan(authStub.admin, libraryStub.externalLibrary1.id, { refreshAllFiles: true });
|
||||
await sut.queueScan(libraryStub.externalLibrary1.id, { refreshAllFiles: true });
|
||||
|
||||
expect(jobMock.queue.mock.calls).toEqual([
|
||||
[
|
||||
@@ -1478,7 +1437,7 @@ describe(LibraryService.name, () => {
|
||||
|
||||
describe('queueEmptyTrash', () => {
|
||||
it('should queue the trash job', async () => {
|
||||
await sut.queueRemoveOffline(authStub.admin, libraryStub.externalLibrary1.id);
|
||||
await sut.queueRemoveOffline(libraryStub.externalLibrary1.id);
|
||||
|
||||
expect(jobMock.queue.mock.calls).toEqual([
|
||||
[
|
||||
@@ -1566,17 +1525,15 @@ describe(LibraryService.name, () => {
|
||||
|
||||
storageMock.checkFileExists.mockResolvedValue(true);
|
||||
|
||||
const result = await sut.validate(authStub.external1, libraryStub.externalLibraryWithImportPaths1.id, {
|
||||
importPaths: ['/data/user1/'],
|
||||
await expect(sut.validate('library-id', { importPaths: ['/data/user1/'] })).resolves.toEqual({
|
||||
importPaths: [
|
||||
{
|
||||
importPath: '/data/user1/',
|
||||
isValid: true,
|
||||
message: undefined,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.importPaths).toEqual([
|
||||
{
|
||||
importPath: '/data/user1/',
|
||||
isValid: true,
|
||||
message: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should detect when path does not exist', async () => {
|
||||
@@ -1585,17 +1542,15 @@ describe(LibraryService.name, () => {
|
||||
throw error;
|
||||
});
|
||||
|
||||
const result = await sut.validate(authStub.external1, libraryStub.externalLibraryWithImportPaths1.id, {
|
||||
importPaths: ['/data/user1/'],
|
||||
await expect(sut.validate('library-id', { importPaths: ['/data/user1/'] })).resolves.toEqual({
|
||||
importPaths: [
|
||||
{
|
||||
importPath: '/data/user1/',
|
||||
isValid: false,
|
||||
message: 'Path does not exist (ENOENT)',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.importPaths).toEqual([
|
||||
{
|
||||
importPath: '/data/user1/',
|
||||
isValid: false,
|
||||
message: 'Path does not exist (ENOENT)',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should detect when path is not a directory', async () => {
|
||||
@@ -1603,17 +1558,15 @@ describe(LibraryService.name, () => {
|
||||
isDirectory: () => false,
|
||||
} as Stats);
|
||||
|
||||
const result = await sut.validate(authStub.external1, libraryStub.externalLibraryWithImportPaths1.id, {
|
||||
importPaths: ['/data/user1/file'],
|
||||
await expect(sut.validate('library-id', { importPaths: ['/data/user1/file'] })).resolves.toEqual({
|
||||
importPaths: [
|
||||
{
|
||||
importPath: '/data/user1/file',
|
||||
isValid: false,
|
||||
message: 'Not a directory',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.importPaths).toEqual([
|
||||
{
|
||||
importPath: '/data/user1/file',
|
||||
isValid: false,
|
||||
message: 'Not a directory',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an unknown exception from stat', async () => {
|
||||
@@ -1621,17 +1574,15 @@ describe(LibraryService.name, () => {
|
||||
throw new Error('Unknown error');
|
||||
});
|
||||
|
||||
const result = await sut.validate(authStub.external1, libraryStub.externalLibraryWithImportPaths1.id, {
|
||||
importPaths: ['/data/user1/'],
|
||||
await expect(sut.validate('library-id', { importPaths: ['/data/user1/'] })).resolves.toEqual({
|
||||
importPaths: [
|
||||
{
|
||||
importPath: '/data/user1/',
|
||||
isValid: false,
|
||||
message: 'Error: Unknown error',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.importPaths).toEqual([
|
||||
{
|
||||
importPath: '/data/user1/',
|
||||
isValid: false,
|
||||
message: 'Error: Unknown error',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should detect when access rights are missing', async () => {
|
||||
@@ -1641,17 +1592,15 @@ describe(LibraryService.name, () => {
|
||||
|
||||
storageMock.checkFileExists.mockResolvedValue(false);
|
||||
|
||||
const result = await sut.validate(authStub.external1, libraryStub.externalLibraryWithImportPaths1.id, {
|
||||
importPaths: ['/data/user1/'],
|
||||
await expect(sut.validate('library-id', { importPaths: ['/data/user1/'] })).resolves.toEqual({
|
||||
importPaths: [
|
||||
{
|
||||
importPath: '/data/user1/',
|
||||
isValid: false,
|
||||
message: 'Lacking read permission for folder',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.importPaths).toEqual([
|
||||
{
|
||||
importPath: '/data/user1/',
|
||||
isValid: false,
|
||||
message: 'Lacking read permission for folder',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should detect when import path is in immich media folder', async () => {
|
||||
@@ -1659,26 +1608,26 @@ describe(LibraryService.name, () => {
|
||||
const validImport = libraryStub.hasImmichPaths.importPaths[1];
|
||||
when(storageMock.checkFileExists).calledWith(validImport, R_OK).mockResolvedValue(true);
|
||||
|
||||
const result = await sut.validate(authStub.external1, libraryStub.hasImmichPaths.id, {
|
||||
importPaths: libraryStub.hasImmichPaths.importPaths,
|
||||
await expect(
|
||||
sut.validate('library-id', { importPaths: libraryStub.hasImmichPaths.importPaths }),
|
||||
).resolves.toEqual({
|
||||
importPaths: [
|
||||
{
|
||||
importPath: libraryStub.hasImmichPaths.importPaths[0],
|
||||
isValid: false,
|
||||
message: 'Cannot use media upload folder for external libraries',
|
||||
},
|
||||
{
|
||||
importPath: validImport,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
importPath: libraryStub.hasImmichPaths.importPaths[2],
|
||||
isValid: false,
|
||||
message: 'Cannot use media upload folder for external libraries',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.importPaths).toEqual([
|
||||
{
|
||||
importPath: libraryStub.hasImmichPaths.importPaths[0],
|
||||
isValid: false,
|
||||
message: 'Cannot use media upload folder for external libraries',
|
||||
},
|
||||
{
|
||||
importPath: validImport,
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
importPath: libraryStub.hasImmichPaths.importPaths[2],
|
||||
isValid: false,
|
||||
message: 'Cannot use media upload folder for external libraries',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user