2026-08-21 19:55:50 -04:00
|
|
|
import { test } from 'node:test';
|
|
|
|
|
import assert from 'node:assert/strict';
|
2026-08-22 22:36:42 -04:00
|
|
|
import { chmod, mkdtemp, writeFile } from 'node:fs/promises';
|
|
|
|
|
import { tmpdir } from 'node:os';
|
|
|
|
|
import { join } from 'node:path';
|
2026-08-21 19:55:50 -04:00
|
|
|
import { chat } from '../src/lib/router.js';
|
|
|
|
|
|
|
|
|
|
test('review router client disables model fallbacks explicitly', async () => {
|
|
|
|
|
const originalFetch = globalThis.fetch;
|
|
|
|
|
let body;
|
|
|
|
|
globalThis.fetch = async (_url, init) => {
|
|
|
|
|
body = JSON.parse(init.body);
|
|
|
|
|
return Response.json({
|
|
|
|
|
message: { content: '{"axes":{},"overall":90,"grade":"A","findings":[]}' },
|
|
|
|
|
_shre: { model: 'ollama-remote/qwen2.5-coder:7b' }
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await chat({
|
|
|
|
|
routerUrl: 'http://router.test',
|
|
|
|
|
model: 'ollama-remote/qwen2.5-coder:7b',
|
|
|
|
|
messages: [{ role: 'user', content: 'review' }],
|
|
|
|
|
agentId: 'code-reviewer',
|
|
|
|
|
sessionId: 'review-test',
|
|
|
|
|
tenantId: 'nirlab',
|
|
|
|
|
timeoutMs: 1000
|
|
|
|
|
});
|
|
|
|
|
} finally {
|
|
|
|
|
globalThis.fetch = originalFetch;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(body.fallbackModels, []);
|
|
|
|
|
assert.equal(body.tools, false);
|
|
|
|
|
assert.equal(body.raw, true);
|
|
|
|
|
assert.equal(body.stream, false);
|
|
|
|
|
});
|
2026-08-22 22:36:42 -04:00
|
|
|
|
|
|
|
|
test('claude-cli models bypass the router and use subscription credentials', async () => {
|
|
|
|
|
const dir = await mkdtemp(join(tmpdir(), 'granthi-review-claude-'));
|
|
|
|
|
const fakeClaude = join(dir, 'claude');
|
|
|
|
|
await writeFile(fakeClaude, `#!/bin/sh
|
|
|
|
|
case " $* " in
|
|
|
|
|
*" --model claude-sonnet-4-6 "*) ;;
|
|
|
|
|
*) exit 21 ;;
|
|
|
|
|
esac
|
|
|
|
|
case " $* " in
|
|
|
|
|
*" --tools --permission-mode dontAsk --no-session-persistence "*) ;;
|
|
|
|
|
*) exit 22 ;;
|
|
|
|
|
esac
|
|
|
|
|
[ -z "\${ANTHROPIC_API_KEY:-}" ] || exit 23
|
|
|
|
|
[ -z "\${ANTHROPIC_TOKEN:-}" ] || exit 24
|
|
|
|
|
printf '%s' '{"result":"{\\"axes\\":{},\\"overall\\":90,\\"grade\\":\\"A\\",\\"findings\\":[]}"}'
|
|
|
|
|
`);
|
|
|
|
|
await chmod(fakeClaude, 0o700);
|
|
|
|
|
|
|
|
|
|
const originalFetch = globalThis.fetch;
|
|
|
|
|
const originalApiKey = process.env.ANTHROPIC_API_KEY;
|
|
|
|
|
const originalToken = process.env.ANTHROPIC_TOKEN;
|
|
|
|
|
globalThis.fetch = async () => { throw new Error('router must not be called'); };
|
|
|
|
|
process.env.ANTHROPIC_API_KEY = 'test-api-key';
|
|
|
|
|
process.env.ANTHROPIC_TOKEN = 'test-token';
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await chat({
|
|
|
|
|
routerUrl: 'http://router.test',
|
|
|
|
|
model: 'claude-cli/claude-sonnet-4-6',
|
|
|
|
|
messages: [{ role: 'user', content: 'review this diff' }],
|
|
|
|
|
agentId: 'code-reviewer',
|
|
|
|
|
sessionId: 'review-test',
|
|
|
|
|
tenantId: 'nirlab',
|
|
|
|
|
timeoutMs: 1000,
|
|
|
|
|
claudeBin: fakeClaude
|
|
|
|
|
});
|
|
|
|
|
assert.match(result.content, /"overall":90/);
|
|
|
|
|
assert.equal(result.servedModel, 'claude-cli/claude-sonnet-4-6');
|
|
|
|
|
} finally {
|
|
|
|
|
globalThis.fetch = originalFetch;
|
|
|
|
|
if (originalApiKey === undefined) delete process.env.ANTHROPIC_API_KEY;
|
|
|
|
|
else process.env.ANTHROPIC_API_KEY = originalApiKey;
|
|
|
|
|
if (originalToken === undefined) delete process.env.ANTHROPIC_TOKEN;
|
|
|
|
|
else process.env.ANTHROPIC_TOKEN = originalToken;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('claude-cli failures do not leak the review prompt', async () => {
|
|
|
|
|
const dir = await mkdtemp(join(tmpdir(), 'granthi-review-claude-error-'));
|
|
|
|
|
const fakeClaude = join(dir, 'claude');
|
|
|
|
|
await writeFile(fakeClaude, '#!/bin/sh\nprintf "%s\\n" "$*" >&2\nexit 25\n');
|
|
|
|
|
await chmod(fakeClaude, 0o700);
|
|
|
|
|
|
|
|
|
|
await assert.rejects(
|
|
|
|
|
chat({
|
|
|
|
|
routerUrl: 'http://router.test',
|
|
|
|
|
model: 'claude-cli/claude-sonnet-4-6',
|
|
|
|
|
messages: [{ role: 'user', content: 'SECRET_REVIEW_DIFF_MARKER' }],
|
|
|
|
|
timeoutMs: 1000,
|
|
|
|
|
claudeBin: fakeClaude
|
|
|
|
|
}),
|
|
|
|
|
(error) => {
|
|
|
|
|
assert.match(error.message, /claude-cli failed/);
|
|
|
|
|
assert.doesNotMatch(error.message, /SECRET_REVIEW_DIFF_MARKER/);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
});
|