kempo-server 1.6.1 → 1.6.3

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.
@@ -3,16 +3,16 @@ import {createMockReq, createMockRes} from './test-utils.js';
3
3
 
4
4
  export default {
5
5
  'runs middleware in order and calls finalHandler': async ({pass, fail}) => {
6
- try {
7
- const mr = new MiddlewareRunner();
8
- const calls = [];
9
- mr.use(async (_req, _res, next) => { calls.push('a'); await next(); calls.push('a:after'); });
10
- mr.use(async (_req, _res, next) => { calls.push('b'); await next(); calls.push('b:after'); });
11
- const req = createMockReq();
12
- const res = createMockRes();
13
- await mr.run(req, res, async () => { calls.push('final'); });
14
- if(calls.join(',') !== 'a,b,final,b:after,a:after') return fail('order incorrect');
15
- pass('middleware order');
16
- } catch(e){ fail(e.message); }
6
+ const mr = new MiddlewareRunner();
7
+ const calls = [];
8
+ mr.use(async (_req, _res, next) => { calls.push('a'); await next(); calls.push('a:after'); });
9
+ mr.use(async (_req, _res, next) => { calls.push('b'); await next(); calls.push('b:after'); });
10
+ const req = createMockReq();
11
+ const res = createMockRes();
12
+ await mr.run(req, res, async () => { calls.push('final'); });
13
+
14
+ if(calls.join(',') !== 'a,b,final,b:after,a:after') return fail('order incorrect');
15
+
16
+ pass('middleware order');
17
17
  }
18
18
  };
