mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
* duplicate detection job, entity, config * queueing * job panel, update api * use embedding in db instead of fetching * disable concurrency * only queue visible assets * handle multiple duplicateIds * update concurrent queue check * add provider * add web placeholder, server endpoint, migration, various fixes * update sql * select embedding by default * rename variable * simplify * remove separate entity, handle re-running with different threshold, set default back to 0.02 * fix tests * add tests * add index to entity * formatting * update asset mock * fix `upsertJobStatus` signature * update sql * formatting * default to 0.03 * optimize clustering * use asset's `duplicateId` if present * update sql * update tests * expose admin setting * refactor * formatting * skip if ml is disabled * debug trash e2e * remove from web * remove from sidebar * test if ml is disabled * update sql * separate duplicate detection from clip in config, disable by default for now * fix doc * lower minimum `maxDistance` * update api * Add and Use Duplicate Detection Feature Flag (#9364) * Add Duplicate Detection Flag * Use Duplicate Detection Flag * Attempt Fixes for Failing Checks * lower minimum `maxDistance` * fix tests --------- Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com> * chore: fixes and additions after rebase * chore: update api (remove new Role enum) * fix: left join smart search so getAll works without machine learning * test: trash e2e go back to checking length of assets is zero * chore: regen api after rebase * test: fix tests after rebase * redundant join --------- Co-authored-by: Nicholas Flamy <30300649+NicholasFlamy@users.noreply.github.com> Co-authored-by: Zack Pollard <zackpollard@ymail.com> Co-authored-by: Zack Pollard <zack@futo.org>
95 lines
3.5 KiB
TypeScript
95 lines
3.5 KiB
TypeScript
import { LoginResponseDto, getAllAssets } from '@immich/sdk';
|
|
import { Socket } from 'socket.io-client';
|
|
import { errorDto } from 'src/responses';
|
|
import { app, asBearerAuth, 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 getAllAssets({}, { headers: asBearerAuth(admin.accessToken) });
|
|
expect(before).toStrictEqual([expect.objectContaining({ id: assetId, isTrashed: true })]);
|
|
|
|
const { status } = await request(app).post('/trash/empty').set('Authorization', `Bearer ${admin.accessToken}`);
|
|
expect(status).toBe(204);
|
|
|
|
await utils.waitForWebsocketEvent({ event: 'assetDelete', id: assetId });
|
|
|
|
const after = await getAllAssets({}, { headers: asBearerAuth(admin.accessToken) });
|
|
expect(after.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
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 getAllAssets({}, { headers: asBearerAuth(admin.accessToken) });
|
|
expect(before).toStrictEqual([expect.objectContaining({ id: assetId, isTrashed: true })]);
|
|
|
|
const { status } = await request(app).post('/trash/restore').set('Authorization', `Bearer ${admin.accessToken}`);
|
|
expect(status).toBe(204);
|
|
|
|
const after = await getAllAssets({}, { headers: asBearerAuth(admin.accessToken) });
|
|
expect(after).toStrictEqual([expect.objectContaining({ id: assetId, isTrashed: false })]);
|
|
});
|
|
});
|
|
|
|
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 } = await request(app)
|
|
.post('/trash/restore/assets')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send({ ids: [assetId] });
|
|
expect(status).toBe(204);
|
|
|
|
const after = await utils.getAssetInfo(admin.accessToken, assetId);
|
|
expect(after.isTrashed).toBe(false);
|
|
});
|
|
});
|
|
});
|