119 lines
3.6 KiB
TypeScript
119 lines
3.6 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
|
|
import { checkToolboxDist } from "../src/index.js";
|
|
|
|
const temporaryDirectories: string[] = [];
|
|
|
|
const manifest = (overrides: Record<string, unknown> = {}) => ({
|
|
schemaVersion: 1,
|
|
id: "de.add-ideas.example",
|
|
name: "Example",
|
|
version: "1.2.3",
|
|
description: "Example app",
|
|
entry: "./",
|
|
icon: "./icon.svg",
|
|
categories: ["example"],
|
|
tags: ["test"],
|
|
integration: {
|
|
contextVersion: 1,
|
|
launchModes: ["navigate"],
|
|
embedding: "unsupported",
|
|
},
|
|
requirements: {
|
|
secureContext: false,
|
|
workers: false,
|
|
indexedDb: false,
|
|
crossOriginIsolated: false,
|
|
},
|
|
privacy: {
|
|
processing: "local",
|
|
fileUploads: false,
|
|
telemetry: false,
|
|
},
|
|
source: {
|
|
repository: "https://git.example.test/example",
|
|
license: "MIT",
|
|
},
|
|
assets: ["./app.js"],
|
|
...overrides,
|
|
});
|
|
|
|
async function fixture(document = manifest()): Promise<string> {
|
|
const root = await mkdtemp(join(tmpdir(), "toolbox-check-"));
|
|
temporaryDirectories.push(root);
|
|
await Promise.all([
|
|
writeFile(join(root, "toolbox-app.json"), JSON.stringify(document)),
|
|
writeFile(
|
|
join(root, "index.html"),
|
|
'<!doctype html><link rel="manifest" href="./site.webmanifest"><link rel="icon" href="./icon.svg"><link rel="stylesheet" href="./style.css"><img src="./image.png" alt=""><script type="module" src="./app.js"></script><title>Example</title>',
|
|
),
|
|
writeFile(
|
|
join(root, "icon.svg"),
|
|
'<svg xmlns="http://www.w3.org/2000/svg"/>',
|
|
),
|
|
writeFile(join(root, "app.js"), "console.log('ok')"),
|
|
writeFile(join(root, "style.css"), "body { color: black; }"),
|
|
writeFile(join(root, "image.png"), "not-a-real-png"),
|
|
writeFile(
|
|
join(root, "site.webmanifest"),
|
|
JSON.stringify({ icons: [{ src: "./icon.svg" }] }),
|
|
),
|
|
]);
|
|
return root;
|
|
}
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(
|
|
temporaryDirectories
|
|
.splice(0)
|
|
.map((path) => rm(path, { recursive: true, force: true })),
|
|
);
|
|
});
|
|
|
|
describe("toolbox-check", () => {
|
|
it("validates files and smoke-fetches nested standalone and context URLs", async () => {
|
|
const report = await checkToolboxDist(await fixture());
|
|
|
|
expect(report.app.id).toBe("de.add-ideas.example");
|
|
expect(report.checkedFiles).toHaveLength(3);
|
|
expect(new URL(report.smoke.standaloneUrl).pathname).toBe(
|
|
"/__toolbox-check__/deep/nested/app/",
|
|
);
|
|
expect(
|
|
new URL(report.smoke.contextualUrl).searchParams.get("toolbox"),
|
|
).toBe("/toolbox.catalog.json");
|
|
expect(report.smoke.fetchedUrls).toEqual(
|
|
expect.arrayContaining([
|
|
expect.stringContaining("/site.webmanifest"),
|
|
expect.stringContaining("/style.css"),
|
|
expect.stringContaining("/app.js"),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("rejects traversal in declared assets", async () => {
|
|
const root = await fixture(manifest({ assets: ["../secret.txt"] }));
|
|
await expect(checkToolboxDist(root)).rejects.toThrow(/unsafe path/u);
|
|
});
|
|
|
|
it("rejects a non-SemVer manifest version", async () => {
|
|
const root = await fixture(manifest({ version: "next" }));
|
|
await expect(checkToolboxDist(root)).rejects.toThrow(/not SemVer/u);
|
|
});
|
|
|
|
it("rejects root-absolute HTML assets that break nested deployment", async () => {
|
|
const root = await fixture();
|
|
await writeFile(
|
|
join(root, "index.html"),
|
|
'<!doctype html><script src="/app.js"></script>',
|
|
);
|
|
await expect(checkToolboxDist(root)).rejects.toThrow(/not relocatable/u);
|
|
});
|
|
});
|