w-orm-lowdb 1.0.35 → 1.0.37

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,346 @@
1
+ import assert from 'assert'
2
+ import w from 'wsemi'
3
+ import fs from 'fs'
4
+ import WOrm from '../src/WOrmLowdb.mjs'
5
+
6
+
7
+ //主鍵為Object.prototype成員名時之往返(帳本R-PKMAP-01, 排程第66項批1c run02)
8
+ //各寫入入口以[主鍵 => 現值]對照表判定主鍵是否已存在, 修前以一般物件加wsemi haskey(即in運算子)判定, 走原型鏈:
9
+ //主鍵為constructor、toString、__proto__等時恆被當成已存在, insert回{n:1,nInserted:0,ok:1}而靜默不寫入,
10
+ //insertBulk誤報主鍵衝突, save誤報nModified, del誤報nDeleted, 且以__proto__為主鍵之紀錄其欄位名被誤認為他筆主鍵(save時字串被展開為{0:'x'}寫入而損毀資料)
11
+ describe('protokey', function() {
12
+ let url = './test/_tmp/protokey'
13
+
14
+ //以下為兩套件之差異處(mk、closeWo、before、after), 其後之測試主體兩套件相同(本檔由level版之tmp/gen-lowdb-protokey.mjs產生)
15
+ //mk: 建立資料庫, name為檔名(url為檔案路徑), opt可覆寫db與cl
16
+ let mk = (name, opt = {}) => {
17
+ let file = `${url}/${name}.json`
18
+ w.fsDeleteFile(file)
19
+ return WOrm({ url: file, db: 'worm', cl: 'c', ...opt })
20
+ }
21
+
22
+ //closeWo: lowdb無close, 不須關閉
23
+ let closeWo = async(wo) => {
24
+ return wo
25
+ }
26
+
27
+ before(function() {
28
+ w.fsDeleteFolder(url)
29
+ fs.mkdirSync(url, { recursive: true })
30
+ })
31
+
32
+ after(function() {
33
+ w.fsDeleteFolder(url)
34
+ })
35
+ //=== 以下兩套件相同 ===
36
+
37
+ //use: 建立資料庫並執行fn, 結束一律關閉
38
+ let n = 0
39
+ let use = async(fn, opt = {}) => {
40
+ n++
41
+ let wo = mk(`p${n}`, opt)
42
+ try {
43
+ return await fn(wo)
44
+ }
45
+ finally {
46
+ await closeWo(wo)
47
+ }
48
+ }
49
+
50
+ //protoNames, Object.prototype之全部自有屬性名(含__proto__、constructor、toString、hasOwnProperty等共12個)
51
+ let protoNames = Object.getOwnPropertyNames(Object.prototype)
52
+
53
+ //ctrlNames, 對照組: 非Object.prototype成員之一般字串, 結果須與修前相同
54
+ let ctrlNames = ['prototype', 'length', 'toJSON', 'then', '__proto', 'proto__', 'id']
55
+
56
+ //snap, 資料庫現況(依id升冪)
57
+ let snap = async(wo) => (await wo.select()).sort((a, b) => (a.id < b.id ? -1 : 1))
58
+
59
+ //捕捉錯誤訊息
60
+ let rej = async(fn) => {
61
+ try {
62
+ await fn()
63
+ }
64
+ catch (err) {
65
+ return String(err && err.message ? err.message : err)
66
+ }
67
+ return null
68
+ }
69
+
70
+ it('protoNames covers all 12 members of Object.prototype', function() {
71
+ assert.strict.equal(protoNames.length, 12)
72
+ assert.strict.ok(protoNames.includes('__proto__'))
73
+ assert.strict.ok(protoNames.includes('constructor'))
74
+ assert.strict.ok(protoNames.includes('hasOwnProperty'))
75
+ })
76
+
77
+ for (let group of [{ title: 'proto', ids: protoNames }, { title: 'ctrl', ids: ctrlNames }]) {
78
+ for (let id of group.ids) {
79
+ describe(`${group.title} id=${id}`, function() {
80
+
81
+ it('insert: fresh id is written, repeat is skipped without overwrite', async function() {
82
+ await use(async(wo) => {
83
+ assert.strict.deepEqual(await wo.insert({ id, a: 1 }), { n: 1, nInserted: 1, ok: 1 })
84
+ assert.strict.deepEqual(await snap(wo), [{ id, a: 1 }])
85
+ assert.strict.deepEqual(await wo.selectByPk(id), { id, a: 1 })
86
+ assert.strict.deepEqual(await wo.insert({ id, a: 2 }), { n: 1, nInserted: 0, ok: 1 })
87
+ assert.strict.deepEqual(await snap(wo), [{ id, a: 1 }])
88
+ })
89
+ })
90
+
91
+ it('insert returnList: per-row nInserted follows real existence', async function() {
92
+ await use(async(wo) => {
93
+ let o = { returnList: true }
94
+ assert.strict.deepEqual(await wo.insert([{ id, a: 1 }, { id: 'plain', a: 2 }], o), [{ n: 1, nInserted: 1, ok: 1 }, { n: 1, nInserted: 1, ok: 1 }])
95
+ assert.strict.deepEqual(await wo.insert([{ id, a: 3 }, { id: 'plain2', a: 4 }], o), [{ n: 1, nInserted: 0, ok: 1 }, { n: 1, nInserted: 1, ok: 1 }])
96
+ assert.strict.deepEqual(await wo.selectByPk(id), { id, a: 1 })
97
+ })
98
+ })
99
+
100
+ it('insertBulk: fresh id succeeds, repeat and same-batch duplicate conflict', async function() {
101
+ await use(async(wo) => {
102
+ assert.strict.deepEqual(await wo.insertBulk({ id, a: 1 }), { n: 1, nInserted: 1, ok: 1 })
103
+ assert.strict.deepEqual(await snap(wo), [{ id, a: 1 }])
104
+ assert.strict.equal(await rej(() => wo.insertBulk({ id, a: 2 })), `can not insertBulk by existed id[${id}]`)
105
+ assert.strict.deepEqual(await snap(wo), [{ id, a: 1 }])
106
+ })
107
+ await use(async(wo) => {
108
+ assert.strict.equal(await rej(() => wo.insertBulk([{ id, a: 1 }, { id, a: 2 }])), `can not insertBulk by existed id[${id}]`)
109
+ assert.strict.deepEqual(await snap(wo), [])
110
+ })
111
+ })
112
+
113
+ it('insertBulk: mixed with an ordinary id in the same batch', async function() {
114
+ await use(async(wo) => {
115
+ assert.strict.deepEqual(await wo.insertBulk([{ id: 'plain', a: 1 }, { id, a: 2 }]), { n: 2, nInserted: 2, ok: 1 })
116
+ assert.strict.deepEqual(await wo.selectByPk(id), { id, a: 2 })
117
+ assert.strict.deepEqual(await wo.selectByPk('plain'), { id: 'plain', a: 1 })
118
+ })
119
+ })
120
+
121
+ it('save: fresh id is inserted and emits the insert event', async function() {
122
+ await use(async(wo) => {
123
+ let modes = []
124
+ wo.on('change', (mode) => modes.push(mode))
125
+ assert.strict.deepEqual(await wo.save({ id, a: 1 }), [{ n: 1, nInserted: 1, nModified: 0, ok: 1 }])
126
+ assert.strict.deepEqual(await snap(wo), [{ id, a: 1 }])
127
+ assert.strict.deepEqual(modes, ['insert', 'save'])
128
+ })
129
+ })
130
+
131
+ it('save: existing id is merged, identical content is not a modification', async function() {
132
+ await use(async(wo) => {
133
+ await wo.save({ id, a: 1 })
134
+ assert.strict.deepEqual(await wo.save({ id, b: 2 }), [{ n: 1, nInserted: 0, nModified: 1, ok: 1 }])
135
+ assert.strict.deepEqual(await snap(wo), [{ id, a: 1, b: 2 }])
136
+ assert.strict.deepEqual(await wo.save({ id, a: 1 }), [{ n: 1, nInserted: 0, nModified: 0, ok: 1 }])
137
+ assert.strict.deepEqual(await snap(wo), [{ id, a: 1, b: 2 }])
138
+ })
139
+ })
140
+
141
+ it('save autoInsert:false: absent id is not written, existing id is modified', async function() {
142
+ await use(async(wo) => {
143
+ assert.strict.deepEqual(await wo.save({ id, a: 1 }, { autoInsert: false }), [{ n: 0, nInserted: 0, nModified: 0, ok: 1 }])
144
+ assert.strict.deepEqual(await snap(wo), [])
145
+ await wo.insert({ id, a: 1 })
146
+ assert.strict.deepEqual(await wo.save({ id, a: 2 }, { autoInsert: false }), [{ n: 1, nInserted: 0, nModified: 1, ok: 1 }])
147
+ assert.strict.deepEqual(await snap(wo), [{ id, a: 2 }])
148
+ })
149
+ })
150
+
151
+ it('save: same id twice in a batch inserts then modifies', async function() {
152
+ await use(async(wo) => {
153
+ assert.strict.deepEqual(await wo.save([{ id, a: 1 }, { id, b: 2 }]), [{ n: 1, nInserted: 1, nModified: 0, ok: 1 }, { n: 1, nInserted: 0, nModified: 1, ok: 1 }])
154
+ assert.strict.deepEqual(await snap(wo), [{ id, a: 1, b: 2 }])
155
+ })
156
+ })
157
+
158
+ it('del: existing id is deleted, repeat deletes nothing', async function() {
159
+ await use(async(wo) => {
160
+ assert.strict.deepEqual(await wo.insert({ id, a: 1 }), { n: 1, nInserted: 1, ok: 1 })
161
+ assert.strict.deepEqual(await wo.del({ id }), [{ n: 1, nDeleted: 1, ok: 1 }])
162
+ assert.strict.deepEqual(await snap(wo), [])
163
+ assert.strict.deepEqual(await wo.selectByPk(id), null)
164
+ assert.strict.deepEqual(await wo.del({ id }), [{ n: 0, nDeleted: 0, ok: 1 }])
165
+ })
166
+ })
167
+
168
+ it('del: absent id reports nothing deleted and leaves other rows', async function() {
169
+ await use(async(wo) => {
170
+ await wo.insert({ id: 'keep', a: 1 })
171
+ assert.strict.deepEqual(await wo.del({ id }), [{ n: 0, nDeleted: 0, ok: 1 }])
172
+ assert.strict.deepEqual(await snap(wo), [{ id: 'keep', a: 1 }])
173
+ })
174
+ })
175
+
176
+ it('del: same id twice in a batch deletes once', async function() {
177
+ await use(async(wo) => {
178
+ assert.strict.deepEqual(await wo.insert({ id, a: 1 }), { n: 1, nInserted: 1, ok: 1 })
179
+ assert.strict.deepEqual(await wo.del([{ id }, { id }]), [{ n: 1, nDeleted: 1, ok: 1 }, { n: 0, nDeleted: 0, ok: 1 }])
180
+ assert.strict.deepEqual(await snap(wo), [])
181
+ })
182
+ })
183
+
184
+ it('selectByPk: absent id is null (table has other rows), present id is the row', async function() {
185
+ await use(async(wo) => {
186
+ await wo.insert({ id: 'keep', a: 1 })
187
+ assert.strict.deepEqual(await wo.selectByPk(id), null)
188
+ await wo.insert({ id, a: 2 })
189
+ assert.strict.deepEqual(await wo.selectByPk(id), { id, a: 2 })
190
+ assert.strict.deepEqual(await wo.selectByPk('keep'), { id: 'keep', a: 1 })
191
+ })
192
+ })
193
+
194
+ })
195
+ }
196
+ }
197
+
198
+ describe('all 12 prototype member names in one batch', function() {
199
+ let rows = () => protoNames.map((id, k) => ({ id, a: k }))
200
+ let sorted = () => rows().sort((a, b) => (a.id < b.id ? -1 : 1))
201
+
202
+ it('insert', async function() {
203
+ await use(async(wo) => {
204
+ assert.strict.deepEqual(await wo.insert(rows()), { n: 12, nInserted: 12, ok: 1 })
205
+ assert.strict.deepEqual(await snap(wo), sorted())
206
+ assert.strict.deepEqual(await wo.insert(rows()), { n: 12, nInserted: 0, ok: 1 })
207
+ })
208
+ })
209
+
210
+ it('insertBulk', async function() {
211
+ await use(async(wo) => {
212
+ assert.strict.deepEqual(await wo.insertBulk(rows()), { n: 12, nInserted: 12, ok: 1 })
213
+ assert.strict.deepEqual(await snap(wo), sorted())
214
+ assert.strict.equal(await rej(() => wo.insertBulk(rows())), `can not insertBulk by existed id[${protoNames[0]}]`)
215
+ })
216
+ })
217
+
218
+ it('save inserts then modifies every row', async function() {
219
+ await use(async(wo) => {
220
+ let ok = (nInserted, nModified) => protoNames.map(() => ({ n: 1, nInserted, nModified, ok: 1 }))
221
+ assert.strict.deepEqual(await wo.save(rows()), ok(1, 0))
222
+ assert.strict.deepEqual(await snap(wo), sorted())
223
+ assert.strict.deepEqual(await wo.save(protoNames.map((id) => ({ id, z: 1 }))), ok(0, 1))
224
+ assert.strict.deepEqual(await snap(wo), sorted().map((v) => ({ ...v, z: 1 })))
225
+ })
226
+ })
227
+
228
+ it('del deletes every row', async function() {
229
+ await use(async(wo) => {
230
+ await wo.insertBulk(rows())
231
+ assert.strict.deepEqual(await wo.del(protoNames.map((id) => ({ id }))), protoNames.map(() => ({ n: 1, nDeleted: 1, ok: 1 })))
232
+ assert.strict.deepEqual(await snap(wo), [])
233
+ })
234
+ })
235
+ })
236
+
237
+ //交叉污染: 以__proto__為主鍵之紀錄, 其欄位名(name)不得被誤認為他筆之主鍵(修前kp['__proto__']=v改寫對照表之原型)
238
+ describe('field names of a __proto__ row are not primary keys', function() {
239
+
240
+ it('insert', async function() {
241
+ await use(async(wo) => {
242
+ assert.strict.deepEqual(await wo.insert([{ id: '__proto__', name: 'x' }, { id: 'name' }]), { n: 2, nInserted: 2, ok: 1 })
243
+ assert.strict.deepEqual(await snap(wo), [{ id: '__proto__', name: 'x' }, { id: 'name' }])
244
+ })
245
+ })
246
+
247
+ it('insertBulk', async function() {
248
+ await use(async(wo) => {
249
+ assert.strict.deepEqual(await wo.insertBulk([{ id: '__proto__', name: 'x' }, { id: 'name' }]), { n: 2, nInserted: 2, ok: 1 })
250
+ assert.strict.deepEqual(await snap(wo), [{ id: '__proto__', name: 'x' }, { id: 'name' }])
251
+ })
252
+ })
253
+
254
+ it('save: no corruption of the other row', async function() {
255
+ await use(async(wo) => {
256
+ await wo.insert({ id: '__proto__', name: 'x' })
257
+ assert.strict.deepEqual(
258
+ await wo.save([{ id: '__proto__', name: 'y' }, { id: 'name', q: 1 }]),
259
+ [{ n: 1, nInserted: 0, nModified: 1, ok: 1 }, { n: 1, nInserted: 1, nModified: 0, ok: 1 }]
260
+ )
261
+ assert.strict.deepEqual(await snap(wo), [{ id: '__proto__', name: 'y' }, { id: 'name', q: 1 }])
262
+ })
263
+ })
264
+
265
+ //row內之欄位名name、k、v(k與v亦為lowdb對照表值{k, v}之欄位名)皆不得被誤認為主鍵
266
+ let row = () => ({ id: '__proto__', name: 'x', k: 1, v: 2 })
267
+
268
+ it('selectByPk: names of its fields are absent', async function() {
269
+ await use(async(wo) => {
270
+ assert.strict.deepEqual(await wo.insert(row()), { n: 1, nInserted: 1, ok: 1 })
271
+ for (let id of ['name', 'k', 'v']) {
272
+ assert.strict.equal(await wo.selectByPk(id), null, id)
273
+ }
274
+ assert.strict.deepEqual(await wo.selectByPk('__proto__'), row())
275
+ })
276
+ })
277
+
278
+ it('save: names of its fields are inserted as new rows', async function() {
279
+ await use(async(wo) => {
280
+ await wo.insert(row())
281
+ assert.strict.deepEqual(
282
+ await wo.save([{ id: 'v', a: 1 }, { id: 'k', a: 2 }]),
283
+ [{ n: 1, nInserted: 1, nModified: 0, ok: 1 }, { n: 1, nInserted: 1, nModified: 0, ok: 1 }]
284
+ )
285
+ assert.strict.deepEqual(await snap(wo), [row(), { id: 'k', a: 2 }, { id: 'v', a: 1 }])
286
+ })
287
+ })
288
+
289
+ it('del: names of its fields delete nothing', async function() {
290
+ await use(async(wo) => {
291
+ await wo.insert(row())
292
+ assert.strict.deepEqual(
293
+ await wo.del([{ id: 'name' }, { id: 'k' }, { id: 'v' }]),
294
+ [{ n: 0, nDeleted: 0, ok: 1 }, { n: 0, nDeleted: 0, ok: 1 }, { n: 0, nDeleted: 0, ok: 1 }]
295
+ )
296
+ assert.strict.deepEqual(await snap(wo), [row()])
297
+ })
298
+ })
299
+
300
+ it('del: only the present row is deleted', async function() {
301
+ await use(async(wo) => {
302
+ await wo.insert({ id: '__proto__', name: 'x' })
303
+ assert.strict.deepEqual(await wo.del([{ id: '__proto__' }, { id: 'name' }]), [{ n: 1, nDeleted: 1, ok: 1 }, { n: 0, nDeleted: 0, ok: 1 }])
304
+ assert.strict.deepEqual(await snap(wo), [])
305
+ })
306
+ await use(async(wo) => {
307
+ await wo.insertBulk([{ id: '__proto__', name: 'x' }, { id: 'name', q: 1 }])
308
+ assert.strict.deepEqual(await wo.del([{ id: '__proto__' }, { id: 'name' }]), [{ n: 1, nDeleted: 1, ok: 1 }, { n: 1, nDeleted: 1, ok: 1 }])
309
+ assert.strict.deepEqual(await snap(wo), [])
310
+ })
311
+ })
312
+
313
+ })
314
+
315
+ //位址名稱(url、db、cl)為原型成員名者: 其對照表之鍵恆含分隔符或為絕對路徑, 不受影響(帳本R-PKMAP-01之刻意不套站點之佐證)
316
+ describe('address names that are prototype members', function() {
317
+
318
+ it('db and cl', async function() {
319
+ await use(async(wo) => {
320
+ assert.strict.deepEqual(await wo.insert({ id: 'a', v: 1 }), { n: 1, nInserted: 1, ok: 1 })
321
+ assert.strict.deepEqual(await wo.save({ id: 'a', v: 2 }), [{ n: 1, nInserted: 0, nModified: 1, ok: 1 }])
322
+ assert.strict.deepEqual(await snap(wo), [{ id: 'a', v: 2 }])
323
+ }, { db: 'constructor', cl: '__proto__' })
324
+ await use(async(wo) => {
325
+ assert.strict.deepEqual(await wo.insert({ id: 'a', v: 1 }), { n: 1, nInserted: 1, ok: 1 })
326
+ assert.strict.deepEqual(await snap(wo), [{ id: 'a', v: 1 }])
327
+ }, { db: 'toString', cl: 'hasOwnProperty' })
328
+ })
329
+
330
+ it('url named like a prototype member', async function() {
331
+ for (let name of ['constructor', '__proto__']) {
332
+ let wo = mk(name)
333
+ try {
334
+ assert.strict.deepEqual(await wo.insert({ id: 'a', v: 1 }), { n: 1, nInserted: 1, ok: 1 })
335
+ assert.strict.deepEqual(await wo.insert({ id: 'a', v: 2 }), { n: 1, nInserted: 0, ok: 1 })
336
+ assert.strict.deepEqual(await snap(wo), [{ id: 'a', v: 1 }])
337
+ }
338
+ finally {
339
+ await closeWo(wo)
340
+ }
341
+ }
342
+ })
343
+
344
+ })
345
+
346
+ })