1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-29 15:11:58 +00:00

chore: migrate to vitest (#7156)

* chore: jest => vitest

* chore: replace jest-when
This commit is contained in:
Jason Rasmussen 2024-04-16 10:44:45 -04:00 committed by GitHub
parent ed2e4e5217
commit 50c9bc0336
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 3445 additions and 5478 deletions

View file

@ -10,7 +10,6 @@ module.exports = {
root: true, root: true,
env: { env: {
node: true, node: true,
jest: true,
}, },
ignorePatterns: ['.eslintrc.js'], ignorePatterns: ['.eslintrc.js'],
rules: { rules: {

View file

@ -1,3 +0,0 @@
#!/usr/bin/env bash
node /usr/src/app/node_modules/.bin/jest --config e2e/"$1"/jest-e2e.json --runInBand

7241
server/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -18,11 +18,9 @@
"check": "tsc --noEmit", "check": "tsc --noEmit",
"check:code": "npm run format && npm run lint && npm run check", "check:code": "npm run format && npm run lint && npm run check",
"check:all": "npm run check:code && npm run test:cov", "check:all": "npm run check:code && npm run test:cov",
"test": "jest", "test": "vitest",
"test:watch": "jest --watch", "test:watch": "vitest --watch",
"test:cov": "jest --coverage", "test:cov": "vitest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"e2e:jobs": "jest --config e2e/jobs/jest-e2e.json --runInBand",
"typeorm": "typeorm", "typeorm": "typeorm",
"typeorm:migrations:create": "typeorm migration:create", "typeorm:migrations:create": "typeorm migration:create",
"typeorm:migrations:generate": "typeorm migration:generate -d ./dist/database.config.js", "typeorm:migrations:generate": "typeorm migration:generate -d ./dist/database.config.js",
@ -33,7 +31,6 @@
"sql:generate": "node ./dist/utils/sql.js" "sql:generate": "node ./dist/utils/sql.js"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.22.11",
"@nestjs/bullmq": "^10.0.1", "@nestjs/bullmq": "^10.0.1",
"@nestjs/common": "^10.2.2", "@nestjs/common": "^10.2.2",
"@nestjs/config": "^3.0.0", "@nestjs/config": "^3.0.0",
@ -49,7 +46,6 @@
"@opentelemetry/exporter-prometheus": "^0.50.0", "@opentelemetry/exporter-prometheus": "^0.50.0",
"@opentelemetry/sdk-node": "^0.50.0", "@opentelemetry/sdk-node": "^0.50.0",
"@socket.io/postgres-adapter": "^0.3.1", "@socket.io/postgres-adapter": "^0.3.1",
"@types/picomatch": "^2.3.3",
"archiver": "^7.0.0", "archiver": "^7.0.0",
"async-lock": "^1.4.0", "async-lock": "^1.4.0",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
@ -90,6 +86,7 @@
"@nestjs/cli": "^10.1.16", "@nestjs/cli": "^10.1.16",
"@nestjs/schematics": "^10.0.2", "@nestjs/schematics": "^10.0.2",
"@nestjs/testing": "^10.2.2", "@nestjs/testing": "^10.2.2",
"@swc/core": "^1.4.14",
"@testcontainers/postgresql": "^10.2.1", "@testcontainers/postgresql": "^10.2.1",
"@types/archiver": "^6.0.0", "@types/archiver": "^6.0.0",
"@types/async-lock": "^1.4.2", "@types/async-lock": "^1.4.2",
@ -98,71 +95,32 @@
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/fluent-ffmpeg": "^2.1.21", "@types/fluent-ffmpeg": "^2.1.21",
"@types/imagemin": "^8.0.1", "@types/imagemin": "^8.0.1",
"@types/jest": "29.5.12",
"@types/jest-when": "^3.5.2",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"@types/lodash": "^4.14.197", "@types/lodash": "^4.14.197",
"@types/mock-fs": "^4.13.1", "@types/mock-fs": "^4.13.1",
"@types/multer": "^1.4.7", "@types/multer": "^1.4.7",
"@types/node": "^20.5.7", "@types/node": "^20.5.7",
"@types/picomatch": "^2.3.3",
"@types/ua-parser-js": "^0.7.36", "@types/ua-parser-js": "^0.7.36",
"@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0", "@typescript-eslint/parser": "^7.0.0",
"@vitest/coverage-v8": "^1.5.0",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^52.0.0", "eslint-plugin-unicorn": "^52.0.0",
"jest": "^29.6.4",
"jest-when": "^3.6.0",
"mock-fs": "^5.2.0", "mock-fs": "^5.2.0",
"prettier": "^3.0.2", "prettier": "^3.0.2",
"prettier-plugin-organize-imports": "^3.2.3", "prettier-plugin-organize-imports": "^3.2.3",
"rimraf": "^5.0.1", "rimraf": "^5.0.1",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"sql-formatter": "^15.0.0", "sql-formatter": "^15.0.0",
"ts-jest": "^29.1.1",
"ts-loader": "^9.4.4",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0", "tsconfig-paths": "^4.2.0",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"utimes": "^5.2.1" "unplugin-swc": "^1.4.5",
}, "utimes": "^5.2.1",
"jest": { "vitest": "^1.5.0"
"clearMocks": true,
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": ".",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.ts$": "ts-jest"
},
"collectCoverageFrom": [
"<rootDir>/src/cores/*.(t|j)s",
"<rootDir>/src/dtos/*.(t|j)s",
"<rootDir>/src/interfaces/*.(t|j)s",
"<rootDir>/src/services/*.(t|j)s",
"<rootDir>/src/utils/*.(t|j)s",
"<rootDir>/src/*.t|j)s"
],
"coverageDirectory": "./coverage",
"coverageThreshold": {
"./src/": {
"branches": 70,
"functions": 75,
"lines": 80,
"statements": 80
}
},
"testEnvironment": "node",
"moduleNameMapper": {
"^test(|/.*)$": "<rootDir>/test/$1",
"^src(|/.*)$": "<rootDir>/src/$1"
},
"globalSetup": "<rootDir>/test/global-setup.js"
}, },
"volta": { "volta": {
"node": "20.12.1" "node": "20.12.1"

View file

@ -1,6 +1,7 @@
import { StorageCore } from 'src/cores/storage.core'; import { StorageCore } from 'src/cores/storage.core';
import { vitest } from 'vitest';
jest.mock('src/constants', () => ({ vitest.mock('src/constants', () => ({
APP_MEDIA_LOCATION: '/photos', APP_MEDIA_LOCATION: '/photos',
})); }));

View file

@ -6,11 +6,12 @@ import { activityStub } from 'test/fixtures/activity.stub';
import { authStub } from 'test/fixtures/auth.stub'; import { authStub } from 'test/fixtures/auth.stub';
import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock';
import { newActivityRepositoryMock } from 'test/repositories/activity.repository.mock'; import { newActivityRepositoryMock } from 'test/repositories/activity.repository.mock';
import { Mocked } from 'vitest';
describe(ActivityService.name, () => { describe(ActivityService.name, () => {
let sut: ActivityService; let sut: ActivityService;
let accessMock: IAccessRepositoryMock; let accessMock: IAccessRepositoryMock;
let activityMock: jest.Mocked<IActivityRepository>; let activityMock: Mocked<IActivityRepository>;
beforeEach(() => { beforeEach(() => {
accessMock = newAccessRepositoryMock(); accessMock = newAccessRepositoryMock();

View file

@ -12,13 +12,14 @@ import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositorie
import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock'; import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock';
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
import { Mocked } from 'vitest';
describe(AlbumService.name, () => { describe(AlbumService.name, () => {
let sut: AlbumService; let sut: AlbumService;
let accessMock: IAccessRepositoryMock; let accessMock: IAccessRepositoryMock;
let albumMock: jest.Mocked<IAlbumRepository>; let albumMock: Mocked<IAlbumRepository>;
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: Mocked<IAssetRepository>;
let userMock: jest.Mocked<IUserRepository>; let userMock: Mocked<IUserRepository>;
beforeEach(() => { beforeEach(() => {
accessMock = newAccessRepositoryMock(); accessMock = newAccessRepositoryMock();

View file

@ -6,11 +6,12 @@ import { keyStub } from 'test/fixtures/api-key.stub';
import { authStub } from 'test/fixtures/auth.stub'; import { authStub } from 'test/fixtures/auth.stub';
import { newKeyRepositoryMock } from 'test/repositories/api-key.repository.mock'; import { newKeyRepositoryMock } from 'test/repositories/api-key.repository.mock';
import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
import { Mocked } from 'vitest';
describe(APIKeyService.name, () => { describe(APIKeyService.name, () => {
let sut: APIKeyService; let sut: APIKeyService;
let keyMock: jest.Mocked<IKeyRepository>; let keyMock: Mocked<IKeyRepository>;
let cryptoMock: jest.Mocked<ICryptoRepository>; let cryptoMock: Mocked<ICryptoRepository>;
beforeEach(() => { beforeEach(() => {
cryptoMock = newCryptoRepositoryMock(); cryptoMock = newCryptoRepositoryMock();

View file

@ -1,4 +1,3 @@
import { when } from 'jest-when';
import { AssetRejectReason, AssetUploadAction } from 'src/dtos/asset-v1-response.dto'; import { AssetRejectReason, AssetUploadAction } from 'src/dtos/asset-v1-response.dto';
import { CreateAssetDto } from 'src/dtos/asset-v1.dto'; import { CreateAssetDto } from 'src/dtos/asset-v1.dto';
import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity, AssetType } from 'src/entities/asset.entity'; import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity, AssetType } from 'src/entities/asset.entity';
@ -20,6 +19,7 @@ import { newLibraryRepositoryMock } from 'test/repositories/library.repository.m
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
import { QueryFailedError } from 'typeorm'; import { QueryFailedError } from 'typeorm';
import { Mocked, vitest } from 'vitest';
const _getCreateAssetDto = (): CreateAssetDto => { const _getCreateAssetDto = (): CreateAssetDto => {
const createAssetDto = new CreateAssetDto(); const createAssetDto = new CreateAssetDto();
@ -62,23 +62,23 @@ const _getAsset_1 = () => {
describe('AssetService', () => { describe('AssetService', () => {
let sut: AssetServiceV1; let sut: AssetServiceV1;
let accessMock: IAccessRepositoryMock; let accessMock: IAccessRepositoryMock;
let assetRepositoryMockV1: jest.Mocked<IAssetRepositoryV1>; let assetRepositoryMockV1: Mocked<IAssetRepositoryV1>;
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: Mocked<IAssetRepository>;
let jobMock: jest.Mocked<IJobRepository>; let jobMock: Mocked<IJobRepository>;
let libraryMock: jest.Mocked<ILibraryRepository>; let libraryMock: Mocked<ILibraryRepository>;
let storageMock: jest.Mocked<IStorageRepository>; let storageMock: Mocked<IStorageRepository>;
let userMock: jest.Mocked<IUserRepository>; let userMock: Mocked<IUserRepository>;
beforeEach(() => { beforeEach(() => {
assetRepositoryMockV1 = { assetRepositoryMockV1 = {
get: jest.fn(), get: vitest.fn(),
getAllByUserId: jest.fn(), getAllByUserId: vitest.fn(),
getDetectedObjectsByUserId: jest.fn(), getDetectedObjectsByUserId: vitest.fn(),
getLocationsByUserId: jest.fn(), getLocationsByUserId: vitest.fn(),
getSearchPropertiesByUserId: jest.fn(), getSearchPropertiesByUserId: vitest.fn(),
getAssetsByChecksums: jest.fn(), getAssetsByChecksums: vitest.fn(),
getExistingAssets: jest.fn(), getExistingAssets: vitest.fn(),
getByOriginalPath: jest.fn(), getByOriginalPath: vitest.fn(),
}; };
accessMock = newAccessRepositoryMock(); accessMock = newAccessRepositoryMock();
@ -90,12 +90,11 @@ describe('AssetService', () => {
sut = new AssetServiceV1(accessMock, assetRepositoryMockV1, assetMock, jobMock, libraryMock, storageMock, userMock); sut = new AssetServiceV1(accessMock, assetRepositoryMockV1, assetMock, jobMock, libraryMock, storageMock, userMock);
when(assetRepositoryMockV1.get) assetRepositoryMockV1.get.mockImplementation((assetId) =>
.calledWith(assetStub.livePhotoStillAsset.id) Promise.resolve(
.mockResolvedValue(assetStub.livePhotoStillAsset); [assetStub.livePhotoMotionAsset, assetStub.livePhotoMotionAsset].find((asset) => asset.id === assetId) ?? null,
when(assetRepositoryMockV1.get) ),
.calledWith(assetStub.livePhotoMotionAsset.id) );
.mockResolvedValue(assetStub.livePhotoMotionAsset);
}); });
describe('uploadFile', () => { describe('uploadFile', () => {

View file

@ -1,12 +1,11 @@
import { BadRequestException, UnauthorizedException } from '@nestjs/common'; import { BadRequestException, UnauthorizedException } from '@nestjs/common';
import { when } from 'jest-when';
import { mapAsset } from 'src/dtos/asset-response.dto'; import { mapAsset } from 'src/dtos/asset-response.dto';
import { AssetJobName, AssetStatsResponseDto, UploadFieldName } from 'src/dtos/asset.dto'; import { AssetJobName, AssetStatsResponseDto, UploadFieldName } from 'src/dtos/asset.dto';
import { AssetEntity, AssetType } from 'src/entities/asset.entity'; import { AssetEntity, AssetType } from 'src/entities/asset.entity';
import { IAssetStackRepository } from 'src/interfaces/asset-stack.interface'; import { IAssetStackRepository } from 'src/interfaces/asset-stack.interface';
import { AssetStats, IAssetRepository } from 'src/interfaces/asset.interface'; import { AssetStats, IAssetRepository } from 'src/interfaces/asset.interface';
import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface'; import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
import { IJobRepository, JobItem, JobName } from 'src/interfaces/job.interface'; import { IJobRepository, JobName } from 'src/interfaces/job.interface';
import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface';
@ -26,6 +25,7 @@ import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.m
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
import { Mocked, vitest } from 'vitest';
const stats: AssetStats = { const stats: AssetStats = {
[AssetType.IMAGE]: 10, [AssetType.IMAGE]: 10,
@ -148,19 +148,25 @@ const uploadTests = [
describe(AssetService.name, () => { describe(AssetService.name, () => {
let sut: AssetService; let sut: AssetService;
let accessMock: IAccessRepositoryMock; let accessMock: IAccessRepositoryMock;
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: Mocked<IAssetRepository>;
let jobMock: jest.Mocked<IJobRepository>; let jobMock: Mocked<IJobRepository>;
let storageMock: jest.Mocked<IStorageRepository>; let storageMock: Mocked<IStorageRepository>;
let userMock: jest.Mocked<IUserRepository>; let userMock: Mocked<IUserRepository>;
let eventMock: jest.Mocked<IEventRepository>; let eventMock: Mocked<IEventRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>; let configMock: Mocked<ISystemConfigRepository>;
let partnerMock: jest.Mocked<IPartnerRepository>; let partnerMock: Mocked<IPartnerRepository>;
let assetStackMock: jest.Mocked<IAssetStackRepository>; let assetStackMock: Mocked<IAssetStackRepository>;
it('should work', () => { it('should work', () => {
expect(sut).toBeDefined(); expect(sut).toBeDefined();
}); });
const mockGetById = (assets: AssetEntity[]) => {
assetMock.getById.mockImplementation((assetId) =>
Promise.resolve(assets.find((asset) => asset.id === assetId) ?? null),
);
};
beforeEach(() => { beforeEach(() => {
accessMock = newAccessRepositoryMock(); accessMock = newAccessRepositoryMock();
assetMock = newAssetRepositoryMock(); assetMock = newAssetRepositoryMock();
@ -184,12 +190,7 @@ describe(AssetService.name, () => {
assetStackMock, assetStackMock,
); );
when(assetMock.getById) mockGetById([assetStub.livePhotoStillAsset, assetStub.livePhotoMotionAsset]);
.calledWith(assetStub.livePhotoStillAsset.id)
.mockResolvedValue(assetStub.livePhotoStillAsset as AssetEntity);
when(assetMock.getById)
.calledWith(assetStub.livePhotoMotionAsset.id)
.mockResolvedValue(assetStub.livePhotoMotionAsset as AssetEntity);
}); });
describe('canUpload', () => { describe('canUpload', () => {
@ -299,12 +300,12 @@ describe(AssetService.name, () => {
describe('getMemoryLane', () => { describe('getMemoryLane', () => {
beforeAll(() => { beforeAll(() => {
jest.useFakeTimers(); vitest.useFakeTimers();
jest.setSystemTime(new Date('2024-01-15')); vitest.setSystemTime(new Date('2024-01-15'));
}); });
afterAll(() => { afterAll(() => {
jest.useRealTimers(); vitest.useRealTimers();
}); });
it('should group the assets correctly', async () => { it('should group the assets correctly', async () => {
@ -469,9 +470,7 @@ describe(AssetService.name, () => {
it('should update parent asset updatedAt when children are added', async () => { it('should update parent asset updatedAt when children are added', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['parent'])); accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['parent']));
when(assetMock.getById) mockGetById([{ ...assetStub.image, id: 'parent' }]);
.calledWith('parent', { stack: { assets: true } })
.mockResolvedValue(assetStub.image);
await sut.updateAll(authStub.user1, { await sut.updateAll(authStub.user1, {
ids: [], ids: [],
stackParentId: 'parent', stackParentId: 'parent',
@ -488,9 +487,7 @@ describe(AssetService.name, () => {
stack: assetStackStub('stack-1', [{ id: 'parent' } as AssetEntity, { id: 'child-1' } as AssetEntity]), stack: assetStackStub('stack-1', [{ id: 'parent' } as AssetEntity, { id: 'child-1' } as AssetEntity]),
} as AssetEntity, } as AssetEntity,
]); ]);
when(assetStackMock.getById) assetStackMock.getById.mockResolvedValue(assetStackStub('stack-1', [{ id: 'parent' } as AssetEntity]));
.calledWith('stack-1')
.mockResolvedValue(assetStackStub('stack-1', [{ id: 'parent' } as AssetEntity]));
await sut.updateAll(authStub.user1, { await sut.updateAll(authStub.user1, {
ids: ['child-1'], ids: ['child-1'],
@ -511,12 +508,10 @@ describe(AssetService.name, () => {
{ id: 'child-1' } as AssetEntity, { id: 'child-1' } as AssetEntity,
{ id: 'child-2' } as AssetEntity, { id: 'child-2' } as AssetEntity,
]); ]);
when(assetMock.getById) assetMock.getById.mockResolvedValue({
.calledWith('parent', { stack: { assets: true } }) id: 'child-1',
.mockResolvedValue({ stack,
id: 'child-1', } as AssetEntity);
stack,
} as AssetEntity);
await sut.updateAll(authStub.user1, { await sut.updateAll(authStub.user1, {
stackParentId: 'parent', stackParentId: 'parent',
@ -547,9 +542,7 @@ describe(AssetService.name, () => {
it('merge stacks if new child has children', async () => { it('merge stacks if new child has children', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['child-1'])); accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['child-1']));
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['parent'])); accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['parent']));
when(assetMock.getById) assetMock.getById.mockResolvedValue({ ...assetStub.image, id: 'parent' });
.calledWith('parent', { stack: { assets: true } })
.mockResolvedValue({ ...assetStub.image, id: 'parent' });
assetMock.getByIds.mockResolvedValue([ assetMock.getByIds.mockResolvedValue([
{ {
id: 'child-1', id: 'child-1',
@ -557,9 +550,7 @@ describe(AssetService.name, () => {
stack: assetStackStub('stack-1', [{ id: 'child-1' } as AssetEntity, { id: 'child-2' } as AssetEntity]), stack: assetStackStub('stack-1', [{ id: 'child-1' } as AssetEntity, { id: 'child-2' } as AssetEntity]),
} as AssetEntity, } as AssetEntity,
]); ]);
when(assetStackMock.getById) assetStackMock.getById.mockResolvedValue(assetStackStub('stack-1', [{ id: 'parent' } as AssetEntity]));
.calledWith('stack-1')
.mockResolvedValue(assetStackStub('stack-1', [{ id: 'parent' } as AssetEntity]));
await sut.updateAll(authStub.user1, { await sut.updateAll(authStub.user1, {
ids: ['child-1'], ids: ['child-1'],
@ -579,9 +570,7 @@ describe(AssetService.name, () => {
it('should send ws asset update event', async () => { it('should send ws asset update event', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['asset-1'])); accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['asset-1']));
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['parent'])); accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['parent']));
when(assetMock.getById) assetMock.getById.mockResolvedValue(assetStub.image);
.calledWith('parent', { stack: { assets: true } })
.mockResolvedValue(assetStub.image);
await sut.updateAll(authStub.user1, { await sut.updateAll(authStub.user1, {
ids: ['asset-1'], ids: ['asset-1'],
@ -626,32 +615,10 @@ describe(AssetService.name, () => {
}); });
describe('handleAssetDeletion', () => { describe('handleAssetDeletion', () => {
beforeEach(() => {
when(jobMock.queue)
.calledWith(
expect.objectContaining({
name: JobName.ASSET_DELETION,
}),
)
.mockImplementation(async (item: JobItem) => {
const jobData = (item as { data?: any })?.data || {};
await sut.handleAssetDeletion(jobData);
});
});
it('should remove faces', async () => { it('should remove faces', async () => {
const assetWithFace = { ...assetStub.image, faces: [faceStub.face1, faceStub.mergeFace1] }; const assetWithFace = { ...assetStub.image, faces: [faceStub.face1, faceStub.mergeFace1] };
when(assetMock.getById) assetMock.getById.mockResolvedValue(assetWithFace);
.calledWith(assetWithFace.id, {
faces: {
person: true,
},
library: true,
stack: { assets: true },
exifInfo: true,
})
.mockResolvedValue(assetWithFace);
await sut.handleAssetDeletion({ id: assetWithFace.id }); await sut.handleAssetDeletion({ id: assetWithFace.id });
@ -676,16 +643,7 @@ describe(AssetService.name, () => {
}); });
it('should update stack primary asset if deleted asset was primary asset in a stack', async () => { it('should update stack primary asset if deleted asset was primary asset in a stack', async () => {
when(assetMock.getById) assetMock.getById.mockResolvedValue(assetStub.primaryImage as AssetEntity);
.calledWith(assetStub.primaryImage.id, {
faces: {
person: true,
},
library: true,
stack: { assets: true },
exifInfo: true,
})
.mockResolvedValue(assetStub.primaryImage as AssetEntity);
await sut.handleAssetDeletion({ id: assetStub.primaryImage.id }); await sut.handleAssetDeletion({ id: assetStub.primaryImage.id });
@ -696,16 +654,7 @@ describe(AssetService.name, () => {
}); });
it('should only delete generated files for readonly assets', async () => { it('should only delete generated files for readonly assets', async () => {
when(assetMock.getById) assetMock.getById.mockResolvedValue(assetStub.readOnly);
.calledWith(assetStub.readOnly.id, {
faces: {
person: true,
},
library: true,
stack: { assets: true },
exifInfo: true,
})
.mockResolvedValue(assetStub.readOnly);
await sut.handleAssetDeletion({ id: assetStub.readOnly.id }); await sut.handleAssetDeletion({ id: assetStub.readOnly.id });
@ -728,7 +677,7 @@ describe(AssetService.name, () => {
}); });
it('should not process assets from external library without fromExternal flag', async () => { it('should not process assets from external library without fromExternal flag', async () => {
when(assetMock.getById).calledWith(assetStub.external.id).mockResolvedValue(assetStub.external); assetMock.getById.mockResolvedValue(assetStub.external);
await sut.handleAssetDeletion({ id: assetStub.external.id }); await sut.handleAssetDeletion({ id: assetStub.external.id });
@ -738,16 +687,7 @@ describe(AssetService.name, () => {
}); });
it('should process assets from external library with fromExternal flag', async () => { it('should process assets from external library with fromExternal flag', async () => {
when(assetMock.getById) assetMock.getById.mockResolvedValue(assetStub.external);
.calledWith(assetStub.external.id, {
faces: {
person: true,
},
library: true,
stack: { assets: true },
exifInfo: true,
})
.mockResolvedValue(assetStub.external);
await sut.handleAssetDeletion({ id: assetStub.external.id, fromExternal: true }); await sut.handleAssetDeletion({ id: assetStub.external.id, fromExternal: true });
@ -769,39 +709,12 @@ describe(AssetService.name, () => {
}); });
it('should delete a live photo', async () => { it('should delete a live photo', async () => {
when(assetMock.getById) assetMock.getById.mockResolvedValue(assetStub.livePhotoStillAsset);
.calledWith(assetStub.livePhotoStillAsset.id, {
faces: {
person: true,
},
library: true,
stack: { assets: true },
exifInfo: true,
})
.mockResolvedValue(assetStub.livePhotoStillAsset);
when(assetMock.getById)
.calledWith(assetStub.livePhotoMotionAsset.id, {
faces: {
person: true,
},
library: true,
stack: { assets: true },
exifInfo: true,
})
.mockResolvedValue(assetStub.livePhotoMotionAsset);
await sut.handleAssetDeletion({ id: assetStub.livePhotoStillAsset.id }); await sut.handleAssetDeletion({ id: assetStub.livePhotoStillAsset.id });
expect(jobMock.queue.mock.calls).toEqual([ expect(jobMock.queue.mock.calls).toEqual([
[{ name: JobName.ASSET_DELETION, data: { id: assetStub.livePhotoMotionAsset.id } }], [{ name: JobName.ASSET_DELETION, data: { id: assetStub.livePhotoMotionAsset.id } }],
[
{
name: JobName.DELETE_FILES,
data: {
files: [undefined, undefined, undefined, undefined, 'fake_path/asset_1.mp4'],
},
},
],
[ [
{ {
name: JobName.DELETE_FILES, name: JobName.DELETE_FILES,
@ -814,18 +727,8 @@ describe(AssetService.name, () => {
}); });
it('should update usage', async () => { it('should update usage', async () => {
when(assetMock.getById) assetMock.getById.mockResolvedValue(assetStub.image);
.calledWith(assetStub.image.id, {
faces: {
person: true,
},
library: true,
stack: { assets: true },
exifInfo: true,
})
.mockResolvedValue(assetStub.image);
await sut.handleAssetDeletion({ id: assetStub.image.id }); await sut.handleAssetDeletion({ id: assetStub.image.id });
expect(userMock.updateUsage).toHaveBeenCalledWith(assetStub.image.ownerId, -5000); expect(userMock.updateUsage).toHaveBeenCalledWith(assetStub.image.ownerId, -5000);
}); });
}); });
@ -874,18 +777,7 @@ describe(AssetService.name, () => {
it('make old parent the child of new parent', async () => { it('make old parent the child of new parent', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set([assetStub.image.id])); accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set([assetStub.image.id]));
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['new'])); accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['new']));
assetMock.getById.mockResolvedValue({ ...assetStub.image, stackId: 'stack-1' });
when(assetMock.getById)
.calledWith(assetStub.image.id, {
faces: {
person: true,
},
library: true,
stack: {
assets: true,
},
})
.mockResolvedValue({ ...assetStub.image, stackId: 'stack-1' });
await sut.updateStackParent(authStub.user1, { await sut.updateStackParent(authStub.user1, {
oldParentId: assetStub.image.id, oldParentId: assetStub.image.id,

View file

@ -16,16 +16,17 @@ import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.moc
import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock';
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
import { Mocked } from 'vitest';
describe(AuditService.name, () => { describe(AuditService.name, () => {
let sut: AuditService; let sut: AuditService;
let accessMock: IAccessRepositoryMock; let accessMock: IAccessRepositoryMock;
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: Mocked<IAssetRepository>;
let auditMock: jest.Mocked<IAuditRepository>; let auditMock: Mocked<IAuditRepository>;
let cryptoMock: jest.Mocked<ICryptoRepository>; let cryptoMock: Mocked<ICryptoRepository>;
let personMock: jest.Mocked<IPersonRepository>; let personMock: Mocked<IPersonRepository>;
let storageMock: jest.Mocked<IStorageRepository>; let storageMock: Mocked<IStorageRepository>;
let userMock: jest.Mocked<IUserRepository>; let userMock: Mocked<IUserRepository>;
beforeEach(() => { beforeEach(() => {
accessMock = newAccessRepositoryMock(); accessMock = newAccessRepositoryMock();

View file

@ -29,6 +29,7 @@ import { newSharedLinkRepositoryMock } from 'test/repositories/shared-link.repos
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
import { newUserTokenRepositoryMock } from 'test/repositories/user-token.repository.mock'; import { newUserTokenRepositoryMock } from 'test/repositories/user-token.repository.mock';
import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
import { Mock, Mocked, vitest } from 'vitest';
// const token = Buffer.from('my-api-key', 'utf8').toString('base64'); // const token = Buffer.from('my-api-key', 'utf8').toString('base64');
@ -58,34 +59,34 @@ const oauthUserWithDefaultQuota = {
describe('AuthService', () => { describe('AuthService', () => {
let sut: AuthService; let sut: AuthService;
let accessMock: jest.Mocked<IAccessRepositoryMock>; let accessMock: Mocked<IAccessRepositoryMock>;
let cryptoMock: jest.Mocked<ICryptoRepository>; let cryptoMock: Mocked<ICryptoRepository>;
let userMock: jest.Mocked<IUserRepository>; let userMock: Mocked<IUserRepository>;
let libraryMock: jest.Mocked<ILibraryRepository>; let libraryMock: Mocked<ILibraryRepository>;
let loggerMock: jest.Mocked<ILoggerRepository>; let loggerMock: Mocked<ILoggerRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>; let configMock: Mocked<ISystemConfigRepository>;
let userTokenMock: jest.Mocked<IUserTokenRepository>; let userTokenMock: Mocked<IUserTokenRepository>;
let shareMock: jest.Mocked<ISharedLinkRepository>; let shareMock: Mocked<ISharedLinkRepository>;
let keyMock: jest.Mocked<IKeyRepository>; let keyMock: Mocked<IKeyRepository>;
let callbackMock: jest.Mock; let callbackMock: Mock;
let userinfoMock: jest.Mock; let userinfoMock: Mock;
beforeEach(() => { beforeEach(() => {
callbackMock = jest.fn().mockReturnValue({ access_token: 'access-token' }); callbackMock = vitest.fn().mockReturnValue({ access_token: 'access-token' });
userinfoMock = jest.fn().mockResolvedValue({ sub, email }); userinfoMock = vitest.fn().mockResolvedValue({ sub, email });
jest.spyOn(generators, 'state').mockReturnValue('state'); vitest.spyOn(generators, 'state').mockReturnValue('state');
jest.spyOn(Issuer, 'discover').mockResolvedValue({ vitest.spyOn(Issuer, 'discover').mockResolvedValue({
id_token_signing_alg_values_supported: ['RS256'], id_token_signing_alg_values_supported: ['RS256'],
Client: jest.fn().mockResolvedValue({ Client: vitest.fn().mockResolvedValue({
issuer: { issuer: {
metadata: { metadata: {
end_session_endpoint: 'http://end-session-endpoint', end_session_endpoint: 'http://end-session-endpoint',
}, },
}, },
authorizationUrl: jest.fn().mockReturnValue('http://authorization-url'), authorizationUrl: vitest.fn().mockReturnValue('http://authorization-url'),
callbackParams: jest.fn().mockReturnValue({ state: 'state' }), callbackParams: vitest.fn().mockReturnValue({ state: 'state' }),
callback: callbackMock, callback: callbackMock,
userinfo: userinfoMock, userinfo: userinfoMock,
}), }),

View file

@ -3,10 +3,11 @@ import { DatabaseService } from 'src/services/database.service';
import { ImmichLogger } from 'src/utils/logger'; import { ImmichLogger } from 'src/utils/logger';
import { Version, VersionType } from 'src/utils/version'; import { Version, VersionType } from 'src/utils/version';
import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock';
import { MockInstance, Mocked, vitest } from 'vitest';
describe(DatabaseService.name, () => { describe(DatabaseService.name, () => {
let sut: DatabaseService; let sut: DatabaseService;
let databaseMock: jest.Mocked<IDatabaseRepository>; let databaseMock: Mocked<IDatabaseRepository>;
beforeEach(() => { beforeEach(() => {
databaseMock = newDatabaseRepositoryMock(); databaseMock = newDatabaseRepositoryMock();
@ -22,14 +23,14 @@ describe(DatabaseService.name, () => {
[{ vectorExt: DatabaseExtension.VECTORS, extName: 'pgvecto.rs', minVersion: new Version(0, 1, 1) }], [{ vectorExt: DatabaseExtension.VECTORS, extName: 'pgvecto.rs', minVersion: new Version(0, 1, 1) }],
[{ vectorExt: DatabaseExtension.VECTOR, extName: 'pgvector', minVersion: new Version(0, 5, 0) }], [{ vectorExt: DatabaseExtension.VECTOR, extName: 'pgvector', minVersion: new Version(0, 5, 0) }],
] as const)('init', ({ vectorExt, extName, minVersion }) => { ] as const)('init', ({ vectorExt, extName, minVersion }) => {
let fatalLog: jest.SpyInstance; let fatalLog: MockInstance;
let errorLog: jest.SpyInstance; let errorLog: MockInstance;
let warnLog: jest.SpyInstance; let warnLog: MockInstance;
beforeEach(() => { beforeEach(() => {
fatalLog = jest.spyOn(ImmichLogger.prototype, 'fatal'); fatalLog = vitest.spyOn(ImmichLogger.prototype, 'fatal');
errorLog = jest.spyOn(ImmichLogger.prototype, 'error'); errorLog = vitest.spyOn(ImmichLogger.prototype, 'error');
warnLog = jest.spyOn(ImmichLogger.prototype, 'warn'); warnLog = vitest.spyOn(ImmichLogger.prototype, 'warn');
databaseMock.getPreferredVectorExtension.mockReturnValue(vectorExt); databaseMock.getPreferredVectorExtension.mockReturnValue(vectorExt);
databaseMock.getExtensionVersion.mockResolvedValue(minVersion); databaseMock.getExtensionVersion.mockResolvedValue(minVersion);

View file

@ -1,6 +1,6 @@
import { BadRequestException } from '@nestjs/common'; import { BadRequestException } from '@nestjs/common';
import { when } from 'jest-when';
import { DownloadResponseDto } from 'src/dtos/download.dto'; import { DownloadResponseDto } from 'src/dtos/download.dto';
import { AssetEntity } from 'src/entities/asset.entity';
import { IAssetRepository } from 'src/interfaces/asset.interface'; import { IAssetRepository } from 'src/interfaces/asset.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { DownloadService } from 'src/services/download.service'; import { DownloadService } from 'src/services/download.service';
@ -11,6 +11,7 @@ import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositorie
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
import { Readable } from 'typeorm/platform/PlatformTools.js'; import { Readable } from 'typeorm/platform/PlatformTools.js';
import { Mocked, vitest } from 'vitest';
const downloadResponse: DownloadResponseDto = { const downloadResponse: DownloadResponseDto = {
totalSize: 105_000, totalSize: 105_000,
@ -25,8 +26,8 @@ const downloadResponse: DownloadResponseDto = {
describe(DownloadService.name, () => { describe(DownloadService.name, () => {
let sut: DownloadService; let sut: DownloadService;
let accessMock: IAccessRepositoryMock; let accessMock: IAccessRepositoryMock;
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: Mocked<IAssetRepository>;
let storageMock: jest.Mocked<IStorageRepository>; let storageMock: Mocked<IStorageRepository>;
it('should work', () => { it('should work', () => {
expect(sut).toBeDefined(); expect(sut).toBeDefined();
@ -82,8 +83,8 @@ describe(DownloadService.name, () => {
it('should download an archive', async () => { it('should download an archive', async () => {
const archiveMock = { const archiveMock = {
addFile: jest.fn(), addFile: vitest.fn(),
finalize: jest.fn(), finalize: vitest.fn(),
stream: new Readable(), stream: new Readable(),
}; };
@ -105,8 +106,8 @@ describe(DownloadService.name, () => {
it('should handle duplicate file names', async () => { it('should handle duplicate file names', async () => {
const archiveMock = { const archiveMock = {
addFile: jest.fn(), addFile: vitest.fn(),
finalize: jest.fn(), finalize: vitest.fn(),
stream: new Readable(), stream: new Readable(),
}; };
@ -128,8 +129,8 @@ describe(DownloadService.name, () => {
it('should be deterministic', async () => { it('should be deterministic', async () => {
const archiveMock = { const archiveMock = {
addFile: jest.fn(), addFile: vitest.fn(),
finalize: jest.fn(), finalize: vitest.fn(),
stream: new Readable(), stream: new Readable(),
}; };
@ -223,14 +224,15 @@ describe(DownloadService.name, () => {
it('should include the video portion of a live photo', async () => { it('should include the video portion of a live photo', async () => {
const assetIds = [assetStub.livePhotoStillAsset.id]; const assetIds = [assetStub.livePhotoStillAsset.id];
const assets = [assetStub.livePhotoStillAsset, assetStub.livePhotoMotionAsset];
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(assetIds)); accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(assetIds));
when(assetMock.getByIds) assetMock.getByIds.mockImplementation(
.calledWith([assetStub.livePhotoStillAsset.id], { exifInfo: true }) (ids) =>
.mockResolvedValue([assetStub.livePhotoStillAsset]); Promise.resolve(
when(assetMock.getByIds) ids.map((id) => assets.find((asset) => asset.id === id)).filter((asset) => !!asset),
.calledWith([assetStub.livePhotoMotionAsset.id], { exifInfo: true }) ) as Promise<AssetEntity[]>,
.mockResolvedValue([assetStub.livePhotoMotionAsset]); );
await expect(sut.getDownloadInfo(authStub.admin, { assetIds })).resolves.toEqual({ await expect(sut.getDownloadInfo(authStub.admin, { assetIds })).resolves.toEqual({
totalSize: 125_000, totalSize: 125_000,

View file

@ -23,9 +23,10 @@ import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
import { newMetricRepositoryMock } from 'test/repositories/metric.repository.mock'; import { newMetricRepositoryMock } from 'test/repositories/metric.repository.mock';
import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock';
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
import { Mocked, vitest } from 'vitest';
const makeMockHandlers = (status: JobStatus) => { const makeMockHandlers = (status: JobStatus) => {
const mock = jest.fn().mockResolvedValue(status); const mock = vitest.fn().mockResolvedValue(status);
return Object.fromEntries(Object.values(JobName).map((jobName) => [jobName, mock])) as unknown as Record< return Object.fromEntries(Object.values(JobName).map((jobName) => [jobName, mock])) as unknown as Record<
JobName, JobName,
JobHandler JobHandler
@ -34,12 +35,12 @@ const makeMockHandlers = (status: JobStatus) => {
describe(JobService.name, () => { describe(JobService.name, () => {
let sut: JobService; let sut: JobService;
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: Mocked<IAssetRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>; let configMock: Mocked<ISystemConfigRepository>;
let eventMock: jest.Mocked<IEventRepository>; let eventMock: Mocked<IEventRepository>;
let jobMock: jest.Mocked<IJobRepository>; let jobMock: Mocked<IJobRepository>;
let personMock: jest.Mocked<IPersonRepository>; let personMock: Mocked<IPersonRepository>;
let metricMock: jest.Mocked<IMetricRepository>; let metricMock: Mocked<IMetricRepository>;
beforeEach(() => { beforeEach(() => {
assetMock = newAssetRepositoryMock(); assetMock = newAssetRepositoryMock();

View file

@ -1,6 +1,4 @@
import { BadRequestException } from '@nestjs/common'; import { BadRequestException } from '@nestjs/common';
import { when } from 'jest-when';
import { R_OK } from 'node:constants';
import { Stats } from 'node:fs'; import { Stats } from 'node:fs';
import { SystemConfigCore } from 'src/cores/system-config.core'; import { SystemConfigCore } from 'src/cores/system-config.core';
import { mapLibrary } from 'src/dtos/library.dto'; import { mapLibrary } from 'src/dtos/library.dto';
@ -28,17 +26,18 @@ import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock'; import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock';
import { makeMockWatcher, newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { makeMockWatcher, newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
import { Mocked, vitest } from 'vitest';
describe(LibraryService.name, () => { describe(LibraryService.name, () => {
let sut: LibraryService; let sut: LibraryService;
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: Mocked<IAssetRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>; let configMock: Mocked<ISystemConfigRepository>;
let cryptoMock: jest.Mocked<ICryptoRepository>; let cryptoMock: Mocked<ICryptoRepository>;
let jobMock: jest.Mocked<IJobRepository>; let jobMock: Mocked<IJobRepository>;
let libraryMock: jest.Mocked<ILibraryRepository>; let libraryMock: Mocked<ILibraryRepository>;
let storageMock: jest.Mocked<IStorageRepository>; let storageMock: Mocked<IStorageRepository>;
let databaseMock: jest.Mocked<IDatabaseRepository>; let databaseMock: Mocked<IDatabaseRepository>;
beforeEach(() => { beforeEach(() => {
configMock = newSystemConfigRepositoryMock(); configMock = newSystemConfigRepositoryMock();
@ -89,15 +88,13 @@ describe(LibraryService.name, () => {
]); ]);
configMock.load.mockResolvedValue(systemConfigStub.libraryWatchEnabled); configMock.load.mockResolvedValue(systemConfigStub.libraryWatchEnabled);
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1); libraryMock.get.mockImplementation((id) =>
Promise.resolve(
when(libraryMock.get) [libraryStub.externalLibraryWithImportPaths1, libraryStub.externalLibraryWithImportPaths2].find(
.calledWith(libraryStub.externalLibraryWithImportPaths1.id) (library) => library.id === id,
.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); ) || null,
),
when(libraryMock.get) );
.calledWith(libraryStub.externalLibraryWithImportPaths2.id)
.mockResolvedValue(libraryStub.externalLibraryWithImportPaths2);
await sut.init(); await sut.init();
@ -751,7 +748,7 @@ describe(LibraryService.name, () => {
configMock.load.mockResolvedValue(systemConfigStub.libraryWatchEnabled); configMock.load.mockResolvedValue(systemConfigStub.libraryWatchEnabled);
const mockClose = jest.fn(); const mockClose = vitest.fn();
storageMock.watch.mockImplementation(makeMockWatcher({ close: mockClose })); storageMock.watch.mockImplementation(makeMockWatcher({ close: mockClose }));
await sut.init(); await sut.init();
@ -1123,7 +1120,7 @@ describe(LibraryService.name, () => {
it('should watch and unwatch library', async () => { it('should watch and unwatch library', async () => {
libraryMock.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); libraryMock.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]);
libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1);
const mockClose = jest.fn(); const mockClose = vitest.fn();
storageMock.watch.mockImplementation(makeMockWatcher({ close: mockClose })); storageMock.watch.mockImplementation(makeMockWatcher({ close: mockClose }));
await sut.watchAll(); await sut.watchAll();
@ -1260,15 +1257,15 @@ describe(LibraryService.name, () => {
configMock.load.mockResolvedValue(systemConfigStub.libraryWatchEnabled); configMock.load.mockResolvedValue(systemConfigStub.libraryWatchEnabled);
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1); libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
when(libraryMock.get) libraryMock.get.mockImplementation((id) =>
.calledWith(libraryStub.externalLibraryWithImportPaths1.id) Promise.resolve(
.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); [libraryStub.externalLibraryWithImportPaths1, libraryStub.externalLibraryWithImportPaths2].find(
(library) => library.id === id,
) || null,
),
);
when(libraryMock.get) const mockClose = vitest.fn();
.calledWith(libraryStub.externalLibraryWithImportPaths2.id)
.mockResolvedValue(libraryStub.externalLibraryWithImportPaths2);
const mockClose = jest.fn();
storageMock.watch.mockImplementation(makeMockWatcher({ close: mockClose })); storageMock.watch.mockImplementation(makeMockWatcher({ close: mockClose }));
await sut.init(); await sut.init();
@ -1545,7 +1542,7 @@ describe(LibraryService.name, () => {
it('should detect when import path is in immich media folder', async () => { it('should detect when import path is in immich media folder', async () => {
storageMock.stat.mockResolvedValue({ isDirectory: () => true } as Stats); storageMock.stat.mockResolvedValue({ isDirectory: () => true } as Stats);
const validImport = libraryStub.hasImmichPaths.importPaths[1]; const validImport = libraryStub.hasImmichPaths.importPaths[1];
when(storageMock.checkFileExists).calledWith(validImport, R_OK).mockResolvedValue(true); storageMock.checkFileExists.mockImplementation((importPath) => Promise.resolve(importPath === validImport));
await expect( await expect(
sut.validate('library-id', { importPaths: libraryStub.hasImmichPaths.importPaths }), sut.validate('library-id', { importPaths: libraryStub.hasImmichPaths.importPaths }),

View file

@ -32,17 +32,18 @@ import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock';
import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock';
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
import { Mocked } from 'vitest';
describe(MediaService.name, () => { describe(MediaService.name, () => {
let sut: MediaService; let sut: MediaService;
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: Mocked<IAssetRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>; let configMock: Mocked<ISystemConfigRepository>;
let jobMock: jest.Mocked<IJobRepository>; let jobMock: Mocked<IJobRepository>;
let mediaMock: jest.Mocked<IMediaRepository>; let mediaMock: Mocked<IMediaRepository>;
let moveMock: jest.Mocked<IMoveRepository>; let moveMock: Mocked<IMoveRepository>;
let personMock: jest.Mocked<IPersonRepository>; let personMock: Mocked<IPersonRepository>;
let storageMock: jest.Mocked<IStorageRepository>; let storageMock: Mocked<IStorageRepository>;
let cryptoMock: jest.Mocked<ICryptoRepository>; let cryptoMock: Mocked<ICryptoRepository>;
beforeEach(() => { beforeEach(() => {
assetMock = newAssetRepositoryMock(); assetMock = newAssetRepositoryMock();

View file

@ -7,10 +7,11 @@ import { memoryStub } from 'test/fixtures/memory.stub';
import { userStub } from 'test/fixtures/user.stub'; import { userStub } from 'test/fixtures/user.stub';
import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock';
import { newMemoryRepositoryMock } from 'test/repositories/memory.repository.mock'; import { newMemoryRepositoryMock } from 'test/repositories/memory.repository.mock';
import { Mocked } from 'vitest';
describe(MemoryService.name, () => { describe(MemoryService.name, () => {
let accessMock: IAccessRepositoryMock; let accessMock: IAccessRepositoryMock;
let memoryMock: jest.Mocked<IMemoryRepository>; let memoryMock: Mocked<IMemoryRepository>;
let sut: MemoryService; let sut: MemoryService;
beforeEach(() => { beforeEach(() => {

View file

@ -1,5 +1,4 @@
import { BinaryField } from 'exiftool-vendored'; import { BinaryField } from 'exiftool-vendored';
import { when } from 'jest-when';
import { randomBytes } from 'node:crypto'; import { randomBytes } from 'node:crypto';
import { Stats } from 'node:fs'; import { Stats } from 'node:fs';
import { constants } from 'node:fs/promises'; import { constants } from 'node:fs/promises';
@ -34,20 +33,21 @@ import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock';
import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock';
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
import { Mocked } from 'vitest';
describe(MetadataService.name, () => { describe(MetadataService.name, () => {
let albumMock: jest.Mocked<IAlbumRepository>; let albumMock: Mocked<IAlbumRepository>;
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: Mocked<IAssetRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>; let configMock: Mocked<ISystemConfigRepository>;
let cryptoRepository: jest.Mocked<ICryptoRepository>; let cryptoRepository: Mocked<ICryptoRepository>;
let jobMock: jest.Mocked<IJobRepository>; let jobMock: Mocked<IJobRepository>;
let metadataMock: jest.Mocked<IMetadataRepository>; let metadataMock: Mocked<IMetadataRepository>;
let moveMock: jest.Mocked<IMoveRepository>; let moveMock: Mocked<IMoveRepository>;
let mediaMock: jest.Mocked<IMediaRepository>; let mediaMock: Mocked<IMediaRepository>;
let personMock: jest.Mocked<IPersonRepository>; let personMock: Mocked<IPersonRepository>;
let storageMock: jest.Mocked<IStorageRepository>; let storageMock: Mocked<IStorageRepository>;
let eventMock: jest.Mocked<IEventRepository>; let eventMock: Mocked<IEventRepository>;
let databaseMock: jest.Mocked<IDatabaseRepository>; let databaseMock: Mocked<IDatabaseRepository>;
let sut: MetadataService; let sut: MetadataService;
beforeEach(() => { beforeEach(() => {
@ -248,14 +248,13 @@ describe(MetadataService.name, () => {
const originalDate = new Date('2023-11-21T16:13:17.517Z'); const originalDate = new Date('2023-11-21T16:13:17.517Z');
const sidecarDate = new Date('2022-01-01T00:00:00.000Z'); const sidecarDate = new Date('2022-01-01T00:00:00.000Z');
assetMock.getByIds.mockResolvedValue([assetStub.sidecar]); assetMock.getByIds.mockResolvedValue([assetStub.sidecar]);
when(metadataMock.readTags) metadataMock.readTags.mockImplementation((path) => {
.calledWith(assetStub.sidecar.originalPath) const map = {
// higher priority tag [assetStub.sidecar.originalPath]: originalDate.toISOString(),
.mockResolvedValue({ CreationDate: originalDate.toISOString() }); [assetStub.sidecar.sidecarPath as string]: sidecarDate.toISOString(),
when(metadataMock.readTags) };
.calledWith(assetStub.sidecar.sidecarPath as string) return Promise.resolve({ CreationDate: map[path] ?? new Date().toISOString() });
// lower priority tag, but in sidecar });
.mockResolvedValue({ CreateDate: sidecarDate.toISOString() });
await sut.handleMetadataExtraction({ id: assetStub.image.id }); await sut.handleMetadataExtraction({ id: assetStub.image.id });
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.sidecar.id]); expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.sidecar.id]);

View file

@ -7,6 +7,7 @@ import { PartnerService } from 'src/services/partner.service';
import { authStub } from 'test/fixtures/auth.stub'; import { authStub } from 'test/fixtures/auth.stub';
import { partnerStub } from 'test/fixtures/partner.stub'; import { partnerStub } from 'test/fixtures/partner.stub';
import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock'; import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock';
import { Mocked } from 'vitest';
const responseDto = { const responseDto = {
admin: <PartnerResponseDto>{ admin: <PartnerResponseDto>{
@ -49,8 +50,8 @@ const responseDto = {
describe(PartnerService.name, () => { describe(PartnerService.name, () => {
let sut: PartnerService; let sut: PartnerService;
let partnerMock: jest.Mocked<IPartnerRepository>; let partnerMock: Mocked<IPartnerRepository>;
let accessMock: jest.Mocked<IAccessRepository>; let accessMock: Mocked<IAccessRepository>;
beforeEach(() => { beforeEach(() => {
partnerMock = newPartnerRepositoryMock(); partnerMock = newPartnerRepositoryMock();

View file

@ -31,6 +31,7 @@ import { newSearchRepositoryMock } from 'test/repositories/search.repository.moc
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
import { IsNull } from 'typeorm'; import { IsNull } from 'typeorm';
import { Mocked } from 'vitest';
const responseDto: PersonResponseDto = { const responseDto: PersonResponseDto = {
id: 'person-1', id: 'person-1',
@ -61,16 +62,16 @@ const detectFaceMock = {
describe(PersonService.name, () => { describe(PersonService.name, () => {
let accessMock: IAccessRepositoryMock; let accessMock: IAccessRepositoryMock;
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: Mocked<IAssetRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>; let configMock: Mocked<ISystemConfigRepository>;
let jobMock: jest.Mocked<IJobRepository>; let jobMock: Mocked<IJobRepository>;
let machineLearningMock: jest.Mocked<IMachineLearningRepository>; let machineLearningMock: Mocked<IMachineLearningRepository>;
let mediaMock: jest.Mocked<IMediaRepository>; let mediaMock: Mocked<IMediaRepository>;
let moveMock: jest.Mocked<IMoveRepository>; let moveMock: Mocked<IMoveRepository>;
let personMock: jest.Mocked<IPersonRepository>; let personMock: Mocked<IPersonRepository>;
let storageMock: jest.Mocked<IStorageRepository>; let storageMock: Mocked<IStorageRepository>;
let searchMock: jest.Mocked<ISearchRepository>; let searchMock: Mocked<ISearchRepository>;
let cryptoMock: jest.Mocked<ICryptoRepository>; let cryptoMock: Mocked<ICryptoRepository>;
let sut: PersonService; let sut: PersonService;
beforeEach(() => { beforeEach(() => {

View file

@ -19,18 +19,19 @@ import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.m
import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock';
import { newSearchRepositoryMock } from 'test/repositories/search.repository.mock'; import { newSearchRepositoryMock } from 'test/repositories/search.repository.mock';
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
import { Mocked, vitest } from 'vitest';
jest.useFakeTimers(); vitest.useFakeTimers();
describe(SearchService.name, () => { describe(SearchService.name, () => {
let sut: SearchService; let sut: SearchService;
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: Mocked<IAssetRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>; let configMock: Mocked<ISystemConfigRepository>;
let machineMock: jest.Mocked<IMachineLearningRepository>; let machineMock: Mocked<IMachineLearningRepository>;
let personMock: jest.Mocked<IPersonRepository>; let personMock: Mocked<IPersonRepository>;
let searchMock: jest.Mocked<ISearchRepository>; let searchMock: Mocked<ISearchRepository>;
let partnerMock: jest.Mocked<IPartnerRepository>; let partnerMock: Mocked<IPartnerRepository>;
let metadataMock: jest.Mocked<IMetadataRepository>; let metadataMock: Mocked<IMetadataRepository>;
beforeEach(() => { beforeEach(() => {
assetMock = newAssetRepositoryMock(); assetMock = newAssetRepositoryMock();

View file

@ -13,15 +13,16 @@ import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.r
import { newServerInfoRepositoryMock } from 'test/repositories/system-info.repository.mock'; import { newServerInfoRepositoryMock } from 'test/repositories/system-info.repository.mock';
import { newSystemMetadataRepositoryMock } from 'test/repositories/system-metadata.repository.mock'; import { newSystemMetadataRepositoryMock } from 'test/repositories/system-metadata.repository.mock';
import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
import { Mocked } from 'vitest';
describe(ServerInfoService.name, () => { describe(ServerInfoService.name, () => {
let sut: ServerInfoService; let sut: ServerInfoService;
let eventMock: jest.Mocked<IEventRepository>; let eventMock: Mocked<IEventRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>; let configMock: Mocked<ISystemConfigRepository>;
let serverInfoMock: jest.Mocked<IServerInfoRepository>; let serverInfoMock: Mocked<IServerInfoRepository>;
let storageMock: jest.Mocked<IStorageRepository>; let storageMock: Mocked<IStorageRepository>;
let userMock: jest.Mocked<IUserRepository>; let userMock: Mocked<IUserRepository>;
let systemMetadataMock: jest.Mocked<ISystemMetadataRepository>; let systemMetadataMock: Mocked<ISystemMetadataRepository>;
beforeEach(() => { beforeEach(() => {
configMock = newSystemConfigRepositoryMock(); configMock = newSystemConfigRepositoryMock();

View file

@ -12,12 +12,13 @@ import { sharedLinkResponseStub, sharedLinkStub } from 'test/fixtures/shared-lin
import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock';
import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
import { newSharedLinkRepositoryMock } from 'test/repositories/shared-link.repository.mock'; import { newSharedLinkRepositoryMock } from 'test/repositories/shared-link.repository.mock';
import { Mocked } from 'vitest';
describe(SharedLinkService.name, () => { describe(SharedLinkService.name, () => {
let sut: SharedLinkService; let sut: SharedLinkService;
let accessMock: IAccessRepositoryMock; let accessMock: IAccessRepositoryMock;
let cryptoMock: jest.Mocked<ICryptoRepository>; let cryptoMock: Mocked<ICryptoRepository>;
let shareMock: jest.Mocked<ISharedLinkRepository>; let shareMock: Mocked<ISharedLinkRepository>;
beforeEach(() => { beforeEach(() => {
accessMock = newAccessRepositoryMock(); accessMock = newAccessRepositoryMock();

View file

@ -15,6 +15,7 @@ import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock'; import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock';
import { newSearchRepositoryMock } from 'test/repositories/search.repository.mock'; import { newSearchRepositoryMock } from 'test/repositories/search.repository.mock';
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
import { Mocked } from 'vitest';
const asset = { const asset = {
id: 'asset-1', id: 'asset-1',
@ -23,12 +24,12 @@ const asset = {
describe(SmartInfoService.name, () => { describe(SmartInfoService.name, () => {
let sut: SmartInfoService; let sut: SmartInfoService;
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: Mocked<IAssetRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>; let configMock: Mocked<ISystemConfigRepository>;
let jobMock: jest.Mocked<IJobRepository>; let jobMock: Mocked<IJobRepository>;
let searchMock: jest.Mocked<ISearchRepository>; let searchMock: Mocked<ISearchRepository>;
let machineMock: jest.Mocked<IMachineLearningRepository>; let machineMock: Mocked<IMachineLearningRepository>;
let databaseMock: jest.Mocked<IDatabaseRepository>; let databaseMock: Mocked<IDatabaseRepository>;
beforeEach(() => { beforeEach(() => {
assetMock = newAssetRepositoryMock(); assetMock = newAssetRepositoryMock();

View file

@ -1,6 +1,6 @@
import { when } from 'jest-when';
import { Stats } from 'node:fs'; import { Stats } from 'node:fs';
import { SystemConfigCore, defaults } from 'src/cores/system-config.core'; import { SystemConfigCore, defaults } from 'src/cores/system-config.core';
import { AssetEntity } from 'src/entities/asset.entity';
import { AssetPathType } from 'src/entities/move.entity'; import { AssetPathType } from 'src/entities/move.entity';
import { SystemConfig, SystemConfigKey } from 'src/entities/system-config.entity'; import { SystemConfig, SystemConfigKey } from 'src/entities/system-config.entity';
import { IAlbumRepository } from 'src/interfaces/album.interface'; import { IAlbumRepository } from 'src/interfaces/album.interface';
@ -25,18 +25,19 @@ import { newPersonRepositoryMock } from 'test/repositories/person.repository.moc
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
import { Mocked } from 'vitest';
describe(StorageTemplateService.name, () => { describe(StorageTemplateService.name, () => {
let sut: StorageTemplateService; let sut: StorageTemplateService;
let albumMock: jest.Mocked<IAlbumRepository>; let albumMock: Mocked<IAlbumRepository>;
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: Mocked<IAssetRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>; let configMock: Mocked<ISystemConfigRepository>;
let moveMock: jest.Mocked<IMoveRepository>; let moveMock: Mocked<IMoveRepository>;
let personMock: jest.Mocked<IPersonRepository>; let personMock: Mocked<IPersonRepository>;
let storageMock: jest.Mocked<IStorageRepository>; let storageMock: Mocked<IStorageRepository>;
let userMock: jest.Mocked<IUserRepository>; let userMock: Mocked<IUserRepository>;
let cryptoMock: jest.Mocked<ICryptoRepository>; let cryptoMock: Mocked<ICryptoRepository>;
let databaseMock: jest.Mocked<IDatabaseRepository>; let databaseMock: Mocked<IDatabaseRepository>;
it('should work', () => { it('should work', () => {
expect(sut).toBeDefined(); expect(sut).toBeDefined();
@ -118,43 +119,28 @@ describe(StorageTemplateService.name, () => {
const newMotionPicturePath = `upload/library/${userStub.user1.id}/2022/2022-06-19/${assetStub.livePhotoStillAsset.id}.mp4`; const newMotionPicturePath = `upload/library/${userStub.user1.id}/2022/2022-06-19/${assetStub.livePhotoStillAsset.id}.mp4`;
const newStillPicturePath = `upload/library/${userStub.user1.id}/2022/2022-06-19/${assetStub.livePhotoStillAsset.id}.jpeg`; const newStillPicturePath = `upload/library/${userStub.user1.id}/2022/2022-06-19/${assetStub.livePhotoStillAsset.id}.jpeg`;
when(assetMock.getByIds) assetMock.getByIds.mockImplementation((ids) => {
.calledWith([assetStub.livePhotoStillAsset.id], { exifInfo: true }) const assets = [assetStub.livePhotoStillAsset, assetStub.livePhotoMotionAsset];
.mockResolvedValue([assetStub.livePhotoStillAsset]); return Promise.resolve(
ids.map((id) => assets.find((asset) => asset.id === id)).filter((asset) => !!asset),
) as Promise<AssetEntity[]>;
});
when(assetMock.getByIds) moveMock.create.mockResolvedValueOnce({
.calledWith([assetStub.livePhotoMotionAsset.id], { exifInfo: true }) id: '123',
.mockResolvedValue([assetStub.livePhotoMotionAsset]); entityId: assetStub.livePhotoStillAsset.id,
pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.livePhotoStillAsset.originalPath,
newPath: newStillPicturePath,
});
when(moveMock.create) moveMock.create.mockResolvedValueOnce({
.calledWith({ id: '124',
entityId: assetStub.livePhotoStillAsset.id, entityId: assetStub.livePhotoMotionAsset.id,
pathType: AssetPathType.ORIGINAL, pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.livePhotoStillAsset.originalPath, oldPath: assetStub.livePhotoMotionAsset.originalPath,
newPath: newStillPicturePath, newPath: newMotionPicturePath,
}) });
.mockResolvedValue({
id: '123',
entityId: assetStub.livePhotoStillAsset.id,
pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.livePhotoStillAsset.originalPath,
newPath: newStillPicturePath,
});
when(moveMock.create)
.calledWith({
entityId: assetStub.livePhotoMotionAsset.id,
pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.livePhotoMotionAsset.originalPath,
newPath: newMotionPicturePath,
})
.mockResolvedValue({
id: '124',
entityId: assetStub.livePhotoMotionAsset.id,
pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.livePhotoMotionAsset.originalPath,
newPath: newMotionPicturePath,
});
await expect(sut.handleMigrationSingle({ id: assetStub.livePhotoStillAsset.id })).resolves.toBe( await expect(sut.handleMigrationSingle({ id: assetStub.livePhotoStillAsset.id })).resolves.toBe(
JobStatus.SUCCESS, JobStatus.SUCCESS,
@ -177,34 +163,22 @@ describe(StorageTemplateService.name, () => {
const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${assetStub.image.id}.jpg`; const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${assetStub.image.id}.jpg`;
const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`; const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`;
when(storageMock.checkFileExists).calledWith(assetStub.image.originalPath).mockResolvedValue(true); storageMock.checkFileExists.mockImplementation((path) => Promise.resolve(path === assetStub.image.originalPath));
when(storageMock.checkFileExists).calledWith(previousFailedNewPath).mockResolvedValue(false); moveMock.getByEntity.mockResolvedValue({
when(moveMock.getByEntity).calledWith(assetStub.image.id, AssetPathType.ORIGINAL).mockResolvedValue({
id: '123', id: '123',
entityId: assetStub.image.id, entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL, pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.image.originalPath, oldPath: assetStub.image.originalPath,
newPath: previousFailedNewPath, newPath: previousFailedNewPath,
}); });
assetMock.getByIds.mockResolvedValue([assetStub.image]);
when(assetMock.getByIds) moveMock.update.mockResolvedValue({
.calledWith([assetStub.image.id], { exifInfo: true }) id: '123',
.mockResolvedValue([assetStub.image]); entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
when(moveMock.update) oldPath: assetStub.image.originalPath,
.calledWith({ newPath,
id: '123', });
oldPath: assetStub.image.originalPath,
newPath,
})
.mockResolvedValue({
id: '123',
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.image.originalPath,
newPath,
});
await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS); await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS);
@ -226,38 +200,24 @@ describe(StorageTemplateService.name, () => {
const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${assetStub.image.id}.jpg`; const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${assetStub.image.id}.jpg`;
const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`; const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`;
when(storageMock.checkFileExists).calledWith(assetStub.image.originalPath).mockResolvedValue(false); storageMock.checkFileExists.mockImplementation((path) => Promise.resolve(path === previousFailedNewPath));
when(storageMock.checkFileExists).calledWith(previousFailedNewPath).mockResolvedValue(true); storageMock.stat.mockResolvedValue({ size: 5000 } as Stats);
when(storageMock.stat) cryptoMock.hashFile.mockResolvedValue(assetStub.image.checksum);
.calledWith(previousFailedNewPath) moveMock.getByEntity.mockResolvedValue({
.mockResolvedValue({ size: 5000 } as Stats);
when(cryptoMock.hashFile).calledWith(previousFailedNewPath).mockResolvedValue(assetStub.image.checksum);
when(moveMock.getByEntity).calledWith(assetStub.image.id, AssetPathType.ORIGINAL).mockResolvedValue({
id: '123', id: '123',
entityId: assetStub.image.id, entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL, pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.image.originalPath, oldPath: assetStub.image.originalPath,
newPath: previousFailedNewPath, newPath: previousFailedNewPath,
}); });
assetMock.getByIds.mockResolvedValue([assetStub.image]);
when(assetMock.getByIds) moveMock.update.mockResolvedValue({
.calledWith([assetStub.image.id], { exifInfo: true }) id: '123',
.mockResolvedValue([assetStub.image]); entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
when(moveMock.update) oldPath: previousFailedNewPath,
.calledWith({ newPath,
id: '123', });
oldPath: previousFailedNewPath,
newPath,
})
.mockResolvedValue({
id: '123',
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
oldPath: previousFailedNewPath,
newPath,
});
await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS); await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS);
@ -281,30 +241,17 @@ describe(StorageTemplateService.name, () => {
userMock.get.mockResolvedValue(userStub.user1); userMock.get.mockResolvedValue(userStub.user1);
const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`; const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`;
when(storageMock.rename).calledWith(assetStub.image.originalPath, newPath).mockRejectedValue({ code: 'EXDEV' }); storageMock.rename.mockRejectedValue({ code: 'EXDEV' });
when(storageMock.stat) storageMock.stat.mockResolvedValue({ size: 5000 } as Stats);
.calledWith(newPath) cryptoMock.hashFile.mockResolvedValue(Buffer.from('different-hash', 'utf8'));
.mockResolvedValue({ size: 5000 } as Stats); assetMock.getByIds.mockResolvedValue([assetStub.image]);
when(cryptoMock.hashFile).calledWith(newPath).mockResolvedValue(Buffer.from('different-hash', 'utf8')); moveMock.create.mockResolvedValue({
id: '123',
when(assetMock.getByIds) entityId: assetStub.image.id,
.calledWith([assetStub.image.id], { exifInfo: true }) pathType: AssetPathType.ORIGINAL,
.mockResolvedValue([assetStub.image]); oldPath: assetStub.image.originalPath,
newPath,
when(moveMock.create) });
.calledWith({
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.image.originalPath,
newPath: newPath,
})
.mockResolvedValue({
id: '123',
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.image.originalPath,
newPath,
});
await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS); await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS);
@ -335,38 +282,24 @@ describe(StorageTemplateService.name, () => {
const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${assetStub.image.id}.jpg`; const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${assetStub.image.id}.jpg`;
const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`; const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`;
when(storageMock.checkFileExists).calledWith(assetStub.image.originalPath).mockResolvedValue(false); storageMock.checkFileExists.mockImplementation((path) => Promise.resolve(previousFailedNewPath === path));
when(storageMock.checkFileExists).calledWith(previousFailedNewPath).mockResolvedValue(true); storageMock.stat.mockResolvedValue({ size: failedPathSize } as Stats);
when(storageMock.stat) cryptoMock.hashFile.mockResolvedValue(failedPathChecksum);
.calledWith(previousFailedNewPath) moveMock.getByEntity.mockResolvedValue({
.mockResolvedValue({ size: failedPathSize } as Stats);
when(cryptoMock.hashFile).calledWith(previousFailedNewPath).mockResolvedValue(failedPathChecksum);
when(moveMock.getByEntity).calledWith(assetStub.image.id, AssetPathType.ORIGINAL).mockResolvedValue({
id: '123', id: '123',
entityId: assetStub.image.id, entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL, pathType: AssetPathType.ORIGINAL,
oldPath: assetStub.image.originalPath, oldPath: assetStub.image.originalPath,
newPath: previousFailedNewPath, newPath: previousFailedNewPath,
}); });
assetMock.getByIds.mockResolvedValue([assetStub.image]);
when(assetMock.getByIds) moveMock.update.mockResolvedValue({
.calledWith([assetStub.image.id], { exifInfo: true }) id: '123',
.mockResolvedValue([assetStub.image]); entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
when(moveMock.update) oldPath: previousFailedNewPath,
.calledWith({ newPath,
id: '123', });
oldPath: previousFailedNewPath,
newPath,
})
.mockResolvedValue({
id: '123',
entityId: assetStub.image.id,
pathType: AssetPathType.ORIGINAL,
oldPath: previousFailedNewPath,
newPath,
});
await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS); await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS);
@ -408,13 +341,8 @@ describe(StorageTemplateService.name, () => {
newPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg', newPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg',
}); });
when(storageMock.checkFileExists) storageMock.checkFileExists.mockResolvedValueOnce(true);
.calledWith('upload/library/user-id/2023/2023-02-23/asset-id.jpg') storageMock.checkFileExists.mockResolvedValueOnce(false);
.mockResolvedValue(true);
when(storageMock.checkFileExists)
.calledWith('upload/library/user-id/2023/2023-02-23/asset-id+1.jpg')
.mockResolvedValue(false);
await sut.handleMigration(); await sut.handleMigration();
@ -538,18 +466,18 @@ describe(StorageTemplateService.name, () => {
oldPath: assetStub.image.originalPath, oldPath: assetStub.image.originalPath,
newPath, newPath,
}); });
when(storageMock.stat) storageMock.stat.mockResolvedValueOnce({
.calledWith(newPath) atime: new Date(),
.mockResolvedValue({ mtime: new Date(),
size: 5000, } as Stats);
} as Stats); storageMock.stat.mockResolvedValueOnce({
when(storageMock.stat) size: 5000,
.calledWith(assetStub.image.originalPath) } as Stats);
.mockResolvedValue({ storageMock.stat.mockResolvedValueOnce({
atime: new Date(), atime: new Date(),
mtime: new Date(), mtime: new Date(),
} as Stats); } as Stats);
when(cryptoMock.hashFile).calledWith(newPath).mockResolvedValue(assetStub.image.checksum); cryptoMock.hashFile.mockResolvedValue(assetStub.image.checksum);
await sut.handleMigration(); await sut.handleMigration();
@ -581,11 +509,9 @@ describe(StorageTemplateService.name, () => {
oldPath: assetStub.image.originalPath, oldPath: assetStub.image.originalPath,
newPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', newPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg',
}); });
when(storageMock.stat) storageMock.stat.mockResolvedValue({
.calledWith('upload/library/user-id/2023/2023-02-23/asset-id.jpg') size: 100,
.mockResolvedValue({ } as Stats);
size: 100,
} as Stats);
await sut.handleMigration(); await sut.handleMigration();

View file

@ -1,10 +1,11 @@
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { StorageService } from 'src/services/storage.service'; import { StorageService } from 'src/services/storage.service';
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
import { Mocked } from 'vitest';
describe(StorageService.name, () => { describe(StorageService.name, () => {
let sut: StorageService; let sut: StorageService;
let storageMock: jest.Mocked<IStorageRepository>; let storageMock: Mocked<IStorageRepository>;
beforeEach(() => { beforeEach(() => {
storageMock = newStorageRepositoryMock(); storageMock = newStorageRepositoryMock();

View file

@ -12,16 +12,17 @@ import { newAccessRepositoryMock } from 'test/repositories/access.repository.moc
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
import { newAuditRepositoryMock } from 'test/repositories/audit.repository.mock'; import { newAuditRepositoryMock } from 'test/repositories/audit.repository.mock';
import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock'; import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock';
import { Mocked } from 'vitest';
const untilDate = new Date(2024); const untilDate = new Date(2024);
const mapAssetOpts = { auth: authStub.user1, stripMetadata: false, withStack: true }; const mapAssetOpts = { auth: authStub.user1, stripMetadata: false, withStack: true };
describe(SyncService.name, () => { describe(SyncService.name, () => {
let sut: SyncService; let sut: SyncService;
let accessMock: jest.Mocked<IAccessRepository>; let accessMock: Mocked<IAccessRepository>;
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: Mocked<IAssetRepository>;
let partnerMock: jest.Mocked<IPartnerRepository>; let partnerMock: Mocked<IPartnerRepository>;
let auditMock: jest.Mocked<IAuditRepository>; let auditMock: Mocked<IAuditRepository>;
beforeEach(() => { beforeEach(() => {
partnerMock = newPartnerRepositoryMock(); partnerMock = newPartnerRepositoryMock();

View file

@ -24,6 +24,7 @@ import { ImmichLogger } from 'src/utils/logger';
import { newEventRepositoryMock } from 'test/repositories/event.repository.mock'; import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
import { MockInstance, Mocked, vitest } from 'vitest';
const updates: SystemConfigEntity[] = [ const updates: SystemConfigEntity[] = [
{ key: SystemConfigKey.FFMPEG_CRF, value: 30 }, { key: SystemConfigKey.FFMPEG_CRF, value: 30 },
@ -156,10 +157,10 @@ const updatedConfig = Object.freeze<SystemConfig>({
describe(SystemConfigService.name, () => { describe(SystemConfigService.name, () => {
let sut: SystemConfigService; let sut: SystemConfigService;
let configMock: jest.Mocked<ISystemConfigRepository>; let configMock: Mocked<ISystemConfigRepository>;
let eventMock: jest.Mocked<IEventRepository>; let eventMock: Mocked<IEventRepository>;
let loggerMock: jest.Mocked<ILoggerRepository>; let loggerMock: Mocked<ILoggerRepository>;
let smartInfoMock: jest.Mocked<ISearchRepository>; let smartInfoMock: Mocked<ISearchRepository>;
beforeEach(() => { beforeEach(() => {
delete process.env.IMMICH_CONFIG_FILE; delete process.env.IMMICH_CONFIG_FILE;
@ -183,10 +184,10 @@ describe(SystemConfigService.name, () => {
}); });
describe('getConfig', () => { describe('getConfig', () => {
let warnLog: jest.SpyInstance; let warnLog: MockInstance;
beforeEach(() => { beforeEach(() => {
warnLog = jest.spyOn(ImmichLogger.prototype, 'warn'); warnLog = vitest.spyOn(ImmichLogger.prototype, 'warn');
}); });
afterEach(() => { afterEach(() => {

View file

@ -1,5 +1,4 @@
import { BadRequestException } from '@nestjs/common'; import { BadRequestException } from '@nestjs/common';
import { when } from 'jest-when';
import { AssetIdErrorReason } from 'src/dtos/asset-ids.response.dto'; import { AssetIdErrorReason } from 'src/dtos/asset-ids.response.dto';
import { TagType } from 'src/entities/tag.entity'; import { TagType } from 'src/entities/tag.entity';
import { ITagRepository } from 'src/interfaces/tag.interface'; import { ITagRepository } from 'src/interfaces/tag.interface';
@ -8,10 +7,11 @@ import { assetStub } from 'test/fixtures/asset.stub';
import { authStub } from 'test/fixtures/auth.stub'; import { authStub } from 'test/fixtures/auth.stub';
import { tagResponseStub, tagStub } from 'test/fixtures/tag.stub'; import { tagResponseStub, tagStub } from 'test/fixtures/tag.stub';
import { newTagRepositoryMock } from 'test/repositories/tag.repository.mock'; import { newTagRepositoryMock } from 'test/repositories/tag.repository.mock';
import { Mocked } from 'vitest';
describe(TagService.name, () => { describe(TagService.name, () => {
let sut: TagService; let sut: TagService;
let tagMock: jest.Mocked<ITagRepository>; let tagMock: Mocked<ITagRepository>;
beforeEach(() => { beforeEach(() => {
tagMock = newTagRepositoryMock(); tagMock = newTagRepositoryMock();
@ -129,9 +129,7 @@ describe(TagService.name, () => {
it('should reject duplicate asset ids and accept new ones', async () => { it('should reject duplicate asset ids and accept new ones', async () => {
tagMock.getById.mockResolvedValue(tagStub.tag1); tagMock.getById.mockResolvedValue(tagStub.tag1);
tagMock.hasAsset.mockImplementation((userId, tagId, assetId) => Promise.resolve(assetId === 'asset-1'));
when(tagMock.hasAsset).calledWith(authStub.admin.user.id, 'tag-1', 'asset-1').mockResolvedValue(true);
when(tagMock.hasAsset).calledWith(authStub.admin.user.id, 'tag-1', 'asset-2').mockResolvedValue(false);
await expect( await expect(
sut.addAssets(authStub.admin, 'tag-1', { sut.addAssets(authStub.admin, 'tag-1', {
@ -160,9 +158,7 @@ describe(TagService.name, () => {
it('should accept accept ids that are tagged and reject the rest', async () => { it('should accept accept ids that are tagged and reject the rest', async () => {
tagMock.getById.mockResolvedValue(tagStub.tag1); tagMock.getById.mockResolvedValue(tagStub.tag1);
tagMock.hasAsset.mockImplementation((userId, tagId, assetId) => Promise.resolve(assetId === 'asset-1'));
when(tagMock.hasAsset).calledWith(authStub.admin.user.id, 'tag-1', 'asset-1').mockResolvedValue(true);
when(tagMock.hasAsset).calledWith(authStub.admin.user.id, 'tag-1', 'asset-2').mockResolvedValue(false);
await expect( await expect(
sut.removeAssets(authStub.admin, 'tag-1', { sut.removeAssets(authStub.admin, 'tag-1', {

View file

@ -7,12 +7,13 @@ import { authStub } from 'test/fixtures/auth.stub';
import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock';
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock'; import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock';
import { Mocked } from 'vitest';
describe(TimelineService.name, () => { describe(TimelineService.name, () => {
let sut: TimelineService; let sut: TimelineService;
let accessMock: IAccessRepositoryMock; let accessMock: IAccessRepositoryMock;
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: Mocked<IAssetRepository>;
let partnerMock: jest.Mocked<IPartnerRepository>; let partnerMock: Mocked<IPartnerRepository>;
beforeEach(() => { beforeEach(() => {
accessMock = newAccessRepositoryMock(); accessMock = newAccessRepositoryMock();
assetMock = newAssetRepositoryMock(); assetMock = newAssetRepositoryMock();

View file

@ -9,13 +9,14 @@ import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositorie
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
import { newEventRepositoryMock } from 'test/repositories/event.repository.mock'; import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
import { Mocked } from 'vitest';
describe(TrashService.name, () => { describe(TrashService.name, () => {
let sut: TrashService; let sut: TrashService;
let accessMock: IAccessRepositoryMock; let accessMock: IAccessRepositoryMock;
let assetMock: jest.Mocked<IAssetRepository>; let assetMock: Mocked<IAssetRepository>;
let jobMock: jest.Mocked<IJobRepository>; let jobMock: Mocked<IJobRepository>;
let eventMock: jest.Mocked<IEventRepository>; let eventMock: Mocked<IEventRepository>;
it('should work', () => { it('should work', () => {
expect(sut).toBeDefined(); expect(sut).toBeDefined();

View file

@ -4,7 +4,6 @@ import {
InternalServerErrorException, InternalServerErrorException,
NotFoundException, NotFoundException,
} from '@nestjs/common'; } from '@nestjs/common';
import { when } from 'jest-when';
import { UpdateUserDto, mapUser } from 'src/dtos/user.dto'; import { UpdateUserDto, mapUser } from 'src/dtos/user.dto';
import { UserEntity, UserStatus } from 'src/entities/user.entity'; import { UserEntity, UserStatus } from 'src/entities/user.entity';
import { IAlbumRepository } from 'src/interfaces/album.interface'; import { IAlbumRepository } from 'src/interfaces/album.interface';
@ -26,6 +25,7 @@ import { newLibraryRepositoryMock } from 'test/repositories/library.repository.m
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock';
import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
import { Mocked, vitest } from 'vitest';
const makeDeletedAt = (daysAgo: number) => { const makeDeletedAt = (daysAgo: number) => {
const deletedAt = new Date(); const deletedAt = new Date();
@ -35,14 +35,14 @@ const makeDeletedAt = (daysAgo: number) => {
describe(UserService.name, () => { describe(UserService.name, () => {
let sut: UserService; let sut: UserService;
let userMock: jest.Mocked<IUserRepository>; let userMock: Mocked<IUserRepository>;
let cryptoRepositoryMock: jest.Mocked<ICryptoRepository>; let cryptoRepositoryMock: Mocked<ICryptoRepository>;
let albumMock: jest.Mocked<IAlbumRepository>; let albumMock: Mocked<IAlbumRepository>;
let jobMock: jest.Mocked<IJobRepository>; let jobMock: Mocked<IJobRepository>;
let libraryMock: jest.Mocked<ILibraryRepository>; let libraryMock: Mocked<ILibraryRepository>;
let storageMock: jest.Mocked<IStorageRepository>; let storageMock: Mocked<IStorageRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>; let configMock: Mocked<ISystemConfigRepository>;
beforeEach(() => { beforeEach(() => {
albumMock = newAlbumRepositoryMock(); albumMock = newAlbumRepositoryMock();
@ -55,10 +55,9 @@ describe(UserService.name, () => {
sut = new UserService(albumMock, cryptoRepositoryMock, jobMock, libraryMock, storageMock, configMock, userMock); sut = new UserService(albumMock, cryptoRepositoryMock, jobMock, libraryMock, storageMock, configMock, userMock);
when(userMock.get).calledWith(authStub.admin.user.id, {}).mockResolvedValue(userStub.admin); userMock.get.mockImplementation((userId) =>
when(userMock.get).calledWith(authStub.admin.user.id, { withDeleted: true }).mockResolvedValue(userStub.admin); Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? null),
when(userMock.get).calledWith(authStub.user1.user.id, {}).mockResolvedValue(userStub.user1); );
when(userMock.get).calledWith(authStub.user1.user.id, { withDeleted: true }).mockResolvedValue(userStub.user1);
}); });
describe('getAll', () => { describe('getAll', () => {
@ -136,12 +135,10 @@ describe(UserService.name, () => {
}); });
it('user can only update its information', async () => { it('user can only update its information', async () => {
when(userMock.get) userMock.get.mockResolvedValueOnce({
.calledWith('not_immich_auth_user_id', {}) ...userStub.user1,
.mockResolvedValueOnce({ id: 'not_immich_auth_user_id',
...userStub.user1, });
id: 'not_immich_auth_user_id',
});
const result = sut.update( const result = sut.update(
{ user: userStub.user1 }, { user: userStub.user1 },
@ -195,7 +192,7 @@ describe(UserService.name, () => {
shouldChangePassword: true, shouldChangePassword: true,
}; };
when(userMock.update).calledWith(userStub.user1.id, update).mockResolvedValueOnce(userStub.user1); userMock.update.mockResolvedValueOnce(userStub.user1);
await sut.update(authStub.admin, update); await sut.update(authStub.admin, update);
expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, { expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, {
id: 'user-id', id: 'user-id',
@ -204,7 +201,7 @@ describe(UserService.name, () => {
}); });
it('update user information should throw error if user not found', async () => { it('update user information should throw error if user not found', async () => {
when(userMock.get).calledWith(userStub.user1.id, {}).mockResolvedValueOnce(null); userMock.get.mockResolvedValueOnce(null);
const result = sut.update(authStub.admin, { const result = sut.update(authStub.admin, {
id: userStub.user1.id, id: userStub.user1.id,
@ -217,7 +214,7 @@ describe(UserService.name, () => {
it('should let the admin update himself', async () => { it('should let the admin update himself', async () => {
const dto = { id: userStub.admin.id, shouldChangePassword: true, isAdmin: true }; const dto = { id: userStub.admin.id, shouldChangePassword: true, isAdmin: true };
when(userMock.update).calledWith(userStub.admin.id, dto).mockResolvedValueOnce(userStub.admin); userMock.update.mockResolvedValueOnce(userStub.admin);
await sut.update(authStub.admin, dto); await sut.update(authStub.admin, dto);
@ -227,7 +224,7 @@ describe(UserService.name, () => {
it('should not let the another user become an admin', async () => { it('should not let the another user become an admin', async () => {
const dto = { id: userStub.user1.id, shouldChangePassword: true, isAdmin: true }; const dto = { id: userStub.user1.id, shouldChangePassword: true, isAdmin: true };
when(userMock.get).calledWith(userStub.user1.id, {}).mockResolvedValueOnce(userStub.user1); userMock.get.mockResolvedValueOnce(userStub.user1);
await expect(sut.update(authStub.admin, dto)).rejects.toBeInstanceOf(BadRequestException); await expect(sut.update(authStub.admin, dto)).rejects.toBeInstanceOf(BadRequestException);
}); });
@ -235,7 +232,7 @@ describe(UserService.name, () => {
describe('restore', () => { describe('restore', () => {
it('should throw error if user could not be found', async () => { it('should throw error if user could not be found', async () => {
when(userMock.get).calledWith(userStub.admin.id, { withDeleted: true }).mockResolvedValue(null); userMock.get.mockResolvedValue(null);
await expect(sut.restore(authStub.admin, userStub.admin.id)).rejects.toThrowError(BadRequestException); await expect(sut.restore(authStub.admin, userStub.admin.id)).rejects.toThrowError(BadRequestException);
expect(userMock.update).not.toHaveBeenCalled(); expect(userMock.update).not.toHaveBeenCalled();
}); });
@ -298,7 +295,7 @@ describe(UserService.name, () => {
describe('create', () => { describe('create', () => {
it('should not create a user if there is no local admin account', async () => { it('should not create a user if there is no local admin account', async () => {
when(userMock.getAdmin).calledWith().mockResolvedValueOnce(null); userMock.getAdmin.mockResolvedValueOnce(null);
await expect( await expect(
sut.create({ sut.create({
@ -335,6 +332,7 @@ describe(UserService.name, () => {
describe('createProfileImage', () => { describe('createProfileImage', () => {
it('should throw an error if the user does not exist', async () => { it('should throw an error if the user does not exist', async () => {
const file = { path: '/profile/path' } as Express.Multer.File; const file = { path: '/profile/path' } as Express.Multer.File;
userMock.get.mockResolvedValue(null);
userMock.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path }); userMock.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path });
await expect(sut.createProfileImage(authStub.admin, file)).rejects.toThrowError(BadRequestException); await expect(sut.createProfileImage(authStub.admin, file)).rejects.toThrowError(BadRequestException);
@ -422,7 +420,7 @@ describe(UserService.name, () => {
describe('resetAdminPassword', () => { describe('resetAdminPassword', () => {
it('should only work when there is an admin account', async () => { it('should only work when there is an admin account', async () => {
userMock.getAdmin.mockResolvedValue(null); userMock.getAdmin.mockResolvedValue(null);
const ask = jest.fn().mockResolvedValue('new-password'); const ask = vitest.fn().mockResolvedValue('new-password');
await expect(sut.resetAdminPassword(ask)).rejects.toBeInstanceOf(BadRequestException); await expect(sut.resetAdminPassword(ask)).rejects.toBeInstanceOf(BadRequestException);
@ -431,7 +429,7 @@ describe(UserService.name, () => {
it('should default to a random password', async () => { it('should default to a random password', async () => {
userMock.getAdmin.mockResolvedValue(userStub.admin); userMock.getAdmin.mockResolvedValue(userStub.admin);
const ask = jest.fn().mockImplementation(() => {}); const ask = vitest.fn().mockImplementation(() => {});
const response = await sut.resetAdminPassword(ask); const response = await sut.resetAdminPassword(ask);
@ -445,7 +443,7 @@ describe(UserService.name, () => {
it('should use the supplied password', async () => { it('should use the supplied password', async () => {
userMock.getAdmin.mockResolvedValue(userStub.admin); userMock.getAdmin.mockResolvedValue(userStub.admin);
const ask = jest.fn().mockResolvedValue('new-password'); const ask = vitest.fn().mockResolvedValue('new-password');
const response = await sut.resetAdminPassword(ask); const response = await sut.resetAdminPassword(ask);

View file

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

View file

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

View file

@ -1,12 +1,13 @@
import { IKeyRepository } from 'src/interfaces/api-key.interface'; 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 { return {
create: jest.fn(), create: vitest.fn(),
update: jest.fn(), update: vitest.fn(),
delete: jest.fn(), delete: vitest.fn(),
getKey: jest.fn(), getKey: vitest.fn(),
getById: jest.fn(), getById: vitest.fn(),
getByUserId: jest.fn(), getByUserId: vitest.fn(),
}; };
}; };

View file

@ -1,10 +1,11 @@
import { IAssetStackRepository } from 'src/interfaces/asset-stack.interface'; 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 { return {
create: jest.fn(), create: vitest.fn(),
update: jest.fn(), update: vitest.fn(),
delete: jest.fn(), delete: vitest.fn(),
getById: jest.fn(), getById: vitest.fn(),
}; };
}; };

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,9 +1,10 @@
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; 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 { return {
encodeImage: jest.fn(), encodeImage: vitest.fn(),
encodeText: jest.fn(), encodeText: vitest.fn(),
detectFaces: jest.fn(), detectFaces: vitest.fn(),
}; };
}; };

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,12 +1,13 @@
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; 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 { return {
getAll: jest.fn(), getAll: vitest.fn(),
get: jest.fn(), get: vitest.fn(),
getByKey: jest.fn(), getByKey: vitest.fn(),
create: jest.fn(), create: vitest.fn(),
remove: jest.fn(), remove: vitest.fn(),
update: jest.fn(), update: vitest.fn(),
}; };
}; };

View file

@ -1,6 +1,7 @@
import { WatchOptions } from 'chokidar'; import { WatchOptions } from 'chokidar';
import { StorageCore } from 'src/cores/storage.core'; import { StorageCore } from 'src/cores/storage.core';
import { IStorageRepository, WatchEvents } from 'src/interfaces/storage.interface'; import { IStorageRepository, WatchEvents } from 'src/interfaces/storage.interface';
import { Mocked, vitest } from 'vitest';
interface MockWatcherOptions { interface MockWatcherOptions {
items?: Array<{ event: 'change' | 'add' | 'unlink' | 'error'; value: string }>; items?: Array<{ event: 'change' | 'add' | 'unlink' | 'error'; value: string }>;
@ -38,29 +39,29 @@ export const makeMockWatcher =
return () => Promise.resolve(); return () => Promise.resolve();
}; };
export const newStorageRepositoryMock = (reset = true): jest.Mocked<IStorageRepository> => { export const newStorageRepositoryMock = (reset = true): Mocked<IStorageRepository> => {
if (reset) { if (reset) {
StorageCore.reset(); StorageCore.reset();
} }
return { return {
createZipStream: jest.fn(), createZipStream: vitest.fn(),
createReadStream: jest.fn(), createReadStream: vitest.fn(),
readFile: jest.fn(), readFile: vitest.fn(),
writeFile: jest.fn(), writeFile: vitest.fn(),
unlink: jest.fn(), unlink: vitest.fn(),
unlinkDir: jest.fn().mockResolvedValue(true), unlinkDir: vitest.fn().mockResolvedValue(true),
removeEmptyDirs: jest.fn(), removeEmptyDirs: vitest.fn(),
checkFileExists: jest.fn(), checkFileExists: vitest.fn(),
mkdirSync: jest.fn(), mkdirSync: vitest.fn(),
checkDiskUsage: jest.fn(), checkDiskUsage: vitest.fn(),
readdir: jest.fn(), readdir: vitest.fn(),
stat: jest.fn(), stat: vitest.fn(),
crawl: jest.fn(), crawl: vitest.fn(),
walk: jest.fn().mockImplementation(async function* () {}), walk: vitest.fn().mockImplementation(async function* () {}),
rename: jest.fn(), rename: vitest.fn(),
copyFile: jest.fn(), copyFile: vitest.fn(),
utimes: jest.fn(), utimes: vitest.fn(),
watch: jest.fn().mockImplementation(makeMockWatcher({})), watch: vitest.fn().mockImplementation(makeMockWatcher({})),
}; };
}; };

View file

@ -1,16 +1,17 @@
import { SystemConfigCore } from 'src/cores/system-config.core'; import { SystemConfigCore } from 'src/cores/system-config.core';
import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; 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) { if (reset) {
SystemConfigCore.reset(); SystemConfigCore.reset();
} }
return { return {
fetchStyle: jest.fn(), fetchStyle: vitest.fn(),
load: jest.fn().mockResolvedValue([]), load: vitest.fn().mockResolvedValue([]),
readFile: jest.fn(), readFile: vitest.fn(),
saveAll: jest.fn().mockResolvedValue([]), saveAll: vitest.fn().mockResolvedValue([]),
deleteKeys: jest.fn(), deleteKeys: vitest.fn(),
}; };
}; };

View file

@ -1,7 +1,8 @@
import { IServerInfoRepository } from 'src/interfaces/server-info.interface'; 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 { return {
getGitHubRelease: jest.fn(), getGitHubRelease: vitest.fn(),
}; };
}; };

View file

@ -1,8 +1,9 @@
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; 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 { return {
get: jest.fn(), get: vitest.fn() as any,
set: jest.fn(), set: vitest.fn(),
}; };
}; };

View file

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

View file

@ -1,11 +1,12 @@
import { IUserTokenRepository } from 'src/interfaces/user-token.interface'; 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 { return {
create: jest.fn(), create: vitest.fn(),
save: jest.fn(), save: vitest.fn(),
delete: jest.fn(), delete: vitest.fn(),
getByToken: jest.fn(), getByToken: vitest.fn(),
getAll: jest.fn(), getAll: vitest.fn(),
}; };
}; };

View file

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

View file

@ -17,10 +17,7 @@
"esModuleInterop": true, "esModuleInterop": true,
"preserveWatchOutput": true, "preserveWatchOutput": true,
"baseUrl": "./", "baseUrl": "./",
"types": ["vitest/globals"]
}, },
"exclude": [ "exclude": ["dist", "node_modules", "upload"]
"dist", }
"node_modules",
"upload"
],
}

15
server/vitest.config.mjs Normal file
View file

@ -0,0 +1,15 @@
import swc from 'unplugin-swc';
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
root: './',
globals: true,
server: {
deps: {
fallbackCJS: true,
},
},
},
plugins: [swc.vite()],
});