import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import axios from "axios";
import { SessionManager, registerAuthTools } from "../../src/tools/auth-tools";
// Mock axios module
jest.mock("axios");
const mockedAxios = axios as jest.Mocked<typeof axios>;
describe("SessionManager", () => {
let sessionManager: SessionManager;
beforeEach(() => {
sessionManager = SessionManager.getInstance();
// Clear any existing sessions
sessionManager.clearAllSessions();
});
afterEach(() => {
sessionManager.clearAllSessions();
});
describe("singleton pattern", () => {
it("should return the same instance", () => {
const instance1 = SessionManager.getInstance();
const instance2 = SessionManager.getInstance();
expect(instance1).toBe(instance2);
});
});
describe("session management", () => {
it("should store and retrieve API key", () => {
const sessionId = "test-session-123";
const apiKey = "test-api-key-456";
sessionManager.setApiKey(sessionId, apiKey);
const retrievedKey = sessionManager.getApiKey(sessionId);
expect(retrievedKey).toBe(apiKey);
});
it("should return undefined for non-existent session", () => {
const sessionId = "non-existent-session";
const retrievedKey = sessionManager.getApiKey(sessionId);
expect(retrievedKey).toBeNull();
});
it("should update existing API key", () => {
const sessionId = "test-session-123";
const originalKey = "original-api-key";
const newKey = "new-api-key";
sessionManager.setApiKey(sessionId, originalKey);
expect(sessionManager.getApiKey(sessionId)).toBe(originalKey);
sessionManager.setApiKey(sessionId, newKey);
expect(sessionManager.getApiKey(sessionId)).toBe(newKey);
});
it("should handle multiple sessions", () => {
const session1 = "session-1";
const session2 = "session-2";
const key1 = "key-1";
const key2 = "key-2";
sessionManager.setApiKey(session1, key1);
sessionManager.setApiKey(session2, key2);
expect(sessionManager.getApiKey(session1)).toBe(key1);
expect(sessionManager.getApiKey(session2)).toBe(key2);
});
it("should clear specific session", () => {
const sessionId = "test-session-123";
const apiKey = "test-api-key-456";
sessionManager.setApiKey(sessionId, apiKey);
expect(sessionManager.getApiKey(sessionId)).toBe(apiKey);
sessionManager.clearSession(sessionId);
expect(sessionManager.getApiKey(sessionId)).toBeNull();
});
it("should clear all sessions", () => {
const session1 = "session-1";
const session2 = "session-2";
const key1 = "key-1";
const key2 = "key-2";
sessionManager.setApiKey(session1, key1);
sessionManager.setApiKey(session2, key2);
expect(sessionManager.getApiKey(session1)).toBe(key1);
expect(sessionManager.getApiKey(session2)).toBe(key2);
sessionManager.clearAllSessions();
expect(sessionManager.getApiKey(session1)).toBeNull();
expect(sessionManager.getApiKey(session2)).toBeNull();
});
});
describe("session validation", () => {
it("should validate session exists", () => {
const sessionId = "test-session-123";
const apiKey = "test-api-key-456";
expect(sessionManager.getApiKey(sessionId)).toBeNull();
sessionManager.setApiKey(sessionId, apiKey);
expect(sessionManager.getApiKey(sessionId)).toBe(apiKey);
sessionManager.clearSession(sessionId);
expect(sessionManager.getApiKey(sessionId)).toBeNull();
});
it("should validate API key format", () => {
const sessionId = "test-session-123";
const validKey = "valid-api-key-123";
const invalidKey = "";
sessionManager.setApiKey(sessionId, validKey);
expect(sessionManager.getApiKey(sessionId)).toBe(validKey);
sessionManager.setApiKey(sessionId, invalidKey);
expect(sessionManager.getApiKey(sessionId)).toBeNull();
});
});
describe("session data types", () => {
it("should handle different session ID formats", () => {
const sessions = [
"simple-session",
"session-with-dashes",
"session_with_underscores",
"sessionWithCamelCase",
"SESSION-WITH-UPPERCASE",
"session123",
"session-with-numbers-123",
];
sessions.forEach((sessionId, index) => {
const apiKey = `key-${index}`;
sessionManager.setApiKey(sessionId, apiKey);
expect(sessionManager.getApiKey(sessionId)).toBe(apiKey);
});
});
it("should handle different API key formats", () => {
const sessionId = "test-session";
const apiKeys = [
"simple-key",
"key-with-dashes",
"key_with_underscores",
"keyWithCamelCase",
"KEY-WITH-UPPERCASE",
"key123",
"key-with-numbers-123",
"very-long-api-key-with-many-characters-and-numbers-123456789",
];
apiKeys.forEach((apiKey, index) => {
const testSessionId = `${sessionId}-${index}`;
sessionManager.setApiKey(testSessionId, apiKey);
expect(sessionManager.getApiKey(testSessionId)).toBe(apiKey);
});
});
});
describe("session persistence", () => {
it("should maintain sessions across multiple operations", () => {
const sessionId = "persistent-session";
const apiKey = "persistent-key";
sessionManager.setApiKey(sessionId, apiKey);
// Perform multiple operations
for (let i = 0; i < 100; i++) {
const tempSessionId = `temp-session-${i}`;
const tempKey = `temp-key-${i}`;
sessionManager.setApiKey(tempSessionId, tempKey);
expect(sessionManager.getApiKey(tempSessionId)).toBe(tempKey);
sessionManager.clearSession(tempSessionId);
}
// Original session should still exist
expect(sessionManager.getApiKey(sessionId)).toBe(apiKey);
});
it("should handle concurrent session operations", () => {
const sessions = Array.from({ length: 100 }, (_, i) => `session-${i}`);
const keys = Array.from({ length: 100 }, (_, i) => `key-${i}`);
// Set all sessions
sessions.forEach((sessionId, index) => {
sessionManager.setApiKey(sessionId, keys[index]);
});
// Verify all sessions
sessions.forEach((sessionId, index) => {
expect(sessionManager.getApiKey(sessionId)).toBe(keys[index]);
});
// Clear all sessions
sessionManager.clearAllSessions();
// Verify all sessions are cleared
sessions.forEach((sessionId) => {
expect(sessionManager.getApiKey(sessionId)).toBeNull();
});
});
});
describe("error handling", () => {
it("should handle null session ID", () => {
expect(() => {
sessionManager.setApiKey(null as any, "test-key");
}).not.toThrow();
expect(sessionManager.getApiKey(null as any)).toBeNull();
});
it("should handle undefined session ID", () => {
expect(() => {
sessionManager.setApiKey(undefined as any, "test-key");
}).not.toThrow();
expect(sessionManager.getApiKey(undefined as any)).toBeNull();
});
it("should handle null API key", () => {
const sessionId = "test-session";
expect(() => {
sessionManager.setApiKey(sessionId, null as any);
}).not.toThrow();
expect(sessionManager.getApiKey(sessionId)).toBeNull();
});
it("should handle undefined API key", () => {
const sessionId = "test-session";
expect(() => {
sessionManager.setApiKey(sessionId, undefined as any);
}).not.toThrow();
expect(sessionManager.getApiKey(sessionId)).toBeNull();
});
});
describe("memory management", () => {
it("should not leak memory with many sessions", () => {
const initialSessions = 1000;
const additionalSessions = 1000;
// Create initial sessions
for (let i = 0; i < initialSessions; i++) {
sessionManager.setApiKey(`session-${i}`, `key-${i}`);
}
// Verify initial sessions exist
for (let i = 0; i < initialSessions; i++) {
expect(sessionManager.getApiKey(`session-${i}`)).toBe(`key-${i}`);
}
// Create additional sessions
for (let i = initialSessions; i < initialSessions + additionalSessions; i++) {
sessionManager.setApiKey(`session-${i}`, `key-${i}`);
}
// Verify all sessions exist
for (let i = 0; i < initialSessions + additionalSessions; i++) {
expect(sessionManager.getApiKey(`session-${i}`)).toBe(`key-${i}`);
}
// Clear all sessions
sessionManager.clearAllSessions();
// Verify all sessions are cleared
for (let i = 0; i < initialSessions + additionalSessions; i++) {
expect(sessionManager.getApiKey(`session-${i}`)).toBeNull();
}
});
});
describe("current session management", () => {
it("should get current API key", () => {
const sessionId = "test-session-123";
const apiKey = "test-api-key-456";
// Initially no current session
expect(sessionManager.getCurrentApiKey()).toBeNull();
// Set API key and verify current session
sessionManager.setApiKey(sessionId, apiKey);
expect(sessionManager.getCurrentApiKey()).toBe(apiKey);
});
it("should get current session ID", () => {
const sessionId = "test-session-123";
const apiKey = "test-api-key-456";
// Initially no current session
expect(sessionManager.getCurrentSession()).toBeNull();
// Set API key and verify current session
sessionManager.setApiKey(sessionId, apiKey);
expect(sessionManager.getCurrentSession()).toBe(sessionId);
});
it("should handle current session when no session is set", () => {
expect(sessionManager.getCurrentApiKey()).toBeNull();
expect(sessionManager.getCurrentSession()).toBeNull();
});
it("should clear current session when clearing specific session", () => {
const sessionId = "test-session-123";
const apiKey = "test-api-key-456";
sessionManager.setApiKey(sessionId, apiKey);
expect(sessionManager.getCurrentSession()).toBe(sessionId);
sessionManager.clearSession(sessionId);
expect(sessionManager.getCurrentSession()).toBeNull();
});
});
describe("list sessions", () => {
it("should list all sessions", () => {
const session1 = "session-1";
const session2 = "session-2";
const key1 = "key-1";
const key2 = "key-2";
// Initially empty
expect(sessionManager.listSessions()).toEqual([]);
// Add sessions
sessionManager.setApiKey(session1, key1);
sessionManager.setApiKey(session2, key2);
const sessions = sessionManager.listSessions();
expect(sessions).toContain(session1);
expect(sessions).toContain(session2);
expect(sessions).toHaveLength(2);
});
it("should return empty array when no sessions", () => {
expect(sessionManager.listSessions()).toEqual([]);
});
});
});
describe("Auth Tools Integration", () => {
let mockServer: any;
let sessionManager: SessionManager;
beforeEach(() => {
sessionManager = SessionManager.getInstance();
sessionManager.clearAllSessions();
// Mock MCP server
mockServer = {
tool: jest.fn(),
};
});
afterEach(() => {
sessionManager.clearAllSessions();
});
describe("registerAuthTools", () => {
it("should register all auth tools", () => {
registerAuthTools(mockServer as McpServer);
// Verify that all tools are registered
expect(mockServer.tool).toHaveBeenCalledWith(
"auth_login",
expect.any(Object),
expect.any(Object),
expect.any(Function)
);
expect(mockServer.tool).toHaveBeenCalledWith(
"auth_sign_in",
expect.any(Object),
expect.any(Object),
expect.any(Function)
);
expect(mockServer.tool).toHaveBeenCalledWith(
"auth_check_password",
expect.any(Object),
expect.any(Object),
expect.any(Function)
);
expect(mockServer.tool).toHaveBeenCalledWith(
"auth_change_password",
expect.any(Object),
expect.any(Object),
expect.any(Function)
);
expect(mockServer.tool).toHaveBeenCalledWith(
"auth_session_info",
expect.any(Object),
expect.any(Object),
expect.any(Function)
);
expect(mockServer.tool).toHaveBeenCalledWith(
"auth_logout",
expect.any(Object),
expect.any(Object),
expect.any(Function)
);
expect(mockServer.tool).toHaveBeenCalledWith(
"auth_test_connection",
expect.any(Object),
expect.any(Object),
expect.any(Function)
);
// Should register 7 tools total
expect(mockServer.tool).toHaveBeenCalledTimes(7);
});
});
describe("auth_login tool", () => {
let authLoginHandler: Function;
beforeEach(() => {
registerAuthTools(mockServer as McpServer);
const loginCall = mockServer.tool.mock.calls.find((call) => call[0] === "auth_login");
authLoginHandler = loginCall[3];
});
it("should handle auth_login with session_id", async () => {
const args = {
api_key: "test-api-key-123",
session_id: "custom-session-456",
};
const result = await authLoginHandler(args);
expect(result.content[0].type).toBe("text");
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
expect(response.session_id).toBe("custom-session-456");
expect(sessionManager.getApiKey("custom-session-456")).toBe("test-api-key-123");
});
it("should handle auth_login without session_id", async () => {
const args = {
api_key: "test-api-key-123",
};
const result = await authLoginHandler(args);
expect(result.content[0].type).toBe("text");
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
expect(response.session_id).toMatch(/^session_\d+_[a-z0-9]+$/);
expect(sessionManager.getApiKey(response.session_id)).toBe("test-api-key-123");
});
});
describe("auth_session_info tool", () => {
let authSessionInfoHandler: Function;
beforeEach(() => {
registerAuthTools(mockServer as McpServer);
const sessionInfoCall = mockServer.tool.mock.calls.find((call) => call[0] === "auth_session_info");
authSessionInfoHandler = sessionInfoCall[3];
});
it("should return session info with current session", async () => {
sessionManager.setApiKey("test-session", "test-key");
const result = await authSessionInfoHandler({});
expect(result.content[0].type).toBe("text");
const response = JSON.parse(result.content[0].text);
expect(response.current_session).toBe("test-session");
expect(response.has_api_key).toBe(true);
expect(response.total_sessions).toBe(1);
expect(response.all_sessions).toContain("test-session");
});
it("should return session info without current session", async () => {
const result = await authSessionInfoHandler({});
expect(result.content[0].type).toBe("text");
const response = JSON.parse(result.content[0].text);
expect(response.current_session).toBeNull();
expect(response.has_api_key).toBe(false);
expect(response.total_sessions).toBe(0);
expect(response.all_sessions).toEqual([]);
});
it("should return session info for specific session", async () => {
sessionManager.setApiKey("session-1", "key-1");
sessionManager.setApiKey("session-2", "key-2");
const result = await authSessionInfoHandler({ session_id: "session-1" });
expect(result.content[0].type).toBe("text");
const response = JSON.parse(result.content[0].text);
expect(response.requested_session).toBe("session-1");
expect(response.has_api_key).toBe(true);
});
});
describe("auth_logout tool", () => {
let authLogoutHandler: Function;
beforeEach(() => {
registerAuthTools(mockServer as McpServer);
const logoutCall = mockServer.tool.mock.calls.find((call) => call[0] === "auth_logout");
authLogoutHandler = logoutCall[3];
});
it("should logout current session", async () => {
sessionManager.setApiKey("test-session", "test-key");
sessionManager.setApiKey("other-session", "other-key");
const result = await authLogoutHandler({});
expect(result.content[0].type).toBe("text");
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
expect(response.message).toContain("other-session"); // Current session is the last one set
expect(sessionManager.getApiKey("test-session")).toBe("test-key");
expect(sessionManager.getApiKey("other-session")).toBeNull();
});
it("should logout specific session", async () => {
sessionManager.setApiKey("session-1", "key-1");
sessionManager.setApiKey("session-2", "key-2");
const result = await authLogoutHandler({ session_id: "session-1" });
expect(result.content[0].type).toBe("text");
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
expect(response.message).toContain("session-1");
expect(sessionManager.getApiKey("session-1")).toBeNull();
expect(sessionManager.getApiKey("session-2")).toBe("key-2");
});
});
describe("auth_check_password tool", () => {
let authCheckPasswordHandler: Function;
beforeEach(() => {
registerAuthTools(mockServer as McpServer);
const checkPasswordCall = mockServer.tool.mock.calls.find((call) => call[0] === "auth_check_password");
authCheckPasswordHandler = checkPasswordCall[3];
});
it("should handle auth_check_password with API key", async () => {
const args = {
password: "test-password",
api_key: "test-api-key",
};
// Mock axios to avoid actual HTTP calls
mockedAxios.mockResolvedValueOnce({
data: { success: true, message: "Password verified" },
} as any);
const result = await authCheckPasswordHandler(args);
expect(result.content[0].type).toBe("text");
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
expect(response.message).toBe("Password check completed");
});
it("should handle auth_check_password with session", async () => {
sessionManager.setApiKey("test-session", "test-api-key");
const args = {
password: "test-password",
session_id: "test-session",
};
// Mock axios to avoid actual HTTP calls
mockedAxios.mockResolvedValueOnce({
data: { success: true, message: "Password verified" },
} as any);
const result = await authCheckPasswordHandler(args);
expect(result.content[0].type).toBe("text");
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
});
});
describe("auth_change_password tool", () => {
let authChangePasswordHandler: Function;
beforeEach(() => {
registerAuthTools(mockServer as McpServer);
const changePasswordCall = mockServer.tool.mock.calls.find((call) => call[0] === "auth_change_password");
authChangePasswordHandler = changePasswordCall[3];
});
it("should handle auth_change_password", async () => {
const args = {
password: "old-password",
new_password: "new-password",
api_key: "test-api-key",
};
// Mock axios to avoid actual HTTP calls
mockedAxios.mockResolvedValueOnce({
data: { success: true, message: "Password changed" },
} as any);
const result = await authChangePasswordHandler(args);
expect(result.content[0].type).toBe("text");
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
expect(response.message).toBe("Password changed successfully");
});
});
describe("auth_test_connection tool", () => {
let authTestConnectionHandler: Function;
beforeEach(() => {
registerAuthTools(mockServer as McpServer);
const testConnectionCall = mockServer.tool.mock.calls.find((call) => call[0] === "auth_test_connection");
authTestConnectionHandler = testConnectionCall[3];
});
it("should handle successful connection test", async () => {
sessionManager.setApiKey("test-session", "test-api-key");
// Mock axios to avoid actual HTTP calls
mockedAxios.mockResolvedValueOnce({
data: { success: true, message: "Connection successful" },
} as any);
const result = await authTestConnectionHandler({ session_id: "test-session" });
expect(result.content[0].type).toBe("text");
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
expect(response.message).toBe("Connection test successful");
});
it("should handle failed connection test", async () => {
// Mock axios to throw error
mockedAxios.mockRejectedValueOnce(new Error("Connection failed"));
const result = await authTestConnectionHandler({});
expect(result.content[0].type).toBe("text");
const response = JSON.parse(result.content[0].text);
expect(response.success).toBe(false);
expect(response.message).toBe("Connection test failed");
});
});
});