openpetswithchatandmcp/apps/desktop/contracts/plugin-manifest.contract.ts
2026-05-24 16:32:31 +02:00

218 lines
9.4 KiB
TypeScript

import { openPetsPluginManifestFilename, validatePluginManifest } from "../src/plugin-manifest.js";
assertEqual(openPetsPluginManifestFilename, "openpets.plugin.json");
const validManifest = {
manifestVersion: 1,
id: "stretch-timer",
name: "Stretch Timer",
version: "1.0.0",
runtime: "declarative",
permissions: ["timer", "pet:speak", "pet:reaction"],
configSchema: {
intervalMinutes: { type: "number", label: "Interval", default: 15 },
message: { type: "text", label: "Message", default: "Time to stretch!" },
mood: {
type: "select",
label: "Mood",
default: "celebrating",
options: [{ label: "Celebrate", value: "celebrating" }],
},
},
triggers: [
{
on: "timer",
everyMinutes: { config: "intervalMinutes" },
actions: [
{ type: "pet.speak", message: "Time to stretch!" },
{ type: "pet.react", reaction: "celebrating" },
],
},
],
};
assertValid(validManifest);
assertValid({ ...validManifest, icon: "bell" });
assertInvalid({ ...validManifest, icon: "https://example.com/icon.svg" }, "invalid_icon");
assertValid({ manifestVersion: 2, id: "js-plugin", name: "JS Plugin", version: "1.0.0", runtime: "javascript", icon: "github", sdkVersion: "1.0.0", entry: "dist/index.js", permissions: ["pet:speak", "network"], network: { hosts: ["api.example.com"] } });
assertInvalid({ manifestVersion: 2, id: "js-plugin", name: "JS Plugin", version: "1.0.0", runtime: "javascript", icon: "<svg>", sdkVersion: "1.0.0", entry: "dist/index.js", permissions: ["pet:speak"] }, "invalid_icon");
assertInvalid({ manifestVersion: 2, id: "js-plugin", name: "JS Plugin", version: "1.0.0", runtime: "declarative", sdkVersion: "1.0.0", entry: "dist/index.js", permissions: ["pet:speak"] }, "invalid_runtime");
assertInvalid({ manifestVersion: 2, id: "js-plugin", name: "JS Plugin", version: "1.0.0", runtime: "javascript", sdkVersion: "1.0.0", entry: "../index.js", permissions: ["pet:speak"] }, "invalid_entry");
assertInvalid({ manifestVersion: 2, id: "js-plugin", name: "JS Plugin", version: "1.0.0", runtime: "javascript", sdkVersion: "1.0.0", entry: "index.js", permissions: ["network"], network: { hosts: ["*.example.com"] } }, "invalid_network_host");
assertValid({ ...validManifest, triggers: [{ on: "timer", everyMinutes: 5, actions: [{ type: "pet.speak", message: { config: "message" } }, { type: "pet.react", reaction: { config: "mood" } }] }] });
assertInvalid({ ...validManifest, triggers: [{ on: "timer", everyMinutes: 5, actions: [{ type: "pet.speak", message: { config: "message", extra: true } }] }] }, "invalid_config_reference");
assertInvalid({ ...validManifest, triggers: [{ on: "timer", everyMinutes: 5, actions: [{ type: "pet.speak", message: { config: "missing" } }] }] }, "invalid_config_reference");
assertInvalid({ ...validManifest, triggers: [{ on: "timer", everyMinutes: 5, actions: [{ type: "pet.speak", message: { config: "mood" } }] }] }, "invalid_config_reference");
assertInvalid({ ...validManifest, triggers: [{ on: "timer", everyMinutes: 5, actions: [{ type: "pet.react", reaction: { config: "message" } }] }] }, "invalid_config_reference");
assertInvalid({ ...validManifest, configSchema: { mood: { type: "select", default: "celebrate", options: [{ label: "Celebrate", value: "celebrate" }] } }, triggers: [{ on: "timer", everyMinutes: 5, actions: [{ type: "pet.react", reaction: { config: "mood" } }] }] }, "invalid_reaction_config_reference");
assertInvalid({ ...validManifest, configSchema: { mood: { type: "select", options: [{ label: "Celebrate", value: "celebrating" }] } }, triggers: [{ on: "timer", everyMinutes: 5, actions: [{ type: "pet.react", reaction: { config: "mood" } }] }] }, "invalid_reaction_config_reference");
assertInvalid({ ...validManifest, extra: true }, "unknown_field");
assertInvalid(
{
...validManifest,
configSchema: { intervalMinutes: { type: "number", label: "Interval", helperText: "Soon" } },
},
"unknown_field",
);
assertInvalid(
{
...validManifest,
triggers: [{ on: "timer", everyMinutes: 5, actions: [], jitter: true }],
},
"unknown_field",
);
assertInvalid(
{
...validManifest,
triggers: [{ on: "timer", everyMinutes: 5, actions: [{ type: "pet.speak", message: "Hi", unsafe: true }] }],
},
"unknown_field",
);
assertInvalid(
{
...validManifest,
configSchema: { mood: { type: "select", options: [{ label: "Celebrate", value: "celebrate", icon: "sparkles" }] } },
},
"unknown_field",
);
assertInvalid({ ...validManifest, runtime: "javascript" }, "unsupported_runtime");
assertInvalid({ ...validManifest, permissions: ["timer", "network"] }, "invalid_permission");
assertInvalid({ ...validManifest, permissions: ["timer", "pet:speak", "timer", "pet:reaction"] }, "duplicate_permission");
assertInvalid(
{
...validManifest,
triggers: [{ on: "timer", everyMinutes: { config: "missing" }, actions: [] }],
},
"invalid_timer_config_reference",
);
assertInvalid(
{
...validManifest,
configSchema: { notNumber: { type: "text" } },
triggers: [{ on: "timer", everyMinutes: { config: "notNumber" }, actions: [] }],
},
"invalid_timer_config_reference",
);
assertInvalid(
{
...validManifest,
triggers: [{ on: "timer", everyMinutes: 5, actions: [{ type: "pet.badge", text: "Busy" }] }],
},
"invalid_action",
);
assertValid({ ...validManifest, configSchema: { ...validManifest.configSchema, when: { type: "time", default: "09:00" } } });
assertInvalid(
{
...validManifest,
configSchema: { days: { type: "multi-select" } },
},
"deferred_config_type",
);
assertInvalid(
{
...validManifest,
configSchema: { date: { type: "date" } },
},
"deferred_config_type",
);
assertInvalid(
{
...validManifest,
configSchema: { schedule: { type: "schedule" } },
},
"deferred_config_type",
);
assertInvalid(
{
...validManifest,
configSchema: { account: { type: "connection" } },
},
"deferred_config_type",
);
assertInvalid(
{
...validManifest,
configSchema: { token: { type: "secret" } },
},
"deferred_config_type",
);
assertInvalid(
{
...validManifest,
configSchema: { choices: { type: "select", dynamicOptions: true } },
},
"deferred_config_feature",
);
assertInvalid({ ...validManifest, configSchema: { text: { type: "text", default: 1 } } }, "invalid_default");
assertInvalid({ ...validManifest, configSchema: { text: { type: "textarea", default: false } } }, "invalid_default");
assertInvalid({ ...validManifest, configSchema: { count: { type: "number", default: Infinity } } }, "invalid_default");
assertInvalid({ ...validManifest, configSchema: { enabled: { type: "boolean", default: "yes" } } }, "invalid_default");
assertInvalid({ ...validManifest, configSchema: { text: { type: "text", options: [{ label: "A", value: "a" }] } } }, "invalid_options");
assertInvalid({ ...validManifest, configSchema: { mood: { type: "select" } } }, "invalid_options");
assertInvalid({ ...validManifest, configSchema: { mood: { type: "select", options: [] } } }, "invalid_options");
assertInvalid({ ...validManifest, configSchema: { mood: { type: "select", options: [{ label: "", value: "x" }] } } }, "invalid_string");
assertInvalid({ ...validManifest, configSchema: { mood: { type: "select", options: [{ label: "X", value: "" }] } } }, "invalid_string");
assertInvalid({ ...validManifest, configSchema: { mood: { type: "select", options: [{ label: "A", value: "x" }, { label: "B", value: "x" }] } } }, "duplicate_option_value");
assertInvalid({ ...validManifest, configSchema: { mood: { type: "select", default: 1, options: [{ label: "A", value: "a" }] } } }, "invalid_default");
assertInvalid({ ...validManifest, configSchema: { mood: { type: "select", default: "b", options: [{ label: "A", value: "a" }] } } }, "invalid_default");
assertInvalid({ ...validManifest, configSchema: { text: { type: "text", label: 1 } } }, "invalid_string");
assertInvalid({ ...validManifest, configSchema: { text: { type: "text", description: false } } }, "invalid_string");
assertInvalid(
{
...validManifest,
triggers: [{ on: "timer", everyMinutes: 4, actions: [] }],
},
"invalid_timer_interval",
);
assertInvalid(
{
...validManifest,
triggers: [{ on: "timer", everyMinutes: { config: "intervalMinutes", extra: true }, actions: [] }],
},
"invalid_timer_interval",
);
assertInvalid(
{
...validManifest,
permissions: ["pet:speak", "pet:reaction"],
},
"missing_permission",
);
assertInvalid(
{
...validManifest,
permissions: ["timer", "pet:reaction"],
},
"missing_permission",
);
assertInvalid(
{
...validManifest,
permissions: ["timer", "pet:speak"],
},
"missing_permission",
);
function assertValid(manifest: unknown): void {
const result = validatePluginManifest(manifest);
if (!result.ok) throw new Error(`Expected valid manifest, got ${JSON.stringify(result.errors)}`);
}
function assertInvalid(manifest: unknown, code: string): void {
const result = validatePluginManifest(manifest);
assertEqual(result.ok, false, `Expected invalid manifest for ${code}`);
assertTrue(result.errors.some((error) => error.code === code), `Expected ${code}, got ${JSON.stringify(result.errors)}`);
}
function assertEqual(actual: unknown, expected: unknown, message?: string): void {
if (actual !== expected) throw new Error(message ?? `Expected ${String(expected)}, got ${String(actual)}`);
}
function assertTrue(value: unknown, message: string): void {
if (!value) throw new Error(message);
}
console.log("Plugin manifest contract validation passed.");