datastore-api 2.0.1 → 2.1.0

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,440 @@
1
+ /*
2
+ * dstore-api.test.ts - Test again the Google Datastore-Emulator.
3
+ *
4
+ * Created by Dr. Maximilian Dornseif 2021-12-10 in huwawi3backend 11.10.0
5
+ * Copyright (c) 2021, 2023 Dr. Maximilian Dornseif
6
+ */
7
+ // @ts-nocheck
8
+ import { Datastore, Key } from '@google-cloud/datastore'
9
+ import Emulator from 'google-datastore-emulator'
10
+ import { afterAll, assert, beforeAll, describe, expect, test } from 'vitest'
11
+
12
+ import { Dstore } from './dstore-api'
13
+
14
+ process.env.GCLOUD_PROJECT = 'project-id' // Set the datastore project Id globally
15
+ let emulator
16
+
17
+ beforeAll(async () => {
18
+ emulator = new Emulator({ debug: false })
19
+ await emulator.start()
20
+ })
21
+
22
+ afterAll(async () => {
23
+ await emulator.stop()
24
+ })
25
+
26
+ function getDstore() {
27
+ return new Dstore(new Datastore({ namespace: 'test', projectId: process.env.GCLOUD_PROJECT }))
28
+ }
29
+
30
+ test('keySerialize', async () => {
31
+ const kvStore = getDstore()
32
+ assert.deepEqual(kvStore.key(['testYodel', 123]).path, ['testYodel', 123])
33
+ assert.deepEqual(JSON.parse(JSON.stringify(kvStore.key(['testYodel', 123]))), {
34
+ id: 123 as any, // typing in inconclusive here
35
+ kind: 'testYodel',
36
+ namespace: 'test',
37
+ path: ['testYodel', 123],
38
+ } as any)
39
+ const ser = kvStore.keySerialize(kvStore.key(['testYodel', 123]))
40
+ expect(ser).toMatchInlineSnapshot('"agByDwsSCXRlc3RZb2RlbBh7DKIBBHRlc3Q"')
41
+ expect(JSON.parse(JSON.stringify(kvStore.keyFromSerialized(ser)))).toMatchInlineSnapshot(`
42
+ {
43
+ "id": "123",
44
+ "kind": "testYodel",
45
+ "namespace": "test",
46
+ "path": [
47
+ "testYodel",
48
+ "123",
49
+ ],
50
+ }
51
+ `)
52
+ })
53
+
54
+ describe('Allocation', () => {
55
+ test('allocateIds', async () => {
56
+ const kvStore = getDstore()
57
+ const keys = await kvStore.datastore.allocateIds(kvStore.datastore.key(['testYodel']), 2)
58
+ expect(Array.isArray(keys)).toBeTruthy()
59
+ expect(keys[0].length).toBe(2)
60
+ expect(keys[0][0].kind).toBe('testYodel')
61
+ expect(keys[0][0].id).toMatch(/\d+/)
62
+ expect(keys?.[1]?.keys?.length).toBe(2)
63
+ expect(keys[1].keys[0].partitionId.namespaceId).toMatchInlineSnapshot('"test"')
64
+ expect(keys[1].keys[0].path[0].idType).toMatchInlineSnapshot('"id"')
65
+ expect(keys[1].keys[0].path[0].kind).toMatchInlineSnapshot('"testYodel"')
66
+ })
67
+
68
+ test('allocateOneId', async () => {
69
+ const kvStore = getDstore()
70
+ const id = await kvStore.allocateOneId()
71
+ expect(id).toMatch(/\d+/)
72
+ })
73
+ })
74
+
75
+ describe('Read', () => {
76
+ test('get num_id', async () => {
77
+ const kvStore = getDstore()
78
+ const entity = { key: kvStore.key(['testYodel', 2]), data: { foo: 'bar' } }
79
+ const entity2 = {
80
+ key: kvStore.key(['testYodel', 3]),
81
+ data: { foo: 'bar' },
82
+ }
83
+ const commitResponse = await kvStore.save([entity, entity2])
84
+ // expect(isNumber(commitResponse?.[0]?.indexUpdates)).toBeTruthy();
85
+ // expect(commitResponse).toMatchInlineSnapshot(`
86
+ // Array [
87
+ // Object {
88
+ // "indexUpdates": 0,
89
+ // "mutationResults": Array [
90
+ // Object {
91
+ // "conflictDetected": false,
92
+ // "key": null,
93
+ // "version": "1234567890123456",
94
+ // },
95
+ // ],
96
+ // },
97
+ // ]
98
+ // `);
99
+ expect(entity).toMatchInlineSnapshot(`
100
+ {
101
+ "data": {
102
+ "_keyStr": "agByDwsSCXRlc3RZb2RlbBgCDKIBBHRlc3Q",
103
+ "foo": "bar",
104
+ Symbol(KEY): Key {
105
+ "id": 2,
106
+ "kind": "testYodel",
107
+ "namespace": "test",
108
+ "path": [
109
+ "testYodel",
110
+ 2,
111
+ ],
112
+ },
113
+ },
114
+ "excludeLargeProperties": true,
115
+ "key": Key {
116
+ "id": 2,
117
+ "kind": "testYodel",
118
+ "namespace": "test",
119
+ "path": [
120
+ "testYodel",
121
+ 2,
122
+ ],
123
+ },
124
+ }
125
+ `)
126
+
127
+ const result = await kvStore.get(entity.key)
128
+ // get returns a single Entity
129
+ expect(Array.isArray(result)).toBeFalsy()
130
+ expect(result).toMatchInlineSnapshot('null')
131
+ // expect(kvStore.readKey(result)).toBeInstanceOf(Key);
132
+
133
+ const result2 = await kvStore.getMulti([entity.key])
134
+ // getMulti returns a Array even for single keys
135
+ expect(result2).toMatchInlineSnapshot(`
136
+ [
137
+ null,
138
+ ]
139
+ `)
140
+ const result3 = await kvStore.getMulti([entity.key, kvStore.key(['testYodel', 3])])
141
+ // getMulti returns a Array with multiple keys
142
+ // expect(Array.isArray(result)).toBeTruthy();
143
+ expect(result3).toMatchInlineSnapshot(`
144
+ [
145
+ null,
146
+ null,
147
+ ]
148
+ `)
149
+ const result4 = await kvStore.getMulti([entity.key, entity.key])
150
+ // getMulti returns a Array but collapses duplicate keys
151
+ // expect(Array.isArray(result)).toBeTruthy();
152
+ // Firestore in Datastore returns the entity once
153
+ // Datastore Emulator returns the Entity twice
154
+ // kvStore should normalize that.
155
+ expect(result4).toMatchInlineSnapshot(`
156
+ [
157
+ null,
158
+ null,
159
+ ]
160
+ `)
161
+
162
+ const result5 = await kvStore.getMulti([entity.key, kvStore.key(['YodelNotThere', 3])])
163
+ // getMulti returns a Array but omits unknown keys
164
+ // expect(Array.isArray(result)).toBeTruthy();
165
+ expect(result5).toMatchInlineSnapshot(`
166
+ [
167
+ null,
168
+ null,
169
+ ]
170
+ `)
171
+ const result6 = await kvStore.getMulti([])
172
+ // getMulti returns a empty Array for an empty array
173
+ // expect(Array.isArray(result)).toBeTruthy();
174
+ expect(result6).toMatchInlineSnapshot('[]')
175
+ })
176
+ })
177
+
178
+ test('get name', async (t) => {
179
+ const kvStore = getDstore()
180
+ const entity = {
181
+ key: kvStore.key(['testYodel', 'two']),
182
+ data: { foo: 'bar' },
183
+ }
184
+ await kvStore.save([entity])
185
+ const result = await kvStore.get(entity.key)
186
+ expect(result?._keyStr).toMatchInlineSnapshot('"agByEgsSCXRlc3RZb2RlbCIDdHdvDKIBBHRlc3Q"')
187
+ expect(result?.foo).toBe('bar')
188
+ })
189
+
190
+ describe('query', async () => {
191
+ test('raw', async () => {
192
+ const kvStore = getDstore()
193
+ const entity = {
194
+ key: kvStore.key(['testYodel', '3']),
195
+ data: { foo: 'bar', baz: 'baz' },
196
+ }
197
+
198
+ await kvStore.save([entity])
199
+ const query = kvStore.datastore.createQuery('testYodel')
200
+ query.limit(1)
201
+ const [entities, runQueryInfo] = await kvStore.datastore.runQuery(query)
202
+ expect(entities.length).toBe(1)
203
+ expect(entities).toMatchInlineSnapshot(`
204
+ [
205
+ {
206
+ "foo": "bar",
207
+ Symbol(KEY): Key {
208
+ "id": "2",
209
+ "kind": "testYodel",
210
+ "namespace": "test",
211
+ "path": [
212
+ "testYodel",
213
+ "2",
214
+ ],
215
+ },
216
+ },
217
+ ]
218
+ `)
219
+ })
220
+
221
+ test('query', async () => {
222
+ const kvStore = getDstore()
223
+ const entity = {
224
+ key: kvStore.key(['testYodel', '3']),
225
+ data: { foo: 'bar', baz: 'baz' },
226
+ }
227
+
228
+ const saveResult = await kvStore.save([entity])
229
+ expect(saveResult).toMatchInlineSnapshot(`
230
+ [
231
+ {
232
+ "commitTime": null,
233
+ "indexUpdates": 0,
234
+ "mutationResults": [
235
+ {
236
+ "conflictDetected": false,
237
+ "createTime": null,
238
+ "key": null,
239
+ "updateTime": null,
240
+ "version": "5",
241
+ },
242
+ ],
243
+ },
244
+ ]
245
+ `)
246
+ expect(await kvStore.get(entity.key)).toMatchInlineSnapshot(`
247
+ {
248
+ "_keyStr": "agByEAsSCXRlc3RZb2RlbCIBMwyiAQR0ZXN0",
249
+ "baz": "baz",
250
+ "foo": "bar",
251
+ Symbol(KEY): Key {
252
+ "kind": "testYodel",
253
+ "name": "3",
254
+ "namespace": "test",
255
+ "path": [
256
+ "testYodel",
257
+ "3",
258
+ ],
259
+ },
260
+ }
261
+ `)
262
+ // Give Datastore time to become consistent
263
+ do {} while ((await kvStore.get(entity.key)) === null)
264
+
265
+ const query = kvStore.createQuery('testYodel')
266
+ query.limit(1)
267
+ const [entities, runQueryInfo] = await kvStore.runQuery(query)
268
+ // expect(entities.length).toBe(1)
269
+ expect(entities).toMatchInlineSnapshot(`
270
+ [
271
+ {
272
+ "_keyStr": "agByDwsSCXRlc3RZb2RlbBgCDKIBBHRlc3Q",
273
+ "foo": "bar",
274
+ Symbol(KEY): Key {
275
+ "id": "2",
276
+ "kind": "testYodel",
277
+ "namespace": "test",
278
+ "path": [
279
+ "testYodel",
280
+ "2",
281
+ ],
282
+ },
283
+ },
284
+ ]
285
+ `)
286
+ expect(runQueryInfo).toMatchInlineSnapshot(`
287
+ {
288
+ "endCursor": "CioSJGoKcHJvamVjdC1pZHIPCxIJdGVzdFlvZGVsGAIMogEEdGVzdBgAIAA=",
289
+ "moreResults": "MORE_RESULTS_AFTER_LIMIT",
290
+ }
291
+ `)
292
+ expect(entities?.[0]?.foo).toBe('bar')
293
+ expect(entities?.[0]?.[Datastore.KEY]?.kind).toBe('testYodel')
294
+ expect(runQueryInfo?.moreResults).toBe('MORE_RESULTS_AFTER_LIMIT')
295
+
296
+ // modern interface
297
+ const [result2] = await kvStore.query('testYodel', [], 1, [], ['baz'])
298
+ expect(result2.length).toBe(1)
299
+ // foo is removed by selection
300
+ expect(JSON.parse(JSON.stringify(result2?.[0]))).toMatchInlineSnapshot(`
301
+ {
302
+ "_keyStr": "agByEAsSCXRlc3RZb2RlbCIBMwyiAQR0ZXN0",
303
+ "baz": "baz",
304
+ }
305
+ `)
306
+
307
+ const key = kvStore.readKey(result2?.[0])
308
+ expect(key.id).toBe(entity.key.id)
309
+ })
310
+ })
311
+
312
+ test('set', async () => {
313
+ // expect.assertions(2);
314
+ const kvStore = getDstore()
315
+ const result = await kvStore.set(kvStore.key(['testYodel', '5e7']), {
316
+ foo: 'bar',
317
+ })
318
+ expect(result.name).toBe('5e7')
319
+ expect(result.kind).toBe('testYodel')
320
+
321
+ // autogenerate key
322
+ const result2 = await kvStore.set(kvStore.key(['testYodel']), { foo: 'bar' })
323
+ expect(result2.kind).toBe('testYodel')
324
+ })
325
+
326
+ test('save / upsert', async () => {
327
+ // expect.assertions(2);
328
+ const kvStore = getDstore()
329
+ const entity = {
330
+ key: kvStore.key(['testYodel', 3]),
331
+ data: { foo: 'bar' } as any,
332
+ }
333
+ const result = await kvStore.save([entity])
334
+ // const result2 = await kvStore.upsert([entity]);
335
+ expect(result?.[0]?.mutationResults?.[0]?.conflictDetected).toBe(false)
336
+ expect(entity.data._keyStr).toMatchInlineSnapshot('"agByDwsSCXRlc3RZb2RlbBgDDKIBBHRlc3Q"')
337
+ expect(entity.data.foo).toBe('bar')
338
+ expect(entity.data[Datastore.KEY].kind).toBe('testYodel')
339
+ })
340
+
341
+ test('update', async (t) => {
342
+ // expect.assertions(3);
343
+ const kvStore = getDstore()
344
+ const keyName = `4insert${Math.random()}`
345
+ const entity = {
346
+ key: kvStore.key(['testYodel', keyName]),
347
+ data: { foo: 'bar' },
348
+ }
349
+ // const request = kvStore.update([entity]);
350
+ // await expect(request).rejects.toThrowError(Error);
351
+
352
+ await kvStore.save([entity])
353
+ const result = await kvStore.update([entity])
354
+ expect(result?.[0]?.mutationResults?.[0]?.conflictDetected).toBe(false)
355
+ expect(result?.[0]?.mutationResults?.[0]?.key).toBe(null)
356
+ // expect(result?.[0]?.indexUpdates).toBe(2);
357
+ })
358
+
359
+ test('insert / delete', async (t) => {
360
+ // expect.assertions(2);
361
+ const kvStore = getDstore()
362
+ const testkey = kvStore.key(['testYodel', 4])
363
+ await kvStore.delete([testkey])
364
+ const entity = {
365
+ key: testkey,
366
+ data: { foo: 'bar' } as any,
367
+ }
368
+ const result = await kvStore.insert([entity])
369
+
370
+ expect(result?.[0]?.mutationResults?.[0]?.conflictDetected).toBe(false)
371
+ expect(result?.[0]?.mutationResults?.[0]?.version).toMatch(/\d+/)
372
+ expect(entity.data.foo).toBe('bar')
373
+ expect(entity.key.path[0]).toBe('testYodel')
374
+ // expect(result?.[0]?.indexUpdates).toBe(3);
375
+
376
+ const result2 = await kvStore.delete([entity.key])
377
+ expect(result2?.[0]?.mutationResults?.[0]?.conflictDetected).toBe(false)
378
+ })
379
+
380
+ test('exception', async () => {
381
+ // expect.assertions(2);
382
+ const kvStore = getDstore()
383
+ try {
384
+ const result = await kvStore.set(kvStore.key(['testYodel', NaN]), {
385
+ foo: 'bar',
386
+ })
387
+ } catch (e) {
388
+ expect(e.stack).toMatch(/Dstore\.set/)
389
+ }
390
+ })
391
+ // describe("Transactions", () => {
392
+ // it("simple", async () => {
393
+ // expect.assertions(2);
394
+ // const kvStore = getDstore("huwawi3Datastore");
395
+ // const outerResult = await kvStore.runInTransaction<any>(async () => {
396
+ // // write inside the transaction
397
+ // const entity = { key: kvStore.key(["testYodel", 6]), data: { foo: "foobar" } };
398
+ // const innerResult = await kvStore.save([entity]);
399
+ // // save does not return anything within transactions
400
+ // expect(innerResult).toMatchInlineSnapshot(`undefined`);
401
+ // return 123;
402
+ // });
403
+ // expect(outerResult).toMatchInlineSnapshot(`123`);
404
+
405
+ // // this fails in the Datastore Emulator
406
+ // const entitiey = await kvStore.get(kvStore.key(["testYodel", 6]));
407
+ // // expect(entitiey).toMatchInlineSnapshot(`
408
+ // // Object {
409
+ // // "foo": "foobar",
410
+ // // Symbol(KEY): Key {
411
+ // // "id": "6",
412
+ // // "kind": "testYodel",
413
+ // // "namespace": "test",
414
+ // // "path": Array [
415
+ // // "testYodel",
416
+ // // "6",
417
+ // // ],
418
+ // // },
419
+ // // }
420
+ // // `);
421
+ // });
422
+
423
+ // it("throws", async () => {
424
+ // expect.assertions(1);
425
+ // const kvStore = getDstore("huwawi3Datastore");
426
+ // const request = kvStore.runInTransaction<any>(async () => {
427
+ // throw new DstoreError("TestError", undefined);
428
+ // });
429
+ // await expect(request).rejects.toThrowError(DstoreError);
430
+ // });
431
+ // });
432
+
433
+ // describe('Exceptions', () => {
434
+ // it('simple', async () => {
435
+ // const t = () => {
436
+ // throw new DstoreError('bla', undefined);
437
+ // };
438
+ // expect(t).toThrow(DstoreError);
439
+ // });
440
+ // });