import { config } from "dotenv";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { RaindropMCPService } from "../src/services/raindropmcp.service.js";
// Defensive check: Ensure RAINDROP_ACCESS_TOKEN is present before running tests
config();
if (
!process.env.RAINDROP_ACCESS_TOKEN ||
process.env.RAINDROP_ACCESS_TOKEN.trim() === ""
) {
throw new Error(
"RAINDROP_ACCESS_TOKEN is missing or empty. Please set it in your environment or .env file before running tests.",
);
}
// Test constants for URIs and resource IDs (populate these as needed)
const USER_PROFILE_URI = "mcp://user/profile";
const DIAGNOSTICS_URI = "diagnostics://server";
const TEST_COLLECTION_ID = 55725911; // <-- set your known collection ID here
const TEST_RAINDROP_ID = 1286757883; // <-- set your known raindrop/bookmark ID here
// Add more constants here as needed for other read-only resources
describe("RaindropMCPService", () => {
let mcpService: RaindropMCPService;
beforeEach(async () => {
if (mcpService && typeof mcpService.cleanup === "function") {
await mcpService.cleanup();
}
mcpService = new RaindropMCPService();
});
afterEach(async () => {
if (typeof mcpService?.cleanup === "function") {
await mcpService.cleanup();
}
mcpService = undefined as unknown as RaindropMCPService;
});
// Only readonly API calls and metadata/resource checks are tested below
it("should successfully initialize McpServer", () => {
const server = mcpService.getServer();
expect(server).toBeDefined();
});
it("should read the user_profile resource via a public API", async () => {
// Add a public method to RaindropMCPService for resource reading if not present
if (typeof mcpService.readResource !== "function") {
throw new Error(
"readResource(uri: string) public method not implemented on RaindropMCPService",
);
}
const result = await mcpService.readResource(USER_PROFILE_URI);
expect(result).toBeDefined();
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBeGreaterThan(0);
const first = result[0];
if (!first) throw new Error("No user profile content returned");
expect(first.uri).toBe(USER_PROFILE_URI);
expect(first.text).toContain("profile");
});
it("should list available tools", async () => {
const tools = await mcpService.listTools();
expect(tools).toBeDefined();
expect(Array.isArray(tools)).toBe(true);
expect(tools.length).toBeGreaterThan(0);
// Check that each tool has required properties and types
for (const tool of tools) {
expect(tool).toHaveProperty("id");
expect(typeof tool.id).toBe("string");
expect(tool).toHaveProperty("name");
expect(typeof tool.name).toBe("string");
expect(tool).toHaveProperty("description");
expect(typeof tool.description).toBe("string");
expect(tool).toHaveProperty("inputSchema");
expect(tool).toHaveProperty("outputSchema");
}
// Check for a known tool
const diagnosticsTool = tools.find((t: any) => t.id === "diagnostics");
expect(diagnosticsTool).toBeDefined();
expect(diagnosticsTool?.name.toLowerCase()).toContain("diagnostic");
});
it("should read the diagnostics resource via a public API", async () => {
if (typeof mcpService.readResource !== "function") {
throw new Error(
"readResource(uri: string) public method not implemented on RaindropMCPService",
);
}
const result = await mcpService.readResource(DIAGNOSTICS_URI);
expect(result).toBeDefined();
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBeGreaterThan(0);
const first = result[0];
if (!first) throw new Error("No diagnostics content returned");
expect(first.uri).toBe(DIAGNOSTICS_URI);
expect(first.text).toContain("diagnostics");
});
it("should return the MCP manifest with correct structure", async () => {
const manifest = await mcpService.getManifest();
expect(manifest).toBeDefined();
expect(manifest).toHaveProperty("name");
expect(manifest).toHaveProperty("version");
expect(manifest).toHaveProperty("description");
expect(manifest).toHaveProperty("capabilities");
expect(manifest).toHaveProperty("tools");
expect(Array.isArray((manifest as any).tools)).toBe(true);
});
it("should list all registered resources with metadata", () => {
const resources = mcpService.listResources();
expect(resources).toBeDefined();
expect(Array.isArray(resources)).toBe(true);
expect(resources.length).toBeGreaterThan(0);
for (const resource of resources) {
expect(resource).toHaveProperty("id");
expect(resource).toHaveProperty("uri");
}
});
it("should return true for healthCheck", async () => {
const healthy = await mcpService.healthCheck();
expect(healthy).toBe(true);
});
it("should return correct server info", () => {
const info = mcpService.getInfo();
expect(info).toBeDefined();
expect(info).toHaveProperty("name");
expect(info).toHaveProperty("version");
expect(info).toHaveProperty("description");
expect(typeof info.name).toBe("string");
expect(typeof info.version).toBe("string");
expect(typeof info.description).toBe("string");
});
it("should read a specific collection resource", async () => {
if (typeof mcpService.readResource !== "function") {
throw new Error(
"readResource(uri: string) public method not implemented on RaindropMCPService",
);
}
const collectionUri = `mcp://collection/${TEST_COLLECTION_ID}`;
const result = await mcpService.readResource(collectionUri);
expect(result).toBeDefined();
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBeGreaterThan(0);
const first = result[0];
if (!first) throw new Error("No collection content returned");
expect(first.uri).toBe(collectionUri);
expect(first.text).toContain("collection");
});
it("should read a specific raindrop (bookmark) resource", async () => {
if (typeof mcpService.readResource !== "function") {
throw new Error(
"readResource(uri: string) public method not implemented on RaindropMCPService",
);
}
const raindropUri = `mcp://raindrop/${TEST_RAINDROP_ID}`;
const result = await mcpService.readResource(raindropUri);
expect(result).toBeDefined();
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBeGreaterThan(0);
const first = result[0];
if (!first) throw new Error("No raindrop content returned");
expect(first.uri).toBe(raindropUri);
expect(first.text).toContain("raindrop");
});
it("should emit valid diagnostics data", async () => {
if (typeof mcpService.callTool !== "function") {
throw new Error(
"callTool(name: string, args?: any) public method not implemented on RaindropMCPService",
);
}
const result = await mcpService.callTool("diagnostics", {});
expect(result).toBeDefined();
expect(result.content).toBeDefined();
expect(Array.isArray(result.content)).toBe(true);
const diag = result.content[0];
expect(diag.type).toBe("resource_link");
expect(diag.uri).toBe("diagnostics://server");
expect(diag.name).toContain("Diagnostics");
expect(diag.description).toContain("Version");
expect(diag.mimeType).toBe("application/json");
expect(diag._meta).toBeDefined();
// Check for key diagnostic fields
expect(diag._meta.version).toBeDefined();
expect(typeof diag._meta.version).toBe("string");
expect(diag._meta.nodeVersion || diag._meta.bunVersion).toBeDefined();
expect(diag._meta.os).toBeDefined();
expect(typeof diag._meta.uptime).toBe("number");
expect(Array.isArray(diag._meta.enabledTools)).toBe(true);
expect(diag._meta.memory).toBeDefined();
expect(typeof diag._meta.memory).toBe("object");
expect(diag._meta.env).toBeDefined();
expect(typeof diag._meta.env).toBe("object");
});
// Additional test to output actual return values for inspection
it("should output actual API data for inspection", async () => {
console.log("=== Testing actual API responses ===");
// Test user profile
try {
const userResult = await mcpService.readResource(USER_PROFILE_URI);
console.log("User Profile Result:", JSON.stringify(userResult, null, 2));
} catch (error) {
console.log(
"User Profile Error:",
error instanceof Error ? error.message : String(error),
);
}
// Test diagnostics
try {
const diagResult = await mcpService.readResource(DIAGNOSTICS_URI);
console.log("Diagnostics Result:", JSON.stringify(diagResult, null, 2));
} catch (error) {
console.log(
"Diagnostics Error:",
error instanceof Error ? error.message : String(error),
);
}
// Test collection
try {
const collectionUri = `mcp://collection/${TEST_COLLECTION_ID}`;
const collectionResult = await mcpService.readResource(collectionUri);
console.log(
"Collection Result:",
JSON.stringify(collectionResult, null, 2),
);
} catch (error) {
console.log(
"Collection Error:",
error instanceof Error ? error.message : String(error),
);
}
// Test raindrop
try {
const raindropUri = `mcp://raindrop/${TEST_RAINDROP_ID}`;
const raindropResult = await mcpService.readResource(raindropUri);
console.log("Raindrop Result:", JSON.stringify(raindropResult, null, 2));
} catch (error) {
console.log(
"Raindrop Error:",
error instanceof Error ? error.message : String(error),
);
}
// Test available tools
try {
const tools = await mcpService.listTools();
console.log(
"Available Tools:",
tools.map((t) => ({
id: t.id,
name: t.name,
description: t.description,
})),
);
} catch (error) {
console.log(
"Tools Error:",
error instanceof Error ? error.message : String(error),
);
}
// Test available resources
try {
const resources = mcpService.listResources();
console.log("Available Resources:", resources);
} catch (error) {
console.log(
"Resources Error:",
error instanceof Error ? error.message : String(error),
);
}
console.log("=== End of API inspection ===");
// This test always passes - it's just for inspection
expect(true).toBe(true);
});
});