Merge branch 'main' into feature/readonly-sharing

# Conflicts:
#	mobile/openapi/.openapi-generator/FILES
#	mobile/openapi/README.md
#	mobile/openapi/lib/api.dart
#	mobile/openapi/lib/api_client.dart
#	server/src/services/album.service.spec.ts
This commit is contained in:
mgabor
2024-04-17 12:59:50 +02:00
257 changed files with 7638 additions and 8458 deletions

View File

@@ -225,7 +225,7 @@ export const assetStub = {
deviceId: 'device-id',
originalPath: '/data/user1/photo.jpg',
previewPath: '/uploads/user-id/thumbs/path.jpg',
checksum: Buffer.from('file hash', 'utf8'),
checksum: Buffer.from('path hash', 'utf8'),
type: AssetType.IMAGE,
thumbnailPath: '/uploads/user-id/webp/path.ext',
thumbhash: Buffer.from('blablabla', 'base64'),
@@ -295,6 +295,46 @@ export const assetStub = {
deletedAt: null,
}),
externalOffline: Object.freeze<AssetEntity>({
id: 'asset-id',
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: '/data/user1/photo.jpg',
previewPath: '/uploads/user-id/thumbs/path.jpg',
checksum: Buffer.from('path hash', 'utf8'),
type: AssetType.IMAGE,
thumbnailPath: '/uploads/user-id/webp/path.ext',
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('2023-02-23T05:06:29.716Z'),
isFavorite: true,
isArchived: false,
isReadOnly: false,
isExternal: true,
duration: null,
isVisible: true,
livePhotoVideo: null,
livePhotoVideoId: null,
isOffline: true,
libraryId: 'library-id',
library: libraryStub.externalLibrary1,
tags: [],
sharedLinks: [],
originalFileName: 'asset-id.jpg',
faces: [],
sidecarPath: null,
exifInfo: {
fileSizeInByte: 5000,
} as ExifEntity,
deletedAt: null,
}),
image1: Object.freeze<AssetEntity>({
id: 'asset-id-1',
deviceAssetId: 'device-asset-id',

View File

@@ -1,16 +1,17 @@
import { AccessCore } from 'src/cores/access.core';
import { IAccessRepository } from 'src/interfaces/access.interface';
import { Mocked, vitest } from 'vitest';
export interface IAccessRepositoryMock {
activity: jest.Mocked<IAccessRepository['activity']>;
asset: jest.Mocked<IAccessRepository['asset']>;
album: jest.Mocked<IAccessRepository['album']>;
authDevice: jest.Mocked<IAccessRepository['authDevice']>;
library: jest.Mocked<IAccessRepository['library']>;
timeline: jest.Mocked<IAccessRepository['timeline']>;
memory: jest.Mocked<IAccessRepository['memory']>;
person: jest.Mocked<IAccessRepository['person']>;
partner: jest.Mocked<IAccessRepository['partner']>;
activity: Mocked<IAccessRepository['activity']>;
asset: Mocked<IAccessRepository['asset']>;
album: Mocked<IAccessRepository['album']>;
authDevice: Mocked<IAccessRepository['authDevice']>;
library: Mocked<IAccessRepository['library']>;
timeline: Mocked<IAccessRepository['timeline']>;
memory: Mocked<IAccessRepository['memory']>;
person: Mocked<IAccessRepository['person']>;
partner: Mocked<IAccessRepository['partner']>;
}
export const newAccessRepositoryMock = (reset = true): IAccessRepositoryMock => {
@@ -20,47 +21,47 @@ export const newAccessRepositoryMock = (reset = true): IAccessRepositoryMock =>
return {
activity: {
checkOwnerAccess: jest.fn().mockResolvedValue(new Set()),
checkAlbumOwnerAccess: jest.fn().mockResolvedValue(new Set()),
checkCreateAccess: jest.fn().mockResolvedValue(new Set()),
checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()),
checkAlbumOwnerAccess: vitest.fn().mockResolvedValue(new Set()),
checkCreateAccess: vitest.fn().mockResolvedValue(new Set()),
},
asset: {
checkOwnerAccess: jest.fn().mockResolvedValue(new Set()),
checkAlbumAccess: jest.fn().mockResolvedValue(new Set()),
checkPartnerAccess: jest.fn().mockResolvedValue(new Set()),
checkSharedLinkAccess: jest.fn().mockResolvedValue(new Set()),
checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()),
checkAlbumAccess: vitest.fn().mockResolvedValue(new Set()),
checkPartnerAccess: vitest.fn().mockResolvedValue(new Set()),
checkSharedLinkAccess: vitest.fn().mockResolvedValue(new Set()),
},
album: {
checkOwnerAccess: jest.fn().mockResolvedValue(new Set()),
checkSharedAlbumAccess: jest.fn().mockResolvedValue(new Set()),
checkSharedLinkAccess: jest.fn().mockResolvedValue(new Set()),
checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()),
checkSharedAlbumAccess: vitest.fn().mockResolvedValue(new Set()),
checkSharedLinkAccess: vitest.fn().mockResolvedValue(new Set()),
},
authDevice: {
checkOwnerAccess: jest.fn().mockResolvedValue(new Set()),
checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()),
},
library: {
checkOwnerAccess: jest.fn().mockResolvedValue(new Set()),
checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()),
},
timeline: {
checkPartnerAccess: jest.fn().mockResolvedValue(new Set()),
checkPartnerAccess: vitest.fn().mockResolvedValue(new Set()),
},
memory: {
checkOwnerAccess: jest.fn().mockResolvedValue(new Set()),
checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()),
},
person: {
checkFaceOwnerAccess: jest.fn().mockResolvedValue(new Set()),
checkOwnerAccess: jest.fn().mockResolvedValue(new Set()),
checkFaceOwnerAccess: vitest.fn().mockResolvedValue(new Set()),
checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()),
},
partner: {
checkUpdateAccess: jest.fn().mockResolvedValue(new Set()),
checkUpdateAccess: vitest.fn().mockResolvedValue(new Set()),
},
};
};

