refactor: small tests (#26141)

This commit is contained in:
Daniel Dietzler
2026-02-11 17:49:00 +01:00
committed by GitHub
parent 222c90b7b7
commit e54678e0d6
22 changed files with 721 additions and 764 deletions

View File

@@ -3,7 +3,7 @@ import { DateTime } from 'luxon';
import { MapAsset } from 'src/dtos/asset-response.dto';
import { AssetJobName, AssetStatsResponseDto } from 'src/dtos/asset.dto';
import { AssetEditAction } from 'src/dtos/editing.dto';
import { AssetMetadataKey, AssetStatus, AssetType, AssetVisibility, JobName, JobStatus } from 'src/enum';
import { AssetFileType, AssetMetadataKey, AssetStatus, AssetType, AssetVisibility, JobName, JobStatus } from 'src/enum';
import { AssetStats } from 'src/repositories/asset.repository';
import { AssetService } from 'src/services/asset.service';
import { AssetFactory } from 'test/factories/asset.factory';
@@ -79,7 +79,7 @@ describe(AssetService.name, () => {
describe('getRandom', () => {
it('should get own random assets', async () => {
mocks.partner.getAll.mockResolvedValue([]);
mocks.asset.getRandom.mockResolvedValue([assetStub.image]);
mocks.asset.getRandom.mockResolvedValue([AssetFactory.create()]);
await sut.getRandom(authStub.admin, 1);
@@ -90,7 +90,7 @@ describe(AssetService.name, () => {
const partner = factory.partner({ inTimeline: false });
const auth = factory.auth({ user: { id: partner.sharedWithId } });
mocks.asset.getRandom.mockResolvedValue([assetStub.image]);
mocks.asset.getRandom.mockResolvedValue([AssetFactory.create()]);
mocks.partner.getAll.mockResolvedValue([partner]);
await sut.getRandom(auth, 1);
@@ -102,7 +102,7 @@ describe(AssetService.name, () => {
const partner = factory.partner({ inTimeline: true });
const auth = factory.auth({ user: { id: partner.sharedWithId } });
mocks.asset.getRandom.mockResolvedValue([assetStub.image]);
mocks.asset.getRandom.mockResolvedValue([AssetFactory.create()]);
mocks.partner.getAll.mockResolvedValue([partner]);
await sut.getRandom(auth, 1);
@@ -113,88 +113,90 @@ describe(AssetService.name, () => {
describe('get', () => {
it('should allow owner access', async () => {
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
mocks.asset.getById.mockResolvedValue(assetStub.image);
const asset = AssetFactory.create();
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
mocks.asset.getById.mockResolvedValue(asset);
await sut.get(authStub.admin, assetStub.image.id);
await sut.get(authStub.admin, asset.id);
expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(
authStub.admin.user.id,
new Set([assetStub.image.id]),
new Set([asset.id]),
undefined,
);
});
it('should allow shared link access', async () => {
mocks.access.asset.checkSharedLinkAccess.mockResolvedValue(new Set([assetStub.image.id]));
mocks.asset.getById.mockResolvedValue(assetStub.image);
const asset = AssetFactory.create();
mocks.access.asset.checkSharedLinkAccess.mockResolvedValue(new Set([asset.id]));
mocks.asset.getById.mockResolvedValue(asset);
await sut.get(authStub.adminSharedLink, assetStub.image.id);
await sut.get(authStub.adminSharedLink, asset.id);
expect(mocks.access.asset.checkSharedLinkAccess).toHaveBeenCalledWith(
authStub.adminSharedLink.sharedLink?.id,
new Set([assetStub.image.id]),
new Set([asset.id]),
);
});
it('should strip metadata for shared link if exif is disabled', async () => {
mocks.access.asset.checkSharedLinkAccess.mockResolvedValue(new Set([assetStub.image.id]));
mocks.asset.getById.mockResolvedValue(assetStub.image);
const asset = AssetFactory.from().exif({ description: 'foo' }).build();
mocks.access.asset.checkSharedLinkAccess.mockResolvedValue(new Set([asset.id]));
mocks.asset.getById.mockResolvedValue(asset);
const result = await sut.get(
{ ...authStub.adminSharedLink, sharedLink: { ...authStub.adminSharedLink.sharedLink!, showExif: false } },
assetStub.image.id,
asset.id,
);
expect(result).toEqual(expect.objectContaining({ hasMetadata: false }));
expect(result).not.toHaveProperty('exifInfo');
expect(mocks.access.asset.checkSharedLinkAccess).toHaveBeenCalledWith(
authStub.adminSharedLink.sharedLink?.id,
new Set([assetStub.image.id]),
new Set([asset.id]),
);
});
it('should allow partner sharing access', async () => {
mocks.access.asset.checkPartnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
mocks.asset.getById.mockResolvedValue(assetStub.image);
const asset = AssetFactory.create();
mocks.access.asset.checkPartnerAccess.mockResolvedValue(new Set([asset.id]));
mocks.asset.getById.mockResolvedValue(asset);
await sut.get(authStub.admin, assetStub.image.id);
await sut.get(authStub.admin, asset.id);
expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(
authStub.admin.user.id,
new Set([assetStub.image.id]),
);
expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set([asset.id]));
});
it('should allow shared album access', async () => {
mocks.access.asset.checkAlbumAccess.mockResolvedValue(new Set([assetStub.image.id]));
mocks.asset.getById.mockResolvedValue(assetStub.image);
const asset = AssetFactory.create();
mocks.access.asset.checkAlbumAccess.mockResolvedValue(new Set([asset.id]));
mocks.asset.getById.mockResolvedValue(asset);
await sut.get(authStub.admin, assetStub.image.id);
await sut.get(authStub.admin, asset.id);
expect(mocks.access.asset.checkAlbumAccess).toHaveBeenCalledWith(
authStub.admin.user.id,
new Set([assetStub.image.id]),
);
expect(mocks.access.asset.checkAlbumAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set([asset.id]));
});
it('should throw an error for no access', async () => {
await expect(sut.get(authStub.admin, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException);
await expect(sut.get(authStub.admin, AssetFactory.create().id)).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.asset.getById).not.toHaveBeenCalled();
});
it('should throw an error for an invalid shared link', async () => {
await expect(sut.get(authStub.adminSharedLink, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException);
await expect(sut.get(authStub.adminSharedLink, AssetFactory.create().id)).rejects.toBeInstanceOf(
BadRequestException,
);
expect(mocks.access.asset.checkOwnerAccess).not.toHaveBeenCalled();
expect(mocks.asset.getById).not.toHaveBeenCalled();
});
it('should throw an error if the asset could not be found', async () => {
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
const asset = AssetFactory.create();
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
await expect(sut.get(authStub.admin, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException);
await expect(sut.get(authStub.admin, asset.id)).rejects.toBeInstanceOf(BadRequestException);
});
});
@@ -208,38 +210,41 @@ describe(AssetService.name, () => {
});
it('should update the asset', async () => {
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
mocks.asset.getById.mockResolvedValue(assetStub.image);
mocks.asset.update.mockResolvedValue(assetStub.image);
const asset = AssetFactory.create();
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
mocks.asset.getById.mockResolvedValue(asset);
mocks.asset.update.mockResolvedValue(asset);
await sut.update(authStub.admin, 'asset-1', { isFavorite: true });
await sut.update(authStub.admin, asset.id, { isFavorite: true });
expect(mocks.asset.update).toHaveBeenCalledWith({ id: 'asset-1', isFavorite: true });
expect(mocks.asset.update).toHaveBeenCalledWith({ id: asset.id, isFavorite: true });
});
it('should update the exif description', async () => {
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
mocks.asset.getById.mockResolvedValue(assetStub.image);
mocks.asset.update.mockResolvedValue(assetStub.image);
const asset = AssetFactory.create();
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
mocks.asset.getById.mockResolvedValue(asset);
mocks.asset.update.mockResolvedValue(asset);
await sut.update(authStub.admin, 'asset-1', { description: 'Test description' });
await sut.update(authStub.admin, asset.id, { description: 'Test description' });
expect(mocks.asset.upsertExif).toHaveBeenCalledWith(
{ assetId: 'asset-1', description: 'Test description', lockedProperties: ['description'] },
{ assetId: asset.id, description: 'Test description', lockedProperties: ['description'] },
{ lockedPropertiesBehavior: 'append' },
);
});
it('should update the exif rating', async () => {
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
mocks.asset.getById.mockResolvedValueOnce(assetStub.image);
mocks.asset.update.mockResolvedValueOnce(assetStub.image);
const asset = AssetFactory.create();
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
mocks.asset.getById.mockResolvedValueOnce(asset);
mocks.asset.update.mockResolvedValueOnce(asset);
await sut.update(authStub.admin, 'asset-1', { rating: 3 });
await sut.update(authStub.admin, asset.id, { rating: 3 });
expect(mocks.asset.upsertExif).toHaveBeenCalledWith(
{
assetId: 'asset-1',
assetId: asset.id,
rating: 3,
lockedProperties: ['rating'],
},
@@ -346,10 +351,11 @@ describe(AssetService.name, () => {
it('should unlink a live video', async () => {
const auth = AuthFactory.create();
const asset = AssetFactory.create();
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.livePhotoStillAsset.id]));
mocks.asset.getById.mockResolvedValueOnce(assetStub.livePhotoStillAsset);
mocks.asset.getById.mockResolvedValueOnce(assetStub.livePhotoMotionAsset);
mocks.asset.update.mockResolvedValueOnce(assetStub.image);
mocks.asset.update.mockResolvedValueOnce(asset);
await sut.update(auth, assetStub.livePhotoStillAsset.id, { livePhotoVideoId: null });
@@ -555,7 +561,11 @@ describe(AssetService.name, () => {
describe('handleAssetDeletion', () => {
it('should clean up files', async () => {
const asset = assetStub.image;
const asset = AssetFactory.from()
.file({ type: AssetFileType.Thumbnail })
.file({ type: AssetFileType.Preview })
.file({ type: AssetFileType.FullSize })
.build();
mocks.assetJob.getForAssetDeletion.mockResolvedValue(asset);
await sut.handleAssetDeletion({ id: asset.id, deleteOnDisk: true });
@@ -565,12 +575,7 @@ describe(AssetService.name, () => {
{
name: JobName.FileDelete,
data: {
files: [
'/uploads/user-id/webp/path.ext',
'/uploads/user-id/thumbs/path.jpg',
'/uploads/user-id/fullsize/path.webp',
asset.originalPath,
],
files: [...asset.files.map(({ path }) => path), asset.originalPath],
},
},
],
@@ -656,14 +661,15 @@ describe(AssetService.name, () => {
});
it('should update usage', async () => {
mocks.assetJob.getForAssetDeletion.mockResolvedValue(assetStub.image);
await sut.handleAssetDeletion({ id: assetStub.image.id, deleteOnDisk: true });
expect(mocks.user.updateUsage).toHaveBeenCalledWith(assetStub.image.ownerId, -5000);
const asset = AssetFactory.from().exif({ fileSizeInByte: 5000 }).build();
mocks.assetJob.getForAssetDeletion.mockResolvedValue(asset);
await sut.handleAssetDeletion({ id: asset.id, deleteOnDisk: true });
expect(mocks.user.updateUsage).toHaveBeenCalledWith(asset.ownerId, -5000);
});
it('should fail if asset could not be found', async () => {
mocks.assetJob.getForAssetDeletion.mockResolvedValue(void 0);
await expect(sut.handleAssetDeletion({ id: assetStub.image.id, deleteOnDisk: true })).resolves.toBe(
await expect(sut.handleAssetDeletion({ id: AssetFactory.create().id, deleteOnDisk: true })).resolves.toBe(
JobStatus.Failed,
);
});
@@ -681,28 +687,30 @@ describe(AssetService.name, () => {
it('should return OCR data for an asset', async () => {
const ocr1 = factory.assetOcr({ text: 'Hello World' });
const ocr2 = factory.assetOcr({ text: 'Test Image' });
const asset = AssetFactory.from().exif().build();
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
mocks.ocr.getByAssetId.mockResolvedValue([ocr1, ocr2]);
mocks.asset.getById.mockResolvedValue(assetStub.image);
mocks.asset.getById.mockResolvedValue(asset);
await expect(sut.getOcr(authStub.admin, 'asset-1')).resolves.toEqual([ocr1, ocr2]);
await expect(sut.getOcr(authStub.admin, asset.id)).resolves.toEqual([ocr1, ocr2]);
expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(
authStub.admin.user.id,
new Set(['asset-1']),
new Set([asset.id]),
undefined,
);
expect(mocks.ocr.getByAssetId).toHaveBeenCalledWith('asset-1');
expect(mocks.ocr.getByAssetId).toHaveBeenCalledWith(asset.id);
});
it('should return empty array when no OCR data exists', async () => {
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
const asset = AssetFactory.from().exif().build();
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
mocks.ocr.getByAssetId.mockResolvedValue([]);
mocks.asset.getById.mockResolvedValue(assetStub.image);
await expect(sut.getOcr(authStub.admin, 'asset-1')).resolves.toEqual([]);
mocks.asset.getById.mockResolvedValue(asset);
await expect(sut.getOcr(authStub.admin, asset.id)).resolves.toEqual([]);
expect(mocks.ocr.getByAssetId).toHaveBeenCalledWith('asset-1');
expect(mocks.ocr.getByAssetId).toHaveBeenCalledWith(asset.id);
});
});
@@ -746,7 +754,7 @@ describe(AssetService.name, () => {
describe('getUserAssetsByDeviceId', () => {
it('get assets by device id', async () => {
const assets = [assetStub.image, assetStub.image1];
const assets = [AssetFactory.create(), AssetFactory.create()];
mocks.asset.getAllByDeviceId.mockResolvedValue(assets.map((asset) => asset.deviceAssetId));