유스케이스 TDD 16테마 스펙과 접근성 제품 결함 수정
- 지원 티켓 작성 UI 신설(설정)·관리자 해결 노트 입력 신설(Admin): 서버 계약은 있었으나 웹 진입점이 없던 2결함 - a11y: outline 채널 포커스 링(ui/shell css), 세션바 44px 터치 타깃, 청록 하드코딩 그라디언트를 테마 토큰으로 교체, 설정 라벨/헤딩/대비·리뷰 44px·모바일 오버플로 수정 - uc-*.spec.ts 16테마 239 시나리오 신규(수집 1109 tests/61 files), 기존 스펙 5종 계약 드리프트 교정 - breakpoint-sweep: widthsFor 솎아내기가 실기기 대표 폭(360/390/1024)을 탈락시키는 테스트 결함 수정 — keep 시드를 전체 DEVICE_WIDTHS로, 3연폭 예외는 솎아내기 발생 여부 기준으로 - 검증: tsc PASS, 병렬 게이트 1040 passed(데스크톱 밀도 충돌 1건 해소 후 focused 68/68 GREEN), 직렬 게이트 52/5/4 삼각화 — breakpoint(테스트 결함)·kb(낡은 dev API 재기동)·voice(일시적) 해소, 교사 재평가 2건은 엔진 구독 한도(resets 3pm)로 skip 후 재검증 대기 - 문서/SSOT: HANDOFF·TODO·대시보드·testing 가이드 동기화, 증거 usecase-tdd-2026-08-18.json, SSOT 체커 PASS(59)
This commit is contained in:
parent
4771b97c3a
commit
cf899b7f15
37 changed files with 11989 additions and 67 deletions
|
|
@ -1,5 +1,9 @@
|
|||
import { expect, test, type APIResponse, type Page } from "@playwright/test";
|
||||
import { completeOnboarding, expectNoHorizontalOverflow } from "./support";
|
||||
import {
|
||||
completeOnboarding,
|
||||
expectNoHorizontalOverflow,
|
||||
withGlobalEngineConfigLock,
|
||||
} from "./support";
|
||||
|
||||
async function expectResponseOk(response: APIResponse) {
|
||||
if (!response.ok()) {
|
||||
|
|
@ -74,6 +78,7 @@ test.describe("production readiness gates", () => {
|
|||
});
|
||||
|
||||
test("admin-owned runtime controls are durable database state", async ({ page }) => {
|
||||
test.setTimeout(60_000); // 엔진 설정 전역 락 대기 여유 포함
|
||||
await signIn(page, "admin", "readiness-admin@twentyoz.kr", "Readiness Admin");
|
||||
|
||||
const usersResponse = await page.request.get("/api/admin/users");
|
||||
|
|
@ -81,21 +86,56 @@ test.describe("production readiness gates", () => {
|
|||
const users = (await usersResponse.json()) as { source: string; durable: boolean };
|
||||
expect(users).toMatchObject({ source: "database", durable: true });
|
||||
|
||||
const engineResponse = await page.request.get("/api/admin/engine-config");
|
||||
await expectResponseOk(engineResponse);
|
||||
const engine = (await engineResponse.json()) as {
|
||||
durable: boolean;
|
||||
source: string;
|
||||
engine_mode: string;
|
||||
engine_url: string;
|
||||
model: string;
|
||||
reasoning_effort: string | null;
|
||||
};
|
||||
expect(engine).toMatchObject({ durable: true, source: "database" });
|
||||
expect(["claude_cli", "claude_api", "codex_cli", "agy_cli", "openai", "solar"]).toContain(
|
||||
engine.engine_mode,
|
||||
);
|
||||
expect(engine.engine_url).toMatch(/^https?:\/\//);
|
||||
expect(engine.model.trim().length).toBeGreaterThan(0);
|
||||
// 엔진 설정의 durable 계약은 "관리자가 저장한 설정이 DB 행으로 남는다"이다.
|
||||
// 시드 없는 격리 DB에서는 저장 전 GET이 runtime_default(durable=false)를
|
||||
// 돌려주는 것이 정상 계약이므로, 실제 게이트웨이 검증을 거친 저장 round-trip
|
||||
// 후의 durable/database 상태를 확인한다. (support.ts withGlobalEngineConfigLock 계약)
|
||||
await withGlobalEngineConfigLock("readiness-admin-engine", async () => {
|
||||
type EngineConfig = {
|
||||
durable: boolean;
|
||||
source: string;
|
||||
engine_mode: string;
|
||||
engine_url: string;
|
||||
model: string;
|
||||
reasoning_effort: string | null;
|
||||
};
|
||||
const currentResponse = await page.request.get("/api/admin/engine-config");
|
||||
await expectResponseOk(currentResponse);
|
||||
const current = (await currentResponse.json()) as EngineConfig;
|
||||
|
||||
const capabilityResponse = await page.request.get(
|
||||
`/api/admin/engine-capabilities?engine_mode=${encodeURIComponent(current.engine_mode)}`,
|
||||
);
|
||||
await expectResponseOk(capabilityResponse);
|
||||
const capability = (await capabilityResponse.json()) as {
|
||||
models: Array<{ id: string; default_reasoning_effort?: string | null }>;
|
||||
};
|
||||
const selected =
|
||||
capability.models.find((model) => model.id === current.model) ?? capability.models[0];
|
||||
if (!selected) throw new Error("engine capability returned no selectable model");
|
||||
|
||||
const patchResponse = await page.request.patch("/api/admin/engine-config", {
|
||||
data: {
|
||||
engine_mode: current.engine_mode,
|
||||
engine_url: current.engine_url,
|
||||
model: selected.id,
|
||||
reasoning_effort:
|
||||
selected.id === current.model
|
||||
? current.reasoning_effort
|
||||
: (selected.default_reasoning_effort ?? null),
|
||||
},
|
||||
});
|
||||
await expectResponseOk(patchResponse);
|
||||
|
||||
const engineResponse = await page.request.get("/api/admin/engine-config");
|
||||
await expectResponseOk(engineResponse);
|
||||
const engine = (await engineResponse.json()) as EngineConfig;
|
||||
expect(engine).toMatchObject({ durable: true, source: "database" });
|
||||
expect(["claude_cli", "claude_api", "codex_cli", "agy_cli", "openai", "solar"]).toContain(
|
||||
engine.engine_mode,
|
||||
);
|
||||
expect(engine.engine_url).toMatch(/^https?:\/\//);
|
||||
expect(engine.model.trim().length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue