w-serv-orm 1.1.16 → 1.1.17
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 +109 -0
- package/dist/w-serv-orm.umd.js +2 -2
- package/dist/w-serv-orm.umd.js.map +1 -1
- package/docs/WServOrm.html +9 -4
- package/docs/WServOrm.mjs.html +94 -3
- package/docs/baseline.mjs.html +624 -0
- package/docs/global.html +1622 -0
- package/docs/index.html +2 -2
- package/package.json +1 -1
- package/src/WServOrm.mjs +92 -1
- package/src/baseline.mjs +552 -0
- package/src/getMapOrm.mjs +188 -10
- package/src/getProc.mjs +13 -4
- package/test/api-guard.test.mjs +401 -0
- package/test/api-keepuser.test.mjs +131 -0
- package/test/tools/memOrm.mjs +122 -0
- package/test/unit-baseline.test.mjs +766 -0
- package/test/unit-guard.test.mjs +952 -0
- package/test/unit-keepuser.test.mjs +307 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import WServOrm from '../src/WServOrm.mjs'
|
|
3
|
+
import getMapOrm from '../src/getMapOrm.mjs'
|
|
4
|
+
import { createMemOrm } from './tools/memOrm.mjs'
|
|
5
|
+
import { getReject, quiet } from './tools/ormStub.mjs'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
//規格: w-web-sso 需求 T2①(任務 orm-run02): sso之tokens.userId是權杖擁有者而非建立者, 依擁有者分組以擁有者當procOrm之第1參數寫入會使userIdUpdate亦被記成擁有者;
|
|
9
|
+
//須能於insert保留列上之userId(擁有者), 同時userIdUpdate記為操作者(procOrm之第1參數); 規則帳本 R-KEEPUSER-01(rulebook_concurrency.md)
|
|
10
|
+
//以有狀態之記憶體ORM(test/tools/memOrm.mjs)驗證; 真後端(lowdb)見 api-keepuser.test.mjs
|
|
11
|
+
//
|
|
12
|
+
//規格句對照(spec = 派工提示詞「本輪範圍」與〈完成條件〉1):
|
|
13
|
+
// K01-K03 「keepUserId:true 時保留擁有者、userIdUpdate 為操作者」「userIdUpdate、timeCreate、timeUpdate 照現行」
|
|
14
|
+
// K02、K05 「列上userId為非空字串者保留, 其餘照現行以操作者補」
|
|
15
|
+
// K04 「未給或 false 時行為與現行完全相同」
|
|
16
|
+
// K06-K07 實作者補強(表無userId欄、clearXSS照常)
|
|
17
|
+
// K08-K11 「與mustExist相同之驗證時點與方式: 未知鍵、非布林、用於不適用之mode → 於任何讀寫與擴充函數之前以固定字串reject」「無效opt於寫入前拒收」
|
|
18
|
+
// K12-K13 實作者補強(擴充函數參數形狀不變、前處理改寫後之input為保留判定之依據)
|
|
19
|
+
// K14 「若save時現行會覆寫userId, 一併評估」→ save現行不覆寫userId(只補userIdUpdate、timeUpdate), 故不適用而拒收
|
|
20
|
+
|
|
21
|
+
let wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
22
|
+
|
|
23
|
+
let KEYS = ['id', 'name', 'userId', 'userIdUpdate', 'timeCreate', 'timeUpdate']
|
|
24
|
+
|
|
25
|
+
function mkSo(keys = KEYS) {
|
|
26
|
+
let m = createMemOrm()
|
|
27
|
+
let so = WServOrm({ a: { keys } }, m.WOrmMem, './unused', 'db')
|
|
28
|
+
return { so, m }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let RE_TIME = /^\d{4}-\d{2}-\d{2}/
|
|
32
|
+
|
|
33
|
+
let MSG_BOOL = 'invalid opt of procOrm, keepUserId needs a boolean'
|
|
34
|
+
let MSG_MODE = 'invalid opt of procOrm, keepUserId supports only insert'
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
describe('procOrm opt: keepUserId 保留擁有者userId(T2①)', function() {
|
|
38
|
+
|
|
39
|
+
let ins = (so) => (input, opt) => so.procOrm('op', 'a', 'insert', input, opt)
|
|
40
|
+
|
|
41
|
+
it('K01 keepUserId:true: 列上userId(擁有者)保留, userIdUpdate為操作者, timeCreate與timeUpdate照現行補值; 單物件與陣列皆然', async function() {
|
|
42
|
+
let { so, m } = mkSo()
|
|
43
|
+
let p = ins(so)
|
|
44
|
+
let r = await p([{ id: 'r1', name: 'x', userId: 'owner' }], { keepUserId: true })
|
|
45
|
+
assert.deepStrictEqual(r, [{ id: 'r1' }])
|
|
46
|
+
let row = m.rows('a')[0]
|
|
47
|
+
assert.strict.equal(row.userId, 'owner')
|
|
48
|
+
assert.strict.equal(row.userIdUpdate, 'op')
|
|
49
|
+
assert.strict.equal(row.name, 'x')
|
|
50
|
+
assert.ok(RE_TIME.test(row.timeCreate), row.timeCreate)
|
|
51
|
+
assert.strict.equal(row.timeUpdate, row.timeCreate)
|
|
52
|
+
|
|
53
|
+
let r2 = await p({ id: 'r2', name: 'y', userId: 'owner2' }, { keepUserId: true })
|
|
54
|
+
assert.deepStrictEqual(r2, { id: 'r2' })
|
|
55
|
+
let row2 = m.rows('a')[1]
|
|
56
|
+
assert.strict.equal(row2.userId, 'owner2')
|
|
57
|
+
assert.strict.equal(row2.userIdUpdate, 'op')
|
|
58
|
+
assert.ok(RE_TIME.test(row2.timeCreate) && RE_TIME.test(row2.timeUpdate))
|
|
59
|
+
|
|
60
|
+
//ORM之insert實際收到者即上述(非事後改寫)
|
|
61
|
+
let sent = m.callsOf('insert')[0].args[0][0]
|
|
62
|
+
assert.strict.equal(sent.userId, 'owner')
|
|
63
|
+
assert.strict.equal(sent.userIdUpdate, 'op')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('K02 逐列判定: 非空字串保留, 空字串、缺欄、null、undefined、數字、物件、布林皆以操作者補; 同批各列擁有者不同者各自保留', async function() {
|
|
67
|
+
let { so, m } = mkSo()
|
|
68
|
+
let p = ins(so)
|
|
69
|
+
let rows = [
|
|
70
|
+
{ id: 'a1', userId: 'o1' },
|
|
71
|
+
{ id: 'a2' },
|
|
72
|
+
{ id: 'a3', userId: '' },
|
|
73
|
+
{ id: 'a4', userId: null },
|
|
74
|
+
{ id: 'a5', userId: undefined },
|
|
75
|
+
{ id: 'a6', userId: 5 },
|
|
76
|
+
{ id: 'a7', userId: { x: 1 } },
|
|
77
|
+
{ id: 'a8', userId: 'o8' },
|
|
78
|
+
{ id: 'a9', userId: true },
|
|
79
|
+
]
|
|
80
|
+
await p(rows, { keepUserId: true })
|
|
81
|
+
let got = Object.fromEntries(m.rows('a').map((v) => [v.id, v.userId]))
|
|
82
|
+
assert.deepStrictEqual(got, { a1: 'o1', a2: 'op', a3: 'op', a4: 'op', a5: 'op', a6: 'op', a7: 'op', a8: 'o8', a9: 'op' })
|
|
83
|
+
assert.strict.equal(m.rows('a').every((v) => v.userIdUpdate === 'op'), true)
|
|
84
|
+
assert.strict.equal(m.rows('a').every((v) => RE_TIME.test(v.timeCreate) && RE_TIME.test(v.timeUpdate)), true)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('K03 呼叫端給之userIdUpdate、timeCreate、timeUpdate仍被覆寫(照現行, keepUserId只影響userId)', async function() {
|
|
88
|
+
let { so, m } = mkSo()
|
|
89
|
+
await ins(so)([{ id: 'r1', userId: 'owner', userIdUpdate: 'forged', timeCreate: '1999-01-01', timeUpdate: '1999-01-02' }], { keepUserId: true })
|
|
90
|
+
let row = m.rows('a')[0]
|
|
91
|
+
assert.strict.equal(row.userId, 'owner')
|
|
92
|
+
assert.strict.equal(row.userIdUpdate, 'op')
|
|
93
|
+
assert.notStrictEqual(row.timeCreate, '1999-01-01')
|
|
94
|
+
assert.notStrictEqual(row.timeUpdate, '1999-01-02')
|
|
95
|
+
assert.ok(RE_TIME.test(row.timeCreate) && row.timeCreate.slice(0, 4) >= '2026')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
//K04、K04b共用: 預期為固定值(非與自身對照)
|
|
99
|
+
async function checkSameAsCurrent(args) {
|
|
100
|
+
let { so, m } = mkSo()
|
|
101
|
+
await so.procOrm('op', 'a', 'insert', [{ id: 'r1', name: 'x', userId: 'owner' }, { id: 'r2' }], ...args)
|
|
102
|
+
let rows = m.rows('a')
|
|
103
|
+
assert.deepStrictEqual(rows.map((v) => [v.id, v.userId, v.userIdUpdate]), [['r1', 'op', 'op'], ['r2', 'op', 'op']], JSON.stringify(args))
|
|
104
|
+
let sent = m.callsOf('insert')[0].args[0]
|
|
105
|
+
assert.deepStrictEqual(sent.map((v) => v.userId), ['op', 'op'])
|
|
106
|
+
assert.strict.equal(m.callsOf('insert').length, 1)
|
|
107
|
+
assert.strict.equal(m.callsOf('select').length, 0)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
it('K04 未給opt、opt為undefined或null、{}、{mustExist:false}: 行為與現行完全相同(列上userId被操作者覆寫), ORM收到之列同(修前(HEAD)即綠, 為既有行為不變之獨立證據)', async function() {
|
|
111
|
+
for (let args of [[], [undefined], [null], [{}], [{ mustExist: false }]]) {
|
|
112
|
+
await checkSameAsCurrent(args)
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('K04b {keepUserId:false}(單獨或與mustExist:false併用): 行為與現行完全相同(列上userId被操作者覆寫)', async function() {
|
|
117
|
+
for (let args of [[{ keepUserId: false }], [{ keepUserId: false, mustExist: false }]]) {
|
|
118
|
+
await checkSameAsCurrent(args)
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('K05 繼承或不可列舉之userId視為未提供(與主鍵只認自有可列舉資料屬性同), 以操作者補; 自有可列舉者保留', async function() {
|
|
123
|
+
let { so, m } = mkSo()
|
|
124
|
+
let inh = Object.create({ userId: 'proto-owner' })
|
|
125
|
+
inh.id = 'i1'
|
|
126
|
+
let hid = { id: 'i2' }
|
|
127
|
+
Object.defineProperty(hid, 'userId', { value: 'hidden-owner', enumerable: false, configurable: true, writable: true })
|
|
128
|
+
let own = { id: 'i3', userId: 'own-owner' }
|
|
129
|
+
await ins(so)([inh, hid, own], { keepUserId: true })
|
|
130
|
+
let got = Object.fromEntries(m.rows('a').map((v) => [v.id, v.userId]))
|
|
131
|
+
assert.deepStrictEqual(got, { i1: 'op', i2: 'op', i3: 'own-owner' })
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('K06 表無userId欄: keepUserId:true無效果(不補userId亦不刪除列上之userId, 同未帶opt); userIdUpdate仍為操作者', async function() {
|
|
135
|
+
let { so, m } = mkSo(['id', 'name', 'userIdUpdate', 'timeUpdate'])
|
|
136
|
+
await ins(so)([{ id: 'r1', userId: 'x' }], { keepUserId: true })
|
|
137
|
+
await ins(so)([{ id: 'r2', userId: 'x' }])
|
|
138
|
+
let rows = m.rows('a')
|
|
139
|
+
assert.deepStrictEqual(rows.map((v) => [v.id, v.userId, v.userIdUpdate]), [['r1', 'x', 'op'], ['r2', 'x', 'op']])
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('K07 保留之userId仍經clearXSS(與其他字串欄位同); 單純空白字串屬非空字串而保留', async function() {
|
|
143
|
+
let { so, m } = mkSo()
|
|
144
|
+
await ins(so)([{ id: 'r1', userId: 'o<script>alert(1)</script>wner' }, { id: 'r2', userId: ' ' }], { keepUserId: true })
|
|
145
|
+
let rows = m.rows('a')
|
|
146
|
+
assert.ok(rows[0].userId.indexOf('<script>') < 0, rows[0].userId)
|
|
147
|
+
assert.ok(rows[0].userId !== 'op' && rows[0].userId.indexOf('wner') >= 0, rows[0].userId)
|
|
148
|
+
assert.strict.equal(rows[1].userId, ' ')
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('K08 keepUserId非布林(字串、數字、null、undefined值、陣列、物件、函數、存取器) → 固定字串reject, ORM 0次呼叫', async function() {
|
|
152
|
+
let { so, m } = mkSo()
|
|
153
|
+
let acc = {}
|
|
154
|
+
Object.defineProperty(acc, 'keepUserId', { get: () => true, enumerable: true })
|
|
155
|
+
let accNoVal = {}
|
|
156
|
+
Object.defineProperty(accNoVal, 'keepUserId', { get: undefined, set: undefined, enumerable: true })
|
|
157
|
+
let cases = [{ keepUserId: 'true' }, { keepUserId: 1 }, { keepUserId: 0 }, { keepUserId: null }, { keepUserId: undefined }, { keepUserId: [] }, { keepUserId: {} }, { keepUserId: () => true }, acc, accNoVal]
|
|
158
|
+
for (let opt of cases) {
|
|
159
|
+
let err = await quiet(() => getReject(ins(so)([{ id: 'n1', userId: 'o' }], opt)))
|
|
160
|
+
assert.strict.equal(err, MSG_BOOL, String(JSON.stringify(opt)))
|
|
161
|
+
}
|
|
162
|
+
assert.strict.equal(m.calls.length, 0)
|
|
163
|
+
assert.strict.equal(m.tables.a === undefined || m.tables.a.size === 0, true)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('K09 keepUserId:true只適用insert: save、del、select、delAll、mix與無效mode皆固定字串reject且ORM 0次呼叫、庫內不變; keepUserId:false於各mode無效果', async function() {
|
|
167
|
+
let { so, m } = mkSo()
|
|
168
|
+
await so.procOrm('op', 'a', 'save', [{ id: 'r1', name: 'A', userId: 'o' }])
|
|
169
|
+
let before = JSON.stringify(m.rows('a'))
|
|
170
|
+
m.reset()
|
|
171
|
+
let inputs = { save: [{ id: 'r1', name: 'B', userId: 'o' }], del: [{ id: 'r1' }], select: {}, delAll: {}, mix: { add: [{ id: 'm1' }] }, bogus: [] }
|
|
172
|
+
for (let mode of Object.keys(inputs)) {
|
|
173
|
+
let err = await quiet(() => getReject(so.procOrm('op', 'a', mode, inputs[mode], { keepUserId: true })))
|
|
174
|
+
assert.strict.equal(err, MSG_MODE, mode)
|
|
175
|
+
}
|
|
176
|
+
assert.strict.equal(m.calls.length, 0)
|
|
177
|
+
assert.strict.equal(JSON.stringify(m.rows('a')), before)
|
|
178
|
+
|
|
179
|
+
//keepUserId:false於各mode無效果(沿用既有各mode之行為與回傳)
|
|
180
|
+
let f = { keepUserId: false }
|
|
181
|
+
assert.deepStrictEqual((await so.procOrm('op', 'a', 'select', {}, f)).map((v) => v.id), ['r1'])
|
|
182
|
+
assert.deepStrictEqual(await so.procOrm('op', 'a', 'save', [{ id: 'r1', name: 'B', userId: 'o' }], f), [{ id: 'r1' }])
|
|
183
|
+
assert.deepStrictEqual(await so.procOrm('op', 'a', 'mix', { add: [{ id: 'm1' }] }, f), [{ id: 'm1' }])
|
|
184
|
+
assert.deepStrictEqual(await so.procOrm('op', 'a', 'del', [{ id: 'm1' }], f), [{ n: 1, nDeleted: 1, ok: 1 }])
|
|
185
|
+
assert.strict.equal(await quiet(() => getReject(so.procOrm('op', 'a', 'bogus', [], f))), 'invalid mode: bogus')
|
|
186
|
+
assert.deepStrictEqual(await so.procOrm('op', 'a', 'delAll', {}, f), { n: 1, nDeleted: 1, ok: 1 })
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('K10 與mustExist及其他鍵之組合: 型別先於mode檢查; 未知鍵(含拼錯、symbol鍵)reject; keepUserId:true加mustExist:false於insert可用', async function() {
|
|
190
|
+
let { so, m } = mkSo()
|
|
191
|
+
await so.procOrm('op', 'a', 'save', [{ id: 'r1', name: 'A' }])
|
|
192
|
+
m.reset()
|
|
193
|
+
let sym = { keepUserId: true, [Symbol('x')]: 1 }
|
|
194
|
+
let cases = [
|
|
195
|
+
['insert', { mustExist: true, keepUserId: true }, 'invalid opt of procOrm, mustExist supports only save and del'],
|
|
196
|
+
['save', { mustExist: true, keepUserId: true }, MSG_MODE],
|
|
197
|
+
['save', { mustExist: true, keepUserId: 'x' }, MSG_BOOL],
|
|
198
|
+
['insert', { mustExist: 'x', keepUserId: true }, 'invalid opt of procOrm, mustExist needs a boolean'],
|
|
199
|
+
['insert', { keepUserId: true, extra: 1 }, 'invalid opt of procOrm, unsupported key'],
|
|
200
|
+
['insert', { keepuserid: true }, 'invalid opt of procOrm, unsupported key'],
|
|
201
|
+
['insert', { keepUserID: true }, 'invalid opt of procOrm, unsupported key'],
|
|
202
|
+
['insert', sym, 'invalid opt of procOrm, unsupported key'],
|
|
203
|
+
['insert', 'keepUserId', 'invalid opt of procOrm, need an object'],
|
|
204
|
+
['insert', [], 'invalid opt of procOrm, need an object'],
|
|
205
|
+
]
|
|
206
|
+
for (let [mode, opt, msg] of cases) {
|
|
207
|
+
let input = mode === 'insert' ? [{ id: 'n1', userId: 'o' }] : [{ id: 'r1', name: 'Z' }]
|
|
208
|
+
let err = await quiet(() => getReject(so.procOrm('op', 'a', mode, input, opt)))
|
|
209
|
+
assert.strict.equal(err, msg, `${mode} ${Object.keys(opt).map(String).join(',')}`)
|
|
210
|
+
}
|
|
211
|
+
assert.strict.equal(m.calls.length, 0)
|
|
212
|
+
let r = await so.procOrm('op', 'a', 'insert', [{ id: 'n2', userId: 'o2' }], { keepUserId: true, mustExist: false })
|
|
213
|
+
assert.deepStrictEqual(r, [{ id: 'n2' }])
|
|
214
|
+
assert.strict.equal(m.rows('a').find((v) => v.id === 'n2').userId, 'o2')
|
|
215
|
+
//mustExist既有行為不受影響: save + mustExist:true仍只改不增
|
|
216
|
+
let err = await quiet(() => getReject(so.procOrm('op', 'a', 'save', [{ id: 'ghost' }], { mustExist: true })))
|
|
217
|
+
assert.strict.equal(err, 'rowNotFound')
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('K11 直接使用getMapOrm(不經getProc)亦同: 保留、預設覆寫、無效opt於ORM呼叫之前reject', async function() {
|
|
221
|
+
let m = createMemOrm()
|
|
222
|
+
let ds = { a: { keys: KEYS } }
|
|
223
|
+
let mapOrm = getMapOrm(ds, { a: m.WOrmMem({ cl: 'a' }) })
|
|
224
|
+
await mapOrm('op', 'a', 'insert', [{ id: 'r1', userId: 'o1' }, { id: 'r2' }], { keepUserId: true })
|
|
225
|
+
await mapOrm('op', 'a', 'insert', [{ id: 'r3', userId: 'o3' }])
|
|
226
|
+
assert.deepStrictEqual(m.rows('a').map((v) => [v.id, v.userId, v.userIdUpdate]), [['r1', 'o1', 'op'], ['r2', 'op', 'op'], ['r3', 'op', 'op']])
|
|
227
|
+
m.reset()
|
|
228
|
+
assert.strict.equal(await quiet(() => getReject(mapOrm('op', 'a', 'insert', [{ id: 'x' }], { keepUserId: 1 }))), MSG_BOOL)
|
|
229
|
+
assert.strict.equal(await quiet(() => getReject(mapOrm('op', 'a', 'save', [{ id: 'r1' }], { keepUserId: true }))), MSG_MODE)
|
|
230
|
+
assert.strict.equal(m.calls.length, 0)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
it('K12 無效opt先於擴充函數驗證: 註冊有副作用之check與前處理時, keepUserId非布林與用於非insert皆固定字串拒收, 擴充函數0次呼叫、ORM 0次呼叫; 有效opt時check照常先執行', async function() {
|
|
234
|
+
let { so, m } = mkSo()
|
|
235
|
+
let called = []
|
|
236
|
+
so.addFunCheck(() => {
|
|
237
|
+
called.push('check')
|
|
238
|
+
throw new Error('check would reject first')
|
|
239
|
+
})
|
|
240
|
+
so.addFunPreProcessing(async (x) => {
|
|
241
|
+
called.push('pre')
|
|
242
|
+
await so.woItems.a.save({ id: 'side-effect' })
|
|
243
|
+
return { input: x.input }
|
|
244
|
+
})
|
|
245
|
+
await wait(5)
|
|
246
|
+
m.reset()
|
|
247
|
+
for (let opt of [{ keepUserId: 'true' }, { keepUserId: 1 }, { keepuserid: true }]) {
|
|
248
|
+
let err = await quiet(() => getReject(so.procOrm('op', 'a', 'insert', [{ id: 'r1' }], opt)))
|
|
249
|
+
assert.ok(/^invalid opt of procOrm, /.test(err), String(err))
|
|
250
|
+
}
|
|
251
|
+
for (let mode of ['save', 'del', 'select', 'delAll', 'mix']) {
|
|
252
|
+
let err = await quiet(() => getReject(so.procOrm('op', 'a', mode, mode === 'mix' ? { add: [] } : [], { keepUserId: true })))
|
|
253
|
+
assert.strict.equal(err, MSG_MODE, mode)
|
|
254
|
+
}
|
|
255
|
+
assert.deepStrictEqual(called, [])
|
|
256
|
+
assert.strict.equal(m.calls.length, 0)
|
|
257
|
+
let err2 = await quiet(() => getReject(so.procOrm('op', 'a', 'insert', [{ id: 'r1' }], { keepUserId: true })))
|
|
258
|
+
assert.strict.equal(err2.message, 'check would reject first')
|
|
259
|
+
assert.deepStrictEqual(called, ['check'])
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it('K13 擴充函數(check、前處理、後處理)之參數物件不含opt(鍵集合同舊介面); 前處理改寫後之input為保留判定之依據', async function() {
|
|
263
|
+
let { so, m } = mkSo()
|
|
264
|
+
let seen = []
|
|
265
|
+
so.addFunCheck((x) => {
|
|
266
|
+
seen.push(`check:${Object.keys(x).sort().join(',')}`)
|
|
267
|
+
})
|
|
268
|
+
so.addFunPreProcessing((x) => {
|
|
269
|
+
seen.push(`pre:${Object.keys(x).sort().join(',')}`)
|
|
270
|
+
return { input: x.input.map((v) => ({ ...v, userId: 'hook-owner' })) }
|
|
271
|
+
})
|
|
272
|
+
so.addFunPostProcessing((x) => {
|
|
273
|
+
seen.push(`post:${Object.keys(x).sort().join(',')}`)
|
|
274
|
+
return x.output
|
|
275
|
+
})
|
|
276
|
+
await wait(5)
|
|
277
|
+
let r = await so.procOrm('op', 'a', 'insert', [{ id: 'r1' }], { keepUserId: true })
|
|
278
|
+
assert.deepStrictEqual(r, [{ id: 'r1' }])
|
|
279
|
+
let base = 'input,mode,userId,woName'
|
|
280
|
+
assert.deepStrictEqual(seen, [`check:${base}`, `pre:${base}`, `post:input,mode,output,userId,woName`])
|
|
281
|
+
let row = m.rows('a')[0]
|
|
282
|
+
assert.strict.equal(row.userId, 'hook-owner', '前處理補之userId為進入ORM前之列上值, 被保留')
|
|
283
|
+
assert.strict.equal(row.userIdUpdate, 'op')
|
|
284
|
+
//未帶opt時前處理補之userId同樣被操作者覆寫(現行)
|
|
285
|
+
await so.procOrm('op', 'a', 'insert', [{ id: 'r2' }])
|
|
286
|
+
assert.strict.equal(m.rows('a')[1].userId, 'op')
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('K14 save現行不覆寫userId(只補userIdUpdate、timeUpdate): 列上userId原樣寫入, 故keepUserId不適用save(K09拒收); 本案鎖定save之現行行為', async function() {
|
|
290
|
+
let { so, m } = mkSo()
|
|
291
|
+
await so.procOrm('creator', 'a', 'insert', [{ id: 'r1', name: 'A', userId: 'owner' }], { keepUserId: true })
|
|
292
|
+
assert.strict.equal(m.rows('a')[0].userId, 'owner')
|
|
293
|
+
await so.procOrm('editor', 'a', 'save', [{ id: 'r1', name: 'B', userId: 'owner' }])
|
|
294
|
+
let row = m.rows('a')[0]
|
|
295
|
+
assert.strict.equal(row.userId, 'owner')
|
|
296
|
+
assert.strict.equal(row.userIdUpdate, 'editor')
|
|
297
|
+
assert.strict.equal(row.name, 'B')
|
|
298
|
+
//save之列未帶userId: 不補操作者(現行), 庫內以ORM之save語意合併(memOrm為合併)而保留擁有者
|
|
299
|
+
await so.procOrm('editor2', 'a', 'save', [{ id: 'r1', name: 'C' }])
|
|
300
|
+
let row2 = m.rows('a')[0]
|
|
301
|
+
assert.strict.equal(row2.userId, 'owner')
|
|
302
|
+
assert.strict.equal(row2.userIdUpdate, 'editor2')
|
|
303
|
+
let sent = m.callsOf('save').map((c) => c.args[0][0])
|
|
304
|
+
assert.strict.equal('userId' in sent[1], false, 'save未對未帶userId之列補userId')
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
})
|