Skip to main content
Glama
CacheInvalidation.test.ts9.13 kB
/** * Tests for CacheInvalidation */ import { CacheManager } from "../CacheManager.js"; import { HttpCacheWrapper } from "../HttpCacheWrapper.js"; import { CacheInvalidation, WordPressCachePatterns } from "../CacheInvalidation.js"; describe("CacheInvalidation", () => { let cacheManager: CacheManager; let httpCache: HttpCacheWrapper; let cacheInvalidation: CacheInvalidation; beforeEach(() => { cacheManager = new CacheManager({ maxSize: 100, defaultTTL: 10000, enableLRU: true, enableStats: true, }); httpCache = new HttpCacheWrapper(cacheManager, "test-site"); cacheInvalidation = new CacheInvalidation(httpCache); }); afterEach(() => { cacheManager.clear(); }); describe("Default Invalidation Rules", () => { test("should have default rules for posts", () => { const rules = cacheInvalidation.getRules(); expect(rules.posts).toBeDefined(); expect(rules.posts.length).toBeGreaterThan(0); const createRule = rules.posts.find((r) => r.trigger === "create"); expect(createRule).toBeDefined(); expect(createRule?.patterns).toContain("posts"); expect(createRule?.immediate).toBe(true); expect(createRule?.cascade).toBe(true); }); test("should have default rules for categories", () => { const rules = cacheInvalidation.getRules(); expect(rules.categories).toBeDefined(); const updateRule = rules.categories.find((r) => r.trigger === "update"); expect(updateRule).toBeDefined(); expect(updateRule?.patterns).toContain("categories/\\d+"); expect(updateRule?.cascade).toBe(true); }); test("should have default rules for users", () => { const rules = cacheInvalidation.getRules(); expect(rules.users).toBeDefined(); const deleteRule = rules.users.find((r) => r.trigger === "delete"); expect(deleteRule).toBeDefined(); expect(deleteRule?.patterns).toContain("users"); }); }); describe("Custom Invalidation Rules", () => { test("should register custom invalidation rules", () => { const customRule = { trigger: "update" as const, patterns: ["custom-endpoint.*"], immediate: true, }; cacheInvalidation.registerRule("custom", customRule); const rules = cacheInvalidation.getRules(); expect(rules.custom).toBeDefined(); expect(rules.custom).toContain(customRule); }); test("should support multiple rules per resource", () => { const rule1 = { trigger: "create" as const, patterns: ["test.*"], immediate: true, }; const rule2 = { trigger: "update" as const, patterns: ["test/\\d+"], immediate: false, }; cacheInvalidation.registerRule("test", rule1); cacheInvalidation.registerRule("test", rule2); const rules = cacheInvalidation.getRules(); expect(rules.test).toHaveLength(2); expect(rules.test).toContain(rule1); expect(rules.test).toContain(rule2); }); }); describe("Event Processing", () => { test("should process invalidation events", async () => { // Pre-populate cache with some test data httpCache.warm("posts", [{ id: 1 }, { id: 2 }]); httpCache.warm("posts/1", { id: 1, title: "Test Post" }); httpCache.warm("categories", [{ id: 1 }]); expect(cacheManager.getStats().totalSize).toBe(3); // Trigger post creation event await cacheInvalidation.trigger({ type: "create", resource: "posts", id: 3, siteId: "test-site", timestamp: Date.now(), }); // Should invalidate posts listings but not specific post const stats = cacheManager.getStats(); expect(stats.totalSize).toBeLessThan(3); }); test("should process events in queue order", async () => { const processedEvents: string[] = []; // Mock the invalidation process to track order const originalInvalidatePattern = httpCache.invalidatePattern; httpCache.invalidatePattern = jest.fn().mockImplementation((pattern: string) => { processedEvents.push(pattern); return originalInvalidatePattern.call(httpCache, pattern); }); // Queue multiple events await Promise.all([ cacheInvalidation.trigger({ type: "create", resource: "posts", siteId: "test-site", timestamp: Date.now(), }), cacheInvalidation.trigger({ type: "update", resource: "categories", siteId: "test-site", timestamp: Date.now(), }), ]); expect(processedEvents.length).toBeGreaterThan(0); }); test("should handle resource invalidation by type", async () => { httpCache.warm("posts", [{ id: 1 }]); httpCache.warm("posts/1", { id: 1 }); expect(cacheManager.getStats().totalSize).toBe(2); await cacheInvalidation.invalidateResource("posts", 1, "update"); // Should clear specific post and related caches const stats = cacheManager.getStats(); expect(stats.totalSize).toBeLessThan(2); }); }); describe("Pattern Matching", () => { test("should replace placeholders in patterns", async () => { // Pre-populate cache httpCache.warm("posts/123", { id: 123 }); httpCache.warm("posts/456", { id: 456 }); httpCache.warm("pages/123", { id: 123 }); expect(cacheManager.getStats().totalSize).toBe(3); // Trigger event with specific ID await cacheInvalidation.trigger({ type: "update", resource: "posts", id: 123, siteId: "test-site", timestamp: Date.now(), }); // Should only invalidate posts/123, not posts/456 or pages/123 const remainingKeys: string[] = []; for (const [key] of (cacheManager as unknown).cache.entries()) { remainingKeys.push(key); } expect(remainingKeys).not.toContain(expect.stringContaining("posts")); }); }); describe("Statistics", () => { test("should track invalidation statistics", () => { const stats = cacheInvalidation.getStats(); expect(stats).toHaveProperty("queueSize"); expect(stats).toHaveProperty("rulesCount"); expect(stats).toHaveProperty("processing"); expect(typeof stats.queueSize).toBe("number"); expect(typeof stats.rulesCount).toBe("number"); expect(typeof stats.processing).toBe("boolean"); expect(stats.rulesCount).toBeGreaterThan(0); }); test("should clear rules", () => { const initialStats = cacheInvalidation.getStats(); expect(initialStats.rulesCount).toBeGreaterThan(0); cacheInvalidation.clearRules(); const clearedStats = cacheInvalidation.getStats(); expect(clearedStats.rulesCount).toBe(0); }); }); }); describe("WordPressCachePatterns", () => { let cacheManager: CacheManager; let httpCache: HttpCacheWrapper; beforeEach(() => { cacheManager = new CacheManager({ maxSize: 100, defaultTTL: 10000, enableLRU: true, enableStats: true, }); httpCache = new HttpCacheWrapper(cacheManager, "test-site"); }); afterEach(() => { cacheManager.clear(); }); test("should invalidate content-related caches", () => { // Pre-populate cache httpCache.warm("posts", []); httpCache.warm("pages", []); httpCache.warm("comments", []); httpCache.warm("categories", []); expect(cacheManager.getStats().totalSize).toBe(4); const invalidated = WordPressCachePatterns.invalidateContent(httpCache); expect(invalidated).toBeGreaterThan(0); // Should invalidate posts, pages, comments but not categories expect(cacheManager.getStats().totalSize).toBeLessThan(4); }); test("should invalidate taxonomy-related caches", () => { httpCache.warm("categories", []); httpCache.warm("tags", []); httpCache.warm("posts", []); expect(cacheManager.getStats().totalSize).toBe(3); const invalidated = WordPressCachePatterns.invalidateTaxonomies(httpCache); expect(invalidated).toBeGreaterThan(0); // Should invalidate categories and tags but not posts expect(cacheManager.getStats().totalSize).toBeLessThan(3); }); test("should invalidate user-related caches", () => { httpCache.warm("users", []); httpCache.warm("users/me", {}); httpCache.warm("posts", []); expect(cacheManager.getStats().totalSize).toBe(3); const invalidated = WordPressCachePatterns.invalidateUsers(httpCache); expect(invalidated).toBeGreaterThan(0); // Should invalidate user caches but not posts expect(cacheManager.getStats().totalSize).toBeLessThan(3); }); test("should invalidate all caches", () => { httpCache.warm("posts", []); httpCache.warm("categories", []); httpCache.warm("users", []); expect(cacheManager.getStats().totalSize).toBe(3); const invalidated = WordPressCachePatterns.invalidateAll(httpCache); expect(invalidated).toBe(3); expect(cacheManager.getStats().totalSize).toBe(0); }); });

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