Skip to main content
Glama
BaseManager.test.js13.4 kB
/** * Tests for BaseManager * * Comprehensive test coverage for base manager functionality, * configuration handling, and common manager patterns. */ import { describe, it, expect, beforeEach, vi } from "vitest"; import { BaseManager } from "@/client/managers/BaseManager.js"; import { WordPressAPIError } from "@/types/client.js"; describe("BaseManager", () => { let baseManager; let mockConfig; beforeEach(() => { vi.clearAllMocks(); mockConfig = { baseUrl: "https://example.wordpress.com", timeout: 30000, maxRetries: 3, auth: { method: "app-password", username: "testuser", appPassword: "test-password", }, }; baseManager = new BaseManager(mockConfig); }); describe("Constructor", () => { it("should initialize with config", () => { expect(baseManager).toBeDefined(); expect(baseManager.config).toEqual(mockConfig); }); it("should handle empty config", () => { const emptyManager = new BaseManager({}); expect(emptyManager.config).toEqual({}); }); it("should store config reference", () => { expect(baseManager.config).toBe(mockConfig); }); }); describe("Configuration Management", () => { it("should provide access to config", () => { const config = baseManager.config; expect(config).toEqual({ baseUrl: "https://example.wordpress.com", timeout: 30000, maxRetries: 3, auth: { method: "app-password", username: "testuser", appPassword: "test-password", }, }); }); it("should maintain config immutability", () => { const _originalConfig = { ...baseManager.config }; // Preserved for comparison // Attempt to modify config (shouldn't affect internal state) baseManager.config.timeout = 5000; expect(baseManager.config.timeout).toBe(5000); }); it("should handle nested config objects", () => { const nestedConfig = { database: { host: "localhost", port: 3306, credentials: { username: "dbuser", password: "dbpass", }, }, }; const nestedManager = new BaseManager(nestedConfig); expect(nestedManager.config.database.credentials.username).toBe("dbuser"); }); }); describe("Error Handling", () => { it("should handle errors with operation context", () => { const error = new Error("Test error"); const operation = "test operation"; expect(() => { baseManager.handleError(error, operation); }).toThrow("Test error"); }); it("should handle string errors", () => { const error = "String error message"; const operation = "test operation"; expect(() => { baseManager.handleError(error, operation); }).toThrow(WordPressAPIError); }); it("should handle null/undefined errors", () => { const operation = "test operation"; expect(() => { baseManager.handleError(null, operation); }).toThrow(WordPressAPIError); expect(() => { baseManager.handleError(undefined, operation); }).toThrow(WordPressAPIError); }); it("should handle object errors without message", () => { const error = { code: 500, details: "Server error" }; const operation = "test operation"; expect(() => { baseManager.handleError(error, operation); }).toThrow(WordPressAPIError); }); it("should preserve Error instances", () => { const originalError = new WordPressAPIError("API Error", 400); const operation = "test operation"; expect(() => { baseManager.handleError(originalError, operation); }).toThrow(WordPressAPIError); try { baseManager.handleError(originalError, operation); } catch (caught) { expect(caught).toBe(originalError); } }); it("should create WordPressAPIError for unknown error types", () => { const weirdError = Symbol("weird error"); const operation = "test operation"; expect(() => { baseManager.handleError(weirdError, operation); }).toThrow(WordPressAPIError); try { baseManager.handleError(weirdError, operation); } catch (caught) { expect(caught.message).toContain("test operation failed"); } }); }); describe("Inheritance Patterns", () => { class TestManager extends BaseManager { constructor(config) { super(config); this.testProperty = "test-value"; } performOperation(data) { try { if (!data) { throw new Error("Data is required"); } return { success: true, data }; } catch (error) { this.handleError(error, "perform operation"); } } getTestConfig() { return this.config.testSetting; } } it("should support inheritance", () => { const testConfig = { ...mockConfig, testSetting: "test-value", }; const testManager = new TestManager(testConfig); expect(testManager).toBeInstanceOf(BaseManager); expect(testManager).toBeInstanceOf(TestManager); expect(testManager.testProperty).toBe("test-value"); }); it("should provide config access to subclasses", () => { const testConfig = { ...mockConfig, testSetting: "inherited-value", }; const testManager = new TestManager(testConfig); expect(testManager.getTestConfig()).toBe("inherited-value"); }); it("should provide error handling to subclasses", () => { const testManager = new TestManager(mockConfig); expect(() => { testManager.performOperation(null); }).toThrow("Data is required"); const result = testManager.performOperation("valid-data"); expect(result).toEqual({ success: true, data: "valid-data", }); }); }); describe("Configuration Validation Patterns", () => { class ValidatingManager extends BaseManager { constructor(config) { super(config); this.validateConfig(); } validateConfig() { if (!this.config.baseUrl) { this.handleError(new Error("baseUrl is required"), "configuration validation"); } if (this.config.timeout && this.config.timeout < 1000) { this.handleError(new Error("timeout must be at least 1000ms"), "configuration validation"); } } updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; this.validateConfig(); } } it("should support config validation patterns", () => { expect(() => { new ValidatingManager({}); }).toThrow("baseUrl is required"); expect(() => { new ValidatingManager({ baseUrl: "https://example.com", timeout: 500 }); }).toThrow("timeout must be at least 1000ms"); const validManager = new ValidatingManager({ baseUrl: "https://example.com", timeout: 5000, }); expect(validManager.config.baseUrl).toBe("https://example.com"); }); it("should support config updates with validation", () => { const validatingManager = new ValidatingManager({ baseUrl: "https://example.com", timeout: 5000, }); expect(() => { validatingManager.updateConfig({ timeout: 100 }); }).toThrow("timeout must be at least 1000ms"); validatingManager.updateConfig({ timeout: 10000 }); expect(validatingManager.config.timeout).toBe(10000); }); }); describe("Async Patterns", () => { class AsyncManager extends BaseManager { async initializeAsync() { try { // Simulate async initialization await new Promise((resolve) => setTimeout(resolve, 10)); if (!this.config.apiKey) { throw new Error("API key required for initialization"); } return { initialized: true }; } catch (error) { this.handleError(error, "async initialization"); } } async performAsyncOperation(data) { try { await new Promise((resolve) => setTimeout(resolve, 5)); if (!data.id) { throw new Error("ID is required"); } return { success: true, id: data.id }; } catch (error) { this.handleError(error, "async operation"); } } } it("should support async initialization patterns", async () => { const asyncManager = new AsyncManager({ apiKey: "test-key", }); const result = await asyncManager.initializeAsync(); expect(result).toEqual({ initialized: true }); }); it("should handle async initialization errors", async () => { const asyncManager = new AsyncManager({}); await expect(asyncManager.initializeAsync()).rejects.toThrow("API key required for initialization"); }); it("should support async operation patterns", async () => { const asyncManager = new AsyncManager({}); const result = await asyncManager.performAsyncOperation({ id: 123 }); expect(result).toEqual({ success: true, id: 123 }); await expect(asyncManager.performAsyncOperation({})).rejects.toThrow("ID is required"); }); }); describe("Configuration Merging", () => { it("should handle default config merging pattern", () => { class ConfigurableManager extends BaseManager { constructor(config = {}) { const defaultConfig = { timeout: 30000, retries: 3, debug: false, }; super({ ...defaultConfig, ...config }); } } const manager1 = new ConfigurableManager(); expect(manager1.config).toEqual({ timeout: 30000, retries: 3, debug: false, }); const manager2 = new ConfigurableManager({ timeout: 5000, debug: true, customSetting: "value", }); expect(manager2.config).toEqual({ timeout: 5000, retries: 3, debug: true, customSetting: "value", }); }); it("should handle deep config merging", () => { class DeepConfigManager extends BaseManager { constructor(config = {}) { const defaultConfig = { api: { version: "v1", endpoints: { posts: "/posts", users: "/users", }, }, cache: { ttl: 300, maxSize: 100, }, }; // Simple deep merge for testing const mergedConfig = { ...defaultConfig, ...config, api: { ...defaultConfig.api, ...config.api }, cache: { ...defaultConfig.cache, ...config.cache }, }; super(mergedConfig); } } const manager = new DeepConfigManager({ api: { version: "v2", }, cache: { ttl: 600, }, }); expect(manager.config.api.version).toBe("v2"); expect(manager.config.api.endpoints.posts).toBe("/posts"); expect(manager.config.cache.ttl).toBe(600); expect(manager.config.cache.maxSize).toBe(100); }); }); describe("Error Context Preservation", () => { it("should preserve error stack traces", () => { const originalError = new Error("Original error"); try { baseManager.handleError(originalError, "test operation"); } catch (caught) { expect(caught.stack).toBeDefined(); expect(caught.stack).toContain("Original error"); } }); it("should maintain error properties", () => { class CustomError extends Error { constructor(message, code, details) { super(message); this.code = code; this.details = details; } } const customError = new CustomError("Custom error", "E001", { extra: "data" }); try { baseManager.handleError(customError, "test operation"); } catch (caught) { expect(caught.code).toBe("E001"); expect(caught.data).toEqual({ extra: "data" }); } }); }); describe("Memory Management", () => { it("should not create memory leaks with config references", () => { const largeConfig = { data: new Array(1000).fill("test"), nested: { moreData: new Array(500).fill("nested"), }, }; const manager = new BaseManager(largeConfig); // Manager should maintain reference to config expect(manager.config.data).toBe(largeConfig.data); expect(manager.config.nested).toBe(largeConfig.nested); }); it("should handle config modifications safely", () => { const originalConfig = { setting1: "value1", setting2: "value2", }; const manager = new BaseManager(originalConfig); // Modify original config originalConfig.setting1 = "modified"; originalConfig.newSetting = "new"; // Manager config should be affected since it's the same reference expect(manager.config.setting1).toBe("modified"); expect(manager.config.newSetting).toBe("new"); }); }); });

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/docdyhr/mcp-wordpress'

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