mirror of
https://github.com/immich-app/immich.git
synced 2025-12-22 17:24:56 +03:00
refactor: remove smart search entity (#17447)
refactor: smart search entity
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { JobName, JobStatus } from 'src/enum';
|
||||
import { AssetFileType, AssetType, JobName, JobStatus } from 'src/enum';
|
||||
import { WithoutProperty } from 'src/repositories/asset.repository';
|
||||
import { DuplicateService } from 'src/services/duplicate.service';
|
||||
import { SearchService } from 'src/services/search.service';
|
||||
@@ -9,6 +9,33 @@ import { beforeEach, vitest } from 'vitest';
|
||||
|
||||
vitest.useFakeTimers();
|
||||
|
||||
const hasEmbedding = {
|
||||
id: 'asset-1',
|
||||
ownerId: 'user-id',
|
||||
files: [
|
||||
{
|
||||
assetId: 'asset-1',
|
||||
createdAt: new Date(),
|
||||
id: 'file-1',
|
||||
path: 'preview.jpg',
|
||||
type: AssetFileType.PREVIEW,
|
||||
updatedAt: new Date(),
|
||||
updateId: 'update-1',
|
||||
},
|
||||
],
|
||||
isVisible: true,
|
||||
stackId: null,
|
||||
type: AssetType.IMAGE,
|
||||
duplicateId: null,
|
||||
embedding: '[1, 2, 3, 4]',
|
||||
};
|
||||
|
||||
const hasDupe = {
|
||||
...hasEmbedding,
|
||||
id: 'asset-2',
|
||||
duplicateId: 'duplicate-id',
|
||||
};
|
||||
|
||||
describe(SearchService.name, () => {
|
||||
let sut: DuplicateService;
|
||||
let mocks: ServiceMocks;
|
||||
@@ -25,16 +52,16 @@ describe(SearchService.name, () => {
|
||||
it('should get duplicates', async () => {
|
||||
mocks.asset.getDuplicates.mockResolvedValue([
|
||||
{
|
||||
duplicateId: assetStub.hasDupe.duplicateId!,
|
||||
assets: [assetStub.hasDupe, assetStub.hasDupe],
|
||||
duplicateId: 'duplicate-id',
|
||||
assets: [assetStub.image, assetStub.image],
|
||||
},
|
||||
]);
|
||||
await expect(sut.getDuplicates(authStub.admin)).resolves.toEqual([
|
||||
{
|
||||
duplicateId: assetStub.hasDupe.duplicateId,
|
||||
duplicateId: 'duplicate-id',
|
||||
assets: [
|
||||
expect.objectContaining({ id: assetStub.hasDupe.id }),
|
||||
expect.objectContaining({ id: assetStub.hasDupe.id }),
|
||||
expect.objectContaining({ id: assetStub.image.id }),
|
||||
expect.objectContaining({ id: assetStub.image.id }),
|
||||
],
|
||||
},
|
||||
]);
|
||||
@@ -175,7 +202,7 @@ describe(SearchService.name, () => {
|
||||
|
||||
it('should skip if asset is part of stack', async () => {
|
||||
const id = assetStub.primaryImage.id;
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.primaryImage);
|
||||
mocks.asset.getAssetForSearchDuplicatesJob.mockResolvedValue({ ...hasEmbedding, stackId: 'stack-id' });
|
||||
|
||||
const result = await sut.handleSearchDuplicates({ id });
|
||||
|
||||
@@ -185,7 +212,7 @@ describe(SearchService.name, () => {
|
||||
|
||||
it('should skip if asset is not visible', async () => {
|
||||
const id = assetStub.livePhotoMotionAsset.id;
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.livePhotoMotionAsset);
|
||||
mocks.asset.getAssetForSearchDuplicatesJob.mockResolvedValue({ ...hasEmbedding, isVisible: false });
|
||||
|
||||
const result = await sut.handleSearchDuplicates({ id });
|
||||
|
||||
@@ -194,7 +221,7 @@ describe(SearchService.name, () => {
|
||||
});
|
||||
|
||||
it('should fail if asset is missing preview image', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.noResizePath);
|
||||
mocks.asset.getAssetForSearchDuplicatesJob.mockResolvedValue({ ...hasEmbedding, files: [] });
|
||||
|
||||
const result = await sut.handleSearchDuplicates({ id: assetStub.noResizePath.id });
|
||||
|
||||
@@ -203,7 +230,7 @@ describe(SearchService.name, () => {
|
||||
});
|
||||
|
||||
it('should fail if asset is missing embedding', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||
mocks.asset.getAssetForSearchDuplicatesJob.mockResolvedValue({ ...hasEmbedding, embedding: null });
|
||||
|
||||
const result = await sut.handleSearchDuplicates({ id: assetStub.image.id });
|
||||
|
||||
@@ -212,21 +239,21 @@ describe(SearchService.name, () => {
|
||||
});
|
||||
|
||||
it('should search for duplicates and update asset with duplicateId', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.hasEmbedding);
|
||||
mocks.asset.getAssetForSearchDuplicatesJob.mockResolvedValue(hasEmbedding);
|
||||
mocks.search.searchDuplicates.mockResolvedValue([
|
||||
{ assetId: assetStub.image.id, distance: 0.01, duplicateId: null },
|
||||
]);
|
||||
const expectedAssetIds = [assetStub.image.id, assetStub.hasEmbedding.id];
|
||||
const expectedAssetIds = [assetStub.image.id, hasEmbedding.id];
|
||||
|
||||
const result = await sut.handleSearchDuplicates({ id: assetStub.hasEmbedding.id });
|
||||
const result = await sut.handleSearchDuplicates({ id: hasEmbedding.id });
|
||||
|
||||
expect(result).toBe(JobStatus.SUCCESS);
|
||||
expect(mocks.search.searchDuplicates).toHaveBeenCalledWith({
|
||||
assetId: assetStub.hasEmbedding.id,
|
||||
embedding: assetStub.hasEmbedding.smartSearch!.embedding,
|
||||
assetId: hasEmbedding.id,
|
||||
embedding: hasEmbedding.embedding,
|
||||
maxDistance: 0.01,
|
||||
type: assetStub.hasEmbedding.type,
|
||||
userIds: [assetStub.hasEmbedding.ownerId],
|
||||
type: hasEmbedding.type,
|
||||
userIds: [hasEmbedding.ownerId],
|
||||
});
|
||||
expect(mocks.asset.updateDuplicates).toHaveBeenCalledWith({
|
||||
assetIds: expectedAssetIds,
|
||||
@@ -239,24 +266,24 @@ describe(SearchService.name, () => {
|
||||
});
|
||||
|
||||
it('should use existing duplicate ID among matched duplicates', async () => {
|
||||
const duplicateId = assetStub.hasDupe.duplicateId;
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.hasEmbedding);
|
||||
mocks.search.searchDuplicates.mockResolvedValue([{ assetId: assetStub.hasDupe.id, distance: 0.01, duplicateId }]);
|
||||
const expectedAssetIds = [assetStub.hasEmbedding.id];
|
||||
const duplicateId = hasDupe.duplicateId;
|
||||
mocks.asset.getAssetForSearchDuplicatesJob.mockResolvedValue(hasEmbedding);
|
||||
mocks.search.searchDuplicates.mockResolvedValue([{ assetId: hasDupe.id, distance: 0.01, duplicateId }]);
|
||||
const expectedAssetIds = [hasEmbedding.id];
|
||||
|
||||
const result = await sut.handleSearchDuplicates({ id: assetStub.hasEmbedding.id });
|
||||
const result = await sut.handleSearchDuplicates({ id: hasEmbedding.id });
|
||||
|
||||
expect(result).toBe(JobStatus.SUCCESS);
|
||||
expect(mocks.search.searchDuplicates).toHaveBeenCalledWith({
|
||||
assetId: assetStub.hasEmbedding.id,
|
||||
embedding: assetStub.hasEmbedding.smartSearch!.embedding,
|
||||
assetId: hasEmbedding.id,
|
||||
embedding: hasEmbedding.embedding,
|
||||
maxDistance: 0.01,
|
||||
type: assetStub.hasEmbedding.type,
|
||||
userIds: [assetStub.hasEmbedding.ownerId],
|
||||
type: hasEmbedding.type,
|
||||
userIds: [hasEmbedding.ownerId],
|
||||
});
|
||||
expect(mocks.asset.updateDuplicates).toHaveBeenCalledWith({
|
||||
assetIds: expectedAssetIds,
|
||||
targetDuplicateId: assetStub.hasDupe.duplicateId,
|
||||
targetDuplicateId: duplicateId,
|
||||
duplicateIds: [],
|
||||
});
|
||||
expect(mocks.asset.upsertJobStatus).toHaveBeenCalledWith(
|
||||
@@ -265,15 +292,15 @@ describe(SearchService.name, () => {
|
||||
});
|
||||
|
||||
it('should remove duplicateId if no duplicates found and asset has duplicateId', async () => {
|
||||
mocks.asset.getById.mockResolvedValue(assetStub.hasDupe);
|
||||
mocks.asset.getAssetForSearchDuplicatesJob.mockResolvedValue(hasDupe);
|
||||
mocks.search.searchDuplicates.mockResolvedValue([]);
|
||||
|
||||
const result = await sut.handleSearchDuplicates({ id: assetStub.hasDupe.id });
|
||||
const result = await sut.handleSearchDuplicates({ id: hasDupe.id });
|
||||
|
||||
expect(result).toBe(JobStatus.SUCCESS);
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({ id: assetStub.hasDupe.id, duplicateId: null });
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({ id: hasDupe.id, duplicateId: null });
|
||||
expect(mocks.asset.upsertJobStatus).toHaveBeenCalledWith({
|
||||
assetId: assetStub.hasDupe.id,
|
||||
assetId: hasDupe.id,
|
||||
duplicatesDetectedAt: expect.any(Date),
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user