mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
move to medium tests
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { LibraryResponseDto, LoginResponseDto, SystemConfigDto, getAllLibraries } from '@immich/sdk';
|
||||
import { LibraryResponseDto, LoginResponseDto, getAllLibraries } from '@immich/sdk';
|
||||
import { cpSync, existsSync, rmSync, unlinkSync } from 'node:fs';
|
||||
import { Socket } from 'socket.io-client';
|
||||
import { userDto, uuidDto } from 'src/fixtures';
|
||||
@@ -1317,171 +1317,6 @@ describe('/libraries', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('watch', () => {
|
||||
let config: SystemConfigDto;
|
||||
let library: LibraryResponseDto;
|
||||
let changeAssetId: string;
|
||||
let deleteAssetId: string;
|
||||
const watcherPath = `${testAssetDir}/temp/watcher-behavior`;
|
||||
|
||||
beforeAll(async () => {
|
||||
config = await utils.getSystemConfig(admin.accessToken);
|
||||
|
||||
const changeAssetPath = `${watcherPath}/change.jpg`;
|
||||
const deleteAssetPath = `${watcherPath}/delete.png`;
|
||||
const changeIgnoredAssetPath = `${watcherPath}/@eaDir/change.png`;
|
||||
const unlinkIgnoredAssetPath = `${watcherPath}/@eaDir/unlink.png`;
|
||||
const changeAssetInternalPath = `${testAssetDirInternal}/temp/watcher-behavior/change.jpg`;
|
||||
const deleteAssetInternalPath = `${testAssetDirInternal}/temp/watcher-behavior/delete.png`;
|
||||
|
||||
utils.createDirectory(watcherPath);
|
||||
cpSync(`${testAssetDir}/albums/nature/el_torcal_rocks.jpg`, changeAssetPath);
|
||||
await utimes(changeAssetPath, 447_775_200_000);
|
||||
utils.createImageFile(deleteAssetPath);
|
||||
utils.createImageFile(changeIgnoredAssetPath);
|
||||
utils.createImageFile(unlinkIgnoredAssetPath);
|
||||
|
||||
library = await utils.createLibrary(admin.accessToken, {
|
||||
ownerId: admin.userId,
|
||||
importPaths: [`${testAssetDirInternal}/temp/watcher-behavior`],
|
||||
exclusionPatterns: ['**/@eaDir/**'],
|
||||
});
|
||||
|
||||
await utils.scan(admin.accessToken, library.id);
|
||||
|
||||
const { assets: changeAssets } = await utils.searchAssets(admin.accessToken, {
|
||||
libraryId: library.id,
|
||||
originalPath: changeAssetInternalPath,
|
||||
});
|
||||
expect(changeAssets.count).toBe(1);
|
||||
changeAssetId = changeAssets.items[0].id;
|
||||
|
||||
const { assets: deleteAssets } = await utils.searchAssets(admin.accessToken, {
|
||||
libraryId: library.id,
|
||||
originalPath: deleteAssetInternalPath,
|
||||
});
|
||||
expect(deleteAssets.count).toBe(1);
|
||||
deleteAssetId = deleteAssets.items[0].id;
|
||||
|
||||
const { status } = await request(app)
|
||||
.put('/system-config')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||
.send({ ...config, library: { ...config.library, watch: { ...config.library.watch, enabled: true } } });
|
||||
expect(status).toBe(200);
|
||||
await utils.waitForWebsocketEvent({ event: 'libraryWatchEnabled', id: library.id });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await request(app).put('/system-config').set('Authorization', `Bearer ${admin.accessToken}`).send(config);
|
||||
utils.removeDirectory(watcherPath);
|
||||
});
|
||||
|
||||
it('should import a new file', async () => {
|
||||
const addAssetPath = `${watcherPath}/add.png`;
|
||||
const addAssetInternalPath = `${testAssetDirInternal}/temp/watcher-behavior/add.png`;
|
||||
utils.createImageFile(addAssetPath);
|
||||
|
||||
await utils.waitForLibraryWatchEvent({ libraryId: library.id, event: 'add', path: addAssetInternalPath });
|
||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
|
||||
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
|
||||
|
||||
const { assets } = await utils.searchAssets(admin.accessToken, {
|
||||
libraryId: library.id,
|
||||
originalPath: addAssetInternalPath,
|
||||
});
|
||||
expect(assets.items).toEqual([expect.objectContaining({ originalPath: addAssetInternalPath })]);
|
||||
|
||||
const asset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
|
||||
expect(asset.originalPath).toBe(addAssetInternalPath);
|
||||
expect(asset.exifInfo).not.toBe(null);
|
||||
});
|
||||
|
||||
it('should detect a changed file', async () => {
|
||||
const changeAssetPath = `${watcherPath}/change.jpg`;
|
||||
const changeAssetInternalPath = `${testAssetDirInternal}/temp/watcher-behavior/change.jpg`;
|
||||
cpSync(`${testAssetDir}/albums/nature/tanners_ridge.jpg`, changeAssetPath);
|
||||
await utimes(changeAssetPath, 447_775_200_001);
|
||||
|
||||
await utils.waitForLibraryWatchEvent({ libraryId: library.id, event: 'change', path: changeAssetInternalPath });
|
||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
|
||||
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
|
||||
|
||||
const { assets } = await utils.searchAssets(admin.accessToken, {
|
||||
libraryId: library.id,
|
||||
originalPath: changeAssetInternalPath,
|
||||
});
|
||||
expect(assets.count).toBe(1);
|
||||
expect(assets.items[0].id).toBe(changeAssetId);
|
||||
|
||||
const updatedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
|
||||
expect(updatedAsset.originalPath).toBe(changeAssetInternalPath);
|
||||
expect(updatedAsset.isOffline).toBe(false);
|
||||
expect(updatedAsset.isTrashed).toBe(false);
|
||||
});
|
||||
|
||||
it('should remove an asset when its file is deleted', async () => {
|
||||
const deleteAssetPath = `${watcherPath}/delete.png`;
|
||||
const deleteAssetInternalPath = `${testAssetDirInternal}/temp/watcher-behavior/delete.png`;
|
||||
utils.removeImageFile(deleteAssetPath);
|
||||
|
||||
await utils.waitForLibraryWatchEvent({ libraryId: library.id, event: 'unlink', path: deleteAssetInternalPath });
|
||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||
|
||||
const { assets } = await utils.searchAssets(admin.accessToken, {
|
||||
libraryId: library.id,
|
||||
originalPath: deleteAssetInternalPath,
|
||||
});
|
||||
expect(assets.items).toEqual([]);
|
||||
|
||||
const { assets: withDeletedAssets } = await utils.searchAssets(admin.accessToken, {
|
||||
libraryId: library.id,
|
||||
withDeleted: true,
|
||||
});
|
||||
expect(withDeletedAssets.items.find((asset) => asset.id === deleteAssetId)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not fire a library watcher event for ignored directory add, change, or unlink activity', async () => {
|
||||
const addIgnoredAssetPath = `${watcherPath}/@eaDir/add.png`;
|
||||
const addIgnoredAssetInternalPath = `${testAssetDirInternal}/temp/watcher-behavior/@eaDir/add.png`;
|
||||
const changeIgnoredAssetPath = `${watcherPath}/@eaDir/change.png`;
|
||||
const changeIgnoredAssetInternalPath = `${testAssetDirInternal}/temp/watcher-behavior/@eaDir/change.png`;
|
||||
const unlinkIgnoredAssetPath = `${watcherPath}/@eaDir/unlink.png`;
|
||||
const unlinkIgnoredAssetInternalPath = `${testAssetDirInternal}/temp/watcher-behavior/@eaDir/unlink.png`;
|
||||
|
||||
const addWatchPromise = utils.waitForLibraryWatchEvent({
|
||||
libraryId: library.id,
|
||||
event: 'add',
|
||||
path: addIgnoredAssetInternalPath,
|
||||
timeout: 7000,
|
||||
});
|
||||
const changeWatchPromise = utils.waitForLibraryWatchEvent({
|
||||
libraryId: library.id,
|
||||
event: 'change',
|
||||
path: changeIgnoredAssetInternalPath,
|
||||
timeout: 7000,
|
||||
});
|
||||
const unlinkWatchPromise = utils.waitForLibraryWatchEvent({
|
||||
libraryId: library.id,
|
||||
event: 'unlink',
|
||||
path: unlinkIgnoredAssetInternalPath,
|
||||
timeout: 7000,
|
||||
});
|
||||
|
||||
utils.createImageFile(addIgnoredAssetPath);
|
||||
cpSync(`${testAssetDir}/albums/nature/tanners_ridge.jpg`, changeIgnoredAssetPath);
|
||||
await utimes(changeIgnoredAssetPath, 447_775_200_001);
|
||||
utils.removeImageFile(unlinkIgnoredAssetPath);
|
||||
|
||||
await Promise.all([
|
||||
expect(addWatchPromise).rejects.toThrow('Timed out waiting for libraryWatchFired event'),
|
||||
expect(changeWatchPromise).rejects.toThrow('Timed out waiting for libraryWatchFired event'),
|
||||
expect(unlinkWatchPromise).rejects.toThrow('Timed out waiting for libraryWatchFired event'),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /libraries/:id/validate', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(app).post(`/libraries/${uuidDto.notFound}/validate`).send({});
|
||||
|
||||
+1
-32
@@ -76,21 +76,8 @@ import { playwrightDbHost, playwrightHost, playwriteBaseUrl } from '../playwrigh
|
||||
export type { Emitter } from '@socket.io/component-emitter';
|
||||
|
||||
type CommandResponse = { stdout: string; stderr: string; exitCode: number | null };
|
||||
type EventType =
|
||||
| 'assetUpload'
|
||||
| 'assetUpdate'
|
||||
| 'assetDelete'
|
||||
| 'userDelete'
|
||||
| 'assetHidden'
|
||||
| 'libraryWatchEnabled'
|
||||
| 'libraryWatchFired';
|
||||
type EventType = 'assetUpload' | 'assetUpdate' | 'assetDelete' | 'userDelete' | 'assetHidden';
|
||||
type WaitOptions = { event: EventType; id?: string; total?: number; timeout?: number };
|
||||
type LibraryWatchEventOptions = {
|
||||
libraryId: string;
|
||||
event: 'add' | 'change' | 'unlink';
|
||||
path: string;
|
||||
timeout?: number;
|
||||
};
|
||||
type AdminSetupOptions = { onboarding?: boolean };
|
||||
type FileData = { bytes?: Buffer; filename: string };
|
||||
|
||||
@@ -141,8 +128,6 @@ const events: Record<EventType, Set<string>> = {
|
||||
assetUpdate: new Set<string>(),
|
||||
assetDelete: new Set<string>(),
|
||||
userDelete: new Set<string>(),
|
||||
libraryWatchEnabled: new Set<string>(),
|
||||
libraryWatchFired: new Set<string>(),
|
||||
};
|
||||
|
||||
const idCallbacks: Record<string, () => void> = {};
|
||||
@@ -150,9 +135,6 @@ const countCallbacks: Record<string, { count: number; callback: () => void }> =
|
||||
|
||||
const execPromise = promisify(exec);
|
||||
|
||||
const getLibraryWatchEventKey = ({ libraryId, event, path }: Omit<LibraryWatchEventOptions, 'timeout'>) =>
|
||||
`${libraryId}:${event}:${path}`;
|
||||
|
||||
const onEvent = ({ event, id }: { event: EventType; id: string }) => {
|
||||
// console.log(`Received event: ${event} [id=${id}]`);
|
||||
const set = events[event];
|
||||
@@ -266,12 +248,6 @@ export const utils = {
|
||||
.on('on_asset_hidden', (assetId: string) => onEvent({ event: 'assetHidden', id: assetId }))
|
||||
.on('on_asset_delete', (assetId: string) => onEvent({ event: 'assetDelete', id: assetId }))
|
||||
.on('on_user_delete', (userId: string) => onEvent({ event: 'userDelete', id: userId }))
|
||||
.on('on_library_watch_enabled', (data: { libraryId: string }) =>
|
||||
onEvent({ event: 'libraryWatchEnabled', id: data.libraryId }),
|
||||
)
|
||||
.on('on_library_watch_fired', (data: { libraryId: string; event: 'add' | 'change' | 'unlink'; path: string }) =>
|
||||
onEvent({ event: 'libraryWatchFired', id: getLibraryWatchEventKey(data) }),
|
||||
)
|
||||
.connect();
|
||||
});
|
||||
},
|
||||
@@ -324,13 +300,6 @@ export const utils = {
|
||||
});
|
||||
},
|
||||
|
||||
waitForLibraryWatchEvent: ({ libraryId, event, path, timeout }: LibraryWatchEventOptions): Promise<void> =>
|
||||
utils.waitForWebsocketEvent({
|
||||
event: 'libraryWatchFired',
|
||||
id: getLibraryWatchEventKey({ libraryId, event, path }),
|
||||
timeout,
|
||||
}),
|
||||
|
||||
initSdk: () => {
|
||||
setBaseUrl(app);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user