mirror of
https://github.com/immich-app/immich.git
synced 2025-05-24 01:12:58 -04:00
262 lines
10 KiB
TypeScript
262 lines
10 KiB
TypeScript
import { LoginResponseDto, getAssetInfo, getAssetStatistics } from '@immich/sdk';
|
|
import { existsSync } from 'node:fs';
|
|
import { Socket } from 'socket.io-client';
|
|
import { errorDto } from 'src/responses';
|
|
import { app, asBearerAuth, testAssetDir, testAssetDirInternal, utils } from 'src/utils';
|
|
import request from 'supertest';
|
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
|
|
describe('/trash', () => {
|
|
let admin: LoginResponseDto;
|
|
let ws: Socket;
|
|
|
|
beforeAll(async () => {
|
|
await utils.resetDatabase();
|
|
admin = await utils.adminSetup({ onboarding: false });
|
|
ws = await utils.connectWebsocket(admin.accessToken);
|
|
});
|
|
|
|
afterAll(() => {
|
|
utils.disconnectWebsocket(ws);
|
|
});
|
|
|
|
describe('POST /trash/empty', () => {
|
|
it('should require authentication', async () => {
|
|
const { status, body } = await request(app).post('/trash/empty');
|
|
|
|
expect(status).toBe(401);
|
|
expect(body).toEqual(errorDto.unauthorized);
|
|
});
|
|
|
|
it('should empty the trash', async () => {
|
|
const { id: assetId } = await utils.createAsset(admin.accessToken);
|
|
await utils.deleteAssets(admin.accessToken, [assetId]);
|
|
|
|
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
|
expect(before).toStrictEqual(expect.objectContaining({ id: assetId, isTrashed: true }));
|
|
|
|
const { status, body } = await request(app)
|
|
.post('/trash/empty')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
|
expect(status).toBe(200);
|
|
expect(body).toEqual({ count: 1 });
|
|
|
|
await utils.waitForWebsocketEvent({ event: 'assetDelete', id: assetId });
|
|
|
|
const after = await getAssetStatistics({ isTrashed: true }, { headers: asBearerAuth(admin.accessToken) });
|
|
expect(after.total).toBe(0);
|
|
|
|
expect(existsSync(before.originalPath)).toBe(false);
|
|
});
|
|
|
|
it('should empty the trash with archived assets', async () => {
|
|
const { id: assetId } = await utils.createAsset(admin.accessToken);
|
|
await utils.archiveAssets(admin.accessToken, [assetId]);
|
|
await utils.deleteAssets(admin.accessToken, [assetId]);
|
|
|
|
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
|
expect(before).toStrictEqual(expect.objectContaining({ id: assetId, isTrashed: true, isArchived: true }));
|
|
|
|
const { status, body } = await request(app)
|
|
.post('/trash/empty')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
|
expect(status).toBe(200);
|
|
expect(body).toEqual({ count: 1 });
|
|
|
|
await utils.waitForWebsocketEvent({ event: 'assetDelete', id: assetId });
|
|
|
|
const after = await getAssetStatistics({ isTrashed: true }, { headers: asBearerAuth(admin.accessToken) });
|
|
expect(after.total).toBe(0);
|
|
|
|
expect(existsSync(before.originalPath)).toBe(false);
|
|
});
|
|
|
|
it('should remove offline assets', async () => {
|
|
const library = await utils.createLibrary(admin.accessToken, {
|
|
ownerId: admin.userId,
|
|
importPaths: [`${testAssetDirInternal}/temp/offline`],
|
|
});
|
|
|
|
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
|
|
|
await utils.scan(admin.accessToken, library.id);
|
|
|
|
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
|
|
expect(assets.items.length).toBe(1);
|
|
const asset = assets.items[0];
|
|
|
|
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] });
|
|
|
|
await utils.scan(admin.accessToken, library.id);
|
|
|
|
const assetBefore = await utils.getAssetInfo(admin.accessToken, asset.id);
|
|
expect(assetBefore).toMatchObject({ isTrashed: true, isOffline: true });
|
|
|
|
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
|
|
|
const { status } = await request(app).post('/trash/empty').set('Authorization', `Bearer ${admin.accessToken}`);
|
|
expect(status).toBe(200);
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, 'backgroundTask');
|
|
|
|
const assetAfter = await utils.getAssetInfo(admin.accessToken, asset.id);
|
|
expect(assetAfter).toMatchObject({ isTrashed: true, isOffline: true });
|
|
});
|
|
|
|
it.skip('should not delete offline assets from disk', async () => {
|
|
// Can't be tested at the moment due to no mechanism to forward time
|
|
const library = await utils.createLibrary(admin.accessToken, {
|
|
ownerId: admin.userId,
|
|
importPaths: [`${testAssetDirInternal}/temp/offline`],
|
|
});
|
|
|
|
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
|
|
|
await utils.scan(admin.accessToken, library.id);
|
|
|
|
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
|
|
expect(assets.items.length).toBe(1);
|
|
const asset = assets.items[0];
|
|
|
|
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] });
|
|
|
|
await utils.scan(admin.accessToken, library.id);
|
|
|
|
const assetBefore = await utils.getAssetInfo(admin.accessToken, asset.id);
|
|
expect(assetBefore).toMatchObject({ isTrashed: true, isOffline: true });
|
|
|
|
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
|
|
|
const { status } = await request(app).post('/trash/empty').set('Authorization', `Bearer ${admin.accessToken}`);
|
|
expect(status).toBe(200);
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, 'backgroundTask');
|
|
|
|
const after = await getAssetStatistics({ isTrashed: true }, { headers: asBearerAuth(admin.accessToken) });
|
|
expect(after.total).toBe(0);
|
|
|
|
expect(existsSync(`${testAssetDir}/temp/offline/offline.png`)).toBe(true);
|
|
|
|
utils.removeImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
|
});
|
|
});
|
|
|
|
describe('POST /trash/restore', () => {
|
|
it('should require authentication', async () => {
|
|
const { status, body } = await request(app).post('/trash/restore');
|
|
|
|
expect(status).toBe(401);
|
|
expect(body).toEqual(errorDto.unauthorized);
|
|
});
|
|
|
|
it('should restore all trashed assets', async () => {
|
|
const { id: assetId } = await utils.createAsset(admin.accessToken);
|
|
await utils.deleteAssets(admin.accessToken, [assetId]);
|
|
|
|
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
|
expect(before).toStrictEqual(expect.objectContaining({ id: assetId, isTrashed: true }));
|
|
|
|
const { status, body } = await request(app)
|
|
.post('/trash/restore')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
|
expect(status).toBe(200);
|
|
expect(body).toEqual({ count: 1 });
|
|
|
|
const after = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
|
expect(after).toStrictEqual(expect.objectContaining({ id: assetId, isTrashed: false }));
|
|
});
|
|
|
|
it('should not restore offline assets', async () => {
|
|
const library = await utils.createLibrary(admin.accessToken, {
|
|
ownerId: admin.userId,
|
|
importPaths: [`${testAssetDirInternal}/temp/offline`],
|
|
});
|
|
|
|
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
|
|
|
await utils.scan(admin.accessToken, library.id);
|
|
|
|
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
|
|
expect(assets.count).toBe(1);
|
|
const assetId = assets.items[0].id;
|
|
|
|
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] });
|
|
|
|
await utils.scan(admin.accessToken, library.id);
|
|
|
|
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
|
expect(before).toStrictEqual(expect.objectContaining({ id: assetId, isOffline: true }));
|
|
|
|
const { status } = await request(app).post('/trash/restore').set('Authorization', `Bearer ${admin.accessToken}`);
|
|
expect(status).toBe(200);
|
|
|
|
const after = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
|
expect(after).toStrictEqual(expect.objectContaining({ id: assetId, isOffline: true }));
|
|
|
|
utils.removeImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
|
});
|
|
});
|
|
|
|
describe('POST /trash/restore/assets', () => {
|
|
it('should require authentication', async () => {
|
|
const { status, body } = await request(app).post('/trash/restore/assets');
|
|
|
|
expect(status).toBe(401);
|
|
expect(body).toEqual(errorDto.unauthorized);
|
|
});
|
|
|
|
it('should restore a trashed asset by id', async () => {
|
|
const { id: assetId } = await utils.createAsset(admin.accessToken);
|
|
await utils.deleteAssets(admin.accessToken, [assetId]);
|
|
|
|
const before = await utils.getAssetInfo(admin.accessToken, assetId);
|
|
expect(before.isTrashed).toBe(true);
|
|
|
|
const { status, body } = await request(app)
|
|
.post('/trash/restore/assets')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send({ ids: [assetId] });
|
|
expect(status).toBe(200);
|
|
expect(body).toEqual({ count: 1 });
|
|
|
|
const after = await utils.getAssetInfo(admin.accessToken, assetId);
|
|
expect(after.isTrashed).toBe(false);
|
|
});
|
|
|
|
it('should not restore an offline asset', async () => {
|
|
const library = await utils.createLibrary(admin.accessToken, {
|
|
ownerId: admin.userId,
|
|
importPaths: [`${testAssetDirInternal}/temp/offline`],
|
|
});
|
|
|
|
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
|
|
|
await utils.scan(admin.accessToken, library.id);
|
|
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
|
|
|
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
|
|
expect(assets.count).toBe(1);
|
|
const assetId = assets.items[0].id;
|
|
|
|
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] });
|
|
|
|
await utils.scan(admin.accessToken, library.id);
|
|
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
|
|
|
const before = await utils.getAssetInfo(admin.accessToken, assetId);
|
|
expect(before.isTrashed).toBe(true);
|
|
|
|
const { status } = await request(app)
|
|
.post('/trash/restore/assets')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send({ ids: [assetId] });
|
|
expect(status).toBe(200);
|
|
|
|
const after = await utils.getAssetInfo(admin.accessToken, assetId);
|
|
expect(after.isTrashed).toBe(true);
|
|
|
|
utils.removeImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
|
});
|
|
});
|
|
});
|