adapt-authoring-docs 1.3.2 → 1.4.1
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/.github/workflows/standardjs.yml +2 -2
- package/bin/docgen.js +38 -22
- package/bin/docserve.js +26 -38
- package/docsify/docsify.js +3 -3
- package/jsdoc3/jsdoc3.js +5 -5
- package/lib/docsData.js +310 -0
- package/package.json +3 -9
- package/swagger/swagger.js +8 -12
- package/tests/docsData.spec.js +482 -0
- package/tests/swagger.spec.js +15 -33
- package/docs/architecture.md +0 -175
- package/jsdoc3/.jsdocConfig.json +0 -57
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, it, before, after } from 'node:test'
|
|
3
|
+
import fs from 'fs/promises'
|
|
4
|
+
import os from 'node:os'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
|
|
7
|
+
/* eslint-disable no-template-curly-in-string */
|
|
8
|
+
/**
|
|
9
|
+
* Creates a temporary fixture directory that simulates a minimal
|
|
10
|
+
* adapt-authoring app so docsData functions can be tested in isolation.
|
|
11
|
+
*/
|
|
12
|
+
async function createFixture () {
|
|
13
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'docs-data-'))
|
|
14
|
+
|
|
15
|
+
// root package.json + adapt-authoring.json
|
|
16
|
+
await fs.writeFile(path.join(root, 'package.json'), JSON.stringify({
|
|
17
|
+
name: 'adapt-authoring',
|
|
18
|
+
version: '1.0.0'
|
|
19
|
+
}))
|
|
20
|
+
await fs.writeFile(path.join(root, 'adapt-authoring.json'), JSON.stringify({
|
|
21
|
+
module: false,
|
|
22
|
+
documentation: { enable: true },
|
|
23
|
+
essentialApis: []
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
// --- fake api module (library, not a loaded module) ---
|
|
27
|
+
const apiDir = path.join(root, 'node_modules', 'adapt-authoring-api')
|
|
28
|
+
await fs.mkdir(path.join(apiDir, 'lib'), { recursive: true })
|
|
29
|
+
await fs.writeFile(path.join(apiDir, 'package.json'), JSON.stringify({
|
|
30
|
+
name: 'adapt-authoring-api',
|
|
31
|
+
version: '1.0.0'
|
|
32
|
+
}))
|
|
33
|
+
await fs.writeFile(path.join(apiDir, 'adapt-authoring.json'), JSON.stringify({
|
|
34
|
+
module: false,
|
|
35
|
+
documentation: { enable: true }
|
|
36
|
+
}))
|
|
37
|
+
await fs.writeFile(path.join(apiDir, 'lib', 'default-routes.json'), JSON.stringify({
|
|
38
|
+
routes: [
|
|
39
|
+
{
|
|
40
|
+
route: '/',
|
|
41
|
+
handlers: { post: 'requestHandler', get: 'queryHandler' },
|
|
42
|
+
permissions: { post: ['write:${scope}'], get: ['read:${scope}'] }
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
route: '/:_id',
|
|
46
|
+
handlers: { get: 'requestHandler', delete: 'requestHandler' },
|
|
47
|
+
permissions: { get: ['read:${scope}'], delete: ['write:${scope}'] }
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}))
|
|
51
|
+
|
|
52
|
+
// --- fake auth module ---
|
|
53
|
+
const authDir = path.join(root, 'node_modules', 'adapt-authoring-auth')
|
|
54
|
+
await fs.mkdir(path.join(authDir, 'lib'), { recursive: true })
|
|
55
|
+
await fs.writeFile(path.join(authDir, 'package.json'), JSON.stringify({
|
|
56
|
+
name: 'adapt-authoring-auth',
|
|
57
|
+
version: '1.0.0'
|
|
58
|
+
}))
|
|
59
|
+
await fs.writeFile(path.join(authDir, 'adapt-authoring.json'), JSON.stringify({
|
|
60
|
+
documentation: { enable: true }
|
|
61
|
+
}))
|
|
62
|
+
await fs.writeFile(path.join(authDir, 'lib', 'routes.json'), JSON.stringify({
|
|
63
|
+
routes: [
|
|
64
|
+
{
|
|
65
|
+
route: '/check',
|
|
66
|
+
handlers: { get: 'checkHandler' },
|
|
67
|
+
permissions: { get: null }
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}))
|
|
71
|
+
await fs.writeFile(path.join(authDir, 'lib', 'default-routes.json'), JSON.stringify({
|
|
72
|
+
routes: [
|
|
73
|
+
{
|
|
74
|
+
route: '/',
|
|
75
|
+
handlers: { post: 'authenticateHandler' },
|
|
76
|
+
permissions: { post: null }
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
route: '/register',
|
|
80
|
+
handlers: { post: 'registerHandler' },
|
|
81
|
+
permissions: { post: ['register:users'] }
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}))
|
|
85
|
+
|
|
86
|
+
// --- fake auth-local module (auth type) ---
|
|
87
|
+
const authLocalDir = path.join(root, 'node_modules', 'adapt-authoring-auth-local')
|
|
88
|
+
await fs.mkdir(authLocalDir, { recursive: true })
|
|
89
|
+
await fs.writeFile(path.join(authLocalDir, 'package.json'), JSON.stringify({
|
|
90
|
+
name: 'adapt-authoring-auth-local',
|
|
91
|
+
version: '1.0.0'
|
|
92
|
+
}))
|
|
93
|
+
await fs.writeFile(path.join(authLocalDir, 'adapt-authoring.json'), JSON.stringify({
|
|
94
|
+
documentation: { enable: true }
|
|
95
|
+
}))
|
|
96
|
+
await fs.writeFile(path.join(authLocalDir, 'routes.json'), JSON.stringify({
|
|
97
|
+
type: 'local',
|
|
98
|
+
routes: [
|
|
99
|
+
{
|
|
100
|
+
route: '/',
|
|
101
|
+
override: true,
|
|
102
|
+
handlers: { post: 'authenticateHandler' },
|
|
103
|
+
meta: { post: { summary: 'Local auth' } }
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
route: '/changepass',
|
|
107
|
+
handlers: { post: 'changePasswordHandler' },
|
|
108
|
+
permissions: { post: null }
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
}))
|
|
112
|
+
|
|
113
|
+
// --- fake content module (API module) ---
|
|
114
|
+
const contentDir = path.join(root, 'node_modules', 'adapt-authoring-content')
|
|
115
|
+
await fs.mkdir(path.join(contentDir, 'schema'), { recursive: true })
|
|
116
|
+
await fs.mkdir(path.join(contentDir, 'errors'), { recursive: true })
|
|
117
|
+
await fs.mkdir(path.join(contentDir, 'conf'), { recursive: true })
|
|
118
|
+
await fs.writeFile(path.join(contentDir, 'package.json'), JSON.stringify({
|
|
119
|
+
name: 'adapt-authoring-content',
|
|
120
|
+
version: '1.0.0'
|
|
121
|
+
}))
|
|
122
|
+
await fs.writeFile(path.join(contentDir, 'adapt-authoring.json'), JSON.stringify({
|
|
123
|
+
documentation: { enable: true }
|
|
124
|
+
}))
|
|
125
|
+
await fs.writeFile(path.join(contentDir, 'routes.json'), JSON.stringify({
|
|
126
|
+
root: 'content',
|
|
127
|
+
routes: [
|
|
128
|
+
{
|
|
129
|
+
route: '/clone',
|
|
130
|
+
handlers: { post: 'handleClone' },
|
|
131
|
+
permissions: { post: ['write:${scope}'] }
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
}))
|
|
135
|
+
await fs.writeFile(path.join(contentDir, 'schema', 'content.schema.json'), JSON.stringify({
|
|
136
|
+
$anchor: 'content',
|
|
137
|
+
type: 'object',
|
|
138
|
+
properties: {
|
|
139
|
+
title: { type: 'string' },
|
|
140
|
+
body: { type: 'string' }
|
|
141
|
+
}
|
|
142
|
+
}))
|
|
143
|
+
await fs.writeFile(path.join(contentDir, 'errors', 'errors.json'), JSON.stringify({
|
|
144
|
+
CONTENT_NOT_FOUND: { statusCode: 404, description: 'Content not found' }
|
|
145
|
+
}))
|
|
146
|
+
await fs.writeFile(path.join(contentDir, 'conf', 'config.schema.json'), JSON.stringify({
|
|
147
|
+
type: 'object',
|
|
148
|
+
properties: {
|
|
149
|
+
cacheDir: { type: 'string', default: '$TEMP/content-cache' }
|
|
150
|
+
}
|
|
151
|
+
}))
|
|
152
|
+
|
|
153
|
+
// --- fake core module with dataDir/tempDir config ---
|
|
154
|
+
const coreDir = path.join(root, 'node_modules', 'adapt-authoring-core')
|
|
155
|
+
await fs.mkdir(path.join(coreDir, 'conf'), { recursive: true })
|
|
156
|
+
await fs.writeFile(path.join(coreDir, 'package.json'), JSON.stringify({
|
|
157
|
+
name: 'adapt-authoring-core',
|
|
158
|
+
version: '1.0.0'
|
|
159
|
+
}))
|
|
160
|
+
await fs.writeFile(path.join(coreDir, 'adapt-authoring.json'), JSON.stringify({
|
|
161
|
+
module: false
|
|
162
|
+
}))
|
|
163
|
+
await fs.writeFile(path.join(coreDir, 'conf', 'config.schema.json'), JSON.stringify({
|
|
164
|
+
type: 'object',
|
|
165
|
+
properties: {
|
|
166
|
+
dataDir: { type: 'string', isDirectory: true, default: '$ROOT/APP_DATA/data' },
|
|
167
|
+
tempDir: { type: 'string', isDirectory: true, default: '$ROOT/APP_DATA/temp' }
|
|
168
|
+
}
|
|
169
|
+
}))
|
|
170
|
+
|
|
171
|
+
// --- fake docs module with config ---
|
|
172
|
+
const docsDir = path.join(root, 'node_modules', 'adapt-authoring-docs')
|
|
173
|
+
await fs.mkdir(path.join(docsDir, 'conf'), { recursive: true })
|
|
174
|
+
await fs.writeFile(path.join(docsDir, 'package.json'), JSON.stringify({
|
|
175
|
+
name: 'adapt-authoring-docs',
|
|
176
|
+
version: '1.0.0'
|
|
177
|
+
}))
|
|
178
|
+
await fs.writeFile(path.join(docsDir, 'adapt-authoring.json'), JSON.stringify({
|
|
179
|
+
module: false,
|
|
180
|
+
documentation: { enable: true }
|
|
181
|
+
}))
|
|
182
|
+
await fs.writeFile(path.join(docsDir, 'conf', 'config.schema.json'), JSON.stringify({
|
|
183
|
+
type: 'object',
|
|
184
|
+
properties: {
|
|
185
|
+
outputDir: { type: 'string', default: '$TEMP/docs-build' },
|
|
186
|
+
manualSections: {
|
|
187
|
+
type: 'object',
|
|
188
|
+
default: { 'getting-started': {}, 'other-guides': { default: true } }
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}))
|
|
192
|
+
|
|
193
|
+
// --- module with useDefaultRoutes: false ---
|
|
194
|
+
const courseassetDir = path.join(root, 'node_modules', 'adapt-authoring-courseassets')
|
|
195
|
+
await fs.mkdir(courseassetDir, { recursive: true })
|
|
196
|
+
await fs.writeFile(path.join(courseassetDir, 'package.json'), JSON.stringify({
|
|
197
|
+
name: 'adapt-authoring-courseassets',
|
|
198
|
+
version: '1.0.0'
|
|
199
|
+
}))
|
|
200
|
+
await fs.writeFile(path.join(courseassetDir, 'adapt-authoring.json'), JSON.stringify({
|
|
201
|
+
documentation: { enable: true }
|
|
202
|
+
}))
|
|
203
|
+
await fs.writeFile(path.join(courseassetDir, 'routes.json'), JSON.stringify({
|
|
204
|
+
root: 'courseassets',
|
|
205
|
+
useDefaultRoutes: false,
|
|
206
|
+
routes: [
|
|
207
|
+
{
|
|
208
|
+
route: '/query',
|
|
209
|
+
handlers: { post: 'queryHandler' },
|
|
210
|
+
permissions: { post: ['read:${scope}'] }
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
}))
|
|
214
|
+
|
|
215
|
+
// --- module with custom permissionsScope ---
|
|
216
|
+
const themeDir = path.join(root, 'node_modules', 'adapt-authoring-coursetheme')
|
|
217
|
+
await fs.mkdir(themeDir, { recursive: true })
|
|
218
|
+
await fs.writeFile(path.join(themeDir, 'package.json'), JSON.stringify({
|
|
219
|
+
name: 'adapt-authoring-coursetheme',
|
|
220
|
+
version: '1.0.0'
|
|
221
|
+
}))
|
|
222
|
+
await fs.writeFile(path.join(themeDir, 'adapt-authoring.json'), JSON.stringify({
|
|
223
|
+
documentation: { enable: true }
|
|
224
|
+
}))
|
|
225
|
+
await fs.writeFile(path.join(themeDir, 'routes.json'), JSON.stringify({
|
|
226
|
+
root: 'coursethemepresets',
|
|
227
|
+
permissionsScope: 'content',
|
|
228
|
+
routes: [
|
|
229
|
+
{
|
|
230
|
+
route: '/:_id/apply',
|
|
231
|
+
handlers: { post: 'applyHandler' },
|
|
232
|
+
permissions: { post: ['write:${scope}'] }
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
}))
|
|
236
|
+
|
|
237
|
+
return root
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
describe('docsData', () => {
|
|
241
|
+
let fixtureDir
|
|
242
|
+
let dependencies
|
|
243
|
+
|
|
244
|
+
before(async () => {
|
|
245
|
+
fixtureDir = await createFixture()
|
|
246
|
+
const { loadDependencies } = await import('../lib/docsData.js')
|
|
247
|
+
dependencies = await loadDependencies(fixtureDir)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
after(async () => {
|
|
251
|
+
if (fixtureDir) await fs.rm(fixtureDir, { recursive: true, force: true }).catch(() => {})
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
describe('loadDependencies', () => {
|
|
255
|
+
it('should discover all modules with adapt-authoring.json', () => {
|
|
256
|
+
assert.ok(dependencies['adapt-authoring-content'])
|
|
257
|
+
assert.ok(dependencies['adapt-authoring-auth'])
|
|
258
|
+
assert.ok(dependencies['adapt-authoring-auth-local'])
|
|
259
|
+
assert.ok(dependencies['adapt-authoring-docs'])
|
|
260
|
+
assert.ok(dependencies['adapt-authoring-api'])
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('should merge package.json and adapt-authoring.json for each dep', () => {
|
|
264
|
+
const content = dependencies['adapt-authoring-content']
|
|
265
|
+
assert.equal(content.name, 'adapt-authoring-content')
|
|
266
|
+
assert.equal(content.version, '1.0.0')
|
|
267
|
+
assert.ok(content.documentation)
|
|
268
|
+
assert.ok(content.rootDir)
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('should set rootDir on each dependency', () => {
|
|
272
|
+
for (const dep of Object.values(dependencies)) {
|
|
273
|
+
assert.ok(dep.rootDir, `${dep.name} should have rootDir`)
|
|
274
|
+
assert.ok(dep.rootDir.includes('node_modules'), `${dep.name} rootDir should be in node_modules`)
|
|
275
|
+
}
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
describe('loadConfigDefaults', () => {
|
|
280
|
+
it('should resolve $TEMP to the app temp directory', async () => {
|
|
281
|
+
const { loadConfigDefaults } = await import('../lib/docsData.js')
|
|
282
|
+
const config = await loadConfigDefaults(fixtureDir, dependencies)
|
|
283
|
+
const outputDir = config.get('adapt-authoring-docs.outputDir')
|
|
284
|
+
const expectedTemp = path.join(fixtureDir, 'APP_DATA', 'temp')
|
|
285
|
+
assert.ok(outputDir.startsWith(expectedTemp), `expected ${outputDir} to start with ${expectedTemp}`)
|
|
286
|
+
assert.ok(outputDir.endsWith('/docs-build'))
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('should return object defaults', async () => {
|
|
290
|
+
const { loadConfigDefaults } = await import('../lib/docsData.js')
|
|
291
|
+
const config = await loadConfigDefaults(fixtureDir, dependencies)
|
|
292
|
+
const sections = config.get('adapt-authoring-docs.manualSections')
|
|
293
|
+
assert.ok(sections)
|
|
294
|
+
assert.ok(sections['getting-started'] !== undefined)
|
|
295
|
+
assert.ok(sections['other-guides']?.default === true)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
it('should return undefined for unknown config keys', async () => {
|
|
299
|
+
const { loadConfigDefaults } = await import('../lib/docsData.js')
|
|
300
|
+
const config = await loadConfigDefaults(fixtureDir, dependencies)
|
|
301
|
+
assert.equal(config.get('nonexistent.key'), undefined)
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
it('should resolve $TEMP in non-docs modules', async () => {
|
|
305
|
+
const { loadConfigDefaults } = await import('../lib/docsData.js')
|
|
306
|
+
const config = await loadConfigDefaults(fixtureDir, dependencies)
|
|
307
|
+
const cacheDir = config.get('adapt-authoring-content.cacheDir')
|
|
308
|
+
const expectedTemp = path.join(fixtureDir, 'APP_DATA', 'temp')
|
|
309
|
+
assert.ok(cacheDir.startsWith(expectedTemp))
|
|
310
|
+
assert.ok(cacheDir.endsWith('/content-cache'))
|
|
311
|
+
})
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
describe('buildRouterTree', () => {
|
|
315
|
+
it('should return api root router', async () => {
|
|
316
|
+
const { buildRouterTree } = await import('../lib/docsData.js')
|
|
317
|
+
const routerTree = await buildRouterTree(dependencies)
|
|
318
|
+
assert.equal(routerTree.path, '/api')
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
it('should create child routers for API modules', async () => {
|
|
322
|
+
const { buildRouterTree } = await import('../lib/docsData.js')
|
|
323
|
+
const routerTree = await buildRouterTree(dependencies)
|
|
324
|
+
const paths = routerTree.childRouters.map(r => r.path)
|
|
325
|
+
assert.ok(paths.includes('/api/content'))
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
it('should merge default routes for API modules', async () => {
|
|
329
|
+
const { buildRouterTree } = await import('../lib/docsData.js')
|
|
330
|
+
const routerTree = await buildRouterTree(dependencies)
|
|
331
|
+
const contentRouter = routerTree.childRouters.find(r => r.path === '/api/content')
|
|
332
|
+
assert.ok(contentRouter)
|
|
333
|
+
const routePaths = contentRouter.routes.map(r => r.route)
|
|
334
|
+
// default routes: / and /:_id, plus custom: /clone
|
|
335
|
+
assert.ok(routePaths.includes('/'), 'should have default root route')
|
|
336
|
+
assert.ok(routePaths.includes('/:_id'), 'should have default :_id route')
|
|
337
|
+
assert.ok(routePaths.includes('/clone'), 'should have custom clone route')
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
it('should skip default routes when useDefaultRoutes is false', async () => {
|
|
341
|
+
const { buildRouterTree } = await import('../lib/docsData.js')
|
|
342
|
+
const routerTree = await buildRouterTree(dependencies)
|
|
343
|
+
const router = routerTree.childRouters.find(r => r.path === '/api/courseassets')
|
|
344
|
+
assert.ok(router)
|
|
345
|
+
assert.equal(router.routes.length, 1)
|
|
346
|
+
assert.equal(router.routes[0].route, '/query')
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
it('should replace placeholders in route config', async () => {
|
|
350
|
+
const { buildRouterTree } = await import('../lib/docsData.js')
|
|
351
|
+
const routerTree = await buildRouterTree(dependencies)
|
|
352
|
+
const contentRouter = routerTree.childRouters.find(r => r.path === '/api/content')
|
|
353
|
+
const rootRoute = contentRouter.routes.find(r => r.route === '/')
|
|
354
|
+
assert.deepEqual(rootRoute.permissions.post, ['write:content'])
|
|
355
|
+
assert.deepEqual(rootRoute.permissions.get, ['read:content'])
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
it('should use permissionsScope for placeholder when defined', async () => {
|
|
359
|
+
const { buildRouterTree } = await import('../lib/docsData.js')
|
|
360
|
+
const routerTree = await buildRouterTree(dependencies)
|
|
361
|
+
const themeRouter = routerTree.childRouters.find(r => r.path === '/api/coursethemepresets')
|
|
362
|
+
assert.ok(themeRouter)
|
|
363
|
+
const applyRoute = themeRouter.routes.find(r => r.route === '/:_id/apply')
|
|
364
|
+
assert.deepEqual(applyRoute.permissions.post, ['write:content'])
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
it('should create auth router at /api/auth', async () => {
|
|
368
|
+
const { buildRouterTree } = await import('../lib/docsData.js')
|
|
369
|
+
const routerTree = await buildRouterTree(dependencies)
|
|
370
|
+
const authRouter = routerTree.childRouters.find(r => r.path === '/api/auth')
|
|
371
|
+
assert.ok(authRouter)
|
|
372
|
+
assert.ok(authRouter.routes.some(r => r.route === '/check'))
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
it('should create auth type child routers', async () => {
|
|
376
|
+
const { buildRouterTree } = await import('../lib/docsData.js')
|
|
377
|
+
const routerTree = await buildRouterTree(dependencies)
|
|
378
|
+
const authRouter = routerTree.childRouters.find(r => r.path === '/api/auth')
|
|
379
|
+
const localRouter = authRouter.childRouters.find(r => r.path === '/api/auth/local')
|
|
380
|
+
assert.ok(localRouter)
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
it('should merge auth type routes with auth defaults', async () => {
|
|
384
|
+
const { buildRouterTree } = await import('../lib/docsData.js')
|
|
385
|
+
const routerTree = await buildRouterTree(dependencies)
|
|
386
|
+
const authRouter = routerTree.childRouters.find(r => r.path === '/api/auth')
|
|
387
|
+
const localRouter = authRouter.childRouters.find(r => r.path === '/api/auth/local')
|
|
388
|
+
const routePaths = localRouter.routes.map(r => r.route)
|
|
389
|
+
// override route: /, default: /register, custom: /changepass
|
|
390
|
+
assert.ok(routePaths.includes('/'), 'should have root (overridden) route')
|
|
391
|
+
assert.ok(routePaths.includes('/register'), 'should have default register route')
|
|
392
|
+
assert.ok(routePaths.includes('/changepass'), 'should have custom changepass route')
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
it('should apply override merging from auth type routes', async () => {
|
|
396
|
+
const { buildRouterTree } = await import('../lib/docsData.js')
|
|
397
|
+
const routerTree = await buildRouterTree(dependencies)
|
|
398
|
+
const authRouter = routerTree.childRouters.find(r => r.path === '/api/auth')
|
|
399
|
+
const localRouter = authRouter.childRouters.find(r => r.path === '/api/auth/local')
|
|
400
|
+
const rootRoute = localRouter.routes.find(r => r.route === '/')
|
|
401
|
+
// override merged meta from auth-local onto default
|
|
402
|
+
assert.ok(rootRoute.meta?.post?.summary === 'Local auth')
|
|
403
|
+
})
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
describe('loadSchemas', () => {
|
|
407
|
+
it('should load schemas from schema/*.schema.json', async () => {
|
|
408
|
+
const { loadSchemas } = await import('../lib/docsData.js')
|
|
409
|
+
const schemas = await loadSchemas(dependencies)
|
|
410
|
+
assert.ok(schemas.schemas.content)
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
it('should provide getSchema returning built schema', async () => {
|
|
414
|
+
const { loadSchemas } = await import('../lib/docsData.js')
|
|
415
|
+
const schemas = await loadSchemas(dependencies)
|
|
416
|
+
const result = await schemas.getSchema('content')
|
|
417
|
+
assert.ok(result.built)
|
|
418
|
+
assert.equal(result.built.type, 'object')
|
|
419
|
+
assert.ok(result.built.properties.title)
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
it('should return empty object for unknown schemas', async () => {
|
|
423
|
+
const { loadSchemas } = await import('../lib/docsData.js')
|
|
424
|
+
const schemas = await loadSchemas(dependencies)
|
|
425
|
+
const result = await schemas.getSchema('nonexistent')
|
|
426
|
+
assert.deepEqual(result.built, {})
|
|
427
|
+
})
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
describe('loadErrors', () => {
|
|
431
|
+
it('should load and merge errors from errors/*.json', async () => {
|
|
432
|
+
const { loadErrors } = await import('../lib/docsData.js')
|
|
433
|
+
const errors = await loadErrors(dependencies)
|
|
434
|
+
assert.ok(errors.CONTENT_NOT_FOUND)
|
|
435
|
+
assert.equal(errors.CONTENT_NOT_FOUND.statusCode, 404)
|
|
436
|
+
assert.equal(errors.CONTENT_NOT_FOUND.code, 'CONTENT_NOT_FOUND')
|
|
437
|
+
assert.equal(errors.CONTENT_NOT_FOUND.meta.description, 'Content not found')
|
|
438
|
+
})
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
describe('buildPermissions', () => {
|
|
442
|
+
it('should build permission store with HTTP methods', async () => {
|
|
443
|
+
const { buildRouterTree, buildPermissions } = await import('../lib/docsData.js')
|
|
444
|
+
const routerTree = await buildRouterTree(dependencies)
|
|
445
|
+
const perms = buildPermissions(routerTree)
|
|
446
|
+
assert.ok(Array.isArray(perms.get))
|
|
447
|
+
assert.ok(Array.isArray(perms.post))
|
|
448
|
+
assert.ok(Array.isArray(perms.put))
|
|
449
|
+
assert.ok(Array.isArray(perms.patch))
|
|
450
|
+
assert.ok(Array.isArray(perms.delete))
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
it('should contain permission entries for secured routes', async () => {
|
|
454
|
+
const { buildRouterTree, buildPermissions } = await import('../lib/docsData.js')
|
|
455
|
+
const routerTree = await buildRouterTree(dependencies)
|
|
456
|
+
const perms = buildPermissions(routerTree)
|
|
457
|
+
assert.ok(perms.post.length > 0, 'should have POST permission entries')
|
|
458
|
+
assert.ok(perms.get.length > 0, 'should have GET permission entries')
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
it('should skip routes with null permissions (unsecured)', async () => {
|
|
462
|
+
const { buildRouterTree, buildPermissions } = await import('../lib/docsData.js')
|
|
463
|
+
const routerTree = await buildRouterTree(dependencies)
|
|
464
|
+
const perms = buildPermissions(routerTree)
|
|
465
|
+
// /api/auth/check has permissions: { get: null } — should not appear
|
|
466
|
+
const checkMatch = perms.get.find(([re]) => re.test('/api/auth/check'))
|
|
467
|
+
assert.equal(checkMatch, undefined, '/api/auth/check should not be in secured routes')
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
it('should store scopes as arrays in permission entries', async () => {
|
|
471
|
+
const { buildRouterTree, buildPermissions } = await import('../lib/docsData.js')
|
|
472
|
+
const routerTree = await buildRouterTree(dependencies)
|
|
473
|
+
const perms = buildPermissions(routerTree)
|
|
474
|
+
for (const entries of Object.values(perms)) {
|
|
475
|
+
for (const [re, scopes] of entries) {
|
|
476
|
+
assert.ok(re instanceof RegExp, 'first element should be RegExp')
|
|
477
|
+
assert.ok(Array.isArray(scopes), 'second element should be array')
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
})
|
|
481
|
+
})
|
|
482
|
+
})
|
package/tests/swagger.spec.js
CHANGED
|
@@ -331,39 +331,21 @@ function createMockApp (options = {}) {
|
|
|
331
331
|
|
|
332
332
|
return {
|
|
333
333
|
pkg: { version: '1.0.0' },
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
permissions: {
|
|
338
|
-
routes: {
|
|
339
|
-
get: [],
|
|
340
|
-
post: [],
|
|
341
|
-
put: [],
|
|
342
|
-
patch: [],
|
|
343
|
-
delete: []
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
334
|
+
schemas: {
|
|
335
|
+
schemas: mockSchemas,
|
|
336
|
+
getSchema: mock.fn(async (s) => schemas[s])
|
|
348
337
|
},
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
childRouters
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
return {}
|
|
366
|
-
}),
|
|
367
|
-
onReady: mock.fn(async () => {})
|
|
338
|
+
routerTree: {
|
|
339
|
+
path: routerPath,
|
|
340
|
+
routes,
|
|
341
|
+
childRouters
|
|
342
|
+
},
|
|
343
|
+
permissions: {
|
|
344
|
+
get: [],
|
|
345
|
+
post: [],
|
|
346
|
+
put: [],
|
|
347
|
+
patch: [],
|
|
348
|
+
delete: []
|
|
349
|
+
}
|
|
368
350
|
}
|
|
369
351
|
}
|