w-orm-reladb 1.1.8 → 1.1.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.
@@ -0,0 +1,390 @@
1
+ import assert from 'assert'
2
+ import wo from '../src/WOrmReladb.mjs'
3
+ import cvFindDef, * as cvMod from '../src/cvFind.mjs'
4
+ import cvOrmFind from 'wsemi/src/cvOrmFind.mjs'
5
+ import cvCases from 'wsemi/src/_cvOrmFindCases.mjs'
6
+
7
+
8
+ //本檔為mssql與sqlite共用之條件(find)白名單測試套組(CLAUDE_rulebook.md之R11), 由api-sqlite.test.mjs與api-mssql.test.mjs引入
9
+ //案例表為wsemi之契約案例表(src/_cvOrmFindCases.mjs, 純資料), 逐案對公開select/delAll執行, 使共用函數cvOrmFind被正確接線而非被後端略過;
10
+ //預期值取自案例表之ids.sql(SQL三值族: $ne/$nin不含NULL), 非現況指紋
11
+ //mssql之E25({name:'peter'})依預設定序不分大小寫, 命中a1與a2(sqlite為二進位比對, 只命中a2), 屬定序差異(設計, 已文件化於R11)
12
+
13
+
14
+ let { seed, cases } = cvCases
15
+
16
+ let ALL = ['a1', 'a2', 'a3', 'a4', 'a5', 'a6']
17
+
18
+ let ids = (rs) => rs.map((v) => v.id).sort()
19
+
20
+ let msgOf = (err) => String(err && err.message !== undefined ? err.message : err)
21
+
22
+ //fills, 產生n個不命中任何列之填充字串
23
+ let fills = (n) => Array.from({ length: n }, (_, i) => `k${i}`)
24
+
25
+ //settle, 執行promise並回傳{ok,v}或{err}
26
+ let settle = (pm) => pm.then((v) => ({ ok: true, v })).catch((err) => ({ err }))
27
+
28
+
29
+ /**
30
+ * 定義共用測試套組
31
+ *
32
+ * @param {String} label 輸入後端名稱字串('sqlite'或'mssql'), 供describe標題與定序差異使用
33
+ * @param {Function} getOpt 輸入取得建構設定之函數, 於hook內呼叫以取得當下有效之連線資訊
34
+ */
35
+ export function defineWhitelistSuites(label, getOpt) {
36
+
37
+ let ci = label === 'mssql' //預設定序不分大小寫
38
+
39
+
40
+ describe(`${label} find白名單: 案例表逐案對select與delAll`, function() {
41
+
42
+ let w = null
43
+ let ins = null
44
+ let cs = null
45
+
46
+ //以共用instance執行各案, 免每次呼叫各自連線(案數多)
47
+ let reset = async () => {
48
+ await w.delAll({}, cs)
49
+ let r = await w.insert(seed(), cs)
50
+ assert.strictEqual(r.nInserted, 6)
51
+ }
52
+
53
+ let want = (c) => {
54
+ if (ci && c.id === 'E25') {
55
+ return ['a1', 'a2']
56
+ }
57
+ return c.ids.sql
58
+ }
59
+
60
+ before(async function() {
61
+ w = wo(getOpt({ cl: 'tfind' }))
62
+ await w.createStorage()
63
+ ins = await w.init()
64
+ cs = { instance: ins }
65
+ })
66
+
67
+ after(async function() {
68
+ await ins.close()
69
+ })
70
+
71
+ it('案例表規模: 至少255案, 含ALL/OK/REJ三類', function() {
72
+ assert.ok(cases.length >= 255, `案數 ${cases.length}`)
73
+ for (let ex of ['ALL', 'OK', 'REJ']) {
74
+ assert.ok(cases.some((c) => c.expect === ex), ex)
75
+ }
76
+ })
77
+
78
+ for (let c of cases) {
79
+ if (c.expect === 'REJ') {
80
+ it(`${c.id} REJ ${c.label}: 兩函數皆reject、資料不變、error事件於reject前恰一次、change不發`, async function() {
81
+ await reset()
82
+ let before = await w.select({}, cs)
83
+ let evs = []
84
+ let fnE = (mode) => evs.push(`error:${mode}`)
85
+ let fnC = (mode) => evs.push(`change:${mode}`)
86
+ w.on('error', fnE)
87
+ w.on('change', fnC)
88
+ let ms = {}
89
+ try {
90
+ for (let mode of ['select', 'delAll']) {
91
+ let r = await settle(w[mode](c.find(), cs))
92
+ evs.push(`reject:${mode}`)
93
+ assert.ok(r.err !== undefined, `${mode} 未reject: ${JSON.stringify(r.v)}`)
94
+ ms[mode] = msgOf(r.err)
95
+ }
96
+ }
97
+ finally {
98
+ w.off('error', fnE)
99
+ w.off('change', fnC)
100
+ }
101
+ assert.deepStrictEqual(evs, ['error:select', 'reject:select', 'error:delAll', 'reject:delAll'])
102
+ for (let mode of ['select', 'delAll']) {
103
+ assert.strictEqual(ms[mode].indexOf('invalid find: '), 0, `${mode}: ${ms[mode]}`)
104
+ assert.ok(ms[mode].indexOf(c.why) >= 0, `${mode} 原因應含[${c.why}]: ${ms[mode]}`)
105
+ }
106
+ assert.deepStrictEqual(await w.select({}, cs), before)
107
+ })
108
+ }
109
+ else {
110
+ it(`${c.id} ${c.expect} ${c.label}: select命中 = delAll刪除 = 案例表ids.sql${c.schema ? '(schema案可reject)' : ''}`, async function() {
111
+ await reset()
112
+ let exp = want(c)
113
+ let rs = await settle(w.select(c.find(), cs))
114
+ if (rs.err !== undefined) {
115
+ //schema案: SQL族可因欄位不存在或型別檢核而reject, 此時兩函數須一致reject且資料不變
116
+ assert.ok(c.schema === true, `非schema案不得reject: ${msgOf(rs.err)}`)
117
+ assert.strictEqual(msgOf(rs.err).indexOf('invalid find: '), 0, msgOf(rs.err))
118
+ let before = await w.select({}, cs)
119
+ let rd = await settle(w.delAll(c.find(), cs))
120
+ assert.ok(rd.err !== undefined, 'select reject而delAll未reject')
121
+ assert.strictEqual(msgOf(rd.err), msgOf(rs.err))
122
+ assert.deepStrictEqual(await w.select({}, cs), before)
123
+ return
124
+ }
125
+ assert.deepStrictEqual(ids(rs.v), exp, 'select之命中')
126
+ let r = await w.delAll(c.find(), cs)
127
+ let left = ids(await w.select({}, cs))
128
+ let gone = ALL.filter((v) => left.indexOf(v) < 0)
129
+ assert.deepStrictEqual(gone, exp, 'delAll之刪除')
130
+ assert.deepStrictEqual(r, { n: exp.length, nDeleted: exp.length, ok: 1 })
131
+ })
132
+ }
133
+ }
134
+
135
+ })
136
+
137
+
138
+ describe(`${label} find白名單: 專屬斷言`, function() {
139
+
140
+ let w = null
141
+ let w2 = null
142
+ let ins = null
143
+ let cs = null
144
+
145
+ let reset = async () => {
146
+ await w.delAll({}, cs)
147
+ await w.insert(seed(), cs)
148
+ }
149
+
150
+ before(async function() {
151
+ w = wo(getOpt({ cl: 'tfind' }))
152
+ await w.createStorage()
153
+ ins = await w.init()
154
+ cs = { instance: ins }
155
+ w2 = wo(getOpt({ cl: 'tfind', logging: true })) //不給instance, 每次呼叫各自連線並輸出實際執行之sql
156
+ })
157
+
158
+ after(async function() {
159
+ await ins.close()
160
+ })
161
+
162
+ it('壞find不送出任何SQL: 形狀不合法者reject, error事件於reject前恰一次', async function() {
163
+ await reset()
164
+ let logs = []
165
+ let old = console.log
166
+ console.log = (...a) => logs.push(a.map((v) => String(v)).join(' '))
167
+ let bads = []
168
+ let n = 0
169
+ try {
170
+ for (let c of cases.filter((v) => v.expect === 'REJ')) {
171
+ let evs = []
172
+ let fnE = (mode) => evs.push(`error:${mode}`)
173
+ w2.on('error', fnE)
174
+ let nb = logs.length
175
+ let rs = await settle(w2.select(c.find()))
176
+ evs.push('reject:select')
177
+ let rd = await settle(w2.delAll(c.find()))
178
+ evs.push('reject:delAll')
179
+ w2.off('error', fnE)
180
+ n++
181
+ if (logs.length !== nb) {
182
+ bads.push(`${c.id} 送出SQL: ${logs.slice(nb).join(' | ').slice(0, 120)}`)
183
+ }
184
+ if (rs.err === undefined || rd.err === undefined) {
185
+ bads.push(`${c.id} 未reject`)
186
+ }
187
+ else if (evs.join() !== 'error:select,reject:select,error:delAll,reject:delAll') {
188
+ bads.push(`${c.id} 事件順序 ${evs.join()}`)
189
+ }
190
+ }
191
+ //對照: 合法find會輸出sql, 確認攔截有效
192
+ let nb = logs.length
193
+ await w2.select({ id: 'a1' })
194
+ assert.ok(logs.length > nb && logs.slice(nb).some((v) => v.indexOf('Executing') >= 0), '對照組未輸出sql, 攔截無效')
195
+ }
196
+ finally {
197
+ console.log = old
198
+ }
199
+ assert.ok(n >= 100, `REJ案數過少: ${n}`)
200
+ assert.deepStrictEqual(bads, [], `下列壞find仍送出SQL:\n${bads.join('\n')}`)
201
+ })
202
+
203
+ it('形狀檢核先於model載入與sequelize實例建立: model檔缺失時壞find回invalid find, 合法find回can not import model', async function() {
204
+ //R11: select/delAll於procClosed之後、initSequelize之前呼叫chkFindShape; 檢核若在載入model之後(HEAD之順序), 壞find會被model載入失敗之訊息掩蓋
205
+ let wm = wo(getOpt({ cl: 'nomodel' }))
206
+ let old = console.log
207
+ console.log = () => {} //initSequelize於model載入失敗時以console.log輸出細節
208
+ try {
209
+ for (let mode of ['select', 'delAll']) {
210
+ let evs = []
211
+ let fn = (m) => evs.push(m)
212
+ wm.on('error', fn)
213
+ let bad = await settle(wm[mode]({ age: { $foo: 1 } }))
214
+ let good = await settle(wm[mode]({}))
215
+ wm.off('error', fn)
216
+ assert.ok(bad.err !== undefined && msgOf(bad.err).indexOf('invalid find: unsupported operator [$foo]') === 0, `${mode} 壞find: ${bad.err && msgOf(bad.err)}`)
217
+ assert.ok(good.err !== undefined && msgOf(good.err).indexOf('can not import model: nomodel') === 0, `${mode} 合法find(對照): ${good.err && msgOf(good.err)}`)
218
+ assert.deepStrictEqual(evs, [mode, mode], `${mode}: error事件各一次`)
219
+ }
220
+ }
221
+ finally {
222
+ console.log = old
223
+ }
224
+ })
225
+
226
+ for (let op of ['$or', '$and']) {
227
+ it(`${op} 999項可、1000項reject(SQLite平鋪1000項為DB錯誤, 由maxList:999於形狀階段先擋)`, async function() {
228
+ await reset()
229
+ let mk = (n) => {
230
+ //首項命中a1, 其餘為不改變結果之填充(取代$or之k無命中列; $and之g不等於zz恆真於a1)
231
+ return op === '$or'
232
+ ? { $or: [{ id: 'a1' }, ...fills(n - 1).map((k) => ({ id: k }))] }
233
+ : { $and: [{ id: 'a1' }, ...fills(n - 1).map((k) => ({ g: { $ne: k } }))] }
234
+ }
235
+ let rs = await w.select(mk(999), cs)
236
+ assert.deepStrictEqual(ids(rs), ['a1'])
237
+ let r = await w.delAll(mk(999), cs)
238
+ assert.deepStrictEqual(r, { n: 1, nDeleted: 1, ok: 1 })
239
+ assert.deepStrictEqual(ids(await w.select({}, cs)), ['a2', 'a3', 'a4', 'a5', 'a6'])
240
+ await reset()
241
+ let before = await w.select({}, cs)
242
+ for (let mode of ['select', 'delAll']) {
243
+ let x = await settle(w[mode](mk(1000), cs))
244
+ assert.ok(x.err !== undefined, `${mode} 1000項應reject`)
245
+ assert.ok(msgOf(x.err).indexOf('invalid find: array is too large (> 999)') === 0, msgOf(x.err))
246
+ }
247
+ assert.deepStrictEqual(await w.select({}, cs), before)
248
+ })
249
+ }
250
+
251
+ //nest, 以op包n層, 每層width項(其餘為不改變結果之填充); nestedFirst決定內層位於清單首或末; 'mix'為逐層交替$or與$and
252
+ //葉為{id:'a1'}, 預期命中a1(填充: $or之{id:never}恆假、$and之{g:{$ne:never}}於a1恆真; 各層可選帶並列欄位使同物件內之隱含AND亦巢狀累加)
253
+ let nest = (op, levels, width, nestedFirst, extra) => {
254
+ let f = { id: 'a1' }
255
+ for (let d = 0; d < levels; d++) {
256
+ let o = op === 'mix' ? (d % 2 === 0 ? '$or' : '$and') : op
257
+ let fills = Array.from({ length: width - 1 }, (_, i) => (o === '$or' ? { id: `never${d}_${i}` } : { g: { $ne: `never${d}_${i}` } }))
258
+ let key = extra ? { [o]: nestedFirst ? [f, ...fills] : [...fills, f], name: { $ne: 'zz' }, age: { $gt: 0 }, g: { $ne: 'zz' }, flag: true, d: { $gte: new Date(0) } } : { [o]: nestedFirst ? [f, ...fills] : [...fills, f] }
259
+ f = key
260
+ }
261
+ return f
262
+ }
263
+ for (let [op, levels, width, nestedFirst, extra] of [
264
+ ['$or', 32, 33, true, false],
265
+ ['$or', 32, 33, false, false],
266
+ ['$and', 32, 33, true, false],
267
+ ['$and', 32, 33, false, false],
268
+ ['mix', 32, 33, true, false],
269
+ ['$or', 32, 99, true, false],
270
+ ['$and', 32, 99, false, false],
271
+ ['$or', 4, 999, true, false],
272
+ ['$and', 4, 999, false, false],
273
+ ['$or', 32, 4, true, true],
274
+ ['mix', 32, 33, false, true],
275
+ ]) {
276
+ it(`巢狀 × 清單長度: ${op} ${levels}層 × 每層${width}項${extra ? ' × 每層並列5欄(隱含AND)' : ''}(內層在${nestedFirst ? '首' : '末'}): select與delAll皆可執行且命中一致(樹深不隨巢狀 × 項數線性累加)`, async function() {
277
+ await reset()
278
+ let rs = await settle(w.select(nest(op, levels, width, nestedFirst, extra), cs))
279
+ assert.ok(rs.err === undefined, `select: ${rs.err && msgOf(rs.err)}`)
280
+ assert.deepStrictEqual(ids(rs.v), ['a1'])
281
+ let rd = await settle(w.delAll(nest(op, levels, width, nestedFirst, extra), cs))
282
+ assert.ok(rd.err === undefined, `delAll: ${rd.err && msgOf(rd.err)}`)
283
+ assert.deepStrictEqual(rd.v, { n: 1, nDeleted: 1, ok: 1 })
284
+ })
285
+ }
286
+
287
+ it('預檢失敗於初始化之前時, 不得關閉呼叫端先前持有之instance(只關閉本次呼叫自己建立者)', async function() {
288
+ let wc = wo(getOpt({ cl: 'tfind' }))
289
+ let instance = await wc.init()
290
+ try {
291
+ for (let mode of ['select', 'delAll']) {
292
+ let x = await settle(wc[mode]({ g: { $bad: 1 } })) //未帶instance
293
+ assert.ok(x.err !== undefined && msgOf(x.err).indexOf('invalid find: unsupported operator [$bad]') === 0, `${mode}: ${x.err && msgOf(x.err)}`)
294
+ //先前持有之instance仍可使用
295
+ let rs = await settle(wc.select({ id: 'a1' }, { instance }))
296
+ assert.ok(rs.err === undefined && Array.isArray(rs.v), `${mode}之後instance被關閉: ${rs.err && msgOf(rs.err)}`)
297
+ }
298
+ }
299
+ finally {
300
+ await instance.close()
301
+ }
302
+ })
303
+
304
+ it('實例已關閉者先回closed訊息(isClosed先於find檢核); 實例未關閉而find壞者回invalid find', async function() {
305
+ let wc = wo(getOpt({ cl: 'tfind' }))
306
+ let instance = await wc.init()
307
+ try {
308
+ let x = await settle(wc.select(null, { instance }))
309
+ assert.ok(x.err !== undefined && msgOf(x.err).indexOf('invalid find: ') === 0, x.err && msgOf(x.err))
310
+ x = await settle(wc.delAll({ $nor: [{ g: 'A' }] }, { instance }))
311
+ assert.ok(x.err !== undefined && msgOf(x.err).indexOf('invalid find: unsupported operator [$nor]') === 0, x.err && msgOf(x.err))
312
+ }
313
+ finally {
314
+ await instance.close()
315
+ }
316
+ for (let bad of [null, [], { $not: { g: 'A' } }, { g: { $regex: 'A' } }]) {
317
+ for (let mode of ['select', 'delAll']) {
318
+ let y = await settle(wc[mode](bad, { instance }))
319
+ assert.ok(y.err !== undefined && msgOf(y.err).indexOf('instance is closed') >= 0, `${mode}: ${y.err && msgOf(y.err)}`)
320
+ }
321
+ }
322
+ })
323
+
324
+ it('頂層: 未給、undefined、{}為全部; null、陣列、字串、數字等純量與陣列皆reject且不刪除任何數據', async function() {
325
+ await reset()
326
+ for (let f of [undefined, {}, Object.create(null)]) {
327
+ assert.deepStrictEqual(ids(await w.select(f, cs)), ALL)
328
+ }
329
+ for (let f of [null, [], [{ id: 'a1' }], 'a1', 0, 1, true, false, () => ({}), Symbol('s'), BigInt(1)]) {
330
+ for (let mode of ['select', 'delAll']) {
331
+ let x = await settle(w[mode](f, cs))
332
+ assert.ok(x.err !== undefined, `${mode}(${String(typeof f)}) 應reject`)
333
+ }
334
+ }
335
+ assert.deepStrictEqual(ids(await w.select({}, cs)), ALL)
336
+ let r = await w.delAll(undefined, cs)
337
+ assert.deepStrictEqual(r, { n: 6, nDeleted: 6, ok: 1 })
338
+ })
339
+
340
+ it('檢查之值即使用之值: select與delAll皆逐屬性只讀一次並以所讀之值轉譯(Proxy之描述子陷阱於第二次讀取時回傳不同值)', async function() {
341
+ for (let mode of ['select', 'delAll']) {
342
+ await reset()
343
+ let n = 0
344
+ let p = new Proxy({ g: 'A' }, {
345
+ getOwnPropertyDescriptor(t, k) {
346
+ let d = Reflect.getOwnPropertyDescriptor(t, k)
347
+ if (k === 'g') {
348
+ n++
349
+ return { ...d, value: n === 1 ? 'A' : 'B' }
350
+ }
351
+ return d
352
+ }
353
+ })
354
+ let r = await w[mode](p, cs)
355
+ if (mode === 'select') {
356
+ assert.deepStrictEqual(ids(r), ['a1', 'a2'], 'select之命中')
357
+ }
358
+ else {
359
+ assert.deepStrictEqual(r, { n: 2, nDeleted: 2, ok: 1 })
360
+ assert.deepStrictEqual(ids(await w.select({}, cs)), ['a3', 'a4', 'a5', 'a6'], 'delAll之刪除')
361
+ }
362
+ assert.strictEqual(n, 1, `${mode}: g被讀取次數`)
363
+ }
364
+ })
365
+
366
+ it('匯出簽名不變: cvFind(find, ctx)、getColKind、getColScale、normPk(w-orm-mdb深引用)', function() {
367
+ assert.strictEqual(typeof cvFindDef, 'function')
368
+ assert.strictEqual(cvFindDef.length, 2)
369
+ for (let [k, len] of [['getColKind', 2], ['getColScale', 2], ['normPk', 2]]) {
370
+ assert.strictEqual(typeof cvMod[k], 'function', k)
371
+ assert.strictEqual(cvMod[k].length, len, k)
372
+ }
373
+ //未給條件者回{}(全部), 不經sequelize
374
+ assert.deepStrictEqual(cvFindDef(undefined, {}), {})
375
+ assert.deepStrictEqual(cvFindDef({}, {}), {})
376
+ })
377
+
378
+ it('chkFindShape即cvOrmFind(find, {maxList:999}): 複本為null原型、與輸入無共用參照, 且對複本再檢核冪等', function() {
379
+ let src = { g: 'A', $or: [{ age: { $in: [20, 30] } }, { d: { $gte: new Date(0) } }] }
380
+ let cp = cvMod.chkFindShape(src)
381
+ assert.strictEqual(Object.getPrototypeOf(cp), null)
382
+ assert.notStrictEqual(cp.$or, src.$or)
383
+ assert.notStrictEqual(cp.$or[1].d.$gte, src.$or[1].d.$gte)
384
+ assert.deepStrictEqual(cvMod.chkFindShape(cp), cp)
385
+ assert.deepStrictEqual(cp, cvOrmFind(src, { maxList: 999 }))
386
+ })
387
+
388
+ })
389
+
390
+ }
@@ -128,6 +128,29 @@ describe('models file', function() {
128
128
  assert.deepStrictEqual((await wAll.select()).map((v) => v.id).sort(), ['a', 'b'])
129
129
  })
