update unit tests and mocks

This commit is contained in:
Hazelnoot 2025-10-08 17:09:52 -04:00
parent 9b843181f8
commit effdea5658
11 changed files with 123 additions and 121 deletions

View file

@ -3,9 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import type { LimitInfo } from '@/misc/rate-limit-utils.js';
import { SkRateLimiterService } from '@/server/SkRateLimiterService.js';
import { Injectable } from '@nestjs/common';
/**
* Fake implementation of SkRateLimiterService that does not enforce any limits.

View file

@ -0,0 +1,63 @@
/*
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { jest } from '@jest/globals';
import { Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js';
/**
* Console implementation where all members are jest mocks.
*/
@Injectable()
export class MockConsole implements Console {
public readonly Console = MockConsole;
/**
* Resets all mocks in the console.
*/
@bindThis
public mockReset(): void {
for (const func of Object.values(this)) {
if (typeof(func) === 'function' && 'mockReset' in func) {
func.mockReset();
}
}
}
/**
* Asserts that no errors and/or warnings have been logged.
*/
@bindThis
public assertNoErrors(opts?: { orWarnings?: boolean }): void {
expect(this.error).not.toHaveBeenCalled();
if (opts?.orWarnings) {
expect(this.warn).not.toHaveBeenCalled();
}
}
public readonly error = jest.fn<Console['error']>();
public readonly warn = jest.fn<Console['warn']>();
public readonly info = jest.fn<Console['info']>();
public readonly log = jest.fn<Console['log']>();
public readonly debug = jest.fn<Console['debug']>();
public readonly trace = jest.fn<Console['trace']>();
public readonly assert = jest.fn<Console['assert']>();
public readonly clear = jest.fn<Console['clear']>();
public readonly count = jest.fn<Console['count']>();
public readonly countReset = jest.fn<Console['countReset']>();
public readonly dir = jest.fn<Console['dir']>();
public readonly dirxml = jest.fn<Console['dirxml']>();
public readonly group = jest.fn<Console['group']>();
public readonly groupCollapsed = jest.fn<Console['groupCollapsed']>();
public readonly groupEnd = jest.fn<Console['groupEnd']>();
public readonly table = jest.fn<Console['table']>();
public readonly time = jest.fn<Console['time']>();
public readonly timeEnd = jest.fn<Console['timeEnd']>();
public readonly timeLog = jest.fn<Console['timeLog']>();
public readonly profile = jest.fn<Console['profile']>();
public readonly profileEnd = jest.fn<Console['profileEnd']>();
public readonly timeStamp = jest.fn<Console['timeStamp']>();
}

View file

@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { DependencyService } from '@/global/DependencyService.js';
import { bindThis } from '@/decorators.js';
/**
* Extension of DependencyService that allows version information to be mocked.
*/
@Injectable()
export class MockDependencyService extends DependencyService {
/**
* Overrides the version for a dependency.
* Pass a string or null to override the version, or pass undefined to clear the override and restore the original value.
*/
@bindThis
public setDependencyVersion(dependency: string, version: string | null | undefined) {
if (version !== undefined) {
this.dependencyVersionCache.set(dependency, version);
} else {
this.dependencyVersionCache.delete(dependency);
}
}
/**
* Resets the mock to initial values.
*/
@bindThis
public mockReset(): void {
this.dependencyVersionCache.clear();
}
}

View file

