Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 182 additions & 0 deletions TEST-CACHE-README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# 缓存自动失效功能测试

本目录包含用于测试 monSQLize 查询缓存自动失效功能的测试脚本。

## 测试脚本

### 1. test-cache-invalidation.js(完整集成测试)

完整的集成测试,验证缓存在实际 MongoDB 环境中的自动失效功能。

**测试内容:**
- ✅ TTL 自动过期 - 验证缓存在 TTL 到期后会自动失效
- ✅ insertOne 自动失效 - 验证插入操作会自动失效相关缓存
- ✅ updateOne 自动失效 - 验证更新操作会自动失效相关缓存
- ✅ deleteOne 自动失效 - 验证删除操作会自动失效相关缓存

**运行方式:**

```bash
# 方式 1:使用 MongoDB Memory Server(需要网络连接)
node test-cache-invalidation.js

# 方式 2:使用本地 MongoDB 实例
MONGODB_URI="mongodb://localhost:27017" node test-cache-invalidation.js

# 方式 3:使用远程 MongoDB 实例
MONGODB_URI="mongodb://username:password@host:port" node test-cache-invalidation.js
```

**注意事项:**
- MongoDB Memory Server 需要网络连接来下载 MongoDB 二进制文件
- 如果在没有网络的环境中,请使用本地或远程 MongoDB 实例

### 2. test-cache-invalidation-mock.js(Mock 测试)

基于 Mock 的单元测试,不需要 MongoDB 实例,用于验证缓存失效的核心逻辑。

**测试内容:**
- ✅ TTL 自动过期逻辑
- ✅ 模式匹配删除(模拟写操作失效)
- ✅ 缓存统计信息

**运行方式:**

```bash
node test-cache-invalidation-mock.js
```

**优点:**
- 无需 MongoDB 实例
- 运行速度快
- 可以在任何环境中运行

## 预期输出

### test-cache-invalidation.js 预期输出:

```
🚀 开始测试缓存自动失效功能

=== 测试 1: TTL 自动过期 ===
第一次查询: 2 条记录 (缓存 MISS)
第二次查询: 2 条记录 (缓存 HIT)
等待 2.5 秒...
第三次查询: 2 条记录 (缓存 MISS - TTL 过期)
✓ TTL 自动过期测试通过

=== 测试 2: insertOne 自动失效 ===
查询前: 2 条记录 (缓存)
插入新记录: Charlie
查询后: 3 条记录 (缓存已自动失效)
✓ insertOne 自动失效测试通过

=== 测试 3: updateOne 自动失效 ===
更新前: Alice 的 age = 25
更新 Alice 的 age 为 26
更新后: Alice 的 age = 26 (缓存已自动失效)
✓ updateOne 自动失效测试通过

=== 测试 4: deleteOne 自动失效 ===
删除前: 3 条记录
删除 Charlie
删除后: 2 条记录 (缓存已自动失效)
✓ deleteOne 自动失效测试通过

✅ 所有测试通过!
```

### test-cache-invalidation-mock.js 预期输出:

```
🚀 开始测试缓存逻辑(Mock版本)

=== 测试 1: TTL 自动过期 ===
第一次设置缓存 (TTL = 2秒)
立即获取: 缓存 HIT (hits: 1, misses: 0)
等待 2.5 秒...
TTL过期后获取: 缓存 MISS (hits: 1, misses: 1)
✓ TTL 自动过期测试通过

=== 测试 2: 模式匹配删除(写操作失效) ===
设置了 4 个缓存键
缓存状态: users查询1=true, users查询2=true, users查询3=true, other查询=true
执行模式删除 (*collection:users*): 删除了 3 个键
删除后状态: users查询1=false, users查询2=false, users查询3=false, other查询=true
✓ 模式匹配删除测试通过

=== 测试 3: 缓存统计信息 ===
初始统计: hits=0, misses=0, sets=0, deletes=0
最终统计: hits=2, misses=1, sets=2, deletes=1
✓ 缓存统计信息测试通过

✅ 所有缓存逻辑测试通过!

💡 提示:运行完整的集成测试请使用 `node test-cache-invalidation.js`
```

