/** * task-6-rereview2.md I-A: `PluginRuntimeService.onApplicationBootstrap`'s * absent-ORM branch (`plugin-runtime.service.ts:~296-300`) was dead * suite-wide — every existing construction of the service that reaches * `onApplicationBootstrap` (boot-registry-order.test.ts, plugin-host.ts) hands * it a real MikroORM. That branch is the half of the boot RULING * (task-6-rereview.md §2) that makes "skip loudly, never abort app.init()" * defensible: dropping the `logError` call, or falling through to `activate` * anyway, would ship green with no test failing. This file pins it directly. */ import { describe, it, expect, vi, beforeAll, afterAll, beforeEach } from 'vitest'; const logMock = vi.hoisted(() => ({ LOG_LEVEL: 'error', logInfo: vi.fn(), logError: vi.fn(), logWarn: vi.fn(), logDebug: vi.fn() })); vi.mock('../../../src/nest/audit/audit-log.logger', () => logMock); import { PluginRuntimeService } from '../../../src/nest/plugins/plugin-runtime.service'; import { UnitOfWork } from '../../../src/nest/database/unit-of-work'; import { AuditService } from '../../../src/nest/audit/audit.service'; import { PluginUserSettingsService } from '../../../src/nest/plugins/plugin-user-settings.service'; import { createTestAddonsService } from '../../helpers/test-addons'; import { createSnapshotTestDb } from '../../helpers/db-mock'; import { createTestOrm, type TestOrm } from '../../helpers/test-orm'; import { AuditLog } from '../../../src/db/entities/AuditLog.entity'; import { Users } from '../../../src/db/entities/Users.entity'; import { Plugins } from '../../../src/db/entities/Plugins.entity'; import { PluginErrorLog } from '../../../src/db/entities/PluginErrorLog.entity'; import { PluginScheduledTasks } from '../../../src/db/entities/PluginScheduledTasks.entity'; import { PluginUserErasureQueue } from '../../../src/db/entities/PluginUserErasureQueue.entity'; import { PluginEgressHosts } from '../../../src/db/entities/PluginEgressHosts.entity'; import { PluginSettingsFields } from '../../../src/db/entities/PluginSettingsFields.entity'; import { PluginActions } from '../../../src/db/entities/PluginActions.entity'; import { PluginUserConfig } from '../../../src/db/entities/PluginUserConfig.entity'; import { PluginEntityMetadata } from '../../../src/db/entities/PluginEntityMetadata.entity'; import { PluginOauthTokens } from '../../../src/db/entities/PluginOauthTokens.entity'; import { PluginOauthState } from '../../../src/db/entities/PluginOauthState.entity'; import { PluginMetaMigrations } from '../../../src/db/entities/PluginMetaMigrations.entity'; import { PluginCapabilityAudit } from '../../../src/db/entities/PluginCapabilityAudit.entity'; import { Settings } from '../../../src/db/entities/Settings.entity'; import { NotificationChannelPreferences } from '../../../src/db/entities/NotificationChannelPreferences.entity'; const testDb = createSnapshotTestDb(); let t: TestOrm; beforeAll(async () => { // Global context disallowed — the production setting — so the WITH-orm // sibling case genuinely proves activation runs through a request context, // the same reasoning airports.service.test.ts and BOOT-REG-002 use. t = await createTestOrm(testDb, { allowGlobalContext: false }); }); afterAll(async () => { await t.close(); testDb.close(); }); beforeEach(() => vi.clearAllMocks()); /** installedDepRows() reads the `plugins` table directly — no discovery needed. */ function installEnabledPlugin(id: string): void { testDb .prepare( `INSERT INTO plugins (id, name, status, enabled, version, api_version, permissions, granted_permissions, capabilities, config, dependencies) VALUES (?, ?, 'inactive', 1, '1.0.0', 1, '[]', '[]', '{}', '{}', '{}')`, ) .run(id, id); } async function buildRuntime(withOrm: boolean): Promise { const audit = new AuditService(t.repo(AuditLog), t.repo(Users)); const addons = await createTestAddonsService(testDb); const userSettings = new PluginUserSettingsService(t.repo(PluginSettingsFields), t.repo(PluginUserConfig)); return new PluginRuntimeService( audit, addons, userSettings, t.repo(Plugins), t.repo(PluginErrorLog), t.repo(PluginScheduledTasks), t.repo(PluginUserErasureQueue), t.repo(PluginEgressHosts), t.repo(PluginSettingsFields), t.repo(PluginActions), t.repo(PluginUserConfig), t.repo(PluginEntityMetadata), t.repo(PluginOauthTokens), t.repo(PluginOauthState), t.repo(PluginMetaMigrations), t.repo(PluginCapabilityAudit), t.repo(Settings), t.repo(NotificationChannelPreferences), // Plan 4 Task 4: `uow` is no longer `@Optional()` — ordered ahead of // `registry?`/`hostFactory?`, matching the real constructor's own order. new UnitOfWork(t.em), undefined, undefined, withOrm ? t.orm : undefined, ); } describe('PluginRuntimeService.onApplicationBootstrap — absent-ORM boot branch (task-6-rereview2.md I-A)', () => { it('RT-BOOT-NOORM-001: without an ORM, boot logs the skip line and never calls activate', async () => { installEnabledPlugin('rtboot-noorm'); const rt = await buildRuntime(false); const activateSpy = vi.spyOn(rt, 'activate'); await rt.onApplicationBootstrap(); // Plan 3j Task 2 (deviation, task-2-report.md): `installedDepRows` is now a // repository read that needs a request context this branch cannot provide, so // it is skipped rather than attempted unwrapped — the log line can no longer // name which plugin(s) it skipped, only that it skipped activation entirely. // Still fail-closed (never throws out of onApplicationBootstrap, never abandons // app.init()), still logged exactly once, still never calls activate. expect(logMock.logError).toHaveBeenCalledTimes(1); const [message] = logMock.logError.mock.calls[0] as [string]; expect(message).toMatch(/PluginRuntimeService\.onApplicationBootstrap: no MikroORM available/); expect(activateSpy).not.toHaveBeenCalled(); }); it('RT-BOOT-NOORM-002: WITH an ORM, the same arrangement activates the plugin — proves case 001 is not vacuous', async () => { installEnabledPlugin('rtboot-withorm'); const rt = await buildRuntime(true); const activateSpy = vi.spyOn(rt, 'activate').mockResolvedValue(undefined); await rt.onApplicationBootstrap(); expect(activateSpy).toHaveBeenCalledWith('rtboot-withorm'); expect(logMock.logError).not.toHaveBeenCalled(); }); });