1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-08 21:06:48 +01:00
immich/server/e2e/jobs/specs/library-watcher.e2e-spec.ts
Jonathan Jogenfors 4cb0f37918
chore(server): Move library watcher to microservices (#7533)
* move watcher init to micro

* document watcher recovery

* chore: fix lint

* add try lock

* use global library watch lock

* fix: ensure lock stays on

* fix: mocks

* unit test for library watch lock

* move statement to correct test

* fix: correct return type of try lock

* fix: tests

* add library teardown

* add chokidar error handler

* make event strings an enum

* wait for event refactor

* refactor event type mocks

* expect correct error

* don't release lock in teardown

* chore: lint

* use enum

* fix mock

* fix lint

* fix watcher await

* remove await

* simplify typing

* remove async

* Revert "remove async"

This reverts commit 84ab5abac4.

* can now change watch settings at runtime

* fix lint

* only watch libraries if enabled

---------

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-03-07 11:36:53 -06:00

224 lines
7.5 KiB
TypeScript

import { LibraryResponseDto, LibraryService, LoginResponseDto, StorageEventType } from '@app/domain';
import { AssetType, LibraryType } from '@app/infra/entities';
import fs from 'node:fs/promises';
import path from 'node:path';
import {
IMMICH_TEST_ASSET_PATH,
IMMICH_TEST_ASSET_TEMP_PATH,
restoreTempFolder,
testApp,
waitForEvent,
} from '../../../src/test-utils/utils';
import { api } from '../../client';
describe(`Library watcher (e2e)`, () => {
let server: any;
let admin: LoginResponseDto;
let libraryService: LibraryService;
const configFilePath = process.env.IMMICH_CONFIG_FILE;
beforeAll(async () => {
process.env.IMMICH_CONFIG_FILE = path.normalize(`${__dirname}/../config/library-watcher-e2e-config.json`);
const app = await testApp.create();
server = app.getHttpServer();
libraryService = testApp.get(LibraryService);
});
beforeEach(async () => {
await testApp.reset();
await restoreTempFolder();
await api.authApi.adminSignUp(server);
admin = await api.authApi.adminLogin(server);
});
afterEach(async () => {
await libraryService.teardown();
});
afterAll(async () => {
await testApp.teardown();
await restoreTempFolder();
process.env.IMMICH_CONFIG_FILE = configFilePath;
});
describe('Event handling', () => {
describe('Single import path', () => {
beforeEach(async () => {
await api.libraryApi.create(server, admin.accessToken, {
type: LibraryType.EXTERNAL,
importPaths: [`${IMMICH_TEST_ASSET_TEMP_PATH}`],
});
});
it('should import a new file', async () => {
await fs.copyFile(
`${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`,
`${IMMICH_TEST_ASSET_TEMP_PATH}/file.jpg`,
);
await waitForEvent(libraryService, StorageEventType.ADD);
const afterAssets = await api.assetApi.getAllAssets(server, admin.accessToken);
expect(afterAssets.length).toEqual(1);
});
it('should import new files with case insensitive extensions', async () => {
await fs.copyFile(
`${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`,
`${IMMICH_TEST_ASSET_TEMP_PATH}/file2.JPG`,
);
await fs.copyFile(
`${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`,
`${IMMICH_TEST_ASSET_TEMP_PATH}/file3.Jpg`,
);
await fs.copyFile(
`${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`,
`${IMMICH_TEST_ASSET_TEMP_PATH}/file4.jpG`,
);
await fs.copyFile(
`${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`,
`${IMMICH_TEST_ASSET_TEMP_PATH}/file5.jPg`,
);
await waitForEvent(libraryService, StorageEventType.ADD, 4);
const afterAssets = await api.assetApi.getAllAssets(server, admin.accessToken);
expect(afterAssets.length).toEqual(4);
});
it('should update a changed file', async () => {
await fs.copyFile(
`${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`,
`${IMMICH_TEST_ASSET_TEMP_PATH}/file.jpg`,
);
await waitForEvent(libraryService, StorageEventType.ADD);
const originalAssets = await api.assetApi.getAllAssets(server, admin.accessToken);
expect(originalAssets.length).toEqual(1);
await fs.copyFile(
`${IMMICH_TEST_ASSET_PATH}/albums/nature/prairie_falcon.jpg`,
`${IMMICH_TEST_ASSET_TEMP_PATH}/file.jpg`,
);
await waitForEvent(libraryService, StorageEventType.CHANGE);
const afterAssets = await api.assetApi.getAllAssets(server, admin.accessToken);
expect(afterAssets).toEqual([
expect.objectContaining({
// Make sure we keep the original asset id
id: originalAssets[0].id,
type: AssetType.IMAGE,
exifInfo: expect.objectContaining({
make: 'Canon',
model: 'Canon EOS R5',
exifImageWidth: 800,
exifImageHeight: 533,
exposureTime: '1/4000',
}),
}),
]);
});
});
describe('Multiple import paths', () => {
beforeEach(async () => {
await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir1`, { recursive: true });
await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir2`, { recursive: true });
await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir3`, { recursive: true });
await api.libraryApi.create(server, admin.accessToken, {
type: LibraryType.EXTERNAL,
importPaths: [
`${IMMICH_TEST_ASSET_TEMP_PATH}/dir1`,
`${IMMICH_TEST_ASSET_TEMP_PATH}/dir2`,
`${IMMICH_TEST_ASSET_TEMP_PATH}/dir3`,
],
});
});
it('should add new files in multiple import paths', async () => {
await fs.copyFile(
`${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`,
`${IMMICH_TEST_ASSET_TEMP_PATH}/dir1/file2.jpg`,
);
await fs.copyFile(
`${IMMICH_TEST_ASSET_PATH}/albums/nature/polemonium_reptans.jpg`,
`${IMMICH_TEST_ASSET_TEMP_PATH}/dir2/file3.jpg`,
);
await fs.copyFile(
`${IMMICH_TEST_ASSET_PATH}/albums/nature/tanners_ridge.jpg`,
`${IMMICH_TEST_ASSET_TEMP_PATH}/dir3/file4.jpg`,
);
await waitForEvent(libraryService, StorageEventType.ADD, 3);
const assets = await api.assetApi.getAllAssets(server, admin.accessToken);
expect(assets.length).toEqual(3);
});
it('should offline a removed file', async () => {
await fs.copyFile(
`${IMMICH_TEST_ASSET_PATH}/albums/nature/polemonium_reptans.jpg`,
`${IMMICH_TEST_ASSET_TEMP_PATH}/dir1/file.jpg`,
);
await waitForEvent(libraryService, StorageEventType.ADD);
const addedAssets = await api.assetApi.getAllAssets(server, admin.accessToken);
expect(addedAssets.length).toEqual(1);
await fs.unlink(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir1/file.jpg`);
await waitForEvent(libraryService, StorageEventType.UNLINK);
const afterAssets = await api.assetApi.getAllAssets(server, admin.accessToken);
expect(afterAssets[0].isOffline).toEqual(true);
});
});
});
describe('Configuration', () => {
let library: LibraryResponseDto;
beforeEach(async () => {
library = await api.libraryApi.create(server, admin.accessToken, {
type: LibraryType.EXTERNAL,
importPaths: [
`${IMMICH_TEST_ASSET_TEMP_PATH}/dir1`,
`${IMMICH_TEST_ASSET_TEMP_PATH}/dir2`,
`${IMMICH_TEST_ASSET_TEMP_PATH}/dir3`,
],
});
await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir1`, { recursive: true });
await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir2`, { recursive: true });
await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir3`, { recursive: true });
});
it('should use an updated import path', async () => {
await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir4`, { recursive: true });
await api.libraryApi.setImportPaths(server, admin.accessToken, library.id, [
`${IMMICH_TEST_ASSET_TEMP_PATH}/dir4`,
]);
await fs.copyFile(
`${IMMICH_TEST_ASSET_PATH}/albums/nature/polemonium_reptans.jpg`,
`${IMMICH_TEST_ASSET_TEMP_PATH}/dir4/file.jpg`,
);
await waitForEvent(libraryService, StorageEventType.ADD);
const afterAssets = await api.assetApi.getAllAssets(server, admin.accessToken);
expect(afterAssets.length).toEqual(1);
});
});
});