mirror of
https://github.com/immich-app/immich.git
synced 2025-12-24 01:11:32 +03:00
test: split integrity out of maintenance
This commit is contained in:
202
e2e/src/api/specs/integrity.e2e-spec.ts
Normal file
202
e2e/src/api/specs/integrity.e2e-spec.ts
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
import { IntegrityReportType, LoginResponseDto, ManualJobName, QueueName } from '@immich/sdk';
|
||||||
|
import { readFile } from 'node:fs/promises';
|
||||||
|
import { createUserDto } from 'src/fixtures';
|
||||||
|
import { app, testAssetDir, utils } from 'src/utils';
|
||||||
|
import request from 'supertest';
|
||||||
|
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
const assetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`;
|
||||||
|
|
||||||
|
describe('/admin/maintenance', () => {
|
||||||
|
let cookie: string | undefined;
|
||||||
|
let admin: LoginResponseDto;
|
||||||
|
let nonAdmin: LoginResponseDto;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await utils.resetDatabase();
|
||||||
|
admin = await utils.adminSetup();
|
||||||
|
nonAdmin = await utils.userSetup(admin.accessToken, createUserDto.user1);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /integrity/summary (& jobs)', async () => {
|
||||||
|
let baseline: Record<IntegrityReportType, number>;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await utils.createAsset(admin.accessToken, {
|
||||||
|
assetData: {
|
||||||
|
filename: 'asset.jpg',
|
||||||
|
bytes: await readFile(assetFilepath),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.copyFolder(`/data/upload/${admin.userId}`, `/data/upload/${admin.userId}-bak`);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await utils.deleteFolder(`/data/upload/${admin.userId}`);
|
||||||
|
await utils.copyFolder(`/data/upload/${admin.userId}-bak`, `/data/upload/${admin.userId}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('may report issues', async () => {
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityOrphanFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityMissingFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityChecksumMismatch,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/maintenance/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({
|
||||||
|
missing_file: 0,
|
||||||
|
orphan_file: expect.any(Number),
|
||||||
|
checksum_mismatch: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
baseline = body;
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('should detect an orphan file (job: check orphan files)', async () => {
|
||||||
|
await utils.putTextFile('orphan', `/data/upload/${admin.userId}/orphan1.png`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityOrphanFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/maintenance/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
orphan_file: baseline.orphan_file + 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('should detect outdated orphan file reports (job: refresh orphan files)', async () => {
|
||||||
|
// these should not be detected:
|
||||||
|
await utils.putTextFile('orphan', `/data/upload/${admin.userId}/orphan2.png`);
|
||||||
|
await utils.putTextFile('orphan', `/data/upload/${admin.userId}/orphan3.png`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityOrphanFilesRefresh,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/maintenance/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
orphan_file: baseline.orphan_file,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('should detect a missing file and not a checksum mismatch (job: check missing files)', async () => {
|
||||||
|
await utils.deleteFolder(`/data/upload/${admin.userId}`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityMissingFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/maintenance/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
missing_file: 1,
|
||||||
|
checksum_mismatch: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('should detect outdated missing file reports (job: refresh missing files)', async () => {
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityMissingFilesRefresh,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/maintenance/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
missing_file: 0,
|
||||||
|
checksum_mismatch: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('should detect a checksum mismatch (job: check file checksums)', async () => {
|
||||||
|
await utils.truncateFolder(`/data/upload/${admin.userId}`);
|
||||||
|
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityChecksumMismatch,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/maintenance/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
checksum_mismatch: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.sequential('should detect outdated checksum mismatch reports (job: refresh file checksums)', async () => {
|
||||||
|
await utils.createJob(admin.accessToken, {
|
||||||
|
name: ManualJobName.IntegrityChecksumMismatchRefresh,
|
||||||
|
});
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/admin/maintenance/integrity/summary')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
checksum_mismatch: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
import { IntegrityReportType, LoginResponseDto, ManualJobName, QueueName } from '@immich/sdk';
|
import { LoginResponseDto } from '@immich/sdk';
|
||||||
import { readFile } from 'node:fs/promises';
|
|
||||||
import { createUserDto } from 'src/fixtures';
|
import { createUserDto } from 'src/fixtures';
|
||||||
import { errorDto } from 'src/responses';
|
import { errorDto } from 'src/responses';
|
||||||
import { app, testAssetDir, utils } from 'src/utils';
|
import { app, utils } from 'src/utils';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
|
import { beforeAll, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
const assetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`;
|
|
||||||
|
|
||||||
describe('/admin/maintenance', () => {
|
describe('/admin/maintenance', () => {
|
||||||
let cookie: string | undefined;
|
let cookie: string | undefined;
|
||||||
@@ -37,188 +34,6 @@ describe('/admin/maintenance', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /integrity/summary (& jobs)', async () => {
|
|
||||||
let baseline: Record<IntegrityReportType, number>;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await utils.createAsset(admin.accessToken, {
|
|
||||||
assetData: {
|
|
||||||
filename: 'asset.jpg',
|
|
||||||
bytes: await readFile(assetFilepath),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.copyFolder(`/data/upload/${admin.userId}`, `/data/upload/${admin.userId}-bak`);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await utils.deleteFolder(`/data/upload/${admin.userId}`);
|
|
||||||
await utils.copyFolder(`/data/upload/${admin.userId}-bak`, `/data/upload/${admin.userId}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.sequential('may report issues', async () => {
|
|
||||||
await utils.createJob(admin.accessToken, {
|
|
||||||
name: ManualJobName.IntegrityOrphanFiles,
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.createJob(admin.accessToken, {
|
|
||||||
name: ManualJobName.IntegrityMissingFiles,
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.createJob(admin.accessToken, {
|
|
||||||
name: ManualJobName.IntegrityChecksumMismatch,
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
||||||
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get('/admin/maintenance/integrity/summary')
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send();
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual({
|
|
||||||
missing_file: 0,
|
|
||||||
orphan_file: expect.any(Number),
|
|
||||||
checksum_mismatch: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
baseline = body;
|
|
||||||
});
|
|
||||||
|
|
||||||
it.sequential('should detect an orphan file (job: check orphan files)', async () => {
|
|
||||||
await utils.putTextFile('orphan', `/data/upload/${admin.userId}/orphan1.png`);
|
|
||||||
|
|
||||||
await utils.createJob(admin.accessToken, {
|
|
||||||
name: ManualJobName.IntegrityOrphanFiles,
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
||||||
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get('/admin/maintenance/integrity/summary')
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send();
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
orphan_file: baseline.orphan_file + 1,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.sequential('should detect outdated orphan file reports (job: refresh orphan files)', async () => {
|
|
||||||
// these should not be detected:
|
|
||||||
await utils.putTextFile('orphan', `/data/upload/${admin.userId}/orphan2.png`);
|
|
||||||
await utils.putTextFile('orphan', `/data/upload/${admin.userId}/orphan3.png`);
|
|
||||||
|
|
||||||
await utils.createJob(admin.accessToken, {
|
|
||||||
name: ManualJobName.IntegrityOrphanFilesRefresh,
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
||||||
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get('/admin/maintenance/integrity/summary')
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send();
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
orphan_file: baseline.orphan_file,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.sequential('should detect a missing file and not a checksum mismatch (job: check missing files)', async () => {
|
|
||||||
await utils.deleteFolder(`/data/upload/${admin.userId}`);
|
|
||||||
|
|
||||||
await utils.createJob(admin.accessToken, {
|
|
||||||
name: ManualJobName.IntegrityMissingFiles,
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
||||||
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get('/admin/maintenance/integrity/summary')
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send();
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
missing_file: 1,
|
|
||||||
checksum_mismatch: 0,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.sequential('should detect outdated missing file reports (job: refresh missing files)', async () => {
|
|
||||||
await utils.createJob(admin.accessToken, {
|
|
||||||
name: ManualJobName.IntegrityMissingFilesRefresh,
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
||||||
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get('/admin/maintenance/integrity/summary')
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send();
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
missing_file: 0,
|
|
||||||
checksum_mismatch: 0,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.sequential('should detect a checksum mismatch (job: check file checksums)', async () => {
|
|
||||||
await utils.truncateFolder(`/data/upload/${admin.userId}`);
|
|
||||||
|
|
||||||
await utils.createJob(admin.accessToken, {
|
|
||||||
name: ManualJobName.IntegrityChecksumMismatch,
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
||||||
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get('/admin/maintenance/integrity/summary')
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send();
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
checksum_mismatch: 1,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.sequential('should detect outdated checksum mismatch reports (job: refresh file checksums)', async () => {
|
|
||||||
await utils.createJob(admin.accessToken, {
|
|
||||||
name: ManualJobName.IntegrityChecksumMismatchRefresh,
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
||||||
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get('/admin/maintenance/integrity/summary')
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send();
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
checksum_mismatch: 0,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// => enter maintenance mode
|
// => enter maintenance mode
|
||||||
|
|
||||||
describe.sequential('POST /', () => {
|
describe.sequential('POST /', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user