fix(agent-config): stabilize patch and target resolution

This commit is contained in:
vince 2026-06-19 12:21:15 +02:00
parent 9b7f20c486
commit b69de71029
2 changed files with 271 additions and 37 deletions

View file

@ -3,6 +3,7 @@ import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import type { DashboardDocument } from "$lib/model";
import { genericDashboardFixture } from "$lib/model/fixtures/generic";
import { createDashboardStore, type DashboardStore } from "$lib/server/db/dashboard-store";
import {
@ -13,6 +14,7 @@ import {
publishDashboardChanges,
rollbackDashboardRevision,
type AgentDashboardOperation,
type JsonPatchOperation,
} from ".";
const stores: DashboardStore[] = [];
@ -125,6 +127,82 @@ describe("agent dashboard configuration API", () => {
expect(result.patch.map((operation) => operation.op)).toContain("remove");
});
test("returns RFC 6902-applicable patches for multiple array removals", () => {
const result = previewDashboardChanges(genericDashboardFixture, [
{
type: "remove_item",
target: { kind: "statusItem", stripId: "runtime", id: "status" },
},
{
type: "remove_item",
target: { kind: "statusItem", stripId: "runtime", id: "sync" },
},
]);
expect(result.ok).toBe(true);
if (!result.ok) throw new Error("expected remove success");
expect(
result.patch
.filter((operation) => operation.op === "remove")
.map((operation) => operation.path),
).toEqual(["/statusStrips/0/items/1", "/statusStrips/0/items/0"]);
expect(applyJsonPatch(genericDashboardFixture, result.patch)).toMatchObject({
statusStrips: [{ items: [] }],
});
});
test("rejects ambiguous service and status targets", () => {
const duplicateDocument = documentWithDuplicateNestedIds();
const connect = previewDashboardChanges(duplicateDocument, [
{
type: "connect_datasource",
target: { kind: "service", id: "identity" },
datasource: {
type: "external",
adapter: "http-status",
reference: "GET https://identity.example.test/health",
},
},
]);
expect(connect.ok).toBe(false);
if (connect.ok) throw new Error("expected ambiguous service failure");
expect(connect.errors[0]).toMatchObject({
code: "ambiguous_target",
operationIndex: 0,
path: "/serviceGroups",
});
const remove = previewDashboardChanges(duplicateDocument, [
{
type: "remove_item",
target: { kind: "service", id: "identity" },
},
]);
expect(remove.ok).toBe(false);
if (remove.ok) throw new Error("expected ambiguous removal failure");
expect(remove.errors[0]).toMatchObject({
code: "ambiguous_target",
operationIndex: 0,
path: "/serviceGroups",
});
const status = previewDashboardChanges(duplicateDocument, [
{
type: "set_status_rule",
target: { kind: "statusItem", id: "status" },
value: "Healthy",
},
]);
expect(status.ok).toBe(false);
if (status.ok) throw new Error("expected ambiguous status failure");
expect(status.errors[0]).toMatchObject({
code: "ambiguous_target",
operationIndex: 0,
path: "/statusStrips",
});
});
test("rejects create_dashboard when it is not the first operation", () => {
const result = previewDashboardChanges(genericDashboardFixture, [
{
@ -434,6 +512,77 @@ function exampleService() {
};
}
function documentWithDuplicateNestedIds(): DashboardDocument {
const document = structuredClone(genericDashboardFixture);
document.layout.serviceGroups.push("secondary-services");
document.serviceGroups.push({
id: "secondary-services",
title: "Secondary Services",
layout: "list",
services: [
{
...document.serviceGroups[0].services[0],
label: "Shadow Identity",
},
],
});
document.layout.statusStrips.push("secondary-runtime");
document.statusStrips.push({
id: "secondary-runtime",
items: [
{
...document.statusStrips[0].items[0],
value: "Healthy",
},
],
});
return document;
}
function applyJsonPatch<T>(value: T, patch: JsonPatchOperation[]): T {
const next = structuredClone(value);
for (const operation of patch) {
const { parent, key } = jsonPointerTarget(next, operation.path);
if (operation.op === "remove") {
if (Array.isArray(parent)) {
parent.splice(Number(key), 1);
} else {
delete parent[key];
}
} else if (operation.op === "add") {
if (Array.isArray(parent)) {
parent.splice(Number(key), 0, operation.value);
} else {
parent[key] = operation.value;
}
} else if (operation.op === "replace") {
if (Array.isArray(parent)) {
parent[Number(key)] = operation.value;
} else {
parent[key] = operation.value;
}
}
}
return next;
}
function jsonPointerTarget(value: unknown, path: string) {
const segments = path
.split("/")
.slice(1)
.map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
const key = segments.pop();
if (key === undefined) throw new Error(`Invalid JSON pointer: ${path}`);
let parent = value as Record<string, unknown> | unknown[];
for (const segment of segments) {
parent = Array.isArray(parent)
? (parent[Number(segment)] as Record<string, unknown> | unknown[])
: (parent[segment] as Record<string, unknown> | unknown[]);
}
return { parent, key };
}
function jsonRequest(body: unknown) {
return new Request("https://dimensionlab.test/api/agent/dashboard", {
body: JSON.stringify(body),