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,188 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import assert from 'assert'
4
+ import { spawnSync } from 'child_process'
5
+ import { pathToFileURL } from 'url'
6
+ import getDb from '../src/getDb.mjs'
7
+ import br from '../src/getDbBackupAndRecover.mjs'
8
+
9
+
10
+ //會寫出備份檔之案例以子行程執行: getDb之輸出目錄於模組載入時取cwd, 子行程之cwd設於本案暫存夾, 不寫入repo根目錄
11
+ let fd = path.resolve('./test/_tmp/unit-getDb')
12
+ let urlGetDb = pathToFileURL(path.resolve('./src/getDb.mjs')).href
13
+
14
+ function mkWoItems(log, behavior = {}) {
15
+ return {
16
+ tests: {
17
+ select: async () => {
18
+ if (behavior.select) {
19
+ return behavior.select()
20
+ }
21
+ return [{ id: 'a' }]
22
+ },
23
+ insert: async (d) => {
24
+ log.push({ fn: 'insert', table: 'tests', d })
25
+ return { n: d.length, nInserted: d.length, ok: 1 }
26
+ },
27
+ createStorage: async () => {
28
+ log.push({ fn: 'createStorage', table: 'tests' })
29
+ },
30
+ },
31
+ }
32
+ }
33
+
34
+ async function getReject(pm) {
35
+ try {
36
+ await pm
37
+ }
38
+ catch (err) {
39
+ return err
40
+ }
41
+ throw new Error('預期reject但resolve')
42
+ }
43
+
44
+ async function quiet(fn) {
45
+ let logOrig = console.log
46
+ console.log = () => {}
47
+ try {
48
+ return await fn()
49
+ }
50
+ finally {
51
+ console.log = logOrig
52
+ }
53
+ }
54
+
55
+ function writeBackup(name, content) {
56
+ let fp = path.join(fd, name)
57
+ fs.writeFileSync(fp, content, 'utf8')
58
+ return fp
59
+ }
60
+
61
+ function runChild(name, code) {
62
+ let dir = path.join(fd, name)
63
+ fs.mkdirSync(dir, { recursive: true })
64
+ let fp = path.join(dir, 'run.mjs')
65
+ fs.writeFileSync(fp, code, 'utf8')
66
+ let r = spawnSync(process.execPath, [fp], { cwd: dir, encoding: 'utf8' })
67
+ let files = fs.readdirSync(dir).filter((v) => v.startsWith('backup-'))
68
+ return { status: r.status, stdout: r.stdout, stderr: r.stderr, dir, files }
69
+ }
70
+
71
+
72
+ describe('getDb備份與還原', function() {
73
+
74
+ before(function() {
75
+ fs.rmSync(fd, { recursive: true, force: true })
76
+ fs.mkdirSync(fd, { recursive: true })
77
+ })
78
+
79
+ after(function() {
80
+ fs.rmSync(fd, { recursive: true, force: true })
81
+ })
82
+
83
+ it('B01 backup之select失敗即reject', async function() {
84
+ //D5: 印log後reject原錯誤
85
+ let woItems = mkWoItems([], { select: () => Promise.reject(new Error('E-select')) })
86
+ let err = await quiet(() => getReject(getDb.backup(woItems, { bLog: false })))
87
+ assert.ok(String(err).includes('E-select'))
88
+ })
89
+
90
+ it('B02 recover之備份檔不存在即reject', async function() {
91
+ //D5
92
+ let err = await quiet(() => getReject(getDb.recover(mkWoItems([]), path.join(fd, 'none.json'), { bLog: false })))
93
+ assert.ok(String(err).includes('fp is not exist'))
94
+ })
95
+
96
+ it('B03 recover之備份內容無效即reject且不寫入', async function() {
97
+ //P4-2: 空檔、壞JSON、空陣列、缺name皆拒絕, 不假成功
98
+ for (let [name, content] of [['empty.json', ''], ['bad.json', '{bad'], ['arr0.json', '[]'], ['noname.json', '[{"x":1}]']]) {
99
+ let log = []
100
+ let err = await quiet(() => getReject(getDb.recover(mkWoItems(log), writeBackup(name, content), { bLog: false })))
101
+ assert.ok(String(err).includes('invalid backup'), `${name}: ${err}`)
102
+ assert.strict.deepEqual(log, [])
103
+ }
104
+ })
105
+
106
+ it('B04 recover之備份含woItems沒有之資料表即於寫入前reject', async function() {
107
+ //P4-2: 先驗全部再寫入, 不留部分還原
108
+ let log = []
109
+ let fp = writeBackup('unknown.json', JSON.stringify([{ name: 'tests', data: [{ id: 'a' }] }, { name: 'nope', data: [{ id: 'b' }] }]))
110
+ let err = await quiet(() => getReject(getDb.recover(mkWoItems(log), fp, { bLog: false })))
111
+ assert.ok(String(err).includes('nope'))
112
+ assert.strict.deepEqual(log, [])
113
+ })
114
+
115
+ it('B05 recover成功', async function() {
116
+ //D5: 成功路徑不變
117
+ let log = []
118
+ let fp = writeBackup('ok.json', JSON.stringify([{ name: 'tests', data: [{ id: 'a' }] }]))
119
+ await quiet(() => getDb.recover(mkWoItems(log), fp, { bLog: false }))
120
+ assert.strict.deepEqual(log, [{ fn: 'insert', table: 'tests', d: [{ id: 'a' }] }])
121
+ })
122
+
123
+ it('B06 recover之bRela缺ds或genModelsByTabs即於寫入前reject', async function() {
124
+ //P4-1: 明確訊息, 不寫入
125
+ let log = []
126
+ let fp = writeBackup('rela.json', JSON.stringify([{ name: 'tests', data: [{ id: 'a' }] }]))
127
+ let err = await quiet(() => getReject(getDb.recover(mkWoItems(log), fp, { bRela: true, bLog: false })))
128
+ assert.ok(String(err).includes('genModelsByTabs'))
129
+ assert.strict.deepEqual(log, [])
130
+ })
131
+
132
+ it('B07 recover之bRela帶ds與genModelsByTabs可產生model並建表寫入', async function() {
133
+ //P4-1: genModelsByTabs收到各表settings, 之後建表並寫入
134
+ let log = []
135
+ let calls = []
136
+ let ds = { tests: { keys: ['id'], settings: { id: { pk: true, type: 'STRING' } } } }
137
+ let genModelsByTabs = (fdModels, tabs) => {
138
+ calls.push({ fdModels, tabs })
139
+ }
140
+ let fp = writeBackup('rela2.json', JSON.stringify([{ name: 'tests', data: [{ id: 'a' }] }]))
141
+ await quiet(() => getDb.recover(mkWoItems(log), fp, { bRela: true, ds, genModelsByTabs, fdModels: './test/_tmp/unit-getDb/models', bLog: false }))
142
+ assert.strict.equal(calls.length, 1)
143
+ assert.strict.deepEqual(calls[0].tabs, { tests: ds.tests.settings })
144
+ assert.strict.deepEqual(log.map((v) => v.fn), ['createStorage', 'insert'])
145
+ })
146
+
147
+ it('B08 低階recover之備份內容無效即reject', async function() {
148
+ //P4-2: WServOrm實例上之recover即此低階函數
149
+ let fp = writeBackup('bad2.json', '{bad')
150
+ let err = await getReject(br.recover(fp, mkWoItems([])))
151
+ assert.ok(String(err).includes('invalid backup'))
152
+ })
153
+
154
+ it('B12 backup之woItems無效即reject且為Error', async function() {
155
+ //錯誤型別: 工具函數一律拋Error(原由pmSeries以字串reject); 空物件與空陣列原會產出recover拒收之空備份, 一併拒絕
156
+ for (let bad of [null, undefined, 'x', 1, [], {}]) {
157
+ let err = await quiet(() => getReject(br.backup(bad)))
158
+ assert.ok(err instanceof Error && err.message === 'invalid woItems', `低階 ${String(bad)}: ${err}`)
159
+ err = await quiet(() => getReject(getDb.backup(bad, { bLog: false })))
160
+ assert.ok(err instanceof Error && err.message === 'invalid woItems', `getDb ${String(bad)}: ${err}`)
161
+ }
162
+ })
163
+
164
+ it('B09 backup成功寫出備份檔(子行程)', async function() {
165
+ //D5: 成功路徑不變
166
+ let r = runChild('bk-ok', `import getDb from '${urlGetDb}'\nlet woItems = { tests: { select: async () => [{ id: 'a' }] } }\nawait getDb.backup(woItems, { bLog: false })\nconsole.log('RESOLVED')\n`)
167
+ assert.strict.equal(r.status, 0, r.stderr)
168
+ assert.ok(r.stdout.includes('RESOLVED'))
169
+ assert.strict.equal(r.files.length, 1)
170
+ let j = JSON.parse(fs.readFileSync(path.join(r.dir, r.files[0]), 'utf8'))
171
+ assert.strict.deepEqual(j, [{ name: 'tests', data: [{ id: 'a' }] }])
172
+ })
173
+
174
+ it('B10 仿g_backupDb未await呼叫而失敗時離開碼非零(子行程)', async function() {
175
+ //D5: 使用端之頂層呼叫未await, 失敗成為未處理之rejection
176
+ let r = runChild('bk-fail', `import getDb from '${urlGetDb}'\nlet woItems = { tests: { select: async () => { throw new Error('E-select') } } }\ngetDb.backup(woItems)\n`)
177
+ assert.notStrictEqual(r.status, 0)
178
+ assert.strict.equal(r.files.length, 0)
179
+ })
180
+
181
+ it('B11 backup資料無法序列化即reject且不寫出空檔(子行程)', async function() {
182
+ //D5: o2j失敗回空字串, 不得寫出空備份而回報成功
183
+ let r = runChild('bk-circular', `import getDb from '${urlGetDb}'\nlet a = { id: 'a' }\na.self = a\nlet woItems = { tests: { select: async () => [a] } }\nlet out = 'RESOLVED'\nawait getDb.backup(woItems, { bLog: false }).catch(() => { out = 'REJECTED' })\nconsole.log(out)\n`)
184
+ assert.ok(r.stdout.includes('REJECTED'), r.stdout + r.stderr)
185
+ assert.strict.equal(r.files.length, 0)
186
+ })
187
+
188
+ })
@@ -1,6 +1,7 @@
1
1
  import assert from 'assert'
