mirror of
https://github.com/immich-app/immich.git
synced 2026-06-16 20:02:15 -07:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba3e0560ed | |||
| 42782bf842 |
@@ -399,7 +399,10 @@ export class IntegrityService extends BaseService {
|
||||
await this.integrityRepository.deleteByIds(outdatedReports);
|
||||
}
|
||||
|
||||
const missingFiles = results.filter(({ exists }) => !exists);
|
||||
const missingFiles = Object.values(
|
||||
Object.fromEntries(results.filter(({ exists }) => !exists).map((file) => [file.path, file])),
|
||||
);
|
||||
|
||||
if (missingFiles.length > 0) {
|
||||
await this.integrityRepository.create(
|
||||
missingFiles.map(({ path, assetId, fileAssetId }) => ({
|
||||
|
||||
@@ -512,6 +512,82 @@ describe(IntegrityService.name, () => {
|
||||
nextCursor: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not fail when the same path is duplicated within a batch', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const storage = ctx.getMock(StorageRepository);
|
||||
|
||||
const {
|
||||
result: { id: ownerId },
|
||||
} = await ctx.newUser();
|
||||
|
||||
const {
|
||||
result: { id: assetId1 },
|
||||
} = await ctx.newAsset({ ownerId, originalPath: '/path/to/duplicate' });
|
||||
|
||||
const {
|
||||
result: { id: assetId2 },
|
||||
} = await ctx.newAsset({ ownerId, originalPath: '/path/to/duplicate' });
|
||||
|
||||
const fileAssetId1 = randomUUID();
|
||||
await ctx.newAssetFile({
|
||||
id: fileAssetId1,
|
||||
assetId: assetId1,
|
||||
type: AssetFileType.Thumbnail,
|
||||
path: '/path/to/duplicate-file',
|
||||
});
|
||||
|
||||
const fileAssetId2 = randomUUID();
|
||||
await ctx.newAssetFile({
|
||||
id: fileAssetId2,
|
||||
assetId: assetId1,
|
||||
type: AssetFileType.Preview,
|
||||
path: '/path/to/duplicate-file',
|
||||
});
|
||||
|
||||
const {
|
||||
result: { id: assetId3 },
|
||||
} = await ctx.newAsset({ ownerId, originalPath: '/path/to/duplicate-cross' });
|
||||
|
||||
const fileAssetId3 = randomUUID();
|
||||
await ctx.newAssetFile({
|
||||
id: fileAssetId3,
|
||||
assetId: assetId3,
|
||||
type: AssetFileType.Thumbnail,
|
||||
path: '/path/to/duplicate-cross',
|
||||
});
|
||||
|
||||
storage.stat.mockRejectedValue(new Error('ENOENT'));
|
||||
|
||||
await expect(
|
||||
sut.handleMissingFiles({
|
||||
items: [
|
||||
{ path: '/path/to/duplicate', assetId: assetId1, fileAssetId: null, reportId: null },
|
||||
{ path: '/path/to/duplicate', assetId: assetId2, fileAssetId: null, reportId: null },
|
||||
{ path: '/path/to/duplicate-file', assetId: null, fileAssetId: fileAssetId1, reportId: null },
|
||||
{ path: '/path/to/duplicate-file', assetId: null, fileAssetId: fileAssetId2, reportId: null },
|
||||
{ path: '/path/to/duplicate-cross', assetId: assetId3, fileAssetId: null, reportId: null },
|
||||
{ path: '/path/to/duplicate-cross', assetId: null, fileAssetId: fileAssetId3, reportId: null },
|
||||
],
|
||||
}),
|
||||
).resolves.toBe(JobStatus.Success);
|
||||
|
||||
await expect(
|
||||
ctx.get(IntegrityRepository).getIntegrityReport(
|
||||
{
|
||||
limit: 100,
|
||||
},
|
||||
IntegrityReport.MissingFile,
|
||||
),
|
||||
).resolves.toEqual({
|
||||
items: expect.arrayContaining([
|
||||
expect.objectContaining({ path: '/path/to/duplicate' }),
|
||||
expect.objectContaining({ path: '/path/to/duplicate-file' }),
|
||||
expect.objectContaining({ path: '/path/to/duplicate-cross' }),
|
||||
]),
|
||||
nextCursor: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleMissingRefresh', () => {
|
||||
@@ -686,6 +762,40 @@ describe(IntegrityService.name, () => {
|
||||
nextCursor: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not fail when the same path is duplicated across assets', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const storage = ctx.getMock(StorageRepository);
|
||||
const job = ctx.getMock(JobRepository);
|
||||
job.queue.mockResolvedValue(void 0);
|
||||
|
||||
const {
|
||||
result: { id: ownerId },
|
||||
} = await ctx.newUser();
|
||||
|
||||
await ctx.newAsset({ ownerId, originalPath: '/path/to/duplicate', checksum: Buffer.from('mismatch-a') });
|
||||
await ctx.newAsset({ ownerId, originalPath: '/path/to/duplicate', checksum: Buffer.from('mismatch-b') });
|
||||
|
||||
storage.createPlainReadStream.mockImplementation(() => Readable.from('garbage data'));
|
||||
|
||||
await expect(sut.handleChecksumFiles({ refreshOnly: false })).resolves.toBe(JobStatus.Success);
|
||||
|
||||
await expect(
|
||||
ctx.get(IntegrityRepository).getIntegrityReport(
|
||||
{
|
||||
limit: 100,
|
||||
},
|
||||
IntegrityReport.ChecksumFail,
|
||||
),
|
||||
).resolves.toEqual({
|
||||
items: [
|
||||
expect.objectContaining({
|
||||
path: '/path/to/duplicate',
|
||||
}),
|
||||
],
|
||||
nextCursor: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleChecksumRefresh', () => {
|
||||
|
||||
Reference in New Issue
Block a user