adapt-authoring-content 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/tests/ContentModule.spec.js +110 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adapt-authoring-content",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "Module for managing Adapt content",
|
|
5
5
|
"homepage": "https://github.com/adapt-security/adapt-authoring-content",
|
|
6
6
|
"license": "GPL-3.0",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"adapt-authoring-api": "^3.0.0",
|
|
15
|
-
"adapt-authoring-core": "^
|
|
15
|
+
"adapt-authoring-core": "^3.0.0",
|
|
16
16
|
"adapt-authoring-mongodb": "^3.1.0"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it, mock } from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
3
|
|
|
4
|
+
import { AbstractApiModule } from 'adapt-authoring-api'
|
|
4
5
|
import ContentModule from '../lib/ContentModule.js'
|
|
5
6
|
import ContentTree from '../lib/ContentTree.js'
|
|
6
7
|
|
|
@@ -685,4 +686,113 @@ describe('ContentModule', () => {
|
|
|
685
686
|
assert.equal(courseLookupQuery._id.$in.length, 1)
|
|
686
687
|
})
|
|
687
688
|
})
|
|
689
|
+
|
|
690
|
+
describe('delete', () => {
|
|
691
|
+
const COURSE_OID = '507f1f77bcf86cd799439011'
|
|
692
|
+
const TARGET_OID = '507f1f77bcf86cd799439012'
|
|
693
|
+
|
|
694
|
+
function createDeleteInstance (overrides = {}) {
|
|
695
|
+
const mongoDeleteMany = mock.fn(async () => {})
|
|
696
|
+
const postDeleteInvoke = mock.fn(async () => {})
|
|
697
|
+
const inst = {
|
|
698
|
+
schemaName: 'content',
|
|
699
|
+
collectionName: 'content',
|
|
700
|
+
setDefaultOptions: mock.fn(),
|
|
701
|
+
findOne: mock.fn(async () => null),
|
|
702
|
+
postDeleteHook: { invoke: postDeleteInvoke },
|
|
703
|
+
updateEnabledPlugins: mock.fn(async () => {}),
|
|
704
|
+
updateSortOrder: mock.fn(async () => {}),
|
|
705
|
+
deleteCounters: mock.fn(async () => {}),
|
|
706
|
+
app: { waitForModule: mock.fn(async () => ({ deleteMany: mongoDeleteMany })) },
|
|
707
|
+
...overrides
|
|
708
|
+
}
|
|
709
|
+
return { inst, mongoDeleteMany, postDeleteInvoke }
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
it('should bulk-delete descendants in one mongodb call and fire postDeleteHook once with the full array', async (t) => {
|
|
713
|
+
const targetDoc = { _id: TARGET_OID, _type: 'page', _courseId: COURSE_OID }
|
|
714
|
+
const descendantDocs = Array.from({ length: 5 }, (_, i) => ({
|
|
715
|
+
_id: `desc${i}`, _parentId: TARGET_OID, _type: 'block', _courseId: COURSE_OID
|
|
716
|
+
}))
|
|
717
|
+
const treeItems = [targetDoc, ...descendantDocs]
|
|
718
|
+
|
|
719
|
+
const { inst, mongoDeleteMany, postDeleteInvoke } = createDeleteInstance({
|
|
720
|
+
findOne: mock.fn(async () => targetDoc)
|
|
721
|
+
})
|
|
722
|
+
|
|
723
|
+
// super.find and super.delete are statically bound to AbstractApiModule.prototype
|
|
724
|
+
// and can't be intercepted via plain-object methods, so swap them at the prototype
|
|
725
|
+
// for the lifetime of this test (auto-restored by t.mock).
|
|
726
|
+
t.mock.method(AbstractApiModule.prototype, 'find', async () => treeItems)
|
|
727
|
+
t.mock.method(AbstractApiModule.prototype, 'delete', async () => {})
|
|
728
|
+
|
|
729
|
+
await ContentModule.prototype.delete.call(inst, { _id: TARGET_OID })
|
|
730
|
+
|
|
731
|
+
assert.equal(mongoDeleteMany.mock.callCount(), 1, 'mongodb.deleteMany called exactly once')
|
|
732
|
+
const [collectionName, query] = mongoDeleteMany.mock.calls[0].arguments
|
|
733
|
+
assert.equal(collectionName, 'content')
|
|
734
|
+
assert.deepEqual(query, { _id: { $in: descendantDocs.map(d => d._id) } })
|
|
735
|
+
|
|
736
|
+
assert.equal(postDeleteInvoke.mock.callCount(), 1, 'postDeleteHook.invoke called exactly once')
|
|
737
|
+
const hookPayload = postDeleteInvoke.mock.calls[0].arguments[0]
|
|
738
|
+
assert.equal(hookPayload.length, 5, 'hook receives the full descendant array')
|
|
739
|
+
})
|
|
740
|
+
|
|
741
|
+
it('should not fire postDeleteHook when invokePostHook is false', async (t) => {
|
|
742
|
+
const targetDoc = { _id: TARGET_OID, _type: 'page', _courseId: COURSE_OID }
|
|
743
|
+
const descendantDocs = [{ _id: 'desc1', _parentId: TARGET_OID, _type: 'block', _courseId: COURSE_OID }]
|
|
744
|
+
const { inst, postDeleteInvoke } = createDeleteInstance({
|
|
745
|
+
findOne: mock.fn(async () => targetDoc)
|
|
746
|
+
})
|
|
747
|
+
|
|
748
|
+
t.mock.method(AbstractApiModule.prototype, 'find', async () => [targetDoc, ...descendantDocs])
|
|
749
|
+
t.mock.method(AbstractApiModule.prototype, 'delete', async () => {})
|
|
750
|
+
|
|
751
|
+
await ContentModule.prototype.delete.call(inst, { _id: TARGET_OID }, { invokePostHook: false })
|
|
752
|
+
|
|
753
|
+
assert.equal(postDeleteInvoke.mock.callCount(), 0)
|
|
754
|
+
})
|
|
755
|
+
})
|
|
756
|
+
|
|
757
|
+
describe('handleClone', () => {
|
|
758
|
+
const SRC_OID = '507f1f77bcf86cd799439011'
|
|
759
|
+
const PARENT_OID = '507f1f77bcf86cd799439022'
|
|
760
|
+
const USER_OID = '507f1f77bcf86cd799439033'
|
|
761
|
+
|
|
762
|
+
function createHandleCloneInstance (overrides = {}) {
|
|
763
|
+
return {
|
|
764
|
+
requestHook: { invoke: mock.fn(async () => {}) },
|
|
765
|
+
findOne: mock.fn(async () => ({ _id: SRC_OID })),
|
|
766
|
+
checkAccess: mock.fn(async () => {}),
|
|
767
|
+
clone: mock.fn(async () => ({ _id: 'new-id' })),
|
|
768
|
+
app: { errors: { NOT_FOUND: { setData: () => new Error('NOT_FOUND') } } },
|
|
769
|
+
...overrides
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function createRes () {
|
|
774
|
+
const res = {
|
|
775
|
+
status: mock.fn(() => res),
|
|
776
|
+
json: mock.fn()
|
|
777
|
+
}
|
|
778
|
+
return res
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
it('should invoke requestHook with req before clone runs', async () => {
|
|
782
|
+
const callOrder = []
|
|
783
|
+
const requestHookInvoke = mock.fn(async () => { callOrder.push('requestHook') })
|
|
784
|
+
const clone = mock.fn(async () => { callOrder.push('clone'); return { _id: 'new-id' } })
|
|
785
|
+
const inst = createHandleCloneInstance({
|
|
786
|
+
requestHook: { invoke: requestHookInvoke },
|
|
787
|
+
clone
|
|
788
|
+
})
|
|
789
|
+
const req = { body: { _id: SRC_OID, _parentId: PARENT_OID }, auth: { user: { _id: USER_OID } } }
|
|
790
|
+
|
|
791
|
+
await ContentModule.prototype.handleClone.call(inst, req, createRes(), mock.fn())
|
|
792
|
+
|
|
793
|
+
assert.equal(requestHookInvoke.mock.callCount(), 1, 'requestHook.invoke called once')
|
|
794
|
+
assert.deepEqual(requestHookInvoke.mock.calls[0].arguments, [req], 'hook receives the req')
|
|
795
|
+
assert.deepEqual(callOrder, ['requestHook', 'clone'], 'requestHook fires before clone')
|
|
796
|
+
})
|
|
797
|
+
})
|
|
688
798
|
})
|