mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
248 lines
8.2 KiB
TypeScript
248 lines
8.2 KiB
TypeScript
import { Ajv } from "ajv";
|
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
import { describe, expect, it } from "vitest";
|
|
import { z } from "zod";
|
|
|
|
import { inlineLocalJsonSchemaReferences } from "../../tests/fixtures/localJsonSchemaReferences.js";
|
|
import { presentInputJsonSchema } from "./inputSchemaPresentation.js";
|
|
import { TOOL_CONTRACTS, toolContract } from "./toolContracts.js";
|
|
import { toolInputSchemaWithMetadata } from "./toolSchemaMetadata.js";
|
|
|
|
describe("advertised input references", () => {
|
|
it.each(["draft-2020-12", "draft-07"] as const)(
|
|
"preserves production native-selector constraints in %s",
|
|
(target) => {
|
|
const contract = toolContract("observe_native_calls");
|
|
const advertised = toolInputSchemaWithMetadata(contract)[
|
|
"~standard"
|
|
].jsonSchema.input({ target });
|
|
const ajv =
|
|
target === "draft-2020-12"
|
|
? new Ajv2020({ strict: false })
|
|
: new Ajv({ strict: false });
|
|
const validate = ajv.compile(advertised);
|
|
for (const selector of [
|
|
"setFoo:",
|
|
"dataTaskWithRequest:completionHandler:",
|
|
"",
|
|
"bad selector:",
|
|
"bad[selector]:",
|
|
"set\0Foo:",
|
|
"setFoo:\n",
|
|
])
|
|
for (const extra of [{}, { unexpected: true }]) {
|
|
const sample = {
|
|
breakpoints: [
|
|
{
|
|
kind: "objc-method",
|
|
class_name: "NSURLSession",
|
|
selector,
|
|
...extra,
|
|
},
|
|
],
|
|
};
|
|
expect(validate(sample)).toBe(
|
|
contract.inputSchema.safeParse(sample).success,
|
|
);
|
|
}
|
|
},
|
|
);
|
|
|
|
it.each(["draft-2020-12", "draft-07"] as const)(
|
|
"preserves complete input meaning and examples through %s references",
|
|
(target) => {
|
|
for (const contract of TOOL_CONTRACTS) {
|
|
const projection =
|
|
toolInputSchemaWithMetadata(contract)["~standard"].jsonSchema.input;
|
|
const referenced = projection({ target });
|
|
const inline = projection({
|
|
target,
|
|
libraryOptions: { reused: "inline" },
|
|
});
|
|
expect(referenced.type, contract.name).toBe("object");
|
|
expect(
|
|
inlineLocalJsonSchemaReferences(referenced),
|
|
contract.name,
|
|
).toEqual(inlineLocalJsonSchemaReferences(inline));
|
|
expect(referenced.examples, contract.name).toEqual(
|
|
contract.examples.map(({ input }) => input),
|
|
);
|
|
expect(projection({ target })).toBe(referenced);
|
|
}
|
|
},
|
|
);
|
|
|
|
it.each(["draft-2020-12", "draft-07"] as const)(
|
|
"retains specialized descriptions, literal metadata and strict validation in %s",
|
|
(target) => {
|
|
const literal = {
|
|
properties: { target: { $ref: "literal caller data" } },
|
|
examples: [{ properties: { path: {} } }],
|
|
};
|
|
const range = z
|
|
.object({
|
|
offset: z.number().int().nonnegative().max(65535),
|
|
length: z.number().int().min(1),
|
|
})
|
|
.strict()
|
|
.describe("Exact byte range in the selected artifact.");
|
|
const canonical = z
|
|
.object({
|
|
before: range,
|
|
after: range,
|
|
metadata: z
|
|
.record(z.string(), z.unknown())
|
|
.meta({ default: literal, examples: [literal] }),
|
|
constructor: z.string(),
|
|
})
|
|
.strict();
|
|
const advertised = toolInputSchemaWithMetadata({
|
|
...toolContract("open_binary"),
|
|
inputSchema: canonical,
|
|
})["~standard"].jsonSchema.input({ target });
|
|
expect(inlineLocalJsonSchemaReferences(advertised)).toMatchObject({
|
|
properties: {
|
|
before: { description: "Exact byte range in the selected artifact." },
|
|
after: { description: "Exact byte range in the selected artifact." },
|
|
metadata: { default: literal, examples: [literal] },
|
|
constructor: { description: "Value for constructor." },
|
|
},
|
|
});
|
|
const ajv =
|
|
target === "draft-2020-12"
|
|
? new Ajv2020({ strict: false })
|
|
: new Ajv({ strict: false });
|
|
const validate = ajv.compile(advertised);
|
|
const valid = {
|
|
before: { offset: 0, length: 1 },
|
|
after: { offset: 65535, length: 2 },
|
|
metadata: literal,
|
|
constructor: "constructor data",
|
|
};
|
|
const samples = [
|
|
valid,
|
|
...[
|
|
{ offset: -1 },
|
|
{ offset: 65536 },
|
|
{ offset: 0.5 },
|
|
{ length: 0 },
|
|
{ unexpected: true },
|
|
].map((change) => ({
|
|
...valid,
|
|
before: { ...valid.before, ...change },
|
|
})),
|
|
{ ...valid, after: null },
|
|
{ ...valid, unexpected: true },
|
|
];
|
|
expect(canonical.safeParse(valid).success).toBe(true);
|
|
for (const sample of samples)
|
|
expect(validate(sample)).toBe(canonical.safeParse(sample).success);
|
|
},
|
|
);
|
|
});
|
|
|
|
describe("root input presentation", () => {
|
|
it("normalizes repeated string intersections without widening validation", () => {
|
|
const root = {
|
|
type: "string",
|
|
minLength: 1,
|
|
description: "Exact selector.",
|
|
allOf: [
|
|
{ type: "string", pattern: "^[^\\s]+$" },
|
|
{ allOf: [{ type: "string", pattern: "^[^\\s]+$" }], minLength: 2 },
|
|
],
|
|
};
|
|
const normalized = inlineLocalJsonSchemaReferences(root);
|
|
expect(normalized).toEqual({
|
|
type: "string",
|
|
minLength: 2,
|
|
pattern: "^[^\\s]+$",
|
|
description: "Exact selector.",
|
|
});
|
|
const ajv = new Ajv2020({ strict: false });
|
|
const original = ajv.compile(root);
|
|
const projected = ajv.compile(
|
|
z.record(z.string(), z.unknown()).parse(normalized),
|
|
);
|
|
for (const sample of ["", "a", "aa", "a a", "a\n", null, {}])
|
|
expect(projected(sample)).toBe(original(sample));
|
|
});
|
|
|
|
it("preserves prototype-named properties in object-root presentation", () => {
|
|
const fields = Object.fromEntries([
|
|
["__proto__", { type: "string" }],
|
|
["constructor", { type: "number" }],
|
|
]);
|
|
const branch = {
|
|
type: "object",
|
|
properties: fields,
|
|
required: ["__proto__"],
|
|
additionalProperties: false,
|
|
};
|
|
const presented = presentInputJsonSchema(
|
|
{ anyOf: [branch, branch] },
|
|
(property) => `Parameter ${property}`,
|
|
);
|
|
expect(presented).toMatchObject({
|
|
type: "object",
|
|
required: ["__proto__"],
|
|
minProperties: 1,
|
|
additionalProperties: false,
|
|
properties: fields,
|
|
});
|
|
expect(presented.anyOf).toBeUndefined();
|
|
expect(Object.getPrototypeOf(presented.properties)).toBe(Object.prototype);
|
|
expect(Object.hasOwn(presented.properties ?? {}, "__proto__")).toBe(true);
|
|
});
|
|
|
|
it("retains reference siblings and does not manufacture root branch constraints", () => {
|
|
const branch = { $ref: "#/$defs/object", additionalProperties: false };
|
|
const root = {
|
|
$defs: {
|
|
object: { type: "object", properties: { value: { type: "string" } } },
|
|
},
|
|
anyOf: [branch],
|
|
};
|
|
const presented = presentInputJsonSchema(root, (property) => property);
|
|
expect(presented.anyOf).toEqual([branch]);
|
|
const ajv = new Ajv2020({ strict: false });
|
|
for (const sample of [{}, { value: "value" }, null])
|
|
expect(ajv.compile(presented)(sample)).toBe(ajv.compile(root)(sample));
|
|
});
|
|
|
|
it("preserves specialized guidance across reference chains without following literal references", () => {
|
|
const literal = { properties: { literal: {} }, $ref: "#/$defs/missing" };
|
|
const presented = presentInputJsonSchema(
|
|
{
|
|
$defs: {
|
|
"a~b": {
|
|
type: "string",
|
|
description: "Literal URL selected by the caller.",
|
|
},
|
|
alias: { $ref: "#/$defs/a~0b" },
|
|
},
|
|
type: "object",
|
|
properties: {
|
|
url: { $ref: "#/$defs/alias" },
|
|
other: { $ref: "#/$defs/alias", description: "Explicit override." },
|
|
},
|
|
default: literal,
|
|
const: literal,
|
|
examples: [literal],
|
|
enum: [literal],
|
|
},
|
|
(property) => `Parameter ${property}`,
|
|
);
|
|
expect(presented).toMatchObject({
|
|
properties: {
|
|
url: { description: "Literal URL selected by the caller." },
|
|
other: { description: "Explicit override." },
|
|
},
|
|
default: literal,
|
|
const: literal,
|
|
examples: [literal],
|
|
enum: [literal],
|
|
});
|
|
});
|
|
});
|