@jintianxiayu/cache-decorator 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +25 -11
  2. package/README.md +364 -270
  3. package/dist/core/cache-error.d.ts +75 -0
  4. package/dist/core/cache-error.d.ts.map +1 -0
  5. package/dist/core/cache-error.js +168 -0
  6. package/dist/core/cache-error.js.map +1 -0
  7. package/dist/core/cache-logger.d.ts +3 -2
  8. package/dist/core/cache-logger.d.ts.map +1 -1
  9. package/dist/core/cache-logger.js +2 -0
  10. package/dist/core/cache-logger.js.map +1 -1
  11. package/dist/decorators/cache-evict.d.ts.map +1 -1
  12. package/dist/decorators/cache-evict.js +15 -10
  13. package/dist/decorators/cache-evict.js.map +1 -1
  14. package/dist/decorators/cache.d.ts +6 -0
  15. package/dist/decorators/cache.d.ts.map +1 -1
  16. package/dist/decorators/cache.js +113 -36
  17. package/dist/decorators/cache.js.map +1 -1
  18. package/jest.config.js +11 -11
  19. package/package.json +1 -1
  20. package/src/adapters/ioredis-cache-client.ts +89 -89
  21. package/src/adapters/node-redis-cache-client.ts +95 -95
  22. package/src/adapters/redis-key-prefix.ts +38 -38
  23. package/src/core/cache-error.ts +233 -0
  24. package/src/core/cache-logger.ts +116 -105
  25. package/src/core/key-builder.ts +33 -33
  26. package/src/core/native-cache.ts +55 -55
  27. package/src/core/pending-cache.ts +29 -29
  28. package/src/core/redis-cache-client.ts +160 -160
  29. package/src/core/redis-cache.ts +104 -104
  30. package/src/decorators/cache-evict.ts +137 -129
  31. package/src/decorators/cache.ts +323 -203
  32. package/src/index.ts +11 -11
  33. package/test/cache-evict-logging.test.ts +398 -362
  34. package/test/cache-logger.integration.test.ts +159 -129
  35. package/test/cache-logger.test.ts +156 -153
  36. package/test/cache-logging.test.ts +929 -544
  37. package/test/cache.test.ts +1017 -255
  38. package/test/fixtures/cache-logger-child.mjs +108 -108
  39. package/test/fixtures/cache-provider-failure-child.mjs +70 -0
  40. package/test/helpers/legacy-redis-cache.ts +41 -22
  41. package/test/helpers/package-consumer.ts +231 -231
  42. package/test/helpers/redis-fixture.ts +142 -142
  43. package/test/ioredis-cache-client.test.ts +143 -143
  44. package/test/legacy-redis-cache.test.ts +51 -0
  45. package/test/native-cache.test.ts +77 -77
  46. package/test/node-redis-cache-client.test.ts +149 -149
  47. package/test/pending-cache.test.ts +69 -69
  48. package/test/redis-cache-client-lifecycle.test.ts +112 -112
  49. package/test/redis-cache-client-types.test.ts +184 -184
  50. package/test/redis-cache-client.integration.test.ts +355 -327
  51. package/test/redis-cache-decorator.test.ts +601 -201
  52. package/test/redis-cache-provider.test.ts +269 -269
  53. package/test/type-contract/contract.ts +119 -64
  54. package/test/type-contract/tsconfig.json +12 -12
  55. package/tsconfig.json +8 -8
