Files
operations/test/env.test.ts
glm-5.1 f534004615 feat(unified-env-remove-callmap): clean up 'direct mode' references in env tests
Remove outdated 'direct mode' terminology from test descriptions
since there is now only one invocation path (registry.execute).
The callMap option was removed from buildEnv() in previous tasks.
2026-05-11 03:29:28 +00:00

271 lines
8.5 KiB
TypeScript

import { describe, it, expect, vi } from "vitest";
import { OperationRegistry, OperationType, buildEnv, type IOperationDefinition, type OperationContext } from "../src/index.js";
import * as Type from "@alkdev/typebox";
import { httpEnvelope, isResponseEnvelope, type ResponseEnvelope } from "../src/response-envelope.js";
import { CallError, InfrastructureErrorCode } from "../src/error.js";
import type { Identity } from "../src/types.js";
function makeOperation(name: string, handler?: any): IOperationDefinition {
return {
name,
namespace: "test",
version: "1.0.0",
type: OperationType.QUERY,
description: `Test ${name}`,
inputSchema: Type.Object({ value: Type.String() }),
outputSchema: Type.Object({ result: Type.String() }),
accessControl: { requiredScopes: [] },
handler: handler || (async (input: any) => ({ result: input.value })),
};
}
describe("buildEnv", () => {
it("creates namespace-keyed env", async () => {
const registry = new OperationRegistry();
registry.register(makeOperation("readFile"));
registry.register(makeOperation("writeFile"));
const env = buildEnv({
registry,
context: {} as OperationContext,
});
expect(env.test).toBeDefined();
expect(typeof env.test.readFile).toBe("function");
expect(typeof env.test.writeFile).toBe("function");
const result = await env.test.readFile({ value: "test" });
expect(isResponseEnvelope(result)).toBe(true);
expect(result.meta.source).toBe("local");
expect(result.data).toEqual({ result: "test" });
});
it("returns ResponseEnvelope with local source", async () => {
const registry = new OperationRegistry();
registry.register(makeOperation("op1"));
const env = buildEnv({
registry,
context: {} as OperationContext,
});
const result = await env.test.op1({ value: "hello" });
expect(isResponseEnvelope(result)).toBe(true);
expect(result.meta.source).toBe("local");
if (result.meta.source === "local") {
expect(result.meta.operationId).toBe("test.op1");
expect(typeof result.meta.timestamp).toBe("number");
}
expect(result.data).toEqual({ result: "hello" });
});
it("passes pre-built ResponseEnvelope through from handler", async () => {
const registry = new OperationRegistry();
const httpEnv = httpEnvelope({ items: [1, 2, 3] }, {
statusCode: 200,
headers: { "content-type": "application/json" },
contentType: "application/json",
});
registry.register({
name: "httpOp",
namespace: "test",
version: "1.0.0",
type: OperationType.QUERY,
description: "http op",
inputSchema: Type.Object({ value: Type.String() }),
outputSchema: Type.Unknown(),
accessControl: { requiredScopes: [] },
handler: async () => httpEnv,
});
const env = buildEnv({
registry,
context: {} as OperationContext,
});
const result = await env.test.httpOp({ value: "x" });
expect(isResponseEnvelope(result)).toBe(true);
expect(result.meta.source).toBe("http");
expect(result.data).toEqual({ items: [1, 2, 3] });
});
it("filters out SUBSCRIPTION operations", () => {
const registry = new OperationRegistry();
registry.register(makeOperation("query"));
registry.register({
...makeOperation("onEvent"),
type: OperationType.SUBSCRIPTION,
});
const env = buildEnv({
registry,
context: {} as OperationContext,
});
expect(env.test.query).toBeDefined();
expect(env.test.onEvent).toBeUndefined();
});
it("filters by allowedNamespaces", () => {
const registry = new OperationRegistry();
registry.register(makeOperation("op1"));
registry.register({
...makeOperation("op2"),
namespace: "other",
});
const env = buildEnv({
registry,
context: {} as OperationContext,
allowedNamespaces: ["test"],
});
expect(env.test).toBeDefined();
expect(env.other).toBeUndefined();
});
it("always uses execute() and sets trusted: true on nested context", async () => {
let capturedContext: OperationContext | undefined;
const registry = new OperationRegistry();
registry.register({
name: "inner",
namespace: "test",
version: "1.0.0",
type: OperationType.QUERY,
description: "inner op",
inputSchema: Type.Object({ value: Type.String() }),
outputSchema: Type.Object({ result: Type.String() }),
accessControl: { requiredScopes: [] },
handler: async (input: any, ctx: OperationContext) => {
capturedContext = ctx;
return { result: input.value };
},
});
const outerContext: OperationContext = {
requestId: "outer-123",
identity: { id: "user1", scopes: ["read"] },
};
const env = buildEnv({ registry, context: outerContext });
await env.test.inner({ value: "hello" });
expect(capturedContext).toBeDefined();
expect(capturedContext!.trusted).toBe(true);
expect(capturedContext!.requestId).toBe("outer-123");
expect(capturedContext!.identity).toEqual({ id: "user1", scopes: ["read"] });
});
it("skips access control for trusted nested calls", async () => {
const registry = new OperationRegistry();
registry.register({
name: "guarded",
namespace: "test",
version: "1.0.0",
type: OperationType.QUERY,
description: "guarded op",
inputSchema: Type.Object({ value: Type.String() }),
outputSchema: Type.Object({ result: Type.String() }),
accessControl: {
requiredScopes: ["admin"],
},
handler: async (input: any) => ({ result: input.value }),
});
const outerContext: OperationContext = {
requestId: "outer-456",
identity: { id: "user1", scopes: ["read"] },
};
const env = buildEnv({ registry, context: outerContext });
const result = await env.test.guarded({ value: "secret" });
expect(isResponseEnvelope(result)).toBe(true);
expect(result.data).toEqual({ result: "secret" });
});
it("propagates identity through nested calls with trusted flag", async () => {
let capturedContext: OperationContext | undefined;
const registry = new OperationRegistry();
registry.register({
name: "inner",
namespace: "test",
version: "1.0.0",
type: OperationType.QUERY,
description: "inner op",
inputSchema: Type.Object({ value: Type.String() }),
outputSchema: Type.Object({ result: Type.String() }),
accessControl: { requiredScopes: [] },
handler: async (input: any, ctx: OperationContext) => {
capturedContext = ctx;
return { result: input.value };
},
});
const identity: Identity = { id: "user1", scopes: ["read"] };
const outerContext: OperationContext = {
requestId: "parent-req-456",
identity,
};
const env = buildEnv({ registry, context: outerContext });
await env.test.inner({ value: "test" });
expect(capturedContext).toBeDefined();
expect(capturedContext!.identity).toEqual(identity);
expect(capturedContext!.trusted).toBe(true);
});
it("returns empty env when registry has no specs", () => {
const registry = new OperationRegistry();
const env = buildEnv({
registry,
context: {} as OperationContext,
});
expect(Object.keys(env)).toHaveLength(0);
});
it("groups operations by namespace", () => {
const registry = new OperationRegistry();
registry.register(makeOperation("op1"));
registry.register({
...makeOperation("op2"),
namespace: "other",
});
const env = buildEnv({
registry,
context: {} as OperationContext,
});
expect(env.test).toBeDefined();
expect(env.other).toBeDefined();
expect(env.test.op1).toBeDefined();
expect(env.other.op2).toBeDefined();
});
it("applies Value.Cast normalization via execute", async () => {
const registry = new OperationRegistry();
registry.register({
name: "withDefaults",
namespace: "test",
version: "1.0.0",
type: OperationType.QUERY,
description: "op with default fields",
inputSchema: Type.Object({ value: Type.String() }),
outputSchema: Type.Object({ name: Type.String(), count: Type.Number({ default: 0 }) }),
accessControl: { requiredScopes: [] },
handler: async () => ({ name: "test" }),
});
const env = buildEnv({
registry,
context: {} as OperationContext,
});
const result = await env.test.withDefaults({ value: "x" });
expect(isResponseEnvelope(result)).toBe(true);
expect(result.data).toEqual({ name: "test", count: 0 });
});
});