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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-authoring-content",
3
- "version": "3.0.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": "^2.0.0",
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
  })