install docker dependencies

This commit is contained in:
Jonathan Jogenfors 2023-09-22 13:28:10 +02:00
parent 6429234bd5
commit a094805c27
12 changed files with 160 additions and 61 deletions

View file

@ -17,6 +17,10 @@ jobs:
run:
working-directory: ./server
env:
ENV LD_LIBRARY_PATH: /usr/local/lib:$LD_LIBRARY_PATH
ENV LD_RUN_PATH: /usr/local/lib:$LD_RUN_PATH
steps:
- name: Checkout code
uses: actions/checkout@v4
@ -30,6 +34,12 @@ jobs:
with:
node-version: "21.0.0-nightly20230921480ab8c3a4"
- name: Install dependencies
run: sudo apt-get update && apt-get install -yqq build-essential ninja-build meson pkg-config jq zlib1g autoconf libglib2.0-dev libexpat1-dev librsvg2-dev libexif-dev libwebp-dev liborc-0.4-dev libjpeg62-turbo-dev libgsf-1-dev libspng-dev libjxl-dev libheif-dev liblcms2-2 mesa-va-drivers libmimalloc2.0 $(if [ $(arch) = "x86_64" ]; then echo "intel-media-va-driver-non-free"; fi) && ./install-ffmpeg.sh && apt-get autoremove && apt-get clean && rm -rf /var/lib/apt/lists/*
- name: Install libraw, imagemagick, libvips
run: ./bin/build-libraw.sh && ./bin/build-imagemagick.sh && ./bin/build-libvips.sh
- name: Checkout test assets
uses: actions/checkout@v4
with:
@ -37,7 +47,7 @@ jobs:
path: ./server/test/assets
- name: Run e2e tests
run: NODE_OPTIONS='--experimental-vm-modules' npm run test:e2e
run: npm run test:e2e -- --forceExit
if: ${{ !cancelled() }}
doc-tests:

View file

@ -26,7 +26,7 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config test/e2e/jest-e2e.json --runInBand --detectOpenHandles --verbose",
"test:e2e": "NODE_OPTIONS='--experimental-vm-modules' jest --config test/e2e/jest-e2e.json --runInBand",
"typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js",
"typeorm:migrations:create": "node --require ts-node/register ./node_modules/typeorm/cli.js migration:create",
"typeorm:migrations:generate": "node --require ts-node/register ./node_modules/typeorm/cli.js migration:generate -d ./src/infra/database.config.ts",

View file

@ -112,5 +112,4 @@ export interface IJobRepository {
getQueueStatus(name: QueueName): Promise<QueueStatus>;
getJobCounts(name: QueueName): Promise<JobCounts>;
obliterate(name: QueueName, force: boolean): Promise<void>;
closeWorkers(): Promise<void>;
}

View file

@ -69,10 +69,6 @@ export class JobService {
}
}
async closeAll(): Promise<void> {
await this.jobRepository.closeWorkers();
}
private async start(name: QueueName, { force }: JobCommandDto): Promise<void> {
const { isActive } = await this.jobRepository.getQueueStatus(name);
if (isActive) {

View file

@ -350,8 +350,6 @@ export class LibraryService {
}
async handleQueueAssetRefresh(job: ILibraryRefreshJob): Promise<boolean> {
console.log('Handle queue asset refresh: ' + JSON.stringify(job));
console.log(await this.repository.getAll(true, LibraryType.EXTERNAL));
const library = await this.repository.get(job.id);
if (!library || library.type !== LibraryType.EXTERNAL) {
this.logger.warn('Can only refresh external libraries');

View file

@ -28,16 +28,6 @@ export class JobRepository implements IJobRepository {
worker.concurrency = concurrency;
}
async closeWorkers() {
for (const queue in Object.keys(this.workers)) {
const queueName = queue as QueueName;
const worker = this.workers[queueName];
if (worker) {
await worker.close();
}
}
}
async getQueueStatus(name: QueueName): Promise<QueueStatus> {
const queue = this.getQueue(name);

View file

@ -44,8 +44,10 @@ export class AppService {
async init(clearBeforeStart = false) {
if (clearBeforeStart) {
// Clear all jobs on application startup, mainly used for e2e testing
await this.jobService.obliterateAll(true);
}
await this.jobService.registerHandlers({
[JobName.DELETE_FILES]: (data: IDeleteFilesJob) => this.storageService.handleDeleteFiles(data),
[JobName.CLEAN_OLD_AUDIT_LOGS]: () => this.auditService.handleCleanup(),

View file

@ -76,10 +76,9 @@ export class MetadataExtractionProcessor {
await this.geocodingRepository.deleteCache();
}
this.logger.log('Initializing Reverse Geocoding');
await this.jobRepository.pause(QueueName.METADATA_EXTRACTION);
//await this.geocodingRepository.init();
await this.geocodingRepository.init();
await this.jobRepository.resume(QueueName.METADATA_EXTRACTION);
this.logger.log('Reverse Geocoding Initialized');

View file

@ -0,0 +1,93 @@
import { JobService, LoginResponseDto, QueueName } from '@app/domain';
import { AppModule } from '@app/immich/app.module';
import { LibraryType } from '@app/infra/entities';
import { INestApplication, Logger } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { api } from '@test/api';
import { db } from '@test/db';
import { waitForQueues } from '@test/test-utils';
import { AppService as MicroAppService } from 'src/microservices/app.service';
import { MetadataExtractionProcessor } from 'src/microservices/processors/metadata-extraction.processor';
describe('File format (e2e)', () => {
let app: INestApplication;
let jobService: JobService;
let server: any;
let moduleFixture: TestingModule;
let admin: LoginResponseDto;
beforeAll(async () => {
jest.useRealTimers();
moduleFixture = await Test.createTestingModule({
imports: [AppModule],
providers: [MetadataExtractionProcessor, MicroAppService],
})
.setLogger(new Logger())
.compile();
app = moduleFixture.createNestApplication();
await app.init();
app.enableShutdownHooks();
server = app.getHttpServer();
jobService = moduleFixture.get(JobService);
await moduleFixture.get(MicroAppService).init(true);
});
beforeEach(async () => {
// We expect https://github.com/etnoy/immich-test-assets to be cloned into the e2e/assets folder
await db.reset();
await jobService.obliterateAll(true);
await api.authApi.adminSignUp(server);
admin = await api.authApi.adminLogin(server);
await api.userApi.update(server, admin.accessToken, { id: admin.userId, externalPath: '/' });
});
it('should import a jpg file', async () => {
const library = await api.libraryApi.createLibrary(server, admin.accessToken, {
type: LibraryType.EXTERNAL,
name: 'Library',
importPaths: [`${__dirname}/../assets/formats/jpg`],
exclusionPatterns: [],
});
await api.libraryApi.scanLibrary(server, admin.accessToken, library.id, {});
await waitForQueues(jobService);
const assets = await api.assetApi.getAllAssets(server, admin.accessToken);
expect(assets).toHaveLength(1);
});
it('should import a heic file', async () => {
const library = await api.libraryApi.createLibrary(server, admin.accessToken, {
type: LibraryType.EXTERNAL,
name: 'Library',
importPaths: [`${__dirname}/../assets/formats/heic`],
exclusionPatterns: [],
});
await api.libraryApi.scanLibrary(server, admin.accessToken, library.id, {});
await waitForQueues(jobService);
const assets = await api.assetApi.getAllAssets(server, admin.accessToken);
expect(assets).toHaveLength(1);
console.log(assets);
});
afterAll(async () => {
console.log(await jobService.getAllJobsStatus());
await jobService.obliterateAll(true);
await app.close();
await moduleFixture.close();
await db.disconnect();
});
});

View file

@ -1,25 +1,20 @@
import { JobService, LoginResponseDto, QueueName } from '@app/domain';
import { AppModule } from '@app/immich/app.module';
import { LibraryType } from '@app/infra/entities';
import { JobRepository } from '@app/infra/repositories';
import { INestApplication, Logger } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { api } from '@test/api';
import { db } from '@test/db';
import { sleep } from '@test/test-utils';
import { waitForQueues } from '@test/test-utils';
import { AppService as MicroAppService } from 'src/microservices/app.service';
import { MetadataExtractionProcessor } from 'src/microservices/processors/metadata-extraction.processor';
describe('libe2e', () => {
describe('Library queue e2e', () => {
let app: INestApplication;
let jobService: JobService;
let server: any;
let moduleFixture: TestingModule;
let admin: LoginResponseDto;
beforeAll(async () => {
@ -44,7 +39,9 @@ describe('libe2e', () => {
});
describe('can import library', () => {
beforeAll(async () => {
beforeEach(async () => {
// We expect https://github.com/etnoy/immich-test-assets to be cloned into the e2e/assets folder
await db.reset();
await jobService.obliterateAll(true);
@ -52,7 +49,9 @@ describe('libe2e', () => {
await api.authApi.adminSignUp(server);
admin = await api.authApi.adminLogin(server);
await api.userApi.update(server, admin.accessToken, { id: admin.userId, externalPath: '/' });
});
it('should scan the whole folder', async () => {
const library = await api.libraryApi.createLibrary(server, admin.accessToken, {
type: LibraryType.EXTERNAL,
name: 'Library',
@ -60,45 +59,34 @@ describe('libe2e', () => {
exclusionPatterns: [],
});
console.log(await api.libraryApi.getAll(server, admin.accessToken));
// We expect https://github.com/etnoy/immich-test-assets to be cloned into the e2e/assets folder
await api.libraryApi.scanLibrary(server, admin.accessToken, library.id, {});
let isFinished = false;
// TODO: this shouldn't be a while loop
while (!isFinished) {
const jobStatus = await api.jobApi.getAllJobsStatus(server, admin.accessToken);
await waitForQueues(jobService);
let jobsActive = false;
Object.values(jobStatus).forEach((job) => {
if (job.queueStatus.isActive) {
jobsActive = true;
}
if (job.queueStatus.active > 0 || job.queueStatus.waiting > 0) {
jobsActive = true;
}
});
if (!jobsActive) {
isFinished = true;
}
await sleep(200);
}
});
it('scans the library', async () => {
const assets = await api.assetApi.getAllAssets(server, admin.accessToken);
expect(assets).toHaveLength(7);
});
it('scan with exclusions', async () => {
const library = await api.libraryApi.createLibrary(server, admin.accessToken, {
type: LibraryType.EXTERNAL,
name: 'Library',
importPaths: [`${__dirname}/../assets/nature/`],
exclusionPatterns: ['**/*o*/**', '**/*c*/**'],
});
await api.libraryApi.scanLibrary(server, admin.accessToken, library.id, {});
await waitForQueues(jobService);
const assets = await api.assetApi.getAllAssets(server, admin.accessToken);
expect(assets).toHaveLength(1);
expect(assets[0].originalFileName).toBe('silver_fir');
});
});
afterEach(async () => {});
afterAll(async () => {
await jobService.closeAll();
await jobService.obliterateAll(true);
await app.close();
await moduleFixture.close();

View file

@ -11,6 +11,5 @@ export const newJobRepositoryMock = (): jest.Mocked<IJobRepository> => {
getQueueStatus: jest.fn(),
getJobCounts: jest.fn(),
obliterate: jest.fn(),
closeWorkers: jest.fn(),
};
};

View file

@ -4,6 +4,7 @@ import {
AuthDeviceResponseDto,
AuthUserDto,
CreateUserDto,
JobService,
LibraryResponseDto,
LoginCredentialDto,
LoginResponseDto,
@ -53,6 +54,30 @@ export function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export async function waitForQueues(jobService: JobService) {
let isFinished = false;
// TODO: this shouldn't be a while loop
while (!isFinished) {
const jobStatus = await jobService.getAllJobsStatus();
let jobsActive = false;
Object.values(jobStatus).forEach((job) => {
if (job.queueStatus.isActive) {
jobsActive = true;
}
if (job.queueStatus.active > 0 || job.queueStatus.waiting > 0) {
jobsActive = true;
}
});
if (!jobsActive) {
isFinished = true;
}
await sleep(100);
}
}
export const api = {
adminSignUp: async (server: any) => {
const { status, body } = await request(server).post('/auth/admin-sign-up').send(adminSignupStub);