import { strictEqual, notStrictEqual, deepStrictEqual } from 'assert' import rimraf from 'rimraf' import Clock from '../src/lamport-clock.js' import { Log } from '../src/log.js' import IdentityProvider from 'orbit-db-identity-provider' import Keystore from '../src/Keystore.js' // Test utils import { config, testAPIs } from 'orbit-db-test-utils' import { identityKeys, signingKeys } from './fixtures/orbit-db-identity-keys.js' const { sync: rmrf } = rimraf const { createIdentity } = IdentityProvider const last = (arr) => { return arr[arr.length - 1] } Object.keys(testAPIs).forEach((IPFS) => { describe('Log - Join (' + IPFS + ')', async function () { this.timeout(config.timeout) let keystore, signingKeystore let log1, log2, log3, log4 let testIdentity, testIdentity2, testIdentity3, testIdentity4 before(async () => { keystore = new Keystore('./keys_1') await keystore.open() for (const [key, value] of Object.entries(identityKeys)) { await keystore.addKey(key, value) } signingKeystore = new Keystore('./keys_2') await signingKeystore.open() for (const [key, value] of Object.entries(signingKeys)) { await signingKeystore.addKey(key, value) } testIdentity = await createIdentity({ id: 'userC', keystore, signingKeystore }) testIdentity2 = await createIdentity({ id: 'userB', keystore, signingKeystore }) testIdentity3 = await createIdentity({ id: 'userD', keystore, signingKeystore }) testIdentity4 = await createIdentity({ id: 'userA', keystore, signingKeystore }) }) after(async () => { if (keystore) { await keystore.close() } if (signingKeystore) { await signingKeystore.close() } rmrf('./keys_1') rmrf('./keys_2') }) beforeEach(async () => { log1 = await Log(testIdentity, { logId: 'X' }) log2 = await Log(testIdentity2, { logId: 'X' }) log3 = await Log(testIdentity3, { logId: 'X' }) log4 = await Log(testIdentity4, { logId: 'X' }) }) it('joins logs', async () => { const items1 = [] const items2 = [] const amount = 20 for (let i = 1; i <= amount; i++) { const n1 = await log1.append('entryA' + i) const n2 = await log2.append('entryB' + i) items1.push(n1) items2.push(n2) } strictEqual(items1.length, amount) strictEqual(items2.length, amount) const valuesA = await log1.values() const valuesB = await log2.values() strictEqual(valuesA.length, amount) strictEqual(valuesB.length, amount) await log1.join(log2) const valuesC = await log1.values() strictEqual(valuesC.length, amount * 2) strictEqual((await log1.heads()).length, 2) }) it('throws an error if first log is not defined', async () => { let err try { await log1.join() } catch (e) { err = e } notStrictEqual(err, null) strictEqual(err.message, 'Log instance not defined') }) it('throws an error if passed argument is not an instance of Log', async () => { let err try { await log1.join({}) } catch (e) { err = e } notStrictEqual(err, null) strictEqual(err.message, 'Given argument is not an instance of Log') }) it('throws an error if trying to join an entry from a different log', async () => { const logIdA = 'AAA' const logIdB = 'BBB' let err try { const logA = await Log(testIdentity, { logId: logIdA }) await logA.append('entryA') const logB = await Log(testIdentity, { logId: logIdB }) await logB.append('entryB') const valuesB = await logB.values() await logA.joinEntry(last(valuesB)) } catch (e) { err = e } notStrictEqual(err, null) strictEqual(err.message, `Entry's id (${logIdB}) doesn't match the log's id (${logIdA}).`) }) it('throws an error if trying to join a different log', async () => { const logIdA = 'AAA' const logIdB = 'BBB' let err try { const logA = await Log(testIdentity, { logId: logIdA }) await logA.append('entryA') const logB = await Log(testIdentity, { logId: logIdB }) await logB.append('entryB') await logA.join(logB) } catch (e) { err = e } notStrictEqual(err, null) strictEqual(err.message, `Entry's id (${logIdB}) doesn't match the log's id (${logIdA}).`) }) it('joins only unique items', async () => { await log1.append('helloA1') await log1.append('helloA2') await log2.append('helloB1') await log2.append('helloB2') await log1.join(log2) await log1.join(log2) const expectedData = [ 'helloA1', 'helloB1', 'helloA2', 'helloB2' ] const values = await log1.values() strictEqual(values.length, 4) deepStrictEqual(values.map((e) => e.payload), expectedData) const item = last(values) strictEqual(item.next.length, 1) }) it('joins logs two ways', async () => { await log1.append('helloA1') await log1.append('helloA2') await log2.append('helloB1') await log2.append('helloB2') await log1.join(log2) await log2.join(log1) const expectedData = [ 'helloA1', 'helloB1', 'helloA2', 'helloB2' ] const values1 = await log1.values() const values2 = await log2.values() deepStrictEqual(values1.map((e) => e.hash), values2.map((e) => e.hash)) deepStrictEqual(values1.map((e) => e.payload), expectedData) deepStrictEqual(values2.map((e) => e.payload), expectedData) }) it('joins logs twice', async () => { await log1.append('helloA1') await log2.append('helloB1') await log2.join(log1) await log1.append('helloA2') await log2.append('helloB2') await log2.join(log1) const expectedData = [ 'helloA1', 'helloB1', 'helloA2', 'helloB2' ] const values = await log2.values() strictEqual(values.length, 4) deepStrictEqual(values.map((e) => e.payload), expectedData) }) it('joins 2 logs two ways', async () => { await log1.append('helloA1') await log2.append('helloB1') await log2.join(log1) await log1.join(log2) await log1.append('helloA2') await log2.append('helloB2') await log2.join(log1) const expectedData = [ 'helloA1', 'helloB1', 'helloA2', 'helloB2' ] const values = await log2.values() strictEqual(values.length, 4) deepStrictEqual(values.map((e) => e.payload), expectedData) }) it('joins 2 logs two ways and has the right heads at every step', async () => { await log1.append('helloA1') const heads1 = await log1.heads() strictEqual(heads1.length, 1) strictEqual(heads1[0].payload, 'helloA1') await log2.append('helloB1') const heads2 = await log2.heads() strictEqual(heads2.length, 1) strictEqual(heads2[0].payload, 'helloB1') await log2.join(log1) const heads3 = await log2.heads() strictEqual(heads3.length, 2) strictEqual(heads3[0].payload, 'helloB1') strictEqual(heads3[1].payload, 'helloA1') await log1.join(log2) const heads4 = await log1.heads() strictEqual(heads4.length, 2) strictEqual(heads4[0].payload, 'helloB1') strictEqual(heads4[1].payload, 'helloA1') await log1.append('helloA2') const heads5 = await log1.heads() strictEqual(heads5.length, 1) strictEqual(heads5[0].payload, 'helloA2') await log2.append('helloB2') const heads6 = await log2.heads() strictEqual(heads6.length, 1) strictEqual(heads6[0].payload, 'helloB2') await log2.join(log1) const heads7 = await log2.heads() strictEqual(heads7.length, 2) strictEqual(heads7[0].payload, 'helloB2') strictEqual(heads7[1].payload, 'helloA2') }) it('joins 4 logs to one', async () => { // order determined by identity's publicKey await log1.append('helloA1') await log1.append('helloA2') await log3.append('helloB1') await log3.append('helloB2') await log2.append('helloC1') await log2.append('helloC2') await log4.append('helloD1') await log4.append('helloD2') await log1.join(log2) await log1.join(log3) await log1.join(log4) const expectedData = [ 'helloA1', 'helloB1', 'helloC1', 'helloD1', 'helloA2', 'helloB2', 'helloC2', 'helloD2' ] const values = await log1.values() strictEqual(values.length, 8) deepStrictEqual(values.map(e => e.payload), expectedData) }) it('joins 4 logs to one is commutative', async () => { await log1.append('helloA1') await log1.append('helloA2') await log2.append('helloB1') await log2.append('helloB2') await log3.append('helloC1') await log3.append('helloC2') await log4.append('helloD1') await log4.append('helloD2') await log1.join(log2) await log1.join(log3) await log1.join(log4) await log2.join(log1) await log2.join(log3) await log2.join(log4) const values1 = await log1.values() const values2 = await log1.values() strictEqual(values1.length, 8) strictEqual(values2.length, 8) deepStrictEqual(values1.map(e => e.payload), values2.map(e => e.payload)) }) it('joins logs and updates clocks', async () => { await log1.append('helloA1') await log2.append('helloB1') await log2.join(log1) await log1.append('helloA2') await log2.append('helloB2') strictEqual((await log1.clock()).id, testIdentity.publicKey) strictEqual((await log2.clock()).id, testIdentity2.publicKey) strictEqual((await log1.clock()).time, 2) strictEqual((await log2.clock()).time, 2) await log3.join(log1) strictEqual(log3.id, 'X') strictEqual((await log3.clock()).id, testIdentity3.publicKey) strictEqual((await log3.clock()).time, 2) await log3.append('helloC1') await log3.append('helloC2') await log1.join(log3) await log1.join(log2) await log4.append('helloD1') await log4.append('helloD2') await log4.join(log2) await log4.join(log1) await log4.join(log3) await log4.append('helloD3') await log4.append('helloD4') await log1.join(log4) await log4.join(log1) await log4.append('helloD5') await log1.append('helloA5') await log4.join(log1) strictEqual((await log4.clock()).id, testIdentity4.publicKey) strictEqual((await log4.clock()).time, 7) await log4.append('helloD6') strictEqual((await log4.clock()).time, 8) const expectedData = [ { payload: 'helloA1', id: 'X', clock: new Clock(testIdentity.publicKey, 1) }, { payload: 'helloB1', id: 'X', clock: new Clock(testIdentity2.publicKey, 1) }, { payload: 'helloD1', id: 'X', clock: new Clock(testIdentity4.publicKey, 1) }, { payload: 'helloA2', id: 'X', clock: new Clock(testIdentity.publicKey, 2) }, { payload: 'helloB2', id: 'X', clock: new Clock(testIdentity2.publicKey, 2) }, { payload: 'helloD2', id: 'X', clock: new Clock(testIdentity4.publicKey, 2) }, { payload: 'helloC1', id: 'X', clock: new Clock(testIdentity3.publicKey, 3) }, { payload: 'helloC2', id: 'X', clock: new Clock(testIdentity3.publicKey, 4) }, { payload: 'helloD3', id: 'X', clock: new Clock(testIdentity4.publicKey, 5) }, { payload: 'helloD4', id: 'X', clock: new Clock(testIdentity4.publicKey, 6) }, { payload: 'helloA5', id: 'X', clock: new Clock(testIdentity.publicKey, 7) }, { payload: 'helloD5', id: 'X', clock: new Clock(testIdentity4.publicKey, 7) }, { payload: 'helloD6', id: 'X', clock: new Clock(testIdentity4.publicKey, 8) } ] const values = await log4.values() const transformed = values.map((e) => { return { payload: e.payload, id: e.id, clock: e.clock } }) strictEqual(values.length, 13) deepStrictEqual(transformed, expectedData) }) it('joins logs from 4 logs', async () => { await log1.append('helloA1') await log1.join(log2) await log2.append('helloB1') await log2.join(log1) await log1.append('helloA2') await log2.append('helloB2') await log1.join(log3) strictEqual(log1.id, 'X') strictEqual((await log1.clock()).id, testIdentity.publicKey) strictEqual((await log1.clock()).time, 2) await log3.join(log1) strictEqual(log3.id, 'X') strictEqual((await log3.clock()).id, testIdentity3.publicKey) strictEqual((await log3.clock()).time, 2) await log3.append('helloC1') await log3.append('helloC2') await log1.join(log3) await log1.join(log2) await log4.append('helloD1') await log4.append('helloD2') await log4.join(log2) await log4.join(log1) await log4.join(log3) await log4.append('helloD3') await log4.append('helloD4') strictEqual((await log4.clock()).id, testIdentity4.publicKey) strictEqual((await log4.clock()).time, 6) const expectedData = [ 'helloA1', 'helloB1', 'helloD1', 'helloA2', 'helloB2', 'helloD2', 'helloC1', 'helloC2', 'helloD3', 'helloD4' ] const values = await log4.values() strictEqual(values.length, 10) deepStrictEqual(values.map((e) => e.payload), expectedData) }) }) })