mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 14:45:24 +00:00
feat: collapse plugin runtime to one tier behind a plugin SDK
This commit is contained in:
1 parent
6e058f6e9e
commit
4758016f41
96 files changed
+34631
-8551
No files matched your search
@@ -5,13 +5,12 @@ import path from "node:path";
|
||||
const VALIDATOR = path.join(__dirname, "validate-plugin-manifest.cjs");
|
||||
const FIXTURES = path.join(__dirname, "__fixtures__", "manifests");
|
||||
|
||||
function runValidator(fixture: string): { status: number; output: string } {
|
||||
function runValidator(fixture?: string): { status: number; output: string } {
|
||||
const args = fixture
|
||||
? [VALIDATOR, path.join(FIXTURES, fixture)]
|
||||
: [VALIDATOR];
|
||||
try {
|
||||
const output = execFileSync(
|
||||
"node",
|
||||
[VALIDATOR, path.join(FIXTURES, fixture)],
|
||||
{ encoding: "utf8" },
|
||||
);
|
||||
const output = execFileSync("node", args, { encoding: "utf8" });
|
||||
return { status: 0, output };
|
||||
} catch (err) {
|
||||
const error = err as { status: number; stdout: string; stderr: string };
|
||||
@@ -23,54 +22,76 @@ describe("validate-plugin-manifest.cjs", () => {
|
||||
it("accepts a valid manifest", () => {
|
||||
const { status, output } = runValidator("valid.json");
|
||||
expect(status).toBe(0);
|
||||
expect(output).toContain("Valid plugin manifest");
|
||||
expect(output).toContain("ok");
|
||||
});
|
||||
|
||||
it("accepts a manifest declaring services", () => {
|
||||
expect(runValidator("valid-services.json").status).toBe(0);
|
||||
});
|
||||
|
||||
it("accepts a manifest declaring actions", () => {
|
||||
expect(runValidator("valid-actions.json").status).toBe(0);
|
||||
});
|
||||
|
||||
it("rejects a manifest missing a required field", () => {
|
||||
const { status, output } = runValidator("invalid-missing-field.json");
|
||||
expect(status).not.toBe(0);
|
||||
expect(output).toContain('Missing required field: "description"');
|
||||
expect(status).toBe(1);
|
||||
expect(output).toContain("id");
|
||||
});
|
||||
|
||||
it("rejects a manifest with a bad id pattern", () => {
|
||||
it("rejects an id that is not a lowercase slug", () => {
|
||||
const { status, output } = runValidator("invalid-bad-id.json");
|
||||
expect(status).not.toBe(0);
|
||||
expect(output).toMatch(/Field "id" must match/);
|
||||
expect(status).toBe(1);
|
||||
expect(output).toContain("id");
|
||||
});
|
||||
|
||||
it("accepts a manifest declaring provides and requires", () => {
|
||||
const { status, output } = runValidator("valid-services.json");
|
||||
it("rejects an unknown category", () => {
|
||||
expect(runValidator("invalid-unknown-category.json").status).toBe(1);
|
||||
});
|
||||
|
||||
// The dotted spelling was the v1 form. Capabilities are colon-separated now
|
||||
// so they can never be confused with RBAC permissions.
|
||||
it("rejects a dotted capability name", () => {
|
||||
const { status, output } = runValidator("invalid-unknown-capability.json");
|
||||
expect(status).toBe(1);
|
||||
expect(output).toContain("hosts.read");
|
||||
});
|
||||
|
||||
it("rejects a service permission the plugin does not declare", () => {
|
||||
expect(runValidator("invalid-service-permission.json").status).toBe(1);
|
||||
});
|
||||
|
||||
it("rejects an action permission the plugin does not declare", () => {
|
||||
expect(runValidator("invalid-action-permission.json").status).toBe(1);
|
||||
});
|
||||
|
||||
it("rejects an unknown top-level field", () => {
|
||||
const { status, output } = runValidator("invalid-unknown-field.json");
|
||||
expect(status).toBe(1);
|
||||
expect(output).toContain("sudoEverything");
|
||||
});
|
||||
|
||||
it("rejects an unknown field nested inside contributes", () => {
|
||||
const { status, output } = runValidator(
|
||||
"invalid-nested-unknown-field.json",
|
||||
);
|
||||
expect(status).toBe(1);
|
||||
expect(output).toContain("extra");
|
||||
});
|
||||
|
||||
// A manifest must not be able to hand a role a permission the plugin does
|
||||
// not own, which is how a plugin could otherwise grant itself admin rights.
|
||||
it("rejects a role default outside the plugin's own namespace", () => {
|
||||
const { status, output } = runValidator(
|
||||
"invalid-role-default-escalation.json",
|
||||
);
|
||||
expect(status).toBe(1);
|
||||
expect(output).toContain("admin.users.manage");
|
||||
});
|
||||
|
||||
it("validates every bundled manifest when given no argument", () => {
|
||||
const { status, output } = runValidator();
|
||||
expect(status).toBe(0);
|
||||
expect(output).toContain("Valid plugin manifest");
|
||||
});
|
||||
|
||||
it("rejects a service gated by an undeclared permission", () => {
|
||||
const { status, output } = runValidator("invalid-service-permission.json");
|
||||
expect(status).not.toBe(0);
|
||||
expect(output).toContain("sampleprovider.undeclared");
|
||||
});
|
||||
|
||||
it("accepts a manifest declaring actions and actionSlots", () => {
|
||||
const { status, output } = runValidator("valid-actions.json");
|
||||
expect(status).toBe(0);
|
||||
expect(output).toContain("Valid plugin manifest");
|
||||
});
|
||||
|
||||
it("rejects an action gated by an undeclared permission", () => {
|
||||
const { status, output } = runValidator("invalid-action-permission.json");
|
||||
expect(status).not.toBe(0);
|
||||
expect(output).toContain("samplebadaction.never_declared");
|
||||
});
|
||||
|
||||
it("rejects a manifest with an unknown permission", () => {
|
||||
const { status, output } = runValidator("invalid-unknown-permission.json");
|
||||
expect(status).not.toBe(0);
|
||||
expect(output).toContain('Unknown permission: "credentials.read"');
|
||||
});
|
||||
|
||||
it("rejects a manifest with an unknown category", () => {
|
||||
const { status, output } = runValidator("invalid-unknown-category.json");
|
||||
expect(status).not.toBe(0);
|
||||
expect(output).toMatch(/Field "category" must be one of/);
|
||||
expect(output).toContain("ssh-terminal");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user