mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-24 10:18:38 +00:00
Gateway: add path-scoped config schema lookup (#37266)
Merged via squash.
Prepared head SHA: 0c4d187f6f
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
committed by
GitHub
parent
c5828cbc08
commit
ff97195500
@@ -1,5 +1,5 @@
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { buildConfigSchema } from "./schema.js";
|
||||
import { buildConfigSchema, lookupConfigSchema } from "./schema.js";
|
||||
import { applyDerivedTags, CONFIG_TAGS, deriveTagsForPath } from "./schema.tags.js";
|
||||
|
||||
describe("config schema", () => {
|
||||
@@ -202,4 +202,101 @@ describe("config schema", () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("looks up a config schema path with immediate child summaries", () => {
|
||||
const lookup = lookupConfigSchema(baseSchema, "gateway.auth");
|
||||
expect(lookup?.path).toBe("gateway.auth");
|
||||
expect(lookup?.hintPath).toBe("gateway.auth");
|
||||
expect(lookup?.children.some((child) => child.key === "token")).toBe(true);
|
||||
const tokenChild = lookup?.children.find((child) => child.key === "token");
|
||||
expect(tokenChild?.path).toBe("gateway.auth.token");
|
||||
expect(tokenChild?.hint?.sensitive).toBe(true);
|
||||
expect(tokenChild?.hintPath).toBe("gateway.auth.token");
|
||||
const schema = lookup?.schema as { properties?: unknown } | undefined;
|
||||
expect(schema?.properties).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns a shallow lookup schema without nested composition keywords", () => {
|
||||
const lookup = lookupConfigSchema(baseSchema, "agents.list.0.runtime");
|
||||
expect(lookup?.path).toBe("agents.list.0.runtime");
|
||||
expect(lookup?.hintPath).toBe("agents.list[].runtime");
|
||||
expect(lookup?.schema).toEqual({});
|
||||
});
|
||||
|
||||
it("matches wildcard ui hints for concrete lookup paths", () => {
|
||||
const lookup = lookupConfigSchema(baseSchema, "agents.list.0.identity.avatar");
|
||||
expect(lookup?.path).toBe("agents.list.0.identity.avatar");
|
||||
expect(lookup?.hintPath).toBe("agents.list.*.identity.avatar");
|
||||
expect(lookup?.hint?.help).toContain("workspace-relative path");
|
||||
});
|
||||
|
||||
it("normalizes bracketed lookup paths", () => {
|
||||
const lookup = lookupConfigSchema(baseSchema, "agents.list[0].identity.avatar");
|
||||
expect(lookup?.path).toBe("agents.list.0.identity.avatar");
|
||||
expect(lookup?.hintPath).toBe("agents.list.*.identity.avatar");
|
||||
});
|
||||
|
||||
it("matches ui hints that use empty array brackets", () => {
|
||||
const lookup = lookupConfigSchema(baseSchema, "agents.list.0.runtime");
|
||||
expect(lookup?.path).toBe("agents.list.0.runtime");
|
||||
expect(lookup?.hintPath).toBe("agents.list[].runtime");
|
||||
expect(lookup?.hint?.label).toBe("Agent Runtime");
|
||||
});
|
||||
|
||||
it("uses the indexed tuple item schema for positional array lookups", () => {
|
||||
const tupleSchema = {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
pair: {
|
||||
type: "array",
|
||||
items: [{ type: "string" }, { type: "number" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
uiHints: {},
|
||||
version: "test",
|
||||
generatedAt: "test",
|
||||
} as unknown as Parameters<typeof lookupConfigSchema>[0];
|
||||
|
||||
const lookup = lookupConfigSchema(tupleSchema, "pair.1");
|
||||
expect(lookup?.path).toBe("pair.1");
|
||||
expect(lookup?.schema).toMatchObject({ type: "number" });
|
||||
expect((lookup?.schema as { items?: unknown } | undefined)?.items).toBeUndefined();
|
||||
});
|
||||
|
||||
it("rejects prototype-chain lookup segments", () => {
|
||||
expect(() => lookupConfigSchema(baseSchema, "constructor")).not.toThrow();
|
||||
expect(lookupConfigSchema(baseSchema, "constructor")).toBeNull();
|
||||
expect(lookupConfigSchema(baseSchema, "__proto__.polluted")).toBeNull();
|
||||
});
|
||||
|
||||
it("rejects overly deep lookup paths", () => {
|
||||
const buildNestedObjectSchema = (segments: string[]) => {
|
||||
const [head, ...rest] = segments;
|
||||
if (!head) {
|
||||
return { type: "string" };
|
||||
}
|
||||
return {
|
||||
type: "object",
|
||||
properties: {
|
||||
[head]: buildNestedObjectSchema(rest),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const deepPathSegments = Array.from({ length: 33 }, (_, index) => `a${index}`);
|
||||
const deepSchema = {
|
||||
schema: buildNestedObjectSchema(deepPathSegments),
|
||||
uiHints: {},
|
||||
version: "test",
|
||||
generatedAt: "test",
|
||||
} as unknown as Parameters<typeof lookupConfigSchema>[0];
|
||||
|
||||
expect(lookupConfigSchema(deepSchema, deepPathSegments.join("."))).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for missing config schema paths", () => {
|
||||
expect(lookupConfigSchema(baseSchema, "gateway.notReal.path")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user