kempo-server 1.6.1 → 1.6.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kempo-server",
3
3
  "type": "module",
4
- "version": "1.6.1",
4
+ "version": "1.6.2",
5
5
  "description": "A lightweight, zero-dependency, file based routing server.",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
@@ -3,15 +3,15 @@ import {createMockReq, createMockRes} from './test-utils.js';
3
3
 
4
4
  export default {
5
5
  'cors origin array and non-OPTIONS continues to next': async ({pass, fail}) => {
6
- try {
7
- const res = createMockRes();
8
- const mw = corsMiddleware({origin: ['http://a','http://b'], methods: ['GET'], headers: ['X']});
9
- const req = createMockReq({method: 'GET', headers: {origin: 'http://b'}});
10
- let called = false;
11
- await mw(req, res, async () => { called = true; });
12
- if(!called) return fail('next not called');
13
- if(res.getHeader('Access-Control-Allow-Origin') !== 'http://b') return fail('allowed origin');
14
- pass('cors array');
15
- } catch(e){ fail(e.message); }
6
+ const res = createMockRes();
7
+ const mw = corsMiddleware({origin: ['http://a','http://b'], methods: ['GET'], headers: ['X']});
8
+ const req = createMockReq({method: 'GET', headers: {origin: 'http://b'}});
9
+ let called = false;
10
+ await mw(req, res, async () => { called = true; });
11
+
12
+ if(!called) return fail('next not called');
13
+ if(res.getHeader('Access-Control-Allow-Origin') !== 'http://b') return fail('allowed origin');
14
+
15
+ pass('cors array');
16
16
  }
17
17
  };
