Skip to main content
Glama
sessionManager.test.ts16 kB
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import type { ILogger } from "../../../src/core/logger.js"; import type { SessionConfig } from "../../../src/transport/config.js"; import { SessionManager } from "../../../src/transport/sessionManager.js"; describe("SessionManager", () => { let mockLogger: ILogger; let sessionManager: SessionManager; beforeEach(() => { // Create mock logger mockLogger = { sendLog: vi.fn(), }; }); afterEach(() => { // Clean up timers if (sessionManager) { sessionManager.stopTTLCleanup(); } vi.useRealTimers(); }); describe("session creation", () => { test("should create session with UUID format", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); const sessionId = sessionManager.create(); // UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx expect(sessionId).toMatch( /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, ); }); test("should create session without metadata", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); const sessionId = sessionManager.create(); expect(sessionId).toBeDefined(); expect(mockLogger.sendLog).toHaveBeenCalledWith( expect.objectContaining({ level: "info", logger: "SessionManager", }), ); }); test("should create session with metadata", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); const metadata = { clientVersion: "1.0", userAgent: "test" }; const sessionId = sessionManager.create(metadata); expect(sessionId).toBeDefined(); const sessions = sessionManager.list(); expect(sessions).toHaveLength(1); expect(sessions[0]?.id).toBe(sessionId); expect(sessions[0]?.metadata).toEqual(metadata); }); test("should create unique session IDs", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); const id1 = sessionManager.create(); const id2 = sessionManager.create(); const id3 = sessionManager.create(); expect(id1).not.toBe(id2); expect(id2).not.toBe(id3); expect(id1).not.toBe(id3); }); test("should log session creation", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); const sessionId = sessionManager.create(); expect(mockLogger.sendLog).toHaveBeenCalledWith({ data: `Session created: ${sessionId}`, level: "info", logger: "SessionManager", }); }); }); describe("session listing", () => { test("should list created session", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); const sessionId = sessionManager.create(); const sessions = sessionManager.list(); expect(sessions).toHaveLength(1); expect(sessions[0]?.id).toBe(sessionId); expect(sessions[0]?.createdAt).toBeTypeOf("number"); expect(sessions[0]?.lastAccessedAt).toBeTypeOf("number"); }); test("should not list non-existent session", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); const sessions = sessionManager.list(); expect(sessions).toHaveLength(0); }); test("should list session with metadata", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); const metadata = { clientVersion: "1.0", userAgent: "test" }; const sessionId = sessionManager.create(metadata); const sessions = sessionManager.list(); expect(sessions).toHaveLength(1); expect(sessions[0]?.id).toBe(sessionId); expect(sessions[0]?.metadata).toEqual(metadata); }); }); describe("TTL-based cleanup integration", () => { test("should keep session before TTL expires", () => { vi.useFakeTimers(); const config: SessionConfig = { enabled: true, ttl: 1000, // 1 second }; sessionManager = new SessionManager(config, mockLogger); const sessionId = sessionManager.create(); expect(sessionManager.list()).toHaveLength(1); // Advance time but not beyond TTL vi.advanceTimersByTime(999); // Session should still exist (no cleanup yet) expect(sessionManager.list()).toHaveLength(1); expect(sessionManager.list()[0]?.id).toBe(sessionId); }); test("should clean up session after TTL with automatic cleanup", () => { vi.useFakeTimers(); const config: SessionConfig = { cleanupInterval: 100, enabled: true, ttl: 1000, }; sessionManager = new SessionManager(config, mockLogger); sessionManager.create(); sessionManager.startTTLCleanup(); expect(sessionManager.list()).toHaveLength(1); // Advance time beyond TTL vi.advanceTimersByTime(1001); // Trigger cleanup interval (configured to 100ms) vi.advanceTimersByTime(100); // Session should be cleaned up expect(sessionManager.list()).toHaveLength(0); }); test("should refresh lastAccessedAt on touch and prevent premature TTL expiry", () => { vi.useFakeTimers(); const config: SessionConfig = { cleanupInterval: 100, enabled: true, ttl: 1000, }; sessionManager = new SessionManager(config, mockLogger); const sessionId = sessionManager.create(); sessionManager.startTTLCleanup(); // Advance close to TTL but refresh access vi.advanceTimersByTime(900); sessionManager.touch(sessionId); // Advance past original TTL and run cleanup vi.advanceTimersByTime(200); vi.advanceTimersByTime(100); // Session should remain because it was accessed expect(sessionManager.list()).toHaveLength(1); expect(sessionManager.list()[0]?.id).toBe(sessionId); }); test("should invoke expiration handler when session expires", () => { vi.useFakeTimers(); const config: SessionConfig = { cleanupInterval: 100, enabled: true, ttl: 1000, }; const onExpired = vi.fn(); sessionManager = new SessionManager(config, mockLogger); sessionManager.setExpirationHandler(onExpired); const sessionId = sessionManager.create(); sessionManager.startTTLCleanup(); // Expire session vi.advanceTimersByTime(1100); vi.advanceTimersByTime(100); expect(onExpired).toHaveBeenCalledWith(sessionId); expect(sessionManager.list()).toHaveLength(0); }); test("should work without TTL configured", () => { const config: SessionConfig = { enabled: true, // No TTL }; sessionManager = new SessionManager(config, mockLogger); const sessionId = sessionManager.create(); // Session should exist regardless of time expect(sessionManager.list()).toHaveLength(1); expect(sessionManager.list()[0]?.id).toBe(sessionId); }); }); describe("session cleanup", () => { test("should clean up existing session", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); const sessionId = sessionManager.create(); const result = sessionManager.cleanup(sessionId); expect(result.success).toBe(true); expect(mockLogger.sendLog).toHaveBeenCalledWith( expect.objectContaining({ data: expect.stringContaining(`Session cleaned up: ${sessionId}`), level: "info", }), ); }); test("should fail to touch non-existent session", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); const result = sessionManager.touch("missing"); expect(result.success).toBe(false); }); test("should fail to clean up non-existent session", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); const result = sessionManager.cleanup("non-existent-id"); expect(result.success).toBe(false); if (!result.success) { expect(result.error.message).toContain("Session not found"); } }); test("should remove session from list after cleanup", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); const sessionId = sessionManager.create(); expect(sessionManager.list()).toHaveLength(1); sessionManager.cleanup(sessionId); expect(sessionManager.list()).toHaveLength(0); }); }); describe("multiple sessions listing", () => { test("should list all active sessions", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); const id1 = sessionManager.create(); const id2 = sessionManager.create(); const id3 = sessionManager.create(); const sessions = sessionManager.list(); expect(sessions).toHaveLength(3); expect(sessions.map((s) => s.id)).toContain(id1); expect(sessions.map((s) => s.id)).toContain(id2); expect(sessions.map((s) => s.id)).toContain(id3); }); test("should return empty array when no sessions", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); const sessions = sessionManager.list(); expect(sessions).toEqual([]); }); test("should not include cleaned up sessions", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); const id1 = sessionManager.create(); const id2 = sessionManager.create(); sessionManager.cleanup(id1); const sessions = sessionManager.list(); expect(sessions).toHaveLength(1); expect(sessions[0]?.id).toBe(id2); }); }); describe("automatic TTL cleanup", () => { test("should start cleanup interval", () => { vi.useFakeTimers(); const config: SessionConfig = { cleanupInterval: 500, enabled: true, ttl: 1000, }; sessionManager = new SessionManager(config, mockLogger); sessionManager.startTTLCleanup(); expect(mockLogger.sendLog).toHaveBeenCalledWith( expect.objectContaining({ data: expect.stringContaining("Starting TTL cleanup"), level: "info", }), ); }); test("should clean up expired sessions automatically", () => { vi.useFakeTimers(); const config: SessionConfig = { cleanupInterval: 500, enabled: true, ttl: 1000, }; sessionManager = new SessionManager(config, mockLogger); // Create sessions sessionManager.create(); sessionManager.create(); expect(sessionManager.list()).toHaveLength(2); // Start cleanup sessionManager.startTTLCleanup(); // Advance time to expire sessions vi.advanceTimersByTime(1001); // Trigger cleanup interval vi.advanceTimersByTime(500); // Sessions should be cleaned up expect(sessionManager.list()).toHaveLength(0); }); test("should not start cleanup if already running", () => { vi.useFakeTimers(); const config: SessionConfig = { cleanupInterval: 500, enabled: true, ttl: 1000, }; sessionManager = new SessionManager(config, mockLogger); sessionManager.startTTLCleanup(); const firstCallCount = (mockLogger.sendLog as ReturnType<typeof vi.fn>) .mock.calls.length; sessionManager.startTTLCleanup(); const secondCallCount = (mockLogger.sendLog as ReturnType<typeof vi.fn>) .mock.calls.length; // Should not log again expect(secondCallCount).toBe(firstCallCount); }); test("should apply default TTL when not specified", () => { vi.useFakeTimers(); const config: SessionConfig = { enabled: true, // No TTL specified so default should apply }; sessionManager = new SessionManager(config, mockLogger); sessionManager.startTTLCleanup(); expect(mockLogger.sendLog).toHaveBeenCalledWith( expect.objectContaining({ data: expect.stringContaining( "Starting TTL cleanup (interval: 300000ms, TTL: 3600000ms)", ), }), ); }); test("should stop cleanup interval", () => { vi.useFakeTimers(); const config: SessionConfig = { cleanupInterval: 500, enabled: true, ttl: 1000, }; sessionManager = new SessionManager(config, mockLogger); sessionManager.startTTLCleanup(); sessionManager.stopTTLCleanup(); expect(mockLogger.sendLog).toHaveBeenCalledWith( expect.objectContaining({ data: "Stopped TTL cleanup", level: "info", }), ); }); test("should not clean up sessions after stopping cleanup", () => { vi.useFakeTimers(); const config: SessionConfig = { cleanupInterval: 500, enabled: true, ttl: 1000, }; sessionManager = new SessionManager(config, mockLogger); sessionManager.create(); sessionManager.startTTLCleanup(); sessionManager.stopTTLCleanup(); // Advance time vi.advanceTimersByTime(2000); // Session should still exist (cleanup stopped) expect(sessionManager.list()).toHaveLength(1); }); }); describe("concurrent access", () => { test("should handle multiple concurrent session creations", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); const ids = Array.from({ length: 100 }, () => sessionManager.create()); // All IDs should be unique const uniqueIds = new Set(ids); expect(uniqueIds.size).toBe(100); // All sessions should be created expect(sessionManager.list()).toHaveLength(100); }); test("should handle concurrent cleanup operations", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); const id1 = sessionManager.create(); const id2 = sessionManager.create(); // Clean up concurrently const result1 = sessionManager.cleanup(id1); const result2 = sessionManager.cleanup(id2); expect(result1.success).toBe(true); expect(result2.success).toBe(true); expect(sessionManager.list()).toHaveLength(0); }); }); describe("utility methods", () => { test("should get active session count", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); expect(sessionManager.getActiveSessionCount()).toBe(0); sessionManager.create(); sessionManager.create(); expect(sessionManager.getActiveSessionCount()).toBe(2); }); test("should clear all sessions", () => { const config: SessionConfig = { enabled: true }; sessionManager = new SessionManager(config, mockLogger); sessionManager.create(); sessionManager.create(); sessionManager.create(); expect(sessionManager.getActiveSessionCount()).toBe(3); sessionManager.clearAll(); expect(sessionManager.getActiveSessionCount()).toBe(0); expect(mockLogger.sendLog).toHaveBeenCalledWith( expect.objectContaining({ data: "All sessions cleared: 3 session(s) removed", level: "info", }), ); }); }); describe("configuration defaults", () => { test("should use default cleanup interval if not specified", () => { vi.useFakeTimers(); const config: SessionConfig = { enabled: true, ttl: 1000, // No cleanupInterval specified }; sessionManager = new SessionManager(config, mockLogger); sessionManager.startTTLCleanup(); // Default cleanup interval should fall back to 5 minutes (300000ms) expect(mockLogger.sendLog).toHaveBeenCalledWith( expect.objectContaining({ data: expect.stringContaining("interval: 300000ms"), }), ); }); test("should use custom cleanup interval if specified", () => { vi.useFakeTimers(); const config: SessionConfig = { cleanupInterval: 250, enabled: true, ttl: 1000, }; sessionManager = new SessionManager(config, mockLogger); sessionManager.startTTLCleanup(); expect(mockLogger.sendLog).toHaveBeenCalledWith( expect.objectContaining({ data: expect.stringContaining("interval: 250ms"), }), ); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/8beeeaaat/touchdesigner-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server