/** * Manual Test Instructions for CacheService * * Since Cloudflare Workers Cache API is only available in the Workers runtime, * these tests must be run in a Cloudflare Workers environment or using Miniflare. * * Test 1: Basic Cache Operations * ------------------------------- * 1. Deploy to Cloudflare Workers development environment * 2. Initialize cache: const cache = new CacheService(300); * 3. Set data: await cache.set('test-key', { foo: 'bar' }, 60); * 4. Get data: const result = await cache.get('test-key'); * 5. Expected: result.data.foo === 'bar', cache_age_seconds ≈ 0 * * Test 2: Cache Key Generation * ----------------------------- * 1. Generate key: const key = cache.generateKey({ provider: 'linode', region: 'us-east' }); * 2. Expected: key === 'https://cache.internal/instances?provider=linode®ion=us-east' * 3. Verify sorting: cache.generateKey({ z: 1, a: 2 }) should have 'a' before 'z' * * Test 3: Cache Miss * ------------------ * 1. Request non-existent key: const result = await cache.get('non-existent'); * 2. Expected: result === null * * Test 4: Cache Expiration * ------------------------ * 1. Set with short TTL: await cache.set('expire-test', { data: 'test' }, 2); * 2. Immediate get: await cache.get('expire-test') → should return data * 3. Wait 3 seconds * 4. Get again: await cache.get('expire-test') → should return null (expired) * * Test 5: Cache Age Tracking * -------------------------- * 1. Set data: await cache.set('age-test', { data: 'test' }, 300); * 2. Wait 5 seconds * 3. Get data: const result = await cache.get('age-test'); * 4. Expected: result.cache_age_seconds ≈ 5 * * Test 6: Cache Deletion * ---------------------- * 1. Set data: await cache.set('delete-test', { data: 'test' }, 300); * 2. Delete: const deleted = await cache.delete('delete-test'); * 3. Expected: deleted === true * 4. Get data: const result = await cache.get('delete-test'); * 5. Expected: result === null * * Test 7: Error Handling (Graceful Degradation) * ---------------------------------------------- * 1. Test with invalid cache response (manual mock required) * 2. Expected: No errors thrown, graceful null return * 3. Verify logs show error message * * Test 8: Integration with Instance API * -------------------------------------- * 1. Create cache instance in instance endpoint handler * 2. Generate key from query params: cache.generateKey(query) * 3. Check cache: const cached = await cache.get(key); * 4. If cache hit: return cached.data with cache metadata * 5. If cache miss: fetch from database, cache result, return data * 6. Verify cache hit on second request * * Performance Validation: * ----------------------- * 1. Measure database query time (first request) * 2. Measure cache hit time (second request) * 3. Expected: Cache hit 10-50x faster than database query * 4. Verify cache age increases on subsequent requests * * TTL Strategy Validation: * ------------------------ * Filtered queries (5 min TTL): * - cache.set(key, data, 300) * - Verify expires after 5 minutes * * Full dataset (1 hour TTL): * - cache.set(key, data, 3600) * - Verify expires after 1 hour * * Post-sync invalidation: * - After sync operation, call cache.delete(key) for all relevant keys * - Verify next request fetches fresh data from database * * Test 9: clearAll Method * ----------------------- * 1. Set multiple cache entries: * - await cache.set('key1', { data: 'test1' }, 300) * - await cache.set('key2', { data: 'test2' }, 300) * - await cache.set('key3', { data: 'test3' }, 300) * 2. Call clearAll: const count = await cache.clearAll() * 3. Expected: count === 0 (enumeration not supported) * 4. Expected: Log message about TTL-based expiration * 5. Note: Individual entries will expire based on TTL * * Test 10: clearAll with Prefix * ------------------------------ * 1. Set entries with different prefixes: * - await cache.set('https://cache.internal/instances?foo=bar', data1) * - await cache.set('https://cache.internal/pricing?foo=bar', data2) * 2. Call with prefix: await cache.clearAll('https://cache.internal/instances') * 3. Expected: count === 0, log shows prefix parameter * 4. Note: Prefix is logged but enumeration not supported by Cache API * * Test 11: clearByEndpoint Method * -------------------------------- * 1. Set endpoint cache: await cache.set('https://cache.internal/instances', data, 300) * 2. Clear by endpoint: const deleted = await cache.clearByEndpoint('instances') * 3. Expected: deleted === true if cache entry existed * 4. Get data: const result = await cache.get('https://cache.internal/instances') * 5. Expected: result === null (entry deleted) * 6. Note: Only exact matches deleted, parameterized queries remain cached * * Test 12: clearByEndpoint with Non-existent Endpoint * ---------------------------------------------------- * 1. Clear non-existent: const deleted = await cache.clearByEndpoint('non-existent') * 2. Expected: deleted === false * 3. Expected: Log message about non-existent endpoint * * Test 13: Cache Invalidation Strategy * ------------------------------------- * 1. Set parameterized cache entries: * - cache.generateKey({ provider: 'linode', region: 'us-east' }) * - cache.generateKey({ provider: 'linode', region: 'eu-west' }) * - cache.generateKey({ provider: 'vultr', region: 'us-east' }) * 2. After schema change or full sync, call clearAll() * 3. Verify entries expire based on TTL * 4. For production: Consider using KV-backed cache index for enumeration * * Test 14: Error Handling in clearAll * ------------------------------------ * 1. Mock cache.delete to throw error * 2. Call clearAll: await cache.clearAll() * 3. Expected: Error is logged and re-thrown * 4. Verify error message includes context * * Test 15: Error Handling in clearByEndpoint * ------------------------------------------- * 1. Mock cache.delete to throw error * 2. Call clearByEndpoint: await cache.clearByEndpoint('instances') * 3. Expected: Returns false, error logged * 4. Application continues without crashing */ import { CacheService } from './cache'; import type { InstanceData } from '../types'; /** * Example: Using CacheService in API endpoint */ async function exampleInstanceEndpointWithCache( queryParams: Record, fetchFromDatabase: () => Promise ): Promise<{ data: InstanceData[]; cached?: boolean; cache_age?: number }> { const cache = new CacheService(300); // 5 minutes default TTL // Generate cache key from query parameters const cacheKey = cache.generateKey(queryParams); // Try to get from cache const cached = await cache.get(cacheKey); if (cached) { console.log(`[API] Cache hit (age: ${cached.cache_age_seconds}s)`); return { data: cached.data, cached: true, cache_age: cached.cache_age_seconds, }; } // Cache miss - fetch from database console.log('[API] Cache miss - fetching from database'); const data = await fetchFromDatabase(); // Determine TTL based on query complexity const hasFilters = Object.keys(queryParams).length > 0; const ttl = hasFilters ? 300 : 3600; // 5 min for filtered, 1 hour for full // Store in cache await cache.set(cacheKey, data, ttl); return { data, cached: false, }; } /** * Example: Cache invalidation after sync */ async function exampleCacheInvalidationAfterSync( syncedProviders: string[] ): Promise { const cache = new CacheService(); // Invalidate all instance caches for synced providers for (const provider of syncedProviders) { // Note: Since Cloudflare Workers Cache API doesn't support pattern matching, // you need to maintain a list of active cache keys or use KV for indexing const key = cache.generateKey({ provider }); await cache.delete(key); console.log(`[Sync] Invalidated cache for provider: ${provider}`); } console.log('[Sync] Cache invalidation complete'); } /** * Example: Using clearAll after schema changes */ async function exampleClearAllAfterSchemaChange(): Promise { const cache = new CacheService(); // After major schema changes or data migrations console.log('[Migration] Clearing all cache entries'); const count = await cache.clearAll(); console.log(`[Migration] Cache clear requested. Entries will expire based on TTL.`); console.log('[Migration] Consider using KV-backed cache index for enumeration in production.'); } /** * Example: Using clearByEndpoint for targeted invalidation */ async function exampleClearByEndpointAfterUpdate(endpoint: string): Promise { const cache = new CacheService(); // Clear cache for specific endpoint after data update console.log(`[Update] Clearing cache for endpoint: ${endpoint}`); const deleted = await cache.clearByEndpoint(endpoint); if (deleted) { console.log(`[Update] Successfully cleared ${endpoint} cache`); } else { console.log(`[Update] No cache entry found for ${endpoint}`); } } /** * Example: Force cache refresh strategy */ async function exampleForceCacheRefresh( endpoint: string, fetchFunction: () => Promise ): Promise { const cache = new CacheService(); // Strategy 1: Clear specific endpoint await cache.clearByEndpoint(endpoint); // Strategy 2: Clear with prefix (logged but not enumerated) await cache.clearAll(`https://cache.internal/${endpoint}`); // Strategy 3: Fetch fresh data and update cache const freshData = await fetchFunction(); const cacheKey = `https://cache.internal/${endpoint}`; await cache.set(cacheKey, freshData, 3600); console.log(`[Refresh] Cache refreshed for ${endpoint}`); } export { exampleInstanceEndpointWithCache, exampleCacheInvalidationAfterSync, exampleClearAllAfterSchemaChange, exampleClearByEndpointAfterUpdate, exampleForceCacheRefresh };