chore: complete vitest migration for remaining packages and API calls

- Convert remaining node:test → vitest imports in packages/* and studio/*
- Fix mock.callCount() → mock.callCount property access for vitest compat
- Fix mock.calls[N].arguments → mock.calls[N] for vitest compat
- Update tsconfig.extensions.json to exclude test files from tsc
- Harden migrate-to-vitest-all.mjs regex for single quotes and optional semicolons
This commit is contained in:
Mikael Hugo 2026-05-02 04:46:11 +02:00
parent b62f7b20ec
commit 1de5d5456a
25 changed files with 125 additions and 121 deletions

View file

@ -1,4 +1,4 @@
import { describe, it, afterEach, before, after } from 'node:test';
import { describe, it, afterEach, before, after } from 'vitest';
import assert from 'node:assert/strict';
import { mkdtempSync, writeFileSync, readFileSync, rmSync, existsSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';

View file

@ -1,4 +1,4 @@
import { describe, it, afterEach } from 'node:test';
import { describe, it, afterEach } from 'vitest';
import assert from 'node:assert/strict';
import { mkdtempSync, readFileSync, rmSync, existsSync } from 'node:fs';
import { join } from 'node:path';

View file

@ -6,7 +6,7 @@
* blocker handling, conversation relay, and cleanup.
*/
import { describe, it, mock } from 'node:test';
import { vi, describe, it } from 'vitest';
import assert from 'node:assert/strict';
import { EventEmitter } from 'node:events';
import { EventBridge } from './event-bridge.js';
@ -20,10 +20,10 @@ import type { SdkAgentEvent, RpcClient, RpcExtensionUIRequest } from '@singulari
function createMockLogger() {
return {
debug: mock.fn(() => {}),
info: mock.fn(() => {}),
warn: mock.fn(() => {}),
error: mock.fn(() => {}),
debug: vi.fn(() => {}),
info: vi.fn(() => {}),
warn: vi.fn(() => {}),
error: vi.fn(() => {}),
};
}
@ -31,18 +31,18 @@ function createMockChannelManager() {
const sentMessages: unknown[] = [];
const mockChannel = {
id: 'ch-123',
send: mock.fn(async (_payload: unknown) => {
send: vi.fn(async (_payload: unknown) => {
sentMessages.push(_payload);
return { id: 'msg-1' };
}),
createMessageComponentCollector: mock.fn((_opts?: unknown) => {
createMessageComponentCollector: vi.fn((_opts?: unknown) => {
const collector = new EventEmitter() as EventEmitter & { stop: (reason?: string) => void };
collector.stop = (reason?: string) => collector.emit('end', [], reason ?? 'manual');
return collector;
}),
};
return {
createProjectChannel: mock.fn(async (_dir: string) => mockChannel),
createProjectChannel: vi.fn(async (_dir: string) => mockChannel),
_channel: mockChannel,
_sentMessages: sentMessages,
};
@ -50,8 +50,8 @@ function createMockChannelManager() {
function createMockClient(): BridgeClient & EventEmitter {
const emitter = new EventEmitter();
const dmSendFn = mock.fn(async () => ({}));
const fetchFn = mock.fn(async (_id: string) => ({ send: dmSendFn }));
const dmSendFn = vi.fn(async () => ({}));
const fetchFn = vi.fn(async (_id: string) => ({ send: dmSendFn }));
(emitter as unknown as Record<string, unknown>).users = { fetch: fetchFn };
return Object.assign(emitter, {
users: { fetch: fetchFn },
@ -61,11 +61,11 @@ function createMockClient(): BridgeClient & EventEmitter {
function createMockSessionManager() {
const sm = new EventEmitter() as EventEmitter & {
getSession: ReturnType<typeof mock.fn>;
resolveBlocker: ReturnType<typeof mock.fn>;
getSession: ReturnType<typeof vi.fn>;
resolveBlocker: ReturnType<typeof vi.fn>;
};
sm.getSession = mock.fn((_id: string) => undefined as ManagedSession | undefined);
sm.resolveBlocker = mock.fn(async (_sid: string, _resp: string) => {});
sm.getSession = vi.fn((_id: string) => undefined as ManagedSession | undefined);
sm.resolveBlocker = vi.fn(async (_sid: string, _resp: string) => {});
return sm;
}
@ -76,8 +76,8 @@ function createMockSession(overrides?: Partial<ManagedSession>): ManagedSession
projectName: 'project',
status: 'running' as SessionStatus,
client: {
steer: mock.fn(async (_msg: string) => {}),
prompt: mock.fn(async () => ({})),
steer: vi.fn(async (_msg: string) => {}),
prompt: vi.fn(async () => ({})),
} as unknown as RpcClient,
events: [],
pendingBlocker: null,
@ -123,8 +123,8 @@ function buildBridge(overrides?: Partial<EventBridgeOptions>) {
// ---------------------------------------------------------------------------
const tick = () => new Promise<void>((r) => setTimeout(r, 30));
function mockFn(obj: unknown): { mock: { callCount(): number; calls: Array<{ arguments: unknown[]; result?: unknown }> } } {
return obj as { mock: { callCount(): number; calls: Array<{ arguments: unknown[]; result?: unknown }> } };
function mockFn(obj: unknown): { mock: { callCount: number; calls: Array<unknown[]> } } {
return obj as { mock: { callCount: number; calls: Array<unknown[]> } };
}
// ---------------------------------------------------------------------------
@ -179,12 +179,12 @@ describe('EventBridge', () => {
sessionId: 'sess-1', projectDir: '/test/project', projectName: 'my-project',
});
await tick();
assert.equal(mockFn(channelManager.createProjectChannel).mock.callCount(), 1);
assert.equal(mockFn(channelManager.createProjectChannel).mock.callCount, 1);
});
it('logs error and skips when channel creation fails', async () => {
const failingCm = {
createProjectChannel: mock.fn(async () => { throw new Error('API error'); }),
createProjectChannel: vi.fn(async () => { throw new Error('API error'); }),
};
const { bridge, sessionManager, logger } = buildBridge({
channelManager: failingCm as unknown as EventBridgeOptions['channelManager'],
@ -194,7 +194,7 @@ describe('EventBridge', () => {
sessionId: 'sess-1', projectDir: '/test/project', projectName: 'my-project',
});
await tick();
assert.ok(mockFn(logger.error).mock.callCount() > 0);
assert.ok(mockFn(logger.error).mock.callCount > 0);
});
});
@ -213,7 +213,7 @@ describe('EventBridge', () => {
});
await tick();
// No errors
assert.equal(mockFn(logger.error).mock.callCount(), 0);
assert.equal(mockFn(logger.error).mock.callCount, 0);
});
it('filters events based on verbosity', async () => {
@ -239,7 +239,7 @@ describe('EventBridge', () => {
event: { type: 'tool_execution_start', name: 'read' } as SdkAgentEvent,
});
await tick();
assert.equal(mockFn(logger.error).mock.callCount(), 0);
assert.equal(mockFn(logger.error).mock.callCount, 0);
});
});
@ -260,7 +260,7 @@ describe('EventBridge', () => {
sessionId: 'sess-1', projectDir: '/test/project', projectName: 'my-project', blocker,
});
await tick();
assert.ok(mockFn(channelManager._channel.createMessageComponentCollector).mock.callCount() > 0);
assert.ok(mockFn(channelManager._channel.createMessageComponentCollector).mock.callCount > 0);
});
it('sends DM when dm_on_blocker is configured', async () => {
@ -287,7 +287,7 @@ describe('EventBridge', () => {
await tick();
const usersFetch = (client as unknown as Record<string, { fetch: unknown }>).users.fetch;
assert.equal(mockFn(usersFetch).mock.callCount(), 1);
assert.equal(mockFn(usersFetch).mock.callCount, 1);
});
it('does not send DM when dm_on_blocker is false', async () => {
@ -310,7 +310,7 @@ describe('EventBridge', () => {
await tick();
const usersFetch = (client as unknown as Record<string, { fetch: unknown }>).users.fetch;
assert.equal(mockFn(usersFetch).mock.callCount(), 0);
assert.equal(mockFn(usersFetch).mock.callCount, 0);
});
});
@ -340,14 +340,14 @@ describe('EventBridge', () => {
const mockInteraction = {
customId: 'blocker:blocker-1:confirm:true',
user: { id: 'owner-1' },
update: mock.fn(async () => {}),
reply: mock.fn(async () => {}),
update: vi.fn(async () => {}),
reply: vi.fn(async () => {}),
};
collector.emit('collect', mockInteraction);
await tick();
assert.equal(mockFn(sessionManager.resolveBlocker).mock.callCount(), 1);
const args = mockFn(sessionManager.resolveBlocker).mock.calls[0]!.arguments;
assert.equal(mockFn(sessionManager.resolveBlocker).mock.callCount, 1);
const args = mockFn(sessionManager.resolveBlocker).mock.calls[0]!;
assert.equal(args[0], 'sess-1');
assert.equal(args[1], 'true');
});
@ -376,19 +376,19 @@ describe('EventBridge', () => {
const mockInteraction = {
customId: 'blocker:blocker-1:confirm:true',
user: { id: 'stranger-99' },
update: mock.fn(async () => {}),
reply: mock.fn(async () => {}),
update: vi.fn(async () => {}),
reply: vi.fn(async () => {}),
};
collector.emit('collect', mockInteraction);
await tick();
assert.equal(mockFn(sessionManager.resolveBlocker).mock.callCount(), 0);
assert.equal(mockFn(mockInteraction.reply).mock.callCount(), 1);
assert.equal(mockFn(sessionManager.resolveBlocker).mock.callCount, 0);
assert.equal(mockFn(mockInteraction.reply).mock.callCount, 1);
});
it('posts error when resolveBlocker throws', async () => {
const { bridge, sessionManager, channelManager } = buildBridge();
sessionManager.resolveBlocker = mock.fn(async () => { throw new Error('No pending blocker'); });
sessionManager.resolveBlocker = vi.fn(async () => { throw new Error('No pending blocker'); });
bridge.start();
sessionManager.emit('session:started', {
@ -411,14 +411,14 @@ describe('EventBridge', () => {
const mockInteraction = {
customId: 'blocker:blocker-1:confirm:true',
user: { id: 'owner-1' },
update: mock.fn(async () => {}),
reply: mock.fn(async () => {}),
update: vi.fn(async () => {}),
reply: vi.fn(async () => {}),
};
collector.emit('collect', mockInteraction);
await tick();
assert.equal(mockFn(mockInteraction.reply).mock.callCount(), 1);
const replyArg = mockFn(mockInteraction.reply).mock.calls[0]!.arguments[0] as Record<string, unknown>;
assert.equal(mockFn(mockInteraction.reply).mock.callCount, 1);
const replyArg = mockFn(mockInteraction.reply).mock.calls[0]![0] as Record<string, unknown>;
assert.ok(String(replyArg.content).includes('Failed to resolve'));
});
});
@ -427,7 +427,7 @@ describe('EventBridge', () => {
it('relays message to session steer when no pending blocker', async () => {
const session = createMockSession();
const { bridge, sessionManager, client } = buildBridge();
sessionManager.getSession = mock.fn(() => session);
sessionManager.getSession = vi.fn(() => session);
bridge.start();
sessionManager.emit('session:started', {
@ -439,14 +439,14 @@ describe('EventBridge', () => {
author: { id: 'owner-1', bot: false },
channelId: 'ch-123',
content: 'check the test results',
react: mock.fn(async () => {}),
reply: mock.fn(async () => {}),
react: vi.fn(async () => {}),
reply: vi.fn(async () => {}),
};
client.emit('messageCreate', msg);
await tick();
assert.equal(mockFn(session.client.steer).mock.callCount(), 1);
assert.equal(mockFn(session.client.steer).mock.calls[0]!.arguments[0], 'check the test results');
assert.equal(mockFn(session.client.steer).mock.callCount, 1);
assert.equal(mockFn(session.client.steer).mock.calls[0]![0], 'check the test results');
});
it('resolves blocker via relay for input method', async () => {
@ -456,7 +456,7 @@ describe('EventBridge', () => {
};
const session = createMockSession({ pendingBlocker: blocker, status: 'blocked' });
const { bridge, sessionManager, client } = buildBridge();
sessionManager.getSession = mock.fn(() => session);
sessionManager.getSession = vi.fn(() => session);
bridge.start();
sessionManager.emit('session:started', {
@ -468,20 +468,20 @@ describe('EventBridge', () => {
author: { id: 'owner-1', bot: false },
channelId: 'ch-123',
content: 'my-api-key-value',
react: mock.fn(async () => {}),
reply: mock.fn(async () => {}),
react: vi.fn(async () => {}),
reply: vi.fn(async () => {}),
};
client.emit('messageCreate', msg);
await tick();
assert.equal(mockFn(sessionManager.resolveBlocker).mock.callCount(), 1);
assert.equal(mockFn(sessionManager.resolveBlocker).mock.calls[0]!.arguments[1], 'my-api-key-value');
assert.equal(mockFn(sessionManager.resolveBlocker).mock.callCount, 1);
assert.equal(mockFn(sessionManager.resolveBlocker).mock.calls[0]![1], 'my-api-key-value');
});
it('ignores bot messages', async () => {
const session = createMockSession();
const { bridge, sessionManager, client } = buildBridge();
sessionManager.getSession = mock.fn(() => session);
sessionManager.getSession = vi.fn(() => session);
bridge.start();
sessionManager.emit('session:started', {
@ -493,36 +493,36 @@ describe('EventBridge', () => {
author: { id: 'bot-1', bot: true },
channelId: 'ch-123',
content: 'automated',
react: mock.fn(async () => {}),
reply: mock.fn(async () => {}),
react: vi.fn(async () => {}),
reply: vi.fn(async () => {}),
});
await tick();
assert.equal(mockFn(session.client.steer).mock.callCount(), 0);
assert.equal(mockFn(session.client.steer).mock.callCount, 0);
});
it('ignores messages in non-project channels', async () => {
const session = createMockSession();
const { bridge, sessionManager, client } = buildBridge();
sessionManager.getSession = mock.fn(() => session);
sessionManager.getSession = vi.fn(() => session);
bridge.start();
client.emit('messageCreate', {
author: { id: 'owner-1', bot: false },
channelId: 'random-ch-999',
content: 'hello',
react: mock.fn(async () => {}),
reply: mock.fn(async () => {}),
react: vi.fn(async () => {}),
reply: vi.fn(async () => {}),
});
await tick();
assert.equal(mockFn(session.client.steer).mock.callCount(), 0);
assert.equal(mockFn(session.client.steer).mock.callCount, 0);
});
it('ignores messages from unauthorized users', async () => {
const session = createMockSession();
const { bridge, sessionManager, client } = buildBridge();
sessionManager.getSession = mock.fn(() => session);
sessionManager.getSession = vi.fn(() => session);
bridge.start();
sessionManager.emit('session:started', {
@ -534,21 +534,21 @@ describe('EventBridge', () => {
author: { id: 'stranger-99', bot: false },
channelId: 'ch-123',
content: 'hack the planet',
react: mock.fn(async () => {}),
reply: mock.fn(async () => {}),
react: vi.fn(async () => {}),
reply: vi.fn(async () => {}),
});
await tick();
assert.equal(mockFn(session.client.steer).mock.callCount(), 0);
assert.equal(mockFn(session.client.steer).mock.callCount, 0);
});
it('posts error when steer fails', async () => {
const session = createMockSession();
(session.client as unknown as Record<string, unknown>).steer = mock.fn(async () => {
(session.client as unknown as Record<string, unknown>).steer = vi.fn(async () => {
throw new Error('session dead');
});
const { bridge, sessionManager, client } = buildBridge();
sessionManager.getSession = mock.fn(() => session);
sessionManager.getSession = vi.fn(() => session);
bridge.start();
sessionManager.emit('session:started', {
@ -560,13 +560,13 @@ describe('EventBridge', () => {
author: { id: 'owner-1', bot: false },
channelId: 'ch-123',
content: 'try this',
react: mock.fn(async () => {}),
reply: mock.fn(async () => {}),
react: vi.fn(async () => {}),
reply: vi.fn(async () => {}),
};
client.emit('messageCreate', msg);
await tick();
assert.equal(mockFn(msg.reply).mock.callCount(), 1);
assert.equal(mockFn(msg.reply).mock.callCount, 1);
});
});
@ -591,7 +591,7 @@ describe('EventBridge', () => {
event: { type: 'tool_execution_start', name: 'read' } as SdkAgentEvent,
});
await tick();
assert.equal(mockFn(logger.error).mock.callCount(), 0);
assert.equal(mockFn(logger.error).mock.callCount, 0);
});
});
@ -612,7 +612,7 @@ describe('EventBridge', () => {
const infoCalls = mockFn(logger.info).mock.calls;
assert.ok(
infoCalls.some((c) => String(c.arguments[0]).includes('session error')),
infoCalls.some((c) => String(c[0]).includes('session error')),
);
});
});

View file

@ -1,4 +1,4 @@
import { describe, it } from 'node:test';
import { describe, it } from 'vitest';
import assert from 'node:assert/strict';
import { EmbedBuilder, ActionRowBuilder, ButtonBuilder } from 'discord.js';
import type { SdkAgentEvent } from '@singularity-forge/rpc-client';

View file

@ -1,4 +1,4 @@
import { describe, it, beforeEach, afterEach } from 'node:test';
import { describe, it, beforeEach, afterEach } from 'vitest';
import assert from 'node:assert/strict';
import { mkdtempSync, existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, statSync } from 'node:fs';
import { join, dirname } from 'node:path';

View file

@ -1,4 +1,4 @@
import { describe, it, beforeEach, afterEach, mock } from 'node:test';
import { vi, describe, it, beforeEach, afterEach } from 'vitest';
import assert from 'node:assert/strict';
import { MessageBatcher } from './message-batcher.js';
import type { SendPayload, BatcherLogger } from './message-batcher.js';
@ -21,7 +21,7 @@ function fakeEvent(content: string, hasEmbed = false): FormattedEvent {
/** Create a tracking send function. */
function createSend() {
const calls: SendPayload[] = [];
const fn = mock.fn(async (payload: SendPayload) => {
const fn = vi.fn(async (payload: SendPayload) => {
calls.push(payload);
});
return { fn, calls };

View file

@ -5,7 +5,7 @@
* allowing tool execution and conversation flow testing without real API calls.
*/
import { describe, it, afterEach } from 'node:test';
import { describe, it, afterEach } from 'vitest';
import assert from 'node:assert/strict';
import { mkdtempSync, rmSync, existsSync } from 'node:fs';
import { join } from 'node:path';

View file

@ -2,7 +2,7 @@
* Tests for the project scanner module.
*/
import { describe, it, afterEach } from 'node:test';
import { describe, it, afterEach } from 'vitest';
import assert from 'node:assert/strict';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, chmodSync } from 'node:fs';
import { join } from 'node:path';

View file

@ -6,7 +6,7 @@
* and cleanup without spawning real SF processes.
*/
import { describe, it, beforeEach, afterEach } from 'node:test';
import { describe, it, beforeEach, afterEach } from 'vitest';
import assert from 'node:assert/strict';
import { resolve, basename } from 'node:path';
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';

View file

@ -1,4 +1,4 @@
import { describe, it, beforeEach } from 'node:test';
import { describe, it, beforeEach } from 'vitest';
import assert from 'node:assert/strict';
import { VerbosityManager, shouldShowAtLevel } from './verbosity.js';

View file

@ -1,7 +1,7 @@
// @singularity-forge/mcp-server — Tests for env-writer utilities
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
import { describe, it, afterEach } from 'node:test';
import { describe, it, afterEach } from 'vitest';
import assert from 'node:assert/strict';
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, readFileSync, realpathSync, symlinkSync } from 'node:fs';
import { tmpdir } from 'node:os';

View file

@ -10,7 +10,7 @@
* 4. Testing CLI path resolution via static method
*/
import { describe, it, beforeEach, afterEach } from 'node:test';
import { describe, it, beforeEach, afterEach } from 'vitest';
import assert from 'node:assert/strict';
import { resolve } from 'node:path';
import { EventEmitter } from 'node:events';

View file

@ -1,7 +1,7 @@
// SF MCP Server — knowledge graph reader tests
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
import { describe, it, before, after, beforeEach, afterEach } from 'node:test';
import { describe, it, before, after, beforeEach, afterEach } from 'vitest';
import assert from 'node:assert/strict';
import { mkdirSync, writeFileSync, rmSync, existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';

View file

@ -1,7 +1,7 @@
// SF MCP Server — reader tests
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
import { describe, it, before, after } from 'node:test';
import { describe, it, before, after } from 'vitest';
import assert from 'node:assert/strict';
import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
import { join } from 'node:path';

View file

@ -4,7 +4,7 @@
// Tests the secure_env_collect tool registered in createMcpServer.
// Uses a mock MCP server to intercept tool registration and elicitInput calls.
import { describe, it, beforeEach } from 'node:test';
import { describe, it, beforeEach } from 'vitest';
import assert from 'node:assert/strict';
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, readFileSync } from 'node:fs';
import { tmpdir } from 'node:os';

View file

@ -154,8 +154,8 @@ describe("generateSummary — chunked fallback (#2932)", () => {
// Assert: should have called completeSimple more than once (chunked)
assert.ok(
mockComplete.mock.callCount() > 1,
`Expected multiple calls for chunked summarization, got ${mockComplete.mock.callCount()}`,
mockComplete.mock.callCount > 1,
`Expected multiple calls for chunked summarization, got ${mockComplete.mock.callCount}`,
);
// First call should be an initial summary, subsequent should be updates
@ -189,7 +189,7 @@ describe("generateSummary — chunked fallback (#2932)", () => {
await generateSummary(messages, model, reserveTokens, undefined, undefined, undefined, undefined, mockComplete);
assert.equal(
mockComplete.mock.callCount(),
mockComplete.mock.callCount,
1,
"Should use single-pass summarization when messages fit in context window",
);

View file

@ -86,8 +86,8 @@ describe("FallbackResolver — findFallback", () => {
const fn = authStorage.markProviderExhausted as any;
assert.equal(fn.mock.calls.length, 1);
assert.equal(fn.mock.calls[0].arguments[0], "zai");
assert.equal(fn.mock.calls[0].arguments[1], "rate_limit");
assert.equal(fn.mock.calls[0][0], "zai");
assert.equal(fn.mock.calls[0][1], "rate_limit");
});
it("skips backed-off providers", async () => {

View file

@ -247,7 +247,7 @@ describe("RetryHandler — long-context entitlement 429 (#2803)", () => {
// Should have called setModel with the base model
const setModelCalls = (deps.agent.setModel as any).mock.calls;
assert.equal(setModelCalls.length, 1);
assert.equal(setModelCalls[0].arguments[0].id, "claude-opus-4-6");
assert.equal(setModelCalls[0][0].id, "claude-opus-4-6");
// Should have notified about model change
assert.equal(onModelChangeFn.mock.calls.length, 1);
@ -343,7 +343,7 @@ describe("RetryHandler — long-context entitlement 429 (#2803)", () => {
const setModelCalls = (deps.agent.setModel as any).mock.calls;
assert.equal(setModelCalls.length, 1, "should apply one model downgrade");
const downgraded = setModelCalls[0].arguments[0] as Model<Api>;
const downgraded = setModelCalls[0][0] as Model<Api>;
assert.equal(downgraded.provider, "openrouter");
assert.equal(downgraded.id, "openai/gpt-5-pro");
assert.equal(downgraded.maxTokens, 297, "expected affordability cap with safety buffer");

View file

@ -44,15 +44,15 @@ function collectTestFiles(dirs) {
}
function migrateImport(content, { hasMockFn, hasMockTimers }) {
// Case 1: import test from "node:test";
// Case 1: import test from "node:test"; (or single quotes, optional semicolon)
content = content.replace(
/^import test from "node:test";$/gm,
/^import test from ["']node:test["'];?$/gm,
"import { test } from 'vitest';",
);
// Case 2: import { ... } from "node:test" (and variants with default import)
content = content.replace(
/import\s+(test,)?\s*\{\s*([^}]+)\}\s+from\s+"node:test";?/g,
/import\s+(test,)?\s*\{\s*([^}]+)\}\s+from\s+["']node:test["'];?/g,
(match, hasDefault, named) => {
const namedList = named
.split(",")
@ -83,7 +83,7 @@ let errors = 0;
for (const file of files) {
try {
const content = readFileSync(file, "utf-8");
if (!content.includes('from "node:test"')) continue;
if (!content.includes('from "node:test"') && !content.includes("from 'node:test'")) continue;
const hasMockFn = content.includes("mock.fn");
const hasMockTimers = content.includes("mock.timers");

View file

@ -231,7 +231,7 @@ describe("Post-execution blocking failure retry bypass", () => {
// Non-execute-task units should return "continue" immediately
assert.equal(result, "continue");
assert.equal(pauseAutoMock.mock.callCount(), 0);
assert.equal(pauseAutoMock.mock.callCount, 0);
});
test("returns continue when verification passes", async () => {
@ -256,7 +256,7 @@ describe("Post-execution blocking failure retry bypass", () => {
// When verification passes, should return "continue" and not call pauseAuto
assert.equal(result, "continue");
assert.equal(pauseAutoMock.mock.callCount(), 0);
assert.equal(pauseAutoMock.mock.callCount, 0);
// Retry state should be cleared
assert.equal(s.pendingVerificationRetry, null);
@ -345,7 +345,7 @@ describe("Post-execution blocking failure retry bypass", () => {
const result = await runPostUnitVerification(vctx, pauseAutoMock);
assert.equal(result, "pause");
assert.equal(pauseAutoMock.mock.callCount(), 1);
assert.equal(pauseAutoMock.mock.callCount, 1);
const adapter = _getAdapter();
const row = adapter
@ -426,7 +426,7 @@ describe("Post-execution retry behavior", () => {
// When autofix is disabled and verification fails, should pause
assert.equal(result, "pause");
assert.equal(pauseAutoMock.mock.callCount(), 1);
assert.equal(pauseAutoMock.mock.callCount, 1);
// Should NOT set up a retry
assert.equal(s.pendingVerificationRetry, null);

View file

@ -210,7 +210,7 @@ describe("Pre-execution fail-closed behavior", () => {
// With valid tasks, pre-exec should pass and not pause
assert.equal(
pauseAutoMock.mock.callCount(),
pauseAutoMock.mock.callCount,
0,
"pauseAuto should NOT be called when pre-execution checks pass",
);
@ -267,7 +267,7 @@ describe("Pre-execution fail-closed behavior", () => {
// With a blocking failure, pauseAuto should be called
assert.equal(
pauseAutoMock.mock.callCount(),
pauseAutoMock.mock.callCount,
1,
"pauseAuto should be called when pre-execution checks fail",
);
@ -281,7 +281,7 @@ describe("Pre-execution fail-closed behavior", () => {
// Verify error notification was shown
const notifyCalls = ctx.ui.notify.mock.calls;
const errorNotify = notifyCalls.find(
(call: { arguments: unknown[] }) => call.arguments[1] === "error",
(call: unknown[]) => call[1] === "error",
);
assert.ok(
errorNotify,

View file

@ -312,7 +312,7 @@ describe("Pre-execution checks → pauseAuto wiring", () => {
// Verify pauseAuto was called
assert.equal(
pauseAutoMock.mock.callCount(),
pauseAutoMock.mock.callCount,
1,
"pauseAuto should be called exactly once when pre-execution checks fail with blocking issues",
);
@ -327,9 +327,9 @@ describe("Pre-execution checks → pauseAuto wiring", () => {
// Verify UI was notified of the failure
const notifyCalls = ctx.ui.notify.mock.calls;
const errorNotify = notifyCalls.find(
(call: { arguments: unknown[] }) =>
call.arguments[1] === "error" &&
String(call.arguments[0]).includes("Pre-execution checks failed"),
(call: unknown[]) =>
call[1] === "error" &&
String(call[0]).includes("Pre-execution checks failed"),
);
assert.ok(
errorNotify,
@ -360,7 +360,7 @@ describe("Pre-execution checks → pauseAuto wiring", () => {
// Verify pauseAuto was called (strict mode promotes warnings to blocking)
assert.equal(
pauseAutoMock.mock.callCount(),
pauseAutoMock.mock.callCount,
1,
"pauseAuto should be called when strict mode is enabled and pre-execution returns warn",
);
@ -375,9 +375,9 @@ describe("Pre-execution checks → pauseAuto wiring", () => {
// Verify UI was notified of the warning
const notifyCalls = ctx.ui.notify.mock.calls;
const warnNotify = notifyCalls.find(
(call: { arguments: unknown[] }) =>
call.arguments[1] === "warning" &&
String(call.arguments[0]).includes(
(call: unknown[]) =>
call[1] === "warning" &&
String(call[0]).includes(
"Pre-execution checks passed with warnings",
),
);
@ -410,7 +410,7 @@ describe("Pre-execution checks → pauseAuto wiring", () => {
// Verify pauseAuto was NOT called (warnings don't block in non-strict mode)
assert.equal(
pauseAutoMock.mock.callCount(),
pauseAutoMock.mock.callCount,
0,
"pauseAuto should NOT be called when strict mode is disabled and only warnings exist",
);
@ -442,7 +442,7 @@ describe("Pre-execution checks → pauseAuto wiring", () => {
// Verify pauseAuto was NOT called (pre-execution checks only run for plan-slice)
assert.equal(
pauseAutoMock.mock.callCount(),
pauseAutoMock.mock.callCount,
0,
"pauseAuto should NOT be called for non-plan-slice unit types",
);
@ -477,7 +477,7 @@ describe("Pre-execution checks → pauseAuto wiring", () => {
// Verify pauseAuto was NOT called (pre-execution checks disabled)
assert.equal(
pauseAutoMock.mock.callCount(),
pauseAutoMock.mock.callCount,
0,
"pauseAuto should NOT be called when enhanced_verification_pre is disabled",
);

View file

@ -139,9 +139,9 @@ describe("validate-milestone stuck-loop guard (#4094)", () => {
);
assert.equal(result, "pause");
assert.equal(pauseAutoMock.mock.callCount(), 1);
assert.equal(ctx.ui.notify.mock.callCount(), 1);
const notifyArgs = ctx.ui.notify.mock.calls[0].arguments;
assert.equal(pauseAutoMock.mock.callCount, 1);
assert.equal(ctx.ui.notify.mock.callCount, 1);
const notifyArgs = ctx.ui.notify.mock.calls[0];
assert.match(notifyArgs[0], /needs-remediation/);
assert.equal(notifyArgs[1], "error");
});
@ -173,7 +173,7 @@ describe("validate-milestone stuck-loop guard (#4094)", () => {
);
assert.equal(result, "pause");
assert.equal(pauseAutoMock.mock.callCount(), 1);
assert.equal(pauseAutoMock.mock.callCount, 1);
});
test("continues when verdict=needs-remediation but a queued remediation slice exists", async () => {
@ -203,7 +203,7 @@ describe("validate-milestone stuck-loop guard (#4094)", () => {
);
assert.equal(result, "continue");
assert.equal(pauseAutoMock.mock.callCount(), 0);
assert.equal(pauseAutoMock.mock.callCount, 0);
});
test("continues when verdict is pass", async () => {
@ -227,7 +227,7 @@ describe("validate-milestone stuck-loop guard (#4094)", () => {
);
assert.equal(result, "continue");
assert.equal(pauseAutoMock.mock.callCount(), 0);
assert.equal(pauseAutoMock.mock.callCount, 0);
});
test("continues when no VALIDATION file exists yet", async () => {
@ -250,6 +250,6 @@ describe("validate-milestone stuck-loop guard (#4094)", () => {
);
assert.equal(result, "continue");
assert.equal(pauseAutoMock.mock.callCount(), 0);
assert.equal(pauseAutoMock.mock.callCount, 0);
});
});

View file

@ -1,4 +1,4 @@
import test from 'node:test'
import { test } from 'vitest';
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'

View file

@ -23,6 +23,10 @@
},
"include": ["src/resources/extensions"],
"exclude": [
"src/resources/extensions/vectordrive/tests/**/*.ts"
"src/resources/extensions/vectordrive/tests/**/*.ts",
"src/resources/extensions/**/tests/**/*.ts",
"src/resources/extensions/**/tests/**/*.mjs",
"src/tests/**/*.ts",
"src/tests/**/*.mjs"
]
}