mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-09-08 10:51:59 +02:00
252 lines
7.3 KiB
TypeScript
252 lines
7.3 KiB
TypeScript
import { describe, expect, test } from "vite-plus/test";
|
|
import type { JsonSchema } from "./jsonSchemaExample";
|
|
import { buildExampleFromSchema } from "./jsonSchemaExample";
|
|
|
|
describe("buildExampleFromSchema", () => {
|
|
test("fills scalar fields with placeholders", () => {
|
|
const schema: JsonSchema = {
|
|
type: "object",
|
|
properties: {
|
|
name: { type: "string" },
|
|
age: { type: "number", format: "int32" },
|
|
active: { type: "boolean" },
|
|
data: { type: "string", format: "byte" },
|
|
},
|
|
};
|
|
|
|
expect(buildExampleFromSchema(schema)).toEqual({
|
|
name: "",
|
|
age: 0,
|
|
active: false,
|
|
data: "",
|
|
});
|
|
});
|
|
|
|
test("encodes 64-bit integers as strings", () => {
|
|
const schema: JsonSchema = {
|
|
type: "object",
|
|
properties: {
|
|
id: { type: "string", format: "int64" },
|
|
count: { type: "string", format: "uint64" },
|
|
offset: { type: "string", format: "sfixed64" },
|
|
},
|
|
};
|
|
|
|
expect(buildExampleFromSchema(schema)).toEqual({ id: "0", count: "0", offset: "0" });
|
|
});
|
|
|
|
test("fills date-time with a parseable timestamp", () => {
|
|
const schema: JsonSchema = {
|
|
type: "object",
|
|
properties: { createdAt: { type: "string", format: "date-time" } },
|
|
};
|
|
|
|
const example = buildExampleFromSchema(schema) as { createdAt: string };
|
|
expect(Number.isNaN(Date.parse(example.createdAt))).toBe(false);
|
|
});
|
|
|
|
test("fills a duration with a value that parses", () => {
|
|
const schema: JsonSchema = {
|
|
type: "object",
|
|
properties: { timeout: { type: "string", format: "duration" } },
|
|
};
|
|
|
|
// An empty string fails protobuf's Duration parsing, so the message wouldn't send
|
|
expect(buildExampleFromSchema(schema)).toEqual({ timeout: "0s" });
|
|
});
|
|
|
|
test("expands nested messages through $defs", () => {
|
|
const schema: JsonSchema = {
|
|
type: "object",
|
|
properties: { user: { $ref: "#/$defs/example.User" } },
|
|
$defs: {
|
|
"example.User": {
|
|
type: "object",
|
|
properties: {
|
|
name: { type: "string" },
|
|
address: { $ref: "#/$defs/example.Address" },
|
|
},
|
|
},
|
|
"example.Address": {
|
|
type: "object",
|
|
properties: { city: { type: "string" } },
|
|
},
|
|
},
|
|
};
|
|
|
|
expect(buildExampleFromSchema(schema)).toEqual({
|
|
user: { name: "", address: { city: "" } },
|
|
});
|
|
});
|
|
|
|
test("gives repeated fields a single placeholder item", () => {
|
|
const schema: JsonSchema = {
|
|
type: "object",
|
|
properties: {
|
|
tags: { type: "array", items: { type: "string" } },
|
|
users: { type: "array", items: { $ref: "#/$defs/example.User" } },
|
|
unknown: { type: "array" },
|
|
},
|
|
$defs: {
|
|
"example.User": { type: "object", properties: { name: { type: "string" } } },
|
|
},
|
|
};
|
|
|
|
expect(buildExampleFromSchema(schema)).toEqual({
|
|
tags: [""],
|
|
users: [{ name: "" }],
|
|
unknown: [],
|
|
});
|
|
});
|
|
|
|
test("uses the first value of an enum", () => {
|
|
const schema: JsonSchema = {
|
|
type: "object",
|
|
properties: {
|
|
status: { type: "string", enum: ["STATUS_UNSPECIFIED", "STATUS_ACTIVE"] },
|
|
empty: { type: "string", enum: [] },
|
|
},
|
|
};
|
|
|
|
expect(buildExampleFromSchema(schema)).toEqual({ status: "STATUS_UNSPECIFIED", empty: "" });
|
|
});
|
|
|
|
test("gives maps a single placeholder entry", () => {
|
|
const schema: JsonSchema = {
|
|
type: "object",
|
|
properties: {
|
|
labels: { type: "object", additionalProperties: { type: "string" } },
|
|
users: { type: "object", additionalProperties: { $ref: "#/$defs/example.User" } },
|
|
},
|
|
$defs: {
|
|
"example.User": { type: "object", properties: { name: { type: "string" } } },
|
|
},
|
|
};
|
|
|
|
expect(buildExampleFromSchema(schema)).toEqual({
|
|
labels: { key: "" },
|
|
users: { key: { name: "" } },
|
|
});
|
|
});
|
|
|
|
test("stops at the root self-reference", () => {
|
|
const schema: JsonSchema = {
|
|
type: "object",
|
|
properties: {
|
|
value: { type: "string" },
|
|
children: { type: "array", items: { $ref: "#" } },
|
|
},
|
|
};
|
|
|
|
expect(buildExampleFromSchema(schema)).toEqual({ value: "", children: [{}] });
|
|
});
|
|
|
|
test("stops at a cycle between messages", () => {
|
|
const schema: JsonSchema = {
|
|
type: "object",
|
|
properties: { node: { $ref: "#/$defs/example.Node" } },
|
|
$defs: {
|
|
"example.Node": {
|
|
type: "object",
|
|
properties: {
|
|
name: { type: "string" },
|
|
parent: { $ref: "#/$defs/example.Node" },
|
|
leaf: { $ref: "#/$defs/example.Leaf" },
|
|
},
|
|
},
|
|
"example.Leaf": {
|
|
type: "object",
|
|
properties: { node: { $ref: "#/$defs/example.Node" } },
|
|
},
|
|
},
|
|
};
|
|
|
|
expect(buildExampleFromSchema(schema)).toEqual({
|
|
node: { name: "", parent: {}, leaf: { node: {} } },
|
|
});
|
|
});
|
|
|
|
test("expands the same message twice when it is not on the same path", () => {
|
|
const schema: JsonSchema = {
|
|
type: "object",
|
|
properties: {
|
|
from: { $ref: "#/$defs/example.User" },
|
|
to: { $ref: "#/$defs/example.User" },
|
|
},
|
|
$defs: {
|
|
"example.User": { type: "object", properties: { name: { type: "string" } } },
|
|
},
|
|
};
|
|
|
|
expect(buildExampleFromSchema(schema)).toEqual({ from: { name: "" }, to: { name: "" } });
|
|
});
|
|
|
|
test("fills every branch of a flattened oneof", () => {
|
|
const schema: JsonSchema = {
|
|
type: "object",
|
|
properties: {
|
|
id: { type: "string" },
|
|
text: { type: "string" },
|
|
image: { $ref: "#/$defs/example.Image" },
|
|
},
|
|
$defs: {
|
|
"example.Image": { type: "object", properties: { url: { type: "string" } } },
|
|
},
|
|
};
|
|
|
|
expect(buildExampleFromSchema(schema)).toEqual({
|
|
id: "",
|
|
text: "",
|
|
image: { url: "" },
|
|
});
|
|
});
|
|
|
|
test("stops expanding once the node budget runs out", () => {
|
|
// Every level references the next one twice, so an unbounded walk would build 2^depth
|
|
// nodes without ever repeating a ref on the same path.
|
|
const depth = 16;
|
|
const $defs: Record<string, JsonSchema> = { [`d${depth}`]: { type: "string" } };
|
|
for (let i = 0; i < depth; i++) {
|
|
$defs[`d${i}`] = {
|
|
type: "object",
|
|
properties: {
|
|
a: { $ref: `#/$defs/d${i + 1}` },
|
|
b: { $ref: `#/$defs/d${i + 1}` },
|
|
},
|
|
};
|
|
}
|
|
|
|
const example = buildExampleFromSchema({
|
|
type: "object",
|
|
properties: { root: { $ref: "#/$defs/d0" } },
|
|
$defs,
|
|
});
|
|
|
|
// 2 ** 16 nodes unbounded; the budget holds it to a couple of thousand
|
|
expect(countNodes(example)).toBeLessThan(10_000);
|
|
});
|
|
|
|
test("handles messages without a known type", () => {
|
|
const schema: JsonSchema = {
|
|
type: "object",
|
|
properties: {
|
|
empty: {},
|
|
struct: { type: "object" },
|
|
missing: { $ref: "#/$defs/example.Nope" },
|
|
},
|
|
};
|
|
|
|
expect(buildExampleFromSchema(schema)).toEqual({ empty: null, struct: {}, missing: {} });
|
|
});
|
|
});
|
|
|
|
function countNodes(value: unknown): number {
|
|
if (Array.isArray(value)) {
|
|
return 1 + value.reduce((total: number, v) => total + countNodes(v), 0);
|
|
}
|
|
if (value !== null && typeof value === "object") {
|
|
return 1 + Object.values(value).reduce((total: number, v) => total + countNodes(v), 0);
|
|
}
|
|
return 1;
|
|
}
|