Merge remote-tracking branch 'origin' into feat/xxhash

This commit is contained in:
Jonathan Jogenfors
2024-10-11 23:04:31 +02:00
28 changed files with 488 additions and 20 deletions

View File

@@ -500,13 +500,13 @@ describe('/libraries', () => {
});
it('should set an asset offline its file is not in any import path', async () => {
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
importPaths: [`${testAssetDirInternal}/temp/offline`],
});
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');

View File

@@ -0,0 +1,95 @@
import { LibraryResponseDto, LoginResponseDto, getAllLibraries, scanLibrary } from '@immich/sdk';
import { readFile } from 'fs/promises';
import { Socket } from 'socket.io-client';
import { userDto, uuidDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { app, asBearerAuth, testAssetDir, testAssetDirInternal, utils } from 'src/utils';
import request from 'supertest';
import { utimes } from 'utimes';
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
import { b } from 'vitest/dist/chunks/suite.BMWOKiTe.js';
const scan = async (accessToken: string, id: string) => scanLibrary({ id }, { headers: asBearerAuth(accessToken) });
describe('/repair', () => {
let admin: LoginResponseDto;
let user: LoginResponseDto;
let library: LibraryResponseDto;
let websocket: Socket;
beforeAll(async () => {
await utils.resetDatabase();
admin = await utils.adminSetup();
await utils.resetAdminConfig(admin.accessToken);
user = await utils.userSetup(admin.accessToken, userDto.user1);
library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId });
websocket = await utils.connectWebsocket(admin.accessToken);
});
afterAll(() => {
utils.disconnectWebsocket(websocket);
utils.resetTempFolder();
});
beforeEach(() => {
utils.resetEvents();
});
describe('POST /check', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post('/libraries').send({});
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should require admin authentication', async () => {
const { status, body } = await request(app)
.post('/repair/check')
.set('Authorization', `Bearer ${user.accessToken}`)
.send({ ownerId: admin.userId });
expect(status).toBe(403);
expect(body).toEqual(errorDto.forbidden);
});
it('should detect a changed original file', async () => {
const asset = await utils.createAsset(admin.accessToken, {
assetData: {
filename: 'polemonium_reptans.jpg',
bytes: await readFile(`${testAssetDir}/albums/nature/polemonium_reptans.jpg`),
},
});
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
let assetPath = '';
{
const { status, body } = await request(app)
.get(`/assets/${asset.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
assetPath = body.originalPath;
}
utils.flipBitInFile(assetPath, 2, 5);
const { status, body } = await request(app)
.post('/repair/check')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ownerId: admin.userId });
expect(status).toBe(201);
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({
ownerId: admin.userId,
name: 'New External Library',
refreshedAt: null,
assetCount: 0,
importPaths: [],
exclusionPatterns: expect.any(Array),
}),
]);
});
});
});

View File

@@ -37,7 +37,7 @@ import {
import { BrowserContext } from '@playwright/test';
import { exec, spawn } from 'node:child_process';
import { createHash } from 'node:crypto';
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path, { dirname } from 'node:path';
import { setTimeout as setAsyncTimeout } from 'node:timers/promises';
@@ -374,8 +374,8 @@ export const utils = {
},
createDirectory: (path: string) => {
if (!existsSync(dirname(path))) {
mkdirSync(dirname(path), { recursive: true });
if (!existsSync(path)) {
mkdirSync(path, { recursive: true });
}
},
@@ -387,12 +387,26 @@ export const utils = {
rmSync(path);
},
flipBitInFile: (filePath: string, byteIndex: number, bitPosition: number) => {
const data = readFileSync(filePath);
// Check if the byte index is within the file size
if (byteIndex >= data.length) {
throw new Error('Byte index is out of range.');
}
// Flip the specific bit using XOR
data[byteIndex] ^= 1 << bitPosition;
writeFileSync(filePath, data);
},
removeDirectory: (path: string) => {
if (!existsSync(path)) {
return;
}
rmSync(path);
rmSync(path, { recursive: true });
},
getAssetInfo: (accessToken: string, id: string) => getAssetInfo({ id }, { headers: asBearerAuth(accessToken) }),