w-orm-mongodb 1.1.37 → 1.1.39
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/README.md +447 -288
- package/dist/w-orm-mongodb.umd.js +2 -2
- package/dist/w-orm-mongodb.umd.js.map +1 -1
- package/docs/WOrmMongodb.html +221 -37
- package/docs/WOrmMongodb.mjs.html +1265 -829
- package/docs/index.html +2 -2
- package/g-basic.mjs +16 -6
- package/g-gfs.mjs +102 -102
- package/g-unique.mjs +66 -0
- package/package.json +2 -3
- package/script.txt +0 -1
- package/src/WOrmMongodb.mjs +1263 -827
- package/test/api-basic.test.mjs +1200 -0
- package/test/api-gfs.test.mjs +574 -0
- package/test/lib/api-setup.mjs +142 -0
- package/test/basic.test.mjs +0 -348
- package/test/gfs.test.mjs +0 -172
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import crypto from 'crypto'
|
|
3
|
+
import stream from 'stream'
|
|
4
|
+
import mongodb from 'mongodb'
|
|
5
|
+
import WOrm from '../src/WOrmMongodb.mjs'
|
|
6
|
+
import { genUrl, startContainer, stopContainer } from './lib/api-setup.mjs'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
let ctName = 'worm-test-mongodb-gfs'
|
|
10
|
+
let ctDb = 'worm'
|
|
11
|
+
let ctPort = null //容器對外埠, 由startContainer動態取得
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
//genOpt
|
|
15
|
+
let genOpt = (cl) => {
|
|
16
|
+
return {
|
|
17
|
+
url: genUrl(ctPort),
|
|
18
|
+
db: ctDb,
|
|
19
|
+
cl,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//genKeys, 取回傳物件之鍵集合, 用於驗證鍵集合固定
|
|
24
|
+
let genKeys = (v) => {
|
|
25
|
+
return Object.keys(v).sort().join(',')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//genU8a, 產生內容可複現之測試數據
|
|
29
|
+
let genU8a = (n, seed = 0) => {
|
|
30
|
+
let u8a = new Uint8Array(n)
|
|
31
|
+
for (let i = 0; i < n; i++) {
|
|
32
|
+
u8a[i] = (i + seed) % 256
|
|
33
|
+
}
|
|
34
|
+
return u8a
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//genHash
|
|
38
|
+
let genHash = (u8a) => {
|
|
39
|
+
return crypto.createHash('sha256').update(Buffer.from(u8a)).digest('hex')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//countDocs, 直接由驅動查GridFS之files與chunks資料表筆數
|
|
43
|
+
let countDocs = async (cl) => {
|
|
44
|
+
let client = new mongodb.MongoClient(genUrl(ctPort))
|
|
45
|
+
let r = null
|
|
46
|
+
try {
|
|
47
|
+
let db = client.db(ctDb)
|
|
48
|
+
r = {
|
|
49
|
+
files: await db.collection(`${cl}.files`).countDocuments({}),
|
|
50
|
+
chunks: await db.collection(`${cl}.chunks`).countDocuments({}),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
await client.close()
|
|
55
|
+
}
|
|
56
|
+
return r
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//uploadByDriver, 直接以驅動上傳指定filename, 用於製造既有數據尚存重複id之情境
|
|
60
|
+
//建立唯一索引後insertGfs已無法製造重複, 故須由驅動直接寫入
|
|
61
|
+
let uploadByDriver = async (cl, filename, u8a) => {
|
|
62
|
+
let client = new mongodb.MongoClient(genUrl(ctPort))
|
|
63
|
+
try {
|
|
64
|
+
let bucket = new mongodb.GridFSBucket(client.db(ctDb), { bucketName: cl })
|
|
65
|
+
await new Promise((resolve, reject) => {
|
|
66
|
+
let rs = new stream.Readable()
|
|
67
|
+
rs._read = () => {}
|
|
68
|
+
rs.push(Buffer.from(u8a))
|
|
69
|
+
rs.push(null)
|
|
70
|
+
rs.pipe(bucket.openUploadStream(filename))
|
|
71
|
+
.on('error', reject)
|
|
72
|
+
.on('finish', resolve)
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
await client.close()
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
before(async function() {
|
|
82
|
+
this.timeout(600000) //含拉取映像與服務啟動
|
|
83
|
+
ctPort = await startContainer(ctName)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
after(async function() {
|
|
87
|
+
this.timeout(120000)
|
|
88
|
+
await stopContainer(ctName)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
describe('gfs basic', function() {
|
|
93
|
+
let rt = null
|
|
94
|
+
let vans = {}
|
|
95
|
+
let vget = {}
|
|
96
|
+
|
|
97
|
+
//nSize, 12mb跨過chunkSizeBytes(10mb)故須分為2塊
|
|
98
|
+
let nSize = 12 * 1024 * 1024
|
|
99
|
+
|
|
100
|
+
before(async function () {
|
|
101
|
+
this.timeout(300000)
|
|
102
|
+
|
|
103
|
+
let cl = 'usersGfs'
|
|
104
|
+
let wo = WOrm(genOpt(cl))
|
|
105
|
+
|
|
106
|
+
//on
|
|
107
|
+
wo.on('change', function(mode, data, res) {
|
|
108
|
+
// console.log('change', mode)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
//u8a
|
|
112
|
+
let u8a = genU8a(nSize)
|
|
113
|
+
|
|
114
|
+
//delAllGfs, 無命中屬正常結果
|
|
115
|
+
rt = null
|
|
116
|
+
// vans[1] = { n: 0, nDeleted: 0, ok: 1 }
|
|
117
|
+
await wo.delAllGfs()
|
|
118
|
+
.then(function(msg) {
|
|
119
|
+
rt = msg
|
|
120
|
+
})
|
|
121
|
+
.catch(function(msg) {
|
|
122
|
+
rt = msg.toString()
|
|
123
|
+
})
|
|
124
|
+
vget[1] = rt
|
|
125
|
+
|
|
126
|
+
//insertGfs
|
|
127
|
+
rt = null
|
|
128
|
+
// vans[2] = { n: 1, nInserted: 1, ok: 1 }
|
|
129
|
+
await wo.insertGfs({ id: 'g-big', u8a })
|
|
130
|
+
.then(function(msg) {
|
|
131
|
+
rt = msg
|
|
132
|
+
})
|
|
133
|
+
.catch(function(msg) {
|
|
134
|
+
rt = msg.toString()
|
|
135
|
+
})
|
|
136
|
+
vget[2] = rt
|
|
137
|
+
|
|
138
|
+
//chunks, 12mb依chunkSizeBytes(10mb)須分為2塊
|
|
139
|
+
// vans[3] = { files: 1, chunks: 2 }
|
|
140
|
+
vget[3] = await countDocs(cl)
|
|
141
|
+
|
|
142
|
+
//selectByPkGfs, 取回內容須與來源逐位元組相同, 且形狀與insertGfs所收之數據物件一致
|
|
143
|
+
rt = null
|
|
144
|
+
// vans[4] = { keys: 'id,u8a', id: 'g-big', length: nSize, hash, b0: 0, b1: 1, b255: 255, b256: 0 }
|
|
145
|
+
await wo.selectByPkGfs('g-big')
|
|
146
|
+
.then(function(msg) {
|
|
147
|
+
rt = {
|
|
148
|
+
keys: genKeys(msg),
|
|
149
|
+
id: msg.id,
|
|
150
|
+
length: msg.u8a.length,
|
|
151
|
+
hash: genHash(msg.u8a),
|
|
152
|
+
b0: msg.u8a[0],
|
|
153
|
+
b1: msg.u8a[1],
|
|
154
|
+
b255: msg.u8a[255],
|
|
155
|
+
b256: msg.u8a[256],
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
.catch(function(msg) {
|
|
159
|
+
rt = msg.toString()
|
|
160
|
+
})
|
|
161
|
+
vget[4] = rt
|
|
162
|
+
|
|
163
|
+
//selectByPkGfs by id not existed
|
|
164
|
+
rt = null
|
|
165
|
+
// vans[5] = null
|
|
166
|
+
await wo.selectByPkGfs('id-not-existed')
|
|
167
|
+
.then(function(msg) {
|
|
168
|
+
rt = msg
|
|
169
|
+
})
|
|
170
|
+
.catch(function(msg) {
|
|
171
|
+
rt = `不應reject: ${msg.toString()}`
|
|
172
|
+
})
|
|
173
|
+
vget[5] = rt
|
|
174
|
+
|
|
175
|
+
//selectByPkGfs by id invalid, 未給有效id視為查無數據
|
|
176
|
+
// vans[6] = [null, null, null]
|
|
177
|
+
vget[6] = [
|
|
178
|
+
await wo.selectByPkGfs(''),
|
|
179
|
+
await wo.selectByPkGfs(123),
|
|
180
|
+
await wo.selectByPkGfs(null),
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
//insertGfs未給id時自動產生
|
|
184
|
+
// vans[7] = { n: 1, nInserted: 1, ok: 1 }
|
|
185
|
+
vget[7] = await wo.insertGfs({ u8a: genU8a(100, 1) })
|
|
186
|
+
|
|
187
|
+
//insertGfs輸入無效視為空結果
|
|
188
|
+
// vans[8] = { n: 0, nInserted: 0, ok: 1 }
|
|
189
|
+
vget[8] = await wo.insertGfs(null)
|
|
190
|
+
|
|
191
|
+
//insertGfs之u8a無效屬呼叫端給值錯誤, 須reject
|
|
192
|
+
rt = null
|
|
193
|
+
// vans[9] = 'Error: invalid data[0].u8a'
|
|
194
|
+
await wo.insertGfs({ id: 'g-bad', u8a: null })
|
|
195
|
+
.then(function(msg) {
|
|
196
|
+
rt = `不應resolve: ${JSON.stringify(msg)}`
|
|
197
|
+
})
|
|
198
|
+
.catch(function(msg) {
|
|
199
|
+
rt = msg.toString()
|
|
200
|
+
})
|
|
201
|
+
vget[9] = rt
|
|
202
|
+
|
|
203
|
+
//delGfs
|
|
204
|
+
rt = null
|
|
205
|
+
// vans[10] = [{ n: 1, nDeleted: 1, ok: 1 }]
|
|
206
|
+
await wo.delGfs({ id: 'g-big' })
|
|
207
|
+
.then(function(msg) {
|
|
208
|
+
rt = msg
|
|
209
|
+
})
|
|
210
|
+
.catch(function(msg) {
|
|
211
|
+
rt = msg.toString()
|
|
212
|
+
})
|
|
213
|
+
vget[10] = rt
|
|
214
|
+
|
|
215
|
+
//delGfs by id not existed, 查無數據屬正常結果
|
|
216
|
+
// vans[11] = [{ n: 0, nDeleted: 0, ok: 1 }]
|
|
217
|
+
vget[11] = await wo.delGfs({ id: 'id-not-existed' })
|
|
218
|
+
|
|
219
|
+
//delGfs未帶有效id, 須以ok為0回報
|
|
220
|
+
// vans[12] = [{ n: 0, nDeleted: 0, ok: 0, err: 'invalid id[undefined]' }]
|
|
221
|
+
vget[12] = await wo.delGfs({ u8a: genU8a(10) })
|
|
222
|
+
|
|
223
|
+
//delGfs輸入無效視為空結果
|
|
224
|
+
// vans[13] = []
|
|
225
|
+
vget[13] = await wo.delGfs(null)
|
|
226
|
+
|
|
227
|
+
//delGfs混合有效與無效id, 須逐筆回報且長度與輸入相同
|
|
228
|
+
await wo.insertGfs({ id: 'g-mix', u8a: genU8a(100, 2) })
|
|
229
|
+
// vans[14] = [{ n: 1, nDeleted: 1, ok: 1 }, { n: 0, nDeleted: 0, ok: 0, err: 'invalid id[undefined]' }]
|
|
230
|
+
vget[14] = await wo.delGfs([{ id: 'g-mix' }, { u8a: genU8a(10) }])
|
|
231
|
+
|
|
232
|
+
//鍵集合須固定
|
|
233
|
+
// vans[15] = ['n,nDeleted,ok', 'n,nDeleted,ok', 'err,n,nDeleted,ok']
|
|
234
|
+
vget[15] = [genKeys(vget[10][0]), genKeys(vget[11][0]), genKeys(vget[12][0])]
|
|
235
|
+
|
|
236
|
+
//delAllGfs, 先插入2筆再全刪
|
|
237
|
+
await wo.insertGfs([
|
|
238
|
+
{ id: 'g-a', u8a: genU8a(100, 3) },
|
|
239
|
+
{ id: 'g-b', u8a: genU8a(100, 4) },
|
|
240
|
+
])
|
|
241
|
+
rt = null
|
|
242
|
+
// vans[16] = { n: 3, nDeleted: 3, ok: 1 } //含未給id而自動產生之該筆
|
|
243
|
+
await wo.delAllGfs()
|
|
244
|
+
.then(function(msg) {
|
|
245
|
+
rt = msg
|
|
246
|
+
})
|
|
247
|
+
.catch(function(msg) {
|
|
248
|
+
rt = msg.toString()
|
|
249
|
+
})
|
|
250
|
+
vget[16] = rt
|
|
251
|
+
|
|
252
|
+
//全刪後files與chunks須一併清除
|
|
253
|
+
// vans[17] = { files: 0, chunks: 0 }
|
|
254
|
+
vget[17] = await countDocs(cl)
|
|
255
|
+
|
|
256
|
+
//insertGfs之鍵集合須固定
|
|
257
|
+
// vans[18] = ['n,nInserted,ok', 'n,nInserted,ok']
|
|
258
|
+
vget[18] = [genKeys(vget[2]), genKeys(vget[8])]
|
|
259
|
+
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
vans[1] = { n: 0, nDeleted: 0, ok: 1 }
|
|
263
|
+
it(`should get ${JSON.stringify(vans[1])} for delAllGfs with no matched data`, async function() {
|
|
264
|
+
assert.strict.deepStrictEqual(vget[1], vans[1])
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
vans[2] = { n: 1, nInserted: 1, ok: 1 }
|
|
268
|
+
it(`should get ${JSON.stringify(vans[2])} for insertGfs`, async function() {
|
|
269
|
+
assert.strict.deepStrictEqual(vget[2], vans[2])
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
vans[3] = { files: 1, chunks: 2 }
|
|
273
|
+
it(`should get ${JSON.stringify(vans[3])} for chunks of 12mb data by chunkSizeBytes 10mb`, async function() {
|
|
274
|
+
assert.strict.deepStrictEqual(vget[3], vans[3])
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
vans[4] = {
|
|
278
|
+
keys: 'id,u8a',
|
|
279
|
+
id: 'g-big',
|
|
280
|
+
length: nSize,
|
|
281
|
+
hash: genHash(genU8a(nSize)),
|
|
282
|
+
b0: 0,
|
|
283
|
+
b1: 1,
|
|
284
|
+
b255: 255,
|
|
285
|
+
b256: 0,
|
|
286
|
+
}
|
|
287
|
+
it(`should get same content for selectByPkGfs`, async function() {
|
|
288
|
+
assert.strict.deepStrictEqual(vget[4], vans[4])
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
vans[5] = null
|
|
292
|
+
it(`should get ${JSON.stringify(vans[5])} for selectByPkGfs by id not existed`, async function() {
|
|
293
|
+
assert.strict.deepStrictEqual(vget[5], vans[5])
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
vans[6] = [null, null, null]
|
|
297
|
+
it(`should get ${JSON.stringify(vans[6])} for selectByPkGfs by id invalid`, async function() {
|
|
298
|
+
assert.strict.deepStrictEqual(vget[6], vans[6])
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
vans[7] = { n: 1, nInserted: 1, ok: 1 }
|
|
302
|
+
it(`should get ${JSON.stringify(vans[7])} for insertGfs without id`, async function() {
|
|
303
|
+
assert.strict.deepStrictEqual(vget[7], vans[7])
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
vans[8] = { n: 0, nInserted: 0, ok: 1 }
|
|
307
|
+
it(`should get ${JSON.stringify(vans[8])} for insertGfs with invalid data`, async function() {
|
|
308
|
+
assert.strict.deepStrictEqual(vget[8], vans[8])
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
vans[9] = 'Error: invalid data[0].u8a'
|
|
312
|
+
it(`should get ${JSON.stringify(vans[9])} for insertGfs with invalid u8a`, async function() {
|
|
313
|
+
assert.strict.deepStrictEqual(vget[9], vans[9])
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
vans[10] = [{ n: 1, nDeleted: 1, ok: 1 }]
|
|
317
|
+
it(`should get ${JSON.stringify(vans[10])} for delGfs`, async function() {
|
|
318
|
+
assert.strict.deepStrictEqual(vget[10], vans[10])
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
vans[11] = [{ n: 0, nDeleted: 0, ok: 1 }]
|
|
322
|
+
it(`should get ${JSON.stringify(vans[11])} for delGfs by id not existed`, async function() {
|
|
323
|
+
assert.strict.deepStrictEqual(vget[11], vans[11])
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
vans[12] = [{ n: 0, nDeleted: 0, ok: 0, err: 'invalid id[undefined]' }]
|
|
327
|
+
it(`should get ${JSON.stringify(vans[12])} for delGfs without valid id`, async function() {
|
|
328
|
+
assert.strict.deepStrictEqual(vget[12], vans[12])
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
vans[13] = []
|
|
332
|
+
it(`should get ${JSON.stringify(vans[13])} for delGfs with invalid data`, async function() {
|
|
333
|
+
assert.strict.deepStrictEqual(vget[13], vans[13])
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
vans[14] = [
|
|
337
|
+
{ n: 1, nDeleted: 1, ok: 1 },
|
|
338
|
+
{ n: 0, nDeleted: 0, ok: 0, err: 'invalid id[undefined]' }
|
|
339
|
+
]
|
|
340
|
+
it(`should get ${JSON.stringify(vans[14])} for delGfs with mixed valid and invalid id`, async function() {
|
|
341
|
+
assert.strict.deepStrictEqual(vget[14], vans[14])
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
vans[15] = ['n,nDeleted,ok', 'n,nDeleted,ok', 'err,n,nDeleted,ok']
|
|
345
|
+
it(`should get ${JSON.stringify(vans[15])} for fixed key set of delGfs`, async function() {
|
|
346
|
+
assert.strict.deepStrictEqual(vget[15], vans[15])
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
vans[16] = { n: 3, nDeleted: 3, ok: 1 }
|
|
350
|
+
it(`should get ${JSON.stringify(vans[16])} for delAllGfs`, async function() {
|
|
351
|
+
assert.strict.deepStrictEqual(vget[16], vans[16])
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
vans[17] = { files: 0, chunks: 0 }
|
|
355
|
+
it(`should get ${JSON.stringify(vans[17])} for files and chunks after delAllGfs`, async function() {
|
|
356
|
+
assert.strict.deepStrictEqual(vget[17], vans[17])
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
vans[18] = ['n,nInserted,ok', 'n,nInserted,ok']
|
|
360
|
+
it(`should get ${JSON.stringify(vans[18])} for fixed key set of insertGfs`, async function() {
|
|
361
|
+
assert.strict.deepStrictEqual(vget[18], vans[18])
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
describe('gfs insert', function() {
|
|
368
|
+
let vans = {}
|
|
369
|
+
let vget = {}
|
|
370
|
+
|
|
371
|
+
before(async function() {
|
|
372
|
+
this.timeout(300000)
|
|
373
|
+
|
|
374
|
+
let cl = 'insertGfs'
|
|
375
|
+
let wo = WOrm(genOpt(cl))
|
|
376
|
+
await wo.delAllGfs()
|
|
377
|
+
|
|
378
|
+
//全新3筆
|
|
379
|
+
vget[1] = await wo.insertGfs([
|
|
380
|
+
{ id: 'i1', u8a: genU8a(100, 1) },
|
|
381
|
+
{ id: 'i2', u8a: genU8a(100, 2) },
|
|
382
|
+
{ id: 'i3', u8a: genU8a(100, 3) },
|
|
383
|
+
])
|
|
384
|
+
let c1 = await countDocs(cl)
|
|
385
|
+
|
|
386
|
+
//重複插入同3筆, 全數已存在故皆不插入, 且不得reject
|
|
387
|
+
vget[2] = await wo.insertGfs([
|
|
388
|
+
{ id: 'i1', u8a: genU8a(100, 1) },
|
|
389
|
+
{ id: 'i2', u8a: genU8a(100, 2) },
|
|
390
|
+
{ id: 'i3', u8a: genU8a(100, 3) },
|
|
391
|
+
])
|
|
392
|
+
|
|
393
|
+
//重複插入不得殘留孤兒chunks, 亦不得新增files
|
|
394
|
+
let c2 = await countDocs(cl)
|
|
395
|
+
vget[3] = { files: c2.files - c1.files, chunks: c2.chunks - c1.chunks }
|
|
396
|
+
|
|
397
|
+
//已存在id不得被覆寫, 內容須為首次所給
|
|
398
|
+
let vi1 = await wo.selectByPkGfs('i1')
|
|
399
|
+
vget[4] = genHash(vi1.u8a) === genHash(genU8a(100, 1))
|
|
400
|
+
|
|
401
|
+
//部份重複, 1筆已存在2筆為新
|
|
402
|
+
vget[5] = await wo.insertGfs([
|
|
403
|
+
{ id: 'i2', u8a: genU8a(100, 2) },
|
|
404
|
+
{ id: 'i4', u8a: genU8a(100, 4) },
|
|
405
|
+
{ id: 'i5', u8a: genU8a(100, 5) },
|
|
406
|
+
])
|
|
407
|
+
|
|
408
|
+
//同批含重複id, 僅首筆成功且保留首筆內容
|
|
409
|
+
vget[6] = await wo.insertGfs([
|
|
410
|
+
{ id: 'i-dup', u8a: genU8a(100, 6) },
|
|
411
|
+
{ id: 'i-dup', u8a: genU8a(100, 7) },
|
|
412
|
+
{ id: 'i-uniq', u8a: genU8a(100, 8) },
|
|
413
|
+
])
|
|
414
|
+
let vdup = await wo.selectByPkGfs('i-dup')
|
|
415
|
+
vget[7] = genHash(vdup.u8a) === genHash(genU8a(100, 6))
|
|
416
|
+
|
|
417
|
+
//併發對同一id插入10次, nInserted總和須為1
|
|
418
|
+
let c3 = await countDocs(cl)
|
|
419
|
+
let rs = await Promise.all(Array.from({ length: 10 }, (v, k) => {
|
|
420
|
+
return wo.insertGfs({ id: 'i-race', u8a: genU8a(100, k) })
|
|
421
|
+
}))
|
|
422
|
+
vget[8] = rs.reduce((sum, v) => sum + v.nInserted, 0)
|
|
423
|
+
vget[9] = rs.filter((v) => v.ok !== 1).length
|
|
424
|
+
|
|
425
|
+
//併發後僅新增1筆files, 且失敗者不得殘留孤兒chunks
|
|
426
|
+
let c4 = await countDocs(cl)
|
|
427
|
+
vget[10] = { files: c4.files - c3.files, chunks: c4.chunks - c3.chunks }
|
|
428
|
+
|
|
429
|
+
//最終筆數, 3+0+2+2+1=8
|
|
430
|
+
vget[11] = (await countDocs(cl)).files
|
|
431
|
+
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
vans[1] = { n: 3, nInserted: 3, ok: 1 }
|
|
435
|
+
it(`should get ${JSON.stringify(vans[1])} for insertGfs 3 new records`, async function() {
|
|
436
|
+
assert.strict.deepStrictEqual(vget[1], vans[1])
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
vans[2] = { n: 3, nInserted: 0, ok: 1 }
|
|
440
|
+
it(`should get ${JSON.stringify(vans[2])} for insertGfs 3 existed records`, async function() {
|
|
441
|
+
assert.strict.deepStrictEqual(vget[2], vans[2])
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
vans[3] = { files: 0, chunks: 0 }
|
|
445
|
+
it(`should get ${JSON.stringify(vans[3])} for no orphan chunks after insertGfs existed records`, async function() {
|
|
446
|
+
assert.strict.deepStrictEqual(vget[3], vans[3])
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
vans[4] = true
|
|
450
|
+
it(`should get ${JSON.stringify(vans[4])} for not overwriting existed id`, async function() {
|
|
451
|
+
assert.strict.deepStrictEqual(vget[4], vans[4])
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
vans[5] = { n: 3, nInserted: 2, ok: 1 }
|
|
455
|
+
it(`should get ${JSON.stringify(vans[5])} for insertGfs 1 existed and 2 new records`, async function() {
|
|
456
|
+
assert.strict.deepStrictEqual(vget[5], vans[5])
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
vans[6] = { n: 3, nInserted: 2, ok: 1 }
|
|
460
|
+
it(`should get ${JSON.stringify(vans[6])} for insertGfs with duplicated id in same batch`, async function() {
|
|
461
|
+
assert.strict.deepStrictEqual(vget[6], vans[6])
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
vans[7] = true
|
|
465
|
+
it(`should get ${JSON.stringify(vans[7])} for keeping first one of duplicated id in same batch`, async function() {
|
|
466
|
+
assert.strict.deepStrictEqual(vget[7], vans[7])
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
vans[8] = 1
|
|
470
|
+
it(`should get ${JSON.stringify(vans[8])} for sum of nInserted by 10 concurrent insertGfs with same id`, async function() {
|
|
471
|
+
assert.strict.deepStrictEqual(vget[8], vans[8])
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
vans[9] = 0
|
|
475
|
+
it(`should get ${JSON.stringify(vans[9])} for count of ok!==1 in 10 concurrent insertGfs`, async function() {
|
|
476
|
+
assert.strict.deepStrictEqual(vget[9], vans[9])
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
vans[10] = { files: 1, chunks: 1 }
|
|
480
|
+
it(`should get ${JSON.stringify(vans[10])} for no orphan chunks after 10 concurrent insertGfs`, async function() {
|
|
481
|
+
assert.strict.deepStrictEqual(vget[10], vans[10])
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
vans[11] = 8
|
|
485
|
+
it(`should get ${JSON.stringify(vans[11])} for files after all insertGfs`, async function() {
|
|
486
|
+
assert.strict.deepStrictEqual(vget[11], vans[11])
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
describe('gfs legacy duplicated id', function() {
|
|
493
|
+
let vans = {}
|
|
494
|
+
let vget = {}
|
|
495
|
+
|
|
496
|
+
before(async function() {
|
|
497
|
+
this.timeout(300000)
|
|
498
|
+
|
|
499
|
+
//既有數據尚存重複id之情境, 以驅動直接製造, 並於本資料表不呼叫insertGfs以免建立唯一索引
|
|
500
|
+
let cl = 'legacyGfs'
|
|
501
|
+
let wo = WOrm(genOpt(cl))
|
|
502
|
+
await wo.delAllGfs()
|
|
503
|
+
await uploadByDriver(cl, 'l-dup', genU8a(100, 1))
|
|
504
|
+
await uploadByDriver(cl, 'l-dup', genU8a(100, 2))
|
|
505
|
+
vget[1] = (await countDocs(cl)).files
|
|
506
|
+
|
|
507
|
+
//delGfs須一併刪除並如實回報nDeleted
|
|
508
|
+
vget[2] = await wo.delGfs({ id: 'l-dup' })
|
|
509
|
+
vget[3] = await countDocs(cl)
|
|
510
|
+
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
vans[1] = 2
|
|
514
|
+
it(`should get ${JSON.stringify(vans[1])} for files of legacy duplicated id`, async function() {
|
|
515
|
+
assert.strict.deepStrictEqual(vget[1], vans[1])
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
vans[2] = [{ n: 1, nDeleted: 2, ok: 1 }]
|
|
519
|
+
it(`should get ${JSON.stringify(vans[2])} for delGfs with legacy duplicated id`, async function() {
|
|
520
|
+
assert.strict.deepStrictEqual(vget[2], vans[2])
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
vans[3] = { files: 0, chunks: 0 }
|
|
524
|
+
it(`should get ${JSON.stringify(vans[3])} for files and chunks after delGfs with legacy duplicated id`, async function() {
|
|
525
|
+
assert.strict.deepStrictEqual(vget[3], vans[3])
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
describe('gfs change event', function() {
|
|
532
|
+
let vans = {}
|
|
533
|
+
let vget = {}
|
|
534
|
+
|
|
535
|
+
before(async function() {
|
|
536
|
+
this.timeout(300000)
|
|
537
|
+
|
|
538
|
+
let cl = 'evGfs'
|
|
539
|
+
let wo = WOrm(genOpt(cl))
|
|
540
|
+
await wo.delAllGfs()
|
|
541
|
+
|
|
542
|
+
//訂閱函數拋錯不得影響本次操作之結果
|
|
543
|
+
wo.on('change', function() {
|
|
544
|
+
throw new Error('訂閱者拋錯')
|
|
545
|
+
})
|
|
546
|
+
|
|
547
|
+
vget[1] = await wo.insertGfs({ id: 'e1', u8a: genU8a(100, 1) })
|
|
548
|
+
vget[2] = genHash((await wo.selectByPkGfs('e1')).u8a) === genHash(genU8a(100, 1))
|
|
549
|
+
vget[3] = await wo.delGfs({ id: 'e1' })
|
|
550
|
+
vget[4] = await wo.delAllGfs()
|
|
551
|
+
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
vans[1] = { n: 1, nInserted: 1, ok: 1 }
|
|
555
|
+
it(`should get ${JSON.stringify(vans[1])} for insertGfs with throwing listener`, async function() {
|
|
556
|
+
assert.strict.deepStrictEqual(vget[1], vans[1])
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
vans[2] = true
|
|
560
|
+
it(`should get ${JSON.stringify(vans[2])} for content after insertGfs with throwing listener`, async function() {
|
|
561
|
+
assert.strict.deepStrictEqual(vget[2], vans[2])
|
|
562
|
+
})
|
|
563
|
+
|
|
564
|
+
vans[3] = [{ n: 1, nDeleted: 1, ok: 1 }]
|
|
565
|
+
it(`should get ${JSON.stringify(vans[3])} for delGfs with throwing listener`, async function() {
|
|
566
|
+
assert.strict.deepStrictEqual(vget[3], vans[3])
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
vans[4] = { n: 0, nDeleted: 0, ok: 1 }
|
|
570
|
+
it(`should get ${JSON.stringify(vans[4])} for delAllGfs with throwing listener`, async function() {
|
|
571
|
+
assert.strict.deepStrictEqual(vget[4], vans[4])
|
|
572
|
+
})
|
|
573
|
+
|
|
574
|
+
})
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import net from 'net'
|
|
2
|
+
import mongodb from 'mongodb'
|
|
3
|
+
import { execFile } from 'child_process'
|
|
4
|
+
import { promisify } from 'util'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
//測試自行創建docker容器供MongoDB服務, 測試結束即銷毀, 故僅需環境已安裝docker
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
let execFileAsync = promisify(execFile)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
let ctImage = 'mongo:8'
|
|
14
|
+
let ctUser = 'username'
|
|
15
|
+
let ctPassword = 'password'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
let delay = (ms) => {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
setTimeout(resolve, ms)
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//getFreePort, 由系統配發空閒埠
|
|
25
|
+
let getFreePort = () => {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
let srv = net.createServer()
|
|
28
|
+
srv.on('error', reject)
|
|
29
|
+
srv.listen(0, () => {
|
|
30
|
+
let port = srv.address().port
|
|
31
|
+
srv.close(() => {
|
|
32
|
+
resolve(port)
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//docker, 以陣列傳參不經shell, 避免路徑與引號轉譯問題
|
|
39
|
+
let docker = (args) => {
|
|
40
|
+
return execFileAsync('docker', args)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//genUrl
|
|
44
|
+
let genUrl = (port) => {
|
|
45
|
+
return `mongodb://${ctUser}:${ctPassword}@127.0.0.1:${port}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
//probeReady, 以mongodb驅動直連容器對外埠試跑ping
|
|
49
|
+
//不使用容器內mongosh, 因mongo映像初始化期間會先啟動僅監聽unix socket之臨時服務,
|
|
50
|
+
//容器內探測會提早回報就緒, 直連TCP方為測試實際所走路徑
|
|
51
|
+
let probeReady = async (port) => {
|
|
52
|
+
let client = new mongodb.MongoClient(genUrl(port), { serverSelectionTimeoutMS: 2000 })
|
|
53
|
+
let ok = false
|
|
54
|
+
try {
|
|
55
|
+
await client.db('admin').command({ ping: 1 })
|
|
56
|
+
ok = true
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
ok = false
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
await client.close().catch(() => {})
|
|
63
|
+
}
|
|
64
|
+
return ok
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//startContainer, 創建容器並等待服務就緒, 回傳容器對外埠
|
|
68
|
+
//對外埠由getFreePort動態取得, 避免與既有MongoDB服務搶27017, 亦避免多測試檔並行時互搶同一埠
|
|
69
|
+
//mongo映像以MONGO_INITDB_ROOT_USERNAME、MONGO_INITDB_ROOT_PASSWORD即建妥帳號, 無須另跑建置語句
|
|
70
|
+
let startContainer = async (ctName) => {
|
|
71
|
+
|
|
72
|
+
//check docker
|
|
73
|
+
await docker(['version', '--format', '{{.Server.Version}}'])
|
|
74
|
+
.catch(() => {
|
|
75
|
+
throw new Error('需先安裝並啟動docker才能執行mongodb測試')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
//清除前次殘留容器
|
|
79
|
+
await docker(['rm', '-f', ctName]).catch(() => {})
|
|
80
|
+
|
|
81
|
+
//run, getFreePort取得之埠於關閉監聽至docker實際綁定之間可能已被他者佔用,
|
|
82
|
+
//多測試檔並行時尤其容易發生, 故綁定失敗即改取新埠重試
|
|
83
|
+
let ctPort = null
|
|
84
|
+
let nTry = 10
|
|
85
|
+
for (let i = 0; i < nTry; i++) {
|
|
86
|
+
|
|
87
|
+
//ctPort
|
|
88
|
+
ctPort = await getFreePort()
|
|
89
|
+
|
|
90
|
+
//run
|
|
91
|
+
let err = null
|
|
92
|
+
await docker(['run', '-d', '--rm', '--name', ctName, '-e', `MONGO_INITDB_ROOT_USERNAME=${ctUser}`, '-e', `MONGO_INITDB_ROOT_PASSWORD=${ctPassword}`, '-p', `${ctPort}:27017`, ctImage])
|
|
93
|
+
.catch((e) => {
|
|
94
|
+
err = e
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
//check
|
|
98
|
+
if (err === null) {
|
|
99
|
+
break
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//僅埠被佔用方可重試, 其餘錯誤須往外拋
|
|
103
|
+
let msg = `${err.stderr || ''}${err.message || ''}`
|
|
104
|
+
let isPortBusy = msg.indexOf('address already in use') >= 0 || msg.indexOf('port is already allocated') >= 0
|
|
105
|
+
if (!isPortBusy || i === nTry - 1) {
|
|
106
|
+
throw err
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
//清除綁定失敗所遺留之容器
|
|
110
|
+
await docker(['rm', '-f', ctName]).catch(() => {})
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
//等待服務就緒, 首次需拉取映像故給予較長時間
|
|
115
|
+
let ready = false
|
|
116
|
+
for (let i = 0; i < 90; i++) {
|
|
117
|
+
let ok = await probeReady(ctPort)
|
|
118
|
+
if (ok) {
|
|
119
|
+
ready = true
|
|
120
|
+
break
|
|
121
|
+
}
|
|
122
|
+
await delay(2000)
|
|
123
|
+
}
|
|
124
|
+
if (!ready) {
|
|
125
|
+
throw new Error('mongodb容器啟動逾時')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return ctPort
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
//stopContainer, 銷毀容器
|
|
132
|
+
let stopContainer = async (ctName) => {
|
|
133
|
+
await docker(['rm', '-f', ctName]).catch(() => {})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
export {
|
|
138
|
+
delay,
|
|
139
|
+
genUrl,
|
|
140
|
+
startContainer,
|
|
141
|
+
stopContainer
|
|
142
|
+
}
|