t142: 雲端子庫顯示同步幾張卡+幾個三元組(政府專案驗收需求)
leo 07-29:「要在雲端子庫顯示同步了幾個 wiki,既然這樣也同步顯示有幾個三元組,
這是為了政府專案驗收。」
kbdb 兩支統計端點:
- entries.ts: COUNT(DISTINCT page_name) AS card_count
⚠️ 必須 distinct——算 block 數會膨脹 3-5 倍,政府驗收看到假數字比沒數字更糟
- records.ts: COUNT(*) AS triplet_count(三元組本來就算全部)
portal.ts 聚合兩者掛進庫目錄;前端 index.html 顯示。
驗:卡數確為 COUNT(DISTINCT page_name)/前端內嵌 JS node --check 全通過
(07-29 白畫面事故教訓)/portal-admin 測試 34 passed(改動前 31 passed,
唯一的 1 failed 是既有債:GET /portal 回 404,改動前後相同,非本次造成)。
註:此為子 CC 完成後未 commit 的懸置工作,總管收工檢查時發現並補收。
This commit is contained in:
@@ -381,10 +381,19 @@ describe('/portal/admin/libraries', () => {
|
||||
await seedAdminSession();
|
||||
mockGetRecord('rec_admin', adminValues());
|
||||
mockListByTemplate('portal_library', []);
|
||||
// t142:GET /portal/admin/libraries 現在並行呼叫三個 kbdb 端點,三個都要 mock
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/entries/libraries'), method: 'GET' })
|
||||
.reply(200, { libraries: ['kb', 'general', 'notes'] });
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/entries/library-stats'), method: 'GET' })
|
||||
.reply(200, { success: true, stats: [] });
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/records/triplet-stats'), method: 'GET' })
|
||||
.reply(200, { success: true, stats: [] });
|
||||
const res = await json('GET', '/portal/admin/libraries', undefined, { Authorization: 'Bearer tok-admin' });
|
||||
expect(res.status).toBe(200);
|
||||
const data = (await res.json()) as { libraries: { name: string; auto?: boolean }[] };
|
||||
@@ -395,6 +404,90 @@ describe('/portal/admin/libraries', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════ t142 庫目錄卡數+三元組數 ═══════════════
|
||||
|
||||
describe('GET /portal/admin/libraries + stats(t142)', () => {
|
||||
it('kbdb 回傳統計 → 已登記庫帶 card_count + triplet_count', async () => {
|
||||
await seedAdminSession();
|
||||
mockGetRecord('rec_admin', adminValues());
|
||||
mockListByTemplate('portal_library', [
|
||||
{ record_id: 'rec_lib_kb', values: { name: 'kb', display_name: '知識庫', status: 'active', graph_source: 'false' } },
|
||||
]);
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/entries/libraries'), method: 'GET' })
|
||||
.reply(200, { libraries: ['kb'] });
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/entries/library-stats'), method: 'GET' })
|
||||
.reply(200, { success: true, stats: [{ library: 'kb', card_count: 42 }] });
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/records/triplet-stats'), method: 'GET' })
|
||||
.reply(200, { success: true, stats: [{ library: 'kb', triplet_count: 111 }] });
|
||||
const res = await json('GET', '/portal/admin/libraries', undefined, { Authorization: 'Bearer tok-admin' });
|
||||
expect(res.status).toBe(200);
|
||||
const data = (await res.json()) as { libraries: { name: string; card_count?: number; triplet_count?: number }[] };
|
||||
const kb = data.libraries.find((l) => l.name === 'kb');
|
||||
expect(kb).toBeDefined();
|
||||
expect(kb!.card_count).toBe(42);
|
||||
expect(kb!.triplet_count).toBe(111);
|
||||
});
|
||||
|
||||
it('auto 庫也帶 card_count + triplet_count', async () => {
|
||||
await seedAdminSession();
|
||||
mockGetRecord('rec_admin', adminValues());
|
||||
mockListByTemplate('portal_library', []);
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/entries/libraries'), method: 'GET' })
|
||||
.reply(200, { libraries: ['notes'] });
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/entries/library-stats'), method: 'GET' })
|
||||
.reply(200, { success: true, stats: [{ library: 'notes', card_count: 7 }] });
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/records/triplet-stats'), method: 'GET' })
|
||||
.reply(200, { success: true, stats: [{ library: 'notes', triplet_count: 108 }] });
|
||||
const res = await json('GET', '/portal/admin/libraries', undefined, { Authorization: 'Bearer tok-admin' });
|
||||
expect(res.status).toBe(200);
|
||||
const data = (await res.json()) as { libraries: { name: string; card_count?: number; triplet_count?: number; auto?: boolean }[] };
|
||||
const notes = data.libraries.find((l) => l.name === 'notes');
|
||||
expect(notes).toBeDefined();
|
||||
expect(notes!.auto).toBe(true);
|
||||
expect(notes!.card_count).toBe(7);
|
||||
expect(notes!.triplet_count).toBe(108);
|
||||
});
|
||||
|
||||
it('庫無內容時 card_count=0 + triplet_count=0(前端顯示「還沒有內容」)', async () => {
|
||||
await seedAdminSession();
|
||||
mockGetRecord('rec_admin', adminValues());
|
||||
mockListByTemplate('portal_library', [
|
||||
{ record_id: 'rec_lib_empty', values: { name: 'empty', display_name: '空庫', status: 'active', graph_source: 'false' } },
|
||||
]);
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/entries/libraries'), method: 'GET' })
|
||||
.reply(200, { libraries: [] });
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/entries/library-stats'), method: 'GET' })
|
||||
.reply(200, { success: true, stats: [] });
|
||||
fetchMock
|
||||
.get(KBDB)
|
||||
.intercept({ path: (p: string) => p.startsWith('/records/triplet-stats'), method: 'GET' })
|
||||
.reply(200, { success: true, stats: [] });
|
||||
const res = await json('GET', '/portal/admin/libraries', undefined, { Authorization: 'Bearer tok-admin' });
|
||||
expect(res.status).toBe(200);
|
||||
const data = (await res.json()) as { libraries: { name: string; card_count: number; triplet_count: number }[] };
|
||||
const empty = data.libraries.find((l) => l.name === 'empty');
|
||||
expect(empty).toBeDefined();
|
||||
expect(empty!.card_count).toBe(0);
|
||||
expect(empty!.triplet_count).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════ t135 庫目錄移除 ═══════════════
|
||||
|
||||
describe('DELETE /portal/admin/libraries(t135)', () => {
|
||||
|
||||
Reference in New Issue
Block a user