Files
kandev/apps/web/lib/plugins/config-schema.test.ts
2026-09-15 09:40:39 +01:00

169 lines
5.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
SECRET_MASK,
buildInitialValues,
missingRequiredFields,
parseConfigSchema,
serializeConfigValues,
} from "./config-schema";
const githubSchema = {
type: "object",
required: ["github_token"],
properties: {
github_token: {
type: "string",
title: "Personal access token",
description: "Fine-grained PAT with repo scope.",
secret: true,
},
webhook_key: { type: "string", format: "password" },
org: { type: "string" },
max_items: { type: "integer", default: 25 },
channel: { type: "string", enum: ["dev", "ops"] },
verbose: { type: "boolean", default: true },
},
} satisfies Record<string, unknown>;
describe("parseConfigSchema", () => {
it("maps properties to fields with titles, requiredness, and secrecy", () => {
const fields = parseConfigSchema(githubSchema);
const byName = Object.fromEntries(fields.map((f) => [f.name, f]));
expect(fields).toHaveLength(6);
expect(byName.github_token).toMatchObject({
type: "string",
label: "Personal access token",
description: "Fine-grained PAT with repo scope.",
required: true,
secret: true,
});
expect(byName.webhook_key.secret).toBe(true);
expect(byName.org).toMatchObject({ label: "org", required: false, secret: false });
expect(byName.max_items).toMatchObject({ type: "integer", defaultValue: 25 });
expect(byName.channel).toMatchObject({ type: "enum", enumValues: ["dev", "ops"] });
expect(byName.verbose.type).toBe("boolean");
});
it("returns [] for missing or unusable schemas", () => {
expect(parseConfigSchema(undefined)).toEqual([]);
expect(parseConfigSchema({})).toEqual([]);
expect(parseConfigSchema({ properties: "nope" })).toEqual([]);
});
it("maps the utility-agent format to a utility agent picker field", () => {
const fields = parseConfigSchema({
type: "object",
required: ["utility_agent"],
properties: {
utility_agent: {
type: "string",
format: "utility-agent",
title: "Utility agent",
},
},
});
expect(fields).toEqual([
expect.objectContaining({
name: "utility_agent",
type: "utility_agent",
label: "Utility agent",
required: true,
}),
]);
});
it("maps the agent-profile format to a direct profile picker field", () => {
const fields = parseConfigSchema({
type: "object",
required: ["agent_profile"],
properties: {
agent_profile: {
type: "string",
format: "agent-profile",
title: "Notes agent",
},
},
});
expect(fields).toEqual([
expect.objectContaining({
name: "agent_profile",
type: "agent_profile",
label: "Notes agent",
required: true,
}),
]);
});
});
describe("buildInitialValues", () => {
it("prefers stored config, then defaults, then empties", () => {
const fields = parseConfigSchema(githubSchema);
const values = buildInitialValues(fields, { github_token: SECRET_MASK, org: "kdlbs" });
expect(values.github_token).toBe(SECRET_MASK);
expect(values.org).toBe("kdlbs");
expect(values.max_items).toBe("25");
expect(values.verbose).toBe(true);
expect(values.channel).toBe("");
});
});
describe("serializeConfigValues", () => {
it("parses numerics, keeps booleans, and drops empty strings", () => {
const fields = parseConfigSchema(githubSchema);
const config = serializeConfigValues(fields, {
github_token: SECRET_MASK,
webhook_key: "",
org: "kdlbs",
max_items: "10",
channel: "",
verbose: false,
});
expect(config).toEqual({
github_token: SECRET_MASK,
org: "kdlbs",
max_items: 10,
verbose: false,
});
});
it("omits unparseable numeric input", () => {
const fields = parseConfigSchema(githubSchema);
const config = serializeConfigValues(fields, { max_items: "abc", verbose: true });
expect(config).not.toHaveProperty("max_items");
});
it("rejects (omits) non-integral input for integer fields instead of truncating", () => {
const fields = parseConfigSchema(githubSchema);
// parseInt would silently turn "1.5" into 1 — the field must be omitted
// so the operator is told rather than getting a corrupted value.
const config = serializeConfigValues(fields, { max_items: "1.5", verbose: true });
expect(config).not.toHaveProperty("max_items");
expect(
serializeConfigValues(fields, { max_items: "Infinity", verbose: true }),
).not.toHaveProperty("max_items");
});
it("submits numeric enum selections with their original type", () => {
const fields = parseConfigSchema({
properties: { level: { type: "integer", enum: [1, 2, 3] } },
});
const config = serializeConfigValues(fields, { level: "2" });
expect(config.level).toBe(2);
});
});
describe("missingRequiredFields", () => {
it("flags blank required fields and accepts the secret mask as set", () => {
const fields = parseConfigSchema(githubSchema);
expect(missingRequiredFields(fields, { github_token: "" })).toEqual(["Personal access token"]);
expect(missingRequiredFields(fields, { github_token: SECRET_MASK })).toEqual([]);
expect(missingRequiredFields(fields, { github_token: "ghp_x" })).toEqual([]);
});
});