PII 표시 토큰 누출 보정

This commit is contained in:
Yun Chan 2026-08-09 21:04:29 +09:00
parent 61a41d1f08
commit 7cece90e33
19 changed files with 284 additions and 197 deletions

View file

@ -145,8 +145,8 @@ async function readMetrics(page: Page): Promise<DashboardMetrics> {
function expectDerivedMetrics(metrics: DashboardMetrics) {
expect(metrics.total).toBeGreaterThan(0);
expect(metrics.done).toBe(32);
expect(metrics.doing).toBe(3);
expect(metrics.done).toBe(33);
expect(metrics.doing).toBe(2);
expect(metrics.planned).toBe(0);
expect(metrics.total).toBe(metrics.done + metrics.doing + metrics.planned);
expect(metrics.ownerBoard).toBe(

View file

@ -192,6 +192,24 @@ async function routeSessionFixtureApi(page: Page, options: SessionFixtureOptions
});
});
await page.route(
`**/api/sessions/${fixtureSessionId}/multimodal-alliance/consent`,
async (route) => {
const consentBody = route.request().postDataJSON() as Record<string, unknown>;
await route.fulfill({
status: 201,
contentType: "application/json",
body: JSON.stringify({
submission_id: consentBody.submission_id,
consent_snapshot_id: "00000000-0000-4000-8000-000000000778",
consent_status: "granted",
deletion_request_id: null,
idempotent_replay: false,
}),
});
},
);
await page.route(`**/api/sessions/${fixtureSessionId}/alliance-pulses`, async (route) => {
await route.fulfill({
status: 200,
@ -319,6 +337,14 @@ async function routeSessionFixtureApi(page: Page, options: SessionFixtureOptions
return { liveCoachRequests, consentRequests };
}
async function acceptVoiceInputConsent(page: Page) {
const consentDialog = page.getByRole("dialog", { name: "음성 입력을 사용하기 전에" });
await expect(consentDialog).toBeVisible();
await expect(consentDialog).toContainText("원음은 보존하지 않습니다");
await consentDialog.getByRole("button", { name: "동의하고 마이크 켜기" }).click();
await expect(consentDialog).toHaveCount(0);
}
/**
* ·WebSocket mock session-mvp.spec.ts .
* audio_end transcriptreplystate:speaking
@ -615,6 +641,56 @@ test.describe("full sweep — counseling session", () => {
await expect(compose).toHaveAttribute("placeholder", "종료된 회기입니다.");
});
test("naturalizes privacy placeholders when a persisted session is resumed", async ({
page,
}) => {
const startedAt = new Date(Date.now() - 60_000).toISOString();
await routeSessionFixtureApi(page, {
detailOverrides: {
started_at: startedAt,
turns: [
{
speaker: "learner",
text:
"[NAME] 이야기와 [PHONE]·[EMAIL]·[RRN]·[NUMID]·[DATE]·[MONEY]·[ADDR]·[ADDRESS]를 확인할까요?",
turn_seq: 1,
created_at: new Date(Date.now() - 50_000).toISOString(),
},
{
speaker: "client",
text: "여기서 뭘 할 수 있게 [NAME]는 건지 잘 [NAME]는데요. [ORG]에서 오라고 했어요.",
turn_seq: 2,
created_at: new Date(Date.now() - 40_000).toISOString(),
},
],
},
});
await page.goto(`/learn/session/${fixtureSessionId}`);
const transcript = page.locator(".sx-transcript");
await expect(transcript.locator(".sx-utt")).toHaveCount(2);
for (const token of [
"[NAME]",
"[ORG]",
"[PHONE]",
"[EMAIL]",
"[RRN]",
"[NUMID]",
"[DATE]",
"[MONEY]",
"[ADDR]",
"[ADDRESS]",
]) {
await expect(transcript).not.toContainText(token);
}
await expect(transcript).toContainText("익명 내담자 이야기");
await expect(transcript).toContainText(
"뭘 할 수 있게 되는 건지 잘 모르겠는데요",
);
await expect(transcript).toContainText("소속 기관에서 오라고 했어요");
});
// checklist: session-transcript-autoscroll
test("releases autoscroll when scrolling up, jumps back with the latest button, and follows new turns", async ({
page,
@ -966,6 +1042,7 @@ test.describe("full sweep — counseling session", () => {
const micButton = page.getByRole("button", { name: "마이크 켜기" });
await expect(micButton).toBeEnabled();
await micButton.click();
await acceptVoiceInputConsent(page);
await page.getByRole("button", { name: "발화 보내기" }).click();
// reply + state:speaking → 재생 중에만 '건너뛰기'가 노출된다.
@ -1018,11 +1095,14 @@ test.describe("full sweep — counseling session", () => {
});
// checklist: session-keyboard-shortcuts
test("toggles pause with P and mic with Space only outside text inputs", async ({ page }) => {
test("toggles pause with P and mic with Alt+M only outside text inputs", async ({ page }) => {
await routeSessionFixtureApi(page);
await installVoiceSpeakingFixture(page, voiceTranscript, voiceClientReply);
await startFixtureSession(page);
await page.evaluate(() => {
if (document.activeElement instanceof HTMLElement) document.activeElement.blur();
});
// P — 일시정지 토글.
await page.keyboard.press("p");
@ -1037,9 +1117,10 @@ test.describe("full sweep — counseling session", () => {
await expect(compose).toHaveValue("p");
await expect(page.getByRole("button", { name: "일시정지" })).toBeVisible();
// 포커스를 입력창 밖으로 옮긴 뒤 Space — 마이크 시작.
// 포커스를 입력창 밖으로 옮긴 뒤 Alt+M — 마이크 시작.
await compose.blur();
await page.keyboard.press("Space");
await page.keyboard.press("Alt+m");
await acceptVoiceInputConsent(page);
await expect(page.getByRole("button", { name: "발화 보내기" })).toBeVisible();
});
});