UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

391 lines (300 loc) 10.8 kB
import { Alepha } from "alepha"; import { describe, expect, test } from "vitest"; import { $resource, AlephaMcp, type McpContext, McpServerProvider, } from "../index.ts"; // --------------------------------------------------------------------------------------------------------------------- describe("$resource primitive", () => { test("should register resource with McpServerProvider", async () => { const alepha = Alepha.create(); class Resources { readme = $resource({ uri: "file:///readme", description: "Project README", handler: async () => ({ text: "# README" }), }); } alepha.with(AlephaMcp).with(Resources); await alepha.start(); const provider = alepha.inject(McpServerProvider); const resource = provider.getResource("file:///readme"); expect(resource).toBeDefined(); expect(resource?.uri).toBe("file:///readme"); expect(resource?.description).toBe("Project README"); }); test("should use property key as name when not provided", async () => { const alepha = Alepha.create(); class Resources { myResource = $resource({ uri: "test://resource", handler: async () => ({ text: "content" }), }); } alepha.with(AlephaMcp).with(Resources); await alepha.start(); const provider = alepha.inject(McpServerProvider); const resource = provider.getResource("test://resource"); expect(resource?.name).toBe("myResource"); }); test("should use custom name when provided", async () => { const alepha = Alepha.create(); class Resources { myResource = $resource({ uri: "test://resource", name: "Custom Resource Name", handler: async () => ({ text: "content" }), }); } alepha.with(AlephaMcp).with(Resources); await alepha.start(); const provider = alepha.inject(McpServerProvider); const resource = provider.getResource("test://resource"); expect(resource?.name).toBe("Custom Resource Name"); }); test("should return text content", async () => { const alepha = Alepha.create(); class Resources { text = $resource({ uri: "text://content", handler: async () => ({ text: "Hello, World!" }), }); } alepha.with(AlephaMcp).with(Resources); await alepha.start(); const provider = alepha.inject(McpServerProvider); const resource = provider.getResource("text://content"); const content = await resource?.read(); expect(content?.text).toBe("Hello, World!"); }); test("should return binary content", async () => { const alepha = Alepha.create(); const binaryData = new Uint8Array([1, 2, 3, 4, 5]); class Resources { binary = $resource({ uri: "binary://data", mimeType: "application/octet-stream", handler: async () => ({ blob: binaryData }), }); } alepha.with(AlephaMcp).with(Resources); await alepha.start(); const provider = alepha.inject(McpServerProvider); const resource = provider.getResource("binary://data"); const content = await resource?.read(); expect(content?.blob).toEqual(binaryData); }); test("should use default mimeType when not provided", async () => { const alepha = Alepha.create(); class Resources { plain = $resource({ uri: "plain://text", handler: async () => ({ text: "plain text" }), }); } alepha.with(AlephaMcp).with(Resources); await alepha.start(); const provider = alepha.inject(McpServerProvider); const resource = provider.getResource("plain://text"); expect(resource?.mimeType).toBe("text/plain"); }); test("should use custom mimeType", async () => { const alepha = Alepha.create(); class Resources { json = $resource({ uri: "json://data", mimeType: "application/json", handler: async () => ({ text: '{"key": "value"}' }), }); } alepha.with(AlephaMcp).with(Resources); await alepha.start(); const provider = alepha.inject(McpServerProvider); const resource = provider.getResource("json://data"); expect(resource?.mimeType).toBe("application/json"); }); test("should generate correct descriptor", async () => { const alepha = Alepha.create(); class Resources { config = $resource({ uri: "config://app", name: "Application Config", description: "Application configuration file", mimeType: "application/json", handler: async () => ({ text: "{}" }), }); } alepha.with(AlephaMcp).with(Resources); await alepha.start(); const provider = alepha.inject(McpServerProvider); const resource = provider.getResource("config://app"); const descriptor = resource?.toDescriptor(); expect(descriptor).toEqual({ uri: "config://app", name: "Application Config", description: "Application configuration file", mimeType: "application/json", }); }); test("should handle async handlers", async () => { const alepha = Alepha.create(); class Resources { async = $resource({ uri: "async://resource", handler: async () => { await new Promise((resolve) => setTimeout(resolve, 10)); return { text: "async content" }; }, }); } alepha.with(AlephaMcp).with(Resources); await alepha.start(); const provider = alepha.inject(McpServerProvider); const resource = provider.getResource("async://resource"); const content = await resource?.read(); expect(content?.text).toBe("async content"); }); test("should propagate handler errors", async () => { const alepha = Alepha.create(); class Resources { failing = $resource({ uri: "failing://resource", handler: async () => { throw new Error("Resource read failed"); }, }); } alepha.with(AlephaMcp).with(Resources); await alepha.start(); const provider = alepha.inject(McpServerProvider); const resource = provider.getResource("failing://resource"); await expect(resource?.read()).rejects.toThrow("Resource read failed"); }); test("should handle dynamic content", async () => { const alepha = Alepha.create(); let counter = 0; class Resources { dynamic = $resource({ uri: "dynamic://counter", handler: async () => { counter++; return { text: `Count: ${counter}` }; }, }); } alepha.with(AlephaMcp).with(Resources); await alepha.start(); const provider = alepha.inject(McpServerProvider); const resource = provider.getResource("dynamic://counter"); const content1 = await resource?.read(); expect(content1?.text).toBe("Count: 1"); const content2 = await resource?.read(); expect(content2?.text).toBe("Count: 2"); }); // ----------------------------------------------------------------------------------------------------------------- // Context tests // ----------------------------------------------------------------------------------------------------------------- test("should receive context in handler", async () => { const alepha = Alepha.create(); let receivedContext: McpContext | undefined; class Resources { contextResource = $resource({ uri: "context://resource", handler: async ({ context }) => { receivedContext = context; return { text: "content" }; }, }); } alepha.with(AlephaMcp).with(Resources); await alepha.start(); const provider = alepha.inject(McpServerProvider); const resource = provider.getResource("context://resource"); const testContext: McpContext = { headers: { authorization: "Bearer test-token" }, }; await resource?.read(testContext); expect(receivedContext).toBeDefined(); expect(receivedContext?.headers?.authorization).toBe("Bearer test-token"); }); test("should receive context with custom data", async () => { const alepha = Alepha.create(); interface UserContext { userId: string; projectId: number; } let receivedData: UserContext | undefined; class Resources { userResource = $resource({ uri: "user://data", handler: async ({ context }) => { receivedData = context?.data as UserContext; return { text: JSON.stringify({ userId: receivedData?.userId, projectId: receivedData?.projectId, }), }; }, }); } alepha.with(AlephaMcp).with(Resources); await alepha.start(); const provider = alepha.inject(McpServerProvider); const resource = provider.getResource("user://data"); const testContext: McpContext<UserContext> = { headers: {}, data: { userId: "user-456", projectId: 123 }, }; const content = await resource?.read(testContext); expect(content?.text).toBe('{"userId":"user-456","projectId":123}'); expect(receivedData).toEqual({ userId: "user-456", projectId: 123 }); }); test("should work without context", async () => { const alepha = Alepha.create(); let contextWasUndefined = false; class Resources { noContext = $resource({ uri: "no://context", handler: async ({ context }) => { contextWasUndefined = context === undefined; return { text: "done" }; }, }); } alepha.with(AlephaMcp).with(Resources); await alepha.start(); const provider = alepha.inject(McpServerProvider); const resource = provider.getResource("no://context"); await resource?.read(); expect(contextWasUndefined).toBe(true); }); test("should use context for access control", async () => { const alepha = Alepha.create(); class Resources { protected = $resource({ uri: "protected://resource", handler: async ({ context }) => { const authHeader = context?.headers?.authorization; if (!authHeader?.toString().startsWith("Bearer ")) { throw new Error("Unauthorized"); } return { text: "Secret content" }; }, }); } alepha.with(AlephaMcp).with(Resources); await alepha.start(); const provider = alepha.inject(McpServerProvider); const resource = provider.getResource("protected://resource"); // Without auth - should fail await expect(resource?.read()).rejects.toThrow("Unauthorized"); // With auth - should succeed const content = await resource?.read({ headers: { authorization: "Bearer valid-token" }, }); expect(content?.text).toBe("Secret content"); }); });