test: complete WP4-07 visual and performance budgets
Dada P0-A isolated Windows CI / validate-and-package (push) Failing after 2m34s
Dada P0-A isolated Windows CI / validate-and-package (push) Failing after 2m34s
This commit is contained in:
@@ -53,6 +53,10 @@ export const WP4_07_DYNAMIC_VALUES = Object.freeze({
|
||||
|
||||
const timestamp = "2026-07-27T04:00:00.000Z";
|
||||
const redResourceVersion = "wp4-07-red-contract-v1";
|
||||
export const WP4_07_REAL_RESOURCE_VERSIONS = Object.freeze({
|
||||
complex: "p0a-complex-v1",
|
||||
static: "p0a-static-v1",
|
||||
});
|
||||
const palette = ["#111111", "#F2F400", "#1769AA", "#C92A24", "#FFFFFF"];
|
||||
const textTemplateIds = [
|
||||
"FLOWER001", "FLOWER003", "FLOWER005", "FLOWER008", "H003", "H004",
|
||||
@@ -100,9 +104,14 @@ function common(index, type, templateOrAssetId, resourceVersion) {
|
||||
};
|
||||
}
|
||||
|
||||
export function createWp407CanvasFixture(resourceVersion = redResourceVersion) {
|
||||
function releaseVersions(input = redResourceVersion) {
|
||||
return typeof input === "string" ? { complex: input, static: input } : input;
|
||||
}
|
||||
|
||||
export function createWp407CanvasFixture(resourceVersions = redResourceVersion) {
|
||||
const versions = releaseVersions(resourceVersions);
|
||||
const text = textTemplateIds.map((templateId, offset) => ({
|
||||
...common(offset + 1, "text_template", templateId, resourceVersion),
|
||||
...common(offset + 1, "text_template", templateId, versions.complex),
|
||||
content: offset === 0 ? "DADA\n视觉预算" : `固定文字 ${String(offset + 1).padStart(2, "0")}`,
|
||||
font_size: 48,
|
||||
position: offset === 0 ? { x: 0.5, y: 0.5 } : position(offset, 4, 0.09),
|
||||
@@ -123,7 +132,7 @@ export function createWp407CanvasFixture(resourceVersion = redResourceVersion) {
|
||||
},
|
||||
}));
|
||||
const stickers = Array.from({ length: 24 }, (_, offset) => ({
|
||||
...common(offset + 13, "static_sticker", `STK${String(offset + 1).padStart(3, "0")}`, resourceVersion),
|
||||
...common(offset + 13, "static_sticker", `STK${String(offset + 1).padStart(3, "0")}`, versions.static),
|
||||
opacity: 0.84 + (offset % 4) * 0.04,
|
||||
position: position(offset, 6, 0.39),
|
||||
rotation: (offset % 5 - 2) * 6,
|
||||
@@ -131,7 +140,7 @@ export function createWp407CanvasFixture(resourceVersion = redResourceVersion) {
|
||||
style_parameters: { flip_horizontal: offset % 7 === 0 },
|
||||
}));
|
||||
const colors = colorCards.map(([cardId, styleId], offset) => ({
|
||||
...common(offset + 37, "color_card", cardId, resourceVersion),
|
||||
...common(offset + 37, "color_card", cardId, versions.complex),
|
||||
colors: [...palette],
|
||||
position: { x: 0.16 + offset * 0.22, y: 0.83 },
|
||||
scale: { x: 1.25, y: 1.25 },
|
||||
@@ -139,7 +148,7 @@ export function createWp407CanvasFixture(resourceVersion = redResourceVersion) {
|
||||
style_parameters: { palette_algorithm_version: "mmcq-v1" },
|
||||
}));
|
||||
const dynamics = dynamicIds.map((dynamicId, offset) => ({
|
||||
...common(offset + 41, "dynamic_sticker", dynamicId, resourceVersion),
|
||||
...common(offset + 41, "dynamic_sticker", dynamicId, versions.complex),
|
||||
dynamic_fields: { ...WP4_07_DYNAMIC_VALUES },
|
||||
formatted_value: dynamicId === "DYN012" ? "12:00 PM" : "@dada_fixture",
|
||||
position: { x: 0.09 + (offset % 5) * 0.205, y: 0.9 + Math.floor(offset / 5) * 0.06 },
|
||||
@@ -162,8 +171,8 @@ export function createWp407CanvasFixture(resourceVersion = redResourceVersion) {
|
||||
};
|
||||
}
|
||||
|
||||
export function wp407FixtureContract() {
|
||||
const canvas = createWp407CanvasFixture();
|
||||
export function wp407FixtureContract(resourceVersions = redResourceVersion) {
|
||||
const canvas = createWp407CanvasFixture(resourceVersions);
|
||||
return {
|
||||
canvas,
|
||||
dynamic_values: WP4_07_DYNAMIC_VALUES,
|
||||
@@ -174,12 +183,12 @@ export function wp407FixtureContract() {
|
||||
};
|
||||
}
|
||||
|
||||
export function wp407FixtureSha256() {
|
||||
return createHash("sha256").update(JSON.stringify(wp407FixtureContract())).digest("hex").toUpperCase();
|
||||
export function wp407FixtureSha256(resourceVersions = redResourceVersion) {
|
||||
return createHash("sha256").update(JSON.stringify(wp407FixtureContract(resourceVersions))).digest("hex").toUpperCase();
|
||||
}
|
||||
|
||||
export function assertWp407Fixture() {
|
||||
const fixture = wp407FixtureContract();
|
||||
export function assertWp407Fixture(resourceVersions = redResourceVersion) {
|
||||
const fixture = wp407FixtureContract(resourceVersions);
|
||||
const counts = Object.fromEntries(["text_template", "static_sticker", "color_card", "dynamic_sticker"].map((type) => [
|
||||
type,
|
||||
fixture.canvas.elements.filter((element) => element.type === type).length,
|
||||
@@ -195,12 +204,12 @@ export function assertWp407Fixture() {
|
||||
if (fixture.environment.viewport.width !== 1920 || fixture.environment.viewport.height !== 1080 || fixture.environment.device_scale_factor !== 1) {
|
||||
throw new Error("candidate viewport or DPR changed");
|
||||
}
|
||||
return { counts, fixture_sha256: wp407FixtureSha256() };
|
||||
return { counts, fixture_sha256: wp407FixtureSha256(resourceVersions) };
|
||||
}
|
||||
|
||||
export const WP4_07_RED_RESOURCE_VERSION = redResourceVersion;
|
||||
export const WP4_07_REQUIRED_FONT_IDS = Object.freeze([
|
||||
"FONT005", "FONT008", "FONT011", "FONT022", "FONT027", "FONT039", "FONT043", "FONT046", "FONT052", "FONT081",
|
||||
"FONT005", "FONT008", "FONT011", "FONT021", "FONT022", "FONT027", "FONT039", "FONT043", "FONT046", "FONT052", "FONT081",
|
||||
"15974853bc3294ef68e7e6d58fe74fd7", "46f8336813e4c48d06a1aef294fdccf6",
|
||||
"53ca6b704728520da50c145eabb2e635", "cca5efc0e02fb1bf62349bd68ef30fc1",
|
||||
"dd25b35dcb7ba4476cbaa9a9592e39e2", "e4210c9872f0c279b35273f230809821",
|
||||
|
||||
@@ -10,7 +10,10 @@ test("WP5 lineage recognizes tasks already merged into later remote heads", () =
|
||||
const gate = inspectWp5TaskLineage({
|
||||
"codex/wp5-03": sha("3"),
|
||||
"codex/wp5-04": sha("4"),
|
||||
}, [commit(4), commit(3), commit(2), commit(1)]);
|
||||
}, {
|
||||
"codex/wp5-03": [commit(3), commit(2), commit(1)],
|
||||
"codex/wp5-04": [commit(4), commit(2), commit(1)],
|
||||
});
|
||||
|
||||
assert.equal(gate.complete, false);
|
||||
assert.equal(gate.candidate_baseline_branch, "codex/wp5-04");
|
||||
@@ -19,13 +22,21 @@ test("WP5 lineage recognizes tasks already merged into later remote heads", () =
|
||||
assert.equal(gate.task_shas["TASK-WP5-02"], sha("2"));
|
||||
});
|
||||
|
||||
test("WP5 lineage becomes complete only at a wp5-07 head containing every task", () => {
|
||||
const heads = { "codex/wp5-07": sha("7") };
|
||||
const complete = inspectWp5TaskLineage(heads, Array.from({ length: 7 }, (_, index) => commit(7 - index)));
|
||||
const incomplete = inspectWp5TaskLineage(heads, [commit(7), commit(6), commit(5), commit(4), commit(3), commit(2)]);
|
||||
test("WP5 lineage accepts independently pushed terminal branches without rewriting their heads", () => {
|
||||
const heads = Object.fromEntries(Array.from({ length: 5 }, (_, index) => [`codex/wp5-0${index + 3}`, sha(String(index + 3))]));
|
||||
const histories = {
|
||||
"codex/wp5-03": [commit(3), commit(2), commit(1)],
|
||||
"codex/wp5-04": [commit(4), commit(2), commit(1)],
|
||||
"codex/wp5-05": [commit(5), commit(4), commit(2), commit(1)],
|
||||
"codex/wp5-06": [commit(6), commit(5), commit(4), commit(2), commit(1)],
|
||||
"codex/wp5-07": [commit(7), commit(4), commit(2), commit(1)],
|
||||
};
|
||||
const complete = inspectWp5TaskLineage(heads, histories);
|
||||
const incomplete = inspectWp5TaskLineage(heads, { ...histories, "codex/wp5-03": [commit(2), commit(1)] });
|
||||
|
||||
assert.equal(complete.complete, true);
|
||||
assert.deepEqual(complete.missing_tasks, []);
|
||||
assert.deepEqual(complete.terminal_branch_shas, heads);
|
||||
assert.equal(incomplete.complete, false);
|
||||
assert.deepEqual(incomplete.missing_tasks, ["TASK-WP5-01"]);
|
||||
assert.deepEqual(incomplete.missing_tasks, ["TASK-WP5-03"]);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { basename, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
||||
|
||||
import { WP4_07_REAL_RESOURCE_VERSIONS } from "./wp4-07-fixture.mjs";
|
||||
|
||||
const dynamicFontTemplates = Object.freeze({
|
||||
"15974853bc3294ef68e7e6d58fe74fd7": "DYN002",
|
||||
"46f8336813e4c48d06a1aef294fdccf6": "DYN016",
|
||||
"53ca6b704728520da50c145eabb2e635": "DYN007",
|
||||
cca5efc0e02fb1bf62349bd68ef30fc1: "DYN015",
|
||||
dd25b35dcb7ba4476cbaa9a9592e39e2: "DYN001",
|
||||
e4210c9872f0c279b35273f230809821: "DYN011",
|
||||
f4bfd4132df2d6be97ceabadf3853505: "DYN008",
|
||||
});
|
||||
|
||||
const dynamicImages = Object.freeze({
|
||||
"DYN001-image28": ["DYN001", "image28.png"],
|
||||
"DYN002-image29": ["DYN002", "image29.png"],
|
||||
"DYN003-image30": ["DYN003", "image30.png"],
|
||||
"DYN004-image32": ["DYN004", "image32.png"],
|
||||
"DYN008-backendui0": ["DYN008", "backendui0.png"],
|
||||
"DYN011-backendui0": ["DYN011", "backendui0.png"],
|
||||
"DYN015-imager2": ["DYN015", "imager2_2.png"],
|
||||
"DYN016-image21": ["DYN016", "image21.png"],
|
||||
});
|
||||
|
||||
function sha256(path) {
|
||||
return createHash("sha256").update(readFileSync(path)).digest("hex").toUpperCase();
|
||||
}
|
||||
|
||||
function walkFiles(directory) {
|
||||
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
||||
const path = join(directory, entry.name);
|
||||
return entry.isDirectory() ? walkFiles(path) : [path];
|
||||
});
|
||||
}
|
||||
|
||||
function firstFile(directory, predicate) {
|
||||
const path = walkFiles(directory).find(predicate);
|
||||
if (!path) throw new Error(`WP4_07_REAL_ASSET_FILE_MISSING:${basename(directory)}`);
|
||||
return path;
|
||||
}
|
||||
|
||||
function inside(root, path) {
|
||||
const delta = relative(resolve(root), resolve(path));
|
||||
return delta !== ".." && !delta.startsWith(`..${sep}`) && !isAbsolute(delta);
|
||||
}
|
||||
|
||||
function contentType(path) {
|
||||
const extension = extname(path).toLowerCase();
|
||||
if (extension === ".png") return "image/png";
|
||||
if (extension === ".otf") return "font/otf";
|
||||
if (extension === ".woff") return "font/woff";
|
||||
if (extension === ".woff2") return "font/woff2";
|
||||
return "font/ttf";
|
||||
}
|
||||
|
||||
function assetRecord(assetId, path, sourceReference, expectedSha256) {
|
||||
if (!existsSync(path) || !statSync(path).isFile()) throw new Error(`WP4_07_REAL_ASSET_UNAVAILABLE:${assetId}`);
|
||||
const actualSha256 = sha256(path);
|
||||
if (expectedSha256 && actualSha256 !== expectedSha256) throw new Error(`WP4_07_REAL_ASSET_HASH_MISMATCH:${assetId}`);
|
||||
return {
|
||||
asset_id: assetId,
|
||||
bytes: statSync(path).size,
|
||||
content_type: contentType(path),
|
||||
path,
|
||||
sha256: actualSha256,
|
||||
source_reference: sourceReference,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadWp407RealAssets() {
|
||||
const manifestPath = resolve(process.env.DADA_WP4_07_FINAL_ASSET_MANIFEST ?? "");
|
||||
if (!process.env.DADA_WP4_07_FINAL_ASSET_MANIFEST || !existsSync(manifestPath)) throw new Error("WP4_07_FINAL_ASSET_MANIFEST_REQUIRED");
|
||||
const handoffPath = resolve(process.env.DADA_COMPLEX_ASSET_MANIFEST ?? join(homedir(), "Desktop", "sticker_web_handoff", "sticker_web_catalog_manifest.json"));
|
||||
const staticRoot = resolve(process.env.DADA_STATIC_STICKER_ROOT ?? join(homedir(), "Desktop", "贴纸素材"));
|
||||
const backgroundPath = resolve(process.env.DADA_WP4_07_BACKGROUND_PATH ?? join(homedir(), "Documents", "贴纸脚本", "time_01_input_20260716.png"));
|
||||
if (!existsSync(handoffPath) || !existsSync(staticRoot)) throw new Error("WP4_07_REAL_ARCHIVE_ROOT_REQUIRED");
|
||||
|
||||
const manifestRaw = readFileSync(manifestPath, "utf8");
|
||||
const manifest = JSON.parse(manifestRaw);
|
||||
const handoff = JSON.parse(readFileSync(handoffPath, "utf8"));
|
||||
const collectionRoots = Object.fromEntries(handoff.collections.map((collection) => [collection.id, resolve(collection.root)]));
|
||||
const fontRoot = collectionRoots.font_panel;
|
||||
const dynamicRoot = collectionRoots.interactive_stickers;
|
||||
if (!fontRoot || !dynamicRoot) throw new Error("WP4_07_REAL_ARCHIVE_COLLECTION_REQUIRED");
|
||||
|
||||
const publicAssets = new Map();
|
||||
for (const item of manifest.assets.font_panel_items) {
|
||||
const packageDirectory = resolve(fontRoot, item.canonical_resource_reference.path);
|
||||
if (!inside(fontRoot, packageDirectory)) throw new Error(`WP4_07_UNSAFE_FONT_REFERENCE:${item.canonical_id}`);
|
||||
const path = firstFile(resolve(packageDirectory, "font_files"), (candidate) => /\.(?:otf|ttf|woff2?|ztf)$/i.test(candidate));
|
||||
publicAssets.set(item.canonical_id, assetRecord(item.canonical_id, path, `font_panel/${item.canonical_id}/${basename(path)}`));
|
||||
}
|
||||
|
||||
for (const item of manifest.assets.static_stickers) {
|
||||
const path = resolve(staticRoot, item.relative_path);
|
||||
if (!inside(staticRoot, path)) throw new Error(`WP4_07_UNSAFE_STATIC_REFERENCE:${item.stable_id}`);
|
||||
publicAssets.set(item.stable_id, assetRecord(item.stable_id, path, `static/${item.stable_id}`, item.sha256));
|
||||
}
|
||||
|
||||
for (const [assetId, templateId] of Object.entries(dynamicFontTemplates)) {
|
||||
const directory = resolve(dynamicRoot, "templates", templateId, "fonts", assetId);
|
||||
const path = firstFile(directory, (candidate) => /\.(?:otf|ttf|woff2?|ztf)$/i.test(candidate));
|
||||
publicAssets.set(assetId, assetRecord(assetId, path, `interactive/${templateId}/fonts/${assetId}/${basename(path)}`));
|
||||
}
|
||||
for (const [assetId, [templateId, filename]] of Object.entries(dynamicImages)) {
|
||||
const path = resolve(dynamicRoot, "templates", templateId, "resource", filename);
|
||||
publicAssets.set(assetId, assetRecord(assetId, path, `interactive/${templateId}/resource/${filename}`));
|
||||
}
|
||||
|
||||
const background = assetRecord("private-background", backgroundPath, "private/background/time_01_input_20260716.png");
|
||||
const manifestSha256 = createHash("sha256").update(manifestRaw).digest("hex").toUpperCase();
|
||||
return {
|
||||
background,
|
||||
evidence: {
|
||||
asset_count: publicAssets.size,
|
||||
assets: [...publicAssets.values()].map(({ asset_id, bytes, content_type, sha256: hash, source_reference }) => ({
|
||||
asset_id, bytes, content_type, sha256: hash, source_reference,
|
||||
})),
|
||||
background: { bytes: background.bytes, content_type: background.content_type, sha256: background.sha256, source_reference: background.source_reference },
|
||||
manifest_sha256: manifestSha256,
|
||||
release_version: manifest.release_version,
|
||||
resource_versions: WP4_07_REAL_RESOURCE_VERSIONS,
|
||||
source_mutations: 0,
|
||||
},
|
||||
publicAssets,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user