w-serv-orm 1.1.7 → 1.1.8

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