feat(portal): GET /portal/data/diagnostics —— 檢修孔聚合端點
leo 2026-08-07 直接指令:「一顆按鈕在設定裡,按鈕下載一個檔案,把檔案發給我,你看那個 檔」。本端點是那個檔的資料來源:聚合 embed 模組健康狀態(module_enabled/cards_embedded/ cards_pending/self_test)、知識庫規模(library_count/triplet_count)、bundle_version、 instance_url。 紅線落實: - 只轉發數字/布林/字串狀態,KBDB /map 回應裡的 narrative/top_entities(卡片內容)讀出 triplet_count 後即丟棄,測試 portal-data.test.ts 新增案專門斷言回應不含內容字樣。 - 認證沿用既有 requirePortalUser session 閘,不對外公開。 3 個新測試全綠(未登入 401/完整聚合含隱私斷言/embed 未開時誠實回 false 不假裝)。 既有 1 個失敗案(/portal HTML 殼 404)為 stash 驗證過的既有失敗,與本次改動無關。
This commit is contained in:
@@ -711,3 +711,81 @@ describe('dedupeSourcesByPage(t129 出處去重)', () => {
|
||||
expect(out.length).toBe(1); // 同 page_name → 合為一筆
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════ 7. GET /portal/data/diagnostics(檢修孔,2026-08-07) ═══════════════
|
||||
|
||||
describe('GET /portal/data/diagnostics', () => {
|
||||
it('未登入 → 401,不碰 KBDB', async () => {
|
||||
const res = await get('/portal/data/diagnostics');
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
|
||||
it('登入 → 200,聚合 embed 健康狀態+規模統計+版本;只含數字/布林/字串狀態', async () => {
|
||||
await seedSession('tok-diag1', 'rec_diag1');
|
||||
mockGetRecord('rec_diag1', userValues());
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/embed/backfill/status'), method: 'GET' })
|
||||
.reply(200, { success: true, enabled: true, pending: 3, embedded: 80 });
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/embed/selftest'), method: 'GET' })
|
||||
.reply(200, { success: true, enabled: true, tested: true, passed: false, note: '搜不到自己' });
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/map'), method: 'GET' })
|
||||
.reply(200, {
|
||||
success: true,
|
||||
libraries: [
|
||||
{ name: 'general', triplet_count: 67, narrative: '不該出現在診斷檔', top_entities: ['密卡', '內容'] },
|
||||
],
|
||||
count: 1,
|
||||
});
|
||||
|
||||
const res = await get('/portal/data/diagnostics', { Authorization: 'Bearer tok-diag1' });
|
||||
expect(res.status).toBe(200);
|
||||
const body = (await res.json()) as {
|
||||
library_count: number;
|
||||
triplet_count: number;
|
||||
embedding: { module_enabled: boolean; cards_embedded: number; cards_pending: number; self_test: { ran: boolean; found_itself: boolean | null } };
|
||||
instance_url: string;
|
||||
bundle_version: string | null;
|
||||
};
|
||||
expect(body.library_count).toBe(1);
|
||||
expect(body.triplet_count).toBe(67);
|
||||
expect(body.embedding.module_enabled).toBe(true);
|
||||
expect(body.embedding.cards_embedded).toBe(80);
|
||||
expect(body.embedding.cards_pending).toBe(3);
|
||||
expect(body.embedding.self_test.ran).toBe(true);
|
||||
expect(body.embedding.self_test.found_itself).toBe(false);
|
||||
expect(body.instance_url).toBe('http://localhost');
|
||||
// 紅線斷言:整份回應不含知識卡內容本體(/map 回應裡的 narrative/top_entities 沒被轉發)
|
||||
const raw = JSON.stringify(body);
|
||||
expect(raw).not.toContain('不該出現在診斷檔');
|
||||
expect(raw).not.toContain('密卡');
|
||||
});
|
||||
|
||||
it('embed 模組未開(自架未開語義搜尋)→ 誠實回 module_enabled:false,不是假裝有 index', async () => {
|
||||
await seedSession('tok-diag2', 'rec_diag2');
|
||||
mockGetRecord('rec_diag2', userValues());
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/embed/backfill/status'), method: 'GET' })
|
||||
.reply(200, { success: true, enabled: false, pending: 0, embedded: 0 });
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/embed/selftest'), method: 'GET' })
|
||||
.reply(200, { success: true, enabled: false, tested: false, passed: null, note: 'embed 模組未開' });
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/map'), method: 'GET' })
|
||||
.reply(200, { success: true, libraries: [], count: 0 });
|
||||
|
||||
const res = await get('/portal/data/diagnostics', { Authorization: 'Bearer tok-diag2' });
|
||||
expect(res.status).toBe(200);
|
||||
const body = (await res.json()) as { embedding: { module_enabled: boolean; self_test: { ran: boolean; found_itself: boolean | null } } };
|
||||
expect(body.embedding.module_enabled).toBe(false);
|
||||
expect(body.embedding.self_test.ran).toBe(false);
|
||||
expect(body.embedding.self_test.found_itself).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user