alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
391 lines (300 loc) • 10.8 kB
text/typescript
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");
});
});