w-orm-reladb 1.1.9 → 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.
- package/README.md +59 -28
- package/dist/w-orm-reladb.umd.js +2 -2
- package/dist/w-orm-reladb.umd.js.map +1 -1
- package/docs/WOrmReladb.html +52 -30
- package/docs/WOrmReladb.mjs.html +59 -28
- package/docs/cvFind.mjs.html +127 -405
- package/docs/global.html +14 -11
- package/docs/importModels.mjs.html +1 -1
- package/docs/index.html +1 -1
- package/g-mssql-transaction-commit.mjs +1 -1
- package/g-mssql-transaction-rollback.mjs +1 -1
- package/g-mssql.mjs +4 -4
- package/g-sqlite-encryption-transaction-commit.mjs +1 -1
- package/g-sqlite-encryption.mjs +4 -4
- package/g-sqlite-transaction-commit.mjs +1 -1
- package/g-sqlite-transaction-rollback.mjs +1 -1
- package/g-sqlite.mjs +4 -4
- package/models/tfind.js +50 -0
- package/package.json +2 -2
- package/src/WOrmReladb.mjs +58 -27
- package/src/cvFind.mjs +126 -404
- package/test/api-find-whitelist.mjs +390 -0
- package/test/api-mssql.test.mjs +2 -0
- package/test/api-sqlite.test.mjs +2 -0
- package/test/api-suite.mjs +95 -136
- package/test/unit-cvfind.test.mjs +241 -218
|
@@ -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
|
+
}
|
package/test/api-mssql.test.mjs
CHANGED
|
@@ -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)
|
package/test/api-sqlite.test.mjs
CHANGED
|
@@ -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)
|