@@ -37,31 +37,19 @@ const cleanupTempDir = async (tempDir) => {
37
37
  /*
38
38
  Lifecycle Callbacks
39
39
  */
40
- export const beforeAll = async (log) => {
41
- log('Setting up module cache test environment...');
42
- };
43
40
 
44
- export const afterAll = async (log) => {
45
- log('Starting cleanup of module cache test environment...');
46
-
47
- log(`Found ${activeCaches.length} active caches to destroy`);
41
+ export const afterAll = async () => {
48
42
  // Destroy all active caches to clean up timers and watchers
49
43
  for(let i = 0; i < activeCaches.length; i++) {
50
- log(`Destroying cache ${i + 1}/${activeCaches.length}`);
51
44
  activeCaches[i].destroy();
52
45
  }
53
46
  activeCaches.length = 0;
54
- log('All caches destroyed');
55
47
 
56
48
  // Give file watchers extra time to fully close
57
- log('Waiting for file watchers to close...');
58
49
  await new Promise(resolve => setTimeout(resolve, 500));
59
- log('File watcher wait complete');
60
50
 
61
51
  const tempDir = path.join(process.cwd(), 'tests', 'temp-cache-test');
62
- log('Cleaning up temp directory...');
63
52
  await cleanupTempDir(tempDir);
64
- log('Module cache test cleanup complete');
65
53
  };
66
54
 
67
55
  /*
@@ -69,206 +57,223 @@ export const afterAll = async (log) => {
69
57
  */
70
58
  export default {
71
59
  'basic LRU functionality works correctly': async ({pass, fail, log}) => {
72
- try {
73
- const cache = createTestCache({
74
- maxSize: 2,
75
- maxMemoryMB: 10,
76
- ttlMs: 60000,
77
- watchFiles: false
78
- });
79
-
80
- const mockStats1 = { mtime: new Date('2023-01-01') };
81
- const mockStats2 = { mtime: new Date('2023-01-02') };
82
- const mockStats3 = { mtime: new Date('2023-01-03') };
60
+ const cache = createTestCache({
61
+ maxSize: 2,
62
+ maxMemoryMB: 10,
63
+ ttlMs: 60000,
64
+ watchFiles: false
65
+ });
83
66
 
84
- cache.set('/test1.js', { default: () => 'test1' }, mockStats1, 1);
85
- if(cache.cache.size !== 1) throw new Error('size should be 1');
67
+ const mockStats1 = { mtime: new Date('2023-01-01') };
68
+ const mockStats2 = { mtime: new Date('2023-01-02') };
69
+ const mockStats3 = { mtime: new Date('2023-01-03') };
86
70
 
87
- cache.set('/test2.js', { default: () => 'test2' }, mockStats2, 1);
88
- if(cache.cache.size !== 2) throw new Error('size should be 2');
71
+ cache.set('/test1.js', { default: () => 'test1' }, mockStats1, 1);
72
+ if(cache.cache.size !== 1) {
73
+ cache.destroy();
74
+ return fail('size should be 1');
75
+ }
89
76
 
90
- cache.set('/test3.js', { default: () => 'test3' }, mockStats3, 1);
91
- if(cache.cache.size !== 2) throw new Error('size should still be 2');
92
- if(cache.get('/test1.js', mockStats1) !== null) throw new Error('oldest should be evicted');
77
+ cache.set('/test2.js', { default: () => 'test2' }, mockStats2, 1);
78
+ if(cache.cache.size !== 2) {
79
+ cache.destroy();
80
+ return fail('size should be 2');
81
+ }
93
82
 
83
+ cache.set('/test3.js', { default: () => 'test3' }, mockStats3, 1);
84
+ if(cache.cache.size !== 2) {
85
+ cache.destroy();
86
+ return fail('size should still be 2');
87
+ }
88
+ if(cache.get('/test1.js', mockStats1) !== null) {
94
89
  cache.destroy();
95
- log(' LRU eviction working correctly');
96
- pass('LRU functionality verified');
97
- } catch(e){
98
- cache?.destroy();
99
- fail(e.message);
90
+ return fail('oldest should be evicted');
100
91
  }
92
+
93
+ cache.destroy();
94
+ pass('LRU functionality verified');
101
95
  },
102
96
 
103
97
  'TTL expiration invalidates entries': async ({pass, fail, log}) => {
104
- try {
105
- const cache = createTestCache({
106
- maxSize: 10,
107
- ttlMs: 50,
108
- watchFiles: false
109
- });
98
+ const cache = createTestCache({
99
+ maxSize: 10,
100
+ ttlMs: 50,
101
+ watchFiles: false
102
+ });
110
103
 
111
- const mockStats = { mtime: new Date('2023-01-01') };
112
- cache.set('/test.js', { default: () => 'test' }, mockStats, 1);
113
-
114
- if(cache.get('/test.js', mockStats) === null) throw new Error('should be available immediately');
115
-
116
- await new Promise(resolve => setTimeout(resolve, 100));
117
-
118
- if(cache.get('/test.js', mockStats) !== null) throw new Error('should be expired');
119
-
104
+ const mockStats = { mtime: new Date('2023-01-01') };
105
+ cache.set('/test.js', { default: () => 'test' }, mockStats, 1);
106
+
107
+ if(cache.get('/test.js', mockStats) === null) {
108
+ cache.destroy();
109
+ return fail('should be available immediately');
110
+ }
111
+
112
+ await new Promise(resolve => setTimeout(resolve, 100));
113
+
114
+ if(cache.get('/test.js', mockStats) !== null) {
120
115
  cache.destroy();
121
- log(' TTL expiration working');
122
- pass('TTL expiration verified');
123
- } catch(e){
124
- cache?.destroy();
125
- fail(e.message);
116
+ return fail('should be expired');
126
117
  }
118
+
119
+ cache.destroy();
120
+ pass('TTL expiration verified');
127
121
  },
128
122
 
129
123
  'file modification invalidates cache entry': async ({pass, fail, log}) => {
130
- try {
131
- const cache = createTestCache({
132
- maxSize: 10,
133
- ttlMs: 60000,
134
- watchFiles: false
135
- });
124
+ const cache = createTestCache({
125
+ maxSize: 10,
126
+ ttlMs: 60000,
127
+ watchFiles: false
128
+ });
136
129
 
137
- const oldStats = { mtime: new Date('2023-01-01') };
138
- const newStats = { mtime: new Date('2023-01-02') };
139
-
140
- cache.set('/test.js', { default: () => 'test' }, oldStats, 1);
141
-
142
- if(cache.get('/test.js', oldStats) === null) throw new Error('should be available with old stats');
143
- if(cache.get('/test.js', newStats) !== null) throw new Error('should be invalidated with newer stats');
144
-
130
+ const oldStats = { mtime: new Date('2023-01-01') };
131
+ const newStats = { mtime: new Date('2023-01-02') };
132
+
133
+ cache.set('/test.js', { default: () => 'test' }, oldStats, 1);
134
+
135
+ if(cache.get('/test.js', oldStats) === null) {
136
+ cache.destroy();
137
+ return fail('should be available with old stats');
138
+ }
139
+
140
+ if(cache.get('/test.js', newStats) !== null) {
145
141
  cache.destroy();
146
- log(' File modification detection working');
147
- pass('File modification invalidation verified');
148
- } catch(e){
149
- cache?.destroy();
150
- fail(e.message);
142
+ return fail('should be invalidated with newer stats');
151
143
  }
144
+
145
+ cache.destroy();
146
+ pass('File modification invalidation verified');
152
147
  },
153
148
 
154
149
  'memory limit enforcement evicts oldest entries': async ({pass, fail, log}) => {
155
- try {
156
- const cache = createTestCache({
157
- maxSize: 100,
158
- maxMemoryMB: 0.002,
159
- ttlMs: 60000,
160
- watchFiles: false
161
- });
150
+ const cache = createTestCache({
151
+ maxSize: 100,
152
+ maxMemoryMB: 0.002,
153
+ ttlMs: 60000,
154
+ watchFiles: false
155
+ });
162
156
 
163
- const mockStats = { mtime: new Date('2023-01-01') };
164
-
165
- cache.set('/test1.js', { default: () => 'test1' }, mockStats, 1);
166
- cache.set('/test2.js', { default: () => 'test2' }, mockStats, 1);
167
- cache.set('/test3.js', { default: () => 'test3' }, mockStats, 1);
168
-
169
- if(cache.get('/test1.js', mockStats) !== null) throw new Error('first entry should be evicted due to memory limit');
170
- if(cache.get('/test2.js', mockStats) === null) throw new Error('second entry should still be cached');
171
- if(cache.get('/test3.js', mockStats) === null) throw new Error('third entry should still be cached');
172
-
157
+ const mockStats = { mtime: new Date('2023-01-01') };
158
+
159
+ cache.set('/test1.js', { default: () => 'test1' }, mockStats, 1);
160
+ cache.set('/test2.js', { default: () => 'test2' }, mockStats, 1);
161
+ cache.set('/test3.js', { default: () => 'test3' }, mockStats, 1);
162
+
163
+ if(cache.get('/test1.js', mockStats) !== null) {
164
+ cache.destroy();
165
+ return fail('first entry should be evicted due to memory limit');
166
+ }
167
+
168
+ if(cache.get('/test2.js', mockStats) === null) {
169
+ cache.destroy();
170
+ return fail('second entry should still be cached');
171
+ }
172
+
173
+ if(cache.get('/test3.js', mockStats) === null) {
173
174
  cache.destroy();
174
- log(' Memory limit enforcement working');
175
- pass('Memory limit enforcement verified');
176
- } catch(e){
177
- cache?.destroy();
178
- fail(e.message);
175
+ return fail('third entry should still be cached');
179
176
  }
177
+
178
+ cache.destroy();
179
+ pass('Memory limit enforcement verified');
180
180
  },
181
181
 
182
182
  'statistics tracking works correctly': async ({pass, fail, log}) => {
183
- try {
184
- const cache = createTestCache({
185
- maxSize: 10,
186
- watchFiles: false
187
- });
183
+ const cache = createTestCache({
184
+ maxSize: 10,
185
+ watchFiles: false
186
+ });
188
187
 
189
- const mockStats = { mtime: new Date('2023-01-01') };
190
-
191
- if(cache.get('/test.js', mockStats) !== null) throw new Error('should be cache miss');
192
- if(cache.stats.misses !== 1) throw new Error('miss count should be 1');
193
-
194
- cache.set('/test.js', { default: () => 'test' }, mockStats, 1);
195
- if(cache.get('/test.js', mockStats) === null) throw new Error('should be cache hit');
196
- if(cache.stats.hits !== 1) throw new Error('hit count should be 1');
197
-
198
- if(cache.getHitRate() !== 50) throw new Error('hit rate should be 50%');
199
-
200
- const stats = cache.getStats();
201
- if(!stats.cache || !stats.stats || !stats.memory) throw new Error('stats structure invalid');
202
-
188
+ const mockStats = { mtime: new Date('2023-01-01') };
189
+
190
+ if(cache.get('/test.js', mockStats) !== null) {
191
+ cache.destroy();
192
+ return fail('should be cache miss');
193
+ }
194
+
195
+ if(cache.stats.misses !== 1) {
196
+ cache.destroy();
197
+ return fail('miss count should be 1');
198
+ }
199
+
200
+ cache.set('/test.js', { default: () => 'test' }, mockStats, 1);
201
+
202
+ if(cache.get('/test.js', mockStats) === null) {
203
+ cache.destroy();
204
+ return fail('should be cache hit');
205
+ }
206
+
207
+ if(cache.stats.hits !== 1) {
208
+ cache.destroy();
209
+ return fail('hit count should be 1');
210
+ }
211
+
212
+ if(cache.getHitRate() !== 50) {
203
213
  cache.destroy();
204
- log(' Statistics tracking accurate');
205
- pass('Statistics tracking verified');
206
- } catch(e){
207
- cache?.destroy();
208
- fail(e.message);
214
+ return fail('hit rate should be 50%');
209
215
  }
216
+
217
+ const stats = cache.getStats();
218
+ if(!stats.cache || !stats.stats || !stats.memory) {
219
+ cache.destroy();
220
+ return fail('stats structure invalid');
221
+ }
222
+
223
+ cache.destroy();
224
+ pass('Statistics tracking verified');
210
225
  },
211
226
 
212
227
  'file watching invalidates cache on changes': async ({pass, fail, log}) => {
213
228
  let cache;
214
229
  let testFilePath;
215
- try {
216
- log('Starting file watching test...');
217
- const tempDir = await createTempDir();
218
- log('Created temp directory');
219
-
220
- cache = createTestCache({
221
- maxSize: 10,
222
- ttlMs: 60000,
223
- watchFiles: true
224
- });
225
- log('Created cache with file watching enabled');
230
+
231
+ const tempDir = await createTempDir();
232
+
233
+ cache = createTestCache({
234
+ maxSize: 10,
235
+ ttlMs: 60000,
236
+ watchFiles: true
237
+ });
226
238
 
227
- testFilePath = path.join(tempDir, 'watch-test.js');
228
-
229
- await writeFile(testFilePath, 'export default () => "v1";');
230
- log('Wrote initial test file');
231
- const initialStats = await stat(testFilePath);
232
-
233
- const module = { default: () => 'v1' };
234
- cache.set(testFilePath, module, initialStats, 1);
235
- log('Set initial cache entry');
236
-
237
- if(cache.get(testFilePath, initialStats) === null) throw new Error('should be in cache initially');
238
- log('Verified initial cache entry exists');
239
-
240
- await new Promise(resolve => setTimeout(resolve, 50));
241
- await writeFile(testFilePath, 'export default () => "v2";');
242
- log('Modified test file');
243
-
244
- await new Promise(resolve => setTimeout(resolve, 200));
245
- log('Waited for file watcher to detect changes');
246
-
247
- if(cache.get(testFilePath, initialStats) !== null) throw new Error('should be invalidated after file change');
248
- if(cache.stats.fileChanges === 0) throw new Error('file change should be tracked');
249
- log('Verified cache invalidation worked');
250
-
251
- // Clean up immediately
239
+ testFilePath = path.join(tempDir, 'watch-test.js');
240
+
241
+ await writeFile(testFilePath, 'export default () => "v1";');
242
+ const initialStats = await stat(testFilePath);
243
+
244
+ const module = { default: () => 'v1' };
245
+ cache.set(testFilePath, module, initialStats, 1);
246
+
247
+ if(cache.get(testFilePath, initialStats) === null) {
248
+ await unlink(testFilePath);
249
+ cache.destroy();
250
+ return fail('should be in cache initially');
251
+ }
252
+
253
+ await new Promise(resolve => setTimeout(resolve, 50));
254
+ await writeFile(testFilePath, 'export default () => "v2";');
255
+
256
+ await new Promise(resolve => setTimeout(resolve, 200));
257
+
258
+ if(cache.get(testFilePath, initialStats) !== null) {
259
+ await unlink(testFilePath);
260
+ cache.destroy();
261
+ return fail('should be invalidated after file change');
262
+ }
263
+
264
+ if(cache.stats.fileChanges === 0) {
252
265
  await unlink(testFilePath);
253
- log('Deleted test file');
254
266
  cache.destroy();
255
- log('Destroyed cache');
256
- cache = null; // Ensure it's not destroyed again in catch
257
-
258
- // Give extra time for file watcher cleanup
259
- await new Promise(resolve => setTimeout(resolve, 100));
260
- log('File watching test cleanup complete');
261
-
262
- log('✓ File watching invalidation working');
263
- pass('File watching verified');
264
- } catch(e){
265
- log(`Error in file watching test: ${e.message}`);
266
- if (testFilePath) {
267
- try { await unlink(testFilePath); log('Cleaned up test file after error'); } catch {}
268
- }
269
- cache?.destroy();
270
- log('Destroyed cache after error');
271
- fail(e.message);
267
+ return fail('file change should be tracked');
272
268
  }
269
+
270
+ // Clean up immediately
271
+ await unlink(testFilePath);
272
+ cache.destroy();
273
+
274
+ // Give extra time for file watcher cleanup
275
+ await new Promise(resolve => setTimeout(resolve, 100));
276
+
277
+ pass('File watching verified');
273
278
  }
274
279
  };
@@ -3,32 +3,31 @@ import createRequestWrapper from '../src/requestWrapper.js';
3
3
 
4
4
  export default {
5
5
  'parses query and path and provides params': async ({pass, fail}) => {
6
- try {
7
- const req = createMockReq({url: '/user/123?x=1&y=2', headers: {host: 'localhost'}});
8
- const wrapped = createRequestWrapper(req, {id: '123'});
9
- if(wrapped.path !== '/user/123') return fail('path');
10
- if(!(wrapped.query.x === '1' && wrapped.query.y === '2')) return fail('query');
11
- if(wrapped.params.id !== '123') return fail('params');
12
- pass('parsed url');
13
- } catch(e){ fail(e.message); }
6
+ const req = createMockReq({url: '/user/123?x=1&y=2', headers: {host: 'localhost'}});
7
+ const wrapped = createRequestWrapper(req, {id: '123'});
8
+
9
+ if(wrapped.path !== '/user/123') return fail('path');
10
+ if(!(wrapped.query.x === '1' && wrapped.query.y === '2')) return fail('query');
11
+ if(wrapped.params.id !== '123') return fail('params');
12
+
13
+ pass('parsed url');
14
14
  },
15
15
  'body/json/text/buffer helpers work': async ({pass, fail}) => {
16
- try {
17
- const payload = {a: 1};
18
- // Each body reader must have its own stream instance
19
- const reqText = createMockReq({method: 'POST', url: '/', headers: {host: 'x', 'content-type': 'application/json'}, body: JSON.stringify(payload)});
20
- const text = await createRequestWrapper(reqText).text();
21
- if(text !== JSON.stringify(payload)) return fail('text');
16
+ const payload = {a: 1};
17
+ // Each body reader must have its own stream instance
18
+ const reqText = createMockReq({method: 'POST', url: '/', headers: {host: 'x', 'content-type': 'application/json'}, body: JSON.stringify(payload)});
19
+ const text = await createRequestWrapper(reqText).text();
20
+ if(text !== JSON.stringify(payload)) return fail('text');
22
21
 
23
- const reqJson = createMockReq({method: 'POST', url: '/', headers: {host: 'x', 'content-type': 'application/json'}, body: JSON.stringify(payload)});
24
- const obj = await createRequestWrapper(reqJson).json();
25
- if(obj.a !== 1) return fail('json');
22
+ const reqJson = createMockReq({method: 'POST', url: '/', headers: {host: 'x', 'content-type': 'application/json'}, body: JSON.stringify(payload)});
23
+ const obj = await createRequestWrapper(reqJson).json();
24
+ if(obj.a !== 1) return fail('json');
26
25
 
27
- const reqBuf = createMockReq({url: '/', headers: {host: 'x'}, body: 'abc'});
28
- const buf = await createRequestWrapper(reqBuf).buffer();
29
- if(!(Buffer.isBuffer(buf) && buf.toString() === 'abc')) return fail('buffer');
30
- pass('helpers');
31
- } catch(e){ fail(e.message); }
26
+ const reqBuf = createMockReq({url: '/', headers: {host: 'x'}, body: 'abc'});
27
+ const buf = await createRequestWrapper(reqBuf).buffer();
28
+ if(!(Buffer.isBuffer(buf) && buf.toString() === 'abc')) return fail('buffer');
29
+
30
+ pass('helpers');
32
31
  },
33
32
  'invalid json throws': async ({pass, fail}) => {
34
33
  const req = createMockReq({url: '/', headers: {host: 'x'}, body: 'not json'});
@@ -40,12 +39,12 @@ export default {
40
39
  }
41
40
  },
42
41
  'get and is helpers': async ({pass, fail}) => {
43
- try {
44
- const req = createMockReq({url: '/', headers: {'content-type': 'text/plain', host: 'x'}});
45
- const w = createRequestWrapper(req);
46
- if(w.get('content-type') !== 'text/plain') return fail('get');
47
- if(w.is('text/plain') !== true) return fail('is');
48
- pass('header helpers');
49
- } catch(e){ fail(e.message); }
42
+ const req = createMockReq({url: '/', headers: {'content-type': 'text/plain', host: 'x'}});
43
+ const w = createRequestWrapper(req);
44
+
45
+ if(w.get('content-type') !== 'text/plain') return fail('get');
46
+ if(w.is('text/plain') !== true) return fail('is');
47
+
48
+ pass('header helpers');
50
49
  }
51
50
  };