- 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>
106 lines
2.6 KiB
JavaScript
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
|
|
};
|