1
0
mirror of https://github.com/immich-app/immich.git synced 2024-11-24 08:52:28 +02:00

chore(server): eslint await-thenable (#7545)

* await-thenable

* fix library watchers

* moar eslint

* fix test

* fix typo

* try to remove check void return

* fix checksVoidReturn

* move to domain utils

* remove eslint ignores

* chore: cleanup types

* chore: use logger

* fix: e2e

---------

Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
Jonathan Jogenfors 2024-03-05 23:23:06 +01:00 committed by GitHub
parent 972d5a3411
commit 5d377e5b0f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 133 additions and 110 deletions

View File

@ -25,6 +25,12 @@ module.exports = {
'unicorn/prefer-top-level-await': 'off',
'unicorn/prefer-event-target': 'off',
'unicorn/no-thenable': 'off',
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
// Note: you must disable the base rule as it can report incorrect errors
'require-await': 'off',
'@typescript-eslint/require-await': 'error',
curly: 2,
'prettier/prettier': 0,
},

View File

@ -208,7 +208,7 @@ describe(`Library watcher (e2e)`, () => {
await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir3`, { recursive: true });
});
it('should use an updated import paths', async () => {
it('should use an updated import path', async () => {
await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir4`, { recursive: true });
await api.libraryApi.setImportPaths(server, admin.accessToken, library.id, [

View File

@ -11,7 +11,7 @@ describe(ActivityService.name, () => {
let accessMock: IAccessRepositoryMock;
let activityMock: jest.Mocked<IActivityRepository>;
beforeEach(async () => {
beforeEach(() => {
accessMock = newAccessRepositoryMock();
activityMock = newActivityRepositoryMock();

View File

@ -23,7 +23,7 @@ describe(AlbumService.name, () => {
let jobMock: jest.Mocked<IJobRepository>;
let userMock: jest.Mocked<IUserRepository>;
beforeEach(async () => {
beforeEach(() => {
accessMock = newAccessRepositoryMock();
albumMock = newAlbumRepositoryMock();
assetMock = newAssetRepositoryMock();

View File

@ -8,7 +8,7 @@ describe(APIKeyService.name, () => {
let keyMock: jest.Mocked<IKeyRepository>;
let cryptoMock: jest.Mocked<ICryptoRepository>;
beforeEach(async () => {
beforeEach(() => {
cryptoMock = newCryptoRepositoryMock();
keyMock = newKeyRepositoryMock();
sut = new APIKeyService(cryptoMock, keyMock);

View File

@ -169,7 +169,7 @@ describe(AssetService.name, () => {
expect(sut).toBeDefined();
});
beforeEach(async () => {
beforeEach(() => {
accessMock = newAccessRepositoryMock();
assetMock = newAssetRepositoryMock();
communicationMock = newCommunicationRepositoryMock();

View File

@ -31,7 +31,7 @@ describe(AuditService.name, () => {
let storageMock: jest.Mocked<IStorageRepository>;
let userMock: jest.Mocked<IUserRepository>;
beforeEach(async () => {
beforeEach(() => {
accessMock = newAccessRepositoryMock();
assetMock = newAssetRepositoryMock();
cryptoMock = newCryptoRepositoryMock();

View File

@ -74,7 +74,7 @@ describe('AuthService', () => {
let callbackMock: jest.Mock;
let userinfoMock: jest.Mock;
beforeEach(async () => {
beforeEach(() => {
callbackMock = jest.fn().mockReturnValue({ access_token: 'access-token' });
userinfoMock = jest.fn().mockResolvedValue({ sub, email });

View File

@ -13,7 +13,7 @@ describe(DatabaseService.name, () => {
let sut: DatabaseService;
let databaseMock: jest.Mocked<IDatabaseRepository>;
beforeEach(async () => {
beforeEach(() => {
databaseMock = newDatabaseRepositoryMock();
sut = new DatabaseService(databaseMock);
@ -31,7 +31,7 @@ describe(DatabaseService.name, () => {
let errorLog: jest.SpyInstance;
let warnLog: jest.SpyInstance;
beforeEach(async () => {
beforeEach(() => {
fatalLog = jest.spyOn(ImmichLogger.prototype, 'fatal');
errorLog = jest.spyOn(ImmichLogger.prototype, 'error');
warnLog = jest.spyOn(ImmichLogger.prototype, 'warn');

View File

@ -1,6 +1,5 @@
import { ImmichLogger } from '@app/infra/logger';
import { Inject, Injectable } from '@nestjs/common';
import { QueryFailedError } from 'typeorm';
import { Version, VersionType } from '../domain.constant';
import {
DatabaseExtension,
@ -61,7 +60,9 @@ export class DatabaseService {
}
private async createVectorExtension() {
await this.databaseRepository.createExtension(this.vectorExt).catch(async (error: QueryFailedError) => {
try {
await this.databaseRepository.createExtension(this.vectorExt);
} catch (error) {
const otherExt =
this.vectorExt === DatabaseExtension.VECTORS ? DatabaseExtension.VECTOR : DatabaseExtension.VECTORS;
this.logger.fatal(`
@ -78,7 +79,7 @@ export class DatabaseService {
In this case, you may set either extension now, but you will not be able to switch to the other extension following a successful startup.
`);
throw error;
});
}
}
private async updateVectorExtension() {

View File

@ -1,3 +1,4 @@
import { ImmichLogger } from '@app/infra/logger';
import { applyDecorators } from '@nestjs/common';
import { ApiProperty } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
@ -157,7 +158,7 @@ export type Paginated<T> = Promise<PaginationResult<T>>;
export async function* usePagination<T>(
pageSize: number,
getNextPage: (pagination: PaginationOptions) => Paginated<T>,
getNextPage: (pagination: PaginationOptions) => PaginationResult<T> | Paginated<T>,
) {
let hasNextPage = true;
@ -252,3 +253,7 @@ export const setIsSuperset = <T>(set: Set<T>, subset: Set<T>): boolean => {
export const setIsEqual = <T>(setA: Set<T>, setB: Set<T>): boolean => {
return setA.size === setB.size && setIsSuperset(setA, setB);
};
export const handlePromiseError = <T>(promise: Promise<T>, logger: ImmichLogger): void => {
promise.catch((error: Error | any) => logger.error(`Promise error: ${error}`, error?.stack));
};

View File

@ -34,7 +34,7 @@ describe(DownloadService.name, () => {
expect(sut).toBeDefined();
});
beforeEach(async () => {
beforeEach(() => {
accessMock = newAccessRepositoryMock();
assetMock = newAssetRepositoryMock();
storageMock = newStorageRepositoryMock();

View File

@ -114,9 +114,7 @@ export class DownloadService {
const assetIds = dto.assetIds;
await this.access.requirePermission(auth, Permission.ASSET_DOWNLOAD, assetIds);
const assets = await this.assetRepository.getByIds(assetIds);
return (async function* () {
yield assets;
})();
return usePagination(PAGINATION_SIZE, () => ({ hasNextPage: false, items: assets }));
}
if (dto.albumId) {

View File

@ -37,7 +37,7 @@ describe(JobService.name, () => {
let jobMock: jest.Mocked<IJobRepository>;
let personMock: jest.Mocked<IPersonRepository>;
beforeEach(async () => {
beforeEach(() => {
assetMock = newAssetRepositoryMock();
configMock = newSystemConfigRepositoryMock();
communicationMock = newCommunicationRepositoryMock();

View File

@ -17,6 +17,7 @@ import {
systemConfigStub,
userStub,
} from '@test';
import { when } from 'jest-when';
import { Stats } from 'node:fs';
import { ILibraryFileJob, ILibraryRefreshJob, JobName } from '../job';
import {
@ -55,7 +56,7 @@ describe(LibraryService.name, () => {
storageMock = newStorageRepositoryMock();
// Always validate owner access for library.
accessMock.library.checkOwnerAccess.mockImplementation(async (_, libraryIds) => libraryIds);
accessMock.library.checkOwnerAccess.mockImplementation((_, libraryIds) => Promise.resolve(libraryIds));
sut = new LibraryService(
accessMock,
@ -106,19 +107,13 @@ describe(LibraryService.name, () => {
configMock.load.mockResolvedValue(systemConfigStub.libraryWatchEnabled);
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
libraryMock.get.mockImplementation(async (id) => {
switch (id) {
case libraryStub.externalLibraryWithImportPaths1.id: {
return libraryStub.externalLibraryWithImportPaths1;
}
case libraryStub.externalLibraryWithImportPaths2.id: {
return libraryStub.externalLibraryWithImportPaths2;
}
default: {
return null;
}
}
});
when(libraryMock.get)
.calledWith(libraryStub.externalLibraryWithImportPaths1.id)
.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1);
when(libraryMock.get)
.calledWith(libraryStub.externalLibraryWithImportPaths2.id)
.mockResolvedValue(libraryStub.externalLibraryWithImportPaths2);
await sut.init();
@ -1278,19 +1273,13 @@ describe(LibraryService.name, () => {
configMock.load.mockResolvedValue(systemConfigStub.libraryWatchEnabled);
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
libraryMock.get.mockImplementation(async (id) => {
switch (id) {
case libraryStub.externalLibraryWithImportPaths1.id: {
return libraryStub.externalLibraryWithImportPaths1;
}
case libraryStub.externalLibraryWithImportPaths2.id: {
return libraryStub.externalLibraryWithImportPaths2;
}
default: {
return null;
}
}
});
when(libraryMock.get)
.calledWith(libraryStub.externalLibraryWithImportPaths1.id)
.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1);
when(libraryMock.get)
.calledWith(libraryStub.externalLibraryWithImportPaths2.id)
.mockResolvedValue(libraryStub.externalLibraryWithImportPaths2);
const mockClose = jest.fn();
storageMock.watch.mockImplementation(makeMockWatcher({ close: mockClose }));
@ -1304,9 +1293,8 @@ describe(LibraryService.name, () => {
describe('handleDeleteLibrary', () => {
it('should not delete a nonexistent library', async () => {
libraryMock.get.mockImplementation(async () => {
return null;
});
libraryMock.get.mockResolvedValue(null);
libraryMock.getAssetIds.mockResolvedValue([]);
libraryMock.delete.mockImplementation(async () => {});

View File

@ -9,7 +9,7 @@ import picomatch from 'picomatch';
import { AccessCore, Permission } from '../access';
import { AuthDto } from '../auth';
import { mimeTypes } from '../domain.constant';
import { usePagination, validateCronExpression } from '../domain.util';
import { handlePromiseError, usePagination, validateCronExpression } from '../domain.util';
import { IBaseJob, IEntityJob, ILibraryFileJob, ILibraryRefreshJob, JOBS_ASSET_PAGINATION_SIZE, JobName } from '../job';
import {
@ -43,7 +43,7 @@ export class LibraryService extends EventEmitter {
private access: AccessCore;
private configCore: SystemConfigCore;
private watchLibraries = false;
private watchers: Record<string, () => void> = {};
private watchers: Record<string, () => Promise<void>> = {};
constructor(
@Inject(IAccessRepository) accessRepository: IAccessRepository,
@ -73,7 +73,11 @@ export class LibraryService extends EventEmitter {
this.jobRepository.addCronJob(
'libraryScan',
scan.cronExpression,
() => this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SCAN_ALL, data: { force: false } }),
() =>
handlePromiseError(
this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SCAN_ALL, data: { force: false } }),
this.logger,
),
scan.enabled,
);
@ -81,12 +85,12 @@ export class LibraryService extends EventEmitter {
await this.watchAll();
}
this.configCore.config$.subscribe(async ({ library }) => {
this.configCore.config$.subscribe(({ library }) => {
this.jobRepository.updateCronJob('libraryScan', library.scan.cronExpression, library.scan.enabled);
if (library.watch.enabled !== this.watchLibraries) {
this.watchLibraries = library.watch.enabled;
await (this.watchLibraries ? this.watchAll() : this.unwatchAll());
handlePromiseError(this.watchLibraries ? this.watchAll() : this.unwatchAll(), this.logger);
}
});
}
@ -124,28 +128,37 @@ export class LibraryService extends EventEmitter {
},
{
onReady: () => _resolve(),
onAdd: async (path) => {
this.logger.debug(`File add event received for ${path} in library ${library.id}}`);
if (matcher(path)) {
await this.scanAssets(library.id, [path], library.ownerId, false);
}
this.emit('add', path);
onAdd: (path) => {
const handler = async () => {
this.logger.debug(`File add event received for ${path} in library ${library.id}}`);
if (matcher(path)) {
await this.scanAssets(library.id, [path], library.ownerId, false);
}
this.emit('add', path);
};
return handlePromiseError(handler(), this.logger);
},
onChange: async (path) => {
this.logger.debug(`Detected file change for ${path} in library ${library.id}`);
if (matcher(path)) {
// Note: if the changed file was not previously imported, it will be imported now.
await this.scanAssets(library.id, [path], library.ownerId, false);
}
this.emit('change', path);
onChange: (path) => {
const handler = async () => {
this.logger.debug(`Detected file change for ${path} in library ${library.id}`);
if (matcher(path)) {
// Note: if the changed file was not previously imported, it will be imported now.
await this.scanAssets(library.id, [path], library.ownerId, false);
}
this.emit('change', path);
};
return handlePromiseError(handler(), this.logger);
},
onUnlink: async (path) => {
this.logger.debug(`Detected deleted file at ${path} in library ${library.id}`);
const asset = await this.assetRepository.getByLibraryIdAndOriginalPath(library.id, path);
if (asset && matcher(path)) {
await this.assetRepository.save({ id: asset.id, isOffline: true });
}
this.emit('unlink', path);
onUnlink: (path) => {
const handler = async () => {
this.logger.debug(`Detected deleted file at ${path} in library ${library.id}`);
const asset = await this.assetRepository.getByLibraryIdAndOriginalPath(library.id, path);
if (asset && matcher(path)) {
await this.assetRepository.save({ id: asset.id, isOffline: true });
}
this.emit('unlink', path);
};
return handlePromiseError(handler(), this.logger);
},
onError: (error) => {
// TODO: should we log, or throw an exception?

View File

@ -48,7 +48,7 @@ describe(MediaService.name, () => {
let storageMock: jest.Mocked<IStorageRepository>;
let cryptoMock: jest.Mocked<ICryptoRepository>;
beforeEach(async () => {
beforeEach(() => {
assetMock = newAssetRepositoryMock();
configMock = newSystemConfigRepositoryMock();
jobMock = newJobRepositoryMock();

View File

@ -56,7 +56,7 @@ describe(MetadataService.name, () => {
let databaseMock: jest.Mocked<IDatabaseRepository>;
let sut: MetadataService;
beforeEach(async () => {
beforeEach(() => {
albumMock = newAlbumRepositoryMock();
assetMock = newAssetRepositoryMock();
configMock = newSystemConfigRepositoryMock();

View File

@ -7,7 +7,7 @@ import _ from 'lodash';
import { Duration } from 'luxon';
import { constants } from 'node:fs/promises';
import { Subscription } from 'rxjs';
import { usePagination } from '../domain.util';
import { handlePromiseError, usePagination } from '../domain.util';
import { IBaseJob, IEntityJob, ISidecarWriteJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job';
import {
ClientEvent,
@ -124,7 +124,7 @@ export class MetadataService {
async init() {
if (!this.subscription) {
this.subscription = this.configCore.config$.subscribe(() => this.init());
this.subscription = this.configCore.config$.subscribe(() => handlePromiseError(this.init(), this.logger));
}
const { reverseGeocoding } = await this.configCore.getConfig();

View File

@ -49,7 +49,7 @@ describe(PartnerService.name, () => {
let partnerMock: jest.Mocked<IPartnerRepository>;
let accessMock: jest.Mocked<IAccessRepository>;
beforeEach(async () => {
beforeEach(() => {
partnerMock = newPartnerRepositoryMock();
sut = new PartnerService(partnerMock, accessMock);
});

View File

@ -80,7 +80,7 @@ describe(PersonService.name, () => {
let cryptoMock: jest.Mocked<ICryptoRepository>;
let sut: PersonService;
beforeEach(async () => {
beforeEach(() => {
accessMock = newAccessRepositoryMock();
assetMock = newAssetRepositoryMock();
configMock = newSystemConfigRepositoryMock();

View File

@ -34,7 +34,7 @@ export interface ClientEventMap {
[ClientEvent.NEW_RELEASE]: ReleaseNotification;
}
export type OnConnectCallback = (userId: string) => Promise<void>;
export type OnConnectCallback = (userId: string) => void | Promise<void>;
export type OnServerEventCallback = () => Promise<void>;
export interface ICommunicationRepository {

View File

@ -47,6 +47,6 @@ export interface IStorageRepository {
crawl(crawlOptions: CrawlOptionsDto): Promise<string[]>;
copyFile(source: string, target: string): Promise<void>;
rename(source: string, target: string): Promise<void>;
watch(paths: string[], options: WatchOptions, events: Partial<WatchEvents>): () => void;
watch(paths: string[], options: WatchOptions, events: Partial<WatchEvents>): () => Promise<void>;
utimes(filepath: string, atime: Date, mtime: Date): Promise<void>;
}

View File

@ -181,7 +181,7 @@ export class SearchService {
return userIds;
}
private async mapResponse(assets: AssetEntity[], nextPage: string | null): Promise<SearchResponseDto> {
private mapResponse(assets: AssetEntity[], nextPage: string | null): SearchResponseDto {
return {
albums: { total: 0, count: 0, items: [], facets: [] },
assets: {

View File

@ -170,7 +170,7 @@ export class ServerInfoService {
return true;
}
private async handleConnect(userId: string) {
private handleConnect(userId: string) {
this.communicationRepository.send(ClientEvent.SERVER_VERSION, userId, serverVersion);
this.newReleaseNotification(userId);
}

View File

@ -22,7 +22,7 @@ describe(SharedLinkService.name, () => {
let cryptoMock: jest.Mocked<ICryptoRepository>;
let shareMock: jest.Mocked<ISharedLinkRepository>;
beforeEach(async () => {
beforeEach(() => {
accessMock = newAccessRepositoryMock();
cryptoMock = newCryptoRepositoryMock();
shareMock = newSharedLinkRepositoryMock();

View File

@ -35,7 +35,7 @@ describe(SmartInfoService.name, () => {
let machineMock: jest.Mocked<IMachineLearningRepository>;
let databaseMock: jest.Mocked<IDatabaseRepository>;
beforeEach(async () => {
beforeEach(() => {
assetMock = newAssetRepositoryMock();
configMock = newSystemConfigRepositoryMock();
searchMock = newSearchRepositoryMock();

View File

@ -45,7 +45,7 @@ describe(StorageTemplateService.name, () => {
expect(sut).toBeDefined();
});
beforeEach(async () => {
beforeEach(() => {
configMock = newSystemConfigRepositoryMock();
assetMock = newAssetRepositoryMock();
albumMock = newAlbumRepositoryMock();

View File

@ -6,7 +6,7 @@ describe(StorageService.name, () => {
let sut: StorageService;
let storageMock: jest.Mocked<IStorageRepository>;
beforeEach(async () => {
beforeEach(() => {
storageMock = newStorageRepositoryMock();
sut = new StorageService(storageMock);
});

View File

@ -148,7 +148,7 @@ describe(SystemConfigService.name, () => {
let communicationMock: jest.Mocked<ICommunicationRepository>;
let smartInfoMock: jest.Mocked<ISearchRepository>;
beforeEach(async () => {
beforeEach(() => {
delete process.env.IMMICH_CONFIG_FILE;
configMock = newSystemConfigRepositoryMock();
communicationMock = newCommunicationRepositoryMock();

View File

@ -118,7 +118,7 @@ export class SystemConfigService {
await this.core.refreshConfig();
}
private async setLogLevel({ logging }: SystemConfig) {
private setLogLevel({ logging }: SystemConfig) {
const envLevel = this.getEnvLogLevel();
const configLevel = logging.enabled ? logging.level : false;
const level = envLevel ?? configLevel;
@ -130,7 +130,7 @@ export class SystemConfigService {
return process.env.LOG_LEVEL as LogLevel;
}
private async validateConfig(newConfig: SystemConfig, oldConfig: SystemConfig) {
private validateConfig(newConfig: SystemConfig, oldConfig: SystemConfig) {
if (!_.isEqual(instanceToPlain(newConfig.logging), oldConfig.logging) && this.getEnvLogLevel()) {
throw new Error('Logging cannot be changed while the environment variable LOG_LEVEL is set.');
}

View File

@ -23,7 +23,7 @@ describe(TrashService.name, () => {
expect(sut).toBeDefined();
});
beforeEach(async () => {
beforeEach(() => {
accessMock = newAccessRepositoryMock();
assetMock = newAssetRepositoryMock();
communicationMock = newCommunicationRepositoryMock();

View File

@ -49,7 +49,7 @@ describe(UserService.name, () => {
let libraryMock: jest.Mocked<ILibraryRepository>;
let storageMock: jest.Mocked<IStorageRepository>;
beforeEach(async () => {
beforeEach(() => {
albumMock = newAlbumRepositoryMock();
assetMock = newAssetRepositoryMock();
cryptoRepositoryMock = newCryptoRepositoryMock();

View File

@ -15,7 +15,7 @@ import { routeToErrorMessage } from '../app.utils';
export class ErrorInterceptor implements NestInterceptor {
private logger = new ImmichLogger(ErrorInterceptor.name);
async intercept(context: ExecutionContext, next: CallHandler<any>): Promise<Observable<any>> {
intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> {
return next.handle().pipe(
catchError((error) =>
throwError(() => {

View File

@ -40,9 +40,9 @@ interface Callback<T> {
(error: null, result: T): void;
}
const callbackify = async <T>(target: (...arguments_: any[]) => T, callback: Callback<T>) => {
const callbackify = <T>(target: (...arguments_: any[]) => T, callback: Callback<T>) => {
try {
return callback(null, await target());
return callback(null, target());
} catch (error: Error | any) {
return callback(error);
}

View File

@ -544,7 +544,7 @@ export class AssetRepository implements IAssetRepository {
}
async getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats> {
let builder = await this.repository
let builder = this.repository
.createQueryBuilder('asset')
.select(`COUNT(asset.id)`, 'count')
.addSelect(`asset.type`, 'type')

View File

@ -166,7 +166,7 @@ export class LibraryRepository implements ILibraryRepository {
@GenerateSql({ params: [DummyValue.UUID] })
async getAssetIds(libraryId: string, withDeleted = false): Promise<string[]> {
let query = await this.repository
let query = this.repository
.createQueryBuilder('library')
.innerJoinAndSelect('library.assets', 'assets')
.where('library.id = :id', { id: libraryId })

View File

@ -1,4 +1,11 @@
import { CropOptions, IMediaRepository, ResizeOptions, TranscodeOptions, VideoInfo } from '@app/domain';
import {
CropOptions,
IMediaRepository,
ResizeOptions,
TranscodeOptions,
VideoInfo,
handlePromiseError,
} from '@app/domain';
import { Colorspace } from '@app/infra/entities';
import { ImmichLogger } from '@app/infra/logger';
import ffmpeg, { FfprobeData } from 'fluent-ffmpeg';
@ -99,8 +106,8 @@ export class MediaRepository implements IMediaRepository {
.addOptions('-pass', '2')
.addOptions('-passlogfile', output)
.on('error', reject)
.on('end', () => fs.unlink(`${output}-0.log`))
.on('end', () => fs.rm(`${output}-0.log.mbtree`, { force: true }))
.on('end', () => handlePromiseError(fs.unlink(`${output}-0.log`), this.logger))
.on('end', () => handlePromiseError(fs.rm(`${output}-0.log.mbtree`, { force: true }), this.logger))
.on('end', resolve)
.run();
})

View File

@ -75,22 +75,22 @@ class JobMock implements IJobRepository {
async resume() {}
async empty() {}
async setConcurrency() {}
async getQueueStatus() {
return null as any;
getQueueStatus() {
return Promise.resolve(null) as any;
}
async getJobCounts() {
return null as any;
getJobCounts() {
return Promise.resolve(null) as any;
}
async pause() {}
async clear() {
return [];
clear() {
return Promise.resolve([]);
}
async waitForQueueCompletion() {}
}
class MediaMockRepository extends MediaRepository {
async generateThumbhash() {
return Buffer.from('mock-thumbhash');
generateThumbhash() {
return Promise.resolve(Buffer.from('mock-thumbhash'));
}
}

View File

@ -3,7 +3,7 @@ import { WatchOptions } from 'chokidar';
interface MockWatcherOptions {
items?: Array<{ event: 'change' | 'add' | 'unlink' | 'error'; value: string }>;
close?: () => void;
close?: () => Promise<void>;
}
export const makeMockWatcher =
@ -29,7 +29,12 @@ export const makeMockWatcher =
}
}
}
return () => close?.();
if (close) {
return () => close();
}
return () => Promise.resolve();
};
export const newStorageRepositoryMock = (reset = true): jest.Mocked<IStorageRepository> => {