system-config-redis-cache.integration.test.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. import { describe, it, expect, beforeEach } from 'vitest';
  2. import { testClient } from 'hono/testing';
  3. import {
  4. IntegrationTestDatabase,
  5. setupIntegrationDatabaseHooksWithEntities
  6. } from '@d8d/shared-test-util';
  7. import { systemConfigRoutesMt } from '../../src/routes/system-config.routes.mt';
  8. import { SystemConfigMt } from '../../src/entities/system-config.entity.mt';
  9. import { SystemConfigServiceMt } from '../../src/services/system-config.service.mt';
  10. import { UserEntityMt, RoleMt } from '@d8d/core-module-mt/user-module-mt';
  11. import { FileMt } from '@d8d/core-module-mt/file-module-mt';
  12. import { TestDataFactory } from '../utils/integration-test-db';
  13. import { AuthService } from '@d8d/core-module-mt/auth-module-mt';
  14. import { UserServiceMt } from '@d8d/core-module-mt/user-module-mt';
  15. import { redisUtil } from '@d8d/shared-utils';
  16. /**
  17. * 格式化租户系统配置的Redis键(测试用辅助函数)
  18. */
  19. function formatTenantConfigKey(tenantId: number, configKey: string): string {
  20. return `system_config:${tenantId}:${configKey}`;
  21. }
  22. // 设置集成测试钩子
  23. setupIntegrationDatabaseHooksWithEntities([SystemConfigMt, UserEntityMt, RoleMt, FileMt])
  24. describe('系统配置Redis缓存集成测试', () => {
  25. let _client: ReturnType<typeof testClient<typeof systemConfigRoutesMt>>;
  26. let authService: AuthService;
  27. let userService: UserServiceMt;
  28. let systemConfigService: SystemConfigServiceMt;
  29. let _testToken: string;
  30. let testUser: any;
  31. beforeEach(async () => {
  32. // 创建测试客户端
  33. _client = testClient(systemConfigRoutesMt);
  34. // 获取数据源
  35. const dataSource = await IntegrationTestDatabase.getDataSource();
  36. if (!dataSource) throw new Error('Database not initialized');
  37. // 初始化服务
  38. userService = new UserServiceMt(dataSource);
  39. authService = new AuthService(userService);
  40. systemConfigService = new SystemConfigServiceMt(dataSource);
  41. // 创建测试用户并生成token
  42. testUser = await TestDataFactory.createTestUser(dataSource, {
  43. username: 'testuser_rediscache',
  44. password: 'TestPassword123!',
  45. email: 'testuser_rediscache@example.com'
  46. });
  47. // 生成测试用户的token
  48. _testToken = authService.generateToken(testUser);
  49. // 清除测试前的缓存
  50. await redisUtil.clearTenantSystemConfigs(testUser.tenantId);
  51. });
  52. describe('缓存命中测试', () => {
  53. it('应该从缓存中获取配置值', async () => {
  54. // 先创建配置
  55. const _config = await systemConfigService.create({
  56. configKey: 'app.cache.test',
  57. configValue: 'cached-value',
  58. description: '缓存测试配置',
  59. tenantId: testUser.tenantId
  60. } as SystemConfigMt);
  61. // 第一次查询 - 应该从数据库获取并写入缓存
  62. const firstResult = await systemConfigService.getConfigByKey('app.cache.test', testUser.tenantId);
  63. expect(firstResult).toBe('cached-value');
  64. // 验证缓存已写入
  65. const cachedValue = await redisUtil.getSystemConfig(formatTenantConfigKey(testUser.tenantId, 'app.cache.test'));
  66. expect(cachedValue).toBe('cached-value');
  67. // 第二次查询 - 应该从缓存获取
  68. const secondResult = await systemConfigService.getConfigByKey('app.cache.test', testUser.tenantId);
  69. expect(secondResult).toBe('cached-value');
  70. });
  71. it('应该批量从缓存中获取配置值', async () => {
  72. // 创建多个配置
  73. await systemConfigService.create({
  74. configKey: 'app.feature1.enabled',
  75. configValue: 'true',
  76. tenantId: testUser.tenantId
  77. } as SystemConfigMt);
  78. await systemConfigService.create({
  79. configKey: 'app.feature2.enabled',
  80. configValue: 'false',
  81. tenantId: testUser.tenantId
  82. } as SystemConfigMt);
  83. // 第一次批量查询 - 应该从数据库获取并写入缓存
  84. const firstResult = await systemConfigService.getConfigsByKeys(
  85. ['app.feature1.enabled', 'app.feature2.enabled'],
  86. testUser.tenantId
  87. );
  88. expect(firstResult['app.feature1.enabled']).toBe('true');
  89. expect(firstResult['app.feature2.enabled']).toBe('false');
  90. // 验证缓存已写入
  91. const cacheKeys = ['app.feature1.enabled', 'app.feature2.enabled'].map(key => formatTenantConfigKey(testUser.tenantId, key));
  92. const cachedValues = await redisUtil.getSystemConfigs(cacheKeys);
  93. expect(cachedValues[cacheKeys[0]]).toBe('true');
  94. expect(cachedValues[cacheKeys[1]]).toBe('false');
  95. // 第二次批量查询 - 应该从缓存获取
  96. const secondResult = await systemConfigService.getConfigsByKeys(
  97. ['app.feature1.enabled', 'app.feature2.enabled'],
  98. testUser.tenantId
  99. );
  100. expect(secondResult['app.feature1.enabled']).toBe('true');
  101. expect(secondResult['app.feature2.enabled']).toBe('false');
  102. });
  103. });
  104. describe('缓存失效测试', () => {
  105. it('应该在配置更新时清除缓存', async () => {
  106. // 创建配置
  107. const _config = await systemConfigService.create({
  108. configKey: 'app.update.test',
  109. configValue: 'initial-value',
  110. tenantId: testUser.tenantId
  111. } as SystemConfigMt);
  112. // 查询一次以填充缓存
  113. await systemConfigService.getConfigByKey('app.update.test', testUser.tenantId);
  114. // 验证缓存已写入
  115. let cachedValue = await redisUtil.getSystemConfig(formatTenantConfigKey(testUser.tenantId, 'app.update.test'));
  116. expect(cachedValue).toBe('initial-value');
  117. // 更新配置
  118. await systemConfigService.setConfig('app.update.test', 'updated-value', testUser.tenantId, '更新后的描述');
  119. // 验证缓存已清除
  120. cachedValue = await redisUtil.getSystemConfig(formatTenantConfigKey(testUser.tenantId, 'app.update.test'));
  121. expect(cachedValue).toBeNull();
  122. // 再次查询应该从数据库获取新值
  123. const result = await systemConfigService.getConfigByKey('app.update.test', testUser.tenantId);
  124. expect(result).toBe('updated-value');
  125. });
  126. it('应该在配置删除时清除缓存', async () => {
  127. // 创建配置
  128. const _config = await systemConfigService.create({
  129. configKey: 'app.delete.test',
  130. configValue: 'to-be-deleted',
  131. tenantId: testUser.tenantId
  132. } as SystemConfigMt);
  133. // 查询一次以填充缓存
  134. await systemConfigService.getConfigByKey('app.delete.test', testUser.tenantId);
  135. // 验证缓存已写入
  136. let cachedValue = await redisUtil.getSystemConfig(formatTenantConfigKey(testUser.tenantId, 'app.delete.test'));
  137. expect(cachedValue).toBe('to-be-deleted');
  138. // 删除配置
  139. await systemConfigService.deleteConfig('app.delete.test', testUser.tenantId);
  140. // 验证缓存已清除
  141. cachedValue = await redisUtil.getSystemConfig(formatTenantConfigKey(testUser.tenantId, 'app.delete.test'));
  142. expect(cachedValue).toBeNull();
  143. });
  144. });
  145. describe('缓存穿透保护测试', () => {
  146. it('应该防止缓存穿透攻击', async () => {
  147. const nonExistentKey = 'app.nonexistent.config';
  148. // 第一次查询不存在的配置
  149. const firstResult = await systemConfigService.getConfigByKey(nonExistentKey, testUser.tenantId);
  150. expect(firstResult).toBeNull();
  151. // 验证空值缓存已设置
  152. const cachedValue = await redisUtil.getSystemConfig(formatTenantConfigKey(testUser.tenantId, nonExistentKey));
  153. expect(redisUtil.isNullValue(cachedValue)).toBe(true);
  154. // 第二次查询应该从空值缓存返回null
  155. const secondResult = await systemConfigService.getConfigByKey(nonExistentKey, testUser.tenantId);
  156. expect(secondResult).toBeNull();
  157. });
  158. it('应该在批量查询中防止缓存穿透', async () => {
  159. const existentKey = 'app.existent.config';
  160. const nonExistentKey = 'app.nonexistent.config';
  161. // 创建一个存在的配置
  162. await systemConfigService.create({
  163. configKey: existentKey,
  164. configValue: 'existent-value',
  165. tenantId: testUser.tenantId
  166. } as SystemConfigMt);
  167. // 批量查询包含存在和不存在的配置
  168. const result = await systemConfigService.getConfigsByKeys(
  169. [existentKey, nonExistentKey],
  170. testUser.tenantId
  171. );
  172. expect(result[existentKey]).toBe('existent-value');
  173. expect(result[nonExistentKey]).toBeUndefined();
  174. // 验证空值缓存已设置
  175. const cacheKeys = [existentKey, nonExistentKey].map(key => formatTenantConfigKey(testUser.tenantId, key));
  176. const cachedValues = await redisUtil.getSystemConfigs(cacheKeys);
  177. expect(cachedValues[cacheKeys[0]]).toBe('existent-value');
  178. expect(redisUtil.isNullValue(cachedValues[cacheKeys[1]])).toBe(true);
  179. });
  180. });
  181. describe('多租户缓存隔离测试', () => {
  182. let _tenant1User: any;
  183. let _tenant2User: any;
  184. beforeEach(async () => {
  185. const dataSource = await IntegrationTestDatabase.getDataSource();
  186. if (!dataSource) throw new Error('Database not initialized');
  187. // 创建租户1的用户
  188. _tenant1User = await TestDataFactory.createTestUser(dataSource, {
  189. username: 'tenant1_user_cache',
  190. password: 'TestPassword123!',
  191. email: 'tenant1_cache@example.com',
  192. tenantId: 1
  193. });
  194. // 创建租户2的用户
  195. _tenant2User = await TestDataFactory.createTestUser(dataSource, {
  196. username: 'tenant2_user_cache',
  197. password: 'TestPassword123!',
  198. email: 'tenant2_cache@example.com',
  199. tenantId: 2
  200. });
  201. // 清除测试前的缓存
  202. await redisUtil.clearTenantSystemConfigs(1);
  203. await redisUtil.clearTenantSystemConfigs(2);
  204. });
  205. it('应该按租户隔离缓存', async () => {
  206. const sharedConfigKey = 'app.shared.config';
  207. // 为租户1创建配置
  208. await systemConfigService.create({
  209. configKey: sharedConfigKey,
  210. configValue: 'tenant1-value',
  211. tenantId: 1
  212. } as SystemConfigMt);
  213. // 为租户2创建配置
  214. await systemConfigService.create({
  215. configKey: sharedConfigKey,
  216. configValue: 'tenant2-value',
  217. tenantId: 2
  218. } as SystemConfigMt);
  219. // 查询租户1的配置
  220. const tenant1Result = await systemConfigService.getConfigByKey(sharedConfigKey, 1);
  221. expect(tenant1Result).toBe('tenant1-value');
  222. // 查询租户2的配置
  223. const tenant2Result = await systemConfigService.getConfigByKey(sharedConfigKey, 2);
  224. expect(tenant2Result).toBe('tenant2-value');
  225. // 验证缓存隔离
  226. const tenant1Cached = await redisUtil.getSystemConfig(formatTenantConfigKey(1, sharedConfigKey));
  227. const tenant2Cached = await redisUtil.getSystemConfig(formatTenantConfigKey(2, sharedConfigKey));
  228. expect(tenant1Cached).toBe('tenant1-value');
  229. expect(tenant2Cached).toBe('tenant2-value');
  230. });
  231. });
  232. describe('缓存预热测试', () => {
  233. it('应该成功预热缓存', async () => {
  234. // 创建一些常用配置
  235. await systemConfigService.create({
  236. configKey: 'app.login.enabled',
  237. configValue: 'true',
  238. tenantId: testUser.tenantId
  239. } as SystemConfigMt);
  240. await systemConfigService.create({
  241. configKey: 'app.payment.enabled',
  242. configValue: 'false',
  243. tenantId: testUser.tenantId
  244. } as SystemConfigMt);
  245. // 预热缓存
  246. await systemConfigService.warmUpCache(testUser.tenantId);
  247. // 验证缓存已预热
  248. const warmupKeys = ['app.login.enabled', 'app.payment.enabled', 'app.notification.enabled'];
  249. const cacheKeys = warmupKeys.map(key => formatTenantConfigKey(testUser.tenantId, key));
  250. const cachedValues = await redisUtil.getSystemConfigs(cacheKeys);
  251. expect(cachedValues[cacheKeys[0]]).toBe('true');
  252. expect(cachedValues[cacheKeys[1]]).toBe('false');
  253. expect(redisUtil.isNullValue(cachedValues[cacheKeys[2]])).toBe(true);
  254. });
  255. });
  256. });