feat(ai-review 對話收斂): 讀 PR review 留言判斷解決狀態並收斂 findings #45
@@ -104,21 +104,21 @@ describe('formatFindingsStats', () => {
|
|||||||
{ level: 'custom', is_new: true },
|
{ level: 'custom', is_new: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
it('formats old and new findings by severity', () => {
|
it('formats old and new findings by severity with an unclassified column', () => {
|
||||||
const stats = formatFindingsStats(statsFindings);
|
const stats = formatFindingsStats(statsFindings);
|
||||||
|
|
||||||
assert.equal(stats, [
|
assert.equal(stats, [
|
||||||
'| 類型 | 🔴 嚴重 | 🟡 警告 | 🔵 建議 |',
|
'| 類型 | 🔴 嚴重 | 🟡 警告 | 🔵 建議 | ⚪ 無法標示 |',
|
||||||
'| --- | --- | --- | --- |',
|
'| --- | --- | --- | --- | --- |',
|
||||||
'| 舊問題 | 1 筆 | 0 筆 | 0 筆 |',
|
'| 新問題 | 0 筆 | 1 筆 | 1 筆 | 1 筆 |',
|
||||||
'| 新問題 | 0 筆 | 1 筆 | 1 筆 |',
|
'| 舊問題 | 1 筆 | 0 筆 | 0 筆 | 0 筆 |',
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('formats compact one-line stats for action logs', () => {
|
it('formats compact one-line stats for action logs', () => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
formatFindingsStatsLine(statsFindings),
|
formatFindingsStatsLine(statsFindings),
|
||||||
'舊: 嚴重1 / 警告0 / 建議0;新: 嚴重0 / 警告1 / 建議1',
|
'新: 嚴重0 / 警告1 / 建議1 / 無法標示1;舊: 嚴重1 / 警告0 / 建議0 / 無法標示0',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -86,6 +86,13 @@ describe('codeWindow', () => {
|
|||||||
it('returns empty string for empty content', () => {
|
it('returns empty string for empty content', () => {
|
||||||
assert.equal(codeWindow('', 10), '');
|
assert.equal(codeWindow('', 10), '');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles out-of-range line numbers without throwing', () => {
|
||||||
|
const content = Array.from({ length: 10 }, (_, i) => `line${i + 1}`).join('\n');
|
||||||
|
assert.doesNotThrow(() => codeWindow(content, -5, 2));
|
||||||
|
assert.equal(codeWindow(content, 0, 1), '1: line1\n2: line2'); // 非正數行號 → 從開頭取窗
|
||||||
|
assert.equal(codeWindow(content, 9999, 2), ''); // 超過檔尾 → 空字串,不丟錯
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('judgeConversationsResolved', () => {
|
describe('judgeConversationsResolved', () => {
|
||||||
|
|||||||
+5
-5
@@ -100,11 +100,11 @@ describe('fetchAccountQuota', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accepts a real openrouter subdomain', async () => {
|
it('only accepts the exact openrouter.ai apex host (subdomains are not OpenRouter)', async () => {
|
||||||
const get = async () => ({ data: { data: { usage: 1, limit: 10, limit_remaining: 9 } } });
|
const get = async () => { throw new Error('should not be called for non-apex host'); };
|
||||||
const q = await fetchAccountQuota('openai', { apiKeys: ['k'], baseURL: 'https://api.openrouter.ai/api/v1' }, { get });
|
const q = await fetchAccountQuota('openai', { apiKeys: ['sk-secret'], baseURL: 'https://api.openrouter.ai/api/v1' }, { get });
|
||||||
assert.equal(q.available, true);
|
assert.equal(q.available, false);
|
||||||
assert.equal(q.source, 'openrouter');
|
assert.match(q.reason, /API key 無法取得/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reports 不適用 for local platforms', async () => {
|
it('reports 不適用 for local platforms', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user