Files
Arcrun/cypher-executor/tests/console-dashboard-cache.test.ts
T
cloud-worker 5690d61438 fix(console): 真實性稽核修正——時區統一台北、精耕層數字、今日完成接 sprint 任務板
稽核輪(fix/console-truth-audit,總管派工+leo 三裁決):

B1 時區:console 全站日期/時間顯示統一 Asia/Taipei(新 lib/taipei-time.ts,
   server 判定與頁面 client JS 同一套、等價性單測互鎖)。原本用瀏覽器本地
   getter——換裝置就換日期;憑證頁改顯「日期+時分(台北)」:raw 1783349834
   =台北 2026-07-06 22:57,7/6 顯示是真的,缺的是時分與固定時區。

B2 總庫數字(leo 裁:14-E 遺產 deprecated 不再顯示):總庫搜尋/設定頁頭部
   統計改讀新 GET /console/kb-scale-data(精耕層 wiki 卡/三元組/已嵌入+最近
   寫入,全 live、limit=1/stats 聚合不掃庫);搜尋仍搜全庫、結果 wiki_card
   加「精耕」綠標;dashboard-data 的 entries 計數補 owner_id(原混租戶:
   459,137 vs leo 458,732)。

B3 設定頁:vectorize 文案改具體事實(config kbdb_embed+acr update,#32
   教訓)+live 狀態;新增 MCP token TTL 誠實佔位(GET /console/settings-data,
   可調功能=Arcrun#19,不假裝能遠端改)。

C 今日完成/今日路線接 sprint 任務板(leo 拍板):比照 #36 等leo清單模式,
   同一輪 Gitea fetch(90s CF cache 共用)解析「## 任務板」checkbox;「今日
   完成」只認「完成(今天台北日)」標記,[x] 無日期不冒領;快取存原始
   completed_days,跨台北午夜不誤算;板檔今天沒 commit → 頁面誠實標「今日
   任務板未更新」;dash_task 降 fallback、燈號判定不變。parser 以 live
   07a/07b 檔實測(標題抽取棄「首個粗體」——實讀發現會抽到完成註記)。

驗證:tsc exit 0;vitest 82/83(1 失敗=pre-existing executor「不存在的
零件」,stash 於 main 複驗同樣紅);新增 taipei-time 10 案例(含跨日/跨年/
client-server 等價/leo 實測 epoch)+任務板 parser 7 案例;兩頁 inline JS
渲染後抽 <script> node --check 全過(mistakes #24 手法)。
2026-07-07 12:25:31 +00:00

110 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* cachedGiteaSprint 快取層測試(總管 #36 審查要求的證據;fix/console-truth-audit 起
* 同一輪快取多帶任務板 board——「等你的事」與「今日完成」共用同一次 Gitea fetch)。
*
* 跑在 vitest-pool-workersworkerd)裡,caches.default 是真的 Cache API——
* 不是 mock cache 物件;fetcher 用注入的假函式計數,證明:
* 1. 第一次 miss(打 fetcher)→ 90s 內第二次 hitfetcher 不再被打),board 一起回放
* 2. hit 回放時 age 隨牆鐘推進(不回放停走的時間)
* 3. fetcher 失敗(回 null)不快取——下次仍會重試,不把抖動放大成 90 秒盲區
* 4. 缺 GITEA_BASE_URL/GITEA_TOKEN 直接 null(快取層不啟動)
*/
import { describe, it, expect } from 'vitest';
import { cachedGiteaSprint } from '../src/routes/console-dashboard';
import type { SprintSnapshot } from '../src/lib/console-dashboard-model';
import type { Bindings } from '../src/types';
function fakeEnv(repo: string): Bindings {
return {
GITEA_BASE_URL: 'https://git.example.test',
GITEA_TOKEN: 'test-token',
GITEA_SPRINT_REPO: repo, // 每測試一個 repo → cache key 隔離
GITEA_SPRINT_DIR: 'system-dev/docs/3-specs/autonomy-dispatch',
} as unknown as Bindings;
}
function snapshot(): SprintSnapshot {
return {
waiting: {
items: [{ title: 'mira .env 歷史憑證外洩', id: '13', urgency: '🔴', sprint: 'sprint-2026-07a.md' }],
source: 'gitea_sprint',
updated_ago_minutes: 10,
stale: false,
sprint_files: ['sprint-2026-07b.md', 'sprint-2026-07a.md'],
},
board: [
{ title: 'T5-graphGraph 查詢 workflow 化)', status: 'todo', completed_days: [], sprint: 'sprint-2026-07b.md' },
{ title: 'T-kb2 07-07', status: 'done', completed_days: ['2026-07-05'], sprint: 'sprint-2026-07a.md' },
],
};
}
/** 收集 waitUntil 的 promiseflush 後 cache.put 才保證落地 */
function collector() {
const pending: Promise<unknown>[] = [];
return { waitUntil: (p: Promise<unknown>) => pending.push(p), flush: () => Promise.all(pending) };
}
describe('cachedGiteaSprint — Gitea 讀取快取(TTL 90swaiting + board 同輪)', () => {
it('第一次 miss 打 fetcher90s 內第二次 hit、fetcher 不再被打,board 一起回放', async () => {
const env = fakeEnv('Leo/cache-test-1');
let calls = 0;
const fetcher = async () => {
calls++;
return snapshot();
};
const c = collector();
const t0 = Date.now();
const r1 = await cachedGiteaSprint(env, t0, c.waitUntil, fetcher);
expect(r1?.cache).toBe('miss');
expect(calls).toBe(1);
await c.flush(); // cache.put 落地
const r2 = await cachedGiteaSprint(env, t0 + 60_000, c.waitUntil, fetcher);
expect(r2?.cache).toBe('hit'); // ← 快取生效的客觀證據
expect(calls).toBe(1); // fetcher 沒有被第二次呼叫=Gitea 沒被打
expect(r2?.waiting.items[0].id).toBe('13');
// 任務板同輪回放:存的是原始 completed_days(「今天完成幾件」由請求當下算,跨午夜不冒領)
expect(r2?.board?.length).toBe(2);
expect(r2?.board?.[1].completed_days).toEqual(['2026-07-05']);
});
it('hit 回放時 age 隨牆鐘推進(存 10 分鐘前、過 60s 讀 → 11 分鐘前),不停走', async () => {
const env = fakeEnv('Leo/cache-test-2');
const c = collector();
const t0 = Date.now();
await cachedGiteaSprint(env, t0, c.waitUntil, async () => snapshot());
await c.flush();
const r = await cachedGiteaSprint(env, t0 + 60_000, c.waitUntil, async () => snapshot());
expect(r?.cache).toBe('hit');
expect(r?.waiting.updated_ago_minutes).toBe(11); // 10 + 1 分鐘 drift
});
it('fetcher 失敗(null)不快取:下一次仍重打 fetcher', async () => {
const env = fakeEnv('Leo/cache-test-3');
const c = collector();
let calls = 0;
const failing = async () => {
calls++;
return null;
};
expect(await cachedGiteaSprint(env, Date.now(), c.waitUntil, failing)).toBeNull();
await c.flush();
expect(await cachedGiteaSprint(env, Date.now(), c.waitUntil, failing)).toBeNull();
expect(calls).toBe(2); // 失敗沒有被 90 秒快取住
});
it('缺 GITEA_BASE_URL/GITEA_TOKEN → null(快取層不啟動,caller 走 fallback', async () => {
const env = { GITEA_SPRINT_REPO: 'Leo/x' } as unknown as Bindings;
let calls = 0;
const fetcher = async () => {
calls++;
return snapshot();
};
const c = collector();
expect(await cachedGiteaSprint(env, Date.now(), c.waitUntil, fetcher)).toBeNull();
expect(calls).toBe(0);
});
});