w-orm-reladb 1.1.9 → 1.1.11
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 +100 -28
- package/dist/w-orm-reladb.umd.js +2 -2
- package/dist/w-orm-reladb.umd.js.map +1 -1
- package/docs/WOrmReladb.html +62 -32
- package/docs/WOrmReladb.mjs.html +101 -29
- package/docs/cvFind.mjs.html +253 -410
- package/docs/global.html +336 -12
- package/docs/importModels.mjs.html +2 -2
- package/docs/index.html +2 -2
- package/g-mssql-transaction-commit.mjs +1 -1
- package/g-mssql-transaction-rollback.mjs +1 -1
- package/g-mssql.mjs +4 -4
- package/g-sqlite-encryption-transaction-commit.mjs +1 -1
- package/g-sqlite-encryption.mjs +4 -4
- package/g-sqlite-transaction-commit.mjs +1 -1
- package/g-sqlite-transaction-rollback.mjs +1 -1
- package/g-sqlite.mjs +4 -4
- package/models/tdate.js +38 -0
- package/models/tdatei.js +20 -0
- package/models/tfind.js +50 -0
- package/package.json +3 -3
- package/src/WOrmReladb.mjs +99 -27
- package/src/cvFind.mjs +251 -408
- package/test/api-date-write.mjs +467 -0
- package/test/api-find-whitelist.mjs +390 -0
- package/test/api-mssql.test.mjs +4 -0
- package/test/api-sqlite.test.mjs +4 -0
- package/test/api-suite.mjs +95 -136
- package/test/tools/withTz.mjs +43 -0
- package/test/unit-cvfind.test.mjs +241 -218
- package/test/unit-date-write.test.mjs +457 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import cloneDeep from 'lodash-es/cloneDeep.js'
|
|
3
|
+
import Sequelize from 'sequelize'
|
|
4
|
+
import wo from '../src/WOrmReladb.mjs'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
//本檔為mssql與sqlite共用之寫入側DATE範圍檢核測試套組(CLAUDE_rulebook.md之R12), 由api-sqlite.test.mjs與api-mssql.test.mjs引入
|
|
8
|
+
//規格句(R12, dspec〈一之三〉8.): 以文字儲存日期之後端, 寫入拒收範圍外Date——DATE/DATEONLY欄之值須為年份在1–9999之有效時間,
|
|
9
|
+
//範圍外與無效時間(Invalid Date、NaN、垃圾字串…, sqlite會存為字典序大於任何年份之文字Invalid date)皆使insert、insertBulk、save整批reject且資料庫不變;
|
|
10
|
+
//訊息不回顯值; error事件於reject前恰一次、change不發; 兩方言行為一致(mssql於送出前即拒); 檢查之值即使用之值(具狀態之valueOf不得於二次求值時存入範圍外年份)
|
|
11
|
+
//檢核優先序: 已關閉 → 初始化 → procPk(缺主鍵) → normPks(主鍵型別不符) → 日期檢核
|
|
12
|
+
//model為models/tdate.js: d、d2為DATE, o為DATEONLY, n為STRING(非日期欄不檢); models/tdatei.js: 整數主鍵 + DATE欄d(測檢核優先序)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
let msgOf = (err) => String(err && err.message !== undefined ? err.message : err)
|
|
16
|
+
|
|
17
|
+
let settle = (pm) => pm.then((v) => ({ ok: true, v })).catch((err) => ({ err }))
|
|
18
|
+
|
|
19
|
+
let byId = (a, b) => (a.id < b.id ? -1 : 1)
|
|
20
|
+
|
|
21
|
+
//期望之訊息: 不回顯值, 只含索引與欄名
|
|
22
|
+
let want = (i, col) => `invalid data: date is not a valid time in years 1-9999 at [data[${i}].${col}]`
|
|
23
|
+
|
|
24
|
+
//normD, 讀回之DATE值統一為ISO字串: sqlite讀回為文字'YYYY-MM-DD HH:mm:ss.SSS +00:00', mssql為Date
|
|
25
|
+
let normD = (x) => {
|
|
26
|
+
if (x === null || x === undefined) {
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
if (x instanceof Date) {
|
|
30
|
+
return x.toISOString()
|
|
31
|
+
}
|
|
32
|
+
let m = String(x).match(/^(\d{4,6})-(\d{2})-(\d{2}) (\d{2}:\d{2}:\d{2}\.\d{3}) \+00:00$/)
|
|
33
|
+
return m ? `${m[1]}-${m[2]}-${m[3]}T${m[4]}Z` : String(x)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//normO, 讀回之DATEONLY值統一為'YYYY-MM-DD': sqlite為文字, mssql可能為Date(UTC午夜)
|
|
37
|
+
let normO = (x) => {
|
|
38
|
+
if (x === null || x === undefined) {
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
if (x instanceof Date) {
|
|
42
|
+
return x.toISOString().slice(0, 10)
|
|
43
|
+
}
|
|
44
|
+
return String(x).slice(0, 10)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 定義共用測試套組
|
|
50
|
+
*
|
|
51
|
+
* @param {String} label 輸入後端名稱字串('sqlite'或'mssql'), 供describe標題使用
|
|
52
|
+
* @param {Function} getOpt 輸入取得建構設定之函數, 於hook內呼叫以取得當下有效之連線資訊
|
|
53
|
+
*/
|
|
54
|
+
export function defineDateWriteSuites(label, getOpt) {
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
describe(`${label} 寫入側DATE範圍: 入口 × 拒收值 × 欄位`, function() {
|
|
58
|
+
|
|
59
|
+
let w = null
|
|
60
|
+
let ins = null
|
|
61
|
+
let cs = null
|
|
62
|
+
|
|
63
|
+
//種子: 3列合法數據, 供「資料庫不變」與save更新既有之用
|
|
64
|
+
let seed = () => [
|
|
65
|
+
{ id: 's1', n: 'a', d: new Date('2000-01-01T00:00:00Z'), d2: null, o: '2000-01-01' },
|
|
66
|
+
{ id: 's2', n: 'b', d: new Date('2010-06-15T12:30:00Z'), d2: new Date('2011-01-01T00:00:00Z'), o: null },
|
|
67
|
+
{ id: 's3', n: 'c', d: null, d2: null, o: '2020-12-31' },
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
let dump = async () => {
|
|
71
|
+
let rs = await w.select({}, cs)
|
|
72
|
+
return rs.sort(byId)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let reset = async () => {
|
|
76
|
+
await w.delAll({}, cs)
|
|
77
|
+
let r = await w.insert(seed(), cs)
|
|
78
|
+
assert.strictEqual(r.nInserted, 3)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
before(async function() {
|
|
82
|
+
w = wo(getOpt({ cl: 'tdate' }))
|
|
83
|
+
await w.createStorage()
|
|
84
|
+
ins = await w.init()
|
|
85
|
+
cs = { instance: ins }
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
after(async function() {
|
|
89
|
+
await ins.close()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
//入口: [名稱, 事件mode, 呼叫函數, 是否針對既有主鍵]
|
|
93
|
+
let entries = [
|
|
94
|
+
['insert', 'insert', (rs) => w.insert(rs, cs), false],
|
|
95
|
+
['insertBulk', 'insertBulk', (rs) => w.insertBulk(rs, cs), false],
|
|
96
|
+
['save(自動插入)', 'save', (rs) => w.save(rs, { ...cs, autoInsert: true }), false],
|
|
97
|
+
['save(更新既有)', 'save', (rs) => w.save(rs, { ...cs, autoInsert: true }), true],
|
|
98
|
+
['save(autoInsert:false且主鍵不存在)', 'save', (rs) => w.save(rs, { ...cs, autoInsert: false }), false],
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
//mk, 產生3筆記錄, patches為[[筆索引, 欄名, 值]…]; 其餘欄與其餘筆皆合法
|
|
102
|
+
let mk = (existing, patches) => {
|
|
103
|
+
let ids = existing ? ['s1', 's2', 's3'] : ['x1', 'x2', 'x3']
|
|
104
|
+
let rs = ids.map((id) => ({ id, n: 'new', d: new Date('2030-01-01T00:00:00Z'), o: '2030-01-01' }))
|
|
105
|
+
for (let [p, col, v] of patches) {
|
|
106
|
+
rs[p][col] = v
|
|
107
|
+
}
|
|
108
|
+
return rs
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
//runReject, 呼叫入口並斷言: reject、訊息(不回顯值)、error事件於reject前恰一次且mode/err一致、change不發、資料庫逐列不變、呼叫端數據不變
|
|
112
|
+
let runReject = async (entry, rs, msg) => {
|
|
113
|
+
let [, mode, fn] = entry
|
|
114
|
+
await reset()
|
|
115
|
+
let before = await dump()
|
|
116
|
+
let snap = cloneDeep(rs)
|
|
117
|
+
let evs = []
|
|
118
|
+
let errs = []
|
|
119
|
+
let fnE = (m, data, e) => {
|
|
120
|
+
evs.push(`error:${m}`)
|
|
121
|
+
errs.push(e)
|
|
122
|
+
}
|
|
123
|
+
let fnC = (m) => evs.push(`change:${m}`)
|
|
124
|
+
w.on('error', fnE)
|
|
125
|
+
w.on('change', fnC)
|
|
126
|
+
let r
|
|
127
|
+
try {
|
|
128
|
+
r = await settle(fn(rs))
|
|
129
|
+
evs.push('reject')
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
w.off('error', fnE)
|
|
133
|
+
w.off('change', fnC)
|
|
134
|
+
}
|
|
135
|
+
assert.ok(r.err !== undefined, `未reject: ${JSON.stringify(r.v)}`)
|
|
136
|
+
assert.strictEqual(msgOf(r.err), msg)
|
|
137
|
+
assert.deepStrictEqual(evs, [`error:${mode}`, 'reject'], 'error事件於reject前恰一次, change不發')
|
|
138
|
+
assert.deepStrictEqual(errs, [msg], 'error事件之err與reject訊息一致')
|
|
139
|
+
assert.deepStrictEqual(await dump(), before, '資料庫逐列不變')
|
|
140
|
+
assert.deepStrictEqual(rs, snap, '呼叫端數據不變')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---- DATE欄之拒收值 × 5入口: 中間筆(第2筆)違規, 其餘合法筆亦不得寫入 ----
|
|
144
|
+
let badDate = [
|
|
145
|
+
['Date 年0末刻', () => new Date('0000-12-31T23:59:59.999Z')],
|
|
146
|
+
['Date 年10000首刻', () => new Date('+010000-01-01T00:00:00.000Z')],
|
|
147
|
+
['Date 8.64e15', () => new Date(8.64e15)],
|
|
148
|
+
['Date -8.64e15', () => new Date(-8.64e15)],
|
|
149
|
+
['epoch 8.64e15', () => 8.64e15],
|
|
150
|
+
['epoch -8.64e15', () => -8.64e15],
|
|
151
|
+
['ISO字串 年0末刻', () => '0000-12-31T23:59:59.999Z'],
|
|
152
|
+
['ISO字串 年10000首刻', () => '+010000-01-01T00:00:00Z'],
|
|
153
|
+
['Number包裝物件 8.64e15', () => Object(8.64e15)],
|
|
154
|
+
]
|
|
155
|
+
for (let entry of entries) {
|
|
156
|
+
for (let [lab, gen] of badDate) {
|
|
157
|
+
it(`${entry[0]} × DATE欄 ${lab}: 整批reject、資料不變`, async function() {
|
|
158
|
+
await runReject(entry, mk(entry[3], [[1, 'd', gen()]]), want(1, 'd'))
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ---- DATEONLY欄之拒收值 × 5入口 ----
|
|
164
|
+
let badOnly = [
|
|
165
|
+
['Date 8.64e15', () => new Date(8.64e15)],
|
|
166
|
+
['Date -8.64e15', () => new Date(-8.64e15)],
|
|
167
|
+
['Date 年10000年中', () => new Date('+010000-06-15T12:00:00Z')],
|
|
168
|
+
['Date 年0年中', () => new Date('0000-06-15T12:00:00Z')],
|
|
169
|
+
['epoch 8.64e15', () => 8.64e15],
|
|
170
|
+
['ISO字串 年10000', () => '+010000-01-01'],
|
|
171
|
+
['ISO字串 年0', () => '0000-12-31'],
|
|
172
|
+
]
|
|
173
|
+
for (let entry of entries) {
|
|
174
|
+
for (let [lab, gen] of badOnly) {
|
|
175
|
+
it(`${entry[0]} × DATEONLY欄 ${lab}: 整批reject、資料不變`, async function() {
|
|
176
|
+
await runReject(entry, mk(entry[3], [[1, 'o', gen()]]), want(1, 'o'))
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ---- 無效時間(sqlite會存為文字Invalid date, mssql由資料庫報錯)亦整批reject × 5入口 ----
|
|
182
|
+
let badInvalid = [
|
|
183
|
+
['Invalid Date', () => new Date(NaN)],
|
|
184
|
+
['NaN', () => NaN],
|
|
185
|
+
['空字串', () => ''],
|
|
186
|
+
['垃圾字串', () => 'garbage'],
|
|
187
|
+
['超出Date範圍之epoch 1e16', () => 1e16],
|
|
188
|
+
['false', () => false],
|
|
189
|
+
]
|
|
190
|
+
for (let entry of entries) {
|
|
191
|
+
for (let [lab, gen] of badInvalid) {
|
|
192
|
+
it(`${entry[0]} × DATE欄 無效時間 ${lab}: 整批reject、資料不變`, async function() {
|
|
193
|
+
await runReject(entry, mk(entry[3], [[1, 'd', gen()]]), want(1, 'd'))
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
it(`${entry[0]} × DATEONLY欄 無效時間 垃圾字串: 整批reject、資料不變`, async function() {
|
|
197
|
+
await runReject(entry, mk(entry[3], [[1, 'o', 'garbage']]), want(1, 'o'))
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---- 位置與多欄 × 5入口 ----
|
|
202
|
+
let bad = () => new Date(8.64e15)
|
|
203
|
+
let positions = [
|
|
204
|
+
['首筆', (e) => mk(e, [[0, 'd', bad()]]), want(0, 'd')],
|
|
205
|
+
['末筆', (e) => mk(e, [[2, 'd', bad()]]), want(2, 'd')],
|
|
206
|
+
['第二個DATE欄(d2)', (e) => mk(e, [[1, 'd2', bad()]]), want(1, 'd2')],
|
|
207
|
+
['同筆多欄違規: 回報model屬性定義順序之第一個(d先於d2先於o)', (e) => mk(e, [[1, 'o', bad()], [1, 'd2', bad()], [1, 'd', bad()]]), want(1, 'd')],
|
|
208
|
+
['同筆d2與o違規: 回報d2', (e) => mk(e, [[1, 'o', bad()], [1, 'd2', bad()]]), want(1, 'd2')],
|
|
209
|
+
['多筆違規: 回報索引最小者', (e) => mk(e, [[2, 'd', bad()], [1, 'd2', bad()]]), want(1, 'd2')],
|
|
210
|
+
]
|
|
211
|
+
for (let entry of entries) {
|
|
212
|
+
for (let [lab, gen, msg] of positions) {
|
|
213
|
+
it(`${entry[0]} × 位置 ${lab}`, async function() {
|
|
214
|
+
await runReject(entry, gen(entry[3]), msg)
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ---- 單一物件(非陣列)輸入 × 5入口 ----
|
|
220
|
+
for (let entry of entries) {
|
|
221
|
+
it(`${entry[0]} × 單一物件(非陣列): 路徑索引為0`, async function() {
|
|
222
|
+
let one = mk(entry[3], [[0, 'd', bad()]])[0]
|
|
223
|
+
await runReject(entry, one, want(0, 'd'))
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ---- 接受值 × 5入口: 邊界內合法、讀回等於寫入之值 ----
|
|
228
|
+
let okCases = [
|
|
229
|
+
['Date 年1首刻', { d: new Date('0001-01-01T00:00:00.000Z') }, { d: '0001-01-01T00:00:00.000Z' }],
|
|
230
|
+
['Date 年9999末刻', { d: new Date('9999-12-31T23:59:59.999Z') }, { d: '9999-12-31T23:59:59.999Z' }],
|
|
231
|
+
['epoch 年1首刻與年9999末刻(d與d2)', { d: Date.parse('0001-01-01T00:00:00.000Z'), d2: Date.parse('9999-12-31T23:59:59.999Z') }, { d: '0001-01-01T00:00:00.000Z', d2: '9999-12-31T23:59:59.999Z' }],
|
|
232
|
+
['ISO字串 年1首刻與年9999末刻', { d: '0001-01-01T00:00:00.000Z', d2: '9999-12-31T23:59:59.999Z' }, { d: '0001-01-01T00:00:00.000Z', d2: '9999-12-31T23:59:59.999Z' }],
|
|
233
|
+
['DATEONLY 年1首日(字串)', { o: '0001-01-01' }, { o: '0001-01-01' }],
|
|
234
|
+
['DATEONLY 年9999末日(字串)', { o: '9999-12-31' }, { o: '9999-12-31' }],
|
|
235
|
+
['DATEONLY Date年中', { o: new Date('2026-06-15T12:00:00Z') }, { o: '2026-06-15' }],
|
|
236
|
+
['null與非日期欄形似範圍外日期之字串', { d: null, d2: null, o: null, n: '275760-09-13 +275760-09-13T00:00:00.000Z' }, { d: null, d2: null, o: null, n: '275760-09-13 +275760-09-13T00:00:00.000Z' }],
|
|
237
|
+
]
|
|
238
|
+
for (let entry of entries) {
|
|
239
|
+
if (entry[0] === 'save(autoInsert:false且主鍵不存在)') {
|
|
240
|
+
continue //此入口不會寫入, 接受側以另案斷言resolve
|
|
241
|
+
}
|
|
242
|
+
for (let [lab, patch, exp] of okCases) {
|
|
243
|
+
it(`${entry[0]} × 接受 ${lab}`, async function() {
|
|
244
|
+
await reset()
|
|
245
|
+
let ids = entry[3] ? ['s1', 's2', 's3'] : ['x1', 'x2', 'x3']
|
|
246
|
+
let rs = ids.map((id, k) => ({ id, ...(k === 1 ? patch : { n: 'other' }) }))
|
|
247
|
+
let r = await settle(entry[2](rs))
|
|
248
|
+
assert.ok(r.err === undefined, `不應reject: ${msgOf(r.err)}`)
|
|
249
|
+
let row = (await dump()).find((v) => v.id === ids[1])
|
|
250
|
+
for (let [k, v] of Object.entries(exp)) {
|
|
251
|
+
let got = k === 'o' ? normO(row[k]) : (k === 'n' ? row[k] : normD(row[k]))
|
|
252
|
+
assert.strictEqual(got, v, `${k} 讀回`)
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
it('save(autoInsert:false且主鍵不存在)接受合法邊界值: resolve且不寫入', async function() {
|
|
259
|
+
await reset()
|
|
260
|
+
let before = await dump()
|
|
261
|
+
let r = await settle(w.save([{ id: 'x9', d: new Date('9999-12-31T23:59:59.999Z') }], { ...cs, autoInsert: false }))
|
|
262
|
+
assert.ok(r.err === undefined, msgOf(r.err))
|
|
263
|
+
assert.deepStrictEqual(r.v, [{ n: 0, nInserted: 0, nModified: 0, ok: 1 }])
|
|
264
|
+
assert.deepStrictEqual(await dump(), before)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('無日期值或日期欄為undefined之記錄不受影響', async function() {
|
|
268
|
+
await reset()
|
|
269
|
+
let r = await w.insert([{ id: 'u1', n: 'x' }, { id: 'u2', d: undefined, o: undefined }], cs)
|
|
270
|
+
assert.deepStrictEqual(r, { n: 2, nInserted: 2, ok: 1 })
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
describe(`${label} 寫入側DATE範圍: 情境與接縫`, function() {
|
|
277
|
+
|
|
278
|
+
let w = null
|
|
279
|
+
let ins = null
|
|
280
|
+
let cs = null
|
|
281
|
+
|
|
282
|
+
before(async function() {
|
|
283
|
+
w = wo(getOpt({ cl: 'tdate' }))
|
|
284
|
+
await w.createStorage()
|
|
285
|
+
ins = await w.init()
|
|
286
|
+
cs = { instance: ins }
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
after(async function() {
|
|
290
|
+
await ins.close()
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
beforeEach(async function() {
|
|
294
|
+
await w.delAll({}, cs)
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
it('修前紅之原始場景: 哨兵new Date(8.64e15)寫入即reject, 其後delAll({d:{$lt:日期}})只刪過期列, 永不過期之替代寫法(9999年末與null)不被刪', async function() {
|
|
298
|
+
//規格句(R12): 「永不過期」哨兵寫入範圍外Date會於sqlite以文字比較被$lt誤中而遭delAll刪除; 現於寫入即拒收
|
|
299
|
+
let r = await settle(w.insert({ id: 'sentinel', d: new Date(8.64e15) }, cs))
|
|
300
|
+
assert.ok(r.err !== undefined, '哨兵須reject')
|
|
301
|
+
await w.insert([
|
|
302
|
+
{ id: 'old', d: new Date('2000-01-01T00:00:00Z') },
|
|
303
|
+
{ id: 'never1', d: new Date('9999-12-31T23:59:59.999Z') },
|
|
304
|
+
{ id: 'never2', d: null },
|
|
305
|
+
], cs)
|
|
306
|
+
assert.deepStrictEqual((await w.select({}, cs)).map((v) => v.id).sort(), ['never1', 'never2', 'old'])
|
|
307
|
+
let rd = await w.delAll({ d: { $lt: new Date('3000-01-01T00:00:00Z') } }, cs)
|
|
308
|
+
assert.deepStrictEqual(rd, { n: 1, nDeleted: 1, ok: 1 })
|
|
309
|
+
assert.deepStrictEqual((await w.select({}, cs)).map((v) => v.id).sort(), ['never1', 'never2'])
|
|
310
|
+
let rs = await w.select({ d: { $gt: new Date('3000-01-01T00:00:00Z') } }, cs)
|
|
311
|
+
assert.deepStrictEqual(rs.map((v) => v.id), ['never1'])
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it('不給instance之呼叫(內部自行初始化): 範圍外值reject、error事件恰一次, 連線已關閉且其後合法呼叫照常', async function() {
|
|
315
|
+
let w2 = wo(getOpt({ cl: 'tdate' }))
|
|
316
|
+
let evs = []
|
|
317
|
+
w2.on('error', (m) => evs.push(m))
|
|
318
|
+
let r = await settle(w2.insertBulk([{ id: 'a', d: new Date(8.64e15) }]))
|
|
319
|
+
assert.strictEqual(msgOf(r.err), want(0, 'd'))
|
|
320
|
+
assert.deepStrictEqual(evs, ['insertBulk'])
|
|
321
|
+
let r2 = await w2.insertBulk([{ id: 'a', d: new Date('9999-12-31T23:59:59.999Z') }])
|
|
322
|
+
assert.deepStrictEqual(r2, { n: 1, nInserted: 1, ok: 1 })
|
|
323
|
+
assert.deepStrictEqual(evs, ['insertBulk'], '合法呼叫不發error')
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
it('呼叫端交易內: 範圍外值reject不影響交易, 同一交易其後合法寫入與commit照常', async function() {
|
|
327
|
+
let transaction = await w.genTransaction()
|
|
328
|
+
let ct = { instance: ins, transaction }
|
|
329
|
+
let done = false
|
|
330
|
+
try {
|
|
331
|
+
let r = await settle(w.insertBulk([{ id: 't1', d: new Date('2001-01-01T00:00:00Z') }, { id: 't2', d: new Date(-8.64e15) }], ct))
|
|
332
|
+
assert.strictEqual(msgOf(r.err), want(1, 'd'))
|
|
333
|
+
let r2 = await w.insertBulk([{ id: 't3', d: new Date('2003-01-01T00:00:00Z') }], ct)
|
|
334
|
+
assert.deepStrictEqual(r2, { n: 1, nInserted: 1, ok: 1 })
|
|
335
|
+
await transaction.commit()
|
|
336
|
+
done = true
|
|
337
|
+
}
|
|
338
|
+
finally {
|
|
339
|
+
if (!done) {
|
|
340
|
+
await transaction.rollback().catch(() => {}) //斷言失敗時不留開啟中之交易(sqlite會鎖庫而連累後續案)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
assert.deepStrictEqual((await w.select({}, cs)).map((v) => v.id), ['t3'])
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
it('save更新既有列: 範圍外值reject後該列維持舊值, 合法值照常更新', async function() {
|
|
347
|
+
await w.insert({ id: 'k', d: new Date('2000-01-01T00:00:00Z') }, cs)
|
|
348
|
+
let r = await settle(w.save({ id: 'k', d: new Date(8.64e15) }, cs))
|
|
349
|
+
assert.strictEqual(msgOf(r.err), want(0, 'd'))
|
|
350
|
+
assert.strictEqual(normD((await w.selectByPk('k', cs)).d), '2000-01-01T00:00:00.000Z')
|
|
351
|
+
let r2 = await w.save({ id: 'k', d: new Date('9999-12-31T23:59:59.999Z') }, cs)
|
|
352
|
+
assert.deepStrictEqual(r2, [{ n: 1, nInserted: 0, nModified: 1, ok: 1 }])
|
|
353
|
+
assert.strictEqual(normD((await w.selectByPk('k', cs)).d), '9999-12-31T23:59:59.999Z')
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
it('insert回傳逐筆結果模式(returnList)亦整批reject', async function() {
|
|
357
|
+
let r = await settle(w.insert([{ id: 'a', d: new Date('2001-01-01T00:00:00Z') }, { id: 'b', d: new Date(8.64e15) }], { ...cs, returnList: true }))
|
|
358
|
+
assert.strictEqual(msgOf(r.err), want(1, 'd'))
|
|
359
|
+
assert.deepStrictEqual(await w.select({}, cs), [])
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
it('訊息不回顯值, 且不隨值而變: 各種範圍外值之訊息逐字相同', async function() {
|
|
363
|
+
let vals = [new Date(8.64e15), new Date(-8.64e15), 8.64e15, '+010000-01-01T00:00:00Z', '0000-12-31T23:59:59.999Z']
|
|
364
|
+
for (let v of vals) {
|
|
365
|
+
let r = await settle(w.insert({ id: 'a', d: v }, cs))
|
|
366
|
+
assert.strictEqual(msgOf(r.err), want(0, 'd'))
|
|
367
|
+
}
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('缺主鍵(autoGenPk為false)之錯誤先於日期檢核(既有優先序不變): insert、insertBulk、save', async function() {
|
|
371
|
+
let w2 = wo(getOpt({ cl: 'tdate', autoGenPk: false }))
|
|
372
|
+
for (let [name, fn] of [['insert', (rs) => w2.insert(rs)], ['insertBulk', (rs) => w2.insertBulk(rs)], ['save', (rs) => w2.save(rs)]]) {
|
|
373
|
+
let r = await settle(fn([{ d: new Date(8.64e15) }]))
|
|
374
|
+
assert.ok(r.err !== undefined, name)
|
|
375
|
+
assert.ok(msgOf(r.err).indexOf('autoGenPk is false') >= 0, `${name}: ${msgOf(r.err)}`)
|
|
376
|
+
}
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
it('主鍵型別不符之錯誤先於日期檢核(既有優先序不變): 整數主鍵給\'abc\'且日期範圍外, insert、insertBulk、save', async function() {
|
|
380
|
+
let wi = wo(getOpt({ cl: 'tdatei' }))
|
|
381
|
+
await wi.createStorage()
|
|
382
|
+
for (let [name, fn] of [['insert', (rs) => wi.insert(rs)], ['insertBulk', (rs) => wi.insertBulk(rs)], ['save', (rs) => wi.save(rs)]]) {
|
|
383
|
+
let r = await settle(fn([{ id: 'abc', d: new Date(8.64e15) }]))
|
|
384
|
+
assert.ok(r.err !== undefined, name)
|
|
385
|
+
assert.ok(msgOf(r.err).indexOf('type does not match pk column') >= 0, `${name}: ${msgOf(r.err)}`)
|
|
386
|
+
}
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
it('整數主鍵model之日期檢核照常: 主鍵合法而日期範圍外則整批reject', async function() {
|
|
390
|
+
let wi = wo(getOpt({ cl: 'tdatei' }))
|
|
391
|
+
await wi.createStorage()
|
|
392
|
+
await wi.delAll()
|
|
393
|
+
let r = await settle(wi.insert([{ id: 1, d: new Date('2001-01-01T00:00:00Z') }, { id: 2, d: new Date(-8.64e15) }]))
|
|
394
|
+
assert.strictEqual(msgOf(r.err), want(1, 'd'))
|
|
395
|
+
assert.deepStrictEqual(await wi.select({}), [])
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
// 規格句(R12): 檢查之值即使用之值——具狀態之valueOf第一次回傳合法值、其後回傳範圍外值者, 存入的是檢核時之值(物件值已換成標準值)
|
|
399
|
+
let stateful = (a, b) => {
|
|
400
|
+
let n = 0
|
|
401
|
+
return {
|
|
402
|
+
valueOf() {
|
|
403
|
+
return n++ === 0 ? a : b
|
|
404
|
+
},
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
it('檢查之值即使用之值(DATE, 具狀態valueOf): insert、insertBulk、save(更新既有)皆存入檢核時之值而非範圍外值', async function() {
|
|
409
|
+
await w.insert({ id: 'upd', d: new Date('2000-01-01T00:00:00Z') }, cs)
|
|
410
|
+
let entriesS = [
|
|
411
|
+
['insert', 'si', (r) => w.insert(r, cs)],
|
|
412
|
+
['insertBulk', 'sb', (r) => w.insertBulk(r, cs)],
|
|
413
|
+
['save(更新既有)', 'upd', (r) => w.save(r, cs)],
|
|
414
|
+
]
|
|
415
|
+
for (let [name, id, fn] of entriesS) {
|
|
416
|
+
let r = await settle(fn([{ id, d: stateful(0, 8.64e15) }]))
|
|
417
|
+
assert.ok(r.err === undefined, `${name}: ${msgOf(r.err)}`)
|
|
418
|
+
let row = await w.selectByPk(id, cs)
|
|
419
|
+
assert.strictEqual(normD(row.d), '1970-01-01T00:00:00.000Z', name)
|
|
420
|
+
}
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
it('檢查之值即使用之值(DATEONLY, 陣列元素具狀態toString): 存入檢核時之日期而非範圍外值', async function() {
|
|
424
|
+
let n = 0
|
|
425
|
+
let el = {
|
|
426
|
+
toString() {
|
|
427
|
+
return n++ === 0 ? '2026' : '10000'
|
|
428
|
+
},
|
|
429
|
+
}
|
|
430
|
+
let r = await settle(w.insert({ id: 'do1', o: [el, 0, 1] }, cs))
|
|
431
|
+
assert.ok(r.err === undefined, msgOf(r.err))
|
|
432
|
+
assert.strictEqual(normO((await w.selectByPk('do1', cs)).o), '2026-01-01')
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
it('Sequelize.literal等SQL運算式不被檢核或換值(照常交資料庫求值)', async function() {
|
|
436
|
+
if (label !== 'sqlite') {
|
|
437
|
+
return //運算式文法依方言
|
|
438
|
+
}
|
|
439
|
+
let r = await settle(w.insert({ id: 'lit', d: Sequelize.literal('\'2020-02-02 00:00:00.000 +00:00\'') }, cs))
|
|
440
|
+
assert.ok(r.err === undefined, msgOf(r.err))
|
|
441
|
+
assert.strictEqual(normD((await w.selectByPk('lit', cs)).d), '2020-02-02T00:00:00.000Z')
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
if (label === 'sqlite') {
|
|
445
|
+
//README之清理範例: 庫內既有之範圍外日期文字(本版前寫入)可由save改寫為範圍內值或null; 以原生SQL模擬舊資料(修前寫入入口已被擋)
|
|
446
|
+
it('既有資料不遷移: 庫內舊的範圍外日期文字不受影響, 可由save改寫為null與9999年末', async function() {
|
|
447
|
+
let sq = new Sequelize({ dialect: 'sqlite', storage: getOpt().storage, logging: false })
|
|
448
|
+
try {
|
|
449
|
+
await sq.query('INSERT INTO tdate (id, d) VALUES (?, ?)', { replacements: ['legacy', '275760-09-13 00:00:00.000 +00:00'] })
|
|
450
|
+
}
|
|
451
|
+
finally {
|
|
452
|
+
await sq.close()
|
|
453
|
+
}
|
|
454
|
+
let row = await w.selectByPk('legacy', cs)
|
|
455
|
+
assert.strictEqual(normD(row.d), '275760-09-13T00:00:00.000Z', '舊資料維持原樣(不遷移)')
|
|
456
|
+
await w.save({ id: 'legacy', d: null }, cs)
|
|
457
|
+
assert.strictEqual((await w.selectByPk('legacy', cs)).d, null)
|
|
458
|
+
await w.save({ id: 'legacy', d: new Date('9999-12-31T23:59:59.999Z') }, cs)
|
|
459
|
+
assert.strictEqual(normD((await w.selectByPk('legacy', cs)).d), '9999-12-31T23:59:59.999Z')
|
|
460
|
+
let r = await settle(w.save({ id: 'legacy', d: new Date(8.64e15) }, cs))
|
|
461
|
+
assert.strictEqual(msgOf(r.err), want(0, 'd'), '以Date再寫範圍外仍被拒')
|
|
462
|
+
})
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
}
|