@x5e/gink
Version:
an eventually consistent database
316 lines • 16.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.testStore = void 0;
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 bundler = new implementation_1.Bundler();
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 address = bundler.addEntry(entryBuilder);
bundler.seal({ medallion: 4, chainStart: 5, timestamp: 5 }, await test_utils_1.keyPair, undefined, "test");
await store.addBundle(bundler);
(0, utils_1.ensure)(address.medallion === 4);
(0, utils_1.ensure)(address.timestamp === 5);
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, 4, 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, "test@identity");
await db.ready;
(0, utils_1.ensure)((await store.getClaimedChains()).size === 0);
await db.getGlobalDirectory().set(3, 4);
const chain = [...(await store.getClaimedChains()).entries()][0][1];
const identity = await store.getChainIdentity([
chain.medallion,
chain.chainStart,
]);
(0, utils_1.ensure)(identity === "test@identity");
});
it(`${implName} getContainersByName`, async () => {
const db = new implementation_1.Database(store, "test@byName");
await db.ready;
const gd = db.getGlobalDirectory();
await gd.setName("foo");
const d2 = await db.createDirectory();
await d2.setName("bar");
const seq = await db.createSequence();
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 (_a) {
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 (_b) {
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 = database.getGlobalDirectory();
await dir.set("foo", "bar");
const prop = await database.createProperty();
await prop.set(dir, "bar");
const prop2 = await database.createProperty();
await prop2.set(dir, "baz");
const box = await database.createBox();
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 database.createProperty();
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");
});
}
exports.testStore = testStore;
//# sourceMappingURL=Store.test.js.map