@@ -4,71 +4,70 @@ import {corsMiddleware, compressionMiddleware, rateLimitMiddleware, securityMidd
4
4
 
5
5
  export default {
6
6
  'cors middleware sets headers and handles OPTIONS': async ({pass, fail}) => {
7
- try {
8
- const res = createMockRes();
9
- const mw = corsMiddleware({origin: '*', methods: ['GET'], headers: ['X']});
10
- const req = createMockReq({method: 'OPTIONS', headers: {origin: 'http://x'}});
11
- await mw(req, res, async () => {});
12
- if(!res.isEnded()) return fail('preflight should end');
13
- if(!(res.getHeader('Access-Control-Allow-Origin') === 'http://x' || res.getHeader('Access-Control-Allow-Origin') === '*')) return fail('origin header');
14
- pass('cors');
15
- } catch(e){ fail(e.message); }
7
+ const res = createMockRes();
8
+ const mw = corsMiddleware({origin: '*', methods: ['GET'], headers: ['X']});
9
+ const req = createMockReq({method: 'OPTIONS', headers: {origin: 'http://x'}});
10
+ await mw(req, res, async () => {});
11
+
12
+ if(!res.isEnded()) return fail('preflight should end');
13
+ if(!(res.getHeader('Access-Control-Allow-Origin') === 'http://x' || res.getHeader('Access-Control-Allow-Origin') === '*')) return fail('origin header');
14
+
15
+ pass('cors');
16
16
  },
17
17
  'compression middleware gzips when threshold met and client accepts': async ({pass, fail}) => {
18
- try {
19
- const res = createMockRes();
20
- const mw = compressionMiddleware({threshold: 1024});
21
- const req = createMockReq({headers: {'accept-encoding': 'gzip'}});
22
- // simulate next writing large body
23
- await mw(req, res, async () => {
24
- res.write(bigString(5000));
25
- res.end();
26
- });
27
- await new Promise(r => setTimeout(r, 10));
28
- const body = res.getBody();
29
- const original = Buffer.from(bigString(5000));
30
- const gzLen = await gzipSize(original);
31
- // If gzipped is smaller, we expect gzip header. Otherwise, implementation may send uncompressed.
32
- if(gzLen < original.length){
33
- if(res.getHeader('Content-Encoding') !== 'gzip') return fail('should gzip when beneficial');
34
- }
35
- if(body.length <= 0) return fail('has body');
36
- pass('compression');
37
- } catch(e){ fail(e.message); }
18
+ const res = createMockRes();
19
+ const mw = compressionMiddleware({threshold: 1024});
20
+ const req = createMockReq({headers: {'accept-encoding': 'gzip'}});
21
+ // simulate next writing large body
22
+ await mw(req, res, async () => {
23
+ res.write(bigString(5000));
24
+ res.end();
25
+ });
26
+ await new Promise(r => setTimeout(r, 10));
27
+ const body = res.getBody();
28
+ const original = Buffer.from(bigString(5000));
29
+ const gzLen = await gzipSize(original);
30
+ // If gzipped is smaller, we expect gzip header. Otherwise, implementation may send uncompressed.
31
+ if(gzLen < original.length){
32
+ if(res.getHeader('Content-Encoding') !== 'gzip') return fail('should gzip when beneficial');
33
+ }
34
+ if(body.length <= 0) return fail('has body');
35
+
36
+ pass('compression');
38
37
  },
39
38
  'rate limit returns 429 after maxRequests': async ({pass, fail}) => {
40
- try {
41
- const cfg = {maxRequests: 2, windowMs: 1000, message: 'Too many'};
42
- const mw = rateLimitMiddleware(cfg);
43
- const req = createMockReq();
44
- const res1 = createMockRes();
45
- await mw(req, res1, async () => {});
46
- const res2 = createMockRes();
47
- await mw(req, res2, async () => {});
48
- const res3 = createMockRes();
49
- await mw(req, res3, async () => {});
50
- if(res3.statusCode !== 429) return fail('should rate limit');
51
- pass('rateLimit');
52
- } catch(e){ fail(e.message); }
39
+ const cfg = {maxRequests: 2, windowMs: 1000, message: 'Too many'};
40
+ const mw = rateLimitMiddleware(cfg);
41
+ const req = createMockReq();
42
+ const res1 = createMockRes();
43
+ await mw(req, res1, async () => {});
44
+ const res2 = createMockRes();
45
+ await mw(req, res2, async () => {});
46
+ const res3 = createMockRes();
47
+ await mw(req, res3, async () => {});
48
+
49
+ if(res3.statusCode !== 429) return fail('should rate limit');
50
+
51
+ pass('rateLimit');
53
52
  },
54
53
  'security middleware sets headers': async ({pass, fail}) => {
55
- try {
56
- const res = createMockRes();
57
- const mw = securityMiddleware({headers: {'X-Test': '1'}});
58
- await mw(createMockReq(), res, async () => {});
59
- if(res.getHeader('X-Test') !== '1') return fail('header set');
60
- pass('security');
61
- } catch(e){ fail(e.message); }
54
+ const res = createMockRes();
55
+ const mw = securityMiddleware({headers: {'X-Test': '1'}});
56
+ await mw(createMockReq(), res, async () => {});
57
+
58
+ if(res.getHeader('X-Test') !== '1') return fail('header set');
59
+
60
+ pass('security');
62
61
  },
63
62
  'logging middleware logs after response end': async ({pass, fail}) => {
64
- try {
65
- const logs = [];
66
- const logger = (m) => logs.push(String(m));
67
- const mw = loggingMiddleware({includeUserAgent: true, includeResponseTime: true}, logger);
68
- const res = createMockRes();
69
- await mw(createMockReq({headers: {'user-agent': 'UA'}}), res, async () => { res.end('x'); });
70
- if(!(logs.length === 1 && logs[0].includes('GET /') && logs[0].includes('UA'))) return fail('logged');
71
- pass('logging');
72
- } catch(e){ fail(e.message); }
63
+ const logs = [];
64
+ const logger = (m) => logs.push(String(m));
65
+ const mw = loggingMiddleware({includeUserAgent: true, includeResponseTime: true}, logger);
66
+ const res = createMockRes();
67
+ await mw(createMockReq({headers: {'user-agent': 'UA'}}), res, async () => { res.end('x'); });
68
+
69
+ if(!(logs.length === 1 && logs[0].includes('GET /') && logs[0].includes('UA'))) return fail('logged');
70
+
71
+ pass('logging');
73
72
  }
74
73
  };
@@ -26,8 +26,7 @@ const cleanupTempConfig = async (tempDir) => {
26
26
  /*
27
27
  Lifecycle Callbacks
28
28
  */
29
- export const afterAll = async (log) => {
30
- log('Cleaning up cache configuration test environment...');
29
+ export const afterAll = async () => {
31
30
  const tempDir = path.join(process.cwd(), 'tests', 'temp-config-test');
32
31
  await cleanupTempConfig(tempDir);
33
32
  };
@@ -36,72 +35,83 @@ export const afterAll = async (log) => {
36
35
  Cache Configuration Tests
37
36
  */
38
37
  export default {
39
- 'default cache configuration includes all required fields': async ({pass, fail, log}) => {
40
- try {
41
- const cache = defaultConfig.cache;
42
-
43
- if(!cache) throw new Error('cache config missing from defaults');
44
- if(typeof cache.enabled !== 'boolean') throw new Error('enabled should be boolean');
45
- if(typeof cache.maxSize !== 'number') throw new Error('maxSize should be number');
46
- if(typeof cache.maxMemoryMB !== 'number') throw new Error('maxMemoryMB should be number');
47
- if(typeof cache.ttlMs !== 'number') throw new Error('ttlMs should be number');
48
- if(typeof cache.maxHeapUsagePercent !== 'number') throw new Error('maxHeapUsagePercent should be number');
49
- if(typeof cache.memoryCheckInterval !== 'number') throw new Error('memoryCheckInterval should be number');
50
- if(typeof cache.watchFiles !== 'boolean') throw new Error('watchFiles should be boolean');
51
- if(typeof cache.enableMemoryMonitoring !== 'boolean') throw new Error('enableMemoryMonitoring should be boolean');
52
-
53
- log('✓ Default cache configuration structure valid');
54
- pass('Default cache configuration verified');
55
- } catch(e){ fail(e.message); }
38
+ 'default cache configuration includes all required fields': async ({pass, fail}) => {
39
+ const cache = defaultConfig.cache;
40
+
41
+ if(!cache) return fail('cache config missing from defaults');
42
+ if(typeof cache.enabled !== 'boolean') return fail('enabled should be boolean');
43
+ if(typeof cache.maxSize !== 'number') return fail('maxSize should be number');
44
+ if(typeof cache.maxMemoryMB !== 'number') return fail('maxMemoryMB should be number');
45
+ if(typeof cache.ttlMs !== 'number') return fail('ttlMs should be number');
46
+ if(typeof cache.maxHeapUsagePercent !== 'number') return fail('maxHeapUsagePercent should be number');
47
+ if(typeof cache.memoryCheckInterval !== 'number') return fail('memoryCheckInterval should be number');
48
+ if(typeof cache.watchFiles !== 'boolean') return fail('watchFiles should be boolean');
49
+ if(typeof cache.enableMemoryMonitoring !== 'boolean') return fail('enableMemoryMonitoring should be boolean');
50
+
51
+ pass('Default cache configuration verified');
56
52
  },
57
53
 
58
- 'cache can be completely disabled': async ({pass, fail, log}) => {
59
- try {
60
- const { tempDir, configPath } = await createTempConfig({
61
- cache: { enabled: false }
62
- });
63
-
64
- // Import router to test config loading
65
- const router = await import('../src/router.js');
66
- const flags = { root: 'docs', config: configPath };
67
-
68
- const handler = await router.default(flags, () => {}); // Empty log function
69
-
70
- if(handler.moduleCache !== null) throw new Error('cache should be null when disabled');
71
-
54
+ 'cache can be completely disabled': async ({pass, fail}) => {
55
+ const { tempDir, configPath } = await createTempConfig({
56
+ cache: { enabled: false }
57
+ });
58
+
59
+ // Import router to test config loading
60
+ const router = await import('../src/router.js');
61
+ const flags = { root: 'docs', config: configPath };
62
+
63
+ const handler = await router.default(flags, () => {}); // Empty log function
64
+
65
+ if(handler.moduleCache !== null) {
72
66
  await cleanupTempConfig(tempDir);
73
- log(' Cache properly disabled via configuration');
74
- pass('Cache disable configuration verified');
75
- } catch(e){ fail(e.message); }
67
+ return fail('cache should be null when disabled');
68
+ }
69
+
70
+ await cleanupTempConfig(tempDir);
71
+ pass('Cache disable configuration verified');
76
72
  },
77
73
 
78
- 'custom cache limits are applied correctly': async ({pass, fail, log}) => {
79
- try {
80
- const { tempDir, configPath } = await createTempConfig({
81
- cache: {
82
- enabled: true,
83
- maxSize: 25,
84
- maxMemoryMB: 10,
85
- ttlMs: 120000
86
- }
87
- });
88
-
89
- const router = await import('../src/router.js');
90
- const flags = { root: 'docs', config: configPath };
91
-
92
- const handler = await router.default(flags, () => {});
93
-
94
- if(!handler.moduleCache) throw new Error('cache should be enabled');
95
-
96
- const stats = handler.getStats();
97
- if(stats.cache.maxSize !== 25) throw new Error('maxSize not applied');
98
- if(stats.cache.maxMemoryMB !== 10) throw new Error('maxMemoryMB not applied');
99
- if(stats.config.ttlMs !== 120000) throw new Error('ttlMs not applied');
100
-
74
+ 'custom cache limits are applied correctly': async ({pass, fail}) => {
75
+ const { tempDir, configPath } = await createTempConfig({
76
+ cache: {
77
+ enabled: true,
78
+ maxSize: 25,
79
+ maxMemoryMB: 10,
80
+ ttlMs: 120000
81
+ }
82
+ });
83
+
84
+ const router = await import('../src/router.js');
85
+ const flags = { root: 'docs', config: configPath };
86
+
87
+ const handler = await router.default(flags, () => {});
88
+
89
+ if(!handler.moduleCache) {
90
+ await cleanupTempConfig(tempDir);
91
+ return fail('cache should be enabled');
92
+ }
93
+
94
+ const stats = handler.getStats();
95
+ if(stats.cache.maxSize !== 25) {
96
+ handler.moduleCache.destroy();
97
+ await cleanupTempConfig(tempDir);
98
+ return fail('maxSize not applied');
99
+ }
100
+
101
+ if(stats.cache.maxMemoryMB !== 10) {
102
+ handler.moduleCache.destroy();
103
+ await cleanupTempConfig(tempDir);
104
+ return fail('maxMemoryMB not applied');
105
+ }
106
+
107
+ if(stats.config.ttlMs !== 120000) {
101
108
  handler.moduleCache.destroy();
102
109
  await cleanupTempConfig(tempDir);
103
- log(' Custom cache limits applied correctly');
104
- pass('Custom cache configuration verified');
105
- } catch(e){ fail(e.message); }
110
+ return fail('ttlMs not applied');
111
+ }
112
+
113
+ handler.moduleCache.destroy();
114
+ await cleanupTempConfig(tempDir);
115
+ pass('Custom cache configuration verified');
106
116
  }
107
117
  };
@@ -2,12 +2,11 @@ import defaultConfig from '../src/defaultConfig.js';
2
2
 
3
3
  export default {
4
4
  'defaultConfig contains required fields and types': async ({pass, fail}) => {
5
- try {
6
- if(typeof defaultConfig !== 'object') throw new Error('not object');
7
- if(!defaultConfig.allowedMimes || !defaultConfig.disallowedRegex) throw new Error('missing keys');
8
- if(!defaultConfig.routeFiles.includes('GET.js')) throw new Error('routeFiles missing GET.js');
9
- if(!defaultConfig.middleware || typeof defaultConfig.middleware !== 'object') throw new Error('middleware missing');
10
- pass('shape ok');
11
- } catch(e){ fail(e.message); }
5
+ if(typeof defaultConfig !== 'object') return fail('not object');
6
+ if(!defaultConfig.allowedMimes || !defaultConfig.disallowedRegex) return fail('missing keys');
7
+ if(!defaultConfig.routeFiles.includes('GET.js')) return fail('routeFiles missing GET.js');
8
+ if(!defaultConfig.middleware || typeof defaultConfig.middleware !== 'object') return fail('middleware missing');
9
+
10
+ pass('shape ok');
12
11
  }
13
12
  };
@@ -8,24 +8,23 @@ const {default: authMiddleware} = await import(url.pathToFileURL(examplePath));
8
8
 
9
9
  export default {
10
10
  'blocks when API key missing and allows when present': async ({pass, fail}) => {
11
- try {
12
- await setEnv({API_KEY: 'abc'}, async () => {
13
- const res1 = createMockRes();
14
- await authMiddleware(createMockReq({url:'/private'}), res1, async ()=>{});
15
- if(res1.statusCode !== 401) return fail('should 401 without key');
11
+ await setEnv({API_KEY: 'abc'}, async () => {
12
+ const res1 = createMockRes();
13
+ await authMiddleware(createMockReq({url:'/private'}), res1, async ()=>{});
14
+ if(res1.statusCode !== 401) return fail('should 401 without key');
16
15
 
17
- const res2 = createMockRes();
18
- const req2 = createMockReq({headers: {'x-api-key': 'abc'}, url:'/private'});
19
- let called = false;
20
- await authMiddleware(req2, res2, async ()=>{ called = true; });
21
- if(!called) return fail('should call next');
22
- if(!(req2.user && req2.user.authenticated)) return fail('user attached');
16
+ const res2 = createMockRes();
17
+ const req2 = createMockReq({headers: {'x-api-key': 'abc'}, url:'/private'});
18
+ let called = false;
19
+ await authMiddleware(req2, res2, async ()=>{ called = true; });
20
+ if(!called) return fail('should call next');
21
+ if(!(req2.user && req2.user.authenticated)) return fail('user attached');
23
22
 
24
- const res3 = createMockRes();
25
- await authMiddleware(createMockReq({url:'/public/file'}), res3, async ()=>{});
26
- if(res3.isEnded() === true) return fail('public should not end');
27
- });
28
- pass('auth middleware');
29
- } catch(e){ fail(e.message); }
23
+ const res3 = createMockRes();
24
+ await authMiddleware(createMockReq({url:'/public/file'}), res3, async ()=>{});
25
+ if(res3.isEnded() === true) return fail('public should not end');
26
+ });
27
+
28
+ pass('auth middleware');
30
29
  }
31
30
  };
@@ -4,43 +4,43 @@ import path from 'path';
4
4
  const toAbs = (root, p) => path.join(root, p);
5
5
 
6
6
  export default {
7
- 'exact match returns file': async ({pass, fail, log}) => {
8
- try {
9
- const root = path.join(process.cwd(), 'tmp-root');
10
- const files = [toAbs(root, 'a/b/GET.js')];
11
- const [file, params] = await findFile(files, root, '/a/b/GET.js', 'GET', log);
12
- if(file !== files[0]) throw new Error('not exact');
13
- if(Object.keys(params).length !== 0) throw new Error('params present');
14
- pass('exact');
15
- } catch(e){ fail(e.message); }
7
+ 'exact match returns file': async ({pass, fail}) => {
8
+ const root = path.join(process.cwd(), 'tmp-root');
9
+ const files = [toAbs(root, 'a/b/GET.js')];
10
+ const [file, params] = await findFile(files, root, '/a/b/GET.js', 'GET', () => {});
11
+
12
+ if(file !== files[0]) return fail('not exact');
13
+ if(Object.keys(params).length !== 0) return fail('params present');
14
+
15
+ pass('exact');
16
16
  },
17
- 'directory index prioritization and method specific': async ({pass, fail, log}) => {
18
- try {
19
- const root = path.join(process.cwd(), 'tmp-root');
20
- const files = ['a/index.html', 'a/GET.js', 'a/index.js'].map(p => toAbs(root, p));
21
- const [file] = await findFile(files, root, '/a', 'GET', log);
22
- if(!file || path.basename(file) !== 'GET.js') throw new Error('priority not respected');
23
- pass('dir index');
24
- } catch(e){ fail(e.message); }
17
+ 'directory index prioritization and method specific': async ({pass, fail}) => {
18
+ const root = path.join(process.cwd(), 'tmp-root');
19
+ const files = ['a/index.html', 'a/GET.js', 'a/index.js'].map(p => toAbs(root, p));
20
+ const [file] = await findFile(files, root, '/a', 'GET', () => {});
21
+
22
+ if(!file || path.basename(file) !== 'GET.js') return fail('priority not respected');
23
+
24
+ pass('dir index');
25
25
  },
26
- 'dynamic match with params and best priority': async ({pass, fail, log}) => {
27
- try {
28
- const root = path.join(process.cwd(), 'tmp-root');
29
- const files = ['user/[id]/GET.js', 'user/[id]/index.html', 'user/[id]/index.js'].map(p => toAbs(root, p));
30
- const [file, params] = await findFile(files, root, '/user/42', 'GET', log);
31
- if(!file || path.basename(file) !== 'GET.js') throw new Error('did not pick GET.js');
32
- if(params.id !== '42') throw new Error('param missing');
33
- pass('dynamic');
34
- } catch(e){ fail(e.message); }
26
+ 'dynamic match with params and best priority': async ({pass, fail}) => {
27
+ const root = path.join(process.cwd(), 'tmp-root');
28
+ const files = ['user/[id]/GET.js', 'user/[id]/index.html', 'user/[id]/index.js'].map(p => toAbs(root, p));
29
+ const [file, params] = await findFile(files, root, '/user/42', 'GET', () => {});
30
+
31
+ if(!file || path.basename(file) !== 'GET.js') return fail('did not pick GET.js');
32
+ if(params.id !== '42') return fail('param missing');
33
+
34
+ pass('dynamic');
35
35
  },
36
- 'no match returns false and empty params': async ({pass, fail, log}) => {
37
- try {
38
- const root = path.join(process.cwd(), 'tmp-root');
39
- const files = ['x/y/index.html'].map(p => toAbs(root, p));
40
- const [file, params] = await findFile(files, root, '/nope', 'GET', log);
41
- if(file !== false) throw new Error('should be false');
42
- if(Object.keys(params).length !== 0) throw new Error('params not empty');
43
- pass('no match');
44
- } catch(e){ fail(e.message); }
36
+ 'no match returns false and empty params': async ({pass, fail}) => {
37
+ const root = path.join(process.cwd(), 'tmp-root');
38
+ const files = ['x/y/index.html'].map(p => toAbs(root, p));
39
+ const [file, params] = await findFile(files, root, '/nope', 'GET', () => {});
40
+
41
+ if(file !== false) return fail('should be false');
42
+ if(Object.keys(params).length !== 0) return fail('params not empty');
43
+
44
+ pass('no match');
45
45
  }
46
46
  };
@@ -5,21 +5,21 @@ import {withTempDir, write, log} from './test-utils.js';
5
5
 
6
6
  export default {
7
7
  'scans directories recursively and filters by mime and disallowed': async ({pass, fail}) => {
8
- try {
9
- await withTempDir(async (dir) => {
10
- const cfg = JSON.parse(JSON.stringify(defaultConfig));
11
- await write(dir, 'index.html', '<!doctype html>');
12
- await write(dir, '.env', 'SECRET=1');
13
- await write(dir, 'notes.xyz', 'unknown');
14
- await write(dir, 'sub/app.js', 'console.log(1)');
15
- const files = await getFiles(dir, cfg, log);
16
- const rel = files.map(f => path.relative(dir, f).replace(/\\/g, '/'));
17
- if(!rel.includes('index.html')) return fail('includes html');
18
- if(!rel.includes('sub/app.js')) return fail('includes js');
19
- if(rel.includes('.env')) return fail('excludes disallowed');
20
- if(rel.includes('notes.xyz')) return fail('excludes unknown ext');
21
- });
22
- pass('scan and filter');
23
- } catch(e){ fail(e.message); }
8
+ await withTempDir(async (dir) => {
9
+ const cfg = JSON.parse(JSON.stringify(defaultConfig));
10
+ await write(dir, 'index.html', '<!doctype html>');
11
+ await write(dir, '.env', 'SECRET=1');
12
+ await write(dir, 'notes.xyz', 'unknown');
13
+ await write(dir, 'sub/app.js', 'console.log(1)');
14
+ const files = await getFiles(dir, cfg, log);
15
+ const rel = files.map(f => path.relative(dir, f).replace(/\\/g, '/'));
16
+
17
+ if(!rel.includes('index.html')) return fail('includes html');
18
+ if(!rel.includes('sub/app.js')) return fail('includes js');
19
+ if(rel.includes('.env')) return fail('excludes disallowed');
20
+ if(rel.includes('notes.xyz')) return fail('excludes unknown ext');
21
+ });
22
+
23
+ pass('scan and filter');
24
24
  }
25
25
  };
@@ -4,27 +4,27 @@ export default {
4
4
  'parses long flags with values and booleans': async ({pass, fail}) => {
5
5
  const args = ['--port', '8080', '--scan'];
6
6
  const flags = getFlags(args, {port: 3000, scan: false});
7
- try {
8
- if(flags.port !== '8080') throw new Error('port not parsed');
9
- if(flags.scan !== true) throw new Error('scan boolean not parsed');
10
- pass('parsed long flags');
11
- } catch(e){ fail(e.message); }
7
+
8
+ if(flags.port !== '8080') return fail('port not parsed');
9
+ if(flags.scan !== true) return fail('scan boolean not parsed');
10
+
11
+ pass('parsed long flags');
12
12
  },
13
13
  'parses short flags using map and preserves defaults': async ({pass, fail}) => {
14
14
  const args = ['-p', '9090', '-s'];
15
15
  const flags = getFlags(args, {port: 3000, scan: false}, {p: 'port', s: 'scan'});
16
- try {
17
- if(flags.port !== '9090') throw new Error('short mapped value failed');
18
- if(flags.scan !== true) throw new Error('short mapped boolean failed');
19
- pass('short flags parsed');
20
- } catch(e){ fail(e.message); }
16
+
17
+ if(flags.port !== '9090') return fail('short mapped value failed');
18
+ if(flags.scan !== true) return fail('short mapped boolean failed');
19
+
20
+ pass('short flags parsed');
21
21
  },
22
22
  'treats next arg starting with dash as boolean flag': async ({pass, fail}) => {
23
23
  const args = ['-l', '-5', 'file'];
24
24
  const flags = getFlags(args, {l: 2});
25
- try {
26
- if(flags.l !== true) throw new Error('should be boolean true');
27
- pass('dash after flag -> boolean');
28
- } catch(e){ fail(e.message); }
25
+
26
+ if(flags.l !== true) return fail('should be boolean true');
27
+
28
+ pass('dash after flag -> boolean');
29
29
  }
30
30
  };
@@ -3,21 +3,29 @@ import path from 'path';
3
3
 
4
4
  export default {
5
5
  'index.js CLI starts server and serves root': async ({pass, fail}) => {
6
- try {
7
- await withTempDir(async (dir) => {
8
- await write(dir, 'index.html', 'home');
9
- const port = randomPort();
10
- const args = [path.join(process.cwd(), 'dist/index.js'), '-r', '.', '-p', String(port), '-l', '0'];
11
- const child = await startNode(args, {cwd: dir});
12
- // wait briefly for server to start
13
- await new Promise(r => setTimeout(r, 400));
14
- const {res, body} = await httpGet(`http://localhost:${port}/index.html`);
15
- if(res.statusCode !== 200) return fail('server running');
16
- if(body.toString() !== 'home') return fail('served');
17
- child.kill();
18
- await new Promise(r => setTimeout(r, 50));
19
- });
20
- pass('cli');
21
- } catch(e){ fail(e.message); }
6
+ await withTempDir(async (dir) => {
7
+ await write(dir, 'index.html', 'home');
8
+ const port = randomPort();
9
+ const args = [path.join(process.cwd(), 'dist/index.js'), '-r', '.', '-p', String(port), '-l', '0'];
10
+ const child = await startNode(args, {cwd: dir});
11
+ // wait briefly for server to start
12
+ await new Promise(r => setTimeout(r, 400));
13
+ const {res, body} = await httpGet(`http://localhost:${port}/index.html`);
14
+
15
+ if(res.statusCode !== 200) {
16
+ child.kill();
17
+ return fail('server running');
18
+ }
19
+
20
+ if(body.toString() !== 'home') {
21
+ child.kill();
22
+ return fail('served');
23
+ }
24
+
25
+ child.kill();
26
+ await new Promise(r => setTimeout(r, 50));
27
+ });
28
+
29
+ pass('cli');
22
30
  }
23
31
  };