test: expand include coverage
This commit is contained in:
parent
26cbbafc86
commit
9f9f6b75e7
@ -109,6 +109,15 @@ describe("resolveConfigIncludes", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("throws when sibling keys are used with primitive includes", () => {
|
||||||
|
const files = { "/config/value.json": "hello" };
|
||||||
|
const obj = { $include: "./value.json", extra: true };
|
||||||
|
expect(() => resolve(obj, files)).toThrow(ConfigIncludeError);
|
||||||
|
expect(() => resolve(obj, files)).toThrow(
|
||||||
|
/Sibling keys require included content to be an object/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("resolves nested includes", () => {
|
it("resolves nested includes", () => {
|
||||||
const files = {
|
const files = {
|
||||||
"/config/level1.json": { nested: { $include: "./level2.json" } },
|
"/config/level1.json": { nested: { $include: "./level2.json" } },
|
||||||
@ -154,12 +163,23 @@ describe("resolveConfigIncludes", () => {
|
|||||||
parseJson: JSON.parse,
|
parseJson: JSON.parse,
|
||||||
};
|
};
|
||||||
const obj = { $include: "./a.json" };
|
const obj = { $include: "./a.json" };
|
||||||
expect(() =>
|
try {
|
||||||
resolveConfigIncludes(obj, "/config/clawdbot.json", resolver),
|
resolveConfigIncludes(obj, "/config/clawdbot.json", resolver);
|
||||||
).toThrow(CircularIncludeError);
|
throw new Error("expected circular include error");
|
||||||
expect(() =>
|
} catch (err) {
|
||||||
resolveConfigIncludes(obj, "/config/clawdbot.json", resolver),
|
expect(err).toBeInstanceOf(CircularIncludeError);
|
||||||
).toThrow(/Circular include detected/);
|
const circular = err as CircularIncludeError;
|
||||||
|
expect(circular.chain).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
"/config/clawdbot.json",
|
||||||
|
"/config/a.json",
|
||||||
|
"/config/b.json",
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
expect(circular.message).toMatch(/Circular include detected/);
|
||||||
|
expect(circular.message).toMatch(/\/config\/a\.json/);
|
||||||
|
expect(circular.message).toMatch(/\/config\/b\.json/);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws ConfigIncludeError for invalid $include value type", () => {
|
it("throws ConfigIncludeError for invalid $include value type", () => {
|
||||||
@ -175,6 +195,21 @@ describe("resolveConfigIncludes", () => {
|
|||||||
expect(() => resolve(obj, files)).toThrow(/expected string, got number/);
|
expect(() => resolve(obj, files)).toThrow(/expected string, got number/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("throws ConfigIncludeError for null/boolean include items", () => {
|
||||||
|
const files = { "/config/valid.json": { valid: true } };
|
||||||
|
const cases = [
|
||||||
|
{ value: null, expected: "object" },
|
||||||
|
{ value: false, expected: "boolean" },
|
||||||
|
];
|
||||||
|
for (const item of cases) {
|
||||||
|
const obj = { $include: ["./valid.json", item.value] };
|
||||||
|
expect(() => resolve(obj, files)).toThrow(ConfigIncludeError);
|
||||||
|
expect(() => resolve(obj, files)).toThrow(
|
||||||
|
new RegExp(`expected string, got ${item.expected}`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("respects max depth limit", () => {
|
it("respects max depth limit", () => {
|
||||||
const files: Record<string, unknown> = {};
|
const files: Record<string, unknown> = {};
|
||||||
for (let i = 0; i < 15; i++) {
|
for (let i = 0; i < 15; i++) {
|
||||||
@ -187,6 +222,29 @@ describe("resolveConfigIncludes", () => {
|
|||||||
expect(() => resolve(obj, files)).toThrow(/Maximum include depth/);
|
expect(() => resolve(obj, files)).toThrow(/Maximum include depth/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("allows depth 10 but rejects depth 11", () => {
|
||||||
|
const okFiles: Record<string, unknown> = {};
|
||||||
|
for (let i = 0; i < 9; i++) {
|
||||||
|
okFiles[`/config/ok${i}.json`] = { $include: `./ok${i + 1}.json` };
|
||||||
|
}
|
||||||
|
okFiles["/config/ok9.json"] = { done: true };
|
||||||
|
expect(resolve({ $include: "./ok0.json" }, okFiles)).toEqual({
|
||||||
|
done: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const failFiles: Record<string, unknown> = {};
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
failFiles[`/config/fail${i}.json`] = { $include: `./fail${i + 1}.json` };
|
||||||
|
}
|
||||||
|
failFiles["/config/fail10.json"] = { done: true };
|
||||||
|
expect(() => resolve({ $include: "./fail0.json" }, failFiles)).toThrow(
|
||||||
|
ConfigIncludeError,
|
||||||
|
);
|
||||||
|
expect(() => resolve({ $include: "./fail0.json" }, failFiles)).toThrow(
|
||||||
|
/Maximum include depth/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("handles relative paths correctly", () => {
|
it("handles relative paths correctly", () => {
|
||||||
const files = { "/config/clients/mueller/agents.json": { id: "mueller" } };
|
const files = { "/config/clients/mueller/agents.json": { id: "mueller" } };
|
||||||
const obj = { agent: { $include: "./clients/mueller/agents.json" } };
|
const obj = { agent: { $include: "./clients/mueller/agents.json" } };
|
||||||
@ -195,6 +253,17 @@ describe("resolveConfigIncludes", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("applies nested includes before sibling overrides", () => {
|
||||||
|
const files = {
|
||||||
|
"/config/base.json": { nested: { $include: "./nested.json" } },
|
||||||
|
"/config/nested.json": { a: 1, b: 2 },
|
||||||
|
};
|
||||||
|
const obj = { $include: "./base.json", nested: { b: 9 } };
|
||||||
|
expect(resolve(obj, files)).toEqual({
|
||||||
|
nested: { a: 1, b: 9 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("resolves parent directory references", () => {
|
it("resolves parent directory references", () => {
|
||||||
const files = { "/shared/common.json": { shared: true } };
|
const files = { "/shared/common.json": { shared: true } };
|
||||||
const obj = { $include: "../../shared/common.json" };
|
const obj = { $include: "../../shared/common.json" };
|
||||||
|
|||||||
@ -11,18 +11,12 @@ const noopLogger = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type Registered = {
|
type Registered = {
|
||||||
methods: Map<
|
methods: Map<string, (ctx: Record<string, unknown>) => unknown>;
|
||||||
string,
|
|
||||||
(ctx: Record<string, unknown>) => Promise<unknown> | unknown
|
|
||||||
>;
|
|
||||||
tools: unknown[];
|
tools: unknown[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function setup(config: Record<string, unknown>): Registered {
|
function setup(config: Record<string, unknown>): Registered {
|
||||||
const methods = new Map<
|
const methods = new Map<string, (ctx: Record<string, unknown>) => unknown>();
|
||||||
string,
|
|
||||||
(ctx: Record<string, unknown>) => Promise<unknown> | unknown
|
|
||||||
>();
|
|
||||||
const tools: unknown[] = [];
|
const tools: unknown[] = [];
|
||||||
plugin.register({
|
plugin.register({
|
||||||
id: "voice-call",
|
id: "voice-call",
|
||||||
@ -174,7 +168,7 @@ describe("voice-call plugin", () => {
|
|||||||
};
|
};
|
||||||
const program = new Command();
|
const program = new Command();
|
||||||
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
||||||
register({
|
await register({
|
||||||
id: "voice-call",
|
id: "voice-call",
|
||||||
name: "Voice Call",
|
name: "Voice Call",
|
||||||
description: "test",
|
description: "test",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user