## 技术实现

### TTL 自动过期

monSQLize 的缓存系统使用惰性过期策略:
- 缓存条目存储 `expireAt` 时间戳
- 在 `get()` 操作时检查是否过期
- 过期的条目会被自动删除并返回 `undefined`

### 写操作自动失效

写操作(insertOne、updateOne、deleteOne)会自动失效相关缓存:
1. 写操作完成后,调用 `cache.delPattern()` 删除匹配的缓存键
2. 使用命名空间模式匹配:`*{iid}:{type}:{db}:{collection}*`
3. 删除该集合的所有查询缓存(find、findOne、count 等)

### 缓存统计

缓存实例维护详细的统计信息:
- `hits` - 缓存命中次数
- `misses` - 缓存未命中次数
- `sets` - 设置缓存次数
- `deletes` - 删除缓存次数
- `evictions` - 淘汰次数(LRU)
- `memoryUsage` - 内存使用量(估算)

## 故障排除

### MongoDB Memory Server 下载失败

**错误信息:**
```
Could NOT download "https://fastdl.mongodb.org/..."
getaddrinfo ENOTFOUND fastdl.mongodb.org
```

**解决方案:**
1. 使用本地 MongoDB 实例:
```bash
# 启动本地 MongoDB
mongod

# 运行测试
MONGODB_URI="mongodb://localhost:27017" node test-cache-invalidation.js
```

2. 或者运行 Mock 测试:
```bash
node test-cache-invalidation-mock.js
```

### 测试超时

如果测试因超时失败,可能是因为:
- MongoDB 连接速度慢
- Memory Server 下载速度慢

**解决方案:**
- 使用本地 MongoDB 实例
- 增加超时时间(修改测试代码)

## 相关文档

- [monSQLize 缓存文档](../docs/cache.md)
- [monSQLize API 文档](../README.md)
157 changes: 157 additions & 0 deletions test-cache-invalidation-mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/**
* Mock 版本测试:验证缓存失效逻辑(不需要 MongoDB)
* 此版本用于验证缓存失效的逻辑是否正确实现
*/

const MemoryCache = require('./lib/cache');

/**
* 等待指定的时间(毫秒)
*/
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

