forked from Cutlery/immich
Compare commits
1 Commits
main
...
refactor/l
Author | SHA1 | Date | |
---|---|---|---|
|
331dd058be |
@ -6,7 +6,7 @@ import {
|
|||||||
getAllLibraries,
|
getAllLibraries,
|
||||||
scanLibrary,
|
scanLibrary,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { existsSync, rmdirSync } from 'node:fs';
|
import { cpSync, existsSync, readdirSync } from 'node:fs';
|
||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
import { userDto, uuidDto } from 'src/fixtures';
|
import { userDto, uuidDto } from 'src/fixtures';
|
||||||
import { errorDto } from 'src/responses';
|
import { errorDto } from 'src/responses';
|
||||||
@ -33,14 +33,12 @@ describe('/library', () => {
|
|||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
utils.disconnectWebsocket(websocket);
|
utils.disconnectWebsocket(websocket);
|
||||||
|
utils.deleteTempFolder();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
utils.resetEvents();
|
utils.resetEvents();
|
||||||
const tempDir = `${testAssetDir}/temp`;
|
utils.deleteTempFolder();
|
||||||
if (existsSync(tempDir)) {
|
|
||||||
rmdirSync(tempDir, { recursive: true });
|
|
||||||
}
|
|
||||||
utils.createImageFile(`${testAssetDir}/temp/directoryA/assetA.png`);
|
utils.createImageFile(`${testAssetDir}/temp/directoryA/assetA.png`);
|
||||||
utils.createImageFile(`${testAssetDir}/temp/directoryB/assetB.png`);
|
utils.createImageFile(`${testAssetDir}/temp/directoryB/assetB.png`);
|
||||||
});
|
});
|
||||||
@ -357,95 +355,6 @@ describe('/library', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
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 utils.createLibrary(admin.accessToken, {
|
|
||||||
ownerId: admin.userId,
|
|
||||||
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,
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should delete an external library with assets', async () => {
|
|
||||||
const library = await utils.createLibrary(admin.accessToken, {
|
|
||||||
ownerId: admin.userId,
|
|
||||||
type: LibraryType.External,
|
|
||||||
importPaths: [`${testAssetDirInternal}/temp`],
|
|
||||||
});
|
|
||||||
|
|
||||||
await scan(admin.accessToken, library.id);
|
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', total: 2 });
|
|
||||||
|
|
||||||
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,
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
// ensure no files get deleted
|
|
||||||
expect(existsSync(`${testAssetDir}/temp/directoryA/assetA.png`)).toBe(true);
|
|
||||||
expect(existsSync(`${testAssetDir}/temp/directoryB/assetB.png`)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /library/:id/statistics', () => {
|
describe('GET /library/:id/statistics', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(app).get(`/library/${uuidDto.notFound}/statistics`);
|
const { status, body } = await request(app).get(`/library/${uuidDto.notFound}/statistics`);
|
||||||
@ -550,6 +459,51 @@ describe('/library', () => {
|
|||||||
|
|
||||||
expect(newAssets.count).toBe(3);
|
expect(newAssets.count).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should offline missing files', async () => {
|
||||||
|
cpSync(`${testAssetDir}/albums/nature`, `${testAssetDir}/temp`, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
console.log(readdirSync(`${testAssetDir}/temp`));
|
||||||
|
|
||||||
|
const library = await utils.createLibrary(admin.accessToken, {
|
||||||
|
ownerId: admin.userId,
|
||||||
|
type: LibraryType.External,
|
||||||
|
importPaths: [`${testAssetDirInternal}/temp`],
|
||||||
|
});
|
||||||
|
|
||||||
|
await scan(admin.accessToken, library.id, { refreshAllFiles: true });
|
||||||
|
// await setTimeout(8000);
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||||
|
// await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
|
||||||
|
// await utils.waitForQueueFinish(admin.accessToken, 'thumbnailGeneration');
|
||||||
|
|
||||||
|
const onlineAssets = await utils.getAllAssets(admin.accessToken);
|
||||||
|
console.log(onlineAssets);
|
||||||
|
// expect(onlineAssets.length).toBeGreaterThan(1);
|
||||||
|
// console.log(onlineAssets)
|
||||||
|
|
||||||
|
utils.deleteTempFolder();
|
||||||
|
|
||||||
|
await scan(admin.accessToken, library.id);
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||||
|
|
||||||
|
const assets = await utils.getAllAssets(admin.accessToken);
|
||||||
|
console.log(assets);
|
||||||
|
|
||||||
|
expect(assets).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
isOffline: true,
|
||||||
|
originalFileName: 'el_torcal_rocks.jpg',
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
isOffline: true,
|
||||||
|
originalFileName: 'tanners_ridge.jpg',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /library/:id/removeOffline', () => {
|
describe('POST /library/:id/removeOffline', () => {
|
||||||
@ -608,4 +562,93 @@ describe('/library', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 utils.createLibrary(admin.accessToken, {
|
||||||
|
ownerId: admin.userId,
|
||||||
|
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,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete an external library with assets', async () => {
|
||||||
|
const library = await utils.createLibrary(admin.accessToken, {
|
||||||
|
ownerId: admin.userId,
|
||||||
|
type: LibraryType.External,
|
||||||
|
importPaths: [`${testAssetDirInternal}/temp`],
|
||||||
|
});
|
||||||
|
|
||||||
|
await scan(admin.accessToken, library.id);
|
||||||
|
await utils.waitForWebsocketEvent({ event: 'assetUpload', total: 2 });
|
||||||
|
|
||||||
|
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,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ensure no files get deleted
|
||||||
|
expect(existsSync(`${testAssetDir}/temp/directoryA/assetA.png`)).toBe(true);
|
||||||
|
expect(existsSync(`${testAssetDir}/temp/directoryB/assetB.png`)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
AllJobStatusResponseDto,
|
||||||
AssetFileUploadResponseDto,
|
AssetFileUploadResponseDto,
|
||||||
AssetResponseDto,
|
AssetResponseDto,
|
||||||
CreateAlbumDto,
|
CreateAlbumDto,
|
||||||
@ -18,6 +19,7 @@ import {
|
|||||||
defaults,
|
defaults,
|
||||||
deleteAssets,
|
deleteAssets,
|
||||||
getAllAssets,
|
getAllAssets,
|
||||||
|
getAllJobsStatus,
|
||||||
getAssetInfo,
|
getAssetInfo,
|
||||||
login,
|
login,
|
||||||
searchMetadata,
|
searchMetadata,
|
||||||
@ -31,6 +33,7 @@ import { createHash } from 'node:crypto';
|
|||||||
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import path, { dirname } from 'node:path';
|
import path, { dirname } from 'node:path';
|
||||||
|
import { setTimeout as setAsyncTimeout } from 'node:timers/promises';
|
||||||
import { promisify } from 'node:util';
|
import { promisify } from 'node:util';
|
||||||
import pg from 'pg';
|
import pg from 'pg';
|
||||||
import { io, type Socket } from 'socket.io-client';
|
import { io, type Socket } from 'socket.io-client';
|
||||||
@ -406,6 +409,35 @@ export const utils = {
|
|||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
deleteTempFolder: () => {
|
||||||
|
rmSync(`${testAssetDir}/temp`, { recursive: true, force: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
isQueueEmpty: async (accessToken: string, queue: keyof AllJobStatusResponseDto) => {
|
||||||
|
const queues = await getAllJobsStatus({ headers: asBearerAuth(accessToken) });
|
||||||
|
const jobCounts = queues[queue].jobCounts;
|
||||||
|
console.log(jobCounts);
|
||||||
|
return !jobCounts.active && !jobCounts.waiting;
|
||||||
|
},
|
||||||
|
|
||||||
|
waitForQueueFinish: (accessToken: string, queue: keyof AllJobStatusResponseDto, ms?: number) => {
|
||||||
|
return new Promise<void>(async (resolve, reject) => {
|
||||||
|
const timeout = setTimeout(() => reject(new Error('Timed out waiting for queue to empty')), ms || 10_000);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const done = await utils.isQueueEmpty(accessToken, queue);
|
||||||
|
if (done) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await setAsyncTimeout(300);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(timeout);
|
||||||
|
// await setAsyncTimeout(5000);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
cliLogin: async (accessToken: string) => {
|
cliLogin: async (accessToken: string) => {
|
||||||
const key = await utils.createApiKey(accessToken);
|
const key = await utils.createApiKey(accessToken);
|
||||||
await immichCli(['login', app, `${key.secret}`]);
|
await immichCli(['login', app, `${key.secret}`]);
|
||||||
|
@ -12,7 +12,7 @@ export default defineConfig({
|
|||||||
test: {
|
test: {
|
||||||
include: ['src/{api,cli}/specs/*.e2e-spec.ts'],
|
include: ['src/{api,cli}/specs/*.e2e-spec.ts'],
|
||||||
globalSetup,
|
globalSetup,
|
||||||
testTimeout: 10_000,
|
testTimeout: 15_000,
|
||||||
poolOptions: {
|
poolOptions: {
|
||||||
threads: {
|
threads: {
|
||||||
singleThread: true,
|
singleThread: true,
|
||||||
|
@ -31,42 +31,6 @@ describe(`${LibraryController.name} (e2e)`, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /library/:id/scan', () => {
|
describe('POST /library/:id/scan', () => {
|
||||||
it('should offline missing files', async () => {
|
|
||||||
await fs.promises.cp(`${IMMICH_TEST_ASSET_PATH}/albums/nature`, `${IMMICH_TEST_ASSET_TEMP_PATH}/albums/nature`, {
|
|
||||||
recursive: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const library = await api.libraryApi.create(server, admin.accessToken, {
|
|
||||||
ownerId: admin.userId,
|
|
||||||
type: LibraryType.EXTERNAL,
|
|
||||||
importPaths: [`${IMMICH_TEST_ASSET_TEMP_PATH}`],
|
|
||||||
});
|
|
||||||
|
|
||||||
await api.libraryApi.scanLibrary(server, admin.accessToken, library.id);
|
|
||||||
|
|
||||||
const onlineAssets = await api.assetApi.getAllAssets(server, admin.accessToken);
|
|
||||||
expect(onlineAssets.length).toBeGreaterThan(1);
|
|
||||||
|
|
||||||
await restoreTempFolder();
|
|
||||||
|
|
||||||
await api.libraryApi.scanLibrary(server, admin.accessToken, library.id);
|
|
||||||
|
|
||||||
const assets = await api.assetApi.getAllAssets(server, admin.accessToken);
|
|
||||||
|
|
||||||
expect(assets).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
expect.objectContaining({
|
|
||||||
isOffline: true,
|
|
||||||
originalFileName: 'el_torcal_rocks.jpg',
|
|
||||||
}),
|
|
||||||
expect.objectContaining({
|
|
||||||
isOffline: true,
|
|
||||||
originalFileName: 'tanners_ridge.jpg',
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should scan new files', async () => {
|
it('should scan new files', async () => {
|
||||||
const library = await api.libraryApi.create(server, admin.accessToken, {
|
const library = await api.libraryApi.create(server, admin.accessToken, {
|
||||||
ownerId: admin.userId,
|
ownerId: admin.userId,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user