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.
- package/README.md +1 -0
- package/dist/w-serv-orm.umd.js +2 -2
- package/dist/w-serv-orm.umd.js.map +1 -1
- package/docs/WServOrm.html +19 -8
- package/docs/WServOrm.mjs.html +20 -6
- package/docs/index.html +1 -1
- package/g-mOrm.mjs +1 -0
- package/package.json +3 -3
- package/schema/tables/tests.mjs +1 -1
- package/src/WServOrm.mjs +19 -5
- package/src/genTestData.mjs +2 -2
- package/src/getDb.mjs +27 -6
- package/src/getDbBackupAndRecover.mjs +27 -2
- package/src/getItemsFail.mjs +2 -34
- package/src/getMapOrm.mjs +128 -46
- package/src/getProc.mjs +79 -70
- package/src/getSettings.mjs +1 -1
- package/test/api-procOrm.test.mjs +27 -2
- package/test/unit-WServOrm.test.mjs +554 -0
- package/test/unit-getDb.test.mjs +188 -0
- package/test/unit-mapOrm.test.mjs +118 -13
|
@@ -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, '
|
|
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, '
|
|
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, '
|
|
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,
|
|
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, '
|
|
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, '
|
|
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, '
|
|
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, '
|
|
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
|
})
|