Created
December 12, 2021 11:52
-
-
Save mdornseif/fb6a2175f16ce0d17ce3f414ca6419a8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* myDatastore.test.ts - establish how the Datastore works. | |
* | |
* Created by Dr. Maximillian Dornseif 2021-12-12 in huwawi3backend 11.10.0 | |
* Copyright (c) 2021 Dr. Maximillian Dornseif | |
*/ | |
import { Datastore, Key } from "@google-cloud/datastore"; | |
describe("Key", () => { | |
const datastore = new Datastore({ namespace: "test" }); | |
it("shape of a key", () => { | |
expect(datastore.key(["Yodel", 123])).toMatchInlineSnapshot(` | |
Key { | |
"id": 123, | |
"kind": "Yodel", | |
"namespace": "test", | |
"path": Array [ | |
"Yodel", | |
123, | |
], | |
} | |
`); | |
expect(datastore.key(["Yodel", "vierfünfsechs"])).toMatchInlineSnapshot(` | |
Key { | |
"kind": "Yodel", | |
"name": "vierfünfsechs", | |
"namespace": "test", | |
"path": Array [ | |
"Yodel", | |
"vierfünfsechs", | |
], | |
} | |
`); | |
}); | |
it("incomplete key", () => { | |
expect(datastore.key(["Yodel"])).toMatchInlineSnapshot(` | |
Key { | |
"kind": "Yodel", | |
"namespace": "test", | |
"path": Array [ | |
"Yodel", | |
undefined, | |
], | |
} | |
`); | |
}); | |
it("key with path", () => { | |
expect(datastore.key(["Yodel", 1, "Juche", 2])).toMatchInlineSnapshot(` | |
Key { | |
"id": 2, | |
"kind": "Juche", | |
"namespace": "test", | |
"parent": Key { | |
"id": 1, | |
"kind": "Yodel", | |
"namespace": "test", | |
"path": Array [ | |
"Yodel", | |
1, | |
], | |
}, | |
"path": Array [ | |
"Yodel", | |
1, | |
"Juche", | |
2, | |
], | |
} | |
`); | |
}); | |
it("serialize to object", () => { | |
expect(datastore.key(["Yodel", 123]).serialized).toMatchInlineSnapshot(` | |
Object { | |
"namespace": "test", | |
"path": Array [ | |
"Yodel", | |
Object { | |
"type": "DatastoreInt", | |
"value": "123", | |
}, | |
], | |
} | |
`); | |
expect(datastore.key(["Yodel", "vierfünfsechs"]).serialized).toMatchInlineSnapshot(` | |
Object { | |
"namespace": "test", | |
"path": Array [ | |
"Yodel", | |
"vierfünfsechs", | |
], | |
} | |
`); | |
}); | |
it("urlsafe", async () => { | |
expect.assertions(3); | |
let ser = await datastore.keyToLegacyUrlSafe(datastore.key(["Yodel", "vierfünfsechs"])); | |
expect(ser).toMatchInlineSnapshot(` | |
Array [ | |
"agdodXdhd2kzchkLEgVZb2RlbCIOdmllcmbDvG5mc2VjaHMMogEEdGVzdA", | |
] | |
`); | |
ser = await datastore.keyToLegacyUrlSafe(datastore.key(["Yodel", 123])); | |
expect(ser).toMatchInlineSnapshot(` | |
Array [ | |
"agdodXdhd2kzcgsLEgVZb2RlbBh7DKIBBHRlc3Q", | |
] | |
`); | |
expect(datastore.keyFromLegacyUrlsafe(ser[0])).toMatchInlineSnapshot(` | |
Key { | |
"id": "123", | |
"kind": "Yodel", | |
"namespace": "test", | |
"path": Array [ | |
"Yodel", | |
"123", | |
], | |
} | |
`); | |
}); | |
it("allocation", async () => { | |
expect.assertions(3); | |
const [keys, info] = await datastore.allocateIds(datastore.key(["Yodel"]), 1); | |
expect(keys).toHaveLength(1); | |
expect(keys?.[0].kind).toMatchInlineSnapshot(`"Yodel"`); | |
// expect(keys).toMatchInlineSnapshot(` | |
// Array [ | |
// Key { | |
// "id": "5762303387500544", | |
// "kind": "Yodel", | |
// "namespace": "test", | |
// "path": Array [ | |
// "Yodel", | |
// "5762303387500544", | |
// ], | |
// }, | |
// ] | |
// `); | |
expect(info.keys).toHaveLength(1); | |
// expect(info).toMatchInlineSnapshot(` | |
// Object { | |
// "keys": Array [ | |
// Object { | |
// "partitionId": Object { | |
// "namespaceId": "test", | |
// "projectId": "huwawi3", | |
// }, | |
// "path": Array [ | |
// Object { | |
// "id": "5764581666324480", | |
// "idType": "id", | |
// "kind": "Yodel", | |
// }, | |
// ], | |
// }, | |
// ], | |
// } | |
// `); | |
}); | |
it("invalid allocation", async () => { | |
const request = datastore.allocateIds(datastore.key(["Yodel", 1]), 1); | |
await expect(request).rejects.toThrowError(); // An incomplete key should be provided. | |
}); | |
}); | |
describe("Read", () => { | |
const datastore = new Datastore({ namespace: "test" }); | |
it("get nothing", async () => { | |
expect.assertions(2); | |
// geting non-existant entities does not produce any error | |
const result1 = await datastore.get(datastore.key(["YodelNie", 1])); | |
expect(result1).toMatchInlineSnapshot(` | |
Array [ | |
undefined, | |
] | |
`); | |
const result2 = await datastore.get([ | |
datastore.key(["YodelNie", 1]), | |
datastore.key(["YodelNie", "zwei"]), | |
datastore.key(["YodelNie", 1]), | |
]); | |
expect(result2).toMatchInlineSnapshot(` | |
Array [ | |
Array [], | |
] | |
`); | |
}); | |
it("get numid", async () => { | |
expect.assertions(4); | |
// write some test data | |
const entity = { key: datastore.key(["Yodel", 1]), data: { foo: "bar" } }; | |
await datastore.save([entity]); | |
const result1 = await datastore.get(datastore.key(["Yodel", 1])); | |
// Returns a Array of entities | |
// Numeric IDs are formated as string (!) | |
expect(result1).toMatchInlineSnapshot(` | |
Array [ | |
Object { | |
"foo": "bar", | |
Symbol(KEY): Key { | |
"id": "1", | |
"kind": "Yodel", | |
"namespace": "test", | |
"path": Array [ | |
"Yodel", | |
"1", | |
], | |
}, | |
}, | |
] | |
`); | |
expect(result1[0][Datastore.KEY]).toBeInstanceOf(Key); | |
const result2 = await datastore.get([ | |
datastore.key(["YodelNie", "zwei"]), | |
datastore.key(["Yodel", 1]), | |
datastore.key(["YodelNie", 1]), | |
]); | |
// Returns a Array of Array of entities | |
expect(result2).toMatchInlineSnapshot(` | |
Array [ | |
Array [ | |
Object { | |
"foo": "bar", | |
Symbol(KEY): Key { | |
"id": "1", | |
"kind": "Yodel", | |
"namespace": "test", | |
"path": Array [ | |
"Yodel", | |
"1", | |
], | |
}, | |
}, | |
], | |
] | |
`); | |
expect(result2[0][0][Datastore.KEY]).toBeInstanceOf(Key); | |
}); | |
it("get name", async () => { | |
expect.assertions(2); | |
// write some test data | |
const entity = { key: datastore.key(["Yodel", "zwei"]), data: { foo: "bar" } }; | |
await datastore.save([entity]); | |
const result = await datastore.get(entity.key); | |
expect(result).toMatchInlineSnapshot(` | |
Array [ | |
Object { | |
"foo": "bar", | |
Symbol(KEY): Key { | |
"kind": "Yodel", | |
"name": "zwei", | |
"namespace": "test", | |
"path": Array [ | |
"Yodel", | |
"zwei", | |
], | |
}, | |
}, | |
] | |
`); | |
expect(result[0][Datastore.KEY]).toBeInstanceOf(Key); | |
}); | |
it("query", async () => { | |
expect.assertions(3); | |
// write some test data | |
const entity = { key: datastore.key(["Yodel", 3]), data: { foo: "baz" } }; | |
await datastore.save([entity]); | |
const query = datastore.createQuery("Yodel"); | |
query.filter("foo", "=", "baz"); | |
query.limit(10); | |
const [entities, runQueryInfo] = await datastore.runQuery(query); | |
expect(entities).toMatchInlineSnapshot(` | |
Array [ | |
Object { | |
"foo": "baz", | |
Symbol(KEY): Key { | |
"id": "3", | |
"kind": "Yodel", | |
"namespace": "test", | |
"path": Array [ | |
"Yodel", | |
"3", | |
], | |
}, | |
}, | |
] | |
`); | |
expect(entities[0][Datastore.KEY]).toBeInstanceOf(Key); | |
expect(runQueryInfo).toMatchInlineSnapshot(` | |
Object { | |
"endCursor": "CiUSH2oJaH5odXdhd2kzcgsLEgVZb2RlbBgDDKIBBHRlc3QYACAA", | |
"moreResults": "NO_MORE_RESULTS", | |
} | |
`); | |
}); | |
}); | |
describe("Writing", () => { | |
const datastore = new Datastore({ namespace: "test" }); | |
it("deletes", async () => { | |
const entity = { key: datastore.key(["Yodel", 3]), data: { foo: "baz" } }; | |
await datastore.save([entity]); | |
const request1 = await datastore.delete(datastore.key(["Yodel", 3])); | |
expect(request1).toHaveLength(3); | |
// expect(request1).toMatchInlineSnapshot(` | |
// Array [ | |
// Object { | |
// "indexUpdates": 3, | |
// "mutationResults": Array [ | |
// Object { | |
// "conflictDetected": false, | |
// "key": null, | |
// "version": "1639303975872936", | |
// }, | |
// ], | |
// }, | |
// undefined, | |
// undefined, | |
// ] | |
// `); | |
// Second request does nothing. | |
const request2 = await datastore.delete([datastore.key(["Yodel", 3])]); | |
expect(request2?.[0]?.indexUpdates).toBe(0); | |
}); | |
it("save", async () => { | |
expect.assertions(5); | |
const inclompleteKey = datastore.key(["Yodel"]); | |
const entity = { key: inclompleteKey, data: { foo: "bar" } }; | |
// entity contains incomplete key | |
expect(entity).toMatchInlineSnapshot(` | |
Object { | |
"data": Object { | |
"foo": "bar", | |
}, | |
"key": Key { | |
"kind": "Yodel", | |
"namespace": "test", | |
"path": Array [ | |
"Yodel", | |
undefined, | |
], | |
}, | |
} | |
`); | |
const result = await datastore.save([entity]); | |
// result contains information about the newly allocated id | |
// This is not a valid Key Object! | |
expect(result?.[0]?.mutationResults?.[0]?.key?.path?.[0]?.kind).toMatchInlineSnapshot(`"Yodel"`); | |
// expect(result).toMatchInlineSnapshot(` | |
// Array [ | |
// Object { | |
// "indexUpdates": 3, | |
// "mutationResults": Array [ | |
// Object { | |
// "conflictDetected": false, | |
// "key": Object { | |
// "partitionId": Object { | |
// "namespaceId": "test", | |
// "projectId": "huwawi3", | |
// }, | |
// "path": Array [ | |
// Object { | |
// "id": "5792384667353088", | |
// "idType": "id", | |
// "kind": "Yodel", | |
// }, | |
// ], | |
// }, | |
// "version": "1639304561408439", | |
// }, | |
// ], | |
// }, | |
// ] | |
// `); | |
// entity now contains the updated key | |
expect(entity.key.id).toBeDefined(); | |
// expect(entity).toMatchInlineSnapshot(` | |
// Object { | |
// "data": Object { | |
// "foo": "bar", | |
// }, | |
// "key": Key { | |
// "id": "5151978652958720", | |
// "kind": "Yodel", | |
// "namespace": "test", | |
// "path": Array [ | |
// "Yodel", | |
// "5151978652958720", | |
// ], | |
// }, | |
// } | |
// `); | |
// entity.data[Datastore.KEY] is NOT present | |
expect(entity.data[Datastore.KEY]).toMatchInlineSnapshot(`undefined`); | |
// saving again is no problem | |
const result2 = await datastore.save([entity]); | |
expect(result2?.[0]?.indexUpdates).toBe(0); | |
// expect(result2).toMatchInlineSnapshot(` | |
// Array [ | |
// Object { | |
// "indexUpdates": 0, | |
// "mutationResults": Array [ | |
// Object { | |
// "conflictDetected": false, | |
// "key": null, | |
// "version": "1639304810306626", | |
// }, | |
// ], | |
// }, | |
// ] | |
// `); | |
// cleanup | |
await datastore.delete([entity.key]); | |
}); | |
it("upsert", async () => { | |
// upsert seems to be a synonym for save on google datastore | |
expect.assertions(6); | |
const inclompleteKey = datastore.key(["Yodel"]); | |
const entity = { key: inclompleteKey, data: { foo: "bar" } }; | |
const commitResponse = await datastore.upsert([entity]); | |
expect(commitResponse?.[0]?.indexUpdates).toBe(3); | |
// entity now contains the updated key | |
expect(entity.key.id).toBeDefined(); | |
// expect(entity).toMatchInlineSnapshot(` | |
// Object { | |
// "data": Object { | |
// "foo": "bar", | |
// }, | |
// "key": Key { | |
// "id": "5151978652958720", | |
// "kind": "Yodel", | |
// "namespace": "test", | |
// "path": Array [ | |
// "Yodel", | |
// "5151978652958720", | |
// ], | |
// }, | |
// } | |
// `); | |
// entity.data[Datastore.KEY] is NOT present | |
expect(entity.data[Datastore.KEY]).toMatchInlineSnapshot(`undefined`); | |
// saving again is no problem | |
const result2 = await datastore.upsert([entity]); | |
expect(result2?.[0]?.indexUpdates).toBe(0); | |
// existing Data in the datastore is not merged but overwritten | |
entity.data = { bar: "foo" } as any; | |
const result3 = await datastore.upsert([entity]); | |
expect(result3?.[0]?.indexUpdates).toBe(4); | |
expect(entity.data).toMatchInlineSnapshot(` | |
Object { | |
"bar": "foo", | |
} | |
`); | |
// cleanup | |
await datastore.delete([entity.key]); | |
}); | |
it("insert", async () => { | |
// insert should only be allowed for new data | |
expect.assertions(5); | |
const inclompleteKey = datastore.key(["Yodel"]); | |
const entity = { key: inclompleteKey, data: { foo: "bar" } }; | |
const commitResponse = await datastore.insert([entity]); | |
expect(commitResponse?.[0]?.indexUpdates).toBe(3); | |
// entity now contains the updated key | |
expect(entity.key.id).toBeDefined(); | |
// expect(entity).toMatchInlineSnapshot(` | |
// Object { | |
// "data": Object { | |
// "foo": "bar", | |
// }, | |
// "key": Key { | |
// "id": "4907028078133248", | |
// "kind": "Yodel", | |
// "namespace": "test", | |
// "path": Array [ | |
// "Yodel", | |
// "4907028078133248", | |
// ], | |
// }, | |
// } | |
// `); | |
// entity.data[Datastore.KEY] is NOT present | |
expect(entity.data[Datastore.KEY]).toMatchInlineSnapshot(`undefined`); | |
// inserting again does throw an exception | |
const request = datastore.insert([entity]); | |
await expect(request).rejects.toThrowError(Error); | |
await expect(request).rejects.toThrow("ALREADY_EXISTS"); | |
// await expect(request).rejects.toMatchInlineSnapshot(` | |
// [Error: 6 ALREADY_EXISTS: entity already exists: app: "h~huwawi3" | |
// name_space: "test" | |
// path < | |
// Element { | |
// type: "Yodel" | |
// id: 0x13c35f4e800000 | |
// } | |
// > | |
// ] | |
// `); | |
// cleanup | |
await datastore.delete([entity.key]); | |
}); | |
it("update", async () => { | |
expect.assertions(7); | |
const inclompleteKey = datastore.key(["Yodel"]); | |
const entity = { key: inclompleteKey, data: { foo: "bar" } }; | |
// update with incomplete key does throw an error | |
const request = datastore.update([entity]); | |
await expect(request).rejects.toThrowError(Error); | |
await expect(request).rejects.toThrow("INVALID_ARGUMENT"); | |
await expect(request).rejects.toMatchInlineSnapshot( | |
`[Error: 3 INVALID_ARGUMENT: Key path element must not be incomplete: [Yodel: ]]` | |
); | |
// Save and get a complete key | |
await datastore.save([entity]); | |
// try updating again | |
const commitResponse = await datastore.update([entity]); | |
expect(commitResponse?.[0]?.indexUpdates).toBe(0); | |
// expect(commitResponse).toMatchInlineSnapshot(` | |
// Array [ | |
// Object { | |
// "indexUpdates": 0, | |
// "mutationResults": Array [ | |
// Object { | |
// "conflictDetected": false, | |
// "key": null, | |
// "version": 2, | |
// }, | |
// ], | |
// }, | |
// ] | |
// `); | |
// existing Data in the datastore is not merged but overwritten | |
entity.data = { bar: "foo" } as any; | |
const commitResponse2 = await datastore.upsert([entity]); | |
expect(commitResponse2?.[0]?.indexUpdates).toBe(4); | |
expect(entity.data).toMatchInlineSnapshot(` | |
Object { | |
"bar": "foo", | |
} | |
`); | |
expect(entity.data[Datastore.KEY]).toMatchInlineSnapshot(`undefined`); | |
}); | |
}); | |
describe("Transactions", () => { | |
const datastore = new Datastore({ namespace: "test" }); | |
it("commit", async () => { | |
expect.assertions(11); | |
const entity = { key: datastore.key(["Yodel", 5]), data: { foo: "preTransactionData" } }; | |
await datastore.save([entity]); | |
const transaction = datastore.transaction(); | |
const runResponse = await transaction.run(); | |
// this breaks pretty-print: expect(runResponse).toMatchInlineSnapshot(); | |
entity.data = { foo: Math.random() } as any; | |
const saveResponse = await transaction.save([entity]); | |
expect(saveResponse).toBe(undefined); | |
const updateResponse = await transaction.update([entity]); | |
expect(updateResponse).toBe(undefined); | |
const getResponse = await transaction.get([entity.key]); | |
// CARVE! We read the data as it has been before the transaction started | |
expect(getResponse).toMatchInlineSnapshot(` | |
Array [ | |
Array [ | |
Object { | |
"foo": "preTransactionData", | |
Symbol(KEY): Key { | |
"id": "5", | |
"kind": "Yodel", | |
"namespace": "test", | |
"path": Array [ | |
"Yodel", | |
"5", | |
], | |
}, | |
}, | |
], | |
] | |
`); | |
const commitResponse = await transaction.commit(); | |
expect(commitResponse?.[0]?.indexUpdates).toBe(4); | |
// expect(commitResponse).toMatchInlineSnapshot(` | |
// Array [ | |
// Object { | |
// "indexUpdates": 3, | |
// "mutationResults": Array [ | |
// Object { | |
// "conflictDetected": false, | |
// "key": null, | |
// "version": "1639307955580710", | |
// }, | |
// ], | |
// }, | |
// ] | |
// `); | |
const getResponse2 = await datastore.get(entity.key); | |
// We read the data now as it was written within the transaction | |
expect(getResponse2?.[0]?.foo).toBe(entity.data.foo); | |
// After commit reading from the TRansaction is impossible | |
const lateGet = transaction.get(entity.key); | |
await expect(lateGet).rejects.toThrowError(Error); | |
await expect(lateGet).rejects.toThrow("INVALID_ARGUMENT"); | |
await expect(lateGet).rejects.toMatchInlineSnapshot( | |
`[Error: 3 INVALID_ARGUMENT: The referenced transaction has expired or is no longer valid.]` | |
); | |
// Rollback is impossible after commit | |
const rollbackResponse = transaction.rollback(); | |
await expect(rollbackResponse).rejects.toThrowError(Error); | |
await expect(rollbackResponse).rejects.toThrow("INVALID_ARGUMENT"); | |
await expect(rollbackResponse).rejects.toMatchInlineSnapshot( | |
`[Error: 3 INVALID_ARGUMENT: The referenced transaction has expired or is no longer valid.]` | |
); | |
}); | |
it("rollback", async () => { | |
expect.assertions(11); | |
const entity = { key: datastore.key(["Yodel", 5]), data: { foo: "preTransactionData" } }; | |
await datastore.save([entity]); | |
const transaction = datastore.transaction(); | |
await transaction.run(); | |
const deleteResponse = await transaction.delete([entity.key]); | |
expect(deleteResponse).toBe(undefined); | |
const [keys, info] = await transaction.allocateIds(datastore.key(["Yodel"]), 1); | |
expect(keys).toHaveLength(1); | |
expect(keys?.[0].kind).toMatchInlineSnapshot(`"Yodel"`); | |
const getResponse = await transaction.get([entity.key]); | |
// CARVE! We read the data as it has been before the transaction started | |
expect(getResponse).toMatchInlineSnapshot(` | |
Array [ | |
Array [ | |
Object { | |
"foo": "preTransactionData", | |
Symbol(KEY): Key { | |
"id": "5", | |
"kind": "Yodel", | |
"namespace": "test", | |
"path": Array [ | |
"Yodel", | |
"5", | |
], | |
}, | |
}, | |
], | |
] | |
`); | |
const rollbackResponse = await transaction.rollback(); | |
// expect(rollbackResponse?.[0]?.indexUpdates).toBe(4); | |
expect(rollbackResponse).toMatchInlineSnapshot(` | |
Array [ | |
Object {}, | |
] | |
`); | |
// We read the data now as it was written within the transaction | |
const getResponse2 = await datastore.get(entity.key); | |
expect(getResponse2?.[0]?.foo).toBe(entity.data.foo); | |
// After commit reading from the Transaction is impossible | |
const lateGet = transaction.get(entity.key); | |
await expect(lateGet).rejects.toThrowError(Error); | |
await expect(lateGet).rejects.toThrow("INVALID_ARGUMENT"); | |
await expect(lateGet).rejects.toMatchInlineSnapshot( | |
`[Error: 3 INVALID_ARGUMENT: The referenced transaction has expired or is no longer valid.]` | |
); | |
// Commit IS possible after rollback | |
const commitResponse = await transaction.commit(); | |
expect(commitResponse).toMatchInlineSnapshot(`Array []`); | |
// Commiting the rolled back transaction is a NOOP. | |
const getResponse3 = await datastore.get(entity.key); | |
expect(getResponse3?.[0]?.foo).toBe(entity.data.foo); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment