Files
kunthawat 2d1be52177 Emdash source with visual editor image upload fix
Fixes:
1. media.ts: wrap placeholder generation in try-catch
2. toolbar.ts: check r.ok, display error message in popover
2026-05-03 10:44:54 +07:00

362 lines
9.4 KiB
TypeScript

import { describe, it, expect } from "vitest";
import {
pluginManifestSchema,
normalizeManifestRoute,
} from "../../../src/plugins/manifest-schema.js";
/** Minimal valid manifest for testing — only storage fields vary */
function makeManifest(storage: Record<string, { indexes: Array<string | string[]> }>) {
return {
id: "test-plugin",
version: "1.0.0",
capabilities: [],
allowedHosts: [],
storage,
hooks: [],
routes: [],
admin: {},
};
}
describe("pluginManifestSchema — route entries", () => {
it("should accept plain string routes", () => {
const result = pluginManifestSchema.safeParse(makeManifest({}));
// Baseline with empty routes is valid
expect(result.success).toBe(true);
const withRoutes = pluginManifestSchema.safeParse({
...makeManifest({}),
routes: ["webhook", "callback"],
});
expect(withRoutes.success).toBe(true);
});
it("should accept structured route objects", () => {
const result = pluginManifestSchema.safeParse({
...makeManifest({}),
routes: [{ name: "webhook", public: true }],
});
expect(result.success).toBe(true);
});
it("should accept a mix of strings and objects", () => {
const result = pluginManifestSchema.safeParse({
...makeManifest({}),
routes: ["callback", { name: "webhook", public: true }],
});
expect(result.success).toBe(true);
});
it("should reject route objects with empty name", () => {
const result = pluginManifestSchema.safeParse({
...makeManifest({}),
routes: [{ name: "", public: true }],
});
expect(result.success).toBe(false);
});
it("should reject route objects with missing name", () => {
const result = pluginManifestSchema.safeParse({
...makeManifest({}),
routes: [{ public: true }],
});
expect(result.success).toBe(false);
});
it("should accept route objects without public (defaults to private)", () => {
const result = pluginManifestSchema.safeParse({
...makeManifest({}),
routes: [{ name: "internal" }],
});
expect(result.success).toBe(true);
});
it("should accept route names with slashes and hyphens", () => {
const result = pluginManifestSchema.safeParse({
...makeManifest({}),
routes: ["auth/callback", "web-hook", { name: "api/v2/data", public: true }],
});
expect(result.success).toBe(true);
});
it("should reject route names with path traversal", () => {
const result = pluginManifestSchema.safeParse({
...makeManifest({}),
routes: ["../../admin/settings"],
});
expect(result.success).toBe(false);
});
it("should reject route names starting with special characters", () => {
const result = pluginManifestSchema.safeParse({
...makeManifest({}),
routes: ["/leading-slash"],
});
expect(result.success).toBe(false);
});
it("should reject route object names with path traversal", () => {
const result = pluginManifestSchema.safeParse({
...makeManifest({}),
routes: [{ name: "../escape", public: true }],
});
expect(result.success).toBe(false);
});
});
describe("normalizeManifestRoute", () => {
it("should convert a plain string to { name } object", () => {
expect(normalizeManifestRoute("webhook")).toEqual({ name: "webhook" });
});
it("should pass through a structured object unchanged", () => {
expect(normalizeManifestRoute({ name: "webhook", public: true })).toEqual({
name: "webhook",
public: true,
});
});
it("should pass through an object without public", () => {
expect(normalizeManifestRoute({ name: "internal" })).toEqual({ name: "internal" });
});
});
describe("pluginManifestSchema — storage index field names", () => {
it("should accept valid simple index field names", () => {
const result = pluginManifestSchema.safeParse(
makeManifest({
items: { indexes: ["status", "createdAt", "count"] },
}),
);
expect(result.success).toBe(true);
});
it("should accept valid composite index field names", () => {
const result = pluginManifestSchema.safeParse(
makeManifest({
items: { indexes: [["status", "createdAt"]] },
}),
);
expect(result.success).toBe(true);
});
it("should reject index field names containing SQL injection payloads", () => {
const result = pluginManifestSchema.safeParse(
makeManifest({
items: { indexes: ["'); DROP TABLE users--"] },
}),
);
expect(result.success).toBe(false);
});
it("should reject index field names with dots (JSON path traversal)", () => {
const result = pluginManifestSchema.safeParse(
makeManifest({
items: { indexes: ["nested.field"] },
}),
);
expect(result.success).toBe(false);
});
it("should reject index field names with hyphens", () => {
const result = pluginManifestSchema.safeParse(
makeManifest({
items: { indexes: ["my-field"] },
}),
);
expect(result.success).toBe(false);
});
it("should reject index field names starting with a number", () => {
const result = pluginManifestSchema.safeParse(
makeManifest({
items: { indexes: ["1field"] },
}),
);
expect(result.success).toBe(false);
});
it("should reject empty index field names", () => {
const result = pluginManifestSchema.safeParse(
makeManifest({
items: { indexes: [""] },
}),
);
expect(result.success).toBe(false);
});
it("should reject malicious field names in composite indexes", () => {
const result = pluginManifestSchema.safeParse(
makeManifest({
items: { indexes: [["status", "'); DROP TABLE--"]] },
}),
);
expect(result.success).toBe(false);
});
});
describe("pluginManifestSchema - admin.settingsSchema url/email field types", () => {
it("should accept url setting field with label and description", () => {
const result = pluginManifestSchema.safeParse({
...makeManifest({}),
admin: {
settingsSchema: {
website: {
type: "url",
label: "Website URL",
description: "The plugin website",
},
},
},
});
expect(result.success).toBe(true);
});
it("should accept url setting field with default and placeholder", () => {
const result = pluginManifestSchema.safeParse({
...makeManifest({}),
admin: {
settingsSchema: {
website: {
type: "url",
label: "Website URL",
description: "The plugin website",
default: "https://example.com",
placeholder: "https://your-site.com",
},
},
},
});
expect(result.success).toBe(true);
if (result.success) {
const parsed = result.data;
expect(parsed.admin?.settingsSchema?.website).toEqual({
type: "url",
label: "Website URL",
description: "The plugin website",
default: "https://example.com",
placeholder: "https://your-site.com",
});
}
});
it("should accept email setting field with label and description", () => {
const result = pluginManifestSchema.safeParse({
...makeManifest({}),
admin: {
settingsSchema: {
supportEmail: {
type: "email",
label: "Support Email",
description: "Email for support",
},
},
},
});
expect(result.success).toBe(true);
});
it("should accept email setting field with default and placeholder", () => {
const result = pluginManifestSchema.safeParse({
...makeManifest({}),
admin: {
settingsSchema: {
supportEmail: {
type: "email",
label: "Support Email",
description: "Email for support",
default: "support@example.com",
placeholder: "your@email.com",
},
},
},
});
expect(result.success).toBe(true);
if (result.success) {
const parsed = result.data;
expect(parsed.admin?.settingsSchema?.supportEmail).toEqual({
type: "email",
label: "Support Email",
description: "Email for support",
default: "support@example.com",
placeholder: "your@email.com",
});
}
});
it("should accept both url and email in the same settingsSchema", () => {
const result = pluginManifestSchema.safeParse({
...makeManifest({}),
admin: {
settingsSchema: {
website: {
type: "url",
label: "Website",
default: "https://example.com",
placeholder: "https://",
},
contactEmail: {
type: "email",
label: "Contact Email",
default: "contact@example.com",
placeholder: "email@domain.com",
},
},
},
});
expect(result.success).toBe(true);
if (result.success) {
const parsed = result.data;
expect(parsed.admin?.settingsSchema?.website.type).toBe("url");
expect(parsed.admin?.settingsSchema?.contactEmail.type).toBe("email");
expect(parsed.admin?.settingsSchema?.website.default).toBe("https://example.com");
expect(parsed.admin?.settingsSchema?.contactEmail.default).toBe("contact@example.com");
}
});
it("should accept url field without optional fields", () => {
const result = pluginManifestSchema.safeParse({
...makeManifest({}),
admin: {
settingsSchema: {
docs: {
type: "url",
label: "Documentation",
},
},
},
});
expect(result.success).toBe(true);
});
it("should accept email field without optional fields", () => {
const result = pluginManifestSchema.safeParse({
...makeManifest({}),
admin: {
settingsSchema: {
notifications: {
type: "email",
label: "Notification Email",
},
},
},
});
expect(result.success).toBe(true);
});
it("should accept number field without optional fields", () => {
const result = pluginManifestSchema.safeParse({
...makeManifest({}),
admin: {
settingsSchema: {
port: {
type: "number",
label: "Server Port",
},
},
},
});
expect(result.success).toBe(true);
});
});