UNPKG

@x5e/gink

Version:

an eventually consistent database

321 lines 17.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.testStore = testStore; const lodash_1 = require("lodash"); const Decomposition_1 = require("../implementation/Decomposition"); const builders_1 = require("../implementation/builders"); const test_utils_1 = require("./test_utils"); const utils_1 = require("../implementation/utils"); const implementation_1 = require("../implementation"); const libsodium_wrappers_1 = require("libsodium-wrappers"); // Jest complains if there's a test suite without a test. it("placeholder", () => { expect(1 + 2).toBe(3); }); /** * * @param storeMaker must return a fresh (empty) store on each invocation * @param implName name of this implementation * @param replacer thing to check when using persistence */ function testStore(implName, storeMaker, replacer) { let store; beforeEach(async () => { store = await storeMaker(); await store.ready; }); afterEach(async () => { await store.close(); }); /* it(`${implName} test accepts chain start but only once`, async () => { const chainStart = makeChainStart("Hello, World!", MEDALLION1, START_MICROS1); const [_info1, acceptedOnce] = await store.addBundle(chainStart); const [_info2, acceptedTwice] = await store.addBundle(chainStart); expect(acceptedOnce).toBeTruthy(); expect(acceptedTwice).toBeFalsy(); }); */ it(`${implName} ensure that it rejects when doesn't have chain start`, async () => { const chainStart = await (0, test_utils_1.makeChainStart)("Hello, World!", test_utils_1.MEDALLION1, test_utils_1.START_MICROS1); const secondTrxn = await (0, test_utils_1.extendChain)("Hello, again!", chainStart, test_utils_1.NEXT_TS1); let added = false; let barfed = false; try { const result = await store.addBundle(secondTrxn); added = result[1]; } catch (e) { barfed = true; } expect(added).toBeFalsy(); expect(barfed).toBeTruthy(); }); it(`${implName} test rejects missing link`, async () => { const chainStart = await (0, test_utils_1.makeChainStart)("Hello, World!", test_utils_1.MEDALLION1, test_utils_1.START_MICROS1); const secondTrxn = await (0, test_utils_1.extendChain)("Hello, again!", chainStart, test_utils_1.NEXT_TS1); const thirdTrxn = await (0, test_utils_1.extendChain)("Hello, a third!", secondTrxn, test_utils_1.NEXT_TS1 + 1); await store.addBundle(chainStart); let added = false; let barfed = false; try { const result = await store.addBundle(thirdTrxn); added = result[1]; } catch (e) { barfed = true; } expect(added).toBeFalsy(); expect(barfed).toBeTruthy(); }); it(`${implName} test creates greeting`, async () => { await (0, test_utils_1.addTrxns)(store); const hasMap = await store.getChainTracker(); expect(hasMap.getBundleInfo([test_utils_1.MEDALLION1, test_utils_1.START_MICROS1]).timestamp).toBe(test_utils_1.NEXT_TS1); expect(hasMap.getBundleInfo([test_utils_1.MEDALLION2, test_utils_1.START_MICROS2]).timestamp).toBe(test_utils_1.NEXT_TS2); }); it(`${implName} test sends trxns in order`, async () => { await (0, test_utils_1.addTrxns)(store); if (replacer) { await store.close(); store = await replacer(); } const sent = []; await store.getBundles((x) => { sent.push(x.bytes); }); expect(sent.length).toBe(4); expect((0, test_utils_1.unbundle)(sent[0]).getTimestamp()).toBe(test_utils_1.START_MICROS1); expect((0, test_utils_1.unbundle)(sent[1]).getTimestamp()).toBe(test_utils_1.START_MICROS2); expect((0, test_utils_1.unbundle)(sent[2]).getTimestamp()).toBe(test_utils_1.NEXT_TS1); expect((0, test_utils_1.unbundle)(sent[3]).getTimestamp()).toBe(test_utils_1.NEXT_TS2); }); it(`${implName} test save/fetch container`, async () => { const bundleBuilder = new builders_1.BundleBuilder(); bundleBuilder.setChainStart(test_utils_1.START_MICROS1); bundleBuilder.setTimestamp(test_utils_1.START_MICROS1); bundleBuilder.setMedallion(test_utils_1.MEDALLION1); bundleBuilder.setVerifyKey((await test_utils_1.keyPair).publicKey); bundleBuilder.setIdentity("test-container"); const changeBuilder = new builders_1.ChangeBuilder(); const containerBuilder = new builders_1.ContainerBuilder(); containerBuilder.setBehavior(builders_1.Behavior.DIRECTORY); changeBuilder.setContainer(containerBuilder); bundleBuilder.getChangesList().push(changeBuilder); const decomposition = new Decomposition_1.Decomposition((0, utils_1.signBundle)(bundleBuilder.serializeBinary(), (await test_utils_1.keyPair).secretKey)); await store.addBundle(decomposition); (0, utils_1.ensure)(decomposition.info.medallion === test_utils_1.MEDALLION1); (0, utils_1.ensure)(decomposition.info.timestamp === test_utils_1.START_MICROS1); const containerBytes = await store.getContainerBytes({ medallion: test_utils_1.MEDALLION1, timestamp: test_utils_1.START_MICROS1, offset: 1, }); (0, utils_1.ensure)(containerBytes); const containerBuilder2 = (builders_1.ContainerBuilder.deserializeBinary(containerBytes)); (0, utils_1.ensure)(containerBuilder2.getBehavior() === builders_1.Behavior.DIRECTORY); }); it(`${implName} create / view Entry`, async () => { const bundleBuilder = new builders_1.BundleBuilder(); const sourceAddress = { medallion: 1, timestamp: 2, offset: 3 }; const entryBuilder = new builders_1.EntryBuilder(); entryBuilder .setBehavior(builders_1.Behavior.DIRECTORY) .setContainer((0, utils_1.muidToBuilder)(sourceAddress)) .setKey((0, utils_1.wrapKey)("abc")) .setValue((0, utils_1.wrapValue)("xyz")); const changeBuilder = new builders_1.ChangeBuilder(); changeBuilder.setEntry(entryBuilder); bundleBuilder.getChangesList().push(changeBuilder); bundleBuilder.setChainStart(5); bundleBuilder.setTimestamp(5); bundleBuilder.setMedallion(101); bundleBuilder.setIdentity("Fred"); bundleBuilder.setVerifyKey((await test_utils_1.keyPair).publicKey); const asBytes = bundleBuilder.serializeBinary(); const signed = (0, utils_1.signBundle)(asBytes, (await test_utils_1.keyPair).secretKey); const view = new Decomposition_1.Decomposition(signed); await store.addBundle(view); const entry = await store.getEntryByKey(sourceAddress, "abc"); (0, utils_1.ensure)(entry); (0, utils_1.ensure)((0, utils_1.matches)(entry.containerId, [2, 1, 3])); (0, utils_1.ensure)((0, utils_1.matches)(entry.entryId, [5, 101, 1])); (0, utils_1.ensure)(entry.value === "xyz"); (0, utils_1.ensure)(entry.storageKey === "abc"); }); it(`${implName} getChainIdentity works`, async () => { const db = new implementation_1.Database({ store, identity: "test@identity" }); await db.ready; await implementation_1.Directory.get(db).set(3, 4); const lastLink = db.getLastLink(); const identity = await store.getChainIdentity([ lastLink.medallion, lastLink.chainStart, ]); (0, utils_1.ensure)(identity === "test@identity", `m=${lastLink.medallion} cs=${lastLink.chainStart} identity=${identity}`); }); it(`${implName} getContainersByName`, async () => { const db = new implementation_1.Database({ store, identity: "test@byName" }); await db.ready; const gd = implementation_1.Directory.get(db); await gd.setName("foo"); const d2 = await implementation_1.Directory.create(db); await d2.setName("bar"); const seq = await implementation_1.Sequence.create(db); await seq.setName("bar"); const fooContainers = await store.getContainersByName("foo"); (0, utils_1.ensure)(fooContainers.length === 1); (0, utils_1.ensure)(fooContainers[0].timestamp === gd.timestamp); (0, utils_1.ensure)(fooContainers[0].medallion === gd.medallion); const barContainers = await store.getContainersByName("bar"); (0, utils_1.ensure)(barContainers.length === 2); (0, utils_1.ensure)(barContainers[0].timestamp === d2.timestamp); (0, utils_1.ensure)(barContainers[0].medallion === d2.medallion); (0, utils_1.ensure)(barContainers[1].timestamp === seq.timestamp); (0, utils_1.ensure)(barContainers[1].medallion === seq.medallion); await seq.setName("baz"); const barContainers2 = await store.getContainersByName("bar"); (0, utils_1.ensure)(barContainers2.length === 1); (0, utils_1.ensure)(barContainers2[0].timestamp === d2.timestamp); (0, utils_1.ensure)(barContainers2[0].medallion === d2.medallion); const bazContainers = await store.getContainersByName("baz"); (0, utils_1.ensure)(bazContainers.length === 1); (0, utils_1.ensure)(bazContainers[0].timestamp === seq.timestamp); (0, utils_1.ensure)(bazContainers[0].medallion === seq.medallion); await seq.setName("last"); const bazContainers2 = await store.getContainersByName("baz"); (0, utils_1.ensure)(bazContainers2.length === 0); const lastContainers = await store.getContainersByName("last"); (0, utils_1.ensure)(lastContainers.length === 1); (0, utils_1.ensure)(lastContainers[0].timestamp === seq.timestamp); (0, utils_1.ensure)(lastContainers[0].medallion === seq.medallion); }); it(`${implName} bundle properly handles identities`, async () => { await store.ready; // Try to add a start a chain without an identity const kp1 = await test_utils_1.keyPair; const bundleBuilder = new builders_1.BundleBuilder(); bundleBuilder.setChainStart(test_utils_1.START_MICROS1); bundleBuilder.setTimestamp(test_utils_1.START_MICROS1); bundleBuilder.setMedallion(test_utils_1.MEDALLION1); bundleBuilder.setComment("should error"); bundleBuilder.setVerifyKey(kp1.publicKey); const decomp = new Decomposition_1.Decomposition((0, utils_1.signBundle)(bundleBuilder.serializeBinary(), kp1.secretKey)); let errored = false; try { await store.addBundle(decomp); } catch { errored = true; } (0, utils_1.ensure)(errored, "chain start bundle allowed without identity?"); // Add a chain start with an identity const decomp2 = await (0, test_utils_1.makeChainStart)("should not error", test_utils_1.MEDALLION2, test_utils_1.START_MICROS2); const added = await store.addBundle(decomp2); (0, utils_1.ensure)(added, "adding chain start bundle with identity failed"); // Now identities should not be allowed for subsequent bundles const kp3 = await test_utils_1.keyPair; const bundleBuilder3 = new builders_1.BundleBuilder(); const parsedPrevious = decomp2.info; bundleBuilder3.setMedallion(parsedPrevious.medallion); bundleBuilder3.setPrevious(parsedPrevious.timestamp); bundleBuilder3.setChainStart(parsedPrevious.chainStart); bundleBuilder3.setTimestamp(test_utils_1.NEXT_TS1); bundleBuilder3.setComment("should error again"); bundleBuilder3.setVerifyKey(kp3.publicKey); bundleBuilder3.setIdentity("error-identity"); const priorHash = decomp2.info.hashCode; (0, utils_1.ensure)(priorHash && priorHash.length === 32); bundleBuilder3.setPriorHash(priorHash); const decomp3 = new Decomposition_1.Decomposition((0, utils_1.signBundle)(bundleBuilder3.serializeBinary(), kp3.secretKey)); let errored2 = false; try { await store.addBundle(decomp3); } catch { errored2 = true; } (0, utils_1.ensure)(errored2, "chain extension bundle allowed with identity?"); }); it(`${implName} getContainerProperties`, async () => { await store.ready; const database = new implementation_1.Database({ store }); await database.ready; const dir = implementation_1.Directory.get(database); await dir.set("foo", "bar"); const prop = await implementation_1.Property.create(database); await prop.set(dir, "bar"); const prop2 = await implementation_1.Property.create(database); await prop2.set(dir, "baz"); const box = await implementation_1.Box.create(database); await prop.set(box, "box"); const after = (0, utils_1.generateTimestamp)(); const properties = await store.getContainerProperties(dir.address); (0, utils_1.ensure)(properties.size === 2); (0, utils_1.ensure)(properties.get((0, utils_1.muidToString)(prop.address)) === "bar"); (0, utils_1.ensure)(properties.get((0, utils_1.muidToString)(prop2.address)) === "baz"); // Test asOf prop.set(dir, "bar2"); prop2.set(dir, "baz2"); const prop3 = await implementation_1.Property.create(database); await prop3.set(dir, "baz3"); const properties2 = await store.getContainerProperties(dir.address); (0, utils_1.ensure)(properties2.size === 3); (0, utils_1.ensure)(properties2.get((0, utils_1.muidToString)(prop.address)) === "bar2"); (0, utils_1.ensure)(properties2.get((0, utils_1.muidToString)(prop2.address)) === "baz2"); (0, utils_1.ensure)(properties2.get((0, utils_1.muidToString)(prop3.address)) === "baz3"); const asOfProperties = await store.getContainerProperties(dir.address, after); (0, utils_1.ensure)(asOfProperties.size === 2); (0, utils_1.ensure)(asOfProperties.get((0, utils_1.muidToString)(prop.address)) === "bar"); (0, utils_1.ensure)(asOfProperties.get((0, utils_1.muidToString)(prop2.address)) === "baz"); }); it(`${implName} encryption and decryption`, async () => { // Test explicitly saving and pulling a symmetric key const symKey = (0, libsodium_wrappers_1.randombytes_buf)(32); const id = await store.saveSymmetricKey(symKey); const pulled = await store.getSymmetricKey(id); (0, utils_1.ensure)((0, lodash_1.isEqual)(symKey, pulled)); // Test encryption and decryption const chainStart = await (0, test_utils_1.makeChainStart)("Hello, World!", test_utils_1.MEDALLION1, test_utils_1.START_MICROS1); await store.addBundle(chainStart); // Can't find a way to test this without a real bundle // (we used a string formatter in python, which is way easier) const innerBundleBuilder = new builders_1.BundleBuilder(); const changeBuilder = new builders_1.ChangeBuilder(); const entryBuilder = new builders_1.EntryBuilder(); entryBuilder.setContainer((0, utils_1.muidToBuilder)({ medallion: -1, timestamp: -1, offset: 1 })); entryBuilder.setBehavior(builders_1.Behavior.BOX); entryBuilder.setValue((0, utils_1.wrapValue)("top secret")); changeBuilder.setEntry(entryBuilder); innerBundleBuilder.getChangesList().push(changeBuilder); const changeBuilder2 = new builders_1.ChangeBuilder(); const entryBuilder2 = new builders_1.EntryBuilder(); entryBuilder2.setBehavior(builders_1.Behavior.DIRECTORY); entryBuilder2.setContainer((0, utils_1.muidToBuilder)({ medallion: -1, timestamp: -1, offset: 4 })); entryBuilder2.setKey((0, utils_1.wrapKey)("key")); entryBuilder2.setValue((0, utils_1.wrapValue)("top secret")); changeBuilder2.setEntry(entryBuilder2); innerBundleBuilder.getChangesList().push(changeBuilder2); const encrypted = (0, utils_1.encryptMessage)(innerBundleBuilder.serializeBinary(), symKey); const outerBundleBuilder = (0, test_utils_1.extendChainWithoutSign)("Outer", chainStart, test_utils_1.NEXT_TS1); outerBundleBuilder.setKeyId(id); outerBundleBuilder.setEncrypted(encrypted); const decomp = new Decomposition_1.Decomposition((0, utils_1.signBundle)(outerBundleBuilder.serializeBinary(), (await test_utils_1.keyPair).secretKey)); await store.addBundle(decomp); const result = await store.getEntryByKey({ medallion: -1, timestamp: -1, offset: 1, }); (0, utils_1.ensure)(result !== undefined); (0, utils_1.ensure)(result.value === "top secret"); const result2 = await store.getEntryByKey({ medallion: -1, timestamp: -1, offset: 4, }, "key"); (0, utils_1.ensure)(result2 !== undefined); (0, utils_1.ensure)(result2.value === "top secret"); }); } //# sourceMappingURL=Store.test.js.map