mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
merge: remote-tracking branch 'origin/main' into feat/integrity-checks-izzy
This commit is contained in:
Vendored
+209
-22
@@ -1,43 +1,61 @@
|
||||
import { AssetFace, AssetFile, Exif } from 'src/database';
|
||||
import { MapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { AssetEditAction, AssetEditActionItem } from 'src/dtos/editing.dto';
|
||||
import { AssetFileType, AssetStatus, AssetType, AssetVisibility } from 'src/enum';
|
||||
import { StorageAsset } from 'src/types';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { fileStub } from 'test/fixtures/file.stub';
|
||||
import { userStub } from 'test/fixtures/user.stub';
|
||||
import { factory } from 'test/small.factory';
|
||||
|
||||
export const previewFile: AssetFile = {
|
||||
id: 'file-1',
|
||||
type: AssetFileType.Preview,
|
||||
path: '/uploads/user-id/thumbs/path.jpg',
|
||||
};
|
||||
export const previewFile = factory.assetFile({ type: AssetFileType.Preview });
|
||||
|
||||
const thumbnailFile: AssetFile = {
|
||||
id: 'file-2',
|
||||
const thumbnailFile = factory.assetFile({
|
||||
type: AssetFileType.Thumbnail,
|
||||
path: '/uploads/user-id/webp/path.ext',
|
||||
};
|
||||
});
|
||||
|
||||
const fullsizeFile: AssetFile = {
|
||||
id: 'file-3',
|
||||
const fullsizeFile = factory.assetFile({
|
||||
type: AssetFileType.FullSize,
|
||||
path: '/uploads/user-id/fullsize/path.webp',
|
||||
};
|
||||
});
|
||||
|
||||
const sidecarFileWithExt: AssetFile = {
|
||||
id: 'sidecar-with-ext',
|
||||
const sidecarFileWithExt = factory.assetFile({
|
||||
type: AssetFileType.Sidecar,
|
||||
path: '/original/path.ext.xmp',
|
||||
};
|
||||
});
|
||||
|
||||
const sidecarFileWithoutExt: AssetFile = {
|
||||
id: 'sidecar-without-ext',
|
||||
const sidecarFileWithoutExt = factory.assetFile({
|
||||
type: AssetFileType.Sidecar,
|
||||
path: '/original/path.xmp',
|
||||
};
|
||||
});
|
||||
|
||||
const editedPreviewFile = factory.assetFile({
|
||||
type: AssetFileType.PreviewEdited,
|
||||
path: '/uploads/user-id/preview/path_edited.jpg',
|
||||
});
|
||||
|
||||
const editedThumbnailFile = factory.assetFile({
|
||||
type: AssetFileType.ThumbnailEdited,
|
||||
path: '/uploads/user-id/thumbnail/path_edited.jpg',
|
||||
});
|
||||
|
||||
const editedFullsizeFile = factory.assetFile({
|
||||
type: AssetFileType.FullSizeEdited,
|
||||
path: '/uploads/user-id/fullsize/path_edited.jpg',
|
||||
});
|
||||
|
||||
const files: AssetFile[] = [fullsizeFile, previewFile, thumbnailFile];
|
||||
|
||||
const editedFiles: AssetFile[] = [
|
||||
fullsizeFile,
|
||||
previewFile,
|
||||
thumbnailFile,
|
||||
editedFullsizeFile,
|
||||
editedPreviewFile,
|
||||
editedThumbnailFile,
|
||||
];
|
||||
|
||||
export const stackStub = (stackId: string, assets: (MapAsset & { exifInfo: Exif })[]) => {
|
||||
return {
|
||||
id: stackId,
|
||||
@@ -104,6 +122,9 @@ export const assetStub = {
|
||||
stackId: null,
|
||||
updateId: '42',
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
|
||||
noWebpPath: Object.freeze({
|
||||
@@ -142,6 +163,9 @@ export const assetStub = {
|
||||
stackId: null,
|
||||
updateId: '42',
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
|
||||
noThumbhash: Object.freeze({
|
||||
@@ -177,6 +201,9 @@ export const assetStub = {
|
||||
stackId: null,
|
||||
updateId: '42',
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
|
||||
primaryImage: Object.freeze({
|
||||
@@ -222,6 +249,9 @@ export const assetStub = {
|
||||
updateId: '42',
|
||||
libraryId: null,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
|
||||
image: Object.freeze({
|
||||
@@ -264,9 +294,10 @@ export const assetStub = {
|
||||
stack: null,
|
||||
orientation: '',
|
||||
projectionType: null,
|
||||
height: 3840,
|
||||
width: 2160,
|
||||
height: null,
|
||||
width: null,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
edits: [],
|
||||
}),
|
||||
|
||||
trashed: Object.freeze({
|
||||
@@ -307,6 +338,9 @@ export const assetStub = {
|
||||
stackId: null,
|
||||
updateId: '42',
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
|
||||
trashedOffline: Object.freeze({
|
||||
@@ -347,6 +381,9 @@ export const assetStub = {
|
||||
stackId: null,
|
||||
updateId: '42',
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
archived: Object.freeze({
|
||||
id: 'asset-id',
|
||||
@@ -386,6 +423,9 @@ export const assetStub = {
|
||||
stackId: null,
|
||||
updateId: '42',
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
|
||||
external: Object.freeze({
|
||||
@@ -425,6 +465,9 @@ export const assetStub = {
|
||||
stackId: null,
|
||||
stack: null,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
|
||||
image1: Object.freeze({
|
||||
@@ -464,6 +507,9 @@ export const assetStub = {
|
||||
libraryId: null,
|
||||
stack: null,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
|
||||
imageFrom2015: Object.freeze({
|
||||
@@ -502,6 +548,9 @@ export const assetStub = {
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
|
||||
video: Object.freeze({
|
||||
@@ -542,6 +591,9 @@ export const assetStub = {
|
||||
libraryId: null,
|
||||
stackId: null,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
|
||||
livePhotoMotionAsset: Object.freeze({
|
||||
@@ -559,7 +611,10 @@ export const assetStub = {
|
||||
files: [] as AssetFile[],
|
||||
libraryId: null,
|
||||
visibility: AssetVisibility.Hidden,
|
||||
} as MapAsset & { faces: AssetFace[]; files: AssetFile[]; exifInfo: Exif }),
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [] as AssetEditActionItem[],
|
||||
} as MapAsset & { faces: AssetFace[]; files: AssetFile[]; exifInfo: Exif; edits: AssetEditActionItem[] }),
|
||||
|
||||
livePhotoStillAsset: Object.freeze({
|
||||
id: 'live-photo-still-asset',
|
||||
@@ -577,7 +632,10 @@ export const assetStub = {
|
||||
files,
|
||||
faces: [] as AssetFace[],
|
||||
visibility: AssetVisibility.Timeline,
|
||||
} as MapAsset & { faces: AssetFace[]; files: AssetFile[] }),
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [] as AssetEditActionItem[],
|
||||
} as MapAsset & { faces: AssetFace[]; files: AssetFile[]; edits: AssetEditActionItem[] }),
|
||||
|
||||
livePhotoWithOriginalFileName: Object.freeze({
|
||||
id: 'live-photo-still-asset',
|
||||
@@ -597,7 +655,10 @@ export const assetStub = {
|
||||
libraryId: null,
|
||||
faces: [] as AssetFace[],
|
||||
visibility: AssetVisibility.Timeline,
|
||||
} as MapAsset & { faces: AssetFace[]; files: AssetFile[] }),
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [] as AssetEditActionItem[],
|
||||
} as MapAsset & { faces: AssetFace[]; files: AssetFile[]; edits: AssetEditActionItem[] }),
|
||||
|
||||
withLocation: Object.freeze({
|
||||
id: 'asset-with-favorite-id',
|
||||
@@ -641,6 +702,9 @@ export const assetStub = {
|
||||
isOffline: false,
|
||||
tags: [],
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
|
||||
sidecar: Object.freeze({
|
||||
@@ -676,6 +740,9 @@ export const assetStub = {
|
||||
libraryId: null,
|
||||
stackId: null,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
|
||||
sidecarWithoutExt: Object.freeze({
|
||||
@@ -708,6 +775,9 @@ export const assetStub = {
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
|
||||
hasEncodedVideo: Object.freeze({
|
||||
@@ -747,6 +817,9 @@ export const assetStub = {
|
||||
stackId: null,
|
||||
stack: null,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
|
||||
hasFileExtension: Object.freeze({
|
||||
@@ -783,6 +856,9 @@ export const assetStub = {
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
|
||||
imageDng: Object.freeze({
|
||||
@@ -823,6 +899,9 @@ export const assetStub = {
|
||||
libraryId: null,
|
||||
stackId: null,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
|
||||
imageHif: Object.freeze({
|
||||
@@ -863,6 +942,9 @@ export const assetStub = {
|
||||
libraryId: null,
|
||||
stackId: null,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
panoramaTif: Object.freeze({
|
||||
id: 'asset-id',
|
||||
@@ -902,5 +984,110 @@ export const assetStub = {
|
||||
libraryId: null,
|
||||
stackId: null,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
edits: [],
|
||||
}),
|
||||
withCropEdit: Object.freeze({
|
||||
id: 'asset-id',
|
||||
status: AssetStatus.Active,
|
||||
deviceAssetId: 'device-asset-id',
|
||||
fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
owner: userStub.user1,
|
||||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.jpg',
|
||||
files,
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.Image,
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
localDateTime: new Date('2025-01-01T01:02:03.456Z'),
|
||||
isFavorite: true,
|
||||
duration: null,
|
||||
isExternal: false,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
updateId: 'foo',
|
||||
libraryId: null,
|
||||
stackId: null,
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.jpg',
|
||||
faces: [],
|
||||
deletedAt: null,
|
||||
sidecarPath: null,
|
||||
exifInfo: {
|
||||
fileSizeInByte: 5000,
|
||||
exifImageHeight: 3840,
|
||||
exifImageWidth: 2160,
|
||||
} as Exif,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
stack: null,
|
||||
orientation: '',
|
||||
projectionType: null,
|
||||
height: 3840,
|
||||
width: 2160,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
edits: [
|
||||
{
|
||||
action: AssetEditAction.Crop,
|
||||
parameters: {
|
||||
width: 1512,
|
||||
height: 1152,
|
||||
x: 216,
|
||||
y: 1512,
|
||||
},
|
||||
},
|
||||
] as AssetEditActionItem[],
|
||||
}),
|
||||
withoutEdits: Object.freeze({
|
||||
id: 'asset-id',
|
||||
status: AssetStatus.Active,
|
||||
deviceAssetId: 'device-asset-id',
|
||||
fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
owner: userStub.user1,
|
||||
ownerId: 'user-id',
|
||||
deviceId: 'device-id',
|
||||
originalPath: '/original/path.jpg',
|
||||
files: editedFiles,
|
||||
checksum: Buffer.from('file hash', 'utf8'),
|
||||
type: AssetType.Image,
|
||||
thumbhash: Buffer.from('blablabla', 'base64'),
|
||||
encodedVideoPath: null,
|
||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||
localDateTime: new Date('2025-01-01T01:02:03.456Z'),
|
||||
isFavorite: true,
|
||||
duration: null,
|
||||
isExternal: false,
|
||||
livePhotoVideo: null,
|
||||
livePhotoVideoId: null,
|
||||
updateId: 'foo',
|
||||
libraryId: null,
|
||||
stackId: null,
|
||||
sharedLinks: [],
|
||||
originalFileName: 'asset-id.jpg',
|
||||
faces: [],
|
||||
deletedAt: null,
|
||||
sidecarPath: null,
|
||||
exifInfo: {
|
||||
fileSizeInByte: 5000,
|
||||
exifImageHeight: 3840,
|
||||
exifImageWidth: 2160,
|
||||
} as Exif,
|
||||
duplicateId: null,
|
||||
isOffline: false,
|
||||
stack: null,
|
||||
orientation: '',
|
||||
projectionType: null,
|
||||
height: 3840,
|
||||
width: 2160,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
edits: [],
|
||||
}),
|
||||
};
|
||||
|
||||
Vendored
+8
@@ -25,6 +25,7 @@ export const faceStub = {
|
||||
deletedAt: new Date(),
|
||||
updatedAt: new Date('2023-01-01T00:00:00Z'),
|
||||
updateId: '0d1173e3-4d80-4d76-b41e-57d56de21125',
|
||||
isVisible: true,
|
||||
}),
|
||||
primaryFace1: Object.freeze({
|
||||
id: 'assetFaceId2',
|
||||
@@ -43,6 +44,7 @@ export const faceStub = {
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2023-01-01T00:00:00Z'),
|
||||
updateId: '0d1173e3-4d80-4d76-b41e-57d56de21125',
|
||||
isVisible: true,
|
||||
}),
|
||||
mergeFace1: Object.freeze({
|
||||
id: 'assetFaceId3',
|
||||
@@ -61,6 +63,7 @@ export const faceStub = {
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2023-01-01T00:00:00Z'),
|
||||
updateId: '0d1173e3-4d80-4d76-b41e-57d56de21125',
|
||||
isVisible: true,
|
||||
}),
|
||||
noPerson1: Object.freeze({
|
||||
id: 'assetFaceId8',
|
||||
@@ -79,6 +82,7 @@ export const faceStub = {
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2023-01-01T00:00:00Z'),
|
||||
updateId: '0d1173e3-4d80-4d76-b41e-57d56de21125',
|
||||
isVisible: true,
|
||||
}),
|
||||
noPerson2: Object.freeze({
|
||||
id: 'assetFaceId9',
|
||||
@@ -97,6 +101,7 @@ export const faceStub = {
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2023-01-01T00:00:00Z'),
|
||||
updateId: '0d1173e3-4d80-4d76-b41e-57d56de21125',
|
||||
isVisible: true,
|
||||
}),
|
||||
fromExif1: Object.freeze({
|
||||
id: 'assetFaceId9',
|
||||
@@ -114,6 +119,7 @@ export const faceStub = {
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2023-01-01T00:00:00Z'),
|
||||
updateId: '0d1173e3-4d80-4d76-b41e-57d56de21125',
|
||||
isVisible: true,
|
||||
}),
|
||||
fromExif2: Object.freeze({
|
||||
id: 'assetFaceId9',
|
||||
@@ -131,6 +137,7 @@ export const faceStub = {
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2023-01-01T00:00:00Z'),
|
||||
updateId: '0d1173e3-4d80-4d76-b41e-57d56de21125',
|
||||
isVisible: true,
|
||||
}),
|
||||
withBirthDate: Object.freeze({
|
||||
id: 'assetFaceId10',
|
||||
@@ -148,5 +155,6 @@ export const faceStub = {
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2023-01-01T00:00:00Z'),
|
||||
updateId: '0d1173e3-4d80-4d76-b41e-57d56de21125',
|
||||
isVisible: true,
|
||||
}),
|
||||
};
|
||||
|
||||
+7
@@ -142,6 +142,11 @@ export const sharedLinkStub = {
|
||||
rating: 3,
|
||||
updatedAt: today,
|
||||
updateId: '42',
|
||||
libraryId: null,
|
||||
stackId: null,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: 500,
|
||||
height: 500,
|
||||
},
|
||||
sharedLinks: [],
|
||||
faces: [],
|
||||
@@ -152,6 +157,8 @@ export const sharedLinkStub = {
|
||||
libraryId: null,
|
||||
stackId: null,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: 500,
|
||||
height: 500,
|
||||
},
|
||||
],
|
||||
albumId: null,
|
||||
|
||||
@@ -56,6 +56,7 @@ import { AlbumTable } from 'src/schema/tables/album.table';
|
||||
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
||||
import { AssetFileTable } from 'src/schema/tables/asset-file.table';
|
||||
import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table';
|
||||
import { AssetMetadataTable } from 'src/schema/tables/asset-metadata.table';
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
||||
import { MemoryTable } from 'src/schema/tables/memory.table';
|
||||
@@ -68,6 +69,7 @@ import { UserTable } from 'src/schema/tables/user.table';
|
||||
import { BASE_SERVICE_DEPENDENCIES, BaseService } from 'src/services/base.service';
|
||||
import { MetadataService } from 'src/services/metadata.service';
|
||||
import { SyncService } from 'src/services/sync.service';
|
||||
import { UploadFile } from 'src/types';
|
||||
import { mockEnvData } from 'test/repositories/config.repository.mock';
|
||||
import { newTelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock';
|
||||
import { factory, newDate, newEmbedding, newUuid } from 'test/small.factory';
|
||||
@@ -179,6 +181,12 @@ export class MediumTestContext<S extends BaseService = BaseService> {
|
||||
return { asset, result };
|
||||
}
|
||||
|
||||
async newMetadata(dto: Insertable<AssetMetadataTable>) {
|
||||
const { assetId, ...item } = dto;
|
||||
const result = await this.get(AssetRepository).upsertMetadata(assetId, [item]);
|
||||
return { metadata: dto, result };
|
||||
}
|
||||
|
||||
async newAssetFile(dto: Insertable<AssetFileTable>) {
|
||||
const result = await this.get(AssetRepository).upsertFile(dto);
|
||||
return { result };
|
||||
@@ -573,6 +581,7 @@ const assetFaceInsert = (assetFace: Partial<AssetFace> & { assetId: string }) =>
|
||||
imageWidth: assetFace.imageWidth ?? 10,
|
||||
personId: assetFace.personId ?? null,
|
||||
sourceType: assetFace.sourceType ?? SourceType.MachineLearning,
|
||||
isVisible: assetFace.isVisible ?? true,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -739,6 +748,17 @@ const loginResponse = (): LoginResponseDto => {
|
||||
};
|
||||
};
|
||||
|
||||
const uploadFile = (file: Partial<UploadFile> = {}) => {
|
||||
return {
|
||||
uuid: newUuid(),
|
||||
checksum: randomBytes(32),
|
||||
originalPath: '/path/to/file.jpg',
|
||||
originalName: 'file.jpg',
|
||||
size: 123_456,
|
||||
...file,
|
||||
};
|
||||
};
|
||||
|
||||
export const mediumFactory = {
|
||||
assetInsert,
|
||||
assetFaceInsert,
|
||||
@@ -753,4 +773,5 @@ export const mediumFactory = {
|
||||
loginDetails,
|
||||
loginResponse,
|
||||
tagInsert,
|
||||
uploadFile,
|
||||
};
|
||||
|
||||
@@ -87,4 +87,64 @@ describe(AssetRepository.name, () => {
|
||||
).resolves.toEqual({ lockedProperties: ['description', 'dateTimeOriginal'] });
|
||||
});
|
||||
});
|
||||
|
||||
describe('unlockProperties', () => {
|
||||
it('should unlock one property', async () => {
|
||||
const { ctx, sut } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
await ctx.newExif({
|
||||
assetId: asset.id,
|
||||
dateTimeOriginal: '2023-11-19T18:11:00',
|
||||
lockedProperties: ['dateTimeOriginal', 'description'],
|
||||
});
|
||||
|
||||
await expect(
|
||||
ctx.database
|
||||
.selectFrom('asset_exif')
|
||||
.select('lockedProperties')
|
||||
.where('assetId', '=', asset.id)
|
||||
.executeTakeFirstOrThrow(),
|
||||
).resolves.toEqual({ lockedProperties: ['dateTimeOriginal', 'description'] });
|
||||
|
||||
await sut.unlockProperties(asset.id, ['dateTimeOriginal']);
|
||||
|
||||
await expect(
|
||||
ctx.database
|
||||
.selectFrom('asset_exif')
|
||||
.select('lockedProperties')
|
||||
.where('assetId', '=', asset.id)
|
||||
.executeTakeFirstOrThrow(),
|
||||
).resolves.toEqual({ lockedProperties: ['description'] });
|
||||
});
|
||||
|
||||
it('should unlock all properties', async () => {
|
||||
const { ctx, sut } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
await ctx.newExif({
|
||||
assetId: asset.id,
|
||||
dateTimeOriginal: '2023-11-19T18:11:00',
|
||||
lockedProperties: ['dateTimeOriginal', 'description'],
|
||||
});
|
||||
|
||||
await expect(
|
||||
ctx.database
|
||||
.selectFrom('asset_exif')
|
||||
.select('lockedProperties')
|
||||
.where('assetId', '=', asset.id)
|
||||
.executeTakeFirstOrThrow(),
|
||||
).resolves.toEqual({ lockedProperties: ['dateTimeOriginal', 'description'] });
|
||||
|
||||
await sut.unlockProperties(asset.id, ['description', 'dateTimeOriginal']);
|
||||
|
||||
await expect(
|
||||
ctx.database
|
||||
.selectFrom('asset_exif')
|
||||
.select('lockedProperties')
|
||||
.where('assetId', '=', asset.id)
|
||||
.executeTakeFirstOrThrow(),
|
||||
).resolves.toEqual({ lockedProperties: null });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { Kysely } from 'kysely';
|
||||
import { AssetMediaStatus } from 'src/dtos/asset-media-response.dto';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { EventRepository } from 'src/repositories/event.repository';
|
||||
import { JobRepository } from 'src/repositories/job.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { StorageRepository } from 'src/repositories/storage.repository';
|
||||
import { UserRepository } from 'src/repositories/user.repository';
|
||||
import { DB } from 'src/schema';
|
||||
import { AssetMediaService } from 'src/services/asset-media.service';
|
||||
import { AssetService } from 'src/services/asset.service';
|
||||
import { mediumFactory, newMediumService } from 'test/medium.factory';
|
||||
import { factory } from 'test/small.factory';
|
||||
import { getKyselyDB } from 'test/utils';
|
||||
|
||||
let defaultDatabase: Kysely<DB>;
|
||||
|
||||
const setup = (db?: Kysely<DB>) => {
|
||||
return newMediumService(AssetMediaService, {
|
||||
database: db || defaultDatabase,
|
||||
real: [AccessRepository, AssetRepository, UserRepository],
|
||||
mock: [EventRepository, LoggingRepository, JobRepository, StorageRepository],
|
||||
});
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
defaultDatabase = await getKyselyDB();
|
||||
});
|
||||
|
||||
describe(AssetService.name, () => {
|
||||
describe('uploadAsset', () => {
|
||||
it('should work', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
|
||||
ctx.getMock(StorageRepository).utimes.mockResolvedValue();
|
||||
ctx.getMock(EventRepository).emit.mockResolvedValue();
|
||||
ctx.getMock(JobRepository).queue.mockResolvedValue();
|
||||
|
||||
const { user } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
await ctx.newExif({ assetId: asset.id, fileSizeInByte: 12_345 });
|
||||
const auth = factory.auth({ user: { id: user.id } });
|
||||
const file = mediumFactory.uploadFile();
|
||||
|
||||
await expect(
|
||||
sut.uploadAsset(
|
||||
auth,
|
||||
{
|
||||
deviceId: 'some-id',
|
||||
deviceAssetId: 'some-id',
|
||||
fileModifiedAt: new Date(),
|
||||
fileCreatedAt: new Date(),
|
||||
assetData: Buffer.from('some data'),
|
||||
},
|
||||
file,
|
||||
),
|
||||
).resolves.toEqual({
|
||||
id: expect.any(String),
|
||||
status: AssetMediaStatus.CREATED,
|
||||
});
|
||||
|
||||
expect(ctx.getMock(EventRepository).emit).toHaveBeenCalledWith('AssetCreate', {
|
||||
asset: expect.objectContaining({ deviceAssetId: 'some-id' }),
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with an empty metadata list', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
|
||||
ctx.getMock(StorageRepository).utimes.mockResolvedValue();
|
||||
ctx.getMock(EventRepository).emit.mockResolvedValue();
|
||||
ctx.getMock(JobRepository).queue.mockResolvedValue();
|
||||
|
||||
const { user } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
await ctx.newExif({ assetId: asset.id, fileSizeInByte: 12_345 });
|
||||
const auth = factory.auth({ user: { id: user.id } });
|
||||
const file = mediumFactory.uploadFile();
|
||||
|
||||
await expect(
|
||||
sut.uploadAsset(
|
||||
auth,
|
||||
{
|
||||
deviceId: 'some-id',
|
||||
deviceAssetId: 'some-id',
|
||||
fileModifiedAt: new Date(),
|
||||
fileCreatedAt: new Date(),
|
||||
assetData: Buffer.from('some data'),
|
||||
metadata: [],
|
||||
},
|
||||
file,
|
||||
),
|
||||
).resolves.toEqual({
|
||||
id: expect.any(String),
|
||||
status: AssetMediaStatus.CREATED,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Kysely } from 'kysely';
|
||||
import { AssetFileType, JobName, SharedLinkType } from 'src/enum';
|
||||
import { AssetFileType, AssetMetadataKey, JobName, SharedLinkType } from 'src/enum';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
import { AlbumRepository } from 'src/repositories/album.repository';
|
||||
import { AssetJobRepository } from 'src/repositories/asset-job.repository';
|
||||
@@ -430,4 +430,177 @@ describe(AssetService.name, () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('upsertBulkMetadata', () => {
|
||||
it('should work', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const auth = factory.auth({ user });
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
const items = [{ assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'foo' } }];
|
||||
|
||||
await sut.upsertBulkMetadata(auth, { items });
|
||||
|
||||
const metadata = await ctx.get(AssetRepository).getMetadata(asset.id);
|
||||
expect(metadata.length).toEqual(1);
|
||||
expect(metadata[0]).toEqual(
|
||||
expect.objectContaining({ key: AssetMetadataKey.MobileApp, value: { iCloudId: 'foo' } }),
|
||||
);
|
||||
});
|
||||
|
||||
it('should work on conflict', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const auth = factory.auth({ user });
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
await ctx.newMetadata({ assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'old-id' } });
|
||||
|
||||
// verify existing metadata
|
||||
await expect(ctx.get(AssetRepository).getMetadata(asset.id)).resolves.toEqual([
|
||||
expect.objectContaining({ key: AssetMetadataKey.MobileApp, value: { iCloudId: 'old-id' } }),
|
||||
]);
|
||||
|
||||
const items = [{ assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'new-id' } }];
|
||||
await sut.upsertBulkMetadata(auth, { items });
|
||||
|
||||
// verify updated metadata
|
||||
await expect(ctx.get(AssetRepository).getMetadata(asset.id)).resolves.toEqual([
|
||||
expect.objectContaining({ key: AssetMetadataKey.MobileApp, value: { iCloudId: 'new-id' } }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should work with multiple assets', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const auth = factory.auth({ user });
|
||||
const { asset: asset1 } = await ctx.newAsset({ ownerId: user.id });
|
||||
const { asset: asset2 } = await ctx.newAsset({ ownerId: user.id });
|
||||
|
||||
const items = [
|
||||
{ assetId: asset1.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } },
|
||||
{ assetId: asset2.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id2' } },
|
||||
];
|
||||
|
||||
await sut.upsertBulkMetadata(auth, { items });
|
||||
|
||||
const metadata1 = await ctx.get(AssetRepository).getMetadata(asset1.id);
|
||||
expect(metadata1).toEqual([
|
||||
expect.objectContaining({ key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } }),
|
||||
]);
|
||||
|
||||
const metadata2 = await ctx.get(AssetRepository).getMetadata(asset2.id);
|
||||
expect(metadata2).toEqual([
|
||||
expect.objectContaining({ key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id2' } }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should work with multiple metadata for the same asset', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const auth = factory.auth({ user });
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
|
||||
const items = [
|
||||
{ assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } },
|
||||
{ assetId: asset.id, key: 'some-other-key', value: { foo: 'bar' } },
|
||||
];
|
||||
|
||||
await sut.upsertBulkMetadata(auth, { items });
|
||||
|
||||
const metadata = await ctx.get(AssetRepository).getMetadata(asset.id);
|
||||
expect(metadata).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
key: AssetMetadataKey.MobileApp,
|
||||
value: { iCloudId: 'id1' },
|
||||
}),
|
||||
expect.objectContaining({
|
||||
key: 'some-other-key',
|
||||
value: { foo: 'bar' },
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteBulkMetadata', () => {
|
||||
it('should work', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const auth = factory.auth({ user });
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
await ctx.newMetadata({ assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'foo' } });
|
||||
|
||||
await sut.deleteBulkMetadata(auth, { items: [{ assetId: asset.id, key: AssetMetadataKey.MobileApp }] });
|
||||
|
||||
const metadata = await ctx.get(AssetRepository).getMetadata(asset.id);
|
||||
expect(metadata.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should work even if the item does not exist', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const auth = factory.auth({ user });
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
|
||||
await sut.deleteBulkMetadata(auth, { items: [{ assetId: asset.id, key: AssetMetadataKey.MobileApp }] });
|
||||
|
||||
const metadata = await ctx.get(AssetRepository).getMetadata(asset.id);
|
||||
expect(metadata.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should work with multiple assets', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const auth = factory.auth({ user });
|
||||
const { asset: asset1 } = await ctx.newAsset({ ownerId: user.id });
|
||||
await ctx.newMetadata({ assetId: asset1.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } });
|
||||
const { asset: asset2 } = await ctx.newAsset({ ownerId: user.id });
|
||||
await ctx.newMetadata({ assetId: asset2.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id2' } });
|
||||
|
||||
await sut.deleteBulkMetadata(auth, {
|
||||
items: [
|
||||
{ assetId: asset1.id, key: AssetMetadataKey.MobileApp },
|
||||
{ assetId: asset2.id, key: AssetMetadataKey.MobileApp },
|
||||
],
|
||||
});
|
||||
|
||||
await expect(ctx.get(AssetRepository).getMetadata(asset1.id)).resolves.toEqual([]);
|
||||
await expect(ctx.get(AssetRepository).getMetadata(asset2.id)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('should work with multiple metadata for the same asset', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const auth = factory.auth({ user });
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
await ctx.newMetadata({ assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } });
|
||||
await ctx.newMetadata({ assetId: asset.id, key: 'some-other-key', value: { foo: 'bar' } });
|
||||
|
||||
await sut.deleteBulkMetadata(auth, {
|
||||
items: [
|
||||
{ assetId: asset.id, key: AssetMetadataKey.MobileApp },
|
||||
{ assetId: asset.id, key: 'some-other-key' },
|
||||
],
|
||||
});
|
||||
|
||||
await expect(ctx.get(AssetRepository).getMetadata(asset.id)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('should not delete unspecified keys', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const auth = factory.auth({ user });
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
await ctx.newMetadata({ assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } });
|
||||
await ctx.newMetadata({ assetId: asset.id, key: 'some-other-key', value: { foo: 'bar' } });
|
||||
|
||||
await sut.deleteBulkMetadata(auth, {
|
||||
items: [{ assetId: asset.id, key: AssetMetadataKey.MobileApp }],
|
||||
});
|
||||
|
||||
const metadata = await ctx.get(AssetRepository).getMetadata(asset.id);
|
||||
expect(metadata).toEqual([expect.objectContaining({ key: 'some-other-key', value: { foo: 'bar' } })]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -57,6 +57,7 @@ describe(OcrService.name, () => {
|
||||
id: expect.any(String),
|
||||
text: 'Test OCR',
|
||||
textScore: 0.95,
|
||||
isVisible: true,
|
||||
x1: 10,
|
||||
y1: 10,
|
||||
x2: 50,
|
||||
@@ -106,6 +107,7 @@ describe(OcrService.name, () => {
|
||||
id: expect.any(String),
|
||||
text: 'One',
|
||||
textScore: 0.9,
|
||||
isVisible: true,
|
||||
x1: 0,
|
||||
y1: 1,
|
||||
x2: 2,
|
||||
@@ -121,6 +123,7 @@ describe(OcrService.name, () => {
|
||||
id: expect.any(String),
|
||||
text: 'Two',
|
||||
textScore: 0.89,
|
||||
isVisible: true,
|
||||
x1: 8,
|
||||
y1: 9,
|
||||
x2: 10,
|
||||
@@ -136,6 +139,7 @@ describe(OcrService.name, () => {
|
||||
id: expect.any(String),
|
||||
text: 'Three',
|
||||
textScore: 0.88,
|
||||
isVisible: true,
|
||||
x1: 16,
|
||||
y1: 17,
|
||||
x2: 18,
|
||||
@@ -151,6 +155,7 @@ describe(OcrService.name, () => {
|
||||
id: expect.any(String),
|
||||
text: 'Four',
|
||||
textScore: 0.87,
|
||||
isVisible: true,
|
||||
x1: 24,
|
||||
y1: 25,
|
||||
x2: 26,
|
||||
@@ -166,6 +171,7 @@ describe(OcrService.name, () => {
|
||||
id: expect.any(String),
|
||||
text: 'Five',
|
||||
textScore: 0.86,
|
||||
isVisible: true,
|
||||
x1: 32,
|
||||
y1: 33,
|
||||
x2: 34,
|
||||
|
||||
@@ -52,6 +52,8 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
||||
livePhotoVideoId: null,
|
||||
stackId: null,
|
||||
libraryId: null,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
const { album } = await ctx.newAlbum({ ownerId: user2.id });
|
||||
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
||||
@@ -79,6 +81,8 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
||||
livePhotoVideoId: asset.livePhotoVideoId,
|
||||
stackId: asset.stackId,
|
||||
libraryId: asset.libraryId,
|
||||
width: asset.width,
|
||||
height: asset.height,
|
||||
},
|
||||
type: SyncEntityType.AlbumAssetCreateV1,
|
||||
},
|
||||
|
||||
@@ -37,6 +37,8 @@ describe(SyncEntityType.AssetV1, () => {
|
||||
deletedAt: null,
|
||||
duration: '0:10:00.00000',
|
||||
libraryId: null,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
|
||||
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]);
|
||||
@@ -60,6 +62,8 @@ describe(SyncEntityType.AssetV1, () => {
|
||||
stackId: null,
|
||||
livePhotoVideoId: null,
|
||||
libraryId: asset.libraryId,
|
||||
width: asset.width,
|
||||
height: asset.height,
|
||||
},
|
||||
type: 'AssetV1',
|
||||
},
|
||||
|
||||
@@ -66,6 +66,8 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
|
||||
stackId: null,
|
||||
livePhotoVideoId: null,
|
||||
libraryId: asset.libraryId,
|
||||
width: null,
|
||||
height: null,
|
||||
},
|
||||
type: SyncEntityType.PartnerAssetV1,
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ export const newAssetRepositoryMock = (): Mocked<RepositoryInterface<AssetReposi
|
||||
upsertExif: vitest.fn(),
|
||||
updateAllExif: vitest.fn(),
|
||||
updateDateTimeOriginal: vitest.fn().mockResolvedValue([]),
|
||||
unlockProperties: vitest.fn().mockResolvedValue([]),
|
||||
upsertJobStatus: vitest.fn(),
|
||||
getForCopy: vitest.fn(),
|
||||
getByDayOfYear: vitest.fn(),
|
||||
@@ -44,8 +45,10 @@ export const newAssetRepositoryMock = (): Mocked<RepositoryInterface<AssetReposi
|
||||
updateByLibraryId: vitest.fn(),
|
||||
getFileSamples: vitest.fn(),
|
||||
getMetadata: vitest.fn(),
|
||||
upsertMetadata: vitest.fn(),
|
||||
getMetadataByKey: vitest.fn(),
|
||||
upsertMetadata: vitest.fn(),
|
||||
upsertBulkMetadata: vitest.fn(),
|
||||
deleteMetadataByKey: vitest.fn(),
|
||||
deleteBulkMetadata: vitest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Activity,
|
||||
ApiKey,
|
||||
AssetFile,
|
||||
AuthApiKey,
|
||||
AuthSharedLink,
|
||||
AuthUser,
|
||||
@@ -250,6 +251,8 @@ const assetFactory = (asset: Partial<MapAsset> = {}) => ({
|
||||
thumbhash: null,
|
||||
type: AssetType.Image,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
...asset,
|
||||
});
|
||||
|
||||
@@ -358,6 +361,7 @@ const assetOcrFactory = (
|
||||
boxScore?: number;
|
||||
textScore?: number;
|
||||
text?: string;
|
||||
isVisible?: boolean;
|
||||
} = {},
|
||||
) => ({
|
||||
id: newUuid(),
|
||||
@@ -373,13 +377,22 @@ const assetOcrFactory = (
|
||||
boxScore: 0.95,
|
||||
textScore: 0.92,
|
||||
text: 'Sample Text',
|
||||
isVisible: true,
|
||||
...ocr,
|
||||
});
|
||||
|
||||
const assetFileFactory = (file: Partial<AssetFile> = {}): AssetFile => ({
|
||||
id: newUuid(),
|
||||
type: AssetFileType.Preview,
|
||||
path: '/uploads/user-id/thumbs/path.jpg',
|
||||
...file,
|
||||
});
|
||||
|
||||
export const factory = {
|
||||
activity: activityFactory,
|
||||
apiKey: apiKeyFactory,
|
||||
asset: assetFactory,
|
||||
assetFile: assetFileFactory,
|
||||
assetOcr: assetOcrFactory,
|
||||
auth: authFactory,
|
||||
authApiKey: authApiKeyFactory,
|
||||
|
||||
@@ -20,6 +20,7 @@ import { AlbumUserRepository } from 'src/repositories/album-user.repository';
|
||||
import { AlbumRepository } from 'src/repositories/album.repository';
|
||||
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
|
||||
import { AppRepository } from 'src/repositories/app.repository';
|
||||
import { AssetEditRepository } from 'src/repositories/asset-edit.repository';
|
||||
import { AssetJobRepository } from 'src/repositories/asset-job.repository';
|
||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { AuditRepository } from 'src/repositories/audit.repository';
|
||||
@@ -217,6 +218,7 @@ export type ServiceOverrides = {
|
||||
app: AppRepository;
|
||||
audit: AuditRepository;
|
||||
asset: AssetRepository;
|
||||
assetEdit: AssetEditRepository;
|
||||
assetJob: AssetJobRepository;
|
||||
config: ConfigRepository;
|
||||
cron: CronRepository;
|
||||
@@ -291,6 +293,7 @@ export const getMocks = () => {
|
||||
album: automock(AlbumRepository, { strict: false }),
|
||||
albumUser: automock(AlbumUserRepository),
|
||||
asset: newAssetRepositoryMock(),
|
||||
assetEdit: automock(AssetEditRepository),
|
||||
assetJob: automock(AssetJobRepository),
|
||||
app: automock(AppRepository, { strict: false }),
|
||||
config: newConfigRepositoryMock(),
|
||||
@@ -359,6 +362,7 @@ export const newTestService = <T extends BaseService>(
|
||||
overrides.apiKey || (mocks.apiKey as As<ApiKeyRepository>),
|
||||
overrides.app || (mocks.app as As<AppRepository>),
|
||||
overrides.asset || (mocks.asset as As<AssetRepository>),
|
||||
overrides.assetEdit || (mocks.assetEdit as As<AssetEditRepository>),
|
||||
overrides.assetJob || (mocks.assetJob as As<AssetJobRepository>),
|
||||
overrides.audit || (mocks.audit as As<AuditRepository>),
|
||||
overrides.config || (mocks.config as As<ConfigRepository> as ConfigRepository),
|
||||
|
||||
Reference in New Issue
Block a user