Upgrade the runtime and native plugin API to OpenCode 2.0.24. Restore session-scoped learning, credential renewal, typed project queries and controlled frontend actions from the legacy Agent. Replace host Shell execution with an approved Landlock/seccomp executor and persistent per-session workspaces. Add typed Bocha search and Tianditu geocoding bridges using the owning session credential. Require a working kernel sandbox before startup and persist workspace files in deployments. Validation completed before this commit: TypeScript checks, 150 Bun tests, Docker build and UID 10001 sandbox execution, native Shell approval and session recovery. Tianditu succeeded; Bocha currently reports exhausted account quota. Unrelated investigation-report contract work is excluded.
46 lines
3.2 KiB
TypeScript
46 lines
3.2 KiB
TypeScript
import { afterEach, describe, expect, it } from "bun:test";
|
|
import { ProjectDataClient, projectDataQuerySchema } from "../../src/product/project-data-client.js";
|
|
import { bindSessionCredential, releaseSessionCredential } from "../../src/product/credential-refresh.js";
|
|
import { historyChart } from "../../src/product/history-chart.js";
|
|
|
|
const originalFetch = globalThis.fetch;
|
|
afterEach(() => { globalThis.fetch = originalFetch; releaseSessionCredential("query-session"); });
|
|
describe("controlled project queries", () => {
|
|
it("dispatches SCADA history to the existing fields endpoint using monitored_value", async () => {
|
|
bindSessionCredential("query-session", { authorization: "Bearer TEST", projectId: "trusted-project" });
|
|
globalThis.fetch = (async (url) => {
|
|
const parsed = new URL(String(url));
|
|
expect(parsed.pathname).toBe("/api/v1/timeseries/scada-readings/fields");
|
|
expect(parsed.searchParams.get("field")).toBe("monitored_value");
|
|
expect(parsed.searchParams.get("device_ids")).toBe("D1,D2");
|
|
return Response.json([{ device_id: "D1", value: 20 }]);
|
|
}) as typeof fetch;
|
|
await new ProjectDataClient("http://server.test").query("query-session", { source: "scada_history", device_ids: ["D1", "D2"], field: "monitored_value", start: "2026-09-01T00:00:00Z", end: "2026-09-01T01:00:00Z" });
|
|
});
|
|
it("uses fixed routes and the bound project credential, including encoded asset IDs", async () => {
|
|
bindSessionCredential("query-session", { authorization: "Bearer TEST", projectId: "trusted-project" });
|
|
let requested: URL | undefined;
|
|
globalThis.fetch = (async (url, init) => {
|
|
requested = new URL(String(url));
|
|
expect(new Headers(init?.headers).get("X-Project-Id")).toBe("trusted-project");
|
|
return Response.json({ pressure: 20 });
|
|
}) as typeof fetch;
|
|
expect(await new ProjectDataClient("http://server.test").query("query-session", { source: "element_properties", element_type: "junction", element_id: "J&network=other" })).toEqual({ pressure: 20 });
|
|
expect(requested?.pathname).toBe("/api/v1/junctions/properties");
|
|
expect(requested?.searchParams.get("junction")).toBe("J&network=other");
|
|
expect(requested?.searchParams.has("network")).toBeFalse();
|
|
});
|
|
it("rejects arbitrary routes, project overrides and excessive time windows before dispatch", () => {
|
|
expect(projectDataQuerySchema.safeParse({ source: "scada_devices", project_id: "other" }).success).toBeFalse();
|
|
expect(projectDataQuerySchema.safeParse({ source: "http", url: "http://other" }).success).toBeFalse();
|
|
expect(projectDataQuerySchema.safeParse({ source: "scada_history", device_ids: ["D1"], field: "monitored_value", start: "2026-09-01T00:00:00Z", end: "2026-09-03T00:00:00Z" }).success).toBeFalse();
|
|
});
|
|
it("keeps returned units, drops null samples and never manufactures missing history", () => {
|
|
expect(() => historyChart({ series: [] }, "J1")).toThrow("NO_HISTORY_DATA");
|
|
const chart = historyChart({ series: [{ element_id: "J1", metric: "pressure", source: "scada_raw", source_unit: "m", points: [{ time: "t1", value: null }, { time: "t2", value: 20 }] }] }, "J1");
|
|
expect(chart.title).toContain("(m)");
|
|
expect(chart.x_data).toEqual(["t2"]);
|
|
expect(chart.series[0].data).toEqual([20]);
|
|
});
|
|
});
|