Skip to content

Instantly share code, notes, and snippets.

@mdornseif
Created December 12, 2021 11:52
Show Gist options
  • Save mdornseif/fb6a2175f16ce0d17ce3f414ca6419a8 to your computer and use it in GitHub Desktop.
Save mdornseif/fb6a2175f16ce0d17ce3f414ca6419a8 to your computer and use it in GitHub Desktop.
/*
* 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