mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-02 21:49:42 +02:00
Three gaps found while auditing the agent skill. `codeman skill install` / `uninstall` had no tests at all, including the linked-case resolution that shipped in 1.14.2 with nothing guarding it. Covered now: global target resolution, `--case` resolving through linked-cases.json, `--case` falling back to the cases dir for an unlinked name, a missing or malformed registry degrading to the fallback instead of throwing, and a nonexistent case being rejected. `resolveSkillTarget` called `process.exit(1)` for a missing case, which would have killed the test runner, so the pure resolution is split out and exported; CLI behavior is unchanged. The `POST /api/sessions` injection call site was never exercised, because the shared route mock hardcoded the gate off. The mock's gate is overridable per test now (default still off, since other tests rely on that), and there is coverage that the path injects when the setting is on, does not when it is off, and is claude-mode gated. Nothing guarded skills/codeman/reference/endpoints.md against drifting from the routes it documents, which is how it drifted in the first place. A static guard parses the endpoints out of the markdown and asserts each is really registered, tolerating the /api/v1 alias and path params. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
143 lines
5.0 KiB
TypeScript
143 lines
5.0 KiB
TypeScript
/**
|
|
* Mock context for route handler testing.
|
|
*
|
|
* Satisfies ALL port interfaces (SessionPort, EventPort, RespawnPort,
|
|
* ConfigPort, InfraPort, AuthPort) so any route module can be tested.
|
|
* Override specific methods in individual tests as needed.
|
|
*
|
|
* Uses app.inject() — no real HTTP ports needed.
|
|
*/
|
|
import { vi } from 'vitest';
|
|
import { MockSession, createMockSession } from './mock-session.js';
|
|
import { resolveTerminalHistoryConfig } from '../../src/config/terminal-history.js';
|
|
|
|
/**
|
|
* Creates a mock context that satisfies all port interfaces.
|
|
* Pre-populated with one session for convenience.
|
|
*/
|
|
export function createMockRouteContext(options?: { sessionId?: string; agentSkillEnabled?: boolean }) {
|
|
const sessionId = options?.sessionId ?? 'test-session-1';
|
|
const session = createMockSession(sessionId);
|
|
const sessions = new Map<string, MockSession>();
|
|
sessions.set(sessionId, session);
|
|
|
|
// Stateful backing for the global tab order (COD-131) so route tests can
|
|
// assert that setSessionOrder() actually persists what the handler computed.
|
|
let sessionOrder: string[] = [];
|
|
|
|
return {
|
|
// -- SessionPort --
|
|
sessions,
|
|
addSession: vi.fn((s: MockSession) => {
|
|
sessions.set(s.id, s);
|
|
}),
|
|
cleanupSession: vi.fn(async () => {}),
|
|
setupSessionListeners: vi.fn(async () => {}),
|
|
persistSessionState: vi.fn(),
|
|
persistSessionStateNow: vi.fn(),
|
|
getSessionStateWithRespawn: vi.fn((s: MockSession) => s.toState()),
|
|
|
|
// -- EventPort --
|
|
broadcast: vi.fn(),
|
|
sendPushNotifications: vi.fn(),
|
|
batchTerminalData: vi.fn(),
|
|
broadcastSessionStateDebounced: vi.fn(),
|
|
batchTaskUpdate: vi.fn(),
|
|
getSseClientCount: vi.fn(() => 0),
|
|
|
|
// -- RespawnPort --
|
|
respawnControllers: new Map(),
|
|
respawnTimers: new Map(),
|
|
setupRespawnListeners: vi.fn(),
|
|
setupTimedRespawn: vi.fn(),
|
|
restoreRespawnController: vi.fn(),
|
|
saveRespawnConfig: vi.fn(),
|
|
|
|
// -- ConfigPort --
|
|
store: {
|
|
getConfig: vi.fn(() => ({ ralphEnabled: false, maxConcurrentSessions: 5 })),
|
|
getSessions: vi.fn(() => ({})),
|
|
getSession: vi.fn(),
|
|
setSession: vi.fn(),
|
|
removeSession: vi.fn(),
|
|
getSettings: vi.fn(() => ({})),
|
|
setSettings: vi.fn(),
|
|
getRalphLoopState: vi.fn(() => ({})),
|
|
setRalphLoopState: vi.fn(),
|
|
getTasks: vi.fn(() => ({})),
|
|
save: vi.fn(),
|
|
load: vi.fn(),
|
|
incrementSessionsCreated: vi.fn(),
|
|
setConfig: vi.fn(),
|
|
getSessionOrder: vi.fn(() => sessionOrder),
|
|
setSessionOrder: vi.fn((order: string[]) => {
|
|
sessionOrder = order;
|
|
}),
|
|
getAggregateStats: vi.fn(() => ({ totalInputTokens: 0, totalOutputTokens: 0, totalCost: 0 })),
|
|
getGlobalStats: vi.fn(() => ({ sessionsCreated: 0 })),
|
|
getDailyStats: vi.fn(() => []),
|
|
cleanupStaleSessions: vi.fn(() => ({ count: 0, cleaned: [] })),
|
|
},
|
|
port: 3000,
|
|
https: false,
|
|
testMode: true,
|
|
serverStartTime: Date.now(),
|
|
getGlobalNiceConfig: vi.fn(async () => undefined),
|
|
getModelConfig: vi.fn(async () => null),
|
|
getClaudeModeConfig: vi.fn(async () => ({})),
|
|
getTerminalHistoryConfig: vi.fn(async () => resolveTerminalHistoryConfig({})),
|
|
// Default OFF mirrors the shipped setting, so existing tests never touch a
|
|
// case's .claude/skills. Overridable per test because the create-time
|
|
// injection call sites are otherwise unreachable from a route test.
|
|
getAgentSkillEnabled: vi.fn(async () => options?.agentSkillEnabled ?? false),
|
|
getDefaultClaudeMdPath: vi.fn(async () => undefined),
|
|
getLightState: vi.fn(() => ({ sessions: [], status: 'ok' })),
|
|
getLightSessionsState: vi.fn(() => {
|
|
const result: Record<string, unknown>[] = [];
|
|
for (const s of sessions.values()) {
|
|
result.push(s.toState());
|
|
}
|
|
return result;
|
|
}),
|
|
startTranscriptWatcher: vi.fn(),
|
|
stopTranscriptWatcher: vi.fn(),
|
|
|
|
// -- InfraPort --
|
|
mux: {
|
|
muxSocket: 'codeman',
|
|
createSession: vi.fn(),
|
|
killSession: vi.fn(),
|
|
listSessions: vi.fn(() => []),
|
|
getStats: vi.fn(() => ({})),
|
|
updateSessionName: vi.fn(() => true),
|
|
getSession: vi.fn(() => null),
|
|
clearRespawnConfig: vi.fn(),
|
|
updateRespawnConfig: vi.fn(),
|
|
setHistoryLimit: vi.fn(async () => {}),
|
|
},
|
|
runSummaryTrackers: new Map(),
|
|
activePlanOrchestrators: new Map(),
|
|
scheduledRuns: new Map(),
|
|
teamWatcher: { getTeams: vi.fn(() => []), getTeamTasks: vi.fn(() => []), hasActiveTeammates: vi.fn(() => false) },
|
|
tunnelManager: null,
|
|
pushStore: null,
|
|
startScheduledRun: vi.fn(),
|
|
stopScheduledRun: vi.fn(),
|
|
|
|
// -- AuthPort --
|
|
authSessions: null,
|
|
qrAuthFailures: null,
|
|
// https already declared above in ConfigPort (shared property)
|
|
|
|
// -- OrchestratorPort --
|
|
orchestratorLoop: null,
|
|
initOrchestratorLoop: vi.fn(),
|
|
|
|
// Convenience accessors (not part of any port interface)
|
|
_session: session,
|
|
_sessionId: sessionId,
|
|
};
|
|
}
|
|
|
|
export type MockRouteContext = ReturnType<typeof createMockRouteContext>;
|