Files
tyx_AI_xhs/tests/unit/wp5-04-asset-release-manifest.test.ts
T
suyx f1bebab611
Dada P0-A isolated Windows CI / validate-and-package (push) Failing after 59s
feat: isolate asset release access classes (TASK-WP5-04)
2026-08-03 19:02:04 +08:00

102 lines
3.8 KiB
TypeScript

import { createHash } from "node:crypto";
import { describe, expect, it } from "vitest";
import { createAssetReleaseManifest } from "../../packages/asset-release-manifest/src/index.js";
const releaseVersion = "asset-20260803.1";
const publicId = "7f0c9530-a7d9-4bf1-8c65-0e9298dd04ac";
const previewId = "ab18fd72-60e1-44e3-a9a0-3dfccb12e17c";
const privateId = "e3792605-5252-4d3b-a101-827408ab3515";
function fixture() {
return createAssetReleaseManifest({
items: [
{
access_class: "public_release_asset",
cache_kind: "thumbnail",
content: Buffer.from("public-content"),
mime_type: "image/png",
relative_path: "public/thumbnails/FLOWER001.png",
resource_id: publicId,
root_ref: "canonical-assets",
},
{
access_class: "internal_preview_asset",
content: Buffer.from("preview-content"),
mime_type: "image/webp",
relative_path: "preview/batch-7/FLOWER009.webp",
resource_id: previewId,
root_ref: "canonical-assets",
},
{
access_class: "private_user_asset",
content: Buffer.from("private-content"),
mime_type: "image/png",
owner_id: "owner-1",
relative_path: "private/owner-1/generated.png",
resource_id: privateId,
root_ref: "managed-assets",
},
],
release_version: releaseVersion,
});
}
describe("TASK-WP5-04 immutable asset release manifest", () => {
it("projects only the selected access class and never exposes source paths", () => {
const manifest = fixture();
const projected = manifest.project("public_release_asset", releaseVersion);
expect(Object.isFrozen(projected)).toBe(true);
expect(projected?.manifest_sha256).toMatch(/^[0-9a-f]{64}$/);
expect(projected?.items).toEqual([expect.objectContaining({
access_class: "public_release_asset",
resource_id: publicId,
sha256: createHash("sha256").update("public-content").digest("hex"),
url: `/api/v1/assets/public/${releaseVersion}/${publicId}`,
})]);
const serialized = JSON.stringify(projected);
expect(serialized).not.toContain(previewId);
expect(serialized).not.toContain(privateId);
expect(serialized).not.toContain("relative_path");
expect(serialized).not.toContain("root_ref");
expect(serialized).not.toMatch(/[A-Z]:\\\\/i);
});
it("keeps resource identifiers opaque, verifies file hashes, and rejects unsafe manifests", () => {
const manifest = fixture();
expect(manifest.read("public_release_asset", releaseVersion, publicId)?.bytes).toEqual(Buffer.from("public-content"));
expect(manifest.read("public_release_asset", releaseVersion, previewId)).toBeUndefined();
expect(manifest.project("private_user_asset", releaseVersion, { ownerId: "owner-1" })?.items)
.toEqual([expect.objectContaining({ resource_id: privateId })]);
expect(manifest.project("private_user_asset", releaseVersion, { ownerId: "owner-2" })?.items).toEqual([]);
expect(() => createAssetReleaseManifest({
items: [{
access_class: "public_release_asset",
cache_kind: "thumbnail",
content: Buffer.from("tampered"),
mime_type: "image/png",
relative_path: "public/tampered.png",
resource_id: publicId,
root_ref: "canonical-assets",
sha256: "0".repeat(64),
}],
release_version: releaseVersion,
})).toThrow(/sha-?256/i);
expect(() => createAssetReleaseManifest({
items: [{
access_class: "public_release_asset",
cache_kind: "thumbnail",
content: Buffer.from("unsafe"),
mime_type: "image/png",
relative_path: "/outside/private.png",
resource_id: publicId,
root_ref: "canonical-assets",
}],
release_version: releaseVersion,
})).toThrow(/relative path/i);
});
});