w-serv-orm 1.1.5 → 1.1.7
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/dist/w-serv-orm.umd.js +2 -2
- package/dist/w-serv-orm.umd.js.map +1 -1
- package/docs/WServOrm.html +6 -3
- package/docs/WServOrm.mjs.html +5 -1
- package/docs/index.html +1 -1
- package/package.json +4 -4
- package/src/WServOrm.mjs +4 -0
- package/src/genTestData.mjs +11 -3
- package/src/getItemsFail.mjs +37 -0
- package/src/getMapOrm.mjs +72 -2
- package/test/api-procOrm.test.mjs +68 -0
- package/test/unit-mapOrm.test.mjs +268 -0
package/src/WServOrm.mjs
CHANGED
|
@@ -9,6 +9,10 @@ import getProc from './getProc.mjs'
|
|
|
9
9
|
/**
|
|
10
10
|
* 產生伺服器ORM的支援物件
|
|
11
11
|
*
|
|
12
|
+
* procOrm之save、del與mix,於ORM回傳之逐筆結果有任一筆ok為0(單筆失敗)時reject,訊息列出失敗筆之主鍵(無有效主鍵者列其於輸入內之位置),錯誤原文另由console.log輸出。
|
|
13
|
+
* 單筆失敗不中斷整批,故reject時其餘筆數已寫入;save與del皆以主鍵為準,數據帶有主鍵時整批重送不會產生重複資料,未帶主鍵者重送則會由ORM另產生主鍵而重複寫入。
|
|
14
|
+
* mix之各步驟遇單筆失敗不中斷後續步驟,待全部步驟完成後一併reject;遇整批性錯誤則立即reject,後續步驟不執行。
|
|
15
|
+
*
|
|
12
16
|
* @class
|
|
13
17
|
* @param {Object} ds 輸入資料表設定物件
|
|
14
18
|
* @param {Object} WOrm 輸入資料庫ORM物件
|
package/src/genTestData.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import get from 'lodash-es/get.js'
|
|
1
|
+
import get from 'lodash-es/get.js'
|
|
2
|
+
import size from 'lodash-es/size.js'
|
|
2
3
|
import isbol from 'wsemi/src/isbol.mjs'
|
|
3
|
-
import isfun from 'wsemi/src/isfun.mjs'
|
|
4
|
+
import isfun from 'wsemi/src/isfun.mjs'
|
|
4
5
|
import pmSeries from 'wsemi/src/pmSeries.mjs'
|
|
6
|
+
import getItemsFail from './getItemsFail.mjs'
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
async function genTestData(ds, woItems, opt = {}) {
|
|
@@ -44,7 +46,13 @@ async function genTestData(ds, woItems, opt = {}) {
|
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
//save
|
|
47
|
-
await woItems[k].save(rs)
|
|
49
|
+
let rss = await woItems[k].save(rs)
|
|
50
|
+
|
|
51
|
+
//check, 單筆失敗時整批仍resolve並以該筆ok為0回報, 須逐筆檢查
|
|
52
|
+
let fails = getItemsFail(rss, rs)
|
|
53
|
+
if (size(fails) > 0) {
|
|
54
|
+
throw new Error(`資料表[${k}]寫入測試資料失敗: ${JSON.stringify(fails)}`)
|
|
55
|
+
}
|
|
48
56
|
|
|
49
57
|
if (bLog) {
|
|
50
58
|
console.log(`${k}.funTest and save`, rs)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import get from 'lodash-es/get.js'
|
|
2
|
+
import each from 'lodash-es/each.js'
|
|
3
|
+
import isarr from 'wsemi/src/isarr.mjs'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
function getItemsFail(rs, data) {
|
|
7
|
+
//由ORM逐筆函數(save、del)之回傳結果取出失敗者, 回傳[{k,id,err}], k為該筆於輸入內之位置
|
|
8
|
+
//w-orm系列規格: 逐筆函數回傳與輸入等長保序之陣列, 單筆失敗時整批仍resolve, 該筆以ok為0並附err回報, 須由呼叫端逐筆檢查
|
|
9
|
+
//逐筆結果不含主鍵, 故主鍵取自輸入之同位置數據
|
|
10
|
+
|
|
11
|
+
//check, 未依規格之ORM可能回傳非陣列, 無從逐筆檢查
|
|
12
|
+
if (!isarr(rs)) {
|
|
13
|
+
return []
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//check, 輸入為單一物件時ORM回傳長度為1之陣列
|
|
17
|
+
if (!isarr(data)) {
|
|
18
|
+
data = [data]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//fails, 僅ok為0者視為失敗
|
|
22
|
+
let fails = []
|
|
23
|
+
each(rs, (v, k) => {
|
|
24
|
+
if (get(v, 'ok') === 0) {
|
|
25
|
+
fails.push({
|
|
26
|
+
k,
|
|
27
|
+
id: get(data, [k, 'id']),
|
|
28
|
+
err: get(v, 'err', ''),
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
return fails
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
export default getItemsFail
|
package/src/getMapOrm.mjs
CHANGED
|
@@ -2,17 +2,46 @@ import get from 'lodash-es/get.js'
|
|
|
2
2
|
import each from 'lodash-es/each.js'
|
|
3
3
|
import map from 'lodash-es/map.js'
|
|
4
4
|
import size from 'lodash-es/size.js'
|
|
5
|
+
import take from 'lodash-es/take.js'
|
|
6
|
+
import join from 'lodash-es/join.js'
|
|
5
7
|
import nowms2str from 'wsemi/src/nowms2str.mjs'
|
|
6
8
|
import isarr from 'wsemi/src/isarr.mjs'
|
|
7
9
|
import iseobj from 'wsemi/src/iseobj.mjs'
|
|
8
10
|
import isestr from 'wsemi/src/isestr.mjs'
|
|
11
|
+
import isnum from 'wsemi/src/isnum.mjs'
|
|
9
12
|
import clearXSS from 'wsemi/src/clearXSS.mjs'
|
|
13
|
+
import getItemsFail from './getItemsFail.mjs'
|
|
10
14
|
|
|
11
15
|
|
|
12
16
|
let _ds = null
|
|
13
17
|
let _woItems = null
|
|
14
18
|
|
|
15
19
|
|
|
20
|
+
function getMsgFail(msg, fails) {
|
|
21
|
+
//前端訊息僅列出失敗筆, 錯誤原文另由console.log輸出, 與既有整批錯誤之處理一致
|
|
22
|
+
//有效主鍵(非空字串或數字)列為id[...], 否則列其於輸入內之位置, 失敗筆數過多時僅列前nShow筆並附總筆數
|
|
23
|
+
|
|
24
|
+
//nShow
|
|
25
|
+
let nShow = 10
|
|
26
|
+
|
|
27
|
+
//ss
|
|
28
|
+
let ss = map(take(fails, nShow), (v) => {
|
|
29
|
+
if (isestr(v.id) || isnum(v.id)) {
|
|
30
|
+
return `id[${v.id}]`
|
|
31
|
+
}
|
|
32
|
+
return `第${v.k + 1}筆`
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
//c
|
|
36
|
+
let c = `${msg}: ${join(ss, ', ')}`
|
|
37
|
+
if (size(fails) > nShow) {
|
|
38
|
+
c += `等共${size(fails)}筆`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return c
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
16
45
|
function adjustData(woName, userId, data, mode = 'save') { //mode: 'insert', 'save', 'insert+save'
|
|
17
46
|
|
|
18
47
|
//check
|
|
@@ -172,6 +201,16 @@ async function mapOrm(userId, woName, mode, input) {
|
|
|
172
201
|
return Promise.reject(err)
|
|
173
202
|
}
|
|
174
203
|
|
|
204
|
+
//check, save單筆失敗時整批仍resolve並以該筆ok為0回報, 須逐筆檢查
|
|
205
|
+
//insert之錯誤皆屬整批性而reject, 其回傳為聚合物件而非逐筆陣列, 故不須檢查
|
|
206
|
+
if (mode === 'save') {
|
|
207
|
+
let fails = getItemsFail(r, input)
|
|
208
|
+
if (size(fails) > 0) {
|
|
209
|
+
console.log(`${woName}.save fail`, fails)
|
|
210
|
+
return Promise.reject(getMsgFail('儲存資料失敗', fails))
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
175
214
|
//僅回傳id避免大量數據時造成負擔
|
|
176
215
|
if (isarr(input)) {
|
|
177
216
|
r = map(input, (v) => {
|
|
@@ -193,6 +232,15 @@ async function mapOrm(userId, woName, mode, input) {
|
|
|
193
232
|
err = '刪除資料失敗'
|
|
194
233
|
})
|
|
195
234
|
|
|
235
|
+
//check, 單筆失敗時整批仍resolve並以該筆ok為0回報, 須逐筆檢查
|
|
236
|
+
if (!isestr(err)) {
|
|
237
|
+
let fails = getItemsFail(r, input)
|
|
238
|
+
if (size(fails) > 0) {
|
|
239
|
+
console.log(`${woName}.del fail`, fails)
|
|
240
|
+
err = getMsgFail('刪除資料失敗', fails)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
196
244
|
}
|
|
197
245
|
else if (mode === 'delAll') {
|
|
198
246
|
|
|
@@ -215,6 +263,9 @@ async function mapOrm(userId, woName, mode, input) {
|
|
|
215
263
|
let rt
|
|
216
264
|
r = [] //default to array
|
|
217
265
|
|
|
266
|
+
//msgsFail, 逐筆失敗僅影響該筆, 不中斷後續步驟, 待全部步驟完成後一併reject; 整批性錯誤則仍立即reject
|
|
267
|
+
let msgsFail = []
|
|
268
|
+
|
|
218
269
|
let add = get(input, 'add', [])
|
|
219
270
|
if (size(add) > 0) {
|
|
220
271
|
|
|
@@ -251,7 +302,7 @@ async function mapOrm(userId, woName, mode, input) {
|
|
|
251
302
|
if (size(del) > 0) {
|
|
252
303
|
|
|
253
304
|
//del
|
|
254
|
-
await wo.del(del)
|
|
305
|
+
let rs = await wo.del(del)
|
|
255
306
|
.catch((msg) => {
|
|
256
307
|
console.log(`${woName}.save catch`, msg)
|
|
257
308
|
err = '刪除資料失敗'
|
|
@@ -262,6 +313,13 @@ async function mapOrm(userId, woName, mode, input) {
|
|
|
262
313
|
return Promise.reject(err)
|
|
263
314
|
}
|
|
264
315
|
|
|
316
|
+
//check, 單筆失敗時整批仍resolve並以該筆ok為0回報, 須逐筆檢查
|
|
317
|
+
let fails = getItemsFail(rs, del)
|
|
318
|
+
if (size(fails) > 0) {
|
|
319
|
+
console.log(`${woName}.del fail`, fails)
|
|
320
|
+
msgsFail.push(getMsgFail('刪除資料失敗', fails))
|
|
321
|
+
}
|
|
322
|
+
|
|
265
323
|
//r
|
|
266
324
|
rt = map(del, (v) => {
|
|
267
325
|
return { id: v.id } //僅回傳id避免大量數據時造成負擔
|
|
@@ -283,7 +341,7 @@ async function mapOrm(userId, woName, mode, input) {
|
|
|
283
341
|
}
|
|
284
342
|
|
|
285
343
|
//save
|
|
286
|
-
await wo.save(diff)
|
|
344
|
+
let rs = await wo.save(diff)
|
|
287
345
|
.catch((msg) => {
|
|
288
346
|
console.log(`${woName}.save catch`, msg)
|
|
289
347
|
err = '變更資料失敗'
|
|
@@ -294,6 +352,13 @@ async function mapOrm(userId, woName, mode, input) {
|
|
|
294
352
|
return Promise.reject(err)
|
|
295
353
|
}
|
|
296
354
|
|
|
355
|
+
//check, 單筆失敗時整批仍resolve並以該筆ok為0回報, 須逐筆檢查
|
|
356
|
+
let fails = getItemsFail(rs, diff)
|
|
357
|
+
if (size(fails) > 0) {
|
|
358
|
+
console.log(`${woName}.save fail`, fails)
|
|
359
|
+
msgsFail.push(getMsgFail('變更資料失敗', fails))
|
|
360
|
+
}
|
|
361
|
+
|
|
297
362
|
//r
|
|
298
363
|
rt = map(diff, (v) => {
|
|
299
364
|
return { id: v.id } //僅回傳id避免大量數據時造成負擔
|
|
@@ -301,6 +366,11 @@ async function mapOrm(userId, woName, mode, input) {
|
|
|
301
366
|
r = [...r, ...rt]
|
|
302
367
|
}
|
|
303
368
|
|
|
369
|
+
//check, 各步驟之逐筆失敗於全部步驟完成後一併reject
|
|
370
|
+
if (size(msgsFail) > 0) {
|
|
371
|
+
return Promise.reject(join(msgsFail, '; '))
|
|
372
|
+
}
|
|
373
|
+
|
|
304
374
|
//console.log('mix r', r)
|
|
305
375
|
}
|
|
306
376
|
else {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import assert from 'assert'
|
|
3
|
+
import WOrm from 'w-orm-lowdb/src/WOrmLowdb.mjs'
|
|
4
|
+
import ds from '../schema/index.mjs'
|
|
5
|
+
import WServOrm from '../src/WServOrm.mjs'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
//以真ORM(w-orm-lowdb)經procOrm驗證逐筆失敗之回報; del帶無效主鍵為各w-orm後端共通之逐筆失敗(規格T6), 不須資料庫例外即可觸發
|
|
9
|
+
let fd = './test/_tmp/api-procOrm'
|
|
10
|
+
|
|
11
|
+
async function getReject(pm) {
|
|
12
|
+
try {
|
|
13
|
+
await pm
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
return err
|
|
17
|
+
}
|
|
18
|
+
throw new Error('預期reject但resolve')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
describe('procOrm接w-orm-lowdb', function() {
|
|
23
|
+
|
|
24
|
+
let so = null
|
|
25
|
+
let proc = (mode, input) => so.procOrm('u1', 'tests', mode, input)
|
|
26
|
+
|
|
27
|
+
before(function() {
|
|
28
|
+
fs.rmSync(fd, { recursive: true, force: true })
|
|
29
|
+
fs.mkdirSync(fd, { recursive: true })
|
|
30
|
+
so = WServOrm(ds, WOrm, `${fd}/db.json`, 'worm', { useCheckUser: false, useExcludeWhenNotAdmin: false })
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
after(async function() {
|
|
34
|
+
await so.closeAll()
|
|
35
|
+
fs.rmSync(fd, { recursive: true, force: true })
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('A01 save成功', async function() {
|
|
39
|
+
//R04: 全部成功時回傳id, 數據已寫入
|
|
40
|
+
let r = await proc('save', [{ id: 'a1', isActive: 'y' }])
|
|
41
|
+
assert.strict.deepEqual(r, [{ id: 'a1' }])
|
|
42
|
+
let rs = await proc('select', { id: 'a1' })
|
|
43
|
+
assert.strict.equal(rs.length, 1)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('A02 del含無效主鍵', async function() {
|
|
47
|
+
//R02: 逐筆ok為0即reject並列出失敗主鍵; R10: 單筆失敗不中斷整批, 有效筆已刪除
|
|
48
|
+
let err = await getReject(proc('del', [{ id: 'a1' }, { id: 123 }]))
|
|
49
|
+
assert.strict.equal(err, '刪除資料失敗: id[123]')
|
|
50
|
+
let rs = await proc('select', { id: 'a1' })
|
|
51
|
+
assert.strict.equal(rs.length, 0)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('A03 mix之del含無效主鍵', async function() {
|
|
55
|
+
//R03: del之逐筆失敗不中斷diff, diff已寫入, 最後reject列出del之失敗
|
|
56
|
+
let err = await getReject(proc('mix', { del: [{ id: 456 }], diff: [{ id: 'a2', isActive: 'y' }] }))
|
|
57
|
+
assert.strict.equal(err, '刪除資料失敗: id[456]')
|
|
58
|
+
let rs = await proc('select', { id: 'a2' })
|
|
59
|
+
assert.strict.equal(rs.length, 1)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('A04 del主鍵未命中', async function() {
|
|
63
|
+
//R04: 主鍵未命中屬正常結果, 回傳ORM原始逐筆陣列
|
|
64
|
+
let r = await proc('del', [{ id: 'a-none' }])
|
|
65
|
+
assert.strict.deepEqual(r, [{ n: 0, nDeleted: 0, ok: 1 }])
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
})
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import WServOrm from '../src/WServOrm.mjs'
|
|
3
|
+
import genTestData from '../src/genTestData.mjs'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
//替身ORM, 回傳形狀依w-orm系列規格(資料庫函數回傳定義計算.md T2、T4):
|
|
7
|
+
//save與del回傳與輸入等長保序之逐筆陣列, 單筆失敗時整批仍resolve, 該筆以ok為0並附err回報; 整批性錯誤則reject
|
|
8
|
+
let plan = {}
|
|
9
|
+
let calls = []
|
|
10
|
+
function WOrmStub() {
|
|
11
|
+
let ev = {}
|
|
12
|
+
for (let fn of ['select', 'insert', 'save', 'del', 'delAll']) {
|
|
13
|
+
ev[fn] = async (data) => {
|
|
14
|
+
calls.push({ fn, data })
|
|
15
|
+
return plan[fn](data)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return ev
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let ds = {
|
|
22
|
+
tests: {
|
|
23
|
+
keys: ['id', 'name', 'timeCreate', 'timeUpdate'],
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let okSave = { n: 1, nInserted: 0, nModified: 1, ok: 1 }
|
|
28
|
+
let okDel = { n: 1, nDeleted: 1, ok: 1 }
|
|
29
|
+
|
|
30
|
+
async function getReject(pm) {
|
|
31
|
+
try {
|
|
32
|
+
await pm
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
return err
|
|
36
|
+
}
|
|
37
|
+
throw new Error('預期reject但resolve')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
describe('mapOrm逐筆失敗', function() {
|
|
42
|
+
|
|
43
|
+
let so = WServOrm(ds, WOrmStub, 'stub-url', 'stub-db', { useCheckUser: false, useExcludeWhenNotAdmin: false })
|
|
44
|
+
let proc = (mode, input) => so.procOrm('u1', 'tests', mode, input)
|
|
45
|
+
|
|
46
|
+
beforeEach(function() {
|
|
47
|
+
plan = {}
|
|
48
|
+
calls = []
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('U01 save陣列全部成功', async function() {
|
|
52
|
+
//R04: 全部成功時save回傳不變, 僅回傳id
|
|
53
|
+
plan.save = (data) => data.map(() => okSave)
|
|
54
|
+
let r = await proc('save', [{ id: 's1', name: 'a' }, { id: 's2', name: 'b' }])
|
|
55
|
+
assert.strict.deepEqual(r, [{ id: 's1' }, { id: 's2' }])
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('U02 save物件全部成功', async function() {
|
|
59
|
+
//R04: 輸入為單一物件時回傳單一{id}
|
|
60
|
+
plan.save = () => [okSave]
|
|
61
|
+
let r = await proc('save', { id: 's1', name: 'a' })
|
|
62
|
+
assert.strict.deepEqual(r, { id: 's1' })
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('U03 save陣列中一筆ok為0', async function() {
|
|
66
|
+
//R01: 逐筆ok為0即reject, 訊息帶失敗筆之主鍵, 不含成功筆
|
|
67
|
+
plan.save = () => [okSave, { n: 1, nInserted: 0, nModified: 0, ok: 0, err: 'E-save' }]
|
|
68
|
+
let err = await getReject(proc('save', [{ id: 's1' }, { id: 's2' }]))
|
|
69
|
+
assert.strict.equal(err, '儲存資料失敗: id[s2]')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('U04 save物件ok為0', async function() {
|
|
73
|
+
//R01: 輸入為單一物件時ORM回傳長度為1之陣列, 仍須檢出
|
|
74
|
+
plan.save = () => [{ n: 1, nInserted: 0, nModified: 0, ok: 0, err: 'E-save' }]
|
|
75
|
+
let err = await getReject(proc('save', { id: 's1' }))
|
|
76
|
+
assert.strict.equal(err, '儲存資料失敗: id[s1]')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('U05 save未帶主鍵之筆ok為0且無err', async function() {
|
|
80
|
+
//R01: 無有效主鍵者以輸入位置標示; R07: ok為0而無err(舊版ORM)仍判失敗
|
|
81
|
+
plan.save = () => [okSave, { n: 1, ok: 0 }]
|
|
82
|
+
let err = await getReject(proc('save', [{ id: 's1' }, { name: 'no-id' }]))
|
|
83
|
+
assert.strict.equal(err, '儲存資料失敗: 第2筆')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('U06 save整批reject', async function() {
|
|
87
|
+
//R05: 整批性錯誤之訊息不變
|
|
88
|
+
plan.save = () => Promise.reject(new Error('E-batch'))
|
|
89
|
+
let err = await getReject(proc('save', [{ id: 's1' }]))
|
|
90
|
+
assert.strict.equal(err, '儲存資料失敗')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('U07 save之ORM回傳非陣列', async function() {
|
|
94
|
+
//R07: 未依規格之ORM回傳非陣列時不檢查, 照舊回傳id
|
|
95
|
+
plan.save = () => ({ n: 1, ok: 1 })
|
|
96
|
+
let r = await proc('save', [{ id: 's1' }])
|
|
97
|
+
assert.strict.deepEqual(r, [{ id: 's1' }])
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('U08 save多筆ok為0', async function() {
|
|
101
|
+
//R08: 失敗筆數超過10筆時僅列前10筆並附總筆數
|
|
102
|
+
let input = []
|
|
103
|
+
for (let i = 1; i <= 12; i++) {
|
|
104
|
+
input.push({ id: `f${i}` })
|
|
105
|
+
}
|
|
106
|
+
plan.save = (data) => data.map(() => ({ n: 1, nInserted: 0, nModified: 0, ok: 0, err: 'E-full' }))
|
|
107
|
+
let err = await getReject(proc('save', input))
|
|
108
|
+
let ids = input.slice(0, 10).map((v) => `id[${v.id}]`).join(', ')
|
|
109
|
+
assert.strict.equal(err, `儲存資料失敗: ${ids}等共12筆`)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('U09 del全部成功含主鍵未命中', async function() {
|
|
113
|
+
//R04: del回傳ORM原始逐筆陣列不變; 主鍵未命中(n為0、ok為1)屬正常結果而非失敗
|
|
114
|
+
let rs = [okDel, { n: 0, nDeleted: 0, ok: 1 }]
|
|
115
|
+
plan.del = () => rs
|
|
116
|
+
let r = await proc('del', [{ id: 'd1' }, { id: 'd-none' }])
|
|
117
|
+
assert.strict.deepEqual(r, rs)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('U10 del數字主鍵ok為0', async function() {
|
|
121
|
+
//R02: del逐筆ok為0即reject; 數字主鍵照列
|
|
122
|
+
plan.del = () => [okDel, { n: 0, nDeleted: 0, ok: 0, err: 'can not delete by invalid id[123]' }]
|
|
123
|
+
let err = await getReject(proc('del', [{ id: 'd1' }, { id: 123 }]))
|
|
124
|
+
assert.strict.equal(err, '刪除資料失敗: id[123]')
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('U10b del物件輸入ok為0且無err', async function() {
|
|
128
|
+
//R02: 物件輸入亦須檢出; R07: ok為0而無err仍判失敗
|
|
129
|
+
plan.del = () => [{ n: 1, nDeleted: 0, ok: 0 }]
|
|
130
|
+
let err = await getReject(proc('del', { id: 'd1' }))
|
|
131
|
+
assert.strict.equal(err, '刪除資料失敗: id[d1]')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('U11 del整批reject', async function() {
|
|
135
|
+
//R05: 整批性錯誤之訊息不變
|
|
136
|
+
plan.del = () => Promise.reject(new Error('E-batch'))
|
|
137
|
+
let err = await getReject(proc('del', [{ id: 'd1' }]))
|
|
138
|
+
assert.strict.equal(err, '刪除資料失敗')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('U12 del之ORM回傳非陣列', async function() {
|
|
142
|
+
//R07: 未依規格之ORM回傳非陣列時不檢查, 照舊回傳原值
|
|
143
|
+
plan.del = () => ({ n: 1, nDeleted: 1, ok: 1 })
|
|
144
|
+
let r = await proc('del', [{ id: 'd1' }])
|
|
145
|
+
assert.strict.deepEqual(r, { n: 1, nDeleted: 1, ok: 1 })
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('U13 mix全部成功', async function() {
|
|
149
|
+
//R04: mix依add、del、diff順序執行, 回傳累積之id
|
|
150
|
+
plan.insert = (data) => ({ n: data.length, nInserted: data.length, ok: 1 })
|
|
151
|
+
plan.del = (data) => data.map(() => okDel)
|
|
152
|
+
plan.save = (data) => data.map(() => okSave)
|
|
153
|
+
let r = await proc('mix', { add: [{ id: 'a1' }], del: [{ id: 'd1' }], diff: [{ id: 'f1' }] })
|
|
154
|
+
assert.strict.deepEqual(r, [{ id: 'a1' }, { id: 'd1' }, { id: 'f1' }])
|
|
155
|
+
assert.strict.deepEqual(calls.map((v) => v.fn), ['insert', 'del', 'save'])
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('U14 mix之del與diff皆有ok為0', async function() {
|
|
159
|
+
//R03: 逐筆失敗不中斷後續步驟, 全部步驟完成後一次reject並分步列出
|
|
160
|
+
plan.insert = (data) => ({ n: data.length, nInserted: data.length, ok: 1 })
|
|
161
|
+
plan.del = () => [okDel, { n: 0, nDeleted: 0, ok: 0, err: 'E-del' }]
|
|
162
|
+
plan.save = () => [{ n: 1, nInserted: 0, nModified: 0, ok: 0, err: 'E-save' }]
|
|
163
|
+
let err = await getReject(proc('mix', { add: [{ id: 'a1' }], del: [{ id: 'd1' }, { id: 'd2' }], diff: [{ id: 'f1' }] }))
|
|
164
|
+
assert.strict.equal(err, '刪除資料失敗: id[d2]; 變更資料失敗: id[f1]')
|
|
165
|
+
assert.strict.deepEqual(calls.map((v) => v.fn), ['insert', 'del', 'save'])
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('U15 mix僅del有ok為0', async function() {
|
|
169
|
+
//R03: del之逐筆失敗不中斷diff, diff仍寫入, 最後reject僅列del之失敗
|
|
170
|
+
plan.del = () => [{ n: 0, nDeleted: 0, ok: 0, err: 'E-del' }]
|
|
171
|
+
plan.save = (data) => data.map(() => okSave)
|
|
172
|
+
let err = await getReject(proc('mix', { del: [{ id: 'd1' }], diff: [{ id: 'f1' }] }))
|
|
173
|
+
assert.strict.equal(err, '刪除資料失敗: id[d1]')
|
|
174
|
+
assert.strict.deepEqual(calls.map((v) => v.fn), ['del', 'save'])
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('U16 mix之del整批reject', async function() {
|
|
178
|
+
//R05: 整批性錯誤維持立即reject, 後續步驟不執行
|
|
179
|
+
plan.del = () => Promise.reject(new Error('E-batch'))
|
|
180
|
+
plan.save = (data) => data.map(() => okSave)
|
|
181
|
+
let err = await getReject(proc('mix', { del: [{ id: 'd1' }], diff: [{ id: 'f1' }] }))
|
|
182
|
+
assert.strict.equal(err, '刪除資料失敗')
|
|
183
|
+
assert.strict.deepEqual(calls.map((v) => v.fn), ['del'])
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('U17 mix之add整批reject', async function() {
|
|
187
|
+
//R05: insert整批性錯誤維持立即reject, 後續步驟不執行
|
|
188
|
+
plan.insert = () => Promise.reject(new Error('E-batch'))
|
|
189
|
+
plan.del = (data) => data.map(() => okDel)
|
|
190
|
+
let err = await getReject(proc('mix', { add: [{ id: 'a1' }], del: [{ id: 'd1' }] }))
|
|
191
|
+
assert.strict.equal(err, '新增資料失敗')
|
|
192
|
+
assert.strict.deepEqual(calls.map((v) => v.fn), ['insert'])
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('U18 mix之del有ok為0後diff整批reject', async function() {
|
|
196
|
+
//R05: 遇整批性錯誤即以該步驟之訊息reject, 先前之逐筆失敗已由log輸出
|
|
197
|
+
plan.del = () => [{ n: 0, nDeleted: 0, ok: 0, err: 'E-del' }]
|
|
198
|
+
plan.save = () => Promise.reject(new Error('E-batch'))
|
|
199
|
+
let err = await getReject(proc('mix', { del: [{ id: 'd1' }], diff: [{ id: 'f1' }] }))
|
|
200
|
+
assert.strict.equal(err, '變更資料失敗')
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('U19 insert成功', async function() {
|
|
204
|
+
//R04: insert回傳聚合物件而非逐筆陣列, 照舊回傳id
|
|
205
|
+
plan.insert = (data) => ({ n: data.length, nInserted: data.length, ok: 1 })
|
|
206
|
+
let r = await proc('insert', [{ id: 'i1' }])
|
|
207
|
+
assert.strict.deepEqual(r, [{ id: 'i1' }])
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('U22 錯誤原文寫入log而不進reject訊息', async function() {
|
|
211
|
+
//R06: 前端訊息只帶主鍵, 錯誤原文由console.log輸出
|
|
212
|
+
plan.save = () => [{ n: 1, nInserted: 0, nModified: 0, ok: 0, err: 'E-secret-detail' }]
|
|
213
|
+
let logs = []
|
|
214
|
+
let logOrig = console.log
|
|
215
|
+
console.log = (...args) => {
|
|
216
|
+
logs.push(args)
|
|
217
|
+
}
|
|
218
|
+
let err
|
|
219
|
+
try {
|
|
220
|
+
err = await getReject(proc('save', [{ id: 's1' }]))
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
console.log = logOrig
|
|
224
|
+
}
|
|
225
|
+
assert.ok(!String(err).includes('E-secret-detail'))
|
|
226
|
+
assert.ok(JSON.stringify(logs).includes('E-secret-detail'))
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
describe('genTestData逐筆失敗', function() {
|
|
233
|
+
|
|
234
|
+
let dsGen = {
|
|
235
|
+
tests: {
|
|
236
|
+
funTest: () => [{ id: 'g1' }, { id: 'g2' }],
|
|
237
|
+
},
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
it('U20 funTest全部成功', async function() {
|
|
241
|
+
//R09: 全部成功時照舊resolve, 並以funTest產生之數據呼叫save
|
|
242
|
+
let saved = null
|
|
243
|
+
let woItems = {
|
|
244
|
+
tests: {
|
|
245
|
+
save: async (rs) => {
|
|
246
|
+
saved = rs
|
|
247
|
+
return rs.map(() => okSave)
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
}
|
|
251
|
+
await genTestData(dsGen, woItems, { mode: 'funTest', bLog: false })
|
|
252
|
+
assert.strict.deepEqual(saved, [{ id: 'g1' }, { id: 'g2' }])
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it('U21 funTest之save有ok為0', async function() {
|
|
256
|
+
//R09: 逐筆ok為0即reject, 訊息含資料表名與錯誤原文(伺服器端開發工具)
|
|
257
|
+
let woItems = {
|
|
258
|
+
tests: {
|
|
259
|
+
save: async () => [okSave, { n: 1, nInserted: 0, nModified: 0, ok: 0, err: 'E-gen' }],
|
|
260
|
+
},
|
|
261
|
+
}
|
|
262
|
+
let err = await getReject(genTestData(dsGen, woItems, { mode: 'funTest', bLog: false }))
|
|
263
|
+
assert.ok(String(err).includes('tests'))
|
|
264
|
+
assert.ok(String(err).includes('E-gen'))
|
|
265
|
+
assert.ok(String(err).includes('g2'))
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
})
|