2
2
  import WServOrm from '../src/WServOrm.mjs'
3
3
  import genTestData from '../src/genTestData.mjs'
4
+ import dsSchema from '../schema/index.mjs'
4
5
 
5
6
 
6
7
  //替身ORM, 回傳形狀依w-orm系列規格(資料庫函數回傳定義計算.md T2、T4):
@@ -66,28 +67,28 @@ describe('mapOrm逐筆失敗', function() {
66
67
  //R01: 逐筆ok為0即reject, 訊息帶失敗筆之主鍵, 不含成功筆
67
68
  plan.save = () => [okSave, { n: 1, nInserted: 0, nModified: 0, ok: 0, err: 'E-save' }]
68
69
  let err = await getReject(proc('save', [{ id: 's1' }, { id: 's2' }]))
69
- assert.strict.equal(err, '儲存資料失敗: id[s2]')
70
+ assert.strict.equal(err, 'failed to save data: id[s2]')
70
71
  })
71
72
 
72
73
  it('U04 save物件ok為0', async function() {
73
74
  //R01: 輸入為單一物件時ORM回傳長度為1之陣列, 仍須檢出
74
75
  plan.save = () => [{ n: 1, nInserted: 0, nModified: 0, ok: 0, err: 'E-save' }]
75
76
  let err = await getReject(proc('save', { id: 's1' }))
76
- assert.strict.equal(err, '儲存資料失敗: id[s1]')
77
+ assert.strict.equal(err, 'failed to save data: id[s1]')
77
78
  })
