/** * 337 계약 시험(설계 2-16) — 모든 도구 머신의 평평한 상태가 물음 약속을 지키는지. * ① `prompt.options` key 마다 `OPTION.` 또는 `OPTION.*` 처리 ② 한 물음 안 kw 안 겹침(없는 옵션 포함) * ③ `meta.kind` 있는 상태마다 ESC ④ `prompt` 와 옛 `instructions` 를 같이 두지 않음 ⑤ 함수 꼴 물음은 빈 context 로 그려짐. * 실행: cd B07_DesignDetail/openwebcad && npx vitest run --dir ../../resources/tester/cad prompt-contract */ import { describe, expect, it, vi } from "vitest"; vi.mock("react-toastify", () => ({ toast: Object.assign(vi.fn(), { error: vi.fn(), info: vi.fn(), warn: vi.fn(), success: vi.fn() }), })); import { type PromptMeta, resolvePrompt, } from "../../../B07_DesignDetail/openwebcad/src/commands/prompt"; import { TOOL_STATE_MACHINES } from "../../../B07_DesignDetail/openwebcad/src/commands/registry"; type StateNode = { config: { meta?: PromptMeta; on?: Record } }; const rows = Object.entries(TOOL_STATE_MACHINES).flatMap(([tool, machine]) => Object.entries((machine as { states: Record }).states).map(([name, node]) => ({ where: `${tool}.${name}`, meta: node.config.meta, on: node.config.on ?? {}, })), ); describe("물음 약속(2-16)", () => { it("도구 머신을 읽음", () => { expect(rows.length).toBeGreaterThan(100); }); it("옵션 처리 · kw 안 겹침 · 함수 꼴은 빈 context 로", () => { const problems: string[] = []; for (const { where, meta, on } of rows) { if (!meta?.prompt) continue; let spec: ReturnType; try { spec = resolvePrompt(meta.prompt, {}); } catch (error) { problems.push(`${where}: 빈 context 로 못 그림 (${(error as Error).message})`); continue; } for (const option of spec?.options ?? []) { if (!on[`OPTION.${option.key}`] && !on["OPTION.*"]) { problems.push(`${where}: OPTION.${option.key} 처리 없음`); } } const kws = [...(spec?.options ?? []), ...(spec?.absent ?? [])].map((option) => option.kw); const twice = kws.filter((kw, index) => kws.indexOf(kw) !== index); if (twice.length) problems.push(`${where}: kw 겹침 ${twice.join(",")}`); } expect(problems).toEqual([]); }); it("meta.kind 있는 상태 = ESC 있음 · prompt 와 instructions 같이 없음", () => { const problems: string[] = []; for (const { where, meta, on } of rows) { if (meta?.kind && !on.ESC) problems.push(`${where}: ESC 없음`); if (meta?.prompt && meta.instructions) problems.push(`${where}: prompt + instructions`); } expect(problems).toEqual([]); }); it("옛 꼴 통과 안 씀 — 옵션 글 배열 없음 · 숫자 없이 TEXT_INPUT 만 받는 상태 없음(글 물음 빼고)", () => { const problems: string[] = []; for (const { where, meta, on } of rows) { if ((meta as { options?: unknown } | undefined)?.options) problems.push(`${where}: meta.options`); if (on.TEXT_INPUT && !on.NUMBER_INPUT && meta?.kind !== "text") problems.push(`${where}: TEXT_INPUT 만`); } expect(problems).toEqual([]); }); });