fix: resolve M-01..M-02, M-05..M-08, L-01..L-03, L-05 from pre-release review
M-01: Compose OperationDefinitionSchema from OperationSpecSchema via Type.Intersect M-02: Extract from_openapi to subpath export, remove from main entry M-05: Fix mapError fragile includes() matching — use startsWith(code+':') or exact M-06: Replace any casts with MCPClientLike/MCPToolResult interfaces in from_mcp M-07: Add injectable fetch to HTTPServiceConfig for from_openapi M-08: Add OpenAPIServiceRegistry with lifecycle methods (add, remove, registerAll) L-01: Validate subscription handler type at registration and runtime L-02: Strengthen isResponseEnvelope with source-specific field validation L-03: Add logger.warn on FromSchema fallback to Type.Unknown L-04: Noted as intentional (SSE GET body handling) L-05: Add registerAll to MCPClientLoader and OpenAPIServiceRegistry
This commit is contained in:
@@ -380,4 +380,85 @@ describe("subscribe", () => {
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results[0].data).toBe("secret-event");
|
||||
});
|
||||
|
||||
it("throws CallError when handler returns a non-async-iterable (plain async function)", async () => {
|
||||
const registry = new OperationRegistry();
|
||||
registry.registerSpec({
|
||||
name: "badSub",
|
||||
namespace: "test",
|
||||
version: "1.0.0",
|
||||
type: OperationType.SUBSCRIPTION,
|
||||
description: "bad sub",
|
||||
inputSchema: Type.Object({}),
|
||||
outputSchema: Type.Unknown(),
|
||||
accessControl: { requiredScopes: [] },
|
||||
});
|
||||
const plainAsyncFn = async (_input: unknown, _context: OperationContext) => "not a generator";
|
||||
(registry as any).handlers.set("test.badSub", plainAsyncFn);
|
||||
|
||||
try {
|
||||
for await (const _ of subscribe(registry, "test.badSub", {}, makeContext())) {
|
||||
expect.fail("Should have thrown");
|
||||
}
|
||||
expect.fail("Should have thrown");
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(CallError);
|
||||
expect((error as CallError).code).toBe(InfrastructureErrorCode.EXECUTION_ERROR);
|
||||
expect((error as CallError).message).toContain("must return an async iterable");
|
||||
}
|
||||
});
|
||||
|
||||
it("throws CallError when handler returns null", async () => {
|
||||
const registry = new OperationRegistry();
|
||||
registry.registerSpec({
|
||||
name: "nullSub",
|
||||
namespace: "test",
|
||||
version: "1.0.0",
|
||||
type: OperationType.SUBSCRIPTION,
|
||||
description: "null sub",
|
||||
inputSchema: Type.Object({}),
|
||||
outputSchema: Type.Unknown(),
|
||||
accessControl: { requiredScopes: [] },
|
||||
});
|
||||
const nullFn = (_input: unknown, _context: OperationContext): any => null;
|
||||
(registry as any).handlers.set("test.nullSub", nullFn);
|
||||
|
||||
try {
|
||||
for await (const _ of subscribe(registry, "test.nullSub", {}, makeContext())) {
|
||||
expect.fail("Should have thrown");
|
||||
}
|
||||
expect.fail("Should have thrown");
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(CallError);
|
||||
expect((error as CallError).code).toBe(InfrastructureErrorCode.EXECUTION_ERROR);
|
||||
expect((error as CallError).message).toContain("must return an async iterable");
|
||||
}
|
||||
});
|
||||
|
||||
it("throws CallError when handler returns a plain object (non-iterable)", async () => {
|
||||
const registry = new OperationRegistry();
|
||||
registry.registerSpec({
|
||||
name: "objSub",
|
||||
namespace: "test",
|
||||
version: "1.0.0",
|
||||
type: OperationType.SUBSCRIPTION,
|
||||
description: "obj sub",
|
||||
inputSchema: Type.Object({}),
|
||||
outputSchema: Type.Unknown(),
|
||||
accessControl: { requiredScopes: [] },
|
||||
});
|
||||
const objFn = (_input: unknown, _context: OperationContext): any => ({ not: "iterable" });
|
||||
(registry as any).handlers.set("test.objSub", objFn);
|
||||
|
||||
try {
|
||||
for await (const _ of subscribe(registry, "test.objSub", {}, makeContext())) {
|
||||
expect.fail("Should have thrown");
|
||||
}
|
||||
expect.fail("Should have thrown");
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(CallError);
|
||||
expect((error as CallError).code).toBe(InfrastructureErrorCode.EXECUTION_ERROR);
|
||||
expect((error as CallError).message).toContain("must return an async iterable");
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user