mirror of
https://github.com/immich-app/immich.git
synced 2026-03-23 14:49:50 +03:00
670 lines
21 KiB
TypeScript
670 lines
21 KiB
TypeScript
import {
|
|
AssetMediaResponseDto,
|
|
IntegrityReportResponseDto,
|
|
LoginResponseDto,
|
|
ManualJobName,
|
|
QueueCommand,
|
|
QueueName,
|
|
} from '@immich/sdk';
|
|
import { readFile } from 'node:fs/promises';
|
|
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`;
|
|
const asset1Filepath = `${testAssetDir}/albums/nature/el_torcal_rocks.jpg`;
|
|
const asset2Filepath = `${testAssetDir}/albums/nature/wood_anemones.jpg`;
|
|
|
|
describe('/admin/integrity', () => {
|
|
let admin: LoginResponseDto;
|
|
let asset: AssetMediaResponseDto;
|
|
|
|
let user1: LoginResponseDto;
|
|
let asset1: AssetMediaResponseDto;
|
|
|
|
let user2: LoginResponseDto;
|
|
let asset2: AssetMediaResponseDto;
|
|
|
|
beforeAll(async () => {
|
|
await utils.resetDatabase();
|
|
admin = await utils.adminSetup();
|
|
|
|
user1 = await utils.userSetup(admin.accessToken, {
|
|
email: '1@example.com',
|
|
name: '1',
|
|
password: '1',
|
|
});
|
|
|
|
user2 = await utils.userSetup(admin.accessToken, {
|
|
email: '2@example.com',
|
|
name: '2',
|
|
password: '2',
|
|
});
|
|
|
|
for (const queue of Object.values(QueueName)) {
|
|
if (queue === QueueName.IntegrityCheck) {
|
|
continue;
|
|
}
|
|
|
|
await utils.queueCommand(admin.accessToken, queue, {
|
|
command: QueueCommand.Pause,
|
|
});
|
|
}
|
|
|
|
asset = await utils.createAsset(admin.accessToken, {
|
|
assetData: {
|
|
filename: 'asset.jpg',
|
|
bytes: await readFile(assetFilepath),
|
|
},
|
|
});
|
|
|
|
asset1 = await utils.createAsset(user1.accessToken, {
|
|
assetData: {
|
|
filename: 'asset.jpg',
|
|
bytes: await readFile(asset1Filepath),
|
|
},
|
|
});
|
|
|
|
asset2 = await utils.createAsset(user2.accessToken, {
|
|
assetData: {
|
|
filename: 'asset.jpg',
|
|
bytes: await readFile(asset2Filepath),
|
|
},
|
|
});
|
|
|
|
await utils.mkFolder('/data/bak');
|
|
await utils.copyFolder(`/data/upload/${admin.userId}`, `/data/bak/${admin.userId}`);
|
|
|
|
for (const queue of Object.values(QueueName)) {
|
|
if (queue === QueueName.IntegrityCheck) {
|
|
continue;
|
|
}
|
|
|
|
await utils.queueCommand(admin.accessToken, queue, {
|
|
command: QueueCommand.Empty,
|
|
});
|
|
|
|
await utils.queueCommand(admin.accessToken, queue, {
|
|
command: QueueCommand.Resume,
|
|
});
|
|
}
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await utils.deleteFolder(`/data/upload/${admin.userId}`);
|
|
await utils.copyFolder(`/data/bak/${admin.userId}`, `/data/upload/${admin.userId}`);
|
|
});
|
|
|
|
describe('POST /summary (& jobs)', async () => {
|
|
it.sequential('reports no issues', async () => {
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityUntrackedFiles,
|
|
});
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityMissingFiles,
|
|
});
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityChecksumMismatch,
|
|
});
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityUntrackedFilesDeleteAll,
|
|
});
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
|
|
const { status, body } = await request(app)
|
|
.get('/admin/integrity/summary')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(status).toBe(200);
|
|
expect(body).toEqual({
|
|
missing_file: 0,
|
|
untracked_file: 0,
|
|
checksum_mismatch: 0,
|
|
});
|
|
});
|
|
|
|
it.sequential('should detect an untracked file (job: check untracked files)', async () => {
|
|
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityUntrackedFiles,
|
|
});
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
|
|
const { status, body } = await request(app)
|
|
.get('/admin/integrity/summary')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(status).toBe(200);
|
|
expect(body).toEqual(
|
|
expect.objectContaining({
|
|
untracked_file: 1,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it.sequential('should detect outdated untracked file reports (job: refresh untracked files)', async () => {
|
|
// these should not be detected:
|
|
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked2.png`);
|
|
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked3.png`);
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityUntrackedFilesRefresh,
|
|
});
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
|
|
const { status, body } = await request(app)
|
|
.get('/admin/integrity/summary')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(status).toBe(200);
|
|
expect(body).toEqual(
|
|
expect.objectContaining({
|
|
untracked_file: 0,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it.sequential('should delete untracked files (job: delete all untracked file reports)', async () => {
|
|
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityUntrackedFiles,
|
|
});
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityUntrackedFilesDeleteAll,
|
|
});
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
|
|
const { status, body } = await request(app)
|
|
.get('/admin/integrity/summary')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(status).toBe(200);
|
|
expect(body).toEqual(
|
|
expect.objectContaining({
|
|
untracked_file: 0,
|
|
}),
|
|
);
|
|
});
|
|
|
|
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/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/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 delete assets with missing files (job: delete all missing file reports)', async () => {
|
|
await utils.deleteFolder(`/data/upload/${user1.userId}`);
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityMissingFiles,
|
|
});
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
|
|
const { status: listStatus, body: listBody } = await request(app)
|
|
.get('/admin/integrity/summary')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(listStatus).toBe(200);
|
|
expect(listBody).toEqual(
|
|
expect.objectContaining({
|
|
missing_file: 1,
|
|
}),
|
|
);
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityMissingFilesDeleteAll,
|
|
});
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
|
|
const { status, body } = await request(app)
|
|
.get('/admin/integrity/summary')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(status).toBe(200);
|
|
expect(body).toEqual(
|
|
expect.objectContaining({
|
|
missing_file: 0,
|
|
}),
|
|
);
|
|
|
|
await expect(utils.getAssetInfo(user1.accessToken, asset1.id)).resolves.toEqual(
|
|
expect.objectContaining({
|
|
isTrashed: true,
|
|
}),
|
|
);
|
|
});
|
|
|
|
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/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/integrity/summary')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(status).toBe(200);
|
|
expect(body).toEqual(
|
|
expect.objectContaining({
|
|
checksum_mismatch: 0,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it.sequential(
|
|
'should delete assets with mismatched checksum (job: delete all checksum mismatch reports)',
|
|
async () => {
|
|
await utils.truncateFolder(`/data/upload/${user2.userId}`);
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityChecksumMismatch,
|
|
});
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
|
|
const { status: listStatus, body: listBody } = await request(app)
|
|
.get('/admin/integrity/summary')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(listStatus).toBe(200);
|
|
expect(listBody).toEqual(
|
|
expect.objectContaining({
|
|
checksum_mismatch: 1,
|
|
}),
|
|
);
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityChecksumMismatchDeleteAll,
|
|
});
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
|
|
const { status, body } = await request(app)
|
|
.get('/admin/integrity/summary')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(status).toBe(200);
|
|
expect(body).toEqual(
|
|
expect.objectContaining({
|
|
checksum_mismatch: 0,
|
|
}),
|
|
);
|
|
|
|
await expect(utils.getAssetInfo(user2.accessToken, asset2.id)).resolves.toEqual(
|
|
expect.objectContaining({
|
|
isTrashed: true,
|
|
}),
|
|
);
|
|
},
|
|
);
|
|
});
|
|
|
|
describe('POST /report', async () => {
|
|
it.sequential('reports untracked files', async () => {
|
|
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityUntrackedFiles,
|
|
});
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
|
|
const { status, body } = await request(app)
|
|
.get('/admin/integrity/report?type=untracked_file')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(status).toBe(200);
|
|
expect(body).toEqual({
|
|
nextCursor: undefined,
|
|
items: expect.arrayContaining([
|
|
{
|
|
id: expect.any(String),
|
|
type: 'untracked_file',
|
|
path: `/data/upload/${admin.userId}/untracked1.png`,
|
|
assetId: null,
|
|
fileAssetId: null,
|
|
createdAt: expect.any(String),
|
|
},
|
|
]),
|
|
});
|
|
});
|
|
|
|
it.sequential('reports 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/integrity/report?type=missing_file')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(status).toBe(200);
|
|
expect(body).toEqual({
|
|
nextCursor: undefined,
|
|
items: expect.arrayContaining([
|
|
{
|
|
id: expect.any(String),
|
|
type: 'missing_file',
|
|
path: expect.any(String),
|
|
assetId: asset.id,
|
|
fileAssetId: null,
|
|
createdAt: expect.any(String),
|
|
},
|
|
]),
|
|
});
|
|
});
|
|
|
|
it.sequential('reports checksum mismatched files', 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/integrity/report?type=checksum_mismatch')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(status).toBe(200);
|
|
expect(body).toEqual({
|
|
nextCursor: undefined,
|
|
items: expect.arrayContaining([
|
|
{
|
|
id: expect.any(String),
|
|
type: 'checksum_mismatch',
|
|
path: expect.any(String),
|
|
assetId: asset.id,
|
|
fileAssetId: null,
|
|
createdAt: expect.any(String),
|
|
},
|
|
]),
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('DELETE /report/:id', async () => {
|
|
it.sequential('delete untracked files', async () => {
|
|
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityUntrackedFiles,
|
|
});
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
|
|
const { status: listStatus, body: listBody } = await request(app)
|
|
.get('/admin/integrity/report?type=untracked_file')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(listStatus).toBe(200);
|
|
|
|
const report = (listBody as IntegrityReportResponseDto).items.find(
|
|
(item) => item.path === `/data/upload/${admin.userId}/untracked1.png`,
|
|
)!;
|
|
|
|
const { status } = await request(app)
|
|
.delete(`/admin/integrity/report/${report.id}`)
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(status).toBe(200);
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityUntrackedFiles,
|
|
});
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
|
|
const { status: listStatus2, body: listBody2 } = await request(app)
|
|
.get('/admin/integrity/report?type=untracked_file')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(listStatus2).toBe(200);
|
|
expect(listBody2).not.toBe(
|
|
expect.objectContaining({
|
|
items: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
id: report.id,
|
|
}),
|
|
]),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it.sequential('delete assets 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: listStatus, body: listBody } = await request(app)
|
|
.get('/admin/integrity/report?type=missing_file')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(listStatus).toBe(200);
|
|
expect(listBody.items.length).toBe(1);
|
|
|
|
const report = (listBody as IntegrityReportResponseDto).items[0];
|
|
|
|
const { status } = await request(app)
|
|
.delete(`/admin/integrity/report/${report.id}`)
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(status).toBe(200);
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityMissingFiles,
|
|
});
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
|
|
const { status: listStatus2, body: listBody2 } = await request(app)
|
|
.get('/admin/integrity/report?type=missing_file')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(listStatus2).toBe(200);
|
|
expect(listBody2.items.length).toBe(0);
|
|
});
|
|
|
|
it.sequential('delete assets with failing checksum', 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: listStatus, body: listBody } = await request(app)
|
|
.get('/admin/integrity/report?type=checksum_mismatch')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(listStatus).toBe(200);
|
|
expect(listBody.items.length).toBe(1);
|
|
|
|
const report = (listBody as IntegrityReportResponseDto).items[0];
|
|
|
|
const { status } = await request(app)
|
|
.delete(`/admin/integrity/report/${report.id}`)
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(status).toBe(200);
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityChecksumMismatch,
|
|
});
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
|
|
const { status: listStatus2, body: listBody2 } = await request(app)
|
|
.get('/admin/integrity/report?type=checksum_mismatch')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(listStatus2).toBe(200);
|
|
expect(listBody2.items.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('GET /report/:type/csv', () => {
|
|
it.sequential('exports untracked files as csv', async () => {
|
|
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityUntrackedFiles,
|
|
});
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
|
|
const { status, headers, text } = await request(app)
|
|
.get('/admin/integrity/report/untracked_file/csv')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
expect(status).toBe(200);
|
|
expect(headers['content-type']).toContain('text/csv');
|
|
expect(headers['content-disposition']).toContain('.csv');
|
|
expect(text).toContain('id,type,assetId,fileAssetId,path');
|
|
expect(text).toContain(`untracked_file`);
|
|
expect(text).toContain(`/data/upload/${admin.userId}/untracked1.png`);
|
|
});
|
|
});
|
|
|
|
describe('GET /report/:id/file', () => {
|
|
it.sequential('downloads untracked file', async () => {
|
|
await utils.putTextFile('untracked-content', `/data/upload/${admin.userId}/untracked1.png`);
|
|
|
|
await utils.createJob(admin.accessToken, {
|
|
name: ManualJobName.IntegrityUntrackedFiles,
|
|
});
|
|
|
|
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
|
|
|
const { body: listBody } = await request(app)
|
|
.get('/admin/integrity/report?type=untracked_file')
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.send();
|
|
|
|
const report = (listBody as IntegrityReportResponseDto).items.find(
|
|
(item) => item.path === `/data/upload/${admin.userId}/untracked1.png`,
|
|
)!;
|
|
|
|
const { status, headers, body } = await request(app)
|
|
.get(`/admin/integrity/report/${report.id}/file`)
|
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
.buffer(true)
|
|
.send();
|
|
|
|
expect(status).toBe(200);
|
|
expect(headers['content-type']).toContain('application/octet-stream');
|
|
expect(body.toString()).toBe('untracked-content');
|
|
});
|
|
});
|
|
});
|