w-serv-orm 1.1.7 → 1.1.9

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,567 @@
1
+ import assert from 'assert'
2
+ import WServOrm from '../src/WServOrm.mjs'
3
+ import getMapOrm from '../src/getMapOrm.mjs'
4
+ import getItemsFailWdc from 'w-data-collector/src/getItemsFail.mjs'
5
+
6
+
7
+ //替身ORM工廠: 以name辨識所屬實例, 呼叫紀錄寫入log; 回傳形狀依w-orm系列規格(save、del回逐筆陣列, insert回聚合物件)
8
+ function mkWOrm(name, log, behavior = {}) {
9
+ return function() {
10
+ let ev = {}
11
+ for (let fn of ['select', 'insert', 'save', 'del', 'delAll']) {
12
+ ev[fn] = async (data) => {
13
+ log.push({ name, fn, data })
14
+ if (behavior[fn]) {
15
+ return behavior[fn](data)
16
+ }
17
+ if (fn === 'select') {
18
+ return [{ id: `${name}-r1`, isActive: 'n' }, { id: `${name}-r2`, isActive: 'y' }]
19
+ }
20
+ if (fn === 'save' || fn === 'del') {
21
+ return (Array.isArray(data) ? data : [data]).map(() => ({ n: 1, ok: 1 }))
22
+ }
23
+ return { n: 1, nInserted: 1, ok: 1 }
24
+ }
25
+ }
26
+ return ev
27
+ }
28
+ }
29
+
30
+ let dsAll = { tests: { keys: ['id', 'name', 'isActive', 'timeUpdate'] } }
31
+ let dsNoTime = { tests: { keys: ['id', 'name', 'isActive'] } }
32
+ let optOff = { useCheckUser: false, useExcludeWhenNotAdmin: false }
33
+ let wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
34
+
35
+ async function getReject(pm) {
36
+ try {
37
+ await pm
38
+ }
39
+ catch (err) {
40
+ return err
41
+ }
42
+ throw new Error('預期reject但resolve')
43
+ }
44
+
45
+ async function captureLog(fn) {
46
+ let logs = []
47
+ let logOrig = console.log
48
+ console.log = (...args) => {
49
+ logs.push(args)
50
+ }
51
+ try {
52
+ await fn()
53
+ }
54
+ finally {
55
+ console.log = logOrig
56
+ }
57
+ return logs
58
+ }
59
+
60
+
61
+ describe('WServOrm實例互不干擾', function() {
62
+
63
+ it('I01 兩實例之procOrm各自路由至自己的ORM', async function() {
64
+ //R1: 雙向皆打自己的ORM, 解構後呼叫亦然
65
+ let log = []
66
+ let A = WServOrm(dsAll, mkWOrm('A', log), 'url-a', 'db-a', optOff)
67
+ let B = WServOrm(dsAll, mkWOrm('B', log), 'url-b', 'db-b', optOff)
68
+ let { procOrm: pA } = A
69
+ let { procOrm: pB } = B
70
+ await pA('u1', 'tests', 'select', {})
71
+ await pB('u1', 'tests', 'select', {})
72
+ await pA('u1', 'tests', 'save', [{ id: 'x' }])
73
+ await pB('u1', 'tests', 'del', [{ id: 'y' }])
74
+ assert.strict.deepEqual(log.map((v) => `${v.name}.${v.fn}`), ['A.select', 'B.select', 'A.save', 'B.del'])
75
+ })
76
+
77
+ it('I02 各實例依自己之ds調整數據', async function() {
78
+ //R1: A之ds含timeUpdate而補值, B之ds不含而不補
79
+ let log = []
80
+ let A = WServOrm(dsAll, mkWOrm('A', log), 'url-a', 'db-a', optOff)
81
+ let B = WServOrm(dsNoTime, mkWOrm('B', log), 'url-b', 'db-b', optOff)
82
+ await A.procOrm('u1', 'tests', 'save', [{ id: 'x' }])
83
+ await B.procOrm('u1', 'tests', 'save', [{ id: 'y' }])
84
+ assert.strict.deepEqual(log.map((v) => v.name), ['A', 'B'])
85
+ assert.ok('timeUpdate' in log[0].data[0])
86
+ assert.ok(!('timeUpdate' in log[1].data[0]))
87
+ })
88
+
89
+ it('I03 已移除之設定給予非預設值即建構拋錯且不建立ORM', async function() {
90
+ //R3: useCheckUser、getUserById、useExcludeWhenNotAdmin已移除; 給予未給、null、false以外之值即拋錯, 避免使用端以為仍有檢查而無聲失去
91
+ //拋錯於建立各資料表ORM之前, 否則已開啟之ORM(如lmdb之檔案)無從關閉; 亦不影響既有實例
92
+ let log = []
93
+ let A = WServOrm(dsAll, mkWOrm('A', log), 'url-a', 'db-a', optOff)
94
+ let created = 0
95
+ let WOrmCount = function() {
96
+ created++
97
+ return {}
98
+ }
99
+ let bads = [
100
+ ['useCheckUser', true],
101
+ ['useCheckUser', 'true'],
102
+ ['useCheckUser', 1],
103
+ ['getUserById', async () => ({ isActive: 'y' })],
104
+ ['getUserById', 'x'],
105
+ ['useExcludeWhenNotAdmin', true],
106
+ ['useExcludeWhenNotAdmin', 'n'],
107
+ ]
108
+ for (let [k, v] of bads) {
109
+ assert.throws(() => WServOrm(dsAll, WOrmCount, 'url-x', 'db-x', { [k]: v }), (e) => e instanceof Error && e.message.startsWith(`opt.${k} has been removed, `), `${k}: ${String(v)}`)
110
+ }
111
+ assert.strict.equal(created, 0)
112
+ let r = await A.procOrm('u1', 'tests', 'select', {})
113
+ assert.strict.deepEqual(r.map((v) => v.id), ['A-r1', 'A-r2'])
114
+ })
115
+
116
+ it('I04 已移除之設定未給、null或false照常建構, 不檢查使用者亦不過濾', async function() {
117
+ //R3: 四個使用端現行之設定({useCheckUser:false, getUserById:null, useExcludeWhenNotAdmin:false})升版照常可用
118
+ //procOrm不做授權: userId不檢查(含空字串、null、undefined), select不依使用者濾除isActive為n者
119
+ let opts = [undefined, null, {}, { useCheckUser: null }, { useCheckUser: false, getUserById: null, useExcludeWhenNotAdmin: false }]
120
+ for (let opt of opts) {
121
+ let X = WServOrm(dsAll, mkWOrm('X', []), 'url-x', 'db-x', opt)
122
+ for (let userId of ['u1', '', null, undefined]) {
123
+ let r = await X.procOrm(userId, 'tests', 'select', {})
124
+ assert.strict.deepEqual(r.map((v) => v.id), ['X-r1', 'X-r2'], `opt ${JSON.stringify(opt)}, userId ${String(userId)}`)
125
+ }
126
+ }
127
+ })
128
+
129
+ it('I05 三種hook各自屬於所註冊之實例', async function() {
130
+ //R1、R4: 雙向隔離; 註冊並等待超過1ms後生效
131
+ let log = []
132
+ let calls = []
133
+ let A = WServOrm(dsAll, mkWOrm('A', log), 'url-a', 'db-a', optOff)
134
+ let B = WServOrm(dsAll, mkWOrm('B', log), 'url-b', 'db-b', optOff)
135
+ for (let [name, ins] of [['A', A], ['B', B]]) {
136
+ ins.addFunCheck(() => {
137
+ calls.push(`${name}-check`)
138
+ })
139
+ ins.addFunPreProcessing((p) => {
140
+ calls.push(`${name}-pre`)
141
+ return p
142
+ })
143
+ ins.addFunPostProcessing((p) => {
144
+ calls.push(`${name}-post`)
145
+ return p.output
146
+ })
147
+ }
148
+ await wait(20)
149
+ await A.procOrm('u1', 'tests', 'select', {})
150
+ assert.strict.deepEqual(calls, ['A-check', 'A-pre', 'A-post'])
151
+ calls.length = 0
152
+ await B.procOrm('u1', 'tests', 'select', {})
153
+ assert.strict.deepEqual(calls, ['B-check', 'B-pre', 'B-post'])
154
+ })
155
+
156
+ it('I06 新建實例不繼承既有實例之hook', async function() {
157
+ //R1d: A註冊後再建C, C之procOrm不呼叫A之hook
158
+ let log = []
159
+ let calls = []
160
+ let A = WServOrm(dsAll, mkWOrm('A', log), 'url-a', 'db-a', optOff)
161
+ A.addFunCheck(() => {
162
+ calls.push('A-check')
163
+ })
164
+ await wait(20)
165
+ let C = WServOrm(dsAll, mkWOrm('C', log), 'url-c', 'db-c', optOff)
166
+ await C.procOrm('u1', 'tests', 'select', {})
167
+ assert.strict.deepEqual(calls, [])
168
+ })
169
+
170
+ it('I07 直接呼叫getMapOrm兩次各打自己的woItems', async function() {
171
+ //R1c: 使用端直接import getMapOrm之用法, 回傳函數之參數形狀不變
172
+ let log = []
173
+ let mk = (name) => ({
174
+ tests: {
175
+ select: async () => {
176
+ log.push(name)
177
+ return []
178
+ },
179
+ },
180
+ })
181
+ let m1 = getMapOrm(dsAll, mk('W1'))
182
+ let m2 = getMapOrm(dsAll, mk('W2'))
183
+ await m1('u1', 'tests', 'select', {})
184
+ await m2('u1', 'tests', 'select', {})
185
+ assert.strict.deepEqual(log, ['W1', 'W2'])
186
+ })
187
+
188
+ it('I08 擴充函數之參數不含isAdmin', async function() {
189
+ //R3: 隨useCheckUser移除, 參數物件只含woName、userId、mode、input, 後處理另含output
190
+ let A = WServOrm(dsAll, mkWOrm('A', []), 'url-a', 'db-a', optOff)
191
+ let ks = {}
192
+ A.addFunCheck((p) => {
193
+ ks.check = Object.keys(p)
194
+ })
195
+ A.addFunPreProcessing((p) => {
196
+ ks.pre = Object.keys(p)
197
+ return p
198
+ })
199
+ A.addFunPostProcessing((p) => {
200
+ ks.post = Object.keys(p)
201
+ return p.output
202
+ })
203
+ await wait(20)
204
+ await A.procOrm('u1', 'tests', 'select', {})
205
+ assert.strict.deepEqual(ks, {
206
+ check: ['woName', 'userId', 'mode', 'input'],
207
+ pre: ['woName', 'userId', 'mode', 'input'],
208
+ post: ['woName', 'userId', 'mode', 'input', 'output'],
209
+ })
210
+ })
211
+
212
+ it('I09 呼叫進行中建立另一實例不影響該次呼叫', async function() {
213
+ //R1a: A之check等待中建立B, A之呼叫仍打A之ORM
214
+ let log = []
215
+ let release = null
216
+ let gate = new Promise((resolve) => {
217
+ release = resolve
218
+ })
219
+ let A = WServOrm(dsAll, mkWOrm('A', log), 'url-a', 'db-a', optOff)
220
+ A.addFunCheck(async () => {
221
+ await gate
222
+ })
223
+ await wait(20)
224
+ let pA = A.procOrm('u1', 'tests', 'select', {})
225
+ await wait(5)
226
+ WServOrm(dsAll, mkWOrm('B', log), 'url-b', 'db-b', optOff)
227
+ release()
228
+ let rA = await pA
229
+ assert.strict.deepEqual(log.map((v) => v.name), ['A'])
230
+ assert.strict.deepEqual(rA.map((v) => v.id), ['A-r1', 'A-r2'])
231
+ })
232
+
233
+ it('I10 同一實例之woItems直寫與procOrm寫入打同一個ORM', async function() {
234
+ //R1b: 使用端先以woItems直接寫入再經procOrm寫入之用法
235
+ let log = []
236
+ let A = WServOrm(dsAll, mkWOrm('A', log), 'url-a', 'db-a', optOff)
237
+ WServOrm(dsAll, mkWOrm('B', log), 'url-b', 'db-b', optOff)
238
+ await A.woItems.tests.save([{ id: 'x' }])
239
+ await A.procOrm('u1', 'tests', 'save', [{ id: 'y' }])
240
+ assert.strict.deepEqual(log.map((v) => v.name), ['A', 'A'])
241
+ })
242
+
243
+ it('I11 回傳函數之名稱與型別不變', async function() {
244
+ //R2: 使用端以解構取用並當裸函數傳遞; addFun*回傳undefined; procOrm為async函數
245
+ let A = WServOrm(dsAll, mkWOrm('A', []), 'url-a', 'db-a', optOff)
246
+ assert.strict.equal(A.procOrm.name, 'procOrm')
247
+ assert.strict.equal(Object.prototype.toString.call(A.procOrm), '[object AsyncFunction]')
248
+ for (let k of ['addFunCheck', 'addFunPreProcessing', 'addFunPostProcessing']) {
249
+ assert.strict.equal(A[k].name, k)
250
+ assert.strict.equal(A[k](() => {}), undefined)
251
+ }
252
+ })
253
+
254
+ })
255
+
256
+
257
+ describe('procOrm後處理', function() {
258
+
259
+ it('H01 逐筆失敗而reject時不執行後處理, 成功時執行', async function() {
260
+ //R5: 後處理只在前面各階段皆成功後執行
261
+ let log = []
262
+ let calls = []
263
+ let save = (d) => d.map((v, k) => (k === 1 ? { n: 1, ok: 0, err: 'E' } : { n: 1, ok: 1 }))
264
+ let A = WServOrm(dsAll, mkWOrm('A', log, { save }), 'url-a', 'db-a', optOff)
265
+ A.addFunPostProcessing((p) => {
266
+ calls.push(p.mode)
267
+ return p.output
268
+ })
269
+ await wait(20)
270
+ let err = await getReject(A.procOrm('u1', 'tests', 'save', [{ id: 'a' }, { id: 'b' }]))
271
+ assert.strict.equal(err, 'failed to save data: id[b]')
272
+ assert.strict.deepEqual(calls, [])
273
+ await A.procOrm('u1', 'tests', 'select', {})
274
+ assert.strict.deepEqual(calls, ['select'])
275
+ })
276
+
277
+ })
278
+
279
+
280
+ describe('mapOrm之log標籤與訊息', function() {
281
+
282
+ it('L01 insert與mix之insert、del整批失敗之標籤', async function() {
283
+ //R6: 標籤依實際操作, reject訊息為該操作之整批失敗訊息
284
+ let log = []
285
+ let fail = () => Promise.reject(new Error('E-batch'))
286
+ let A = WServOrm(dsAll, mkWOrm('A', log, { insert: fail, del: fail }), 'url-a', 'db-a', optOff)
287
+ let errs = []
288
+ let logs = await captureLog(async () => {
289
+ errs.push(await getReject(A.procOrm('u1', 'tests', 'insert', [{ id: 'a' }])))
290
+ errs.push(await getReject(A.procOrm('u1', 'tests', 'mix', { add: [{ id: 'a' }] })))
291
+ errs.push(await getReject(A.procOrm('u1', 'tests', 'mix', { del: [{ id: 'd' }] })))
292
+ })
293
+ assert.strict.deepEqual(errs, ['failed to insert data', 'failed to insert data', 'failed to delete data'])
294
+ assert.strict.deepEqual(logs.map((v) => v[0]), ['tests.insert catch', 'tests.insert catch', 'tests.del catch'])
295
+ })
296
+
297
+ it('L02 adjustData失敗之標籤含表名', async function() {
298
+ //R6: save、mix之add與diff三處adjustData失敗皆記表名, reject訊息為整批失敗訊息
299
+ let log = []
300
+ let A = WServOrm({ tests: {} }, mkWOrm('A', log), 'url-a', 'db-a', optOff)
301
+ let errs = []
302
+ let logs = await captureLog(async () => {
303
+ errs.push(await getReject(A.procOrm('u1', 'tests', 'save', [{ id: 'a' }])))
304
+ errs.push(await getReject(A.procOrm('u1', 'tests', 'mix', { add: [{ id: 'a' }] })))
305
+ errs.push(await getReject(A.procOrm('u1', 'tests', 'mix', { diff: [{ id: 'a' }] })))
306
+ })
307
+ assert.strict.deepEqual(errs, ['failed to adjust data', 'failed to adjust data', 'failed to adjust data'])
308
+ assert.strict.deepEqual(logs.map((v) => v[0]), ['tests.adjustData catch', 'tests.adjustData catch', 'tests.adjustData catch'])
309
+ })
310
+
311
+ it('T01 失敗筆主鍵超過100字元時訊息內截斷且log保留全文', async function() {
312
+ //R7: 截為100字元(含「…」), 恰為100字元者不截斷
313
+ let id150 = 'k'.repeat(150)
314
+ let id100 = 'm'.repeat(100)
315
+ let save = (d) => d.map(() => ({ n: 1, ok: 0, err: 'E' }))
316
+ let A = WServOrm(dsAll, mkWOrm('A', [], { save }), 'url-a', 'db-a', optOff)
317
+ let err1 = null
318
+ let err2 = null
319
+ let logs = await captureLog(async () => {
320
+ err1 = await getReject(A.procOrm('u1', 'tests', 'save', [{ id: id150 }]))
321
+ err2 = await getReject(A.procOrm('u1', 'tests', 'save', [{ id: id100 }]))
322
+ })
323
+ assert.strict.equal(err1, `failed to save data: id[${'k'.repeat(99)}…]`)
324
+ assert.strict.equal(err2, `failed to save data: id[${id100}]`)
325
+ assert.ok(JSON.stringify(logs).includes(id150))
326
+ })
327
+
328
+ })
329
+
330
+
331
+ describe('procOrm補主鍵', function() {
332
+
333
+ let isId = (v) => typeof v === 'string' && v.length > 0
334
+
335
+ it('P01 insert、save、mix之新增未帶主鍵時補值並回傳', async function() {
336
+ //D1: 本套件以genIDSeq補值, 回傳之id即ORM收到之id; 物件輸入回傳單一{id}
337
+ let log = []
338
+ let A = WServOrm(dsAll, mkWOrm('A', log), 'url-a', 'db-a', optOff)
339
+ let r1 = await A.procOrm('u1', 'tests', 'insert', [{ name: 'a' }])
340
+ let r2 = await A.procOrm('u1', 'tests', 'save', { name: 'b' })
341
+ let r3 = await A.procOrm('u1', 'tests', 'mix', { add: [{ name: 'c' }] })
342
+ assert.ok(isId(r1[0].id) && isId(r2.id) && isId(r3[0].id))
343
+ assert.strict.equal(log[0].data[0].id, r1[0].id)
344
+ assert.strict.equal(log[1].data.id, r2.id)
345
+ assert.strict.equal(log[2].data[0].id, r3[0].id)
346
+ })
347
+
348
+ it('P02 依主鍵值決定是否補值', async function() {
349
+ //D1: null、空字串、物件補值; 數字與非空字串保留(reladb、mdb視數字為有效主鍵)
350
+ let log = []
351
+ let A = WServOrm(dsAll, mkWOrm('A', log), 'url-a', 'db-a', optOff)
352
+ let input = [{ id: null }, { id: '' }, { id: { x: 1 } }, { id: 123 }, { id: 'keep' }]
353
+ let r = await A.procOrm('u1', 'tests', 'save', input)
354
+ let got = log[0].data.map((v) => v.id)
355
+ assert.ok(isId(got[0]) && isId(got[1]) && isId(got[2]))
356
+ assert.strict.equal(got[3], 123)
357
+ assert.strict.equal(got[4], 'keep')
358
+ assert.strict.deepEqual(r.map((v) => v.id), got)
359
+ })
360
+
361
+ it('P03 schema判定主鍵欄與型別', async function() {
362
+ //D1: 無id欄、id非pk、id型別非字串型者不補; settings標pk且為字串型者補
363
+ let log = []
364
+ let dsCases = [
365
+ [{ tests: { keys: ['name'] } }, false],
366
+ [{ tests: { keys: ['id', 'name'], settings: { id: { type: 'STRING' }, name: { type: 'STRING' } } } }, false],
367
+ [{ tests: { keys: ['id', 'name'], settings: { id: { pk: true, type: 'INTEGER' }, name: { type: 'STRING' } } } }, false],
368
+ [{ tests: { keys: ['id', 'name'], settings: { id: { pk: true, type: 'STRING' }, name: { type: 'STRING' } } } }, true],
369
+ [{ tests: { keys: ['id', 'name'], settings: { id: { pk: true }, name: {} } } }, true],
370
+ ]
371
+ for (let [ds, expectGen] of dsCases) {
372
+ log.length = 0
373
+ let A = WServOrm(ds, mkWOrm('A', log), 'url-a', 'db-a', optOff)
374
+ await A.procOrm('u1', 'tests', 'insert', [{ name: 'a' }])
375
+ assert.strict.equal(isId(log[0].data[0].id), expectGen, JSON.stringify(ds))
376
+ }
377
+ })
378
+
379
+ it('P04 單一空物件維持原樣交ORM', async function() {
380
+ //D1: 調整後無欄位之列不補, 保留ORM視空物件為無效輸入之既有行為
381
+ let log = []
382
+ let A = WServOrm({ tests: { keys: ['id', 'name'] } }, mkWOrm('A', log), 'url-a', 'db-a', optOff)
383
+ await A.procOrm('u1', 'tests', 'save', {})
384
+ assert.strict.deepEqual(log[0].data, {})
385
+ })
386
+
387
+ it('P05 同批多筆各自補值且呼叫端原物件不被加上主鍵', async function() {
388
+ //D1、D1a: 各自唯一; 補值寫在複本, 呼叫端原物件不含補出之主鍵
389
+ let log = []
390
+ let A = WServOrm(dsAll, mkWOrm('A', log), 'url-a', 'db-a', optOff)
391
+ let input = [{ name: 'a' }, { name: 'b' }, { name: 'c' }]
392
+ let r = await A.procOrm('u1', 'tests', 'insert', input)
393
+ let ids = r.map((v) => v.id)
394
+ assert.strict.equal(new Set(ids).size, 3)
395
+ assert.ok(input.every((v) => !('id' in v)))
396
+ })
397
+
398
+ it('P06 逐筆失敗之訊息以呼叫端視角列出且log含補出之主鍵', async function() {
399
+ //X2: 呼叫端未帶主鍵者仍列第N筆, log保留補出之主鍵
400
+ let log = []
401
+ let save = (d) => d.map((v, k) => (k === 1 ? { n: 1, ok: 0, err: 'E' } : { n: 1, ok: 1 }))
402
+ let A = WServOrm(dsAll, mkWOrm('A', log, { save }), 'url-a', 'db-a', optOff)
403
+ let err = null
404
+ let logs = await captureLog(async () => {
405
+ err = await getReject(A.procOrm('u1', 'tests', 'save', [{ id: 's1' }, { name: 'no-id' }]))
406
+ })
407
+ assert.strict.equal(err, 'failed to save data: data[1]')
408
+ assert.ok(JSON.stringify(logs).includes(log[0].data[1].id))
409
+ })
410
+
411
+ })
412
+
413
+
414
+ describe('hook串接與註冊', function() {
415
+
416
+ let dsTwo = { users: { keys: ['id', 'name'] }, items: { keys: ['id', 'name'] } }
417
+
418
+ it('K01 多個模組各註冊一個後處理皆正確', async function() {
419
+ //D3: 每個後處理皆收參數物件, output為前一個之回傳值
420
+ let log = []
421
+ let A = WServOrm(dsTwo, mkWOrm('A', log), 'url-a', 'db-a', optOff)
422
+ A.addFunPostProcessing(({ woName, output }) => (woName !== 'users' ? output : output.map((v) => ({ ...v, tagA: 1 }))))
423
+ A.addFunPostProcessing(({ woName, output }) => (woName !== 'items' ? output : output.map((v) => ({ ...v, tagB: 1 }))))
424
+ await wait(20)
425
+ let rU = await A.procOrm('u1', 'users', 'select', {})
426
+ let rI = await A.procOrm('u1', 'items', 'select', {})
427
+ assert.ok(rU.every((v) => v.tagA === 1 && !('tagB' in v)))
428
+ assert.ok(rI.every((v) => v.tagB === 1 && !('tagA' in v)))
429
+ })
430
+
431
+ it('K02 後處理回傳undefined往下傳且拋錯即中止', async function() {
432
+ //D3: 不加特例; 某個拋錯則procOrm reject且後續不執行
433
+ let calls = []
434
+ let A = WServOrm(dsAll, mkWOrm('A', []), 'url-a', 'db-a', optOff)
435
+ A.addFunPostProcessing(() => undefined)
436
+ A.addFunPostProcessing((p) => {
437
+ calls.push(p.output)
438
+ return p.output
439
+ })
440
+ await wait(20)
441
+ let r = await A.procOrm('u1', 'tests', 'select', {})
442
+ assert.strict.equal(r, undefined)
443
+ assert.strict.deepEqual(calls, [undefined])
444
+ let B = WServOrm(dsAll, mkWOrm('B', []), 'url-b', 'db-b', optOff)
445
+ let after = []
446
+ B.addFunPostProcessing(() => {
447
+ throw new Error('E-post')
448
+ })
449
+ B.addFunPostProcessing((p) => {
450
+ after.push(1)
451
+ return p.output
452
+ })
453
+ await wait(20)
454
+ let err = await getReject(B.procOrm('u1', 'tests', 'select', {}))
455
+ assert.ok(String(err).includes('E-post'))
456
+ assert.strict.deepEqual(after, [])
457
+ })
458
+
459
+ it('K03 多個前處理皆收完整參數物件', async function() {
460
+ //P3-3: 前一個只回{input}時, 下一個仍收得到woName
461
+ let log = []
462
+ let seen = []
463
+ let A = WServOrm(dsTwo, mkWOrm('A', log), 'url-a', 'db-a', optOff)
464
+ A.addFunPreProcessing((p) => ({ input: p.input.map((v) => ({ ...v, preA: 1 })) }))
465
+ A.addFunPreProcessing((p) => {
466
+ seen.push(p.woName)
467
+ return { ...p, input: p.input.map((v) => ({ ...v, preB: 1 })) }
468
+ })
469
+ await wait(20)
470
+ await A.procOrm('u1', 'users', 'save', [{ id: 'x' }])
471
+ assert.strict.deepEqual(seen, ['users'])
472
+ assert.strict.equal(log[0].data[0].preA, 1)
473
+ assert.strict.equal(log[0].data[0].preB, 1)
474
+ })
475
+
476
+ it('K04 前處理回傳非物件或缺input即reject且不呼叫ORM', async function() {
477
+ //P3-3: 防止input成為undefined而令select回全表
478
+ for (let bad of [undefined, [1], { woName: 'tests' }]) {
479
+ let log = []
480
+ let A = WServOrm(dsAll, mkWOrm('A', log), 'url-a', 'db-a', optOff)
481
+ A.addFunPreProcessing(() => bad)
482
+ await wait(20)
483
+ let err = await getReject(A.procOrm('u1', 'tests', 'select', {}))
484
+ assert.strict.equal(err, 'invalid return of pre-processing, need an object with input')
485
+ assert.strict.deepEqual(log, [])
486
+ }
487
+ })
488
+
489
+ it('K05 呼叫進行中註冊之後處理不套用於該次呼叫', async function() {
490
+ //P3-1: 逐一呼叫前先取快照
491
+ let release = null
492
+ let gate = new Promise((resolve) => {
493
+ release = resolve
494
+ })
495
+ let calls = []
496
+ let A = WServOrm(dsAll, mkWOrm('A', []), 'url-a', 'db-a', optOff)
497
+ A.addFunPostProcessing(async (p) => {
498
+ calls.push('h1')
499
+ await gate
500
+ return p.output
501
+ })
502
+ await wait(20)
503
+ let pm = A.procOrm('u1', 'tests', 'select', {})
504
+ await wait(5)
505
+ A.addFunPostProcessing((p) => {
506
+ calls.push('h2')
507
+ return p.output
508
+ })
509
+ await wait(20)
510
+ release()
511
+ await pm
512
+ assert.strict.deepEqual(calls, ['h1'])
513
+ await A.procOrm('u1', 'tests', 'select', {})
514
+ assert.strict.deepEqual(calls, ['h1', 'h1', 'h2'])
515
+ })
516
+
517
+ it('K06 註冊非函數即同步拋錯, 函數與async函數可註冊', async function() {
518
+ //D4: 訊息含hook名稱; 合法註冊之實例另建(A之check已因非函數註冊而被守門, 見K07)
519
+ let A = WServOrm(dsAll, mkWOrm('A', []), 'url-a', 'db-a', optOff)
520
+ for (let k of ['addFunCheck', 'addFunPreProcessing', 'addFunPostProcessing']) {
521
+ for (let bad of [undefined, null, 'x', {}]) {
522
+ assert.throws(() => A[k](bad), (e) => e instanceof Error && e.message.includes(k))
523
+ }
524
+ }
525
+ let B = WServOrm(dsAll, mkWOrm('B', []), 'url-b', 'db-b', optOff)
526
+ for (let k of ['addFunCheck', 'addFunPreProcessing', 'addFunPostProcessing']) {
527
+ B[k](function(p) {
528
+ return k === 'addFunCheck' ? undefined : (k === 'addFunPreProcessing' ? p : p.output)
529
+ })
530
+ B[k](async (p) => (k === 'addFunCheck' ? undefined : (k === 'addFunPreProcessing' ? p : p.output)))
531
+ }
532
+ await wait(20)
533
+ let r = await B.procOrm('u1', 'tests', 'select', {})
534
+ assert.strict.equal(r.length, 2)
535
+ })
536
+
537
+ it('K07 check註冊失敗被吞掉仍擋下呼叫, 前後處理則不影響', async function() {
538
+ //P3-4: check維持fail-closed; 前後處理註冊失敗時陣列不變
539
+ let A = WServOrm(dsAll, mkWOrm('A', []), 'url-a', 'db-a', optOff)
540
+ try {
541
+ A.addFunCheck(null)
542
+ }
543
+ catch (e) {}
544
+ await wait(20)
545
+ let err = await getReject(A.procOrm('u1', 'tests', 'select', {}))
546
+ assert.ok(String(err).includes('addFunCheck'))
547
+ //錯誤型別: 守門於procOrm內reject, 與procOrm其餘拒絕一致為字串
548
+ assert.strict.equal(typeof err, 'string')
549
+ let B = WServOrm(dsAll, mkWOrm('B', []), 'url-b', 'db-b', optOff)
550
+ for (let k of ['addFunPreProcessing', 'addFunPostProcessing']) {
551
+ try {
552
+ B[k](null)
553
+ }
554
+ catch (e) {}
555
+ }
556
+ await wait(20)
557
+ let r = await B.procOrm('u1', 'tests', 'select', {})
558
+ assert.strict.equal(r.length, 2)
559
+ })
560
+
561
+ it('K08 src/getItemsFail.mjs相容檔轉出w-data-collector之版本', async function() {
562
+ //X1: 使用端以深層路徑引用(如w-web-sso server/ormWrite.mjs)
563
+ let m = await import('../src/getItemsFail.mjs')
564
+ assert.strict.equal(m.default, getItemsFailWdc)
565
+ })
566
+
567
+ })