adapt-authoring-adaptframework 2.0.9 → 2.0.10
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adapt-authoring-adaptframework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.10",
|
|
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",
|
|
@@ -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
|
-
|
|
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,258 @@ 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
|
+
const rollback = AdaptFrameworkImport.prototype.rollback
|
|
341
|
+
|
|
342
|
+
it('should call rollback when an error is passed', async () => {
|
|
343
|
+
let rollbackCalled = false
|
|
344
|
+
const ctx = {
|
|
345
|
+
settings: { removeSource: false },
|
|
346
|
+
rollback: async () => { rollbackCalled = true }
|
|
347
|
+
}
|
|
348
|
+
await cleanUp.call(ctx, new Error('test'))
|
|
349
|
+
assert.equal(rollbackCalled, true)
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
it('should not call rollback when no error is passed', async () => {
|
|
353
|
+
let rollbackCalled = false
|
|
354
|
+
const ctx = {
|
|
355
|
+
settings: { removeSource: false },
|
|
356
|
+
rollback: async () => { rollbackCalled = true }
|
|
357
|
+
}
|
|
358
|
+
await cleanUp.call(ctx, undefined)
|
|
359
|
+
assert.equal(rollbackCalled, false)
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
it('should run rollback even when removeSource is false', async () => {
|
|
363
|
+
let rollbackCalled = false
|
|
364
|
+
const ctx = {
|
|
365
|
+
settings: { removeSource: false },
|
|
366
|
+
rollback: async () => { rollbackCalled = true }
|
|
367
|
+
}
|
|
368
|
+
await cleanUp.call(ctx, new Error('test'))
|
|
369
|
+
assert.equal(rollbackCalled, true)
|
|
370
|
+
})
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
describe('#rollback()', () => {
|
|
374
|
+
const rollback = AdaptFrameworkImport.prototype.rollback
|
|
375
|
+
|
|
376
|
+
function makeRollbackCtx (overrides = {}) {
|
|
377
|
+
return {
|
|
378
|
+
newContentPlugins: {},
|
|
379
|
+
updatedContentPlugins: {},
|
|
380
|
+
assetMap: {},
|
|
381
|
+
newTagIds: [],
|
|
382
|
+
contentJson: { course: {} },
|
|
383
|
+
idMap: {},
|
|
384
|
+
contentplugin: null,
|
|
385
|
+
assets: null,
|
|
386
|
+
content: null,
|
|
387
|
+
courseassets: null,
|
|
388
|
+
...overrides
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
it('should uninstall newly installed plugins', async () => {
|
|
393
|
+
const uninstalled = []
|
|
394
|
+
const ctx = makeRollbackCtx({
|
|
395
|
+
contentplugin: {
|
|
396
|
+
uninstallPlugin: async (id) => uninstalled.push(id)
|
|
397
|
+
},
|
|
398
|
+
newContentPlugins: {
|
|
399
|
+
'adapt-contrib-text': { _id: 'p1', name: 'adapt-contrib-text' },
|
|
400
|
+
'adapt-contrib-gmcq': { _id: 'p2', name: 'adapt-contrib-gmcq' }
|
|
401
|
+
}
|
|
402
|
+
})
|
|
403
|
+
await rollback.call(ctx)
|
|
404
|
+
assert.deepEqual(uninstalled.sort(), ['p1', 'p2'])
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
it('should delete imported assets', async () => {
|
|
408
|
+
const deleted = []
|
|
409
|
+
const ctx = makeRollbackCtx({
|
|
410
|
+
assets: {
|
|
411
|
+
delete: async ({ _id }) => deleted.push(_id)
|
|
412
|
+
},
|
|
413
|
+
assetMap: {
|
|
414
|
+
'course/en/assets/logo.png': 'a1',
|
|
415
|
+
'course/en/assets/bg.jpg': 'a2'
|
|
416
|
+
}
|
|
417
|
+
})
|
|
418
|
+
await rollback.call(ctx)
|
|
419
|
+
assert.deepEqual(deleted.sort(), ['a1', 'a2'])
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
it('should delete course content and course assets', async () => {
|
|
423
|
+
const contentDeleted = []
|
|
424
|
+
const courseAssetsDeleted = []
|
|
425
|
+
const ctx = makeRollbackCtx({
|
|
426
|
+
content: {
|
|
427
|
+
deleteMany: async (query) => contentDeleted.push(query)
|
|
428
|
+
},
|
|
429
|
+
courseassets: {
|
|
430
|
+
deleteMany: async (query) => courseAssetsDeleted.push(query)
|
|
431
|
+
},
|
|
432
|
+
contentJson: { course: { _id: 'oldCourseId' } },
|
|
433
|
+
idMap: { oldCourseId: '507f1f77bcf86cd799439011' }
|
|
434
|
+
})
|
|
435
|
+
await rollback.call(ctx)
|
|
436
|
+
assert.equal(contentDeleted.length, 1)
|
|
437
|
+
assert.equal(courseAssetsDeleted.length, 1)
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
it('should skip plugin uninstall when contentplugin is not available', async () => {
|
|
441
|
+
const ctx = makeRollbackCtx({
|
|
442
|
+
contentplugin: null,
|
|
443
|
+
newContentPlugins: { 'adapt-contrib-text': { _id: 'p1', name: 'adapt-contrib-text' } }
|
|
444
|
+
})
|
|
445
|
+
await rollback.call(ctx) // should not throw
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
it('should skip asset deletion when assets module is not available', async () => {
|
|
449
|
+
const ctx = makeRollbackCtx({
|
|
450
|
+
assets: null,
|
|
451
|
+
assetMap: { 'some/path.png': 'a1' }
|
|
452
|
+
})
|
|
453
|
+
await rollback.call(ctx) // should not throw
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
it('should skip content deletion when course ID is not in idMap', async () => {
|
|
457
|
+
const deleted = []
|
|
458
|
+
const ctx = makeRollbackCtx({
|
|
459
|
+
content: {
|
|
460
|
+
deleteMany: async (query) => deleted.push(query)
|
|
461
|
+
},
|
|
462
|
+
courseassets: {
|
|
463
|
+
deleteMany: async (query) => deleted.push(query)
|
|
464
|
+
},
|
|
465
|
+
contentJson: { course: { _id: 'oldCourseId' } },
|
|
466
|
+
idMap: {} // no mapping exists
|
|
467
|
+
})
|
|
468
|
+
await rollback.call(ctx)
|
|
469
|
+
assert.equal(deleted.length, 0)
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
it('should continue cleaning up when an individual asset deletion fails', async () => {
|
|
473
|
+
const deleted = []
|
|
474
|
+
const ctx = makeRollbackCtx({
|
|
475
|
+
assets: {
|
|
476
|
+
delete: async ({ _id }) => {
|
|
477
|
+
if (_id === 'a1') throw new Error('delete failed')
|
|
478
|
+
deleted.push(_id)
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
assetMap: {
|
|
482
|
+
'path/a.png': 'a1',
|
|
483
|
+
'path/b.png': 'a2',
|
|
484
|
+
'path/c.png': 'a3'
|
|
485
|
+
}
|
|
486
|
+
})
|
|
487
|
+
await rollback.call(ctx)
|
|
488
|
+
assert.deepEqual(deleted.sort(), ['a2', 'a3'])
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
it('should continue cleaning up when an individual plugin uninstall fails', async () => {
|
|
492
|
+
const uninstalled = []
|
|
493
|
+
const ctx = makeRollbackCtx({
|
|
494
|
+
contentplugin: {
|
|
495
|
+
uninstallPlugin: async (id) => {
|
|
496
|
+
if (id === 'p1') throw new Error('uninstall failed')
|
|
497
|
+
uninstalled.push(id)
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
newContentPlugins: {
|
|
501
|
+
'plugin-a': { _id: 'p1', name: 'plugin-a' },
|
|
502
|
+
'plugin-b': { _id: 'p2', name: 'plugin-b' }
|
|
503
|
+
}
|
|
504
|
+
})
|
|
505
|
+
await rollback.call(ctx)
|
|
506
|
+
assert.deepEqual(uninstalled, ['p2'])
|
|
507
|
+
})
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
describe('#importCoursePlugins() - early missing plugin detection', () => {
|
|
511
|
+
const importCoursePlugins = AdaptFrameworkImport.prototype.importCoursePlugins
|
|
512
|
+
|
|
513
|
+
function makePluginCtx (overrides = {}) {
|
|
514
|
+
return {
|
|
515
|
+
configEnabledPlugins: [],
|
|
516
|
+
usedContentPlugins: {},
|
|
517
|
+
installedPlugins: {},
|
|
518
|
+
newContentPlugins: {},
|
|
519
|
+
updatedContentPlugins: {},
|
|
520
|
+
pluginsToMigrate: ['core'],
|
|
521
|
+
componentNameMap: {},
|
|
522
|
+
settings: { isDryRun: false, importPlugins: true, updatePlugins: false },
|
|
523
|
+
statusReport: { info: [], warn: [], error: [] },
|
|
524
|
+
contentplugin: {
|
|
525
|
+
find: async () => []
|
|
526
|
+
},
|
|
527
|
+
...overrides
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
it('should report missing plugins in statusReport during dry run', async () => {
|
|
532
|
+
const ctx = makePluginCtx({
|
|
533
|
+
configEnabledPlugins: ['adapt-contrib-text', 'adapt-contrib-missing'],
|
|
534
|
+
usedContentPlugins: { 'adapt-contrib-text': { version: '1.0.0' } },
|
|
535
|
+
settings: { isDryRun: true, importPlugins: true, updatePlugins: false },
|
|
536
|
+
contentplugin: { find: async () => [] }
|
|
537
|
+
})
|
|
538
|
+
await importCoursePlugins.call(ctx)
|
|
539
|
+
assert.equal(ctx.statusReport.error.length, 1)
|
|
540
|
+
assert.equal(ctx.statusReport.error[0].code, 'MISSING_PLUGINS')
|
|
541
|
+
assert.deepEqual(ctx.statusReport.error[0].data, ['adapt-contrib-missing'])
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
it('should not report error when config plugins exist in the import package', async () => {
|
|
545
|
+
const ctx = makePluginCtx({
|
|
546
|
+
configEnabledPlugins: ['adapt-contrib-text'],
|
|
547
|
+
usedContentPlugins: { 'adapt-contrib-text': { name: 'adapt-contrib-text', version: '1.0.0', type: 'component' } },
|
|
548
|
+
contentplugin: {
|
|
549
|
+
find: async () => [{ name: 'adapt-contrib-text', version: '1.0.0', targetAttribute: '_text', isLocalInstall: true }]
|
|
550
|
+
}
|
|
551
|
+
})
|
|
552
|
+
await importCoursePlugins.call(ctx)
|
|
553
|
+
assert.equal(ctx.statusReport.error.length, 0)
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
it('should not report error when config plugins are installed on the server', async () => {
|
|
557
|
+
const ctx = makePluginCtx({
|
|
558
|
+
configEnabledPlugins: ['adapt-contrib-text'],
|
|
559
|
+
usedContentPlugins: {},
|
|
560
|
+
contentplugin: {
|
|
561
|
+
find: async () => [{ name: 'adapt-contrib-text', version: '1.0.0', targetAttribute: '_text' }]
|
|
562
|
+
}
|
|
563
|
+
})
|
|
564
|
+
await importCoursePlugins.call(ctx)
|
|
565
|
+
assert.equal(ctx.statusReport.error.length, 0)
|
|
566
|
+
})
|
|
567
|
+
|
|
568
|
+
it('should not report error when configEnabledPlugins is empty', async () => {
|
|
569
|
+
const ctx = makePluginCtx({
|
|
570
|
+
configEnabledPlugins: [],
|
|
571
|
+
contentplugin: { find: async () => [] }
|
|
572
|
+
})
|
|
573
|
+
await importCoursePlugins.call(ctx)
|
|
574
|
+
assert.equal(ctx.statusReport.error.length, 0)
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
it('should only flag plugins missing from both package and server', async () => {
|
|
578
|
+
const ctx = makePluginCtx({
|
|
579
|
+
configEnabledPlugins: ['adapt-contrib-text', 'adapt-contrib-gmcq', 'adapt-contrib-missing'],
|
|
580
|
+
usedContentPlugins: { 'adapt-contrib-text': { version: '1.0.0' } },
|
|
581
|
+
settings: { isDryRun: true, importPlugins: true, updatePlugins: false },
|
|
582
|
+
contentplugin: {
|
|
583
|
+
find: async () => [{ name: 'adapt-contrib-gmcq', version: '2.0.0', targetAttribute: '_gmcq' }]
|
|
584
|
+
}
|
|
585
|
+
})
|
|
586
|
+
await importCoursePlugins.call(ctx)
|
|
587
|
+
assert.equal(ctx.statusReport.error.length, 1)
|
|
588
|
+
assert.deepEqual(ctx.statusReport.error[0].data, ['adapt-contrib-missing'])
|
|
589
|
+
})
|
|
590
|
+
})
|
|
327
591
|
})
|