import { vi } from "vitest";
import { MetricsCollector } from "@/performance/MetricsCollector.js";
describe("MetricsCollector", () => {
let collector;
let mockMonitor;
beforeEach(() => {
// Create a mock PerformanceMonitor
mockMonitor = {
recordRequest: vi.fn(),
updateCacheMetrics: vi.fn(),
getMetrics: vi.fn().mockReturnValue({
requests: {
total: 100,
failed: 5,
averageResponseTime: 250,
},
cache: {
hitRate: 0.85,
totalSize: 1000,
},
system: {
memoryUsage: 75,
cpuUsage: 30,
},
tools: {
toolUsageCount: {},
toolResponseTimes: {},
},
}),
getAlerts: vi.fn().mockReturnValue([]),
stop: vi.fn(),
};
collector = new MetricsCollector(mockMonitor);
});
afterEach(() => {
if (collector) {
collector.stop();
}
vi.clearAllMocks();
});
describe("initialization", () => {
it("should create collector with default configuration", () => {
expect(collector).toBeDefined();
expect(collector.constructor.name).toBe("MetricsCollector");
});
it("should accept custom configuration", () => {
const customCollector = new MetricsCollector(mockMonitor, {
enableRealTime: false,
collectInterval: 60000,
});
expect(customCollector).toBeDefined();
});
});
describe("metrics collection", () => {
it("should collect current metrics from monitor", () => {
const metrics = collector.collectCurrentMetrics();
expect(mockMonitor.getMetrics).toHaveBeenCalled();
expect(metrics).toEqual({
requests: {
total: 100,
failed: 5,
averageResponseTime: 250,
},
cache: {
hitRate: 0.85,
totalSize: 1000,
},
system: {
memoryUsage: 75,
cpuUsage: 30,
},
tools: {
toolUsageCount: {},
toolResponseTimes: {},
},
});
});
it("should get aggregated cache stats", () => {
const stats = collector.getAggregatedCacheStats();
expect(stats).toHaveProperty("hits");
expect(stats).toHaveProperty("misses");
expect(stats).toHaveProperty("evictions");
expect(stats).toHaveProperty("totalSize");
expect(stats).toHaveProperty("hitRate");
});
it("should get aggregated client stats", () => {
const stats = collector.getAggregatedClientStats();
expect(stats).toHaveProperty("totalRequests");
expect(stats).toHaveProperty("successfulRequests");
expect(stats).toHaveProperty("failedRequests");
expect(stats).toHaveProperty("averageResponseTime");
expect(stats).toHaveProperty("rateLimitHits");
expect(stats).toHaveProperty("authFailures");
});
});
describe("client registration", () => {
it("should register clients", () => {
const mockClient = { request: vi.fn() };
collector.registerClient("site1", mockClient);
// Just verify no errors - internal state is private
expect(() => collector.registerClient("site2", mockClient)).not.toThrow();
});
it("should handle client without request method", () => {
const mockClient = {}; // No request method
// Should not throw
expect(() => collector.registerClient("site1", mockClient)).not.toThrow();
});
});
describe("cache manager registration", () => {
it("should register cache managers", () => {
const mockCacheManager = {
getStats: vi.fn().mockReturnValue({
hits: 10,
misses: 2,
evictions: 1,
totalSize: 100,
hitRate: 0.83,
}),
};
collector.registerCacheManager("site1", mockCacheManager);
// Verify by checking aggregated stats includes this cache
const stats = collector.getAggregatedCacheStats();
expect(stats.hits).toBe(10);
expect(stats.misses).toBe(2);
});
});
describe("tool tracking", () => {
it("should track tool execution", async () => {
const executionId = collector.startToolExecution("wp_list_posts", { per_page: 10 }, "site1");
expect(executionId).toBeTruthy();
expect(typeof executionId).toBe("string");
// Wait a bit to ensure some response time
await new Promise((resolve) => setTimeout(resolve, 10));
collector.endToolExecution(executionId, true);
expect(mockMonitor.recordRequest).toHaveBeenCalledWith(expect.any(Number), true, "wp_list_posts");
});
it("should handle tool execution errors", () => {
const executionId = collector.startToolExecution("wp_list_posts", {}, "site1");
const error = new Error("Test error");
collector.endToolExecution(executionId, false, error);
expect(mockMonitor.recordRequest).toHaveBeenCalledWith(expect.any(Number), false, "wp_list_posts");
});
it("should handle invalid execution IDs gracefully", () => {
// Should not throw
expect(() => collector.endToolExecution("invalid-id", true)).not.toThrow();
expect(mockMonitor.recordRequest).not.toHaveBeenCalled();
});
});
describe("request tracking", () => {
it("should record raw requests", () => {
collector.recordRawRequest(150, true, "/wp/v2/posts", false);
expect(mockMonitor.recordRequest).toHaveBeenCalledWith(150, true);
});
it("should record cached requests", () => {
collector.recordRawRequest(5, true, "/wp/v2/posts", true);
expect(mockMonitor.recordRequest).toHaveBeenCalledWith(5, true);
});
});
describe("site metrics", () => {
it("should get metrics for specific site", () => {
const mockClient = {
getStats: vi.fn().mockReturnValue({
totalRequests: 50,
successfulRequests: 45,
failedRequests: 5,
averageResponseTime: 200,
rateLimitHits: 0,
authFailures: 0,
}),
};
const mockCache = {
getStats: vi.fn().mockReturnValue({
hits: 100,
misses: 20,
evictions: 5,
totalSize: 500,
hitRate: 0.83,
}),
};
collector.registerClient("site1", mockClient);
collector.registerCacheManager("site1", mockCache);
const siteMetrics = collector.getSiteMetrics("site1");
expect(siteMetrics).toHaveProperty("client");
expect(siteMetrics).toHaveProperty("cache");
expect(siteMetrics.isActive).toBe(true);
expect(mockClient.getStats).toHaveBeenCalled();
expect(mockCache.getStats).toHaveBeenCalled();
});
it("should handle missing site", () => {
const siteMetrics = collector.getSiteMetrics("non-existent");
expect(siteMetrics.isActive).toBe(false);
expect(siteMetrics.client).toBeUndefined();
expect(siteMetrics.cache).toBeUndefined();
});
});
describe("performance comparison", () => {
it("should compare site performance", () => {
// Register multiple sites with different performance characteristics
const sites = ["site1", "site2", "site3"];
sites.forEach((site, index) => {
collector.registerClient(site, {
getStats: vi.fn().mockReturnValue({
totalRequests: 100 * (index + 1),
successfulRequests: 100 * (index + 1) - 5 * (index + 1),
failedRequests: 5 * (index + 1),
averageResponseTime: 100 * (index + 1),
rateLimitHits: 0,
authFailures: 0,
}),
});
collector.registerCacheManager(site, {
getStats: vi.fn().mockReturnValue({
hits: 80 - index * 10,
misses: 20 + index * 10,
evictions: 0,
totalSize: 1000,
hitRate: (80 - index * 10) / 100,
}),
});
});
const comparison = collector.compareSitePerformance();
expect(comparison.sites).toHaveLength(3);
expect(comparison.bestPerforming).toBeTruthy();
expect(comparison.worstPerforming).toBeTruthy();
expect(comparison.comparison).toBeDefined();
// Verify each site has comparison data
sites.forEach((site) => {
expect(comparison.comparison[site]).toHaveProperty("responseTime");
expect(comparison.comparison[site]).toHaveProperty("cacheHitRate");
expect(comparison.comparison[site]).toHaveProperty("errorRate");
expect(comparison.comparison[site]).toHaveProperty("requestCount");
expect(comparison.comparison[site]).toHaveProperty("ranking");
});
});
});
describe("optimization suggestions", () => {
it("should generate optimization suggestions", () => {
const suggestions = collector.generateOptimizationSuggestions();
expect(suggestions).toHaveProperty("critical");
expect(suggestions).toHaveProperty("recommended");
expect(suggestions).toHaveProperty("optional");
expect(Array.isArray(suggestions.critical)).toBe(true);
expect(Array.isArray(suggestions.recommended)).toBe(true);
expect(Array.isArray(suggestions.optional)).toBe(true);
});
it("should identify critical issues with poor performance", () => {
// Mock poor performance metrics
mockMonitor.getMetrics.mockReturnValue({
requests: {
total: 100,
failed: 20,
averageResponseTime: 6000, // > 5000ms
},
cache: {
hitRate: 0.5,
totalSize: 1000,
},
system: {
memoryUsage: 90,
cpuUsage: 80,
},
tools: {
toolUsageCount: {},
toolResponseTimes: {},
},
});
const suggestions = collector.generateOptimizationSuggestions();
expect(suggestions.critical.length).toBeGreaterThan(0);
expect(suggestions.critical.some((s) => s.includes("Response times are critically high"))).toBe(true);
expect(suggestions.critical.some((s) => s.includes("Error rate is critically high"))).toBe(true);
});
it("should provide recommendations for suboptimal performance", () => {
// Mock suboptimal but not critical performance
mockMonitor.getMetrics.mockReturnValue({
requests: {
total: 100,
failed: 5,
averageResponseTime: 2500, // Between 2000-5000ms
},
cache: {
hitRate: 0.7, // Below 0.8
totalSize: 1000,
},
system: {
memoryUsage: 85, // Above 80%
cpuUsage: 50,
},
tools: {
toolUsageCount: {},
toolResponseTimes: {},
},
});
const suggestions = collector.generateOptimizationSuggestions();
expect(suggestions.recommended.length).toBeGreaterThan(0);
expect(suggestions.recommended.some((s) => s.includes("Cache hit rate is below 80%"))).toBe(true);
expect(suggestions.recommended.some((s) => s.includes("Response times could be improved"))).toBe(true);
expect(suggestions.recommended.some((s) => s.includes("Memory usage is high"))).toBe(true);
});
});
describe("detailed report", () => {
it("should export detailed performance report", () => {
// Register a site to make the report more complete
collector.registerClient("site1", {
getStats: vi.fn().mockReturnValue({
totalRequests: 100,
successfulRequests: 95,
failedRequests: 5,
averageResponseTime: 200,
rateLimitHits: 0,
authFailures: 0,
}),
});
const report = collector.exportDetailedReport();
expect(report).toHaveProperty("timestamp");
expect(report).toHaveProperty("overview");
expect(report).toHaveProperty("siteComparison");
expect(report).toHaveProperty("aggregatedStats");
expect(report.aggregatedStats).toHaveProperty("cache");
expect(report.aggregatedStats).toHaveProperty("client");
expect(report).toHaveProperty("optimizations");
expect(report).toHaveProperty("alerts");
// Verify timestamp is ISO format
expect(new Date(report.timestamp).toISOString()).toBe(report.timestamp);
});
});
describe("cleanup", () => {
it("should stop monitoring and cleanup resources", () => {
// Start some tool executions to verify they get cleaned up
const executionId = collector.startToolExecution("test_tool", {}, "site1");
collector.stop();
expect(mockMonitor.stop).toHaveBeenCalled();
// Verify tool execution is cleaned up (should not throw or record)
collector.endToolExecution(executionId, true);
expect(mockMonitor.recordRequest).not.toHaveBeenCalled();
});
});
});