Merge remote-tracking branch 'origin' into feat/xxhash
This commit is contained in:
@@ -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');
|
||||
|
||||
|
||||
95
e2e/src/api/specs/repair.e2e-spec.ts
Normal file
95
e2e/src/api/specs/repair.e2e-spec.ts
Normal 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),
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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) }),
|
||||
|
||||
Reference in New Issue
Block a user