adapt-authoring-server 1.4.2 → 2.0.0
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/index.js +1 -0
- package/lib/Router.js +6 -3
- package/lib/ServerUtils.js +3 -125
- package/lib/utils/addExistenceProps.js +43 -0
- package/lib/utils/cacheRouteConfig.js +12 -0
- package/lib/utils/generateRouterMap.js +40 -0
- package/lib/utils/getAllRoutes.js +25 -0
- package/lib/utils/mapHandler.js +9 -0
- package/lib/utils.js +5 -0
- package/package.json +2 -2
- package/tests/ServerUtils.spec.js +0 -289
- package/tests/utils-addExistenceProps.spec.js +90 -0
- package/tests/utils-cacheRouteConfig.spec.js +48 -0
- package/tests/utils-generateRouterMap.spec.js +138 -0
- package/tests/utils-getAllRoutes.spec.js +113 -0
- package/tests/utils-mapHandler.spec.js +42 -0
package/index.js
CHANGED
package/lib/Router.js
CHANGED
|
@@ -2,6 +2,9 @@ import _ from 'lodash'
|
|
|
2
2
|
import { App } from 'adapt-authoring-core'
|
|
3
3
|
import express from 'express'
|
|
4
4
|
import ServerUtils from './ServerUtils.js'
|
|
5
|
+
import { addExistenceProps } from './utils/addExistenceProps.js'
|
|
6
|
+
import { cacheRouteConfig } from './utils/cacheRouteConfig.js'
|
|
7
|
+
import { generateRouterMap } from './utils/generateRouterMap.js'
|
|
5
8
|
/**
|
|
6
9
|
* Handles the Express routing functionality
|
|
7
10
|
* @memberof server
|
|
@@ -54,7 +57,7 @@ class Router {
|
|
|
54
57
|
* @type {Array<Function>}
|
|
55
58
|
*/
|
|
56
59
|
this.handlerMiddleware = [
|
|
57
|
-
|
|
60
|
+
addExistenceProps,
|
|
58
61
|
ServerUtils.handleInternalRoutes,
|
|
59
62
|
...handlerMiddleware.filter(_.isFunction)
|
|
60
63
|
]
|
|
@@ -66,7 +69,7 @@ class Router {
|
|
|
66
69
|
* @type {Object}
|
|
67
70
|
*/
|
|
68
71
|
get map () {
|
|
69
|
-
return
|
|
72
|
+
return generateRouterMap(this)
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
/**
|
|
@@ -226,7 +229,7 @@ class Router {
|
|
|
226
229
|
if (this.routes.length) {
|
|
227
230
|
this.routes.forEach(r => {
|
|
228
231
|
Object.entries(r.handlers).forEach(([method, handler]) => {
|
|
229
|
-
this.expressRouter[method](r.route,
|
|
232
|
+
this.expressRouter[method](r.route, cacheRouteConfig(r), ...this.getHandlerMiddleware(), handler)
|
|
230
233
|
this.log('verbose', 'ADD_ROUTE', method.toUpperCase(), `${this.path !== '/' ? this.path : ''}${r.route}`)
|
|
231
234
|
})
|
|
232
235
|
})
|
package/lib/ServerUtils.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import _ from 'lodash'
|
|
2
1
|
import { App } from 'adapt-authoring-core'
|
|
2
|
+
import { getAllRoutes } from './utils/getAllRoutes.js'
|
|
3
|
+
|
|
3
4
|
/**
|
|
4
5
|
* Server-related utilities
|
|
5
6
|
* @memberof server
|
|
@@ -23,7 +24,7 @@ class ServerUtils {
|
|
|
23
24
|
*/
|
|
24
25
|
static methodNotAllowedHandler (router) {
|
|
25
26
|
const routePatterns = []
|
|
26
|
-
const routeMap =
|
|
27
|
+
const routeMap = getAllRoutes(router)
|
|
27
28
|
|
|
28
29
|
for (const [path, methods] of routeMap.entries()) {
|
|
29
30
|
const pathPattern = path
|
|
@@ -75,55 +76,6 @@ class ServerUtils {
|
|
|
75
76
|
res.status(App.instance.errors.NOT_FOUND.statusCode).end()
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
/**
|
|
79
|
-
* Handler for returning an API map
|
|
80
|
-
* @param {Router} topRouter
|
|
81
|
-
* @return {Function} Middleware function
|
|
82
|
-
*/
|
|
83
|
-
static mapHandler (topRouter) {
|
|
84
|
-
return (req, res) => res.json(topRouter.map)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Collects all registered routes with their methods across the router hierarchy
|
|
89
|
-
* @param {Router} router The router to traverse
|
|
90
|
-
* @return {Map<string, Set<string>>} Map of route paths to sets of allowed methods
|
|
91
|
-
*/
|
|
92
|
-
static getAllRoutes (router) {
|
|
93
|
-
const routeMap = new Map()
|
|
94
|
-
|
|
95
|
-
router.flattenRouters().forEach(r => {
|
|
96
|
-
r.routes.forEach(route => {
|
|
97
|
-
const fullPath = `${r.path !== '/' ? r.path : ''}${route.route}`
|
|
98
|
-
|
|
99
|
-
if (!routeMap.has(fullPath)) {
|
|
100
|
-
routeMap.set(fullPath, new Set())
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
Object.keys(route.handlers).forEach(method => {
|
|
104
|
-
routeMap.get(fullPath).add(method.toUpperCase())
|
|
105
|
-
})
|
|
106
|
-
})
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
return routeMap
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Generates a map for a given router
|
|
114
|
-
* @param {Router} topRouter
|
|
115
|
-
* @return {Object} The route map
|
|
116
|
-
*/
|
|
117
|
-
static generateRouterMap (topRouter) {
|
|
118
|
-
return topRouter.flattenRouters()
|
|
119
|
-
.sort((a, b) => a.root.localeCompare(b.root))
|
|
120
|
-
.reduce((m, r) => {
|
|
121
|
-
const key = `${getRelativeRoute(topRouter, r)}endpoints`
|
|
122
|
-
const endpoints = getEndpoints(r)
|
|
123
|
-
return endpoints.length ? { ...m, [key]: endpoints } : m
|
|
124
|
-
}, {})
|
|
125
|
-
}
|
|
126
|
-
|
|
127
79
|
/**
|
|
128
80
|
* Adds extra properties to the request object to allow for easy translations
|
|
129
81
|
* @param {Function} next
|
|
@@ -160,47 +112,6 @@ class ServerUtils {
|
|
|
160
112
|
next()
|
|
161
113
|
}
|
|
162
114
|
|
|
163
|
-
/**
|
|
164
|
-
* Adds extra properties to the request object to allow for easy existence checking of common request objects
|
|
165
|
-
* @param {external:ExpressRequest} req
|
|
166
|
-
* @param {external:ExpressResponse} res
|
|
167
|
-
* @param {Function} next
|
|
168
|
-
* @example
|
|
169
|
-
* "IMPORTANT NOTE: body data is completely ignored for GET requests, any code
|
|
170
|
-
* requiring it should switch to use POST."
|
|
171
|
-
*
|
|
172
|
-
* let req = { 'params': { 'foo':'bar' }, 'query': {}, 'body': {} };
|
|
173
|
-
* req.hasParams // true
|
|
174
|
-
* req.hasQuery // false
|
|
175
|
-
* req.hasBody // false
|
|
176
|
-
*/
|
|
177
|
-
static addExistenceProps (req, res, next) {
|
|
178
|
-
if (req.method === 'GET') {
|
|
179
|
-
req.body = {}
|
|
180
|
-
}
|
|
181
|
-
const storeVal = (key, exists) => {
|
|
182
|
-
req[`has${_.capitalize(key)}`] = exists
|
|
183
|
-
}
|
|
184
|
-
['body', 'params', 'query'].forEach(attr => {
|
|
185
|
-
if (!req[attr]) {
|
|
186
|
-
return storeVal(attr, false)
|
|
187
|
-
}
|
|
188
|
-
const entries = Object.entries(req[attr])
|
|
189
|
-
let deleted = 0
|
|
190
|
-
if (entries.length === 0) {
|
|
191
|
-
return storeVal(attr, false)
|
|
192
|
-
}
|
|
193
|
-
entries.forEach(([key, val]) => {
|
|
194
|
-
if (val === undefined || val === null) {
|
|
195
|
-
delete req[attr][key]
|
|
196
|
-
deleted++
|
|
197
|
-
}
|
|
198
|
-
})
|
|
199
|
-
storeVal(attr, deleted < entries.length)
|
|
200
|
-
})
|
|
201
|
-
next()
|
|
202
|
-
}
|
|
203
|
-
|
|
204
115
|
/**
|
|
205
116
|
* Handles restriction of routes marked as internal
|
|
206
117
|
* @param {external:ExpressRequest} req
|
|
@@ -215,39 +126,6 @@ class ServerUtils {
|
|
|
215
126
|
}
|
|
216
127
|
next()
|
|
217
128
|
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Caches the route config on the incoming request
|
|
221
|
-
* @param {Route} routeConfig
|
|
222
|
-
* @return {Function}
|
|
223
|
-
*/
|
|
224
|
-
static cacheRouteConfig (routeConfig) {
|
|
225
|
-
return (req, res, next) => {
|
|
226
|
-
req.routeConfig = routeConfig
|
|
227
|
-
next()
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
/** @ignore */ function getEndpoints (r) {
|
|
232
|
-
return r.routes.map(route => {
|
|
233
|
-
return {
|
|
234
|
-
url: `${r.url}${route.route}`,
|
|
235
|
-
accepted_methods: Object.keys(route.handlers).reduce((memo, method) => {
|
|
236
|
-
return {
|
|
237
|
-
...memo,
|
|
238
|
-
[method]: route?.meta?.[method] ?? {}
|
|
239
|
-
}
|
|
240
|
-
}, {})
|
|
241
|
-
}
|
|
242
|
-
})
|
|
243
|
-
}
|
|
244
|
-
/** @ignore */ function getRelativeRoute (relFrom, relTo) {
|
|
245
|
-
if (relFrom === relTo) {
|
|
246
|
-
return `${relFrom.route}_`
|
|
247
|
-
}
|
|
248
|
-
let route = ''
|
|
249
|
-
for (let r = relTo; r !== relFrom; r = r.parentRouter) route = `${r.root}_${route}`
|
|
250
|
-
return route
|
|
251
129
|
}
|
|
252
130
|
|
|
253
131
|
export default ServerUtils
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Adds extra properties to the request object to allow for easy existence checking of common request objects
|
|
5
|
+
* @param {external:ExpressRequest} req
|
|
6
|
+
* @param {external:ExpressResponse} res
|
|
7
|
+
* @param {Function} next
|
|
8
|
+
* @memberof server
|
|
9
|
+
* @example
|
|
10
|
+
* "IMPORTANT NOTE: body data is completely ignored for GET requests, any code
|
|
11
|
+
* requiring it should switch to use POST."
|
|
12
|
+
*
|
|
13
|
+
* let req = { 'params': { 'foo':'bar' }, 'query': {}, 'body': {} };
|
|
14
|
+
* req.hasParams // true
|
|
15
|
+
* req.hasQuery // false
|
|
16
|
+
* req.hasBody // false
|
|
17
|
+
*/
|
|
18
|
+
export function addExistenceProps (req, res, next) {
|
|
19
|
+
if (req.method === 'GET') {
|
|
20
|
+
req.body = {}
|
|
21
|
+
}
|
|
22
|
+
const storeVal = (key, exists) => {
|
|
23
|
+
req[`has${_.capitalize(key)}`] = exists
|
|
24
|
+
}
|
|
25
|
+
;['body', 'params', 'query'].forEach(attr => {
|
|
26
|
+
if (!req[attr]) {
|
|
27
|
+
return storeVal(attr, false)
|
|
28
|
+
}
|
|
29
|
+
const entries = Object.entries(req[attr])
|
|
30
|
+
let deleted = 0
|
|
31
|
+
if (entries.length === 0) {
|
|
32
|
+
return storeVal(attr, false)
|
|
33
|
+
}
|
|
34
|
+
entries.forEach(([key, val]) => {
|
|
35
|
+
if (val === undefined || val === null) {
|
|
36
|
+
delete req[attr][key]
|
|
37
|
+
deleted++
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
storeVal(attr, deleted < entries.length)
|
|
41
|
+
})
|
|
42
|
+
next()
|
|
43
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caches the route config on the incoming request
|
|
3
|
+
* @param {Route} routeConfig
|
|
4
|
+
* @return {Function}
|
|
5
|
+
* @memberof server
|
|
6
|
+
*/
|
|
7
|
+
export function cacheRouteConfig (routeConfig) {
|
|
8
|
+
return (req, res, next) => {
|
|
9
|
+
req.routeConfig = routeConfig
|
|
10
|
+
next()
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a map for a given router
|
|
3
|
+
* @param {Router} topRouter
|
|
4
|
+
* @return {Object} The route map
|
|
5
|
+
* @memberof server
|
|
6
|
+
*/
|
|
7
|
+
export function generateRouterMap (topRouter) {
|
|
8
|
+
return topRouter.flattenRouters()
|
|
9
|
+
.sort((a, b) => a.root.localeCompare(b.root))
|
|
10
|
+
.reduce((m, r) => {
|
|
11
|
+
const key = `${getRelativeRoute(topRouter, r)}endpoints`
|
|
12
|
+
const endpoints = getEndpoints(r)
|
|
13
|
+
return endpoints.length ? { ...m, [key]: endpoints } : m
|
|
14
|
+
}, {})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** @ignore */
|
|
18
|
+
function getEndpoints (r) {
|
|
19
|
+
return r.routes.map(route => {
|
|
20
|
+
return {
|
|
21
|
+
url: `${r.url}${route.route}`,
|
|
22
|
+
accepted_methods: Object.keys(route.handlers).reduce((memo, method) => {
|
|
23
|
+
return {
|
|
24
|
+
...memo,
|
|
25
|
+
[method]: route?.meta?.[method] ?? {}
|
|
26
|
+
}
|
|
27
|
+
}, {})
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** @ignore */
|
|
33
|
+
function getRelativeRoute (relFrom, relTo) {
|
|
34
|
+
if (relFrom === relTo) {
|
|
35
|
+
return `${relFrom.route}_`
|
|
36
|
+
}
|
|
37
|
+
let route = ''
|
|
38
|
+
for (let r = relTo; r !== relFrom; r = r.parentRouter) route = `${r.root}_${route}`
|
|
39
|
+
return route
|
|
40
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collects all registered routes with their methods across the router hierarchy
|
|
3
|
+
* @param {Router} router The router to traverse
|
|
4
|
+
* @return {Map<string, Set<string>>} Map of route paths to sets of allowed methods
|
|
5
|
+
* @memberof server
|
|
6
|
+
*/
|
|
7
|
+
export function getAllRoutes (router) {
|
|
8
|
+
const routeMap = new Map()
|
|
9
|
+
|
|
10
|
+
router.flattenRouters().forEach(r => {
|
|
11
|
+
r.routes.forEach(route => {
|
|
12
|
+
const fullPath = `${r.path !== '/' ? r.path : ''}${route.route}`
|
|
13
|
+
|
|
14
|
+
if (!routeMap.has(fullPath)) {
|
|
15
|
+
routeMap.set(fullPath, new Set())
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
Object.keys(route.handlers).forEach(method => {
|
|
19
|
+
routeMap.get(fullPath).add(method.toUpperCase())
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
return routeMap
|
|
25
|
+
}
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { addExistenceProps } from './utils/addExistenceProps.js'
|
|
2
|
+
export { cacheRouteConfig } from './utils/cacheRouteConfig.js'
|
|
3
|
+
export { generateRouterMap } from './utils/generateRouterMap.js'
|
|
4
|
+
export { getAllRoutes } from './utils/getAllRoutes.js'
|
|
5
|
+
export { mapHandler } from './utils/mapHandler.js'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adapt-authoring-server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Provides an Express application routing and more",
|
|
5
5
|
"homepage": "https://github.com/adapt-security/adapt-authoring-server",
|
|
6
6
|
"license": "GPL-3.0",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"test": "node --test tests/**/*.spec.js"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"adapt-authoring-core": "^
|
|
14
|
+
"adapt-authoring-core": "^2.0.0",
|
|
15
15
|
"express": "^5.1.0",
|
|
16
16
|
"hbs": "^4.2.0",
|
|
17
17
|
"lodash": "^4.17.21"
|
|
@@ -16,104 +16,6 @@ describe('ServerUtils', () => {
|
|
|
16
16
|
ServerUtils = (await import('../lib/ServerUtils.js')).default
|
|
17
17
|
})
|
|
18
18
|
|
|
19
|
-
describe('#addExistenceProps()', () => {
|
|
20
|
-
it('should set hasBody, hasParams, hasQuery to false for empty objects', () => {
|
|
21
|
-
const req = { method: 'POST', body: {}, params: {}, query: {} }
|
|
22
|
-
const res = {}
|
|
23
|
-
let nextCalled = false
|
|
24
|
-
const next = () => { nextCalled = true }
|
|
25
|
-
|
|
26
|
-
ServerUtils.addExistenceProps(req, res, next)
|
|
27
|
-
|
|
28
|
-
assert.equal(req.hasBody, false)
|
|
29
|
-
assert.equal(req.hasParams, false)
|
|
30
|
-
assert.equal(req.hasQuery, false)
|
|
31
|
-
assert.equal(nextCalled, true)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('should set hasBody, hasParams, hasQuery to true for populated objects', () => {
|
|
35
|
-
const req = { method: 'POST', body: { foo: 'bar' }, params: { id: '123' }, query: { search: 'test' } }
|
|
36
|
-
const res = {}
|
|
37
|
-
let nextCalled = false
|
|
38
|
-
const next = () => { nextCalled = true }
|
|
39
|
-
|
|
40
|
-
ServerUtils.addExistenceProps(req, res, next)
|
|
41
|
-
|
|
42
|
-
assert.equal(req.hasBody, true)
|
|
43
|
-
assert.equal(req.hasParams, true)
|
|
44
|
-
assert.equal(req.hasQuery, true)
|
|
45
|
-
assert.equal(nextCalled, true)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it('should remove undefined and null values from request objects', () => {
|
|
49
|
-
const req = { method: 'POST', body: { foo: 'bar', baz: undefined, qux: null }, params: {}, query: {} }
|
|
50
|
-
const res = {}
|
|
51
|
-
const next = () => {}
|
|
52
|
-
|
|
53
|
-
ServerUtils.addExistenceProps(req, res, next)
|
|
54
|
-
|
|
55
|
-
assert.equal(req.body.foo, 'bar')
|
|
56
|
-
assert.equal(req.body.baz, undefined)
|
|
57
|
-
assert.equal(req.body.qux, undefined)
|
|
58
|
-
assert.equal(req.hasBody, true)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('should clear body for GET requests', () => {
|
|
62
|
-
const req = { method: 'GET', body: { foo: 'bar' }, params: {}, query: {} }
|
|
63
|
-
const res = {}
|
|
64
|
-
const next = () => {}
|
|
65
|
-
|
|
66
|
-
ServerUtils.addExistenceProps(req, res, next)
|
|
67
|
-
|
|
68
|
-
assert.deepEqual(req.body, {})
|
|
69
|
-
assert.equal(req.hasBody, false)
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it('should handle falsy attr values (missing body/params/query)', () => {
|
|
73
|
-
const req = { method: 'POST', body: null, params: undefined, query: false }
|
|
74
|
-
const res = {}
|
|
75
|
-
const next = () => {}
|
|
76
|
-
|
|
77
|
-
ServerUtils.addExistenceProps(req, res, next)
|
|
78
|
-
|
|
79
|
-
assert.equal(req.hasBody, false)
|
|
80
|
-
assert.equal(req.hasParams, false)
|
|
81
|
-
assert.equal(req.hasQuery, false)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('should mark has* false when all entries are null or undefined', () => {
|
|
85
|
-
const req = { method: 'POST', body: { a: null, b: undefined }, params: {}, query: {} }
|
|
86
|
-
const res = {}
|
|
87
|
-
const next = () => {}
|
|
88
|
-
|
|
89
|
-
ServerUtils.addExistenceProps(req, res, next)
|
|
90
|
-
|
|
91
|
-
assert.equal(req.hasBody, false)
|
|
92
|
-
})
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
describe('#cacheRouteConfig()', () => {
|
|
96
|
-
it('should cache route config on request object', () => {
|
|
97
|
-
const routeConfig = { route: '/test', handlers: {}, internal: false }
|
|
98
|
-
const middleware = ServerUtils.cacheRouteConfig(routeConfig)
|
|
99
|
-
const req = {}
|
|
100
|
-
const res = {}
|
|
101
|
-
let nextCalled = false
|
|
102
|
-
const next = () => { nextCalled = true }
|
|
103
|
-
|
|
104
|
-
middleware(req, res, next)
|
|
105
|
-
|
|
106
|
-
assert.equal(req.routeConfig, routeConfig)
|
|
107
|
-
assert.equal(nextCalled, true)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
it('should return a middleware function', () => {
|
|
111
|
-
const middleware = ServerUtils.cacheRouteConfig({})
|
|
112
|
-
|
|
113
|
-
assert.equal(typeof middleware, 'function')
|
|
114
|
-
})
|
|
115
|
-
})
|
|
116
|
-
|
|
117
19
|
describe('#addErrorHandler()', () => {
|
|
118
20
|
it('should add sendError method to response object', () => {
|
|
119
21
|
const req = {}
|
|
@@ -251,100 +153,6 @@ describe('ServerUtils', () => {
|
|
|
251
153
|
})
|
|
252
154
|
})
|
|
253
155
|
|
|
254
|
-
describe('#mapHandler()', () => {
|
|
255
|
-
it('should return a function', () => {
|
|
256
|
-
const mockRouter = { map: { test: 'data' } }
|
|
257
|
-
const handler = ServerUtils.mapHandler(mockRouter)
|
|
258
|
-
|
|
259
|
-
assert.equal(typeof handler, 'function')
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
it('should respond with router map', () => {
|
|
263
|
-
const mockRouter = { map: { test: 'data' } }
|
|
264
|
-
const handler = ServerUtils.mapHandler(mockRouter)
|
|
265
|
-
const req = {}
|
|
266
|
-
let responseData = null
|
|
267
|
-
const res = {
|
|
268
|
-
json: (data) => { responseData = data }
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
handler(req, res)
|
|
272
|
-
|
|
273
|
-
assert.deepEqual(responseData, { test: 'data' })
|
|
274
|
-
})
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
describe('#getAllRoutes()', () => {
|
|
278
|
-
it('should collect routes from router hierarchy', () => {
|
|
279
|
-
const mockRouter = {
|
|
280
|
-
path: '/api',
|
|
281
|
-
routes: [
|
|
282
|
-
{ route: '/users', handlers: { get: () => {}, post: () => {} } },
|
|
283
|
-
{ route: '/posts', handlers: { get: () => {} } }
|
|
284
|
-
],
|
|
285
|
-
flattenRouters: () => [mockRouter]
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const routeMap = ServerUtils.getAllRoutes(mockRouter)
|
|
289
|
-
|
|
290
|
-
assert.ok(routeMap instanceof Map)
|
|
291
|
-
assert.equal(routeMap.size, 2)
|
|
292
|
-
assert.ok(routeMap.has('/api/users'))
|
|
293
|
-
assert.ok(routeMap.has('/api/posts'))
|
|
294
|
-
assert.ok(routeMap.get('/api/users').has('GET'))
|
|
295
|
-
assert.ok(routeMap.get('/api/users').has('POST'))
|
|
296
|
-
assert.ok(routeMap.get('/api/posts').has('GET'))
|
|
297
|
-
})
|
|
298
|
-
|
|
299
|
-
it('should collect routes from multiple routers in hierarchy', () => {
|
|
300
|
-
const childRouter = {
|
|
301
|
-
path: '/api/v1',
|
|
302
|
-
routes: [
|
|
303
|
-
{ route: '/items', handlers: { get: () => {} } }
|
|
304
|
-
]
|
|
305
|
-
}
|
|
306
|
-
const parentRouter = {
|
|
307
|
-
path: '/api',
|
|
308
|
-
routes: [
|
|
309
|
-
{ route: '/health', handlers: { get: () => {} } }
|
|
310
|
-
],
|
|
311
|
-
flattenRouters: () => [parentRouter, childRouter]
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const routeMap = ServerUtils.getAllRoutes(parentRouter)
|
|
315
|
-
|
|
316
|
-
assert.equal(routeMap.size, 2)
|
|
317
|
-
assert.ok(routeMap.has('/api/health'))
|
|
318
|
-
assert.ok(routeMap.has('/api/v1/items'))
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
it('should handle root path "/" by omitting it from the prefix', () => {
|
|
322
|
-
const mockRouter = {
|
|
323
|
-
path: '/',
|
|
324
|
-
routes: [
|
|
325
|
-
{ route: '/status', handlers: { get: () => {} } }
|
|
326
|
-
],
|
|
327
|
-
flattenRouters: () => [mockRouter]
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const routeMap = ServerUtils.getAllRoutes(mockRouter)
|
|
331
|
-
|
|
332
|
-
assert.ok(routeMap.has('/status'))
|
|
333
|
-
})
|
|
334
|
-
|
|
335
|
-
it('should return empty map for router with no routes', () => {
|
|
336
|
-
const mockRouter = {
|
|
337
|
-
path: '/api',
|
|
338
|
-
routes: [],
|
|
339
|
-
flattenRouters: () => [mockRouter]
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const routeMap = ServerUtils.getAllRoutes(mockRouter)
|
|
343
|
-
|
|
344
|
-
assert.equal(routeMap.size, 0)
|
|
345
|
-
})
|
|
346
|
-
})
|
|
347
|
-
|
|
348
156
|
describe('#methodNotAllowedHandler()', () => {
|
|
349
157
|
it('should return a middleware function', () => {
|
|
350
158
|
const mockRouter = {
|
|
@@ -420,103 +228,6 @@ describe('ServerUtils', () => {
|
|
|
420
228
|
})
|
|
421
229
|
})
|
|
422
230
|
|
|
423
|
-
describe('#generateRouterMap()', () => {
|
|
424
|
-
it('should generate a router map', () => {
|
|
425
|
-
const mockRouter = {
|
|
426
|
-
root: 'api',
|
|
427
|
-
path: '/api',
|
|
428
|
-
url: 'http://localhost:5000/api',
|
|
429
|
-
routes: [
|
|
430
|
-
{ route: '/test', handlers: { get: () => {} } }
|
|
431
|
-
],
|
|
432
|
-
childRouters: [],
|
|
433
|
-
flattenRouters: function () {
|
|
434
|
-
return [this]
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
const map = ServerUtils.generateRouterMap(mockRouter)
|
|
439
|
-
|
|
440
|
-
assert.equal(typeof map, 'object')
|
|
441
|
-
})
|
|
442
|
-
|
|
443
|
-
it('should return empty object for router with no routes', () => {
|
|
444
|
-
const mockRouter = {
|
|
445
|
-
root: 'api',
|
|
446
|
-
path: '/api',
|
|
447
|
-
url: 'http://localhost:5000/api',
|
|
448
|
-
routes: [],
|
|
449
|
-
childRouters: [],
|
|
450
|
-
flattenRouters: function () {
|
|
451
|
-
return [this]
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
const map = ServerUtils.generateRouterMap(mockRouter)
|
|
456
|
-
|
|
457
|
-
assert.deepEqual(map, {})
|
|
458
|
-
})
|
|
459
|
-
|
|
460
|
-
it('should include endpoint URLs and accepted methods', () => {
|
|
461
|
-
const mockRouter = {
|
|
462
|
-
root: 'api',
|
|
463
|
-
route: '/api',
|
|
464
|
-
path: '/api',
|
|
465
|
-
url: 'http://localhost:5000/api',
|
|
466
|
-
routes: [
|
|
467
|
-
{ route: '/users', handlers: { get: () => {}, post: () => {} }, meta: { get: { description: 'list users' } } }
|
|
468
|
-
],
|
|
469
|
-
childRouters: [],
|
|
470
|
-
parentRouter: null,
|
|
471
|
-
flattenRouters: function () {
|
|
472
|
-
return [this]
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const map = ServerUtils.generateRouterMap(mockRouter)
|
|
477
|
-
const keys = Object.keys(map)
|
|
478
|
-
|
|
479
|
-
assert.ok(keys.length > 0)
|
|
480
|
-
const endpoints = map[keys[0]]
|
|
481
|
-
assert.ok(Array.isArray(endpoints))
|
|
482
|
-
assert.equal(endpoints[0].url, 'http://localhost:5000/api/users')
|
|
483
|
-
assert.ok('get' in endpoints[0].accepted_methods)
|
|
484
|
-
assert.ok('post' in endpoints[0].accepted_methods)
|
|
485
|
-
assert.deepEqual(endpoints[0].accepted_methods.get, { description: 'list users' })
|
|
486
|
-
assert.deepEqual(endpoints[0].accepted_methods.post, {})
|
|
487
|
-
})
|
|
488
|
-
|
|
489
|
-
it('should use relative route keys for child routers', () => {
|
|
490
|
-
const childRouter = {
|
|
491
|
-
root: 'users',
|
|
492
|
-
path: '/api/users',
|
|
493
|
-
url: 'http://localhost:5000/api/users',
|
|
494
|
-
routes: [
|
|
495
|
-
{ route: '/:id', handlers: { get: () => {} } }
|
|
496
|
-
],
|
|
497
|
-
childRouters: [],
|
|
498
|
-
parentRouter: null
|
|
499
|
-
}
|
|
500
|
-
const mockRouter = {
|
|
501
|
-
root: 'api',
|
|
502
|
-
route: '/api',
|
|
503
|
-
path: '/api',
|
|
504
|
-
url: 'http://localhost:5000/api',
|
|
505
|
-
routes: [],
|
|
506
|
-
childRouters: [childRouter],
|
|
507
|
-
flattenRouters: function () {
|
|
508
|
-
return [this, childRouter]
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
childRouter.parentRouter = mockRouter
|
|
512
|
-
|
|
513
|
-
const map = ServerUtils.generateRouterMap(mockRouter)
|
|
514
|
-
const keys = Object.keys(map)
|
|
515
|
-
|
|
516
|
-
assert.ok(keys.some(k => k.includes('users')))
|
|
517
|
-
})
|
|
518
|
-
})
|
|
519
|
-
|
|
520
231
|
describe('#rootNotFoundHandler()', () => {
|
|
521
232
|
it('should respond with NOT_FOUND status code', () => {
|
|
522
233
|
App.instance.errors = App.instance.errors || {}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { addExistenceProps } from '../lib/utils/addExistenceProps.js'
|
|
4
|
+
|
|
5
|
+
describe('addExistenceProps()', () => {
|
|
6
|
+
it('should set hasBody, hasParams, hasQuery to false for empty objects', () => {
|
|
7
|
+
const req = { method: 'POST', body: {}, params: {}, query: {} }
|
|
8
|
+
const res = {}
|
|
9
|
+
let nextCalled = false
|
|
10
|
+
const next = () => { nextCalled = true }
|
|
11
|
+
|
|
12
|
+
addExistenceProps(req, res, next)
|
|
13
|
+
|
|
14
|
+
assert.equal(req.hasBody, false)
|
|
15
|
+
assert.equal(req.hasParams, false)
|
|
16
|
+
assert.equal(req.hasQuery, false)
|
|
17
|
+
assert.equal(nextCalled, true)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('should set hasBody, hasParams, hasQuery to true for populated objects', () => {
|
|
21
|
+
const req = { method: 'POST', body: { foo: 'bar' }, params: { id: '123' }, query: { search: 'test' } }
|
|
22
|
+
const res = {}
|
|
23
|
+
let nextCalled = false
|
|
24
|
+
const next = () => { nextCalled = true }
|
|
25
|
+
|
|
26
|
+
addExistenceProps(req, res, next)
|
|
27
|
+
|
|
28
|
+
assert.equal(req.hasBody, true)
|
|
29
|
+
assert.equal(req.hasParams, true)
|
|
30
|
+
assert.equal(req.hasQuery, true)
|
|
31
|
+
assert.equal(nextCalled, true)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should remove undefined and null values from request objects', () => {
|
|
35
|
+
const req = { method: 'POST', body: { foo: 'bar', baz: undefined, qux: null }, params: {}, query: {} }
|
|
36
|
+
const res = {}
|
|
37
|
+
const next = () => {}
|
|
38
|
+
|
|
39
|
+
addExistenceProps(req, res, next)
|
|
40
|
+
|
|
41
|
+
assert.equal(req.body.foo, 'bar')
|
|
42
|
+
assert.equal(req.body.baz, undefined)
|
|
43
|
+
assert.equal(req.body.qux, undefined)
|
|
44
|
+
assert.equal(req.hasBody, true)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should clear body for GET requests', () => {
|
|
48
|
+
const req = { method: 'GET', body: { foo: 'bar' }, params: {}, query: {} }
|
|
49
|
+
const res = {}
|
|
50
|
+
const next = () => {}
|
|
51
|
+
|
|
52
|
+
addExistenceProps(req, res, next)
|
|
53
|
+
|
|
54
|
+
assert.deepEqual(req.body, {})
|
|
55
|
+
assert.equal(req.hasBody, false)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('should handle falsy attr values (missing body/params/query)', () => {
|
|
59
|
+
const req = { method: 'POST', body: null, params: undefined, query: false }
|
|
60
|
+
const res = {}
|
|
61
|
+
const next = () => {}
|
|
62
|
+
|
|
63
|
+
addExistenceProps(req, res, next)
|
|
64
|
+
|
|
65
|
+
assert.equal(req.hasBody, false)
|
|
66
|
+
assert.equal(req.hasParams, false)
|
|
67
|
+
assert.equal(req.hasQuery, false)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should mark has* false when all entries are null or undefined', () => {
|
|
71
|
+
const req = { method: 'POST', body: { a: null, b: undefined }, params: {}, query: {} }
|
|
72
|
+
const res = {}
|
|
73
|
+
const next = () => {}
|
|
74
|
+
|
|
75
|
+
addExistenceProps(req, res, next)
|
|
76
|
+
|
|
77
|
+
assert.equal(req.hasBody, false)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should always call next()', () => {
|
|
81
|
+
const req = { method: 'POST', body: {}, params: {}, query: {} }
|
|
82
|
+
const res = {}
|
|
83
|
+
let nextCalled = false
|
|
84
|
+
const next = () => { nextCalled = true }
|
|
85
|
+
|
|
86
|
+
addExistenceProps(req, res, next)
|
|
87
|
+
|
|
88
|
+
assert.equal(nextCalled, true)
|
|
89
|
+
})
|
|
90
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { cacheRouteConfig } from '../lib/utils/cacheRouteConfig.js'
|
|
4
|
+
|
|
5
|
+
describe('cacheRouteConfig()', () => {
|
|
6
|
+
it('should return a middleware function', () => {
|
|
7
|
+
const middleware = cacheRouteConfig({})
|
|
8
|
+
|
|
9
|
+
assert.equal(typeof middleware, 'function')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('should cache route config on request object', () => {
|
|
13
|
+
const routeConfig = { route: '/test', handlers: {}, internal: false }
|
|
14
|
+
const middleware = cacheRouteConfig(routeConfig)
|
|
15
|
+
const req = {}
|
|
16
|
+
const res = {}
|
|
17
|
+
let nextCalled = false
|
|
18
|
+
const next = () => { nextCalled = true }
|
|
19
|
+
|
|
20
|
+
middleware(req, res, next)
|
|
21
|
+
|
|
22
|
+
assert.equal(req.routeConfig, routeConfig)
|
|
23
|
+
assert.equal(nextCalled, true)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should always call next()', () => {
|
|
27
|
+
const middleware = cacheRouteConfig({})
|
|
28
|
+
const req = {}
|
|
29
|
+
const res = {}
|
|
30
|
+
let nextCalled = false
|
|
31
|
+
const next = () => { nextCalled = true }
|
|
32
|
+
|
|
33
|
+
middleware(req, res, next)
|
|
34
|
+
|
|
35
|
+
assert.equal(nextCalled, true)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should store the exact config reference', () => {
|
|
39
|
+
const config = { route: '/specific', internal: true }
|
|
40
|
+
const middleware = cacheRouteConfig(config)
|
|
41
|
+
const req = {}
|
|
42
|
+
|
|
43
|
+
middleware(req, {}, () => {})
|
|
44
|
+
|
|
45
|
+
assert.equal(req.routeConfig, config)
|
|
46
|
+
assert.equal(req.routeConfig.internal, true)
|
|
47
|
+
})
|
|
48
|
+
})
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { generateRouterMap } from '../lib/utils/generateRouterMap.js'
|
|
4
|
+
|
|
5
|
+
describe('generateRouterMap()', () => {
|
|
6
|
+
it('should generate a router map', () => {
|
|
7
|
+
const mockRouter = {
|
|
8
|
+
root: 'api',
|
|
9
|
+
path: '/api',
|
|
10
|
+
url: 'http://localhost:5000/api',
|
|
11
|
+
routes: [
|
|
12
|
+
{ route: '/test', handlers: { get: () => {} } }
|
|
13
|
+
],
|
|
14
|
+
childRouters: [],
|
|
15
|
+
flattenRouters: function () {
|
|
16
|
+
return [this]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const map = generateRouterMap(mockRouter)
|
|
21
|
+
|
|
22
|
+
assert.equal(typeof map, 'object')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('should return empty object for router with no routes', () => {
|
|
26
|
+
const mockRouter = {
|
|
27
|
+
root: 'api',
|
|
28
|
+
path: '/api',
|
|
29
|
+
url: 'http://localhost:5000/api',
|
|
30
|
+
routes: [],
|
|
31
|
+
childRouters: [],
|
|
32
|
+
flattenRouters: function () {
|
|
33
|
+
return [this]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const map = generateRouterMap(mockRouter)
|
|
38
|
+
|
|
39
|
+
assert.deepEqual(map, {})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should include endpoint URLs and accepted methods', () => {
|
|
43
|
+
const mockRouter = {
|
|
44
|
+
root: 'api',
|
|
45
|
+
route: '/api',
|
|
46
|
+
path: '/api',
|
|
47
|
+
url: 'http://localhost:5000/api',
|
|
48
|
+
routes: [
|
|
49
|
+
{ route: '/users', handlers: { get: () => {}, post: () => {} }, meta: { get: { description: 'list users' } } }
|
|
50
|
+
],
|
|
51
|
+
childRouters: [],
|
|
52
|
+
parentRouter: null,
|
|
53
|
+
flattenRouters: function () {
|
|
54
|
+
return [this]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const map = generateRouterMap(mockRouter)
|
|
59
|
+
const keys = Object.keys(map)
|
|
60
|
+
|
|
61
|
+
assert.ok(keys.length > 0)
|
|
62
|
+
const endpoints = map[keys[0]]
|
|
63
|
+
assert.ok(Array.isArray(endpoints))
|
|
64
|
+
assert.equal(endpoints[0].url, 'http://localhost:5000/api/users')
|
|
65
|
+
assert.ok('get' in endpoints[0].accepted_methods)
|
|
66
|
+
assert.ok('post' in endpoints[0].accepted_methods)
|
|
67
|
+
assert.deepEqual(endpoints[0].accepted_methods.get, { description: 'list users' })
|
|
68
|
+
assert.deepEqual(endpoints[0].accepted_methods.post, {})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should use relative route keys for child routers', () => {
|
|
72
|
+
const childRouter = {
|
|
73
|
+
root: 'users',
|
|
74
|
+
path: '/api/users',
|
|
75
|
+
url: 'http://localhost:5000/api/users',
|
|
76
|
+
routes: [
|
|
77
|
+
{ route: '/:id', handlers: { get: () => {} } }
|
|
78
|
+
],
|
|
79
|
+
childRouters: [],
|
|
80
|
+
parentRouter: null
|
|
81
|
+
}
|
|
82
|
+
const mockRouter = {
|
|
83
|
+
root: 'api',
|
|
84
|
+
route: '/api',
|
|
85
|
+
path: '/api',
|
|
86
|
+
url: 'http://localhost:5000/api',
|
|
87
|
+
routes: [],
|
|
88
|
+
childRouters: [childRouter],
|
|
89
|
+
flattenRouters: function () {
|
|
90
|
+
return [this, childRouter]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
childRouter.parentRouter = mockRouter
|
|
94
|
+
|
|
95
|
+
const map = generateRouterMap(mockRouter)
|
|
96
|
+
const keys = Object.keys(map)
|
|
97
|
+
|
|
98
|
+
assert.ok(keys.some(k => k.includes('users')))
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('should sort routers alphabetically by root', () => {
|
|
102
|
+
const routerB = {
|
|
103
|
+
root: 'b-router',
|
|
104
|
+
path: '/api/b-router',
|
|
105
|
+
url: 'http://localhost:5000/api/b-router',
|
|
106
|
+
routes: [{ route: '/data', handlers: { get: () => {} } }],
|
|
107
|
+
childRouters: [],
|
|
108
|
+
parentRouter: null
|
|
109
|
+
}
|
|
110
|
+
const routerA = {
|
|
111
|
+
root: 'a-router',
|
|
112
|
+
path: '/api/a-router',
|
|
113
|
+
url: 'http://localhost:5000/api/a-router',
|
|
114
|
+
routes: [{ route: '/data', handlers: { get: () => {} } }],
|
|
115
|
+
childRouters: [],
|
|
116
|
+
parentRouter: null
|
|
117
|
+
}
|
|
118
|
+
const mockRouter = {
|
|
119
|
+
root: 'api',
|
|
120
|
+
route: '/api',
|
|
121
|
+
path: '/api',
|
|
122
|
+
url: 'http://localhost:5000/api',
|
|
123
|
+
routes: [],
|
|
124
|
+
childRouters: [routerB, routerA],
|
|
125
|
+
flattenRouters: function () {
|
|
126
|
+
return [routerB, routerA]
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
routerA.parentRouter = mockRouter
|
|
130
|
+
routerB.parentRouter = mockRouter
|
|
131
|
+
|
|
132
|
+
const map = generateRouterMap(mockRouter)
|
|
133
|
+
const keys = Object.keys(map)
|
|
134
|
+
|
|
135
|
+
assert.ok(keys[0].includes('a-router'))
|
|
136
|
+
assert.ok(keys[1].includes('b-router'))
|
|
137
|
+
})
|
|
138
|
+
})
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { getAllRoutes } from '../lib/utils/getAllRoutes.js'
|
|
4
|
+
|
|
5
|
+
describe('getAllRoutes()', () => {
|
|
6
|
+
it('should collect routes from a single router', () => {
|
|
7
|
+
const mockRouter = {
|
|
8
|
+
path: '/api',
|
|
9
|
+
routes: [
|
|
10
|
+
{ route: '/users', handlers: { get: () => {}, post: () => {} } },
|
|
11
|
+
{ route: '/posts', handlers: { get: () => {} } }
|
|
12
|
+
],
|
|
13
|
+
flattenRouters: () => [mockRouter]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const routeMap = getAllRoutes(mockRouter)
|
|
17
|
+
|
|
18
|
+
assert.ok(routeMap instanceof Map)
|
|
19
|
+
assert.equal(routeMap.size, 2)
|
|
20
|
+
assert.ok(routeMap.has('/api/users'))
|
|
21
|
+
assert.ok(routeMap.has('/api/posts'))
|
|
22
|
+
assert.ok(routeMap.get('/api/users').has('GET'))
|
|
23
|
+
assert.ok(routeMap.get('/api/users').has('POST'))
|
|
24
|
+
assert.ok(routeMap.get('/api/posts').has('GET'))
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should collect routes from multiple routers in hierarchy', () => {
|
|
28
|
+
const childRouter = {
|
|
29
|
+
path: '/api/v1',
|
|
30
|
+
routes: [
|
|
31
|
+
{ route: '/items', handlers: { get: () => {} } }
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
const parentRouter = {
|
|
35
|
+
path: '/api',
|
|
36
|
+
routes: [
|
|
37
|
+
{ route: '/health', handlers: { get: () => {} } }
|
|
38
|
+
],
|
|
39
|
+
flattenRouters: () => [parentRouter, childRouter]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const routeMap = getAllRoutes(parentRouter)
|
|
43
|
+
|
|
44
|
+
assert.equal(routeMap.size, 2)
|
|
45
|
+
assert.ok(routeMap.has('/api/health'))
|
|
46
|
+
assert.ok(routeMap.has('/api/v1/items'))
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should handle root path "/" by omitting it from the prefix', () => {
|
|
50
|
+
const mockRouter = {
|
|
51
|
+
path: '/',
|
|
52
|
+
routes: [
|
|
53
|
+
{ route: '/status', handlers: { get: () => {} } }
|
|
54
|
+
],
|
|
55
|
+
flattenRouters: () => [mockRouter]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const routeMap = getAllRoutes(mockRouter)
|
|
59
|
+
|
|
60
|
+
assert.ok(routeMap.has('/status'))
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should return empty map for router with no routes', () => {
|
|
64
|
+
const mockRouter = {
|
|
65
|
+
path: '/api',
|
|
66
|
+
routes: [],
|
|
67
|
+
flattenRouters: () => [mockRouter]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const routeMap = getAllRoutes(mockRouter)
|
|
71
|
+
|
|
72
|
+
assert.equal(routeMap.size, 0)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('should uppercase method names', () => {
|
|
76
|
+
const mockRouter = {
|
|
77
|
+
path: '/api',
|
|
78
|
+
routes: [
|
|
79
|
+
{ route: '/data', handlers: { get: () => {}, post: () => {}, delete: () => {} } }
|
|
80
|
+
],
|
|
81
|
+
flattenRouters: () => [mockRouter]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const routeMap = getAllRoutes(mockRouter)
|
|
85
|
+
const methods = routeMap.get('/api/data')
|
|
86
|
+
|
|
87
|
+
assert.ok(methods.has('GET'))
|
|
88
|
+
assert.ok(methods.has('POST'))
|
|
89
|
+
assert.ok(methods.has('DELETE'))
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should merge methods for duplicate paths across routers', () => {
|
|
93
|
+
const router1 = {
|
|
94
|
+
path: '/api',
|
|
95
|
+
routes: [{ route: '/users', handlers: { get: () => {} } }]
|
|
96
|
+
}
|
|
97
|
+
const router2 = {
|
|
98
|
+
path: '/api',
|
|
99
|
+
routes: [{ route: '/users', handlers: { post: () => {} } }]
|
|
100
|
+
}
|
|
101
|
+
const parentRouter = {
|
|
102
|
+
path: '/api',
|
|
103
|
+
routes: [],
|
|
104
|
+
flattenRouters: () => [router1, router2]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const routeMap = getAllRoutes(parentRouter)
|
|
108
|
+
|
|
109
|
+
assert.equal(routeMap.size, 1)
|
|
110
|
+
assert.ok(routeMap.get('/api/users').has('GET'))
|
|
111
|
+
assert.ok(routeMap.get('/api/users').has('POST'))
|
|
112
|
+
})
|
|
113
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { mapHandler } from '../lib/utils/mapHandler.js'
|
|
4
|
+
|
|
5
|
+
describe('mapHandler()', () => {
|
|
6
|
+
it('should return a function', () => {
|
|
7
|
+
const mockRouter = { map: { test: 'data' } }
|
|
8
|
+
const handler = mapHandler(mockRouter)
|
|
9
|
+
|
|
10
|
+
assert.equal(typeof handler, 'function')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('should respond with router map as JSON', () => {
|
|
14
|
+
const mockRouter = { map: { test: 'data' } }
|
|
15
|
+
const handler = mapHandler(mockRouter)
|
|
16
|
+
const req = {}
|
|
17
|
+
let responseData = null
|
|
18
|
+
const res = {
|
|
19
|
+
json: (data) => { responseData = data }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
handler(req, res)
|
|
23
|
+
|
|
24
|
+
assert.deepEqual(responseData, { test: 'data' })
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should return the current map value on each call', () => {
|
|
28
|
+
const mockRouter = { map: { initial: true } }
|
|
29
|
+
const handler = mapHandler(mockRouter)
|
|
30
|
+
const res = { json: () => {} }
|
|
31
|
+
|
|
32
|
+
let captured
|
|
33
|
+
res.json = (data) => { captured = data }
|
|
34
|
+
|
|
35
|
+
handler({}, res)
|
|
36
|
+
assert.deepEqual(captured, { initial: true })
|
|
37
|
+
|
|
38
|
+
mockRouter.map = { updated: true }
|
|
39
|
+
handler({}, res)
|
|
40
|
+
assert.deepEqual(captured, { updated: true })
|
|
41
|
+
})
|
|
42
|
+
})
|