feat: add toolbox contract shell and testkit
This commit is contained in:
261
packages/contract/test/schema-parity.test.ts
Normal file
261
packages/contract/test/schema-parity.test.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
import Ajv2020, { type ValidateFunction } from "ajv/dist/2020.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { parseToolboxApp, parseToolboxCatalog } from "../src/index.js";
|
||||
|
||||
const appSchema = JSON.parse(
|
||||
readFileSync(
|
||||
new URL("../../../schemas/toolbox-app.v1.schema.json", import.meta.url),
|
||||
"utf8",
|
||||
),
|
||||
) as object;
|
||||
const catalogSchema = JSON.parse(
|
||||
readFileSync(
|
||||
new URL("../../../schemas/toolbox-catalog.v1.schema.json", import.meta.url),
|
||||
"utf8",
|
||||
),
|
||||
) as object;
|
||||
|
||||
const ajv = new Ajv2020({
|
||||
allErrors: true,
|
||||
strict: true,
|
||||
// `not: { required: [...] }` intentionally names the opposite union arm.
|
||||
strictRequired: false,
|
||||
});
|
||||
const validateApp = ajv.compile(appSchema);
|
||||
const validateCatalog = ajv.compile(catalogSchema);
|
||||
|
||||
type Parser = (value: unknown) => unknown;
|
||||
|
||||
function runtimeAccepts(parser: Parser, value: unknown): boolean {
|
||||
try {
|
||||
parser(value);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function expectParity(
|
||||
validate: ValidateFunction,
|
||||
parser: Parser,
|
||||
value: unknown,
|
||||
expected: boolean,
|
||||
): void {
|
||||
const schemaAccepted = validate(value);
|
||||
expect(
|
||||
schemaAccepted,
|
||||
ajv.errorsText(validate.errors, { separator: "\n" }),
|
||||
).toBe(expected);
|
||||
expect(runtimeAccepts(parser, value)).toBe(expected);
|
||||
}
|
||||
|
||||
const validApp = (): Record<string, unknown> => ({
|
||||
schemaVersion: 1,
|
||||
id: "de.add-ideas.example",
|
||||
name: "Example",
|
||||
version: "1.2.3",
|
||||
description: "Example application",
|
||||
entry: "./",
|
||||
icon: "./icon.svg",
|
||||
categories: ["documents"],
|
||||
tags: ["example"],
|
||||
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://example.test/source",
|
||||
license: "MIT",
|
||||
},
|
||||
});
|
||||
|
||||
const validCatalog = (): Record<string, unknown> => ({
|
||||
schemaVersion: 1,
|
||||
id: "de.add-ideas.toolbox",
|
||||
name: "Example Toolbox",
|
||||
home: "./",
|
||||
theme: { mode: "system", brand: "Example" },
|
||||
apps: [{ manifest: "./apps/example/toolbox-app.json", enabled: true }],
|
||||
});
|
||||
|
||||
describe("canonical schema and runtime parser parity", () => {
|
||||
const acceptedApps: Array<[string, () => Record<string, unknown>]> = [
|
||||
["the baseline app", validApp],
|
||||
[
|
||||
"relative references containing spaces and path @ signs",
|
||||
() => ({
|
||||
...validApp(),
|
||||
entry: " ./nested app/?mode=1#result ",
|
||||
actions: [{ id: "docs", label: "Docs", url: "./people/@me" }],
|
||||
}),
|
||||
],
|
||||
[
|
||||
"case-insensitive and compact absolute HTTP URLs",
|
||||
() => ({
|
||||
...validApp(),
|
||||
source: { repository: "HTTPS:example.test", license: "MIT" },
|
||||
}),
|
||||
],
|
||||
];
|
||||
|
||||
it.each(acceptedApps)("accepts %s", (_label, fixture) => {
|
||||
expectParity(validateApp, parseToolboxApp, fixture(), true);
|
||||
});
|
||||
|
||||
const rejectedApps: Array<[string, () => Record<string, unknown>]> = [
|
||||
["a whitespace-only name", () => ({ ...validApp(), name: " \t " })],
|
||||
[
|
||||
"a whitespace-only list item",
|
||||
() => ({ ...validApp(), categories: ["documents", " \n "] }),
|
||||
],
|
||||
[
|
||||
"a non-web entry scheme",
|
||||
() => ({ ...validApp(), entry: "javascript:alert(1)" }),
|
||||
],
|
||||
["a backslash path", () => ({ ...validApp(), icon: ".\\icon.svg" })],
|
||||
["an empty HTTP authority", () => ({ ...validApp(), entry: "https://" })],
|
||||
[
|
||||
"a credential-bearing privacy URL",
|
||||
() => ({
|
||||
...validApp(),
|
||||
privacy: {
|
||||
processing: "local",
|
||||
fileUploads: false,
|
||||
telemetry: false,
|
||||
url: "https://user:secret@example.test/privacy",
|
||||
},
|
||||
}),
|
||||
],
|
||||
[
|
||||
"a relative source repository",
|
||||
() => ({
|
||||
...validApp(),
|
||||
source: { repository: "../source", license: "MIT" },
|
||||
}),
|
||||
],
|
||||
[
|
||||
"a compact credential-bearing source repository",
|
||||
() => ({
|
||||
...validApp(),
|
||||
source: { repository: "https:user@example.test", license: "MIT" },
|
||||
}),
|
||||
],
|
||||
[
|
||||
"a whitespace-only action label",
|
||||
() => ({
|
||||
...validApp(),
|
||||
actions: [{ id: "docs", label: " ", url: "./docs" }],
|
||||
}),
|
||||
],
|
||||
];
|
||||
|
||||
it.each(rejectedApps)("rejects %s", (_label, fixture) => {
|
||||
expectParity(validateApp, parseToolboxApp, fixture(), false);
|
||||
});
|
||||
|
||||
const acceptedCatalogs: Array<[string, () => Record<string, unknown>]> = [
|
||||
["the baseline catalog", validCatalog],
|
||||
[
|
||||
"an uppercase HTTPS manifest URL",
|
||||
() => ({
|
||||
...validCatalog(),
|
||||
apps: [
|
||||
{
|
||||
manifest: "HTTPS://example.test/apps/example/toolbox-app.json",
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
];
|
||||
|
||||
it.each(acceptedCatalogs)("accepts %s", (_label, fixture) => {
|
||||
expectParity(validateCatalog, parseToolboxCatalog, fixture(), true);
|
||||
});
|
||||
|
||||
const rejectedCatalogs: Array<[string, () => Record<string, unknown>]> = [
|
||||
[
|
||||
"a whitespace-only catalog name",
|
||||
() => ({ ...validCatalog(), name: " " }),
|
||||
],
|
||||
[
|
||||
"a whitespace-only brand",
|
||||
() => ({
|
||||
...validCatalog(),
|
||||
theme: { mode: "system", brand: "\t" },
|
||||
}),
|
||||
],
|
||||
[
|
||||
"a non-web home URL",
|
||||
() => ({ ...validCatalog(), home: "file:///tmp/toolbox" }),
|
||||
],
|
||||
[
|
||||
"a backslash manifest path",
|
||||
() => ({
|
||||
...validCatalog(),
|
||||
apps: [{ manifest: ".\\apps\\toolbox-app.json", enabled: true }],
|
||||
}),
|
||||
],
|
||||
[
|
||||
"a credential-bearing manifest URL",
|
||||
() => ({
|
||||
...validCatalog(),
|
||||
apps: [
|
||||
{
|
||||
manifest: "https://user:secret@example.test/toolbox-app.json",
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
[
|
||||
"a non-web external entry",
|
||||
() => ({
|
||||
...validCatalog(),
|
||||
apps: [
|
||||
{
|
||||
name: "External",
|
||||
entry: "data:text/html,hello",
|
||||
launch: "new-tab",
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
[
|
||||
"a whitespace-only external name",
|
||||
() => ({
|
||||
...validCatalog(),
|
||||
apps: [
|
||||
{
|
||||
name: " ",
|
||||
entry: "https://example.test/",
|
||||
launch: "new-tab",
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
];
|
||||
|
||||
it.each(rejectedCatalogs)("rejects %s", (_label, fixture) => {
|
||||
expectParity(validateCatalog, parseToolboxCatalog, fixture(), false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user