perf(console): Gitea 等leo清單讀取加 CF Cache 層(TTL 90s,#36 審查①)
- cachedGiteaWaiting:caches.default + Cache-Control max-age=90;前端 60s 刷新下 Gitea 從每分鐘 3-4 call 降到 ≤1 輪/90s - hit 回放用 reviveWaitingAges 隨牆鐘補算「維護於 N 分鐘前」(不停走) - 失敗不快取(不把網路抖動放大成 90 秒盲區);cache.put 走 waitUntil 不阻塞 - waiting_meta.cache 吐 hit/miss,部署後 curl 兩次即得快取生效證據 - 測試:tests/console-dashboard-cache.test.ts 4 案例(真 workerd Cache API, 注入 fetcher 計數證明 hit 不再打 Gitea);全套 69/70(1 fail=main 既有) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015d5jDbuqT5Htwv3Q88XXKk
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* cachedGiteaWaiting 快取層測試(總管 #36 審查要求的證據)。
|
||||
*
|
||||
* 跑在 vitest-pool-workers(workerd)裡,caches.default 是真的 Cache API——
|
||||
* 不是 mock cache 物件;fetcher 用注入的假函式計數,證明:
|
||||
* 1. 第一次 miss(打 fetcher)→ 90s 內第二次 hit(fetcher 不再被打)
|
||||
* 2. hit 回放時 age 隨牆鐘推進(不回放停走的時間)
|
||||
* 3. fetcher 失敗(回 null)不快取——下次仍會重試,不把抖動放大成 90 秒盲區
|
||||
* 4. 缺 GITEA_BASE_URL/GITEA_TOKEN 直接 null(快取層不啟動)
|
||||
*/
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { cachedGiteaWaiting } from '../src/routes/console-dashboard';
|
||||
import type { WaitingModel } 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 model(): WaitingModel {
|
||||
return {
|
||||
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'],
|
||||
};
|
||||
}
|
||||
|
||||
/** 收集 waitUntil 的 promise,flush 後 cache.put 才保證落地 */
|
||||
function collector() {
|
||||
const pending: Promise<unknown>[] = [];
|
||||
return { waitUntil: (p: Promise<unknown>) => pending.push(p), flush: () => Promise.all(pending) };
|
||||
}
|
||||
|
||||
describe('cachedGiteaWaiting — Gitea 讀取快取(TTL 90s)', () => {
|
||||
it('第一次 miss 打 fetcher;90s 內第二次 hit、fetcher 不再被打', async () => {
|
||||
const env = fakeEnv('Leo/cache-test-1');
|
||||
let calls = 0;
|
||||
const fetcher = async () => {
|
||||
calls++;
|
||||
return model();
|
||||
};
|
||||
const c = collector();
|
||||
const t0 = Date.now();
|
||||
|
||||
const r1 = await cachedGiteaWaiting(env, t0, c.waitUntil, fetcher);
|
||||
expect(r1?.cache).toBe('miss');
|
||||
expect(calls).toBe(1);
|
||||
await c.flush(); // cache.put 落地
|
||||
|
||||
const r2 = await cachedGiteaWaiting(env, t0 + 60_000, c.waitUntil, fetcher);
|
||||
expect(r2?.cache).toBe('hit'); // ← 快取生效的客觀證據
|
||||
expect(calls).toBe(1); // fetcher 沒有被第二次呼叫=Gitea 沒被打
|
||||
expect(r2?.items[0].id).toBe('13');
|
||||
});
|
||||
|
||||
it('hit 回放時 age 隨牆鐘推進(存 10 分鐘前、過 60s 讀 → 11 分鐘前),不停走', async () => {
|
||||
const env = fakeEnv('Leo/cache-test-2');
|
||||
const c = collector();
|
||||
const t0 = Date.now();
|
||||
await cachedGiteaWaiting(env, t0, c.waitUntil, async () => model());
|
||||
await c.flush();
|
||||
const r = await cachedGiteaWaiting(env, t0 + 60_000, c.waitUntil, async () => model());
|
||||
expect(r?.cache).toBe('hit');
|
||||
expect(r?.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 cachedGiteaWaiting(env, Date.now(), c.waitUntil, failing)).toBeNull();
|
||||
await c.flush();
|
||||
expect(await cachedGiteaWaiting(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 model();
|
||||
};
|
||||
const c = collector();
|
||||
expect(await cachedGiteaWaiting(env, Date.now(), c.waitUntil, fetcher)).toBeNull();
|
||||
expect(calls).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user