@ -6,7 +6,6 @@
import process from 'node:process';
import { Injectable } from '@nestjs/common';
import { EnvService } from '@/global/EnvService.js';
import { CacheManagementService } from '@/global/CacheManagementService.js';
import { bindThis } from '@/decorators.js';
/**
@ -15,12 +14,7 @@ import { bindThis } from '@/decorators.js';
*/
@Injectable()
export class MockEnvService extends EnvService {
private _env: Partial<Record<string, string>>;
constructor(cacheManagementService: CacheManagementService) {
super(cacheManagementService);
this._env = process.env;
}
private _env: Partial<Record<string, string>> = process.env;
/**
* Gets the mocked environment.
@ -42,25 +36,11 @@ export class MockEnvService extends EnvService {
}
}
/**
* Overrides the version for a dependency.
* Pass a string or null to override the version, or pass undefined to clear the override and restore the original value.
*/
@bindThis
public setDependencyVersion(dependency: string, version: string | null | undefined) {
if (version !== undefined) {
this.dependencyVersionCache.set(dependency, version);
} else {
this.dependencyVersionCache.delete(dependency);
}
}
/**
* Resets the mock to initial values.
*/
@bindThis
public mockReset(): void {
this._env = process.env;
this.dependencyVersionCache.clear();
}
}

View file

@ -1,76 +0,0 @@
/*
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { jest } from '@jest/globals';
import { Injectable } from '@nestjs/common';
import type { KEYWORD } from 'color-convert/conversions.js';
import type { Config } from '@/config.js';
import Logger, { type Console } from '@/logger.js';
import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
import { NativeTimeService, TimeService } from '@/global/TimeService.js';
/**
* Mocked implementation of LoggerService.
* Suppresses all log output to prevent console spam, and records calls for assertions.
*/
@Injectable()
export class MockLoggerService extends LoggerService {
/**
* Mocked Console implementation.
* All logs from all logger instances will be sent here.
*/
public readonly console: jest.Mocked<Console> = {
error: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
log: jest.fn(),
debug: jest.fn(),
};
/**
* Controls the verbose flag for logger instances.
* Defaults to false (not verbose).
*/
public verbose: boolean;
constructor(config?: Config, timeService?: TimeService) {
config ??= { logging: { verbose: false } } as Config;
timeService ??= new NativeTimeService();
super(config, timeService);
}
/**
* Resets the instance to initial state.
* Mocks are reset, and verbose flag is cleared.
*/
@bindThis
public reset() {
this.console.error.mockReset();
this.console.warn.mockReset();
this.console.info.mockReset();
this.console.log.mockReset();
this.console.debug.mockReset();
this.verbose = false;
}
/**
* Asserts that no errors and/or warnings have been logged.
*/
@bindThis
public assertNoErrors(opts?: { orWarnings?: boolean }): void {
expect(this.console.error).not.toHaveBeenCalled();
if (opts?.orWarnings) {
expect(this.console.warn).not.toHaveBeenCalled();
}
}
@bindThis
getLogger(domain: string, color?: KEYWORD | undefined): Logger {
return new Logger(domain, color, this.verbose, this.console);
}
}

View file

