diff --git a/packages/backend/test/misc/FakeSkRateLimiterService.ts b/packages/backend/test/misc/FakeSkRateLimiterService.ts index 1f1f159620..7cd730a1e3 100644 --- a/packages/backend/test/misc/FakeSkRateLimiterService.ts +++ b/packages/backend/test/misc/FakeSkRateLimiterService.ts @@ -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. diff --git a/packages/backend/test/misc/MockConsole.ts b/packages/backend/test/misc/MockConsole.ts new file mode 100644 index 0000000000..f307ee2fc9 --- /dev/null +++ b/packages/backend/test/misc/MockConsole.ts @@ -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(); + public readonly warn = jest.fn(); + public readonly info = jest.fn(); + public readonly log = jest.fn(); + public readonly debug = jest.fn(); + public readonly trace = jest.fn(); + public readonly assert = jest.fn(); + public readonly clear = jest.fn(); + public readonly count = jest.fn(); + public readonly countReset = jest.fn(); + public readonly dir = jest.fn(); + public readonly dirxml = jest.fn(); + public readonly group = jest.fn(); + public readonly groupCollapsed = jest.fn(); + public readonly groupEnd = jest.fn(); + public readonly table = jest.fn(); + public readonly time = jest.fn(); + public readonly timeEnd = jest.fn(); + public readonly timeLog = jest.fn(); + public readonly profile = jest.fn(); + public readonly profileEnd = jest.fn(); + public readonly timeStamp = jest.fn(); +} diff --git a/packages/backend/test/misc/MockDependencyService.ts b/packages/backend/test/misc/MockDependencyService.ts new file mode 100644 index 0000000000..a8a7f2637e --- /dev/null +++ b/packages/backend/test/misc/MockDependencyService.ts @@ -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(); + } +} diff --git a/packages/backend/test/misc/MockEnvService.ts b/packages/backend/test/misc/MockEnvService.ts index 6ec3b56d07..f388d2f4f2 100644 --- a/packages/backend/test/misc/MockEnvService.ts +++ b/packages/backend/test/misc/MockEnvService.ts @@ -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>; - - constructor(cacheManagementService: CacheManagementService) { - super(cacheManagementService); - this._env = process.env; - } + private _env: Partial> = 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(); } } diff --git a/packages/backend/test/misc/MockLoggerService.ts b/packages/backend/test/misc/MockLoggerService.ts deleted file mode 100644 index df81b36ba3..0000000000 --- a/packages/backend/test/misc/MockLoggerService.ts +++ /dev/null @@ -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 = { - 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); - } -} diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts index 3e2d800916..3bb71a1471 100644 --- a/packages/backend/test/misc/mock-resolver.ts +++ b/packages/backend/test/misc/mock-resolver.ts @@ -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, diff --git a/packages/backend/test/unit/UtilityService.ts b/packages/backend/test/unit/UtilityService.ts index 8c89ea28b0..d2e405ee77 100644 --- a/packages/backend/test/unit/UtilityService.ts +++ b/packages/backend/test/unit/UtilityService.ts @@ -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); }); diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts index 88ab7780a0..eccad42633 100644 --- a/packages/backend/test/unit/activitypub.ts +++ b/packages/backend/test/unit/activitypub.ts @@ -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(DI.usersRepository); config = app.get(DI.config); cacheManagementService = app.get(CacheManagementService); - mockLoggerService = app.get(LoggerService); + mockConsole = app.get(DI.console); notesRepository = app.get(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(); }); }); }); diff --git a/packages/backend/test/unit/chart.ts b/packages/backend/test/unit/chart.ts index ef2844046c..17307a6ccc 100644 --- a/packages/backend/test/unit/chart.ts +++ b/packages/backend/test/unit/chart.ts @@ -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'); diff --git a/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts index e623a547f9..4f9dd9dafa 100644 --- a/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts +++ b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts @@ -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(); diff --git a/packages/backend/test/unit/server/api/SkRateLimiterServiceTests.ts b/packages/backend/test/unit/server/api/SkRateLimiterServiceTests.ts index 925b880947..c5465c01f2 100644 --- a/packages/backend/test/unit/server/api/SkRateLimiterServiceTests.ts +++ b/packages/backend/test/unit/server/api/SkRateLimiterServiceTests.ts @@ -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(() => {