adapt-authoring-adaptframework 2.0.9 → 2.1.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.
@@ -1,10 +1,10 @@
1
1
  import _ from 'lodash'
2
2
  import { App, Hook, ensureDir, writeJson } from 'adapt-authoring-core'
3
3
  import { parseObjectId } from 'adapt-authoring-mongodb'
4
- import { createWriteStream } from 'fs'
4
+ import { createWriteStream } from 'node:fs'
5
5
  import AdaptCli from 'adapt-cli'
6
6
  import { log, logDir, logMemory, copyFrameworkSource } from './utils.js'
7
- import fs from 'fs/promises'
7
+ import fs from 'node:fs/promises'
8
8
  import path from 'upath'
9
9
  import semver from 'semver'
10
10
  import zipper from 'zipper'
@@ -1,6 +1,6 @@
1
1
  import { App, Hook, spawn, readJson, writeJson } from 'adapt-authoring-core'
2
2
  import { parseObjectId } from 'adapt-authoring-mongodb'
3
- import fs from 'fs/promises'
3
+ import fs from 'node:fs/promises'
4
4
  import { glob } from 'glob'
5
5
  import octopus from 'adapt-octopus'
6
6
  import path from 'upath'
@@ -630,7 +630,8 @@ class AdaptFrameworkImport {
630
630
 
631
631
  let managedPluginUpdateBlocked = false
632
632
  Object.keys(this.usedContentPlugins).forEach(p => {
633
- const installedP = this.installedPlugins[p]
633
+ const { name: pluginName } = this.usedContentPlugins[p]
634
+ const installedP = this.installedPlugins[pluginName]
634
635
  let { version: importVersion } = this.usedContentPlugins[p]
635
636
  if (!semver.valid(importVersion)) {
636
637
  if (!installedP) {
@@ -677,8 +678,9 @@ class AdaptFrameworkImport {
677
678
  // pre-process: store update metadata and fix missing targetAttributes
678
679
  for (const p of allPlugins) {
679
680
  const isUpdate = pluginsToUpdate.includes(p)
680
- if (isUpdate && this.installedPlugins[p]) {
681
- this.updatedContentPlugins[p] = this.installedPlugins[p]
681
+ const { name: pName } = this.usedContentPlugins[p]
682
+ if (isUpdate && this.installedPlugins[pName]) {
683
+ this.updatedContentPlugins[p] = this.installedPlugins[pName]
682
684
  }
683
685
  const pluginBowerPath = path.join(this.usedContentPlugins[p].path, 'bower.json')
684
686
  const bowerJson = await readJson(pluginBowerPath)
@@ -1,11 +1,11 @@
1
1
  import { AbstractModule, Hook, readJson } from 'adapt-authoring-core'
2
2
  import AdaptFrameworkBuild from './AdaptFrameworkBuild.js'
3
3
  import AdaptFrameworkImport from './AdaptFrameworkImport.js'
4
- import ApiDefs from './apidefs.js'
5
- import fs from 'fs/promises'
4
+ import fs from 'node:fs/promises'
6
5
  import { getHandler, postHandler, importHandler, postUpdateHandler, getUpdateHandler } from './handlers.js'
6
+ import { loadRouteConfig } from 'adapt-authoring-server'
7
7
  import { runCliCommand } from './utils.js'
8
- import path from 'path'
8
+ import path from 'node:path'
9
9
  import semver from 'semver'
10
10
 
11
11
  /**
@@ -229,44 +229,16 @@ class AdaptFrameworkModule extends AbstractModule {
229
229
  * Router for handling all API calls
230
230
  * @type {Router}
231
231
  */
232
- this.apiRouter = server.api.createChildRouter('adapt')
233
- this.apiRouter.addRoute(
234
- {
235
- route: '/preview/:id',
236
- handlers: { post: postHandler },
237
- meta: ApiDefs.preview
238
- },
239
- {
240
- route: '/publish/:id',
241
- handlers: { post: postHandler, get: getHandler },
242
- meta: ApiDefs.publish
243
- },
244
- {
245
- route: '/import',
246
- handlers: { post: [importHandler] },
247
- meta: ApiDefs.import
248
- },
249
- {
250
- route: '/export/:id',
251
- handlers: { post: postHandler, get: getHandler },
252
- meta: ApiDefs.export
232
+ const config = await loadRouteConfig(this.rootDir, this, {
233
+ handlerAliases: { getHandler, postHandler, importHandler, postUpdateHandler, getUpdateHandler }
234
+ })
235
+ this.apiRouter = server.api.createChildRouter(config.root)
236
+ for (const r of config.routes) {
237
+ this.apiRouter.addRoute(r)
238
+ if (!r.permissions) continue
239
+ for (const [method, perms] of Object.entries(r.permissions)) {
240
+ auth.secureRoute(`${this.apiRouter.path}${r.route}`, method, perms)
253
241
  }
254
- )
255
- auth.secureRoute(`${this.apiRouter.path}/preview/:id`, 'post', ['preview:adapt'])
256
- auth.secureRoute(`${this.apiRouter.path}/publish/:id`, 'get', ['publish:adapt'])
257
- auth.secureRoute(`${this.apiRouter.path}/publish/:id`, 'post', ['publish:adapt'])
258
- auth.secureRoute(`${this.apiRouter.path}/import`, 'post', ['import:adapt'])
259
- auth.secureRoute(`${this.apiRouter.path}/export/:id`, 'get', ['export:adapt'])
260
- auth.secureRoute(`${this.apiRouter.path}/export/:id`, 'post', ['export:adapt'])
261
- auth.secureRoute(`${this.apiRouter.path}/update`, 'post', ['update:adapt'])
262
-
263
- if (this.getConfig('enableUpdateApi')) {
264
- this.apiRouter.addRoute({
265
- route: '/update',
266
- handlers: { post: postUpdateHandler, get: getUpdateHandler },
267
- meta: ApiDefs.update
268
- })
269
- auth.secureRoute(`${this.apiRouter.path}/update`, 'get', ['update:adapt'])
270
242
  }
271
243
  }
272
244
 
package/lib/handlers.js CHANGED
@@ -127,8 +127,11 @@ export async function importHandler (req, res, next) {
127
127
  */
128
128
  export async function postUpdateHandler (req, res, next) {
129
129
  try {
130
- log('info', 'running framework update')
131
130
  const framework = await App.instance.waitForModule('adaptframework')
131
+ if (!framework.getConfig('enableUpdateApi')) {
132
+ return next(App.instance.errors.NOT_FOUND.setData({ type: 'route', id: req.originalUrl }))
133
+ }
134
+ log('info', 'running framework update')
132
135
  const previousVersion = framework.version
133
136
  await framework.updateFramework(req.body.version)
134
137
  const currentVersion = framework.version !== previousVersion ? framework.version : undefined
@@ -151,6 +154,9 @@ export async function postUpdateHandler (req, res, next) {
151
154
  export async function getUpdateHandler (req, res, next) {
152
155
  try {
153
156
  const framework = await App.instance.waitForModule('adaptframework')
157
+ if (!framework.getConfig('enableUpdateApi')) {
158
+ return next(App.instance.errors.NOT_FOUND.setData({ type: 'route', id: req.originalUrl }))
159
+ }
154
160
  const current = framework.version
155
161
  const latest = await framework.getLatestVersion()
156
162
  res.json({
@@ -1,5 +1,5 @@
1
1
  import { App } from 'adapt-authoring-core'
2
- import fs from 'fs/promises'
2
+ import fs from 'node:fs/promises'
3
3
  import path from 'upath'
4
4
 
5
5
  /**
package/lib/utils/log.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { App } from 'adapt-authoring-core'
2
2
  import bytes from 'bytes'
3
- import fsSync from 'fs'
3
+ import fsSync from 'node:fs'
4
4
  import path from 'upath'
5
5
 
6
6
  let fw
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-authoring-adaptframework",
3
- "version": "2.0.9",
3
+ "version": "2.1.0",
4
4
  "description": "Adapt framework integration for the Adapt authoring tool",
5
5
  "homepage": "https://github.com/adapt-security/adapt-authoring-adaptframework",
6
6
  "license": "GPL-3.0",
@@ -8,7 +8,7 @@
8
8
  "main": "index.js",
9
9
  "repository": "github:adapt-security/adapt-authoring-adaptframework",
10
10
  "scripts": {
11
- "test": "node --test 'tests/**/*.spec.js'"
11
+ "test": "node --test --test-force-exit --experimental-test-module-mocks 'tests/**/*.spec.js'"
12
12
  },
13
13
  "dependencies": {
14
14
  "adapt-authoring-browserslist": "^1.3.4",
@@ -34,7 +34,7 @@
34
34
  "adapt-authoring-auth": "^2.0.0",
35
35
  "adapt-authoring-jsonschema": "^1.1.5",
36
36
  "adapt-authoring-middleware": "^1.0.1",
37
- "adapt-authoring-server": "^2.0.0",
37
+ "adapt-authoring-server": "^2.1.0",
38
38
  "adapt-authoring-tags": "^1.0.1"
39
39
  },
40
40
  "devDependencies": {
package/routes.json ADDED
@@ -0,0 +1,216 @@
1
+ {
2
+ "root": "adapt",
3
+ "routes": [
4
+ {
5
+ "route": "/preview/:id",
6
+ "handlers": { "post": "postHandler" },
7
+ "permissions": { "post": ["preview:adapt"] },
8
+ "meta": {
9
+ "post": {
10
+ "summary": "Build a preview of an Adapt course",
11
+ "responses": {
12
+ "200": {
13
+ "description": "The Adapt build data",
14
+ "content": {
15
+ "application/json": {
16
+ "schema": { "$ref": "#components/schemas/adaptbuild" }
17
+ }
18
+ }
19
+ }
20
+ }
21
+ }
22
+ }
23
+ },
24
+ {
25
+ "route": "/publish/:id",
26
+ "handlers": { "post": "postHandler", "get": "getHandler" },
27
+ "permissions": { "post": ["publish:adapt"], "get": ["publish:adapt"] },
28
+ "meta": {
29
+ "post": {
30
+ "summary": "Create a publish zip of an Adapt course",
31
+ "responses": {
32
+ "200": {
33
+ "description": "The Adapt build data",
34
+ "content": {
35
+ "application/json": {
36
+ "schema": { "$ref": "#components/schemas/adaptbuild" }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ },
42
+ "get": {
43
+ "summary": "Retrieve an Adapt course publish zip",
44
+ "responses": {
45
+ "200": {
46
+ "description": "Course build zip file",
47
+ "content": { "application/zip": {} }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ },
53
+ {
54
+ "route": "/import",
55
+ "handlers": { "post": "importHandler" },
56
+ "permissions": { "post": ["import:adapt"] },
57
+ "meta": {
58
+ "post": {
59
+ "summary": "Import an Adapt course",
60
+ "requestBody": {
61
+ "content": {
62
+ "application/json": {
63
+ "schema": {
64
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
65
+ "type": "object",
66
+ "properties": {
67
+ "path": { "type": "String" },
68
+ "isDryRun": { "type": "Boolean", "default": false },
69
+ "importContent": { "type": "Boolean", "default": true },
70
+ "importPlugins": { "type": "Boolean", "default": true },
71
+ "updatePlugins": { "type": "Boolean", "default": false }
72
+ }
73
+ }
74
+ }
75
+ }
76
+ },
77
+ "responses": {
78
+ "200": {
79
+ "description": "",
80
+ "content": {
81
+ "application/json": {
82
+ "schema": {
83
+ "properties": {
84
+ "title": { "type": "string" },
85
+ "courseId": { "type": "string" },
86
+ "versions": { "type": "object" },
87
+ "content": {
88
+ "type": "object",
89
+ "properties": {
90
+ "course": { "type": "number" },
91
+ "config": { "type": "number" },
92
+ "menu": { "type": "number" },
93
+ "page": { "type": "number" },
94
+ "article": { "type": "number" },
95
+ "block": { "type": "number" },
96
+ "component": { "type": "number" }
97
+ }
98
+ },
99
+ "statusReport": {
100
+ "type": "object",
101
+ "properties": {
102
+ "error": {
103
+ "type": "array",
104
+ "items": {
105
+ "type": "object",
106
+ "properties": {
107
+ "code": { "type": "string" },
108
+ "data": { "type": "string" }
109
+ }
110
+ }
111
+ },
112
+ "warn": {
113
+ "type": "array",
114
+ "items": {
115
+ "type": "object",
116
+ "properties": {
117
+ "code": { "type": "string" },
118
+ "data": { "type": "string" }
119
+ }
120
+ }
121
+ },
122
+ "info": {
123
+ "type": "array",
124
+ "items": {
125
+ "type": "object",
126
+ "properties": {
127
+ "code": { "type": "string" },
128
+ "data": { "type": "string" }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+ }
142
+ },
143
+ {
144
+ "route": "/export/:id",
145
+ "handlers": { "post": "postHandler", "get": "getHandler" },
146
+ "permissions": { "post": ["export:adapt"], "get": ["export:adapt"] },
147
+ "meta": {
148
+ "post": {
149
+ "summary": "Create an export zip of an Adapt course",
150
+ "responses": {
151
+ "200": {
152
+ "description": "The Adapt build data",
153
+ "content": {
154
+ "application/json": {
155
+ "schema": { "$ref": "#components/schemas/adaptbuild" }
156
+ }
157
+ }
158
+ }
159
+ }
160
+ },
161
+ "get": {
162
+ "summary": "Retrieve an Adapt course export zip",
163
+ "responses": {
164
+ "200": {
165
+ "description": "Course build zip file",
166
+ "content": { "application/zip": {} }
167
+ }
168
+ }
169
+ }
170
+ }
171
+ },
172
+ {
173
+ "route": "/update",
174
+ "handlers": { "post": "postUpdateHandler", "get": "getUpdateHandler" },
175
+ "permissions": { "post": ["update:adapt"], "get": ["update:adapt"] },
176
+ "meta": {
177
+ "post": {
178
+ "summary": "Updates the installed framework",
179
+ "responses": {
180
+ "200": {
181
+ "description": "Describes the upgraded elements",
182
+ "content": {
183
+ "application/json": {
184
+ "schema": {
185
+ "properties": {
186
+ "from": { "type": "string" },
187
+ "to": { "type": "string" }
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+ }
194
+ },
195
+ "get": {
196
+ "summary": "Retrieve framework update data",
197
+ "responses": {
198
+ "200": {
199
+ "content": {
200
+ "application/json": {
201
+ "schema": {
202
+ "properties": {
203
+ "canBeUpdated": { "type": "boolean" },
204
+ "currentVersion": { "type": "string" },
205
+ "latestCompatibleVersion": { "type": "string" }
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }
211
+ }
212
+ }
213
+ }
214
+ }
215
+ ]
216
+ }
@@ -1,6 +1,16 @@
1
- import { describe, it } from 'node:test'
1
+ import { describe, it, mock } from 'node:test'
2
2
  import assert from 'node:assert/strict'
3
- import AdaptFrameworkImport from '../lib/AdaptFrameworkImport.js'
3
+
4
+ // Prevent log() from triggering App.instance boot during tests
5
+ mock.module('../lib/utils/log.js', {
6
+ namedExports: {
7
+ log: async () => {},
8
+ logDir: () => {},
9
+ logMemory: () => {}
10
+ }
11
+ })
12
+
13
+ const { default: AdaptFrameworkImport } = await import('../lib/AdaptFrameworkImport.js')
4
14
 
5
15
  describe('AdaptFrameworkImport', () => {
6
16
  describe('.typeToSchema()', () => {
@@ -324,4 +334,257 @@ describe('AdaptFrameworkImport', () => {
324
334
  assert.equal(ctx.contentJson.contentObjects.art1._type, 'article')
325
335
  })
326
336
  })
337
+
338
+ describe('#cleanUp()', () => {
339
+ const cleanUp = AdaptFrameworkImport.prototype.cleanUp
340
+
341
+ it('should call rollback when an error is passed', async () => {
342
+ let rollbackCalled = false
343
+ const ctx = {
344
+ settings: { removeSource: false },
345
+ rollback: async () => { rollbackCalled = true }
346
+ }
347
+ await cleanUp.call(ctx, new Error('test'))
348
+ assert.equal(rollbackCalled, true)
349
+ })
350
+
351
+ it('should not call rollback when no error is passed', async () => {
352
+ let rollbackCalled = false
353
+ const ctx = {
354
+ settings: { removeSource: false },
355
+ rollback: async () => { rollbackCalled = true }
356
+ }
357
+ await cleanUp.call(ctx, undefined)
358
+ assert.equal(rollbackCalled, false)
359
+ })
360
+
361
+ it('should run rollback even when removeSource is false', async () => {
362
+ let rollbackCalled = false
363
+ const ctx = {
364
+ settings: { removeSource: false },
365
+ rollback: async () => { rollbackCalled = true }
366
+ }
367
+ await cleanUp.call(ctx, new Error('test'))
368
+ assert.equal(rollbackCalled, true)
369
+ })
370
+ })
371
+
372
+ describe('#rollback()', () => {
373
+ const rollback = AdaptFrameworkImport.prototype.rollback
374
+
375
+ function makeRollbackCtx (overrides = {}) {
376
+ return {
377
+ newContentPlugins: {},
378
+ updatedContentPlugins: {},
379
+ assetMap: {},
380
+ newTagIds: [],
381
+ contentJson: { course: {} },
382
+ idMap: {},
383
+ contentplugin: null,
384
+ assets: null,
385
+ content: null,
386
+ courseassets: null,
387
+ ...overrides
388
+ }
389
+ }
390
+
391
+ it('should uninstall newly installed plugins', async () => {
392
+ const uninstalled = []
393
+ const ctx = makeRollbackCtx({
394
+ contentplugin: {
395
+ uninstallPlugin: async (id) => uninstalled.push(id)
396
+ },
397
+ newContentPlugins: {
398
+ 'adapt-contrib-text': { _id: 'p1', name: 'adapt-contrib-text' },
399
+ 'adapt-contrib-gmcq': { _id: 'p2', name: 'adapt-contrib-gmcq' }
400
+ }
401
+ })
402
+ await rollback.call(ctx)
403
+ assert.deepEqual(uninstalled.sort(), ['p1', 'p2'])
404
+ })
405
+
406
+ it('should delete imported assets', async () => {
407
+ const deleted = []
408
+ const ctx = makeRollbackCtx({
409
+ assets: {
410
+ delete: async ({ _id }) => deleted.push(_id)
411
+ },
412
+ assetMap: {
413
+ 'course/en/assets/logo.png': 'a1',
414
+ 'course/en/assets/bg.jpg': 'a2'
415
+ }
416
+ })
417
+ await rollback.call(ctx)
418
+ assert.deepEqual(deleted.sort(), ['a1', 'a2'])
419
+ })
420
+
421
+ it('should delete course content and course assets', async () => {
422
+ const contentDeleted = []
423
+ const courseAssetsDeleted = []
424
+ const ctx = makeRollbackCtx({
425
+ content: {
426
+ deleteMany: async (query) => contentDeleted.push(query)
427
+ },
428
+ courseassets: {
429
+ deleteMany: async (query) => courseAssetsDeleted.push(query)
430
+ },
431
+ contentJson: { course: { _id: 'oldCourseId' } },
432
+ idMap: { oldCourseId: '507f1f77bcf86cd799439011' }
433
+ })
434
+ await rollback.call(ctx)
435
+ assert.equal(contentDeleted.length, 1)
436
+ assert.equal(courseAssetsDeleted.length, 1)
437
+ })
438
+
439
+ it('should skip plugin uninstall when contentplugin is not available', async () => {
440
+ const ctx = makeRollbackCtx({
441
+ contentplugin: null,
442
+ newContentPlugins: { 'adapt-contrib-text': { _id: 'p1', name: 'adapt-contrib-text' } }
443
+ })
444
+ await rollback.call(ctx) // should not throw
445
+ })
446
+
447
+ it('should skip asset deletion when assets module is not available', async () => {
448
+ const ctx = makeRollbackCtx({
449
+ assets: null,
450
+ assetMap: { 'some/path.png': 'a1' }
451
+ })
452
+ await rollback.call(ctx) // should not throw
453
+ })
454
+
455
+ it('should skip content deletion when course ID is not in idMap', async () => {
456
+ const deleted = []
457
+ const ctx = makeRollbackCtx({
458
+ content: {
459
+ deleteMany: async (query) => deleted.push(query)
460
+ },
461
+ courseassets: {
462
+ deleteMany: async (query) => deleted.push(query)
463
+ },
464
+ contentJson: { course: { _id: 'oldCourseId' } },
465
+ idMap: {} // no mapping exists
466
+ })
467
+ await rollback.call(ctx)
468
+ assert.equal(deleted.length, 0)
469
+ })
470
+
471
+ it('should continue cleaning up when an individual asset deletion fails', async () => {
472
+ const deleted = []
473
+ const ctx = makeRollbackCtx({
474
+ assets: {
475
+ delete: async ({ _id }) => {
476
+ if (_id === 'a1') throw new Error('delete failed')
477
+ deleted.push(_id)
478
+ }
479
+ },
480
+ assetMap: {
481
+ 'path/a.png': 'a1',
482
+ 'path/b.png': 'a2',
483
+ 'path/c.png': 'a3'
484
+ }
485
+ })
486
+ await rollback.call(ctx)
487
+ assert.deepEqual(deleted.sort(), ['a2', 'a3'])
488
+ })
489
+
490
+ it('should continue cleaning up when an individual plugin uninstall fails', async () => {
491
+ const uninstalled = []
492
+ const ctx = makeRollbackCtx({
493
+ contentplugin: {
494
+ uninstallPlugin: async (id) => {
495
+ if (id === 'p1') throw new Error('uninstall failed')
496
+ uninstalled.push(id)
497
+ }
498
+ },
499
+ newContentPlugins: {
500
+ 'plugin-a': { _id: 'p1', name: 'plugin-a' },
501
+ 'plugin-b': { _id: 'p2', name: 'plugin-b' }
502
+ }
503
+ })
504
+ await rollback.call(ctx)
505
+ assert.deepEqual(uninstalled, ['p2'])
506
+ })
507
+ })
508
+
509
+ describe('#importCoursePlugins() - early missing plugin detection', () => {
510
+ const importCoursePlugins = AdaptFrameworkImport.prototype.importCoursePlugins
511
+
512
+ function makePluginCtx (overrides = {}) {
513
+ return {
514
+ configEnabledPlugins: [],
515
+ usedContentPlugins: {},
516
+ installedPlugins: {},
517
+ newContentPlugins: {},
518
+ updatedContentPlugins: {},
519
+ pluginsToMigrate: ['core'],
520
+ componentNameMap: {},
521
+ settings: { isDryRun: false, importPlugins: true, updatePlugins: false },
522
+ statusReport: { info: [], warn: [], error: [] },
523
+ contentplugin: {
524
+ find: async () => []
525
+ },
526
+ ...overrides
527
+ }
528
+ }
529
+
530
+ it('should report missing plugins in statusReport during dry run', async () => {
531
+ const ctx = makePluginCtx({
532
+ configEnabledPlugins: ['adapt-contrib-text', 'adapt-contrib-missing'],
533
+ usedContentPlugins: { 'adapt-contrib-text': { version: '1.0.0' } },
534
+ settings: { isDryRun: true, importPlugins: true, updatePlugins: false },
535
+ contentplugin: { find: async () => [] }
536
+ })
537
+ await importCoursePlugins.call(ctx)
538
+ assert.equal(ctx.statusReport.error.length, 1)
539
+ assert.equal(ctx.statusReport.error[0].code, 'MISSING_PLUGINS')
540
+ assert.deepEqual(ctx.statusReport.error[0].data, ['adapt-contrib-missing'])
541
+ })
542
+
543
+ it('should not report error when config plugins exist in the import package', async () => {
544
+ const ctx = makePluginCtx({
545
+ configEnabledPlugins: ['adapt-contrib-text'],
546
+ usedContentPlugins: { 'adapt-contrib-text': { name: 'adapt-contrib-text', version: '1.0.0', type: 'component' } },
547
+ contentplugin: {
548
+ find: async () => [{ name: 'adapt-contrib-text', version: '1.0.0', targetAttribute: '_text', isLocalInstall: true }]
549
+ }
550
+ })
551
+ await importCoursePlugins.call(ctx)
552
+ assert.equal(ctx.statusReport.error.length, 0)
553
+ })
554
+
555
+ it('should not report error when config plugins are installed on the server', async () => {
556
+ const ctx = makePluginCtx({
557
+ configEnabledPlugins: ['adapt-contrib-text'],
558
+ usedContentPlugins: {},
559
+ contentplugin: {
560
+ find: async () => [{ name: 'adapt-contrib-text', version: '1.0.0', targetAttribute: '_text' }]
561
+ }
562
+ })
563
+ await importCoursePlugins.call(ctx)
564
+ assert.equal(ctx.statusReport.error.length, 0)
565
+ })
566
+
567
+ it('should not report error when configEnabledPlugins is empty', async () => {
568
+ const ctx = makePluginCtx({
569
+ configEnabledPlugins: [],
570
+ contentplugin: { find: async () => [] }
571
+ })
572
+ await importCoursePlugins.call(ctx)
573
+ assert.equal(ctx.statusReport.error.length, 0)
574
+ })
575
+
576
+ it('should only flag plugins missing from both package and server', async () => {
577
+ const ctx = makePluginCtx({
578
+ configEnabledPlugins: ['adapt-contrib-text', 'adapt-contrib-gmcq', 'adapt-contrib-missing'],
579
+ usedContentPlugins: { 'adapt-contrib-text': { version: '1.0.0' } },
580
+ settings: { isDryRun: true, importPlugins: true, updatePlugins: false },
581
+ contentplugin: {
582
+ find: async () => [{ name: 'adapt-contrib-gmcq', version: '2.0.0', targetAttribute: '_gmcq' }]
583
+ }
584
+ })
585
+ await importCoursePlugins.call(ctx)
586
+ assert.equal(ctx.statusReport.error.length, 1)
587
+ assert.deepEqual(ctx.statusReport.error[0].data, ['adapt-contrib-missing'])
588
+ })
589
+ })
327
590
  })
package/lib/apidefs.js DELETED
@@ -1,153 +0,0 @@
1
- const responseDataMeta = {
2
- 200: {
3
- description: 'The Adapt build data',
4
- content: {
5
- 'application/json': {
6
- schema: { $ref: '#components/schemas/adaptbuild' }
7
- }
8
- }
9
- }
10
- }
11
-
12
- const responseZipMeta = {
13
- 200: {
14
- description: 'Course build zip file',
15
- content: { 'application/zip': {} }
16
- }
17
- }
18
-
19
- const statusReportItemSchema = {
20
- type: 'array',
21
- items: {
22
- type: 'object',
23
- properties: {
24
- code: { type: 'string' },
25
- data: { type: 'string' }
26
- }
27
- }
28
- }
29
-
30
- export default {
31
- preview: {
32
- post: {
33
- summary: 'Build a preview of an Adapt course',
34
- responses: responseDataMeta
35
- }
36
- },
37
- publish: {
38
- post: {
39
- summary: 'Create a publish zip of an Adapt course',
40
- responses: responseDataMeta
41
- },
42
- get: {
43
- summary: 'Retrieve an Adapt course publish zip',
44
- responses: responseZipMeta
45
- }
46
- },
47
- import: {
48
- post: {
49
- summary: 'Import an Adapt course',
50
- requestBody: {
51
- content: {
52
- 'application/json': {
53
- schema: {
54
- $schema: 'https://json-schema.org/draft/2020-12/schema',
55
- type: 'object',
56
- properties: {
57
- path: { type: 'String' },
58
- isDryRun: { type: 'Boolean', default: false },
59
- importContent: { type: 'Boolean', default: true },
60
- importPlugins: { type: 'Boolean', default: true },
61
- updatePlugins: { type: 'Boolean', default: false }
62
- }
63
- }
64
- }
65
- }
66
- },
67
- responses: {
68
- 200: {
69
- description: '',
70
- content: {
71
- 'application/json': {
72
- schema: {
73
- properties: {
74
- title: { type: 'string' },
75
- courseId: { type: 'string' },
76
- versions: { type: 'object' },
77
- content: {
78
- type: 'object',
79
- properties: {
80
- course: { type: 'number' },
81
- config: { type: 'number' },
82
- menu: { type: 'number' },
83
- page: { type: 'number' },
84
- article: { type: 'number' },
85
- block: { type: 'number' },
86
- component: { type: 'number' }
87
- }
88
- },
89
- statusReport: {
90
- type: 'object',
91
- properties: {
92
- error: statusReportItemSchema,
93
- warn: statusReportItemSchema,
94
- info: statusReportItemSchema
95
- }
96
- }
97
- }
98
- }
99
- }
100
- }
101
- }
102
- }
103
- }
104
- },
105
- export: {
106
- post: {
107
- summary: 'Create an export zip of an Adapt course',
108
- responses: responseDataMeta
109
- },
110
- get: {
111
- summary: 'Retrieve an Adapt course export zip',
112
- responses: responseZipMeta
113
- }
114
- },
115
- update: {
116
- post: {
117
- summary: 'Updates the installed framework',
118
- responses: {
119
- 200: {
120
- description: 'Describes the upgraded elements',
121
- content: {
122
- 'application/json': {
123
- schema: {
124
- properties: {
125
- from: { type: 'string' },
126
- to: { type: 'string' }
127
- }
128
- }
129
- }
130
- }
131
- }
132
- }
133
- },
134
- get: {
135
- summary: 'Retrieve framework update data',
136
- responses: {
137
- 200: {
138
- content: {
139
- 'application/json': {
140
- schema: {
141
- properties: {
142
- canBeUpdated: { type: 'boolean' },
143
- currentVersion: { type: 'string' },
144
- latestCompatibleVersion: { type: 'string' }
145
- }
146
- }
147
- }
148
- }
149
- }
150
- }
151
- }
152
- }
153
- }