w-serv-orm 1.1.10 → 1.1.12

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,325 @@
1
+ import assert from 'assert'
2
+ import WServOrm from '../src/WServOrm.mjs'
3
+ import getMapOrm from '../src/getMapOrm.mjs'
4
+
5
+
6
+ //替身ORM: 記錄收到之數據, 回傳形狀依w-orm系列規格(save、del回逐筆陣列, insert與delAll回聚合物件); plan可覆寫各函數之回傳
7
+ let calls = []
8
+ let plan = {}
9
+ function WOrmStub() {
10
+ let ev = {}
11
+ for (let fn of ['select', 'insert', 'save', 'del', 'delAll']) {
12
+ ev[fn] = async (data) => {
13
+ calls.push({ fn, data })
14
+ if (plan[fn]) {
15
+ return plan[fn](data)
16
+ }
17
+ if (fn === 'select') {
18
+ return []
19
+ }
20
+ if (fn === 'save') {
21
+ return (Array.isArray(data) ? data : [data]).map(() => ({ n: 1, nInserted: 0, nModified: 1, ok: 1 }))
22
+ }
23
+ if (fn === 'del') {
24
+ return (Array.isArray(data) ? data : [data]).map(() => ({ n: 1, nDeleted: 1, ok: 1 }))
25
+ }
26
+ if (fn === 'delAll') {
27
+ return { n: 0, nDeleted: 0, ok: 1 }
28
+ }
29
+ return { n: 1, nInserted: 1, ok: 1 }
30
+ }
31
+ }
32
+ return ev
33
+ }
34
+
35
+ let dsStr = { tests: { keys: ['id', 'name'], settings: { id: { pk: true, type: 'STRING' }, name: { type: 'STRING' } } } }
36
+ let dsUnknown = { tests: { keys: ['id', 'name'] } }
37
+ let dsInt = { tests: { keys: ['id', 'name'], settings: { id: { pk: true, type: 'INTEGER' }, name: { type: 'STRING' } } } }
38
+ let dsAudit = { tests: { keys: ['id', 'name', 'userId', 'timeCreate', 'userIdUpdate', 'timeUpdate'] } }
39
+ let isGen = (v) => typeof v === 'string' && v.length > 30
40
+ let idOf = (d) => (Array.isArray(d) ? d[0].id : d.id)
41
+ let wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
42
+
43
+ async function getReject(pm) {
44
+ try {
45
+ await pm
46
+ }
47
+ catch (err) {
48
+ return err
49
+ }
50
+ throw new Error('預期reject但resolve')
51
+ }
52
+
53
+ async function quiet(fn) {
54
+ let logOrig = console.log
55
+ console.log = () => {}
56
+ try {
57
+ return await fn()
58
+ }
59
+ finally {
60
+ console.log = logOrig
61
+ }
62
+ }
63
+
64
+
65
+ describe('R-ID-02 字串型主鍵欄之數字主鍵以字串處置', function() {
66
+
67
+ beforeEach(function() {
68
+ calls = []
69
+ plan = {}
70
+ })
71
+
72
+ it('N01 寫入六入口之有限數字主鍵轉為字串並回報之, 呼叫端物件不變', async function() {
73
+ //R-ID-02 規則1(字串型或型別未知)、規則2(寫入)、規則4(複本)
74
+ for (let ds of [dsStr, dsUnknown]) {
75
+ let so = WServOrm(ds, WOrmStub, 'u', 'd')
76
+ for (let n of [123, 0, -5, 1.5]) {
77
+ calls = []
78
+ let a = { id: n, name: 'a' }
79
+ let rs = [
80
+ await so.procOrm('u1', 'tests', 'insert', [a]),
81
+ await so.procOrm('u1', 'tests', 'insert', a),
82
+ await so.procOrm('u1', 'tests', 'save', [a]),
83
+ await so.procOrm('u1', 'tests', 'save', a),
84
+ await so.procOrm('u1', 'tests', 'mix', { add: [a] }),
85
+ await so.procOrm('u1', 'tests', 'mix', { diff: [a] }),
86
+ ]
87
+ let s = String(n)
88
+ assert.deepStrictEqual(calls.map((c) => idOf(c.data)), [s, s, s, s, s, s], JSON.stringify(ds))
89
+ assert.deepStrictEqual(rs.map((r) => idOf(r)), [s, s, s, s, s, s])
90
+ assert.strictEqual(a.id, n)
91
+ }
92
+ }
93
+ })
94
+
95
+ it('N02 刪除三入口之數字主鍵轉為字串, 逐筆失敗之訊息照列', async function() {
96
+ //R-ID-02 規則2(刪除; mix回報之del列為轉型後之值); R-MSG-01 規則3
97
+ let so = WServOrm(dsStr, WOrmStub, 'u', 'd')
98
+ await so.procOrm('u1', 'tests', 'del', [{ id: 123 }, { id: 'x' }])
99
+ await so.procOrm('u1', 'tests', 'del', { id: 7 })
100
+ let r = await so.procOrm('u1', 'tests', 'mix', { del: [{ id: 8 }] })
101
+ assert.deepStrictEqual(calls.map((c) => c.data), [[{ id: '123' }, { id: 'x' }], { id: '7' }, [{ id: '8' }]])
102
+ assert.deepStrictEqual(r, [{ id: '8' }])
103
+ plan.del = (data) => data.map((v, k) => (k === 0 ? { n: 1, nDeleted: 0, ok: 0, err: 'E-del' } : { n: 1, nDeleted: 1, ok: 1 }))
104
+ let err = await quiet(() => getReject(so.procOrm('u1', 'tests', 'del', [{ id: 123 }, { id: 'y' }])))
105
+ assert.strictEqual(err, 'failed to delete data: id[123]')
106
+ })
107
+
108
+ it('N03 查詢條件之比較運算子(含$not與巢狀)轉型, 他欄與其餘運算子不變, 呼叫端條件不變', async function() {
109
+ //R-ID-02 規則2(查詢條件)、規則4
110
+ let so = WServOrm(dsStr, WOrmStub, 'u', 'd')
111
+ let finds = [
112
+ { id: 123 },
113
+ { id: { $eq: 1, $ne: 2, $gt: 3, $gte: 4, $lt: 5, $lte: 6 } },
114
+ { id: { $in: [7, 'a'], $nin: [8], $all: [9] } },
115
+ { id: { $not: { $eq: 10 } } },
116
+ { id: { $not: { $in: [11, 12] } } },
117
+ { $or: [{ id: 13 }, { name: 'x' }], $and: [{ id: { $in: [14] } }], $nor: [{ id: 15 }] },
118
+ { $and: [{ $or: [{ id: 16 }] }] },
119
+ { name: 17, id: { $regex: '^a', $exists: true } },
120
+ ]
121
+ let findsCopy = JSON.parse(JSON.stringify(finds))
122
+ for (let f of finds) {
123
+ await so.procOrm('u1', 'tests', 'select', f)
124
+ }
125
+ await so.procOrm('u1', 'tests', 'delAll', { id: 18 })
126
+ await so.procOrm('u1', 'tests', 'delAll', { id: { $in: [19] } })
127
+ assert.deepStrictEqual(calls.map((c) => c.data), [
128
+ { id: '123' },
129
+ { id: { $eq: '1', $ne: '2', $gt: '3', $gte: '4', $lt: '5', $lte: '6' } },
130
+ { id: { $in: ['7', 'a'], $nin: ['8'], $all: ['9'] } },
131
+ { id: { $not: { $eq: '10' } } },
132
+ { id: { $not: { $in: ['11', '12'] } } },
133
+ { $or: [{ id: '13' }, { name: 'x' }], $and: [{ id: { $in: ['14'] } }], $nor: [{ id: '15' }] },
134
+ { $and: [{ $or: [{ id: '16' }] }] },
135
+ { name: 17, id: { $regex: '^a', $exists: true } },
136
+ { id: '18' },
137
+ { id: { $in: ['19'] } },
138
+ ])
139
+ assert.deepStrictEqual(finds, findsCopy)
140
+ })
141
+
142
+ it('N04 主鍵欄為整數型者各入口皆不轉型亦不補值', async function() {
143
+ //R-ID-02 規則1; R-ID-01 規則1(整數型不補值)
144
+ let so = WServOrm(dsInt, WOrmStub, 'u', 'd')
145
+ let r = await so.procOrm('u1', 'tests', 'insert', [{ id: 123 }, { name: 'a' }])
146
+ await so.procOrm('u1', 'tests', 'del', [{ id: 5 }])
147
+ await so.procOrm('u1', 'tests', 'select', { id: 5 })
148
+ await so.procOrm('u1', 'tests', 'delAll', { id: { $in: [6] } })
149
+ await so.procOrm('u1', 'tests', 'mix', { diff: [{ id: 7, name: 'b' }] })
150
+ assert.deepStrictEqual(calls[0].data.map((v) => v.id), [123, undefined])
151
+ assert.deepStrictEqual(r.map((v) => v.id), [123, undefined])
152
+ assert.deepStrictEqual(calls.slice(1).map((c) => c.data), [[{ id: 5 }], { id: 5 }, { id: { $in: [6] } }, [{ id: 7, name: 'b' }]])
153
+ })
154
+
155
+ it('N05 NaN與正負Infinity視為未提供: 寫入補值且回報一致, 刪除與查詢原樣', async function() {
156
+ //R-ID-02 規則3; R-ID-01 規則1
157
+ let so = WServOrm(dsStr, WOrmStub, 'u', 'd')
158
+ let r = await so.procOrm('u1', 'tests', 'insert', [{ id: NaN }, { id: Infinity }, { id: -Infinity }])
159
+ let got = calls[0].data.map((v) => v.id)
160
+ assert.ok(got.every(isGen), JSON.stringify(got))
161
+ assert.deepStrictEqual(r.map((v) => v.id), got)
162
+ await so.procOrm('u1', 'tests', 'del', [{ id: Infinity }])
163
+ await so.procOrm('u1', 'tests', 'select', { id: NaN })
164
+ assert.deepStrictEqual(calls[1].data, [{ id: Infinity }])
165
+ assert.deepStrictEqual(calls[2].data, { id: NaN })
166
+ })
167
+
168
+ it('N06 同批之數字與其字串形式為同一字串主鍵', async function() {
169
+ //R-ID-02 規則2: 後端依同批重複處置, 本套件回報兩筆同鍵(insert回傳之id含被略過者)
170
+ let so = WServOrm(dsStr, WOrmStub, 'u', 'd')
171
+ let r = await so.procOrm('u1', 'tests', 'insert', [{ id: 1 }, { id: '1' }])
172
+ assert.deepStrictEqual(calls[0].data.map((v) => v.id), ['1', '1'])
173
+ assert.deepStrictEqual(r, [{ id: '1' }, { id: '1' }])
174
+ })
175
+
176
+ it('N07 ds無該表者select與del不拋錯且原樣交ORM', async function() {
177
+ //R-ID-02 規則1: 直接呼叫getMapOrm而ds與woItems不一致者不轉型
178
+ let procOrm = getMapOrm({ others: dsStr.tests }, { tests: WOrmStub() })
179
+ await procOrm('u1', 'tests', 'select', { id: 123 })
180
+ await procOrm('u1', 'tests', 'del', [{ id: 123 }])
181
+ await procOrm('u1', 'tests', 'delAll', { id: 123 })
182
+ assert.deepStrictEqual(calls.map((c) => c.data), [{ id: 123 }, [{ id: 123 }], { id: 123 }])
183
+ })
184
+
185
+ it('N08 型別字串去參數後比對: STRING(36)視為字串型而補值與轉型', async function() {
186
+ //R-ID-01 規則1(取「(」之前之字並轉大寫); R-ID-02 規則1
187
+ let cases = [
188
+ ['STRING(36)', true],
189
+ ['string(36)', true],
190
+ ['TEXT', true],
191
+ ['INTEGER', false],
192
+ ['INTEGER(11)', false],
193
+ ]
194
+ for (let [type, expect] of cases) {
195
+ calls = []
196
+ let ds = { tests: { keys: ['id', 'name'], settings: { id: { pk: true, type }, name: { type: 'STRING' } } } }
197
+ let so = WServOrm(ds, WOrmStub, 'u', 'd')
198
+ await so.procOrm('u1', 'tests', 'insert', [{ name: 'a' }, { id: 5 }])
199
+ let got = calls[0].data.map((v) => v.id)
200
+ assert.strictEqual(isGen(got[0]), expect, type)
201
+ assert.strictEqual(got[1], expect ? '5' : 5, type)
202
+ }
203
+ })
204
+
205
+ it('N09 後處理之input為前處理後之input(主鍵不轉型), output為轉型後之回報', async function() {
206
+ //R-ID-02 規則5; R-HOOK-01 規則5
207
+ let so = WServOrm(dsStr, WOrmStub, 'u', 'd')
208
+ let seen = []
209
+ so.addFunPreProcessing(({ input }) => ({ input: [...input, { id: 2, name: 'pre' }] }))
210
+ so.addFunPostProcessing(({ input, output }) => {
211
+ seen.push({ input, output })
212
+ return output
213
+ })
214
+ await wait(20)
215
+ let r = await so.procOrm('u1', 'tests', 'insert', [{ id: 1, name: 'a' }])
216
+ assert.deepStrictEqual(seen[0].input, [{ id: 1, name: 'a' }, { id: 2, name: 'pre' }])
217
+ assert.deepStrictEqual(seen[0].output, [{ id: '1' }, { id: '2' }])
218
+ assert.deepStrictEqual(r, [{ id: '1' }, { id: '2' }])
219
+ })
220
+
221
+ })
222
+
223
+
224
+ describe('R-MIX-01 mix之跨段同鍵整批拒絕', function() {
225
+
226
+ beforeEach(function() {
227
+ calls = []
228
+ plan = {}
229
+ })
230
+
231
+ it('M01 add與del、del與diff正規化後同鍵者整批reject且ORM未被呼叫, add與diff同鍵照常', async function() {
232
+ //R-MIX-01 規則1-4
233
+ let so = WServOrm(dsStr, WOrmStub, 'u', 'd')
234
+ let mix = (input) => quiet(() => getReject(so.procOrm('u1', 'tests', 'mix', input)))
235
+ assert.strictEqual(await mix({ add: [{ id: 123 }], del: [{ id: '123' }] }), 'invalid input of mix, same id in add and del: add[0], del[0]')
236
+ assert.strictEqual(await mix({ add: [{ id: 'a' }, { id: 'x' }], del: [{ id: 'b' }, { id: 'a' }] }), 'invalid input of mix, same id in add and del: add[0], del[1]')
237
+ assert.strictEqual(await mix({ del: [{ id: 'b' }], diff: [{ id: 'c' }, { id: 'b' }] }), 'invalid input of mix, same id in del and diff: del[0], diff[1]')
238
+ assert.strictEqual(await mix({ add: [{ id: 'a' }], del: [{ id: 'a' }, { id: 'b' }], diff: [{ id: 'b' }] }), 'invalid input of mix, same id in add and del: add[0], del[0]; invalid input of mix, same id in del and diff: del[1], diff[0]')
239
+ assert.deepStrictEqual(calls, [])
240
+
241
+ //未提供主鍵者不參與比對; add與diff同鍵照常(insert略過、save更新, 不遺失)
242
+ await so.procOrm('u1', 'tests', 'mix', { add: [{ name: 'x' }], del: [{ name: 'y' }], diff: [{ name: 'z' }] })
243
+ await so.procOrm('u1', 'tests', 'mix', { add: [{ id: 'c' }], diff: [{ id: 'c' }] })
244
+ assert.deepStrictEqual(calls.map((v) => v.fn), ['insert', 'del', 'save', 'insert', 'save'])
245
+
246
+ //整數主鍵欄之5與'5'亦視為同鍵
247
+ calls = []
248
+ let si = WServOrm(dsInt, WOrmStub, 'u', 'd')
249
+ let err = await quiet(() => getReject(si.procOrm('u1', 'tests', 'mix', { add: [{ id: 5 }], del: [{ id: '5' }] })))
250
+ assert.strictEqual(err, 'invalid input of mix, same id in add and del: add[0], del[0]')
251
+ assert.deepStrictEqual(calls, [])
252
+ })
253
+
254
+ })
255
+
256
+
257
+ describe('R-INPUT-01 寫入與delAll之輸入形狀', function() {
258
+
259
+ beforeEach(function() {
260
+ calls = []
261
+ plan = {}
262
+ })
263
+
264
+ it('E01 insert、save、mix之新增與修改之元素非一般物件即整批reject且ORM未被呼叫', async function() {
265
+ //R-INPUT-01 規則1、2; 有稽核欄位者原為failed to adjust data, 無者原寫入垃圾紀錄
266
+ for (let ds of [dsUnknown, dsAudit]) {
267
+ let so = WServOrm(ds, WOrmStub, 'u', 'd')
268
+ let rj = (mode, input) => quiet(() => getReject(so.procOrm('u1', 'tests', mode, input)))
269
+ for (let bad of ['abc', ['a', 'b'], 5, null, true, new Date('2026-01-01T00:00:00Z')]) {
270
+ assert.strictEqual(await rj('insert', [bad]), 'failed to insert data, not an object: data[0]', String(bad))
271
+ assert.strictEqual(await rj('save', [{ id: 'a' }, bad]), 'failed to save data, not an object: data[1]', String(bad))
272
+ assert.strictEqual(await rj('mix', { add: [bad] }), 'failed to insert data, not an object: data[0]', String(bad))
273
+ assert.strictEqual(await rj('mix', { diff: [{ id: 'b' }, bad] }), 'failed to update data, not an object: data[1]', String(bad))
274
+ }
275
+ assert.strictEqual(await rj('insert', 'abc'), 'failed to insert data, not an object: data[0]')
276
+ assert.strictEqual(await rj('save', 5), 'failed to save data, not an object: data[0]')
277
+ assert.strictEqual(await rj('insert', null), 'failed to insert data, not an object: data[0]')
278
+ assert.strictEqual(await rj('insert', undefined), 'failed to insert data, not an object: data[0]')
279
+ assert.strictEqual(await rj('insert', [{ id: 'a' }, 'x', 5]), 'failed to insert data, not an object: data[1], data[2]')
280
+ assert.strictEqual(await rj('mix', { add: ['x'], diff: [5] }), 'failed to insert data, not an object: data[0]; failed to update data, not an object: data[0]')
281
+ assert.deepStrictEqual(calls, [])
282
+ }
283
+ })
284
+
285
+ it('E02 mix之add、del、diff給予而非陣列即reject且ORM未被呼叫', async function() {
286
+ //R-INPUT-01 規則2
287
+ let so = WServOrm(dsStr, WOrmStub, 'u', 'd')
288
+ let msg = 'invalid input of mix, need the result object of ltdtDiffByKey'
289
+ for (let input of [{ add: { id: 'a' } }, { del: { id: 'a' } }, { diff: { id: 'a' } }, { add: null }, { del: 'a' }, { add: [], diff: 5 }]) {
290
+ assert.strictEqual(await quiet(() => getReject(so.procOrm('u1', 'tests', 'mix', input))), msg, JSON.stringify(input))
291
+ }
292
+ assert.deepStrictEqual(calls, [])
293
+ let r = await so.procOrm('u1', 'tests', 'mix', { add: [], del: [], diff: [] })
294
+ assert.deepStrictEqual(r, [])
295
+ })
296
+
297
+ it('E03 delAll之find非物件即reject且ORM未被呼叫, undefined與物件照常', async function() {
298
+ //R-INPUT-01 規則3
299
+ let so = WServOrm(dsStr, WOrmStub, 'u', 'd')
300
+ for (let find of [[], ['a'], 'a', null, 5, true]) {
301
+ assert.strictEqual(await quiet(() => getReject(so.procOrm('u1', 'tests', 'delAll', find))), 'invalid find of delAll, need an object', JSON.stringify(find))
302
+ }
303
+ assert.deepStrictEqual(calls, [])
304
+ await so.procOrm('u1', 'tests', 'delAll', undefined)
305
+ await so.procOrm('u1', 'tests', 'delAll', {})
306
+ await so.procOrm('u1', 'tests', 'delAll', { name: 'x' })
307
+ assert.deepStrictEqual(calls.map((c) => c.data), [undefined, {}, { name: 'x' }])
308
+ })
309
+
310
+ it('E04 多筆批次之空物件列補值且回報與ORM收到者一致, 單筆空物件維持原樣', async function() {
311
+ //R-ID-01 規則3(裁示D5)
312
+ let so = WServOrm(dsUnknown, WOrmStub, 'u', 'd')
313
+ let r1 = await so.procOrm('u1', 'tests', 'insert', [{}, { name: 'x' }])
314
+ let r2 = await so.procOrm('u1', 'tests', 'save', [{}, {}])
315
+ let r3 = await so.procOrm('u1', 'tests', 'insert', [{}])
316
+ let r4 = await so.procOrm('u1', 'tests', 'insert', {})
317
+ assert.ok(calls[0].data.every((v) => isGen(v.id)) && calls[1].data.every((v) => isGen(v.id)))
318
+ assert.deepStrictEqual(r1.map((v) => v.id), calls[0].data.map((v) => v.id))
319
+ assert.deepStrictEqual(r2.map((v) => v.id), calls[1].data.map((v) => v.id))
320
+ assert.deepStrictEqual(calls[2].data, [{}])
321
+ assert.deepStrictEqual(calls[3].data, {})
322
+ assert.deepStrictEqual([r3, r4], [[{ id: undefined }], { id: undefined }])
323
+ })
324
+
325
+ })