fix: address console read client review findings
This commit is contained in:
@@ -10,6 +10,40 @@ const executor = () =>
|
||||
({ run: vi.fn().mockResolvedValue({}) }) as unknown as ConsoleReadExecutor;
|
||||
|
||||
describe("CapabilityClient", () => {
|
||||
it("decodes the actual capability interpreter envelope at the client boundary", async () => {
|
||||
const interpreterResult = {
|
||||
capabilities: [
|
||||
{
|
||||
kind: "node_spec",
|
||||
name: "local.lda_docs.read_documents",
|
||||
sourceId: "local.lda_docs",
|
||||
description: "Read selected project documents.",
|
||||
outcomes: ["ok", "error"],
|
||||
isAsync: false,
|
||||
inputFields: ["names"],
|
||||
outputFields: ["documents"],
|
||||
},
|
||||
],
|
||||
nextCursor: null,
|
||||
total: 1,
|
||||
};
|
||||
const run = vi.fn(
|
||||
(
|
||||
_operation: unknown,
|
||||
_params: unknown,
|
||||
decode: (value: unknown) => unknown,
|
||||
) => Promise.resolve(decode(interpreterResult)),
|
||||
);
|
||||
const client = createCapabilityClient({ run } as unknown as ConsoleReadExecutor);
|
||||
|
||||
const page = await client.list({});
|
||||
|
||||
expect(page.capabilities[0]?.name).toBe(
|
||||
"local.lda_docs.read_documents",
|
||||
);
|
||||
expect(page.total).toBe(1);
|
||||
});
|
||||
|
||||
it("lowers list filters to the capability operation payload", async () => {
|
||||
const readExecutor = executor();
|
||||
const client = createCapabilityClient(readExecutor);
|
||||
|
||||
@@ -12,7 +12,7 @@ const wrapperHints = {
|
||||
describe("capability models", () => {
|
||||
it("preserves discriminated node and wrapper summaries", () => {
|
||||
const page = decodeCapabilityPage({
|
||||
items: [
|
||||
capabilities: [
|
||||
{
|
||||
kind: "node_spec",
|
||||
name: "local.docs.read",
|
||||
@@ -21,6 +21,7 @@ describe("capability models", () => {
|
||||
outcomes: ["ok", "error"],
|
||||
inputFields: ["names"],
|
||||
outputFields: ["documents"],
|
||||
isAsync: false,
|
||||
},
|
||||
{
|
||||
kind: "wrapper_artifact",
|
||||
@@ -30,15 +31,19 @@ describe("capability models", () => {
|
||||
outcomes: ["ok"],
|
||||
inputFields: [],
|
||||
outputFields: ["report"],
|
||||
isAsync: true,
|
||||
artifactId: "reports",
|
||||
title: "Reports",
|
||||
version: 2,
|
||||
},
|
||||
],
|
||||
nextCursor: null,
|
||||
total: 2,
|
||||
});
|
||||
|
||||
expect(page.items[0]?.kind).toBe("node_spec");
|
||||
expect(page.items[1]?.kind).toBe("wrapper_artifact");
|
||||
expect(page.items[0]?.description).toBeNull();
|
||||
expect(page.capabilities[0]?.kind).toBe("node_spec");
|
||||
expect(page.capabilities[1]?.kind).toBe("wrapper_artifact");
|
||||
expect(page.capabilities[0]?.description).toBeNull();
|
||||
});
|
||||
|
||||
it("decodes nullable detail fields for both capability kinds", () => {
|
||||
|
||||
@@ -37,7 +37,7 @@ const CapabilitySummarySchema = v.variant("kind", [
|
||||
]);
|
||||
|
||||
const CapabilityPageSchema = v.object({
|
||||
items: v.array(CapabilitySummarySchema),
|
||||
capabilities: v.array(CapabilitySummarySchema),
|
||||
nextCursor: v.nullish(v.string(), null),
|
||||
total: v.number(),
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { ConsoleApiError } from "../../connection/api.js";
|
||||
import type { RpcResponse } from "../../connection/contracts.js";
|
||||
import { createConsoleReadExecutor } from "./read-executor.js";
|
||||
|
||||
@@ -103,6 +104,28 @@ describe("ConsoleReadExecutor", () => {
|
||||
expect(recordEvidence).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["protocol", "malformed JSON response"],
|
||||
["decode", "malformed response from server"],
|
||||
] as const)(
|
||||
"maps typed %s invocation failures to decode errors",
|
||||
async (kind, message) => {
|
||||
const recordEvidence = vi.fn();
|
||||
const executor = createConsoleReadExecutor({
|
||||
target: "http://console.test/rpc",
|
||||
recordEvidence,
|
||||
invoke: vi.fn(async () => {
|
||||
throw new ConsoleApiError(kind, message);
|
||||
}),
|
||||
});
|
||||
|
||||
await expect(
|
||||
executor.run("workflow.capabilities.list", {}, (value) => value),
|
||||
).rejects.toMatchObject({ kind: "decode", message });
|
||||
expect(recordEvidence).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps evidence ids unique across consecutive reads", async () => {
|
||||
const recordEvidence = vi.fn();
|
||||
const executor = createConsoleReadExecutor({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { callOperation } from "../../connection/api.js";
|
||||
import { callOperation, ConsoleApiError } from "../../connection/api.js";
|
||||
import type {
|
||||
OperationName,
|
||||
RpcResponse,
|
||||
@@ -37,6 +37,13 @@ const errorKindForCode = (code: string): ConsoleClientErrorKind => {
|
||||
const errorMessage = (error: unknown): string =>
|
||||
error instanceof Error ? error.message : String(error);
|
||||
|
||||
const clientErrorKindForInvocation = (
|
||||
error: unknown,
|
||||
): "decode" | "transport" =>
|
||||
error instanceof ConsoleApiError && error.kind !== "transport"
|
||||
? "decode"
|
||||
: "transport";
|
||||
|
||||
export const createConsoleReadExecutor = (options: {
|
||||
readonly target: string;
|
||||
readonly recordEvidence: (record: EvidenceRecord) => void;
|
||||
@@ -83,7 +90,7 @@ export const createConsoleReadExecutor = (options: {
|
||||
0,
|
||||
);
|
||||
throw new ConsoleClientError(
|
||||
"transport",
|
||||
clientErrorKindForInvocation(error),
|
||||
operation,
|
||||
errorMessage(error),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user