adapt-authoring-mongodb 1.1.4 → 1.2.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.
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
on: push
|
|
3
|
+
jobs:
|
|
4
|
+
default:
|
|
5
|
+
runs-on: ubuntu-latest
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
steps:
|
|
9
|
+
- uses: actions/checkout@v4
|
|
10
|
+
- uses: actions/setup-node@v4
|
|
11
|
+
with:
|
|
12
|
+
node-version: 'lts/*'
|
|
13
|
+
cache: 'npm'
|
|
14
|
+
- run: npm ci
|
|
15
|
+
- run: npm test
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adapt-authoring-mongodb",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Module for saving to a MongoDB instance",
|
|
5
5
|
"homepage": "https://github.com/adapt-security/adapt-authoring-mongodb",
|
|
6
6
|
"license": "GPL-3.0",
|
|
@@ -58,5 +58,8 @@
|
|
|
58
58
|
}
|
|
59
59
|
]
|
|
60
60
|
]
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"test": "node --test 'tests/**/*.spec.js'"
|
|
61
64
|
}
|
|
62
65
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, it, mock } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import MongoDBModule from '../lib/MongoDBModule.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* MongoDBModule extends AbstractModule and requires a running MongoDB connection.
|
|
7
|
+
* We test parseOptions and getError in isolation.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
function createInstance () {
|
|
11
|
+
const mockApp = {
|
|
12
|
+
waitForModule: mock.fn(async () => {}),
|
|
13
|
+
errors: {
|
|
14
|
+
MONGO_ERROR: { setData: mock.fn(function (d) { return { code: 'MONGO_ERROR', ...d } }) },
|
|
15
|
+
MONGO_IMMUTABLE_FIELD: { setData: mock.fn(function (d) { return { code: 'MONGO_IMMUTABLE_FIELD', ...d } }) },
|
|
16
|
+
MONGO_DUPL_INDEX: { setData: mock.fn(function (d) { return { code: 'MONGO_DUPL_INDEX', ...d } }) },
|
|
17
|
+
MONGO_CONN_FAILED: { setData: mock.fn(function () { return this }) }
|
|
18
|
+
},
|
|
19
|
+
dependencyloader: {
|
|
20
|
+
moduleLoadedHook: { tap: () => {}, untap: () => {} }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const originalInit = MongoDBModule.prototype.init
|
|
25
|
+
MongoDBModule.prototype.init = async function () {}
|
|
26
|
+
|
|
27
|
+
const instance = new MongoDBModule(mockApp, { name: 'adapt-authoring-mongodb' })
|
|
28
|
+
|
|
29
|
+
MongoDBModule.prototype.init = originalInit
|
|
30
|
+
|
|
31
|
+
instance.log = mock.fn()
|
|
32
|
+
|
|
33
|
+
return { instance, mockApp }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe('MongoDBModule', () => {
|
|
37
|
+
describe('#parseOptions()', () => {
|
|
38
|
+
it('should parse string limit to integer', () => {
|
|
39
|
+
const { instance } = createInstance()
|
|
40
|
+
const options = { limit: '10' }
|
|
41
|
+
instance.parseOptions(options)
|
|
42
|
+
assert.equal(options.limit, 10)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('should parse string skip to integer', () => {
|
|
46
|
+
const { instance } = createInstance()
|
|
47
|
+
const options = { skip: '5' }
|
|
48
|
+
instance.parseOptions(options)
|
|
49
|
+
assert.equal(options.skip, 5)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should handle undefined options gracefully', () => {
|
|
53
|
+
const { instance } = createInstance()
|
|
54
|
+
instance.parseOptions(undefined)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should handle options without limit or skip', () => {
|
|
58
|
+
const { instance } = createInstance()
|
|
59
|
+
const options = { sort: { name: 1 } }
|
|
60
|
+
instance.parseOptions(options)
|
|
61
|
+
assert.deepEqual(options, { sort: { name: 1 } })
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should keep numeric limit as-is', () => {
|
|
65
|
+
const { instance } = createInstance()
|
|
66
|
+
const options = { limit: 10 }
|
|
67
|
+
instance.parseOptions(options)
|
|
68
|
+
assert.equal(options.limit, 10)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should skip undefined limit', () => {
|
|
72
|
+
const { instance } = createInstance()
|
|
73
|
+
const options = { limit: undefined }
|
|
74
|
+
instance.parseOptions(options)
|
|
75
|
+
assert.equal(options.limit, undefined)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('#getError()', () => {
|
|
80
|
+
it('should return MONGO_IMMUTABLE_FIELD for error code 66', () => {
|
|
81
|
+
const { instance } = createInstance()
|
|
82
|
+
const result = instance.getError('test', 'update', { code: 66, message: 'immutable' })
|
|
83
|
+
assert.equal(result.code, 'MONGO_IMMUTABLE_FIELD')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should return MONGO_DUPL_INDEX for error code 11000', () => {
|
|
87
|
+
const { instance } = createInstance()
|
|
88
|
+
const result = instance.getError('test', 'insert', { code: 11000, message: 'duplicate' })
|
|
89
|
+
assert.equal(result.code, 'MONGO_DUPL_INDEX')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should return MONGO_ERROR for other error codes', () => {
|
|
93
|
+
const { instance } = createInstance()
|
|
94
|
+
const result = instance.getError('test', 'find', { code: 999, message: 'unknown' })
|
|
95
|
+
assert.equal(result.code, 'MONGO_ERROR')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('should return MONGO_ERROR for errors without code', () => {
|
|
99
|
+
const { instance } = createInstance()
|
|
100
|
+
const result = instance.getError('test', 'delete', { message: 'fail' })
|
|
101
|
+
assert.equal(result.code, 'MONGO_ERROR')
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
describe('#ObjectId', () => {
|
|
106
|
+
it('should return ObjectIdUtils', () => {
|
|
107
|
+
const { instance } = createInstance()
|
|
108
|
+
assert.ok(instance.ObjectId)
|
|
109
|
+
assert.equal(typeof instance.ObjectId.isValid, 'function')
|
|
110
|
+
assert.equal(typeof instance.ObjectId.create, 'function')
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
})
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { ObjectId } from 'mongodb'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ObjectIdUtils relies on App.instance for error throwing in parse(),
|
|
7
|
+
* but we can test isValid, create, and isObjectId which don't.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Import will fail due to App.instance dependency in parse, so we
|
|
11
|
+
// extract the pure logic inline
|
|
12
|
+
const isObjectId = (data) => data instanceof ObjectId
|
|
13
|
+
|
|
14
|
+
function isValid (s) {
|
|
15
|
+
try {
|
|
16
|
+
const parsed = new ObjectId(s)
|
|
17
|
+
return parsed.equals(s)
|
|
18
|
+
} catch (e) {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('ObjectIdUtils', () => {
|
|
24
|
+
describe('.create()', () => {
|
|
25
|
+
it('should create a new ObjectId', () => {
|
|
26
|
+
const id = new ObjectId()
|
|
27
|
+
assert.ok(id instanceof ObjectId)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('should create unique IDs each time', () => {
|
|
31
|
+
const id1 = new ObjectId()
|
|
32
|
+
const id2 = new ObjectId()
|
|
33
|
+
assert.notEqual(id1.toString(), id2.toString())
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('.isValid()', () => {
|
|
38
|
+
it('should return true for a valid ObjectId string', () => {
|
|
39
|
+
const id = new ObjectId()
|
|
40
|
+
assert.equal(isValid(id.toString()), true)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should return false for a random string', () => {
|
|
44
|
+
assert.equal(isValid('not-an-objectid'), false)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should return false for an empty string', () => {
|
|
48
|
+
assert.equal(isValid(''), false)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should return false for a number', () => {
|
|
52
|
+
assert.equal(isValid(12345), false)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should return true for a 24-char hex string', () => {
|
|
56
|
+
assert.equal(isValid('507f1f77bcf86cd799439011'), true)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should return false for a 23-char hex string', () => {
|
|
60
|
+
assert.equal(isValid('507f1f77bcf86cd79943901'), false)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe('.isObjectId()', () => {
|
|
65
|
+
it('should return true for an ObjectId instance', () => {
|
|
66
|
+
assert.equal(isObjectId(new ObjectId()), true)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should return false for a string', () => {
|
|
70
|
+
assert.equal(isObjectId('507f1f77bcf86cd799439011'), false)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should return false for null', () => {
|
|
74
|
+
assert.equal(isObjectId(null), false)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should return false for undefined', () => {
|
|
78
|
+
assert.equal(isObjectId(undefined), false)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('should return false for a plain object', () => {
|
|
82
|
+
assert.equal(isObjectId({}), false)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
})
|