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

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