Files
tyx_AI_xhs/tests/api/wp0-04-resource-boundary.test.ts
T

134 lines
5.1 KiB
TypeScript

import { createHash, randomUUID } from "node:crypto";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { createApp } from "../../apps/api/src/app.js";
import {
createPublicAssetResolver,
validateReadOnlyAssetRoot,
} from "../../apps/api/src/local-data-root.js";
const temporaryDirectories: string[] = [];
afterEach(() => {
for (const directory of temporaryDirectories.splice(0)) {
if (resolve(directory).startsWith(resolve(tmpdir()))) rmSync(directory, { force: true, recursive: true });
}
});
describe("TDD-WP0-DATA-001-root-validation resource boundary", () => {
it("serves a manifest resource by stable ID without exposing the absolute root or object key", async () => {
const base = mkdtempSync(join(tmpdir(), "dada-wp0-04-api-"));
temporaryDirectories.push(base);
const assetRoot = join(base, "read-only-assets");
const relativePath = "images/source.png";
const bytes = Buffer.from("synthetic png fixture");
const assetId = randomUUID();
mkdirSync(join(assetRoot, "images"), { recursive: true });
writeFileSync(join(assetRoot, relativePath), bytes);
const manifest = JSON.stringify({ assets: [{ asset_id: assetId, relative_path: relativePath }] });
writeFileSync(join(assetRoot, "catalog.json"), manifest);
const validatedRoot = validateReadOnlyAssetRoot({
dataRoot: join(base, "data"),
expectedSha256: createHash("sha256").update(manifest).digest("hex"),
manifestRelativePath: "catalog.json",
root: assetRoot,
rootRef: "fixture_assets",
});
if (!validatedRoot.ok) throw new Error(validatedRoot.reason);
const publicAssets = createPublicAssetResolver({
entries: [{
assetId,
mimeType: "image/png",
relativePath,
resourceVersion: "fixture-v1",
rootRef: "fixture_assets",
sha256: createHash("sha256").update(bytes).digest("hex"),
}],
roots: [validatedRoot],
});
const app = await createApp({ browserGate: false, publicAssets });
try {
const resourceUrl = `/api/v1/assets/public/fixture-v1/${assetId}`;
const response = await app.inject({
headers: { host: "127.0.0.1:43121" },
method: "GET",
url: resourceUrl,
});
expect(response.statusCode).toBe(200);
expect(response.headers["content-type"]).toContain("image/png");
expect(response.rawPayload).toEqual(bytes);
const wrongVersion = await app.inject({
headers: { host: "127.0.0.1:43121" },
method: "GET",
url: `/api/v1/assets/public/other-version/${assetId}`,
});
expect(wrongVersion.statusCode).toBe(404);
const browserVisible = JSON.stringify({
headers: response.headers,
resource_id: assetId,
url: resourceUrl,
});
expect(browserVisible).not.toContain(assetRoot);
expect(browserVisible).not.toContain(relativePath);
expect(browserVisible).not.toMatch(/[A-Za-z]:\\/);
const evidenceDirectory = process.env.DADA_EVIDENCE_DIR_DATA_ROOT;
if (evidenceDirectory) {
mkdirSync(evidenceDirectory, { recursive: true });
writeFileSync(join(evidenceDirectory, "response.json"), `${JSON.stringify({
content_type: response.headers["content-type"],
path_exposed: false,
resource_id: assetId,
status: "passed",
}, null, 2)}\n`);
}
} finally {
await app.close();
}
});
it("does not resolve unknown IDs or path-like route parameters", async () => {
const app = await createApp({ browserGate: false, publicAssets: createPublicAssetResolver({ entries: [], roots: [] }) });
try {
for (const target of [randomUUID(), "..%2F..%2Fsecret", "C:%5CUsers%5Csecret"]) {
const response = await app.inject({
headers: { host: "127.0.0.1:43121" },
method: "GET",
url: `/api/v1/assets/public/fixture-v1/${target}`,
});
expect(response.statusCode).toBe(404);
expect(response.body).not.toMatch(/[A-Za-z]:\\/);
}
} finally {
await app.close();
}
});
});
describe("TDD-WP0-DATA-002-no-backup-migration route inventory", () => {
it("contains no product backup, archive, business import, migration or recovery route", async () => {
const app = await createApp({ browserGate: false });
try {
const routes = app.printRoutes();
for (const forbidden of ["backup", "archive", "project-package", "business-import", "migration", "restore", "recovery"]) {
expect(routes.toLowerCase()).not.toContain(forbidden);
}
const evidenceDirectory = process.env.DADA_EVIDENCE_DIR_NO_TRANSFER;
if (evidenceDirectory) {
mkdirSync(evidenceDirectory, { recursive: true });
writeFileSync(join(evidenceDirectory, "route-inventory.json"), `${JSON.stringify({
forbidden_routes: [],
status: "passed",
}, null, 2)}\n`);
}
} finally {
await app.close();
}
});
});