130
130
 
131
+ it('model之defaultScope帶group/having、include或含Op.and之where時delAll拒絕且不刪除(select與destroy對這些選項之處置不同, 或scope之Op.and被cvFind頂層之Op.and覆寫)', async function() {
132
+ //rulebook R1: findAll套用scope之group/having與required include(只回部分列), destroy卻不套用而刪除全部符合where者(run05 sol F03、opus 疑慮(ii), sqlite實測 select=[2] 而 destroy=3);
133
+ //scope.where含Op.and者, sequelize以Object.assign合併而被cvFind輸出之頂層Op.and覆寫, scope條件消失(opus 疑慮(i))
134
+ let cases = [
135
+ ['group+having', 'defaultScope: { group: [\'id\'], having: sequelize.literal(\'value > 0\') }'],
136
+ ['required include', 'defaultScope: { include: [{ model: sequelize.define(\'uchild\', { id: { type: DataTypes.STRING, primaryKey: true } }, { tableName: \'uchild\' }), required: true }] }'],
137
+ ['where含Op.and', 'defaultScope: { where: { [require(\'sequelize\').Op.and]: [{ name: \'x\' }] } }'],
138
+ ]
139
+ for (let [i, [t, sc]] of cases.entries()) {
140
+ let fdm = path.join(fd, `m6${i}`)
141
+ fs.mkdirSync(fdm, { recursive: true })
142
+ fs.writeFileSync(path.join(fdm, 'users.js'), genModel(true).replace('tableName: \'users\'', `tableName: 'users', ${sc}`), 'utf8')
143
+ let st = path.join(fd, `m6${i}.sqlite`)
144
+ let w = wo({ url: 'sqlite://:', storage: st, db: 'worm', cl: 'users', fdModels: fdm })
145
+ await w.createStorage()
146
+ await w.insert([{ id: 'a', name: 'x', value: 1 }, { id: 'b', name: 'y', value: 0 }])
147
+ let r = await w.delAll({ value: { $gte: 0 } }).then(() => null).catch((err) => String(err.message || err))
148
+ assert.ok(r !== null && r.indexOf('defaultScope') >= 0, `${t}: 未reject: ${r}`)
149
+ let wAll = wo({ url: 'sqlite://:', storage: st, db: 'worm', cl: 'users', fdModels: path.join(fd, 'm1') })
150
+ assert.deepStrictEqual((await wAll.select()).map((v) => v.id).sort(), ['a', 'b'], `${t}: 資料被刪`)
151
+ }
152
+ })
153
+
131
154
  it('BIGINT主鍵: 超出安全整數之數值為型別不符, 精確值以標準整數寫法之字串給予', async function() {
132
155
  //rulebook R8: 整數類主鍵之數值須為安全整數, 否則已失精度而會命中相鄰主鍵; 超出安全整數之標準整數寫法字串保留為字串(表外細化)
133
156
  let fdm = path.join(fd, 'm5')
@@ -2,6 +2,7 @@ import net from 'net'
2
2
  import { execFile } from 'child_process'
3
3
  import { promisify } from 'util'
4
4
  import { defineSuites } from './api-suite.mjs'
5
+ import { defineWhitelistSuites } from './api-find-whitelist.mjs'
5
6
 
6
7
 
7
8
  //測試自行創建docker容器供MSSQL服務, 測試結束即銷毀, 故僅需環境已安裝docker
@@ -113,3 +114,4 @@ after(async function() {
113
114
 
114
115
 
115
116
  defineSuites('mssql', getOpt)
117
+ defineWhitelistSuites('mssql', getOpt)
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs'
2
2
  import { defineSuites } from './api-suite.mjs'
3
+ import { defineWhitelistSuites } from './api-find-whitelist.mjs'
3
4
 
4
5
 
5
6
  //sqlite使用獨立暫存storage檔測試, 不使用專案根目錄之worm.sqlite
@@ -40,3 +41,4 @@ after(function() {
40
41
 
41
42
 
42
43
  defineSuites('sqlite', getOpt)
44
+ defineWhitelistSuites('sqlite', getOpt)