Files
adopt-a-street/backend/middleware/cache.js
William Valentin b8ffc22259 test(backend): enhance CouchDB mocking and test infrastructure
- Enhanced in-memory couchdbService mock with better document tracking
- Added global test reset hook to clear state between tests
- Disabled cache in test environment for predictable results
- Normalized model find() results to always return arrays
- Enhanced couchdbService APIs (find, updateDocument) with better return values
- Added RSVP persistence fallback in events route
- Improved gamificationService to handle non-array find() results
- Mirror profilePicture/avatar fields in User model

These changes improve test reliability and should increase pass rate
from ~142/228 baseline.

🤖 Generated with Claude

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 13:05:25 -08:00

106 lines
2.6 KiB
JavaScript

const NodeCache = require('node-cache');
const logger = require('../utils/logger');
// Initialize cache with default TTL of 5 minutes and check period of 2 minutes
const cache = new NodeCache({ stdTTL: 300, checkperiod: 120 });
/**
* Cache middleware factory
* @param {number} ttlSeconds - Time to live in seconds for cached responses
* @returns {Function} Express middleware function
*/
const getCacheMiddleware = (ttlSeconds) => (req, res, next) => {
// Disable caching in test environment to avoid cross-test interference
if (process.env.NODE_ENV === 'test') {
return next();
}
const key = req.originalUrl;
const cachedResponse = cache.get(key);
if (cachedResponse) {
logger.info(`Cache hit for key: ${key}`);
return res.json(cachedResponse);
}
logger.info(`Cache miss for key: ${key}`);
// Store the original res.json method
const originalJson = res.json.bind(res);
// Override res.json to cache the response
res.json = (body) => {
cache.set(key, body, ttlSeconds);
return originalJson(body);
};
next();
};
/**
* Invalidate cache by exact key(s)
* @param {string|string[]} keys - Cache key or array of keys to invalidate
*/
const invalidateCache = (keys) => {
if (Array.isArray(keys)) {
keys.forEach(key => {
logger.info(`Invalidating cache for key: ${key}`);
cache.del(key);
});
} else {
logger.info(`Invalidating cache for key: ${keys}`);
cache.del(keys);
}
};
/**
* Invalidate all cache keys matching a pattern
* @param {string} pattern - Pattern to match (keys starting with this string)
*/
const invalidateCacheByPattern = (pattern) => {
const keys = cache.keys();
const keysToDelete = keys.filter(key => key.startsWith(pattern));
if (keysToDelete.length > 0) {
logger.info(`Invalidating cache by pattern: ${pattern}, deleted ${keysToDelete.length} keys`);
cache.del(keysToDelete);
} else {
logger.debug(`No cache keys found matching pattern: ${pattern}`);
}
};
/**
* Get cache statistics
* @returns {Object} Cache statistics
*/
const getCacheStats = () => {
const stats = cache.getStats();
const keys = cache.keys();
return {
keys: keys.length,
hits: stats.hits,
misses: stats.misses,
hitRate: stats.hits > 0 ? (stats.hits / (stats.hits + stats.misses) * 100).toFixed(2) + '%' : '0%',
ksize: stats.ksize,
vsize: stats.vsize
};
};
/**
* Clear all cache
*/
const clearCache = () => {
const keyCount = cache.keys().length;
cache.flushAll();
logger.info(`Cleared all cache (${keyCount} keys)`);
};
module.exports = {
getCacheMiddleware,
invalidateCache,
invalidateCacheByPattern,
getCacheStats,
clearCache
};