78
79
 
79
80
  it('U05 save未帶主鍵之筆ok為0且無err', async function() {
80
81
  //R01: 無有效主鍵者以輸入位置標示; R07: ok為0而無err(舊版ORM)仍判失敗
81
82
  plan.save = () => [okSave, { n: 1, ok: 0 }]
82
83
  let err = await getReject(proc('save', [{ id: 's1' }, { name: 'no-id' }]))
83
- assert.strict.equal(err, '儲存資料失敗: 第2筆')
84
+ assert.strict.equal(err, 'failed to save data: data[1]')
84
85
  })
85
86
 
86
87
  it('U06 save整批reject', async function() {
87
88
  //R05: 整批性錯誤之訊息不變
88
89
  plan.save = () => Promise.reject(new Error('E-batch'))
89
90
  let err = await getReject(proc('save', [{ id: 's1' }]))
90
- assert.strict.equal(err, '儲存資料失敗')
91
+ assert.strict.equal(err, 'failed to save data')
91
92
  })
92
93
 
93
94
  it('U07 save之ORM回傳非陣列', async function() {
@@ -106,7 +107,7 @@ describe('mapOrm逐筆失敗', function() {
106
107
  plan.save = (data) => data.map(() => ({ n: 1, nInserted: 0, nModified: 0, ok: 0, err: 'E-full' }))
107
108
  let err = await getReject(proc('save', input))
108
109
  let ids = input.slice(0, 10).map((v) => `id[${v.id}]`).join(', ')
109
- assert.strict.equal(err, `儲存資料失敗: ${ids}等共12筆`)
110
+ assert.strict.equal(err, `failed to save data: ${ids}, ... (12 in total)`)
110
111
  })
111
112
 
112
113
  it('U09 del全部成功含主鍵未命中', async function() {
@@ -121,21 +122,21 @@ describe('mapOrm逐筆失敗', function() {
121
122
  //R02: del逐筆ok為0即reject; 數字主鍵照列
122
123
  plan.del = () => [okDel, { n: 0, nDeleted: 0, ok: 0, err: 'can not delete by invalid id[123]' }]
123
124
  let err = await getReject(proc('del', [{ id: 'd1' }, { id: 123 }]))
124
- assert.strict.equal(err, '刪除資料失敗: id[123]')
125
+ assert.strict.equal(err, 'failed to delete data: id[123]')
125
126
  })
126
127
 
127
128
  it('U10b del物件輸入ok為0且無err', async function() {
128
129
  //R02: 物件輸入亦須檢出; R07: ok為0而無err仍判失敗
129
130
  plan.del = () => [{ n: 1, nDeleted: 0, ok: 0 }]
130
131
  let err = await getReject(proc('del', { id: 'd1' }))
131
- assert.strict.equal(err, '刪除資料失敗: id[d1]')
132
+ assert.strict.equal(err, 'failed to delete data: id[d1]')
132
133
  })
133
134
 
134
135
  it('U11 del整批reject', async function() {
135
136
  //R05: 整批性錯誤之訊息不變
136
137
  plan.del = () => Promise.reject(new Error('E-batch'))
137
138
  let err = await getReject(proc('del', [{ id: 'd1' }]))
138
- assert.strict.equal(err, '刪除資料失敗')
139
+ assert.strict.equal(err, 'failed to delete data')
139
140
  })
140
141
 
141
142
  it('U12 del之ORM回傳非陣列', async function() {
@@ -161,7 +162,7 @@ describe('mapOrm逐筆失敗', function() {
161
162
  plan.del = () => [okDel, { n: 0, nDeleted: 0, ok: 0, err: 'E-del' }]
162
163
  plan.save = () => [{ n: 1, nInserted: 0, nModified: 0, ok: 0, err: 'E-save' }]
163
164
  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.equal(err, 'failed to delete data: id[d2]; failed to update data: id[f1]')
165
166
  assert.strict.deepEqual(calls.map((v) => v.fn), ['insert', 'del', 'save'])
166
167
  })
167
168
 
@@ -170,7 +171,7 @@ describe('mapOrm逐筆失敗', function() {
170
171
  plan.del = () => [{ n: 0, nDeleted: 0, ok: 0, err: 'E-del' }]
171
172
  plan.save = (data) => data.map(() => okSave)
172
173
  let err = await getReject(proc('mix', { del: [{ id: 'd1' }], diff: [{ id: 'f1' }] }))
173
- assert.strict.equal(err, '刪除資料失敗: id[d1]')
174
+ assert.strict.equal(err, 'failed to delete data: id[d1]')
174
175
  assert.strict.deepEqual(calls.map((v) => v.fn), ['del', 'save'])
175
176
  })
176
177
 
@@ -179,7 +180,7 @@ describe('mapOrm逐筆失敗', function() {
179
180
  plan.del = () => Promise.reject(new Error('E-batch'))
180
181
  plan.save = (data) => data.map(() => okSave)
181
182
  let err = await getReject(proc('mix', { del: [{ id: 'd1' }], diff: [{ id: 'f1' }] }))
182
- assert.strict.equal(err, '刪除資料失敗')
183
+ assert.strict.equal(err, 'failed to delete data')
183
184
  assert.strict.deepEqual(calls.map((v) => v.fn), ['del'])
184
185
  })
185
186
 
@@ -188,7 +189,7 @@ describe('mapOrm逐筆失敗', function() {
188
189
  plan.insert = () => Promise.reject(new Error('E-batch'))
189
190
  plan.del = (data) => data.map(() => okDel)
190
191
  let err = await getReject(proc('mix', { add: [{ id: 'a1' }], del: [{ id: 'd1' }] }))
191
- assert.strict.equal(err, '新增資料失敗')
192
+ assert.strict.equal(err, 'failed to insert data')
192
193
  assert.strict.deepEqual(calls.map((v) => v.fn), ['insert'])
193
194
  })
194
195
 
@@ -197,7 +198,7 @@ describe('mapOrm逐筆失敗', function() {
197
198
  plan.del = () => [{ n: 0, nDeleted: 0, ok: 0, err: 'E-del' }]
198
199
  plan.save = () => Promise.reject(new Error('E-batch'))
199
200
  let err = await getReject(proc('mix', { del: [{ id: 'd1' }], diff: [{ id: 'f1' }] }))
200
- assert.strict.equal(err, '變更資料失敗')
201
+ assert.strict.equal(err, 'failed to update data')
201
202
  })
202
203
 
203
204
  it('U19 insert成功', async function() {
@@ -229,6 +230,88 @@ describe('mapOrm逐筆失敗', function() {
229
230
  })
230
231
 
231
232
 
233
+ describe('mapOrm主鍵與clearXSS', function() {
234
+
235
+ let so = WServOrm(ds, WOrmStub, 'stub-url', 'stub-db', { useCheckUser: false, useExcludeWhenNotAdmin: false })
236
+ let proc = (mode, input) => so.procOrm('u1', 'tests', mode, input)
237
+
238
+ beforeEach(function() {
239
+ plan = {}
240
+ calls = []
241
+ })
242
+
243
+ it('X01 數學式之主鍵與欄位原樣寫入並回傳', async function() {
244
+ //wsemi 1.9.13起clearXSS只轉義會開始標籤之<, 數學式不改寫, 主鍵不被拒絕且與寫入者一致
245
+ plan.save = (data) => data.map(() => okSave)
246
+ let r = await proc('save', [{ id: 'a < b', name: '溫度 > 30°C' }, { id: '1 > 0', name: 'x <= 5' }])
247
+ assert.strict.deepEqual(r, [{ id: 'a < b' }, { id: '1 > 0' }])
248
+ assert.strict.deepEqual(calls[0].data.map((v) => [v.id, v.name]), [['a < b', '溫度 > 30°C'], ['1 > 0', 'x <= 5']])
249
+ })
250
+
251
+ it('X02 save之主鍵會被改寫即整批reject且不呼叫ORM', async function() {
252
+ //改寫後之主鍵與呼叫端所持者不同而查不到, 整批拒絕; 訊息只列位置不回顯主鍵; 結尾之<亦會被轉義
253
+ plan.save = (data) => data.map(() => okSave)
254
+ let err = await getReject(proc('save', [{ id: 's1' }, { id: 'a<b' }, { id: 'x <' }]))
255
+ assert.strict.equal(err, 'failed to save data, id contains HTML markup: data[1], data[2]')
256
+ assert.strict.deepEqual(calls, [])
257
+ })
258
+
259
+ it('X03 insert物件之主鍵會被改寫即reject且不呼叫ORM', async function() {
260
+ plan.insert = (data) => ({ n: data.length, nInserted: data.length, ok: 1 })
261
+ let err = await getReject(proc('insert', { id: '<script>x' }))
262
+ assert.strict.equal(err, 'failed to insert data, id contains HTML markup: data[0]')
263
+ assert.strict.deepEqual(calls, [])
264
+ })
265
+
266
+ it('X04 mix之新增或修改之主鍵會被改寫即於任何步驟之前reject', async function() {
267
+ //mix依add、del、diff執行, 預檢於任何步驟寫入之前, 故一筆都不寫入
268
+ plan.insert = (data) => ({ n: data.length, nInserted: data.length, ok: 1 })
269
+ plan.del = (data) => data.map(() => okDel)
270
+ plan.save = (data) => data.map(() => okSave)
271
+ let err = await getReject(proc('mix', { add: [{ id: 'a1' }, { id: 'a<b' }], del: [{ id: 'd1' }], diff: [{ id: '</x>' }] }))
272
+ assert.strict.equal(err, 'failed to insert data, id contains HTML markup: data[1]; failed to update data, id contains HTML markup: data[0]')
273
+ assert.strict.deepEqual(calls, [])
274
+ err = await getReject(proc('mix', { del: [{ id: 'd1' }], diff: [{ id: 'f1' }, { id: '<!--x-->' }] }))
275
+ assert.strict.equal(err, 'failed to update data, id contains HTML markup: data[1]')
276
+ assert.strict.deepEqual(calls, [])
277
+ })
278
+
279
+ it('X05 數字主鍵、未帶主鍵與不被改寫之主鍵照常寫入', async function() {
280
+ //判準為是否被改寫: 數字不經清除, 未帶主鍵者由本套件補值, 白名單標籤之規範寫法不被改寫
281
+ plan.save = (data) => data.map(() => okSave)
282
+ let r = await proc('save', [{ id: 5 }, { id: '<b>粗體</b>' }, { name: 'no-id' }])
283
+ assert.strict.equal(r[0].id, 5)
284
+ assert.strict.equal(r[1].id, '<b>粗體</b>')
285
+ assert.ok(typeof r[2].id === 'string' && r[2].id.length > 0)
286
+ })
287
+
288
+ it('X06 select與del不經clearXSS, 主鍵原樣交ORM', async function() {
289
+ //此不對稱即主鍵會被改寫者須於寫入前拒絕之原因
290
+ plan.select = () => []
291
+ plan.del = (data) => data.map(() => ({ n: 0, nDeleted: 0, ok: 1 }))
292
+ await proc('select', { id: 'a<b' })
293
+ await proc('del', [{ id: 'a<b' }])
294
+ assert.strict.deepEqual(calls.map((v) => v.data), [{ id: 'a<b' }, [{ id: 'a<b' }]])
295
+ })
296
+
297
+ it('X07 insert與mix各入口之其餘主鍵狀態', async function() {
298
+ //數字與不被改寫之主鍵照寫, mix之修改未帶主鍵者補值, mix之刪除不經clearXSS而原樣交ORM
299
+ plan.insert = (data) => ({ n: data.length, nInserted: data.length, ok: 1 })
300
+ plan.del = (data) => data.map(() => okDel)
301
+ plan.save = (data) => data.map(() => okSave)
302
+ let r1 = await proc('insert', [{ id: 7 }, { id: 'a < b' }])
303
+ assert.strict.deepEqual(r1, [{ id: 7 }, { id: 'a < b' }])
304
+ let r2 = await proc('mix', { add: [{ id: 8 }, { id: '1 > 0' }], del: [{ id: 'a<b' }], diff: [{ id: 9 }, { id: 'x <= 5' }, { name: 'no-id' }] })
305
+ assert.strict.deepEqual(r2.slice(0, 5), [{ id: 8 }, { id: '1 > 0' }, { id: 'a<b' }, { id: 9 }, { id: 'x <= 5' }])
306
+ assert.ok(typeof r2[5].id === 'string' && r2[5].id.length > 0)
307
+ assert.strict.deepEqual(calls.map((v) => v.fn), ['insert', 'insert', 'del', 'save'])
308
+ assert.strict.deepEqual(calls[2].data, [{ id: 'a<b' }])
309
+ assert.strict.equal(calls[3].data[2].id, r2[5].id)
310
+ })
311
+
312
+ })
313
+
314
+
232
315
  describe('genTestData逐筆失敗', function() {
233
316
 
234
317
  let dsGen = {
@@ -265,4 +348,26 @@ describe('genTestData逐筆失敗', function() {
265
348
  assert.ok(String(err).includes('g2'))
266
349
  })
267
350
 
351
+ it('U23 funTestAndSave與funTest之逐筆失敗訊息相同', async function() {
352
+ //R09: funTestAndSave由w-data-collector之build產生(schema/index.mjs)且其錯誤原樣轉出; w-data-collector 1.0.46起兩種模式之訊息同為英文同格式
353
+ let woItems = {
354
+ tests: {
355
+ save: async (rs) => rs.map((v, k) => (k === 1 ? { n: 1, nInserted: 0, nModified: 0, ok: 0, err: 'E-gen' } : okSave)),
356
+ },
357
+ }
358
+ let logOrig = console.log
359
+ console.log = () => {}
360
+ let errs = []
361
+ try {
362
+ for (let mode of ['funTestAndSave', 'funTest']) {
363
+ errs.push(await getReject(genTestData(dsSchema, woItems, { mode, bLog: false })))
364
+ }
365
+ }
366
+ finally {
367
+ console.log = logOrig
368
+ }
369
+ let msg = 'failed to write test data into table[tests]: [{"k":1,"id":"id-for-tests-2","err":"E-gen"}]'
370
+ assert.strict.deepEqual(errs.map((e) => e.message), [msg, msg])
371
+ })
372
+
268
373
  })