fix: reject non-json model values
This commit is contained in:
parent
74651a49de
commit
b7c673bbd7
3 changed files with 72 additions and 3 deletions
|
|
@ -140,16 +140,17 @@ function service(
|
||||||
icon: string,
|
icon: string,
|
||||||
href?: string,
|
href?: string,
|
||||||
) {
|
) {
|
||||||
return {
|
const entry = {
|
||||||
id,
|
id,
|
||||||
label,
|
label,
|
||||||
description,
|
description,
|
||||||
icon,
|
icon,
|
||||||
link: href ? { href, external: true } : undefined,
|
|
||||||
severity: "ok" as const,
|
severity: "ok" as const,
|
||||||
detail: "ready",
|
detail: "ready",
|
||||||
datasource: { type: "placeholder" as const, reason: "service health adapter pending" },
|
datasource: { type: "placeholder" as const, reason: "service health adapter pending" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return href ? { ...entry, link: { href, external: true } } : entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
function group(id: string, title: string, services: ReturnType<typeof service>[]) {
|
function group(id: string, title: string, services: ReturnType<typeof service>[]) {
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,31 @@ describe("dashboard model validation", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("rejects thresholds on text metric values", () => {
|
||||||
|
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
|
||||||
|
invalid.telemetry[0].value = { kind: "text", value: "available" };
|
||||||
|
invalid.telemetry[0].thresholds = { warning: 10 };
|
||||||
|
|
||||||
|
const result = validateDashboardDocument(invalid);
|
||||||
|
|
||||||
|
expect(result.valid).toBe(false);
|
||||||
|
if (!result.valid) {
|
||||||
|
expect(result.errors.join(" ")).toContain("text metric values");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects undefined properties because they are not JSON values", () => {
|
||||||
|
const invalid = JSON.parse(JSON.stringify(genericDashboardFixture));
|
||||||
|
invalid.serviceGroups[0].services[0].link = undefined;
|
||||||
|
|
||||||
|
const result = validateDashboardDocument(invalid);
|
||||||
|
|
||||||
|
expect(result.valid).toBe(false);
|
||||||
|
if (!result.valid) {
|
||||||
|
expect(result.errors.join(" ")).toContain("must be omitted instead of undefined");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("exports JSON Schema for external tool contracts", () => {
|
it("exports JSON Schema for external tool contracts", () => {
|
||||||
expect(dashboardDocumentJsonSchema.$id).toContain("dashboard-document.v1");
|
expect(dashboardDocumentJsonSchema.$id).toContain("dashboard-document.v1");
|
||||||
expect(dashboardDocumentJsonSchema.properties).toHaveProperty("schemaVersion");
|
expect(dashboardDocumentJsonSchema.properties).toHaveProperty("schemaVersion");
|
||||||
|
|
|
||||||
|
|
@ -55,11 +55,14 @@ export function validateDashboardDocument(
|
||||||
value: unknown,
|
value: unknown,
|
||||||
): DashboardValidationResult {
|
): DashboardValidationResult {
|
||||||
const finiteNumberIssues: SemanticValidationIssue[] = [];
|
const finiteNumberIssues: SemanticValidationIssue[] = [];
|
||||||
|
const undefinedIssues: SemanticValidationIssue[] = [];
|
||||||
collectFiniteNumberIssues(value, "", finiteNumberIssues);
|
collectFiniteNumberIssues(value, "", finiteNumberIssues);
|
||||||
|
collectUndefinedIssues(value, "", undefinedIssues);
|
||||||
|
|
||||||
if (validateDashboard(value)) {
|
if (validateDashboard(value)) {
|
||||||
const semanticIssues = [
|
const semanticIssues = [
|
||||||
...finiteNumberIssues,
|
...finiteNumberIssues,
|
||||||
|
...undefinedIssues,
|
||||||
...validateSemanticRules(value),
|
...validateSemanticRules(value),
|
||||||
];
|
];
|
||||||
if (semanticIssues.length === 0) {
|
if (semanticIssues.length === 0) {
|
||||||
|
|
@ -73,7 +76,11 @@ export function validateDashboardDocument(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const details = [...(validateDashboard.errors || []), ...finiteNumberIssues];
|
const details = [
|
||||||
|
...(validateDashboard.errors || []),
|
||||||
|
...finiteNumberIssues,
|
||||||
|
...undefinedIssues,
|
||||||
|
];
|
||||||
return {
|
return {
|
||||||
valid: false,
|
valid: false,
|
||||||
errors: formatValidationErrors(details),
|
errors: formatValidationErrors(details),
|
||||||
|
|
@ -154,6 +161,16 @@ function validateSemanticRules(document: DashboardDocument): SemanticValidationI
|
||||||
);
|
);
|
||||||
|
|
||||||
document.telemetry.forEach((card, index) => {
|
document.telemetry.forEach((card, index) => {
|
||||||
|
if (card.value.kind === "text" && card.thresholds !== undefined) {
|
||||||
|
issues.push(
|
||||||
|
semanticIssue(
|
||||||
|
`/telemetry/${index}/thresholds`,
|
||||||
|
"must not be set for text metric values",
|
||||||
|
{ id: card.id },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const warning = card.thresholds?.warning;
|
const warning = card.thresholds?.warning;
|
||||||
const danger = card.thresholds?.danger;
|
const danger = card.thresholds?.danger;
|
||||||
if (warning !== undefined && danger !== undefined && warning > danger) {
|
if (warning !== undefined && danger !== undefined && warning > danger) {
|
||||||
|
|
@ -170,6 +187,32 @@ function validateSemanticRules(document: DashboardDocument): SemanticValidationI
|
||||||
return issues;
|
return issues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function collectUndefinedIssues(
|
||||||
|
value: unknown,
|
||||||
|
path: string,
|
||||||
|
issues: SemanticValidationIssue[],
|
||||||
|
) {
|
||||||
|
if (value === undefined) {
|
||||||
|
issues.push(
|
||||||
|
semanticIssue(path || "/", "must be omitted instead of undefined", {}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.forEach((item, index) => {
|
||||||
|
collectUndefinedIssues(item, `${path}/${index}`, issues);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value && typeof value === "object") {
|
||||||
|
Object.entries(value).forEach(([key, item]) => {
|
||||||
|
collectUndefinedIssues(item, `${path}/${escapeJsonPointer(key)}`, issues);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function collectFiniteNumberIssues(
|
function collectFiniteNumberIssues(
|
||||||
value: unknown,
|
value: unknown,
|
||||||
path: string,
|
path: string,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue