refactor: test mocks (#16008)

This commit is contained in:
Jason Rasmussen
2025-02-10 18:47:42 -05:00
committed by GitHub
parent 8794c84e9d
commit 735f8d661e
74 changed files with 3820 additions and 4043 deletions

View File

@@ -1,27 +1,20 @@
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
import { ISearchRepository } from 'src/interfaces/search.interface';
import { WithoutProperty } from 'src/interfaces/asset.interface';
import { JobName, JobStatus } from 'src/interfaces/job.interface';
import { DuplicateService } from 'src/services/duplicate.service';
import { SearchService } from 'src/services/search.service';
import { ILoggingRepository, ISystemMetadataRepository } from 'src/types';
import { assetStub } from 'test/fixtures/asset.stub';
import { authStub } from 'test/fixtures/auth.stub';
import { newTestService } from 'test/utils';
import { Mocked, beforeEach, vitest } from 'vitest';
import { newTestService, ServiceMocks } from 'test/utils';
import { beforeEach, vitest } from 'vitest';
vitest.useFakeTimers();
describe(SearchService.name, () => {
let sut: DuplicateService;
let assetMock: Mocked<IAssetRepository>;
let jobMock: Mocked<IJobRepository>;
let loggerMock: Mocked<ILoggingRepository>;
let searchMock: Mocked<ISearchRepository>;
let systemMock: Mocked<ISystemMetadataRepository>;
let mocks: ServiceMocks;
beforeEach(() => {
({ sut, assetMock, jobMock, loggerMock, searchMock, systemMock } = newTestService(DuplicateService));
({ sut, mocks } = newTestService(DuplicateService));
});
it('should work', () => {
@@ -30,7 +23,7 @@ describe(SearchService.name, () => {
describe('getDuplicates', () => {
it('should get duplicates', async () => {
assetMock.getDuplicates.mockResolvedValue([
mocks.asset.getDuplicates.mockResolvedValue([
{
duplicateId: assetStub.hasDupe.duplicateId!,
assets: [assetStub.hasDupe, assetStub.hasDupe],
@@ -50,7 +43,7 @@ describe(SearchService.name, () => {
describe('handleQueueSearchDuplicates', () => {
beforeEach(() => {
systemMock.get.mockResolvedValue({
mocks.systemMetadata.get.mockResolvedValue({
machineLearning: {
enabled: true,
duplicateDetection: {
@@ -61,7 +54,7 @@ describe(SearchService.name, () => {
});
it('should skip if machine learning is disabled', async () => {
systemMock.get.mockResolvedValue({
mocks.systemMetadata.get.mockResolvedValue({
machineLearning: {
enabled: false,
duplicateDetection: {
@@ -71,13 +64,13 @@ describe(SearchService.name, () => {
});
await expect(sut.handleQueueSearchDuplicates({})).resolves.toBe(JobStatus.SKIPPED);
expect(jobMock.queue).not.toHaveBeenCalled();
expect(jobMock.queueAll).not.toHaveBeenCalled();
expect(systemMock.get).toHaveBeenCalled();
expect(mocks.job.queue).not.toHaveBeenCalled();
expect(mocks.job.queueAll).not.toHaveBeenCalled();
expect(mocks.systemMetadata.get).toHaveBeenCalled();
});
it('should skip if duplicate detection is disabled', async () => {
systemMock.get.mockResolvedValue({
mocks.systemMetadata.get.mockResolvedValue({
machineLearning: {
enabled: true,
duplicateDetection: {
@@ -87,21 +80,21 @@ describe(SearchService.name, () => {
});
await expect(sut.handleQueueSearchDuplicates({})).resolves.toBe(JobStatus.SKIPPED);
expect(jobMock.queue).not.toHaveBeenCalled();
expect(jobMock.queueAll).not.toHaveBeenCalled();
expect(systemMock.get).toHaveBeenCalled();
expect(mocks.job.queue).not.toHaveBeenCalled();
expect(mocks.job.queueAll).not.toHaveBeenCalled();
expect(mocks.systemMetadata.get).toHaveBeenCalled();
});
it('should queue missing assets', async () => {
assetMock.getWithout.mockResolvedValue({
mocks.asset.getWithout.mockResolvedValue({
items: [assetStub.image],
hasNextPage: false,
});
await sut.handleQueueSearchDuplicates({});
expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.DUPLICATE);
expect(jobMock.queueAll).toHaveBeenCalledWith([
expect(mocks.asset.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.DUPLICATE);
expect(mocks.job.queueAll).toHaveBeenCalledWith([
{
name: JobName.DUPLICATE_DETECTION,
data: { id: assetStub.image.id },
@@ -110,15 +103,15 @@ describe(SearchService.name, () => {
});
it('should queue all assets', async () => {
assetMock.getAll.mockResolvedValue({
mocks.asset.getAll.mockResolvedValue({
items: [assetStub.image],
hasNextPage: false,
});
await sut.handleQueueSearchDuplicates({ force: true });
expect(assetMock.getAll).toHaveBeenCalled();
expect(jobMock.queueAll).toHaveBeenCalledWith([
expect(mocks.asset.getAll).toHaveBeenCalled();
expect(mocks.job.queueAll).toHaveBeenCalledWith([
{
name: JobName.DUPLICATE_DETECTION,
data: { id: assetStub.image.id },
@@ -129,7 +122,7 @@ describe(SearchService.name, () => {
describe('handleSearchDuplicates', () => {
beforeEach(() => {
systemMock.get.mockResolvedValue({
mocks.systemMetadata.get.mockResolvedValue({
machineLearning: {
enabled: true,
duplicateDetection: {
@@ -140,7 +133,7 @@ describe(SearchService.name, () => {
});
it('should skip if machine learning is disabled', async () => {
systemMock.get.mockResolvedValue({
mocks.systemMetadata.get.mockResolvedValue({
machineLearning: {
enabled: false,
duplicateDetection: {
@@ -149,7 +142,7 @@ describe(SearchService.name, () => {
},
});
const id = assetStub.livePhotoMotionAsset.id;
assetMock.getById.mockResolvedValue(assetStub.livePhotoMotionAsset);
mocks.asset.getById.mockResolvedValue(assetStub.livePhotoMotionAsset);
const result = await sut.handleSearchDuplicates({ id });
@@ -157,7 +150,7 @@ describe(SearchService.name, () => {
});
it('should skip if duplicate detection is disabled', async () => {
systemMock.get.mockResolvedValue({
mocks.systemMetadata.get.mockResolvedValue({
machineLearning: {
enabled: true,
duplicateDetection: {
@@ -166,7 +159,7 @@ describe(SearchService.name, () => {
},
});
const id = assetStub.livePhotoMotionAsset.id;
assetMock.getById.mockResolvedValue(assetStub.livePhotoMotionAsset);
mocks.asset.getById.mockResolvedValue(assetStub.livePhotoMotionAsset);
const result = await sut.handleSearchDuplicates({ id });
@@ -177,40 +170,40 @@ describe(SearchService.name, () => {
const result = await sut.handleSearchDuplicates({ id: assetStub.image.id });
expect(result).toBe(JobStatus.FAILED);
expect(loggerMock.error).toHaveBeenCalledWith(`Asset ${assetStub.image.id} not found`);
expect(mocks.logger.error).toHaveBeenCalledWith(`Asset ${assetStub.image.id} not found`);
});
it('should skip if asset is not visible', async () => {
const id = assetStub.livePhotoMotionAsset.id;
assetMock.getById.mockResolvedValue(assetStub.livePhotoMotionAsset);
mocks.asset.getById.mockResolvedValue(assetStub.livePhotoMotionAsset);
const result = await sut.handleSearchDuplicates({ id });
expect(result).toBe(JobStatus.SKIPPED);
expect(loggerMock.debug).toHaveBeenCalledWith(`Asset ${id} is not visible, skipping`);
expect(mocks.logger.debug).toHaveBeenCalledWith(`Asset ${id} is not visible, skipping`);
});
it('should fail if asset is missing preview image', async () => {
assetMock.getById.mockResolvedValue(assetStub.noResizePath);
mocks.asset.getById.mockResolvedValue(assetStub.noResizePath);
const result = await sut.handleSearchDuplicates({ id: assetStub.noResizePath.id });
expect(result).toBe(JobStatus.FAILED);
expect(loggerMock.warn).toHaveBeenCalledWith(`Asset ${assetStub.noResizePath.id} is missing preview image`);
expect(mocks.logger.warn).toHaveBeenCalledWith(`Asset ${assetStub.noResizePath.id} is missing preview image`);
});
it('should fail if asset is missing embedding', async () => {
assetMock.getById.mockResolvedValue(assetStub.image);
mocks.asset.getById.mockResolvedValue(assetStub.image);
const result = await sut.handleSearchDuplicates({ id: assetStub.image.id });
expect(result).toBe(JobStatus.FAILED);
expect(loggerMock.debug).toHaveBeenCalledWith(`Asset ${assetStub.image.id} is missing embedding`);
expect(mocks.logger.debug).toHaveBeenCalledWith(`Asset ${assetStub.image.id} is missing embedding`);
});
it('should search for duplicates and update asset with duplicateId', async () => {
assetMock.getById.mockResolvedValue(assetStub.hasEmbedding);
searchMock.searchDuplicates.mockResolvedValue([
mocks.asset.getById.mockResolvedValue(assetStub.hasEmbedding);
mocks.search.searchDuplicates.mockResolvedValue([
{ assetId: assetStub.image.id, distance: 0.01, duplicateId: null },
]);
const expectedAssetIds = [assetStub.image.id, assetStub.hasEmbedding.id];
@@ -218,58 +211,58 @@ describe(SearchService.name, () => {
const result = await sut.handleSearchDuplicates({ id: assetStub.hasEmbedding.id });
expect(result).toBe(JobStatus.SUCCESS);
expect(searchMock.searchDuplicates).toHaveBeenCalledWith({
expect(mocks.search.searchDuplicates).toHaveBeenCalledWith({
assetId: assetStub.hasEmbedding.id,
embedding: assetStub.hasEmbedding.smartSearch!.embedding,
maxDistance: 0.01,
type: assetStub.hasEmbedding.type,
userIds: [assetStub.hasEmbedding.ownerId],
});
expect(assetMock.updateDuplicates).toHaveBeenCalledWith({
expect(mocks.asset.updateDuplicates).toHaveBeenCalledWith({
assetIds: expectedAssetIds,
targetDuplicateId: expect.any(String),
duplicateIds: [],
});
expect(assetMock.upsertJobStatus).toHaveBeenCalledWith(
expect(mocks.asset.upsertJobStatus).toHaveBeenCalledWith(
...expectedAssetIds.map((assetId) => ({ assetId, duplicatesDetectedAt: expect.any(Date) })),
);
});
it('should use existing duplicate ID among matched duplicates', async () => {
const duplicateId = assetStub.hasDupe.duplicateId;
assetMock.getById.mockResolvedValue(assetStub.hasEmbedding);
searchMock.searchDuplicates.mockResolvedValue([{ assetId: assetStub.hasDupe.id, distance: 0.01, 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 result = await sut.handleSearchDuplicates({ id: assetStub.hasEmbedding.id });
expect(result).toBe(JobStatus.SUCCESS);
expect(searchMock.searchDuplicates).toHaveBeenCalledWith({
expect(mocks.search.searchDuplicates).toHaveBeenCalledWith({
assetId: assetStub.hasEmbedding.id,
embedding: assetStub.hasEmbedding.smartSearch!.embedding,
maxDistance: 0.01,
type: assetStub.hasEmbedding.type,
userIds: [assetStub.hasEmbedding.ownerId],
});
expect(assetMock.updateDuplicates).toHaveBeenCalledWith({
expect(mocks.asset.updateDuplicates).toHaveBeenCalledWith({
assetIds: expectedAssetIds,
targetDuplicateId: assetStub.hasDupe.duplicateId,
duplicateIds: [],
});
expect(assetMock.upsertJobStatus).toHaveBeenCalledWith(
expect(mocks.asset.upsertJobStatus).toHaveBeenCalledWith(
...expectedAssetIds.map((assetId) => ({ assetId, duplicatesDetectedAt: expect.any(Date) })),
);
});
it('should remove duplicateId if no duplicates found and asset has duplicateId', async () => {
assetMock.getById.mockResolvedValue(assetStub.hasDupe);
searchMock.searchDuplicates.mockResolvedValue([]);
mocks.asset.getById.mockResolvedValue(assetStub.hasDupe);
mocks.search.searchDuplicates.mockResolvedValue([]);
const result = await sut.handleSearchDuplicates({ id: assetStub.hasDupe.id });
expect(result).toBe(JobStatus.SUCCESS);
expect(assetMock.update).toHaveBeenCalledWith({ id: assetStub.hasDupe.id, duplicateId: null });
expect(assetMock.upsertJobStatus).toHaveBeenCalledWith({
expect(mocks.asset.update).toHaveBeenCalledWith({ id: assetStub.hasDupe.id, duplicateId: null });
expect(mocks.asset.upsertJobStatus).toHaveBeenCalledWith({
assetId: assetStub.hasDupe.id,
duplicatesDetectedAt: expect.any(Date),
});