Files
tyx_AI_xhs/tests/package/postv1-runtime-assets.test.mjs
T
suyx a70b9fc241
Dada P0-A isolated Windows CI / validate-and-package (push) Waiting to run
feat(POSTV1-ASSET-ALL-16): 完整还原归档文字模板素材
2026-08-06 16:20:24 +08:00

134 lines
6.7 KiB
JavaScript

import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import complexAssetCatalog from "../../apps/web/src/generated/complex-assets.json" with { type: "json" };
import {
createRuntimeAssetManifest,
deployRuntimeAssetPlan,
readRuntimeAssetManifest,
serializeRuntimeAssetManifest,
} from "../../scripts/lib/runtime-assets.mjs";
test("committed P0-A runtime manifest covers the frozen first-version binary assets", () => {
const manifest = readRuntimeAssetManifest("config/runtime-assets-manifest.json");
assert.equal(manifest.counts.dynamic_fonts, 18);
assert.equal(manifest.counts.dynamic_images, 43);
assert.equal(manifest.counts.font_panel_items, 86);
assert.equal(manifest.counts.static_stickers, 1407);
assert.equal(manifest.counts.text_fonts >= 332, true);
assert.equal(manifest.counts.text_images >= 300, true);
assert.equal(manifest.counts.text_previews, 261);
assert.equal(manifest.entries.length, Object.values(manifest.counts).reduce((sum, count) => sum + count, 0));
const publicAssetKeys = new Set(manifest.entries.map((entry) => `${entry.resourceVersion}\u0000${entry.assetId}`));
for (const template of complexAssetCatalog.text_templates) {
for (const fontId of template.render_model.text_layers.map((layer) => layer.font_id)) {
assert.equal(publicAssetKeys.has(`p0a-complex-v1\u0000${fontId}`), true, `${template.template_id}:${fontId}`);
}
for (const imageId of template.render_model.image_layers.map((layer) => layer.asset_id)) {
assert.equal(publicAssetKeys.has(`p0a-complex-v1\u0000${imageId}`), true, `${template.template_id}:${imageId}`);
}
for (const imageId of template.render_model.particle_layers.map((layer) => layer.asset_id)) {
assert.equal(publicAssetKeys.has(`p0a-complex-v1\u0000${imageId}`), true, `${template.template_id}:${imageId}`);
}
for (const imageId of template.render_model.text_layers.flatMap((layer) => layer.fill_pattern_asset_id ? [layer.fill_pattern_asset_id] : [])) {
assert.equal(publicAssetKeys.has(`p0a-complex-v1\u0000${imageId}`), true, `${template.template_id}:${imageId}`);
}
}
assert.doesNotMatch(serializeRuntimeAssetManifest(manifest), /[A-Za-z]:[\\/]/);
});
test("runtime asset deployment creates verified hardlinks and a path-free manifest", async (t) => {
const root = await mkdtemp(join(tmpdir(), "dada-runtime-assets-"));
t.after(() => rm(root, { force: true, recursive: true }));
const sourceRoot = join(root, "source");
const assetRoot = join(root, "assets");
const sourcePath = join(sourceRoot, "sticker.png");
const bytes = Buffer.from("runtime asset fixture");
await mkdir(sourceRoot);
await writeFile(sourcePath, bytes);
const entry = {
assetId: "STK001",
mimeType: "image/png",
relativePath: "p0a-static-v1/STK001.png",
resourceVersion: "p0a-static-v1",
rootRef: "p0a_runtime_assets",
sha256: createHash("sha256").update(bytes).digest("hex"),
};
const manifest = createRuntimeAssetManifest({
counts: { dynamic_fonts: 0, dynamic_images: 0, font_panel_items: 0, static_stickers: 1, text_fonts: 0, text_images: 0, text_previews: 0 },
entries: [entry],
});
await deployRuntimeAssetPlan({ assetRoot, manifest, resources: [{ entry, sourcePath }] });
const targetPath = join(assetRoot, entry.relativePath);
const [sourceStat, targetStat] = await Promise.all([stat(sourcePath), stat(targetPath)]);
assert.equal(sourceStat.ino, targetStat.ino);
assert.deepEqual(await readFile(targetPath), bytes);
const writtenManifest = await readFile(join(assetRoot, "manifest.json"), "utf8");
assert.deepEqual(JSON.parse(writtenManifest), manifest);
assert.doesNotMatch(writtenManifest, /[A-Za-z]:[\\/]/);
});
test("runtime asset deployment refuses a mismatched existing target", async (t) => {
const root = await mkdtemp(join(tmpdir(), "dada-runtime-assets-conflict-"));
t.after(() => rm(root, { force: true, recursive: true }));
const sourcePath = join(root, "source.png");
const assetRoot = join(root, "assets");
const targetPath = join(assetRoot, "p0a-static-v1", "STK001.png");
await mkdir(join(assetRoot, "p0a-static-v1"), { recursive: true });
await writeFile(sourcePath, "expected");
await writeFile(targetPath, "unexpected");
const entry = {
assetId: "STK001",
mimeType: "image/png",
relativePath: "p0a-static-v1/STK001.png",
resourceVersion: "p0a-static-v1",
rootRef: "p0a_runtime_assets",
sha256: createHash("sha256").update("expected").digest("hex"),
};
const manifest = createRuntimeAssetManifest({
counts: { dynamic_fonts: 0, dynamic_images: 0, font_panel_items: 0, static_stickers: 1, text_fonts: 0, text_images: 0, text_previews: 0 },
entries: [entry],
});
assert.throws(
() => deployRuntimeAssetPlan({ assetRoot, manifest, resources: [{ entry, sourcePath }] }),
/asset_target_conflict/,
);
});
test("runtime asset deployment upgrades only an unchanged file covered by its previous manifest", async (t) => {
const root = await mkdtemp(join(tmpdir(), "dada-runtime-assets-upgrade-"));
t.after(() => rm(root, { force: true, recursive: true }));
const assetRoot = join(root, "assets");
const firstSourcePath = join(root, "source-v1.ttf");
const secondSourcePath = join(root, "source-v2.ttf");
const entry = (bytes) => ({
assetId: "TEXT-FONT-FIXTURE-01",
mimeType: "font/ttf",
relativePath: "p0a-complex-v1/TEXT-FONT-FIXTURE-01.ttf",
resourceVersion: "p0a-complex-v1",
rootRef: "p0a_runtime_assets",
sha256: createHash("sha256").update(bytes).digest("hex"),
});
const counts = { dynamic_fonts: 0, dynamic_images: 0, font_panel_items: 0, static_stickers: 0, text_fonts: 1, text_images: 0, text_previews: 0 };
await writeFile(firstSourcePath, "version one");
const firstEntry = entry("version one");
const firstManifest = createRuntimeAssetManifest({ counts, entries: [firstEntry] });
deployRuntimeAssetPlan({ assetRoot, manifest: firstManifest, resources: [{ entry: firstEntry, sourcePath: firstSourcePath }] });
await writeFile(secondSourcePath, "version two");
const secondEntry = entry("version two");
const secondManifest = createRuntimeAssetManifest({ counts, entries: [secondEntry] });
deployRuntimeAssetPlan({ allowManagedUpdate: true, assetRoot, manifest: secondManifest, resources: [{ entry: secondEntry, sourcePath: secondSourcePath }] });
assert.equal(await readFile(join(assetRoot, secondEntry.relativePath), "utf8"), "version two");
assert.deepEqual(readRuntimeAssetManifest(join(assetRoot, "manifest.json")), secondManifest);
});