feat: release Find-Word v0.2.3
This commit is contained in:
+75
-14
@@ -4,44 +4,89 @@ import { createQuestions } from "../../src/game";
|
||||
import { isAudioRequest } from "./audio-requests";
|
||||
|
||||
// 과거 evidence의 검증 스크립트는 보존하고, query URL 전수 검사를 정규 suite로 옮긴다.
|
||||
test("intro·목표 101개 versioned WAV를 실제 Chrome에서 해시·디코드 검증한다", async ({
|
||||
test("음성·현장음 109개 versioned WAV를 실제 Chrome에서 해시·디코드 검증한다", async ({
|
||||
page,
|
||||
browser,
|
||||
}, info) => {
|
||||
const external: string[] = [];
|
||||
const errors: string[] = [];
|
||||
const failed: string[] = [];
|
||||
const requests: string[] = [];
|
||||
page.on("request", (request) => {
|
||||
const url = new URL(request.url());
|
||||
if (url.origin !== "http://127.0.0.1:4173") external.push(url.href);
|
||||
if (isAudioRequest(url)) requests.push(url.href);
|
||||
if (isAudioRequest(url) || url.pathname.startsWith("/ambience/"))
|
||||
requests.push(url.href);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") errors.push(message.text());
|
||||
});
|
||||
page.on("requestfailed", (request) => failed.push(request.url()));
|
||||
page.on("pageerror", (error) => errors.push(error.message));
|
||||
await page.goto("/");
|
||||
await expect(page).toHaveTitle("단어를 맞춰라 · Find-Word v0.1.1");
|
||||
await expect(page.locator(".version")).toHaveText("v0.1.1");
|
||||
await expect(page).toHaveTitle("단어를 맞춰라 · Find-Word v0.2.3");
|
||||
await expect(page.locator(".version")).toHaveText("v0.2.3");
|
||||
const plan = JSON.parse(
|
||||
readFileSync(".agent/AMBIENCE-SOURCES-v0.2.3.json", "utf8"),
|
||||
);
|
||||
const published = await page.evaluate(async () => ({
|
||||
manifest: await (await fetch("/ambience/manifest.json?v=0.2.3")).json(),
|
||||
notice: await (await fetch("/ambience/AMBIENCE_NOTICES.md?v=0.2.3")).text(),
|
||||
}));
|
||||
expect(published.manifest).toEqual(
|
||||
JSON.parse(readFileSync("public/ambience/manifest.json", "utf8")),
|
||||
);
|
||||
for (const asset of plan.assets) {
|
||||
const start = asset.selectedSegmentStartSeconds;
|
||||
expect(Number.isFinite(start)).toBe(true);
|
||||
expect(start).toBeGreaterThanOrEqual(0);
|
||||
expect(start + 12).toBeLessThanOrEqual(asset.directMediaDurationSeconds);
|
||||
const entry = published.manifest.files.find(
|
||||
(e: { environmentId: string }) => e.environmentId === asset.environmentId,
|
||||
);
|
||||
expect(entry.selectedSegmentStartSeconds).toBe(start);
|
||||
expect(entry.conversion.startSeconds).toBe(start);
|
||||
expect(entry.conversion.durationSeconds).toBe(12);
|
||||
const section = published.notice
|
||||
.split("## ")
|
||||
.find((s: string) =>
|
||||
s.startsWith(`${asset.displayName} — ${asset.environmentId}\n`),
|
||||
);
|
||||
expect(section).toContain(`source seconds ${start}–${start + 12}.`);
|
||||
}
|
||||
// intro는 테스트에서 URL을 재조립하지 않고 배포 앱의 실제 요청을 관찰한다.
|
||||
const introRequest = page.waitForRequest(
|
||||
(request) => new URL(request.url()).pathname === "/audio/intro.wav",
|
||||
);
|
||||
await page.getByRole("button", { name: "게임 시작 · 소리 듣기" }).click();
|
||||
const introUrl = (await introRequest).url();
|
||||
await page.getByRole("button", { name: "처음으로" }).click();
|
||||
await expect(page.getByRole("group", { name: "답 선택" })).toBeVisible({
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByRole("button", { name: "문제 풀이 종료" }).click();
|
||||
await page.getByRole("button", { name: "처음 화면으로" }).click();
|
||||
const targetUrls = [
|
||||
...new Set(
|
||||
Array.from({ length: 200 }, (_, seed) =>
|
||||
Array.from({ length: 1000 }, (_, seed) =>
|
||||
createQuestions(seed, "mixed").map((question) => question.audioUrl),
|
||||
).flat(),
|
||||
),
|
||||
];
|
||||
expect(targetUrls).toHaveLength(100);
|
||||
const ambience = JSON.parse(
|
||||
readFileSync("public/ambience/manifest.json", "utf8"),
|
||||
);
|
||||
const urls = [
|
||||
introUrl,
|
||||
...ambience.files.map(
|
||||
(entry: { file: string }) =>
|
||||
new URL(`/ambience/${entry.file}?v=0.2.3`, page.url()).href,
|
||||
),
|
||||
...targetUrls.map((url) => new URL(url, page.url()).href),
|
||||
];
|
||||
expect(
|
||||
new Set(urls.map((url) => new URL(url).searchParams.get("v"))),
|
||||
).toEqual(new Set(["0.1.1"]));
|
||||
).toEqual(new Set(["0.2.3"]));
|
||||
expect(
|
||||
urls.every((url) => new URL(url).origin === new URL(page.url()).origin),
|
||||
).toBe(true);
|
||||
@@ -51,9 +96,24 @@ test("intro·목표 101개 versioned WAV를 실제 Chrome에서 해시·디코
|
||||
version: string;
|
||||
files: { file: string; sha256: string; bytes: number; duration: number }[];
|
||||
};
|
||||
expect(manifest.version).toBe("0.1.1");
|
||||
expect(manifest.version).toBe("0.2.3");
|
||||
// 두 manifest의 파일명 충돌 없이 경로를 명시해 109개 응답을 독립 대조한다.
|
||||
const entries = [
|
||||
...manifest.files.map((entry) => ({
|
||||
...entry,
|
||||
path: `/audio/${entry.file}`,
|
||||
})),
|
||||
...ambience.files.map(
|
||||
(entry: {
|
||||
file: string;
|
||||
sha256: string;
|
||||
bytes: number;
|
||||
duration: number;
|
||||
}) => ({ ...entry, path: `/ambience/${entry.file}` }),
|
||||
),
|
||||
];
|
||||
expect(urls.map((url) => new URL(url).pathname).sort()).toEqual(
|
||||
manifest.files.map((entry) => `/audio/${entry.file}`).sort(),
|
||||
entries.map((entry) => entry.path).sort(),
|
||||
);
|
||||
requests.length = 0;
|
||||
const rows = await page.evaluate(
|
||||
@@ -63,7 +123,7 @@ test("intro·목표 101개 versioned WAV를 실제 Chrome에서 해시·디코
|
||||
for (const url of urls) {
|
||||
// query를 파일명으로 취급하지 않는다. 원문 파일과 응답 해시를 독립 대조한다.
|
||||
const entry = entries.find(
|
||||
(entry) => `/audio/${entry.file}` === new URL(url).pathname,
|
||||
(entry) => entry.path === new URL(url).pathname,
|
||||
)!;
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw Error(`${url}: HTTP ${response.status}`);
|
||||
@@ -103,22 +163,23 @@ test("intro·목표 101개 versioned WAV를 실제 Chrome에서 해시·디코
|
||||
}
|
||||
return rows;
|
||||
},
|
||||
{ urls, entries: manifest.files },
|
||||
{ urls, entries },
|
||||
);
|
||||
expect(rows).toHaveLength(101);
|
||||
expect(rows).toHaveLength(109);
|
||||
expect([...requests].sort()).toEqual([...urls].sort());
|
||||
expect(external).toEqual([]);
|
||||
expect(errors).toEqual([]);
|
||||
expect(failed).toEqual([]);
|
||||
const path = info.outputPath("browser-audio-integrity.json");
|
||||
writeFileSync(
|
||||
path,
|
||||
JSON.stringify(
|
||||
{ chrome: browser.version(), external, errors, files: rows },
|
||||
{ chrome: browser.version(), external, errors, failed, files: rows },
|
||||
null,
|
||||
2,
|
||||
) + "\n",
|
||||
);
|
||||
await info.attach("101 versioned WAV integrity", {
|
||||
await info.attach("109 versioned WAV integrity", {
|
||||
path,
|
||||
contentType: "application/json",
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user