@@ -1,77 +1,77 @@
1
- import { MemoryCacheProvider } from '../src/core/native-cache';
2
-
3
- describe('MemoryCacheProvider', () => {
4
- let provider: MemoryCacheProvider;
5
-
6
- beforeEach(() => {
7
- provider = new MemoryCacheProvider();
8
- });
9
-
10
- afterEach(() => {
11
- jest.useRealTimers();
12
- });
13
-
14
- describe('get & set', () => {
15
- it('应返回已缓存的值', () => {
16
- provider.set('key1', 'value1');
17
- expect(provider.get('key1')).toBe('value1');
18
- });
19
-
20
- it('应返回 undefined 对不存在的键', () => {
21
- expect(provider.get('nonexistent')).toBeUndefined();
22
- });
23
- });
24
-
25
- describe('TTL', () => {
26
- it('应在 TTL 后失效', () => {
27
- jest.useFakeTimers();
28
-
29
- provider.set('key1', 'value1', 5);
30
- expect(provider.get('key1')).toBe('value1');
31
-
32
- jest.advanceTimersByTime(5001);
33
- expect(provider.get('key1')).toBeUndefined();
34
- });
35
-
36
- it('无 TTL 时不应失效', () => {
37
- jest.useFakeTimers();
38
-
39
- provider.set('key1', 'value1');
40
- jest.advanceTimersByTime(24 * 60 * 60 * 1000);
41
-
42
- expect(provider.get('key1')).toBe('value1');
43
- });
44
- });
45
-
46
- describe('delete', () => {
47
- it('应删除指定键', () => {
48
- provider.set('key1', 'value1');
49
- provider.delete('key1');
50
- expect(provider.get('key1')).toBeUndefined();
51
- });
52
- });
53
-
54
- describe('clear', () => {
55
- it('应清除所有缓存', () => {
56
- provider.set('key1', 'value1');
57
- provider.set('key2', 'value2');
58
- provider.clear();
59
- expect(provider.get('key1')).toBeUndefined();
60
- expect(provider.get('key2')).toBeUndefined();
61
- });
62
- });
63
-
64
- describe('deleteByPattern', () => {
65
- it('cache-evict-allentries-prefix/MemoryCacheProvider 实现前缀匹配', () => {
66
- provider.set('user:1', 'user1');
67
- provider.set('user:2', 'user2');
68
- provider.set('order:1', 'order1');
69
-
70
- provider.deleteByPattern('user:*');
71
-
72
- expect(provider.get('user:1')).toBeUndefined();
73
- expect(provider.get('user:2')).toBeUndefined();
74
- expect(provider.get('order:1')).toBe('order1');
75
- });
76
- });
77
- });
1
+ import { MemoryCacheProvider } from '../src/core/native-cache';
2
+
3
+ describe('MemoryCacheProvider', () => {
4
+ let provider: MemoryCacheProvider;
5
+
6
+ beforeEach(() => {
7
+ provider = new MemoryCacheProvider();
8
+ });
9
+
10
+ afterEach(() => {
11
+ jest.useRealTimers();
12
+ });
13
+
14
+ describe('get & set', () => {
15
+ it('应返回已缓存的值', () => {
16
+ provider.set('key1', 'value1');
17
+ expect(provider.get('key1')).toBe('value1');
18
+ });
19
+
20
+ it('应返回 undefined 对不存在的键', () => {
21
+ expect(provider.get('nonexistent')).toBeUndefined();
22
+ });
23
+ });
24
+
25
+ describe('TTL', () => {
26
+ it('应在 TTL 后失效', () => {
27
+ jest.useFakeTimers();
28
+
29
+ provider.set('key1', 'value1', 5);
30
+ expect(provider.get('key1')).toBe('value1');
31
+
32
+ jest.advanceTimersByTime(5001);
33
+ expect(provider.get('key1')).toBeUndefined();
34
+ });
35
+
36
+ it('无 TTL 时不应失效', () => {
37
+ jest.useFakeTimers();
38
+
39
+ provider.set('key1', 'value1');
40
+ jest.advanceTimersByTime(24 * 60 * 60 * 1000);
41
+
42
+ expect(provider.get('key1')).toBe('value1');
43
+ });
44
+ });
45
+
46
+ describe('delete', () => {
47
+ it('应删除指定键', () => {
48
+ provider.set('key1', 'value1');
49
+ provider.delete('key1');
50
+ expect(provider.get('key1')).toBeUndefined();
51
+ });
52
+ });
53
+
54
+ describe('clear', () => {
55
+ it('应清除所有缓存', () => {
56
+ provider.set('key1', 'value1');
57
+ provider.set('key2', 'value2');
58
+ provider.clear();
59
+ expect(provider.get('key1')).toBeUndefined();
60
+ expect(provider.get('key2')).toBeUndefined();
61
+ });
62
+ });
63
+
64
+ describe('deleteByPattern', () => {
65
+ it('cache-evict-allentries-prefix/MemoryCacheProvider 实现前缀匹配', () => {
66
+ provider.set('user:1', 'user1');
67
+ provider.set('user:2', 'user2');
68
+ provider.set('order:1', 'order1');
69
+
70
+ provider.deleteByPattern('user:*');
71
+
72
+ expect(provider.get('user:1')).toBeUndefined();
73
+ expect(provider.get('user:2')).toBeUndefined();
74
+ expect(provider.get('order:1')).toBe('order1');
75
+ });
76
+ });
77
+ });
@@ -1,149 +1,149 @@
1
- import { createNodeRedisCacheClient } from '../src/adapters/node-redis-cache-client';
2
- import { NodeRedisCacheClientSource } from '../src/core/redis-cache-client';
3
-
4
- function createSource(): jest.Mocked<NodeRedisCacheClientSource> {
5
- const source = {
6
- get: jest.fn<Promise<unknown>, Parameters<NodeRedisCacheClientSource['get']>>(),
7
- set: jest.fn<Promise<unknown>, Parameters<NodeRedisCacheClientSource['set']>>(),
8
- setEx: jest.fn<Promise<unknown>, Parameters<NodeRedisCacheClientSource['setEx']>>(),
9
- del: jest.fn<Promise<unknown>, Parameters<NodeRedisCacheClientSource['del']>>(),
10
- scan: jest.fn<Promise<unknown>, Parameters<NodeRedisCacheClientSource['scan']>>(),
11
- flushDb: jest.fn<Promise<unknown>, Parameters<NodeRedisCacheClientSource['flushDb']>>(),
12
- } satisfies jest.Mocked<NodeRedisCacheClientSource>;
13
- return source;
14
- }
15
-
16
- it('redis-cache-client/A03 node-redis 无 TTL 写入成功', async () => {
17
- const source = createSource();
18
- const client = createNodeRedisCacheClient(source, { keyPrefix: 'cache:' });
19
- const request = Object.freeze({ key: 'order:1', value: '{"status":"open"}' });
20
- source.set.mockResolvedValue('OK');
21
-
22
- await expect(client.set(request)).resolves.toBeUndefined();
23
-
24
- expect(source.set).toHaveBeenCalledWith('cache:order:1', request.value);
25
- expect(source.setEx).not.toHaveBeenCalled();
26
- });
27
-
28
- it('redis-cache-client/A04 node-redis 带 TTL 写入成功', async () => {
29
- const source = createSource();
30
- const client = createNodeRedisCacheClient(source, { keyPrefix: 'cache:' });
31
- const request = Object.freeze({ key: 'order:2', value: 'paid', ttlSeconds: 60 });
32
- source.setEx.mockResolvedValue('OK');
33
-
34
- await expect(client.set(request)).resolves.toBeUndefined();
35
-
36
- expect(source.setEx).toHaveBeenCalledWith('cache:order:2', request.ttlSeconds, request.value);
37
- expect(source.set).not.toHaveBeenCalled();
38
- });
39
-
40
- it('redis-cache-client/A05 GET 响应规范化', async () => {
41
- const source = createSource();
42
- const client = createNodeRedisCacheClient(source, { keyPrefix: 'cache:' });
43
- source.get.mockResolvedValueOnce('cached').mockResolvedValueOnce(null);
44
-
45
- await expect(client.get('present')).resolves.toBe('cached');
46
- await expect(client.get('missing')).resolves.toBeNull();
47
-
48
- expect(source.get).toHaveBeenNthCalledWith(1, 'cache:present');
49
- expect(source.get).toHaveBeenNthCalledWith(2, 'cache:missing');
50
- });
51
-
52
- it('redis-cache-client/A06 删除和清库响应规范化', async () => {
53
- const source = createSource();
54
- const client = createNodeRedisCacheClient(source, { keyPrefix: 'cache:' });
55
- source.del.mockResolvedValueOnce(2).mockResolvedValueOnce(0);
56
- source.flushDb.mockResolvedValue('OK');
57
-
58
- await expect(client.deleteMany(['first', 'second'])).resolves.toBeUndefined();
59
- await expect(client.deleteMany(['missing'])).resolves.toBeUndefined();
60
- await expect(client.deleteMany([])).resolves.toBeUndefined();
61
- await expect(client.flushDatabase()).resolves.toBeUndefined();
62
-
63
- expect(source.del).toHaveBeenCalledTimes(2);
64
- expect(source.del).toHaveBeenNthCalledWith(1, ['cache:first', 'cache:second']);
65
- expect(source.del).toHaveBeenNthCalledWith(2, ['cache:missing']);
66
- expect(source.flushDb).toHaveBeenCalledTimes(1);
67
- });
68
-
69
- it('redis-cache-client/A07 调用上下文与请求保持不变', async () => {
70
- const source = createSource();
71
- const options = Object.freeze({ keyPrefix: 'scope:' });
72
- const client = createNodeRedisCacheClient(source, options);
73
- source.set.mockImplementation(function (this: NodeRedisCacheClientSource): Promise<unknown> {
74
- expect(this).toBe(source);
75
- return Promise.resolve('OK');
76
- });
77
- source.del.mockImplementation(function (this: NodeRedisCacheClientSource): Promise<unknown> {
78
- expect(this).toBe(source);
79
- return Promise.resolve(2);
80
- });
81
- source.scan.mockImplementation(function (this: NodeRedisCacheClientSource): Promise<unknown> {
82
- expect(this).toBe(source);
83
- return Promise.resolve({ cursor: '0', keys: ['scope:first', 'scope:second'] });
84
- });
85
- const writeRequest = Object.freeze({ key: 'first', value: 'value' });
86
- const keys = Object.freeze(['first', 'second']);
87
- const scanRequest = Object.freeze({ cursor: '0', pattern: 'first*', count: 100 });
88
-
89
- await client.set(writeRequest);
90
- await client.deleteMany(keys);
91
- await client.scan(scanRequest);
92
-
93
- expect(options).toEqual({ keyPrefix: 'scope:' });
94
- expect(writeRequest).toEqual({ key: 'first', value: 'value' });
95
- expect(keys).toEqual(['first', 'second']);
96
- expect(scanRequest).toEqual({ cursor: '0', pattern: 'first*', count: 100 });
97
- });
98
-
99
- it('redis-cache-client/A08 非标准响应明确失败', async () => {
100
- const source = createSource();
101
- const client = createNodeRedisCacheClient(source);
102
- source.get.mockResolvedValue(Buffer.from('value'));
103
- source.set.mockResolvedValue(true);
104
- source.setEx.mockResolvedValue(undefined);
105
- source.del.mockResolvedValue(1.5);
106
- source.scan.mockResolvedValue(['0', []]);
107
- source.flushDb.mockResolvedValue(null);
108
-
109
- await expect(client.get('key')).rejects.toThrow(TypeError);
110
- await expect(client.set({ key: 'key', value: 'value' })).rejects.toThrow(TypeError);
111
- await expect(client.set({ key: 'key', value: 'value', ttlSeconds: 1 })).rejects.toThrow(TypeError);
112
- await expect(client.deleteMany(['key'])).rejects.toThrow(TypeError);
113
- await expect(client.scan({ cursor: '0', pattern: '*', count: 100 })).rejects.toThrow(TypeError);
114
- await expect(client.flushDatabase()).rejects.toThrow(TypeError);
115
- });
116
-
117
- it('cache-evict-allentries-prefix/F07 node-redis 缺省前缀不改写 key', async () => {
118
- const source = createSource();
119
- const client = createNodeRedisCacheClient(source);
120
- source.scan.mockResolvedValue({ cursor: '0', keys: ['name:1'] });
121
- source.del.mockResolvedValue(1);
122
-
123
- const page = await client.scan({ cursor: '0', pattern: 'name*', count: 100 });
124
- await client.deleteMany(page.keys);
125
-
126
- expect(source.scan).toHaveBeenCalledWith('0', { MATCH: 'name*', COUNT: 100 });
127
- expect(page).toEqual({ cursor: '0', keys: ['name:1'] });
128
- expect(source.del).toHaveBeenCalledWith(['name:1']);
129
- });
130
-
131
- it('cache-evict-allentries-prefix/F08 node-redis 显式前缀访问同一命名空间', async () => {
132
- const source = createSource();
133
- const client = createNodeRedisCacheClient(source, { keyPrefix: 'cache:' });
134
- source.get.mockResolvedValue('value');
135
- source.set.mockResolvedValue('OK');
136
- source.scan.mockResolvedValue({ cursor: '0', keys: ['cache:name:1'] });
137
- source.del.mockResolvedValue(1);
138
-
139
- await expect(client.get('name:1')).resolves.toBe('value');
140
- await client.set({ key: 'name:1', value: 'updated' });
141
- const page = await client.scan({ cursor: '0', pattern: 'name*', count: 100 });
142
- await client.deleteMany(page.keys);
143
-
144
- expect(source.get).toHaveBeenCalledWith('cache:name:1');
145
- expect(source.set).toHaveBeenCalledWith('cache:name:1', 'updated');
146
- expect(source.scan).toHaveBeenCalledWith('0', { MATCH: 'cache:name*', COUNT: 100 });
147
- expect(page).toEqual({ cursor: '0', keys: ['name:1'] });
148
- expect(source.del).toHaveBeenCalledWith(['cache:name:1']);
149
- });
1
+ import { createNodeRedisCacheClient } from '../src/adapters/node-redis-cache-client';
2
+ import { NodeRedisCacheClientSource } from '../src/core/redis-cache-client';
3
+
4
+ function createSource(): jest.Mocked<NodeRedisCacheClientSource> {
5
+ const source = {
6
+ get: jest.fn<Promise<unknown>, Parameters<NodeRedisCacheClientSource['get']>>(),
7
+ set: jest.fn<Promise<unknown>, Parameters<NodeRedisCacheClientSource['set']>>(),
8
+ setEx: jest.fn<Promise<unknown>, Parameters<NodeRedisCacheClientSource['setEx']>>(),
9
+ del: jest.fn<Promise<unknown>, Parameters<NodeRedisCacheClientSource['del']>>(),
10
+ scan: jest.fn<Promise<unknown>, Parameters<NodeRedisCacheClientSource['scan']>>(),
11
+ flushDb: jest.fn<Promise<unknown>, Parameters<NodeRedisCacheClientSource['flushDb']>>(),
12
+ } satisfies jest.Mocked<NodeRedisCacheClientSource>;
13
+ return source;
14
+ }
15
+
16
+ it('redis-cache-client/A03 node-redis 无 TTL 写入成功', async () => {
17
+ const source = createSource();
18
+ const client = createNodeRedisCacheClient(source, { keyPrefix: 'cache:' });
19
+ const request = Object.freeze({ key: 'order:1', value: '{"status":"open"}' });
20
+ source.set.mockResolvedValue('OK');
21
+
22
+ await expect(client.set(request)).resolves.toBeUndefined();
23
+
24
+ expect(source.set).toHaveBeenCalledWith('cache:order:1', request.value);
25
+ expect(source.setEx).not.toHaveBeenCalled();
26
+ });
27
+
28
+ it('redis-cache-client/A04 node-redis 带 TTL 写入成功', async () => {
29
+ const source = createSource();
30
+ const client = createNodeRedisCacheClient(source, { keyPrefix: 'cache:' });
31
+ const request = Object.freeze({ key: 'order:2', value: 'paid', ttlSeconds: 60 });
32
+ source.setEx.mockResolvedValue('OK');
33
+
34
+ await expect(client.set(request)).resolves.toBeUndefined();
35
+
36
+ expect(source.setEx).toHaveBeenCalledWith('cache:order:2', request.ttlSeconds, request.value);
37
+ expect(source.set).not.toHaveBeenCalled();
38
+ });
39
+
40
+ it('redis-cache-client/A05 GET 响应规范化', async () => {
41
+ const source = createSource();
42
+ const client = createNodeRedisCacheClient(source, { keyPrefix: 'cache:' });
43
+ source.get.mockResolvedValueOnce('cached').mockResolvedValueOnce(null);
44
+
45
+ await expect(client.get('present')).resolves.toBe('cached');
46
+ await expect(client.get('missing')).resolves.toBeNull();
47
+
48
+ expect(source.get).toHaveBeenNthCalledWith(1, 'cache:present');
49
+ expect(source.get).toHaveBeenNthCalledWith(2, 'cache:missing');
50
+ });
51
+
52
+ it('redis-cache-client/A06 删除和清库响应规范化', async () => {
53
+ const source = createSource();
54
+ const client = createNodeRedisCacheClient(source, { keyPrefix: 'cache:' });
55
+ source.del.mockResolvedValueOnce(2).mockResolvedValueOnce(0);
56
+ source.flushDb.mockResolvedValue('OK');
57
+
58
+ await expect(client.deleteMany(['first', 'second'])).resolves.toBeUndefined();
59
+ await expect(client.deleteMany(['missing'])).resolves.toBeUndefined();
60
+ await expect(client.deleteMany([])).resolves.toBeUndefined();
61
+ await expect(client.flushDatabase()).resolves.toBeUndefined();
62
+
63
+ expect(source.del).toHaveBeenCalledTimes(2);
64
+ expect(source.del).toHaveBeenNthCalledWith(1, ['cache:first', 'cache:second']);
65
+ expect(source.del).toHaveBeenNthCalledWith(2, ['cache:missing']);
66
+ expect(source.flushDb).toHaveBeenCalledTimes(1);
67
+ });
68
+
69
+ it('redis-cache-client/A07 调用上下文与请求保持不变', async () => {
70
+ const source = createSource();
71
+ const options = Object.freeze({ keyPrefix: 'scope:' });
72
+ const client = createNodeRedisCacheClient(source, options);
73
+ source.set.mockImplementation(function (this: NodeRedisCacheClientSource): Promise<unknown> {
74
+ expect(this).toBe(source);
75
+ return Promise.resolve('OK');
76
+ });
77
+ source.del.mockImplementation(function (this: NodeRedisCacheClientSource): Promise<unknown> {
78
+ expect(this).toBe(source);
79
+ return Promise.resolve(2);
80
+ });
81
+ source.scan.mockImplementation(function (this: NodeRedisCacheClientSource): Promise<unknown> {
82
+ expect(this).toBe(source);
83
+ return Promise.resolve({ cursor: '0', keys: ['scope:first', 'scope:second'] });
84
+ });
85
+ const writeRequest = Object.freeze({ key: 'first', value: 'value' });
86
+ const keys = Object.freeze(['first', 'second']);
87
+ const scanRequest = Object.freeze({ cursor: '0', pattern: 'first*', count: 100 });
88
+
89
+ await client.set(writeRequest);
90
+ await client.deleteMany(keys);
91
+ await client.scan(scanRequest);
92
+
93
+ expect(options).toEqual({ keyPrefix: 'scope:' });
94
+ expect(writeRequest).toEqual({ key: 'first', value: 'value' });
95
+ expect(keys).toEqual(['first', 'second']);
96
+ expect(scanRequest).toEqual({ cursor: '0', pattern: 'first*', count: 100 });
97
+ });
98
+
99
+ it('redis-cache-client/A08 非标准响应明确失败', async () => {
100
+ const source = createSource();
101
+ const client = createNodeRedisCacheClient(source);
102
+ source.get.mockResolvedValue(Buffer.from('value'));
103
+ source.set.mockResolvedValue(true);
104
+ source.setEx.mockResolvedValue(undefined);
105
+ source.del.mockResolvedValue(1.5);
106
+ source.scan.mockResolvedValue(['0', []]);
107
+ source.flushDb.mockResolvedValue(null);
108
+
109
+ await expect(client.get('key')).rejects.toThrow(TypeError);
110
+ await expect(client.set({ key: 'key', value: 'value' })).rejects.toThrow(TypeError);
111
+ await expect(client.set({ key: 'key', value: 'value', ttlSeconds: 1 })).rejects.toThrow(TypeError);
112
+ await expect(client.deleteMany(['key'])).rejects.toThrow(TypeError);
113
+ await expect(client.scan({ cursor: '0', pattern: '*', count: 100 })).rejects.toThrow(TypeError);
114
+ await expect(client.flushDatabase()).rejects.toThrow(TypeError);
115
+ });
116
+
117
+ it('cache-evict-allentries-prefix/F07 node-redis 缺省前缀不改写 key', async () => {
118
+ const source = createSource();
119
+ const client = createNodeRedisCacheClient(source);
120
+ source.scan.mockResolvedValue({ cursor: '0', keys: ['name:1'] });
121
+ source.del.mockResolvedValue(1);
122
+
123
+ const page = await client.scan({ cursor: '0', pattern: 'name*', count: 100 });
124
+ await client.deleteMany(page.keys);
125
+
126
+ expect(source.scan).toHaveBeenCalledWith('0', { MATCH: 'name*', COUNT: 100 });
127
+ expect(page).toEqual({ cursor: '0', keys: ['name:1'] });
128
+ expect(source.del).toHaveBeenCalledWith(['name:1']);
129
+ });
130
+
131
+ it('cache-evict-allentries-prefix/F08 node-redis 显式前缀访问同一命名空间', async () => {
132
+ const source = createSource();
133
+ const client = createNodeRedisCacheClient(source, { keyPrefix: 'cache:' });
134
+ source.get.mockResolvedValue('value');
135
+ source.set.mockResolvedValue('OK');
136
+ source.scan.mockResolvedValue({ cursor: '0', keys: ['cache:name:1'] });
137
+ source.del.mockResolvedValue(1);
138
+
139
+ await expect(client.get('name:1')).resolves.toBe('value');
140
+ await client.set({ key: 'name:1', value: 'updated' });
141
+ const page = await client.scan({ cursor: '0', pattern: 'name*', count: 100 });
142
+ await client.deleteMany(page.keys);
143
+
144
+ expect(source.get).toHaveBeenCalledWith('cache:name:1');
145
+ expect(source.set).toHaveBeenCalledWith('cache:name:1', 'updated');
146
+ expect(source.scan).toHaveBeenCalledWith('0', { MATCH: 'cache:name*', COUNT: 100 });
147
+ expect(page).toEqual({ cursor: '0', keys: ['name:1'] });
148
+ expect(source.del).toHaveBeenCalledWith(['cache:name:1']);
149
+ });
@@ -1,69 +1,69 @@
1
- import { PendingCache } from '../src/core/pending-cache';
2
-
3
- describe('PendingCache', () => {
4
- let pendingCache: PendingCache;
5
-
6
- beforeEach(() => {
7
- pendingCache = new PendingCache();
8
- });
9
-
10
- describe('get & set', () => {
11
- it('应返回已存储的 Promise', async () => {
12
- const promise = Promise.resolve('value');
13
- pendingCache.set('key1', promise);
14
- expect(await pendingCache.get('key1')).toBe('value');
15
- });
16
-
17
- it('应返回 undefined 对不存在的键', () => {
18
- expect(pendingCache.get('nonexistent')).toBeUndefined();
19
- });
20
- });
21
-
22
- describe('请求合并', () => {
23
- it('应在 Promise 完成时自动删除', async () => {
24
- let resolve: (value: string) => void;
25
- const promise = new Promise<string>((r) => {
26
- resolve = r;
27
- });
28
-
29
- pendingCache.set('key1', promise);
30
- expect(pendingCache.get('key1')).toBeDefined();
31
-
32
- resolve!('value');
33
- await promise;
34
- await new Promise((resolve) => setTimeout(resolve, 0));
35
-
36
- expect(pendingCache.get('key1')).toBeUndefined();
37
- });
38
-
39
- it('应在 Promise 拒绝时自动删除且保留原始异常', async () => {
40
- const expectedError = new Error('request failed');
41
- const promise = Promise.reject(expectedError);
42
-
43
- pendingCache.set('key1', promise);
44
-
45
- await expect(promise).rejects.toBe(expectedError);
46
- await Promise.resolve();
47
- expect(pendingCache.get('key1')).toBeUndefined();
48
- });
49
- });
50
-
51
- describe('delete', () => {
52
- it('应删除指定键', async () => {
53
- const promise = Promise.resolve('value');
54
- pendingCache.set('key1', promise);
55
- pendingCache.delete('key1');
56
- expect(pendingCache.get('key1')).toBeUndefined();
57
- });
58
- });
59
-
60
- describe('clear', () => {
61
- it('应清除所有 pending 请求', async () => {
62
- pendingCache.set('key1', Promise.resolve('v1'));
63
- pendingCache.set('key2', Promise.resolve('v2'));
64
- pendingCache.clear();
65
- expect(pendingCache.get('key1')).toBeUndefined();
66
- expect(pendingCache.get('key2')).toBeUndefined();
67
- });
68
- });
69
- });
1
+ import { PendingCache } from '../src/core/pending-cache';
2
+
3
+ describe('PendingCache', () => {
4
+ let pendingCache: PendingCache;
5
+
6
+ beforeEach(() => {
7
+ pendingCache = new PendingCache();
8
+ });
9
+
10
+ describe('get & set', () => {
11
+ it('应返回已存储的 Promise', async () => {
12
+ const promise = Promise.resolve('value');
13
+ pendingCache.set('key1', promise);
14
+ expect(await pendingCache.get('key1')).toBe('value');
15
+ });
16
+
17
+ it('应返回 undefined 对不存在的键', () => {
18
+ expect(pendingCache.get('nonexistent')).toBeUndefined();
19
+ });
20
+ });
21
+
22
+ describe('请求合并', () => {
23
+ it('应在 Promise 完成时自动删除', async () => {
24
+ let resolve: (value: string) => void;
25
+ const promise = new Promise<string>((r) => {
26
+ resolve = r;
27
+ });
28
+
29
+ pendingCache.set('key1', promise);
30
+ expect(pendingCache.get('key1')).toBeDefined();
31
+
32
+ resolve!('value');
33
+ await promise;
34
+ await new Promise((resolve) => setTimeout(resolve, 0));
35
+
36
+ expect(pendingCache.get('key1')).toBeUndefined();
37
+ });
38
+
39
+ it('应在 Promise 拒绝时自动删除且保留原始异常', async () => {
40
+ const expectedError = new Error('request failed');
41
+ const promise = Promise.reject(expectedError);
42
+
43
+ pendingCache.set('key1', promise);
44
+
45
+ await expect(promise).rejects.toBe(expectedError);
46
+ await Promise.resolve();
47
+ expect(pendingCache.get('key1')).toBeUndefined();
48
+ });
49
+ });
50
+
51
+ describe('delete', () => {
52
+ it('应删除指定键', async () => {
53
+ const promise = Promise.resolve('value');
54
+ pendingCache.set('key1', promise);
55
+ pendingCache.delete('key1');
56
+ expect(pendingCache.get('key1')).toBeUndefined();
57
+ });
58
+ });
59
+
60
+ describe('clear', () => {
61
+ it('应清除所有 pending 请求', async () => {
62
+ pendingCache.set('key1', Promise.resolve('v1'));
63
+ pendingCache.set('key2', Promise.resolve('v2'));
64
+ pendingCache.clear();
65
+ expect(pendingCache.get('key1')).toBeUndefined();
66
+ expect(pendingCache.get('key2')).toBeUndefined();
67
+ });
68
+ });
69
+ });