async function testCacheLogic() {
console.log('🚀 开始测试缓存逻辑(Mock版本)\n');

// 创建缓存实例
const cache = MemoryCache.createDefault({
maxSize: 1000,
enableStats: true
});

try {
// =====================================================
// 测试 1: TTL 自动过期
// =====================================================
console.log('=== 测试 1: TTL 自动过期 ===');

// 清空缓存
await cache.clear();

// 第一次设置(TTL = 2 秒)
await cache.set('test:key1', { data: 'test data' }, 2000);
console.log('第一次设置缓存 (TTL = 2秒)');

// 立即获取(应该命中)
let value = await cache.get('test:key1');
const stats1 = cache.stats;
console.log(`立即获取: ${value ? '缓存 HIT' : '缓存 MISS'} (hits: ${stats1.hits}, misses: ${stats1.misses})`);

if (!value) {
throw new Error('TTL测试失败:立即获取应该命中缓存');
}

// 等待 2.5 秒让 TTL 过期
console.log('等待 2.5 秒...');
await sleep(2500);

// 再次获取(应该过期)
value = await cache.get('test:key1');
const stats2 = cache.stats;
console.log(`TTL过期后获取: ${value ? '缓存 HIT' : '缓存 MISS'} (hits: ${stats2.hits}, misses: ${stats2.misses})`);

if (value) {
throw new Error('TTL测试失败:过期后不应该命中缓存');
}

console.log('✓ TTL 自动过期测试通过\n');

// =====================================================
// 测试 2: 模式匹配删除(模拟写操作失效)
// =====================================================
console.log('=== 测试 2: 模式匹配删除(写操作失效) ===');

// 清空缓存并设置多个键
await cache.clear();

// 模拟同一集合的不同查询缓存
await cache.set('test:collection:users:find:query1', { count: 2 }, 60000);
await cache.set('test:collection:users:find:query2', { count: 3 }, 60000);
await cache.set('test:collection:users:findOne:query1', { user: 'Alice' }, 60000);
await cache.set('test:collection:other:find:query1', { count: 5 }, 60000);

console.log('设置了 4 个缓存键');

// 验证所有键都存在
let exists1 = await cache.exists('test:collection:users:find:query1');
let exists2 = await cache.exists('test:collection:users:find:query2');
let exists3 = await cache.exists('test:collection:users:findOne:query1');
let exists4 = await cache.exists('test:collection:other:find:query1');

console.log(`缓存状态: users查询1=${exists1}, users查询2=${exists2}, users查询3=${exists3}, other查询=${exists4}`);

// 模拟写操作:删除 users 集合的所有缓存
const deleted = await cache.delPattern('*collection:users*');
console.log(`执行模式删除 (*collection:users*): 删除了 ${deleted} 个键`);

// 验证只有 users 相关的缓存被删除
exists1 = await cache.exists('test:collection:users:find:query1');
exists2 = await cache.exists('test:collection:users:find:query2');
exists3 = await cache.exists('test:collection:users:findOne:query1');
exists4 = await cache.exists('test:collection:other:find:query1');

console.log(`删除后状态: users查询1=${exists1}, users查询2=${exists2}, users查询3=${exists3}, other查询=${exists4}`);

if (exists1 || exists2 || exists3) {
throw new Error('模式匹配删除测试失败:users相关缓存应该被删除');
}

if (!exists4) {
throw new Error('模式匹配删除测试失败:other集合的缓存不应该被删除');
}

console.log('✓ 模式匹配删除测试通过\n');

// =====================================================
// 测试 3: 缓存统计信息
// =====================================================
console.log('=== 测试 3: 缓存统计信息 ===');

// 创建新的缓存实例以获得干净的统计
const testCache = MemoryCache.createDefault({
maxSize: 1000,
enableStats: true
});

const initialStats = testCache.stats;
console.log(`初始统计: hits=${initialStats.hits}, misses=${initialStats.misses}, sets=${initialStats.sets}, deletes=${initialStats.deletes}`);

// 执行一系列操作
await testCache.set('key1', 'value1', 60000); // sets +1
await testCache.set('key2', 'value2', 60000); // sets +1
await testCache.get('key1'); // hits +1
await testCache.get('key1'); // hits +1
await testCache.get('key3'); // misses +1
await testCache.del('key1'); // deletes +1

const finalStats = testCache.stats;
console.log(`最终统计: hits=${finalStats.hits}, misses=${finalStats.misses}, sets=${finalStats.sets}, deletes=${finalStats.deletes}`);

// 验证统计数据(由于是新实例,直接比较绝对值)
if (finalStats.hits !== 2) {
throw new Error(`统计测试失败:期望 hits = 2,实际 = ${finalStats.hits}`);
}
if (finalStats.misses !== 1) {
throw new Error(`统计测试失败:期望 misses = 1,实际 = ${finalStats.misses}`);
}
if (finalStats.sets !== 2) {
throw new Error(`统计测试失败:期望 sets = 2,实际 = ${finalStats.sets}`);
}
if (finalStats.deletes !== 1) {
throw new Error(`统计测试失败:期望 deletes = 1,实际 = ${finalStats.deletes}`);
}

console.log('✓ 缓存统计信息测试通过\n');

console.log('✅ 所有缓存逻辑测试通过!');
console.log('\n💡 提示:运行完整的集成测试请使用 `node test-cache-invalidation.js`');

} catch (error) {
console.error('❌ 测试失败:', error.message);
process.exit(1);
}
}

testCacheLogic();
Loading