@ -4,14 +4,14 @@
*/
import { Inject } from '@nestjs/common';
import { MockLoggerService } from './MockLoggerService.js';
import { MockConsole } from './MockConsole.js';
import { MockEnvService } from './MockEnvService.js';
import type { Config } from '@/config.js';
import type { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
import type { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import type { ApRequestService } from '@/core/activitypub/ApRequestService.js';
import type { IObject, IObjectWithId } from '@/core/activitypub/type.js';
import type { HttpRequestService } from '@/core/HttpRequestService.js';
import type { LoggerService } from '@/core/LoggerService.js';
import type { UtilityService } from '@/core/UtilityService.js';
import type {
FollowRequestsRepository,
@ -23,6 +23,7 @@ import type {
} from '@/models/_.js';
import type { CacheService } from '@/core/CacheService.js';
import { ApLogService } from '@/core/ApLogService.js';
import { LoggerService } from '@/core/LoggerService.js';
import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js';
import { fromTuple } from '@/misc/from-tuple.js';
import { SystemAccountService } from '@/core/SystemAccountService.js';
@ -30,6 +31,7 @@ import { bindThis } from '@/decorators.js';
import { Resolver } from '@/core/activitypub/ApResolverService.js';
import { DI } from '@/di-symbols.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { NativeTimeService } from '@/global/TimeService.js';
type MockResponse = {
type: string;
@ -88,7 +90,7 @@ export class MockResolver extends Resolver {
httpRequestService ?? {} as HttpRequestService,
apRendererService ?? {} as ApRendererService,
apDbResolverService ?? {} as ApDbResolverService,
loggerService ?? new MockLoggerService(),
loggerService ?? new LoggerService(new MockConsole(), new NativeTimeService(), new MockEnvService()),
apLogService ?? {} as ApLogService,
apUtilityService ?? {} as ApUtilityService,
cacheService ?? {} as CacheService,

View file

@ -4,7 +4,6 @@
*/
import * as assert from 'assert';
import { FakeCacheManagementService } from '../misc/FakeCacheManagementService.js';
import type { MiMeta } from '@/models/_.js';
import type { Config } from '@/config.js';
import type { SoftwareSuspension } from '@/models/Meta.js';
@ -32,7 +31,7 @@ describe('UtilityService', () => {
federation: 'all',
} as unknown as MiMeta;
const envService = new EnvService(new FakeCacheManagementService());
const envService = new EnvService();
utilityService = new UtilityService(config, meta, envService);
});

View file

@ -10,7 +10,7 @@ import { generateKeyPair } from 'crypto';
import { Test, TestingModule } from '@nestjs/testing';
import { jest } from '@jest/globals';
import { MockApResolverService } from '../misc/MockApResolverService.js';
import { MockLoggerService } from '../misc/MockLoggerService.js';
import { MockConsole } from '../misc/MockConsole.js';
import type { Config } from '@/config.js';
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
import { ApImageService } from '@/core/activitypub/models/ApImageService.js';
@ -106,7 +106,7 @@ describe('ActivityPub', () => {
let usersRepository: UsersRepository;
let config: Config;
let cacheManagementService: CacheManagementService;
let mockLoggerService: MockLoggerService;
let mockConsole: MockConsole;
let notesRepository: NotesRepository;
const metaInitial = {
@ -162,7 +162,7 @@ describe('ActivityPub', () => {
})
.overrideProvider(DI.meta).useValue(meta)
.overrideProvider(ApResolverService).useClass(MockApResolverService)
.overrideProvider(LoggerService).useClass(MockLoggerService)
.overrideProvider(DI.console).useClass(MockConsole)
.compile();
await app.init();
@ -182,7 +182,7 @@ describe('ActivityPub', () => {
usersRepository = app.get<UsersRepository>(DI.usersRepository);
config = app.get<Config>(DI.config);
cacheManagementService = app.get(CacheManagementService);
mockLoggerService = app.get<MockLoggerService>(LoggerService);
mockConsole = app.get<MockConsole>(DI.console);
notesRepository = app.get<NotesRepository>(DI.notesRepository);
});
@ -198,7 +198,7 @@ describe('ActivityPub', () => {
cacheManagementService.clear();
// Reset mocks
mockLoggerService.reset();
mockConsole.mockReset();
resolver.clear();
});
@ -219,7 +219,7 @@ describe('ActivityPub', () => {
const user = await personService.createPerson(actor.id, resolver);
mockLoggerService.assertNoErrors();
mockConsole.assertNoErrors();
assert.deepStrictEqual(user.uri, actor.id);
assert.deepStrictEqual(user.username, actor.preferredUsername);
assert.deepStrictEqual(user.inbox, actor.inbox);
@ -231,7 +231,7 @@ describe('ActivityPub', () => {
const note = await noteService.createNote(post.id, undefined, resolver, true);
mockLoggerService.assertNoErrors();
mockConsole.assertNoErrors();
assert.deepStrictEqual(note?.uri, post.id);
assert.deepStrictEqual(note.visibility, 'public');
assert.deepStrictEqual(note.text, post.content);
@ -298,7 +298,7 @@ describe('ActivityPub', () => {
const user = await personService.createPerson(actor.id, resolver);
const userProfile = await userProfilesRepository.findOneByOrFail({ userId: user.id });
mockLoggerService.assertNoErrors();
mockConsole.assertNoErrors();
assert.deepStrictEqual(userProfile.followingVisibility, 'public');
assert.deepStrictEqual(userProfile.followersVisibility, 'public');
};
@ -360,7 +360,7 @@ describe('ActivityPub', () => {
assert.strictEqual(note.uri, item.id);
}
mockLoggerService.assertNoErrors();
mockConsole.assertNoErrors();
});
test('Fetch featured notes from IActor pointing to another remote server', async () => {
@ -858,7 +858,7 @@ describe('ActivityPub', () => {
expect(publicKey).not.toBeNull();
expect(publicKey?.keyPem).toBe('key material');
mockLoggerService.assertNoErrors();
mockConsole.assertNoErrors();
});
it('should accept SocialHome actor', async () => {
@ -907,7 +907,7 @@ describe('ActivityPub', () => {
expect(user.uri).toBe(actor.id);
expect(publicKey).not.toBeNull();
mockLoggerService.assertNoErrors();
mockConsole.assertNoErrors();
});
});
});

View file

@ -3,13 +3,14 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { MockConsole } from '../misc/MockConsole.js';
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
import { DataSource } from 'typeorm';
import { Test, TestingModule } from '@nestjs/testing';
import { GodOfTimeService } from '../misc/GodOfTimeService.js';
import { MockLoggerService } from '../misc/MockLoggerService.js';
import { MockRedis } from '../misc/MockRedis.js';
import { GlobalModule } from '@/GlobalModule.js';
import TestChart from '@/core/chart/charts/test.js';
@ -46,7 +47,7 @@ describe('Chart', () => {
})
.overrideProvider(DI.redis).useClass(MockRedis)
.overrideProvider(TimeService).useClass(GodOfTimeService)
.overrideProvider(LoggerService).useClass(MockLoggerService)
.overrideProvider(DI.console).useClass(MockConsole)
.compile();
logger = app.get(LoggerService).getLogger('chart');

View file

@ -7,7 +7,7 @@ import { jest } from '@jest/globals';
import { Test, TestingModule } from '@nestjs/testing';
import { addHours, addSeconds, subDays, subHours, subSeconds } from 'date-fns';
import { GodOfTimeService } from '../../../misc/GodOfTimeService.js';
import { MockLoggerService } from '../../../misc/MockLoggerService.js';
import { MockConsole } from '../../../misc/MockConsole.js';
import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
import { MiSystemWebhook, MiUser, MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/_.js';
import { IdService } from '@/core/IdService.js';
@ -24,7 +24,6 @@ import { CacheManagementService } from '@/global/CacheManagementService.js';
import { TimeService } from '@/global/TimeService.js';
import { CoreModule } from '@/core/CoreModule.js';
import { QueueProcessorModule } from '@/queue/QueueProcessorModule.js';
import { LoggerService } from '@/core/LoggerService.js';
const baseDate = new Date(Date.UTC(2000, 11, 15, 12, 0, 0));
@ -109,7 +108,7 @@ describe('CheckModeratorsActivityProcessorService', () => {
fetchActiveSystemWebhooks: jest.fn(),
enqueueSystemWebhook: jest.fn(),
})
.overrideProvider(LoggerService).useClass(MockLoggerService)
.overrideProvider(DI.console).useClass(MockConsole)
.compile();
await app.init();

View file

@ -27,13 +27,12 @@ describe(SkRateLimiterService, () => {
beforeAll(() => {
mockTimeService = new GodOfTimeService();
mockEnvService = new MockEnvService();
mockRedis = new MockRedis(mockTimeService);
const fakeConfig = { host: 'example.com' } as unknown as Config;
mockInternalEventService = new MockInternalEventService(fakeConfig);
cacheManagementService = new CacheManagementService(mockRedis, mockTimeService, mockInternalEventService);
mockEnvService = new MockEnvService(cacheManagementService);
});
afterAll(() => {