项目文件夹

文件
wehub-resource-sync 070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:00:47 +08:00

127 行
4.3 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import { getActiveRoute, postActiveRoute } from '../src/routes/active-context.js';
import { ACTIVE_CONTEXT_TTL_MS } from '../src/constants.js';
interface MockStore {
current:
| {
projectId: string;
fileName: string | null;
ts: number;
}
| null;
}
function makeDeps(now = 1_000) {
const store: MockStore = { current: null };
// Annotated return type widens the mock so `.mockReturnValue(null)` is
// allowed by the inferred Mock type later in the file.
const getProject = vi.fn(
(_db: unknown, id: string): { name?: string | null } | null | undefined => ({
name: `Project ${id}`,
}),
);
return {
store,
db: { fake: true },
getProject,
now: () => now,
};
}
const EMPTY_INPUT = { body: {}, query: {}, params: {} };
describe('active context — POST /api/active', () => {
it('clears the store when body.active === false', async () => {
const deps = makeDeps();
deps.store.current = { projectId: 'p1', fileName: 'a.html', ts: 1 };
const parsed = postActiveRoute.parse({ ...EMPTY_INPUT, body: { active: false } });
expect(parsed.ok).toBe(true);
if (!parsed.ok) return;
const out = await postActiveRoute.handle(parsed.value, deps);
expect(out).toEqual({ ok: true, value: { active: false } });
expect(deps.store.current).toBeNull();
});
it('rejects when projectId is missing', () => {
const parsed = postActiveRoute.parse({ ...EMPTY_INPUT, body: {} });
expect(parsed.ok).toBe(false);
if (parsed.ok) return;
expect(parsed.error.code).toBe('BAD_REQUEST');
expect(parsed.error.message).toBe('projectId is required');
});
it('stores projectId + fileName + timestamp on success', async () => {
const deps = makeDeps(5_000);
const parsed = postActiveRoute.parse({
...EMPTY_INPUT,
body: { projectId: 'p1', fileName: 'index.html' },
});
expect(parsed.ok).toBe(true);
if (!parsed.ok) return;
const out = await postActiveRoute.handle(parsed.value, deps);
expect(out).toEqual({
ok: true,
value: { active: true, projectId: 'p1', fileName: 'index.html', ts: 5_000 },
});
expect(deps.store.current).toEqual({ projectId: 'p1', fileName: 'index.html', ts: 5_000 });
});
it('treats empty fileName as null', async () => {
const deps = makeDeps(7_000);
const parsed = postActiveRoute.parse({
...EMPTY_INPUT,
body: { projectId: 'p1', fileName: '' },
});
expect(parsed.ok).toBe(true);
if (!parsed.ok) return;
const out = await postActiveRoute.handle(parsed.value, deps);
expect(out.ok).toBe(true);
if (!out.ok) return;
expect(out.value).toMatchObject({ active: true, fileName: null });
});
});
describe('active context — GET /api/active', () => {
it('returns inactive when nothing is stored', async () => {
const deps = makeDeps();
const out = await getActiveRoute.handle(undefined, deps);
expect(out).toEqual({ ok: true, value: { active: false } });
});
it('returns inactive and clears when TTL has expired', async () => {
const deps = makeDeps(10_000 + ACTIVE_CONTEXT_TTL_MS);
deps.store.current = { projectId: 'p1', fileName: null, ts: 9_000 };
const out = await getActiveRoute.handle(undefined, deps);
expect(out).toEqual({ ok: true, value: { active: false } });
expect(deps.store.current).toBeNull();
});
it('returns active payload with project name + ageMs when fresh', async () => {
const deps = makeDeps(2_500);
deps.store.current = { projectId: 'p7', fileName: 'plan.md', ts: 2_000 };
const out = await getActiveRoute.handle(undefined, deps);
expect(out.ok).toBe(true);
if (!out.ok) return;
expect(out.value).toEqual({
active: true,
projectId: 'p7',
projectName: 'Project p7',
fileName: 'plan.md',
ts: 2_000,
ageMs: 500,
});
expect(deps.getProject).toHaveBeenCalledWith(deps.db, 'p7');
});
it('tolerates a missing project (projectName = null)', async () => {
const deps = makeDeps(3_000);
deps.getProject.mockReturnValue(null);
deps.store.current = { projectId: 'p9', fileName: null, ts: 2_500 };
const out = await getActiveRoute.handle(undefined, deps);
expect(out.ok).toBe(true);
if (!out.ok) return;
expect(out.value).toMatchObject({ active: true, projectName: null });
});
});