View File

@@ -1,10 +1,11 @@
import { IActivityRepository } from 'src/interfaces/activity.interface';
import { Mocked, vitest } from 'vitest';
export const newActivityRepositoryMock = (): jest.Mocked<IActivityRepository> => {
export const newActivityRepositoryMock = (): Mocked<IActivityRepository> => {
return {
search: jest.fn(),
create: jest.fn(),
delete: jest.fn(),
getStatistics: jest.fn(),
search: vitest.fn(),
create: vitest.fn(),
delete: vitest.fn(),
getStatistics: vitest.fn(),
};
};

View File

@@ -1,27 +1,28 @@
import { IAlbumRepository } from 'src/interfaces/album.interface';
import { Mocked, vitest } from 'vitest';
export const newAlbumRepositoryMock = (): jest.Mocked<IAlbumRepository> => {
export const newAlbumRepositoryMock = (): Mocked<IAlbumRepository> => {
return {
getById: jest.fn(),
getByIds: jest.fn(),
getByAssetId: jest.fn(),
getMetadataForIds: jest.fn(),
getInvalidThumbnail: jest.fn(),
getOwned: jest.fn(),
getShared: jest.fn(),
getNotShared: jest.fn(),
restoreAll: jest.fn(),
softDeleteAll: jest.fn(),
deleteAll: jest.fn(),
getAll: jest.fn(),
addAssetIds: jest.fn(),
removeAsset: jest.fn(),
removeAssetIds: jest.fn(),
getAssetIds: jest.fn(),
hasAsset: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
updateThumbnails: jest.fn(),
getById: vitest.fn(),
getByIds: vitest.fn(),
getByAssetId: vitest.fn(),
getMetadataForIds: vitest.fn(),
getInvalidThumbnail: vitest.fn(),
getOwned: vitest.fn(),
getShared: vitest.fn(),
getNotShared: vitest.fn(),
restoreAll: vitest.fn(),
softDeleteAll: vitest.fn(),
deleteAll: vitest.fn(),
getAll: vitest.fn(),
addAssetIds: vitest.fn(),
removeAsset: vitest.fn(),
removeAssetIds: vitest.fn(),
getAssetIds: vitest.fn(),
hasAsset: vitest.fn(),
create: vitest.fn(),
update: vitest.fn(),
delete: vitest.fn(),
updateThumbnails: vitest.fn(),
};
};

View File

@@ -1,12 +1,13 @@
import { IKeyRepository } from 'src/interfaces/api-key.interface';
import { Mocked, vitest } from 'vitest';
export const newKeyRepositoryMock = (): jest.Mocked<IKeyRepository> => {
export const newKeyRepositoryMock = (): Mocked<IKeyRepository> => {
return {
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
getKey: jest.fn(),
getById: jest.fn(),
getByUserId: jest.fn(),
create: vitest.fn(),
update: vitest.fn(),
delete: vitest.fn(),
getKey: vitest.fn(),
getById: vitest.fn(),
getByUserId: vitest.fn(),
};
};

View File

@@ -1,10 +1,11 @@
import { IAssetStackRepository } from 'src/interfaces/asset-stack.interface';
import { Mocked, vitest } from 'vitest';
export const newAssetStackRepositoryMock = (): jest.Mocked<IAssetStackRepository> => {
export const newAssetStackRepositoryMock = (): Mocked<IAssetStackRepository> => {
return {
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
getById: jest.fn(),
create: vitest.fn(),
update: vitest.fn(),
delete: vitest.fn(),
getById: vitest.fn(),
};
};

View File

@@ -1,39 +1,42 @@
import { IAssetRepository } from 'src/interfaces/asset.interface';
import { Mocked, vitest } from 'vitest';
export const newAssetRepositoryMock = (): jest.Mocked<IAssetRepository> => {
export const newAssetRepositoryMock = (): Mocked<IAssetRepository> => {
return {
create: jest.fn(),
upsertExif: jest.fn(),
upsertJobStatus: jest.fn(),
getByDayOfYear: jest.fn(),
getByIds: jest.fn().mockResolvedValue([]),
getByIdsWithAllRelations: jest.fn().mockResolvedValue([]),
getByAlbumId: jest.fn(),
getByUserId: jest.fn(),
getById: jest.fn(),
getWithout: jest.fn(),
getByChecksum: jest.fn(),
getWith: jest.fn(),
getRandom: jest.fn(),
getFirstAssetForAlbumId: jest.fn(),
getLastUpdatedAssetForAlbumId: jest.fn(),
getAll: jest.fn().mockResolvedValue({ items: [], hasNextPage: false }),
getAllByDeviceId: jest.fn(),
updateAll: jest.fn(),
getLibraryAssetPaths: jest.fn(),
getByLibraryIdAndOriginalPath: jest.fn(),
deleteAll: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
findLivePhotoMatch: jest.fn(),
getMapMarkers: jest.fn(),
getStatistics: jest.fn(),
getTimeBucket: jest.fn(),
getTimeBuckets: jest.fn(),
restoreAll: jest.fn(),
softDeleteAll: jest.fn(),
getAssetIdByCity: jest.fn(),
getAssetIdByTag: jest.fn(),
searchMetadata: jest.fn(),
create: vitest.fn(),
upsertExif: vitest.fn(),
upsertJobStatus: vitest.fn(),
getByDayOfYear: vitest.fn(),
getByIds: vitest.fn().mockResolvedValue([]),
getByIdsWithAllRelations: vitest.fn().mockResolvedValue([]),
getByAlbumId: vitest.fn(),
getByUserId: vitest.fn(),
getById: vitest.fn(),
getWithout: vitest.fn(),
getByChecksum: vitest.fn(),
getWith: vitest.fn(),
getRandom: vitest.fn(),
getFirstAssetForAlbumId: vitest.fn(),
getLastUpdatedAssetForAlbumId: vitest.fn(),
getAll: vitest.fn().mockResolvedValue({ items: [], hasNextPage: false }),
getAllByDeviceId: vitest.fn(),
updateAll: vitest.fn(),
getExternalLibraryAssetPaths: vitest.fn(),
getByLibraryIdAndOriginalPath: vitest.fn(),
deleteAll: vitest.fn(),
update: vitest.fn(),
remove: vitest.fn(),
findLivePhotoMatch: vitest.fn(),
getMapMarkers: vitest.fn(),
getStatistics: vitest.fn(),
getTimeBucket: vitest.fn(),
getTimeBuckets: vitest.fn(),
restoreAll: vitest.fn(),
softDeleteAll: vitest.fn(),
getAssetIdByCity: vitest.fn(),
getAssetIdByTag: vitest.fn(),
searchMetadata: vitest.fn(),
getAllForUserFullSync: vitest.fn(),
getChangedDeltaSync: vitest.fn(),
};
};

View File

@@ -1,8 +1,9 @@
import { IAuditRepository } from 'src/interfaces/audit.interface';
import { Mocked, vitest } from 'vitest';
export const newAuditRepositoryMock = (): jest.Mocked<IAuditRepository> => {
export const newAuditRepositoryMock = (): Mocked<IAuditRepository> => {
return {
getAfter: jest.fn(),
removeBefore: jest.fn(),
getAfter: vitest.fn(),
removeBefore: vitest.fn(),
};
};

View File

@@ -1,14 +1,15 @@
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { Mocked, vitest } from 'vitest';
export const newCryptoRepositoryMock = (): jest.Mocked<ICryptoRepository> => {
export const newCryptoRepositoryMock = (): Mocked<ICryptoRepository> => {
return {
randomUUID: jest.fn().mockReturnValue('random-uuid'),
randomBytes: jest.fn().mockReturnValue(Buffer.from('random-bytes', 'utf8')),
compareBcrypt: jest.fn().mockReturnValue(true),
hashBcrypt: jest.fn().mockImplementation((input) => Promise.resolve(`${input} (hashed)`)),
hashSha256: jest.fn().mockImplementation((input) => `${input} (hashed)`),
hashSha1: jest.fn().mockImplementation((input) => Buffer.from(`${input.toString()} (hashed)`)),
hashFile: jest.fn().mockImplementation((input) => `${input} (file-hashed)`),
newPassword: jest.fn().mockReturnValue(Buffer.from('random-bytes').toString('base64')),
randomUUID: vitest.fn().mockReturnValue('random-uuid'),
randomBytes: vitest.fn().mockReturnValue(Buffer.from('random-bytes', 'utf8')),
compareBcrypt: vitest.fn().mockReturnValue(true),
hashBcrypt: vitest.fn().mockImplementation((input) => Promise.resolve(`${input} (hashed)`)),
hashSha256: vitest.fn().mockImplementation((input) => `${input} (hashed)`),
hashSha1: vitest.fn().mockImplementation((input) => Buffer.from(`${input.toString()} (hashed)`)),
hashFile: vitest.fn().mockImplementation((input) => `${input} (file-hashed)`),
newPassword: vitest.fn().mockReturnValue(Buffer.from('random-bytes').toString('base64')),
};
};

View File

@@ -1,21 +1,22 @@
import { IDatabaseRepository } from 'src/interfaces/database.interface';
import { Version } from 'src/utils/version';
import { Mocked, vitest } from 'vitest';
export const newDatabaseRepositoryMock = (): jest.Mocked<IDatabaseRepository> => {
export const newDatabaseRepositoryMock = (): Mocked<IDatabaseRepository> => {
return {
getExtensionVersion: jest.fn(),
getAvailableExtensionVersion: jest.fn(),
getPreferredVectorExtension: jest.fn(),
getPostgresVersion: jest.fn().mockResolvedValue(new Version(14, 0, 0)),
createExtension: jest.fn().mockImplementation(() => Promise.resolve()),
updateExtension: jest.fn(),
updateVectorExtension: jest.fn(),
reindex: jest.fn(),
shouldReindex: jest.fn(),
runMigrations: jest.fn(),
withLock: jest.fn().mockImplementation((_, function_: <R>() => Promise<R>) => function_()),
tryLock: jest.fn(),
isBusy: jest.fn(),
wait: jest.fn(),
getExtensionVersion: vitest.fn(),
getAvailableExtensionVersion: vitest.fn(),
getPreferredVectorExtension: vitest.fn(),
getPostgresVersion: vitest.fn().mockResolvedValue(new Version(14, 0, 0)),
createExtension: vitest.fn().mockImplementation(() => Promise.resolve()),
updateExtension: vitest.fn(),
updateVectorExtension: vitest.fn(),
reindex: vitest.fn(),
shouldReindex: vitest.fn(),
runMigrations: vitest.fn(),
withLock: vitest.fn().mockImplementation((_, function_: <R>() => Promise<R>) => function_()),
tryLock: vitest.fn(),
isBusy: vitest.fn(),
wait: vitest.fn(),
};
};

View File

@@ -1,10 +1,11 @@
import { IEventRepository } from 'src/interfaces/event.interface';
import { Mocked, vitest } from 'vitest';
export const newEventRepositoryMock = (): jest.Mocked<IEventRepository> => {
export const newEventRepositoryMock = (): Mocked<IEventRepository> => {
return {
clientSend: jest.fn(),
clientBroadcast: jest.fn(),
serverSend: jest.fn(),
serverSendAsync: jest.fn(),
clientSend: vitest.fn(),
clientBroadcast: vitest.fn(),
serverSend: vitest.fn(),
serverSendAsync: vitest.fn(),
};
};

View File

@@ -1,20 +1,21 @@
import { IJobRepository } from 'src/interfaces/job.interface';
import { Mocked, vitest } from 'vitest';
export const newJobRepositoryMock = (): jest.Mocked<IJobRepository> => {
export const newJobRepositoryMock = (): Mocked<IJobRepository> => {
return {
addHandler: jest.fn(),
addCronJob: jest.fn(),
deleteCronJob: jest.fn(),
updateCronJob: jest.fn(),
setConcurrency: jest.fn(),
empty: jest.fn(),
pause: jest.fn(),
resume: jest.fn(),
queue: jest.fn().mockImplementation(() => Promise.resolve()),
queueAll: jest.fn().mockImplementation(() => Promise.resolve()),
getQueueStatus: jest.fn(),
getJobCounts: jest.fn(),
clear: jest.fn(),
waitForQueueCompletion: jest.fn(),
addHandler: vitest.fn(),
addCronJob: vitest.fn(),
deleteCronJob: vitest.fn(),
updateCronJob: vitest.fn(),
setConcurrency: vitest.fn(),
empty: vitest.fn(),
pause: vitest.fn(),
resume: vitest.fn(),
queue: vitest.fn().mockImplementation(() => Promise.resolve()),
queueAll: vitest.fn().mockImplementation(() => Promise.resolve()),
getQueueStatus: vitest.fn(),
getJobCounts: vitest.fn(),
clear: vitest.fn(),
waitForQueueCompletion: vitest.fn(),
};
};

View File

@@ -1,18 +1,19 @@
import { ILibraryRepository } from 'src/interfaces/library.interface';
import { Mocked, vitest } from 'vitest';
export const newLibraryRepositoryMock = (): jest.Mocked<ILibraryRepository> => {
export const newLibraryRepositoryMock = (): Mocked<ILibraryRepository> => {
return {
get: jest.fn(),
getCountForUser: jest.fn(),
create: jest.fn(),
delete: jest.fn(),
softDelete: jest.fn(),
update: jest.fn(),
getStatistics: jest.fn(),
getDefaultUploadLibrary: jest.fn(),
getUploadLibraryCount: jest.fn(),
getAssetIds: jest.fn(),
getAllDeleted: jest.fn(),
getAll: jest.fn(),
get: vitest.fn(),
getCountForUser: vitest.fn(),
create: vitest.fn(),
delete: vitest.fn(),
softDelete: vitest.fn(),
update: vitest.fn(),
getStatistics: vitest.fn(),
getDefaultUploadLibrary: vitest.fn(),
getUploadLibraryCount: vitest.fn(),
getAssetIds: vitest.fn(),
getAllDeleted: vitest.fn(),
getAll: vitest.fn(),
};
};

View File

@@ -0,0 +1,16 @@
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { Mocked, vitest } from 'vitest';
export const newLoggerRepositoryMock = (): Mocked<ILoggerRepository> => {
return {
setLogLevel: vitest.fn(),
setContext: vitest.fn(),
verbose: vitest.fn(),
debug: vitest.fn(),
log: vitest.fn(),
warn: vitest.fn(),
error: vitest.fn(),
fatal: vitest.fn(),
};
};

View File

@@ -1,9 +1,10 @@
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
import { Mocked, vitest } from 'vitest';
export const newMachineLearningRepositoryMock = (): jest.Mocked<IMachineLearningRepository> => {
export const newMachineLearningRepositoryMock = (): Mocked<IMachineLearningRepository> => {
return {
encodeImage: jest.fn(),
encodeText: jest.fn(),
detectFaces: jest.fn(),
encodeImage: vitest.fn(),
encodeText: vitest.fn(),
detectFaces: vitest.fn(),
};
};

View File

@@ -1,11 +1,12 @@
import { IMediaRepository } from 'src/interfaces/media.interface';
import { Mocked, vitest } from 'vitest';
export const newMediaRepositoryMock = (): jest.Mocked<IMediaRepository> => {
export const newMediaRepositoryMock = (): Mocked<IMediaRepository> => {
return {
generateThumbhash: jest.fn(),
resize: jest.fn(),
crop: jest.fn(),
probe: jest.fn(),
transcode: jest.fn(),
generateThumbhash: vitest.fn(),
resize: vitest.fn(),
crop: vitest.fn(),
probe: vitest.fn(),
transcode: vitest.fn(),
};
};

View File

@@ -1,14 +1,15 @@
import { IMemoryRepository } from 'src/interfaces/memory.interface';
import { Mocked, vitest } from 'vitest';
export const newMemoryRepositoryMock = (): jest.Mocked<IMemoryRepository> => {
export const newMemoryRepositoryMock = (): Mocked<IMemoryRepository> => {
return {
search: jest.fn().mockResolvedValue([]),
get: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
getAssetIds: jest.fn().mockResolvedValue(new Set()),
addAssetIds: jest.fn(),
removeAssetIds: jest.fn(),
search: vitest.fn().mockResolvedValue([]),
get: vitest.fn(),
create: vitest.fn(),
update: vitest.fn(),
delete: vitest.fn(),
getAssetIds: vitest.fn().mockResolvedValue(new Set()),
addAssetIds: vitest.fn(),
removeAssetIds: vitest.fn(),
};
};

View File

@@ -1,17 +1,18 @@
import { IMetadataRepository } from 'src/interfaces/metadata.interface';
import { Mocked, vitest } from 'vitest';
export const newMetadataRepositoryMock = (): jest.Mocked<IMetadataRepository> => {
export const newMetadataRepositoryMock = (): Mocked<IMetadataRepository> => {
return {
init: jest.fn(),
teardown: jest.fn(),
reverseGeocode: jest.fn(),
readTags: jest.fn(),
writeTags: jest.fn(),
extractBinaryTag: jest.fn(),
getCameraMakes: jest.fn(),
getCameraModels: jest.fn(),
getCities: jest.fn(),
getCountries: jest.fn(),
getStates: jest.fn(),
init: vitest.fn(),
teardown: vitest.fn(),
reverseGeocode: vitest.fn(),
readTags: vitest.fn(),
writeTags: vitest.fn(),
extractBinaryTag: vitest.fn(),
getCameraMakes: vitest.fn(),
getCameraModels: vitest.fn(),
getCities: vitest.fn(),
getCountries: vitest.fn(),
getStates: vitest.fn(),
};
};

View File

@@ -1,30 +1,31 @@
import { IMetricRepository } from 'src/interfaces/metric.interface';
import { Mocked, vitest } from 'vitest';
export const newMetricRepositoryMock = (): jest.Mocked<IMetricRepository> => {
export const newMetricRepositoryMock = (): Mocked<IMetricRepository> => {
return {
api: {
addToCounter: jest.fn(),
addToGauge: jest.fn(),
addToHistogram: jest.fn(),
configure: jest.fn(),
addToCounter: vitest.fn(),
addToGauge: vitest.fn(),
addToHistogram: vitest.fn(),
configure: vitest.fn(),
},
host: {
addToCounter: jest.fn(),
addToGauge: jest.fn(),
addToHistogram: jest.fn(),
configure: jest.fn(),
addToCounter: vitest.fn(),
addToGauge: vitest.fn(),
addToHistogram: vitest.fn(),
configure: vitest.fn(),
},
jobs: {
addToCounter: jest.fn(),
addToGauge: jest.fn(),
addToHistogram: jest.fn(),
configure: jest.fn(),
addToCounter: vitest.fn(),
addToGauge: vitest.fn(),
addToHistogram: vitest.fn(),
configure: vitest.fn(),
},
repo: {
addToCounter: jest.fn(),
addToGauge: jest.fn(),
addToHistogram: jest.fn(),
configure: jest.fn(),
addToCounter: vitest.fn(),
addToGauge: vitest.fn(),
addToHistogram: vitest.fn(),
configure: vitest.fn(),
},
};
};

View File

@@ -1,10 +1,11 @@
import { IMoveRepository } from 'src/interfaces/move.interface';
import { Mocked, vitest } from 'vitest';
export const newMoveRepositoryMock = (): jest.Mocked<IMoveRepository> => {
export const newMoveRepositoryMock = (): Mocked<IMoveRepository> => {
return {
create: jest.fn(),
getByEntity: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
create: vitest.fn(),
getByEntity: vitest.fn(),
update: vitest.fn(),
delete: vitest.fn(),
};
};

View File

@@ -1,11 +1,12 @@
import { IPartnerRepository } from 'src/interfaces/partner.interface';
import { Mocked, vitest } from 'vitest';
export const newPartnerRepositoryMock = (): jest.Mocked<IPartnerRepository> => {
export const newPartnerRepositoryMock = (): Mocked<IPartnerRepository> => {
return {
create: jest.fn(),
remove: jest.fn(),
getAll: jest.fn(),
get: jest.fn(),
update: jest.fn(),
create: vitest.fn(),
remove: vitest.fn(),
getAll: vitest.fn(),
get: vitest.fn(),
update: vitest.fn(),
};
};

View File

@@ -1,32 +1,33 @@
import { IPersonRepository } from 'src/interfaces/person.interface';
import { Mocked, vitest } from 'vitest';
export const newPersonRepositoryMock = (): jest.Mocked<IPersonRepository> => {
export const newPersonRepositoryMock = (): Mocked<IPersonRepository> => {
return {
getById: jest.fn(),
getAll: jest.fn(),
getAllForUser: jest.fn(),
getAssets: jest.fn(),
getAllWithoutFaces: jest.fn(),
getById: vitest.fn(),
getAll: vitest.fn(),
getAllForUser: vitest.fn(),
getAssets: vitest.fn(),
getAllWithoutFaces: vitest.fn(),
getByName: jest.fn(),
getByName: vitest.fn(),
create: jest.fn(),
update: jest.fn(),
deleteAll: jest.fn(),
delete: jest.fn(),
deleteAllFaces: jest.fn(),
create: vitest.fn(),
update: vitest.fn(),
deleteAll: vitest.fn(),
delete: vitest.fn(),
deleteAllFaces: vitest.fn(),
getStatistics: jest.fn(),
getAllFaces: jest.fn(),
getFacesByIds: jest.fn(),
getRandomFace: jest.fn(),
getStatistics: vitest.fn(),
getAllFaces: vitest.fn(),
getFacesByIds: vitest.fn(),
getRandomFace: vitest.fn(),
reassignFaces: jest.fn(),
createFaces: jest.fn(),
getFaces: jest.fn(),
reassignFace: jest.fn(),
getFaceById: jest.fn(),
getFaceByIdWithAssets: jest.fn(),
getNumberOfPeople: jest.fn(),
reassignFaces: vitest.fn(),
createFaces: vitest.fn(),
getFaces: vitest.fn(),
reassignFace: vitest.fn(),
getFaceById: vitest.fn(),
getFaceByIdWithAssets: vitest.fn(),
getNumberOfPeople: vitest.fn(),
};
};

View File

@@ -1,14 +1,15 @@
import { ISearchRepository } from 'src/interfaces/search.interface';
import { Mocked, vitest } from 'vitest';
export const newSearchRepositoryMock = (): jest.Mocked<ISearchRepository> => {
export const newSearchRepositoryMock = (): Mocked<ISearchRepository> => {
return {
init: jest.fn(),
searchMetadata: jest.fn(),
searchSmart: jest.fn(),
searchFaces: jest.fn(),
upsert: jest.fn(),
searchPlaces: jest.fn(),
getAssetsByCity: jest.fn(),
deleteAllSearchEmbeddings: jest.fn(),
init: vitest.fn(),
searchMetadata: vitest.fn(),
searchSmart: vitest.fn(),
searchFaces: vitest.fn(),
upsert: vitest.fn(),
searchPlaces: vitest.fn(),
getAssetsByCity: vitest.fn(),
deleteAllSearchEmbeddings: vitest.fn(),
};
};

View File

@@ -1,12 +1,13 @@
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
import { Mocked, vitest } from 'vitest';
export const newSharedLinkRepositoryMock = (): jest.Mocked<ISharedLinkRepository> => {
export const newSharedLinkRepositoryMock = (): Mocked<ISharedLinkRepository> => {
return {
getAll: jest.fn(),
get: jest.fn(),
getByKey: jest.fn(),
create: jest.fn(),
remove: jest.fn(),
update: jest.fn(),
getAll: vitest.fn(),
get: vitest.fn(),
getByKey: vitest.fn(),
create: vitest.fn(),
remove: vitest.fn(),
update: vitest.fn(),
};
};

View File

@@ -1,6 +1,7 @@
import { WatchOptions } from 'chokidar';
import { StorageCore } from 'src/cores/storage.core';
import { IStorageRepository, StorageEventType, WatchEvents } from 'src/interfaces/storage.interface';
import { IStorageRepository, WatchEvents } from 'src/interfaces/storage.interface';
import { Mocked, vitest } from 'vitest';
interface MockWatcherOptions {
items?: Array<{ event: 'change' | 'add' | 'unlink' | 'error'; value: string }>;
@@ -13,19 +14,19 @@ export const makeMockWatcher =
events.onReady?.();
for (const item of items || []) {
switch (item.event) {
case StorageEventType.ADD: {
case 'add': {
events.onAdd?.(item.value);
break;
}
case StorageEventType.CHANGE: {
case 'change': {
events.onChange?.(item.value);
break;
}
case StorageEventType.UNLINK: {
case 'unlink': {
events.onUnlink?.(item.value);
break;
}
case StorageEventType.ERROR: {
case 'error': {
events.onError?.(new Error(item.value));
}
}
@@ -38,29 +39,29 @@ export const makeMockWatcher =
return () => Promise.resolve();
};
export const newStorageRepositoryMock = (reset = true): jest.Mocked<IStorageRepository> => {
export const newStorageRepositoryMock = (reset = true): Mocked<IStorageRepository> => {
if (reset) {
StorageCore.reset();
}
return {
createZipStream: jest.fn(),
createReadStream: jest.fn(),
readFile: jest.fn(),
writeFile: jest.fn(),
unlink: jest.fn(),
unlinkDir: jest.fn().mockResolvedValue(true),
removeEmptyDirs: jest.fn(),
checkFileExists: jest.fn(),
mkdirSync: jest.fn(),
checkDiskUsage: jest.fn(),
readdir: jest.fn(),
stat: jest.fn(),
crawl: jest.fn(),
walk: jest.fn().mockImplementation(async function* () {}),
rename: jest.fn(),
copyFile: jest.fn(),
utimes: jest.fn(),
watch: jest.fn().mockImplementation(makeMockWatcher({})),
createZipStream: vitest.fn(),
createReadStream: vitest.fn(),
readFile: vitest.fn(),
writeFile: vitest.fn(),
unlink: vitest.fn(),
unlinkDir: vitest.fn().mockResolvedValue(true),
removeEmptyDirs: vitest.fn(),
checkFileExists: vitest.fn(),
mkdirSync: vitest.fn(),
checkDiskUsage: vitest.fn(),
readdir: vitest.fn(),
stat: vitest.fn(),
crawl: vitest.fn(),
walk: vitest.fn().mockImplementation(async function* () {}),
rename: vitest.fn(),
copyFile: vitest.fn(),
utimes: vitest.fn(),
watch: vitest.fn().mockImplementation(makeMockWatcher({})),
};
};

View File

@@ -1,16 +1,17 @@
import { SystemConfigCore } from 'src/cores/system-config.core';
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
import { Mocked, vitest } from 'vitest';
export const newSystemConfigRepositoryMock = (reset = true): jest.Mocked<ISystemConfigRepository> => {
export const newSystemConfigRepositoryMock = (reset = true): Mocked<ISystemConfigRepository> => {
if (reset) {
SystemConfigCore.reset();
}
return {
fetchStyle: jest.fn(),
load: jest.fn().mockResolvedValue([]),
readFile: jest.fn(),
saveAll: jest.fn().mockResolvedValue([]),
deleteKeys: jest.fn(),
fetchStyle: vitest.fn(),
load: vitest.fn().mockResolvedValue([]),
readFile: vitest.fn(),
saveAll: vitest.fn().mockResolvedValue([]),
deleteKeys: vitest.fn(),
};
};

View File

@@ -1,7 +1,8 @@
import { IServerInfoRepository } from 'src/interfaces/server-info.interface';
import { Mocked, vitest } from 'vitest';
export const newServerInfoRepositoryMock = (): jest.Mocked<IServerInfoRepository> => {
export const newServerInfoRepositoryMock = (): Mocked<IServerInfoRepository> => {
return {
getGitHubRelease: jest.fn(),
getGitHubRelease: vitest.fn(),
};
};

View File

@@ -1,8 +1,9 @@
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { Mocked, vitest } from 'vitest';
export const newSystemMetadataRepositoryMock = (): jest.Mocked<ISystemMetadataRepository> => {
export const newSystemMetadataRepositoryMock = (): Mocked<ISystemMetadataRepository> => {
return {
get: jest.fn(),
set: jest.fn(),
get: vitest.fn() as any,
set: vitest.fn(),
};
};

View File

@@ -1,16 +1,17 @@
import { ITagRepository } from 'src/interfaces/tag.interface';
import { Mocked, vitest } from 'vitest';
export const newTagRepositoryMock = (): jest.Mocked<ITagRepository> => {
export const newTagRepositoryMock = (): Mocked<ITagRepository> => {
return {
getAll: jest.fn(),
getById: jest.fn(),
create: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
hasAsset: jest.fn(),
hasName: jest.fn(),
getAssets: jest.fn(),
addAssets: jest.fn(),
removeAssets: jest.fn(),
getAll: vitest.fn(),
getById: vitest.fn(),
create: vitest.fn(),
update: vitest.fn(),
remove: vitest.fn(),
hasAsset: vitest.fn(),
hasName: vitest.fn(),
getAssets: vitest.fn(),
addAssets: vitest.fn(),
removeAssets: vitest.fn(),
};
};

View File

@@ -1,11 +1,12 @@
import { IUserTokenRepository } from 'src/interfaces/user-token.interface';
import { Mocked, vitest } from 'vitest';
export const newUserTokenRepositoryMock = (): jest.Mocked<IUserTokenRepository> => {
export const newUserTokenRepositoryMock = (): Mocked<IUserTokenRepository> => {
return {
create: jest.fn(),
save: jest.fn(),
delete: jest.fn(),
getByToken: jest.fn(),
getAll: jest.fn(),
create: vitest.fn(),
save: vitest.fn(),
delete: vitest.fn(),
getByToken: vitest.fn(),
getAll: vitest.fn(),
};
};

View File

@@ -1,25 +1,26 @@
import { UserCore } from 'src/cores/user.core';
import { IUserRepository } from 'src/interfaces/user.interface';
import { Mocked, vitest } from 'vitest';
export const newUserRepositoryMock = (reset = true): jest.Mocked<IUserRepository> => {
export const newUserRepositoryMock = (reset = true): Mocked<IUserRepository> => {
if (reset) {
UserCore.reset();
}
return {
get: jest.fn(),
getAdmin: jest.fn(),
getByEmail: jest.fn(),
getByStorageLabel: jest.fn(),
getByOAuthId: jest.fn(),
getUserStats: jest.fn(),
getList: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
getDeletedUsers: jest.fn(),
hasAdmin: jest.fn(),
updateUsage: jest.fn(),
syncUsage: jest.fn(),
get: vitest.fn(),
getAdmin: vitest.fn(),
getByEmail: vitest.fn(),
getByStorageLabel: vitest.fn(),
getByOAuthId: vitest.fn(),
getUserStats: vitest.fn(),
getList: vitest.fn(),
create: vitest.fn(),
update: vitest.fn(),
delete: vitest.fn(),
getDeletedUsers: vitest.fn(),
hasAdmin: vitest.fn(),
updateUsage: vitest.fn(),
syncUsage: vitest.fn(),
};
};

View File

@@ -1,168 +0,0 @@
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { DateTime } from 'luxon';
import fs from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { EventEmitter } from 'node:stream';
import { AppTestModule } from 'src/app.module';
import { dataSource } from 'src/database.config';
import { IJobRepository, JobItem, JobItemHandler, QueueName } from 'src/interfaces/job.interface';
import { IMediaRepository } from 'src/interfaces/media.interface';
import { StorageEventType } from 'src/interfaces/storage.interface';
import { MediaRepository } from 'src/repositories/media.repository';
import { ApiService } from 'src/services/api.service';
import { MicroservicesService } from 'src/services/microservices.service';
import { EntityTarget, ObjectLiteral } from 'typeorm';
export const IMMICH_TEST_ASSET_PATH = process.env.IMMICH_TEST_ASSET_PATH as string;
export const IMMICH_TEST_ASSET_TEMP_PATH = join(tmpdir(), 'immich');
export const today = DateTime.fromObject({ year: 2023, month: 11, day: 3 });
export const yesterday = today.minus({ days: 1 });
export interface ResetOptions {
entities?: EntityTarget<ObjectLiteral>[];
}
export const db = {
reset: async (options?: ResetOptions) => {
if (!dataSource.isInitialized) {
await dataSource.initialize();
}
await dataSource.transaction(async (em) => {
const entities = options?.entities || [];
const tableNames =
entities.length > 0
? entities.map((entity) => em.getRepository(entity).metadata.tableName)
: dataSource.entityMetadatas
.map((entity) => entity.tableName)
.filter((tableName) => !tableName.startsWith('geodata'));
let deleteUsers = false;
for (const tableName of tableNames) {
if (tableName === 'users') {
deleteUsers = true;
continue;
}
await em.query(`DELETE FROM ${tableName} CASCADE;`);
}
if (deleteUsers) {
await em.query(`DELETE FROM "users" CASCADE;`);
}
// Release all locks
await em.query('SELECT pg_advisory_unlock_all()');
});
},
disconnect: async () => {
if (dataSource.isInitialized) {
await dataSource.destroy();
}
},
};
class JobMock implements IJobRepository {
private _handler: JobItemHandler = () => Promise.resolve();
addHandler(_queueName: QueueName, _concurrency: number, handler: JobItemHandler) {
this._handler = handler;
}
addCronJob() {}
updateCronJob() {}
deleteCronJob() {}
validateCronExpression() {}
queue(item: JobItem) {
return this._handler(item);
}
queueAll(items: JobItem[]) {
return Promise.all(items.map((arg) => this._handler(arg))).then(() => {});
}
async resume() {}
async empty() {}
async setConcurrency() {}
getQueueStatus() {
return Promise.resolve(null) as any;
}
getJobCounts() {
return Promise.resolve(null) as any;
}
async pause() {}
clear() {
return Promise.resolve([]);
}
async waitForQueueCompletion() {}
}
class MediaMockRepository extends MediaRepository {
generateThumbhash() {
return Promise.resolve(Buffer.from('mock-thumbhash'));
}
}
let app: INestApplication;
export const testApp = {
create: async (): Promise<INestApplication> => {
const moduleFixture = await Test.createTestingModule({ imports: [AppTestModule] })
.overrideProvider(IJobRepository)
.useClass(JobMock)
.overrideProvider(IMediaRepository)
.useClass(MediaMockRepository)
.compile();
app = await moduleFixture.createNestApplication().init();
await app.get(ApiService).init();
await db.reset();
await app.get(ApiService).init();
await app.get(MicroservicesService).init();
return app;
},
reset: async (options?: ResetOptions) => {
await db.reset(options);
},
get: (member: any) => app.get(member),
teardown: async () => {
if (app) {
await app.get(MicroservicesService).teardown();
await app.close();
}
await db.disconnect();
},
};
export function waitForEvent(emitter: EventEmitter, event: string, times = 1): Promise<void[]> {
const promises: Promise<void>[] = [];
for (let i = 1; i <= times; i++) {
promises.push(
new Promise((resolve, reject) => {
const success = (value: any) => {
emitter.off(StorageEventType.ERROR, fail);
resolve(value);
};
const fail = (error: Error) => {
emitter.off(event, success);
reject(error);
};
emitter.once(event, success);
emitter.once(StorageEventType.ERROR, fail);
}),
);
}
return Promise.all(promises);
}
const directoryExists = async (dirPath: string) =>
await fs.promises
.access(dirPath)
.then(() => true)
.catch(() => false);
export async function restoreTempFolder(): Promise<void> {
if (await directoryExists(`${IMMICH_TEST_ASSET_TEMP_PATH}`)) {
// Temp directory exists, delete all files inside it
await fs.promises.rm(IMMICH_TEST_ASSET_TEMP_PATH, { recursive: true });
}
// Create temp folder
await fs.promises.mkdir(IMMICH_TEST_ASSET_TEMP_PATH);
}