From 5aa8d3e384c5743d2cd76826574a9abf53acd0a2 Mon Sep 17 00:00:00 2001 From: haad Date: Tue, 12 Apr 2016 18:15:31 +0200 Subject: [PATCH] Test OrbitDB, refactor to use promises --- src/Client.js | 4 +- src/OrbitDB.js | 55 +++- src/db/Operation.js | 13 +- test/client.test.js | 524 ++++++++++++++++++++++++++++++++++ test/orbit-client-tests.js | 524 ---------------------------------- test/orbit-db-test-cache.json | 3 + test/orbit-db.test.js | 187 ++++++++++++ 7 files changed, 769 insertions(+), 541 deletions(-) create mode 100644 test/client.test.js delete mode 100644 test/orbit-client-tests.js create mode 100644 test/orbit-db-test-cache.json create mode 100644 test/orbit-db.test.js diff --git a/src/Client.js b/src/Client.js index 92f05a2..d3bf4e2 100644 --- a/src/Client.js +++ b/src/Client.js @@ -23,14 +23,14 @@ class Client { if(password === undefined) password = ''; if(subscribe === undefined) subscribe = true; - await(this.db.use(channel, this.user, password)); + await(this.db.use(channel, this.user)); this.db.events[channel].on('write', this._onWrite.bind(this)); this.db.events[channel].on('sync', this._onSync.bind(this)); this.db.events[channel].on('load', this._onLoad.bind(this)); this.db.events[channel].on('loaded', this._onLoaded.bind(this)); if(subscribe) - this._pubsub.subscribe(channel, password, async((channel, message) => this.db.sync(channel, message))); + this._pubsub.subscribe(channel, password, async((channel, message) => await(this.db.sync(channel, message)))); return { iterator: (options) => this._iterator(channel, password, options), diff --git a/src/OrbitDB.js b/src/OrbitDB.js index 02140a8..80e3a02 100644 --- a/src/OrbitDB.js +++ b/src/OrbitDB.js @@ -2,6 +2,7 @@ const Lazy = require('lazy.js'); const EventEmitter = require('events').EventEmitter; +const Promise = require('bluebird'); const async = require('asyncawait/async'); const await = require('asyncawait/await'); const logger = require('orbit-common/lib/logger')("orbit-db.OrbitDB"); @@ -17,24 +18,27 @@ class OrbitDB { this.events = {}; this.options = options || {}; this.lastWrite = null; + this._cached = []; } /* Public methods */ - use(channel, user, password) { + use(channel, user) { this.user = user; return new Promise((resolve, reject) => { Log.create(this._ipfs, this.user.username).then((log) => { this._logs[channel] = log; this.events[channel] = new EventEmitter(); - Cache.loadCache(this.options.cacheFile); - this.sync(channel, Cache.get(channel)); + if(this.options.cacheFile) { + Cache.loadCache(this.options.cacheFile); + this.sync(channel, Cache.get(channel)); + } resolve(); }).catch(reject); }); } sync(channel, hash) { - // console.log("--> Head:", hash) + console.log("--> Head:", hash) if(hash && hash !== this.lastWrite && this._logs[channel]) { this.events[channel].emit('load', 'sync', channel); const oldCount = this._logs[channel].items.length; @@ -42,11 +46,25 @@ class OrbitDB { this._logs[channel].join(other).then(() => { // Only emit the event if something was added const joinedCount = (this._logs[channel].items.length - oldCount); + console.log("JOIN", joinedCount); if(joinedCount > 0) { this.events[channel].emit('sync', channel, hash); Cache.set(channel, hash); + + // Cache the payloads + const payloadHashes = other.items.map((f) => f.payload); + Promise.map(payloadHashes, (f) => { + return OrbitDB.fetchPayload(this._ipfs, f); + }, { concurrency: 1 }).then((r) => { + r.forEach((f) => this._cached.push(f)); + this.events[channel].emit('loaded', 'sync', channel); + }).catch((e) => { + this.events[channel].emit('error', e.message); + }); + + } else { + this.events[channel].emit('loaded', 'sync', channel); } - this.events[channel].emit('loaded', 'sync', channel); }); }); } else { @@ -62,7 +80,19 @@ class OrbitDB { // console.log("--> Query:", channel, opts); if(!opts) opts = {}; - const operations = Lazy(this._logs[channel].items); + if(!this._cached) this._cached = []; + + const operations = Lazy(this._logs[channel].items) + // .map((f) => { + // let res = Lazy(this._cached).findWhere({ hash: f.payload }); + // if(!res) { + // const payload = await(OrbitDB.fetchPayload(this._ipfs, f.payload)); + // this._cached.push(payload); + // res = payload; + // } + // return res; + // }) + const amount = opts.limit ? (opts.limit > -1 ? opts.limit : this._logs[channel].items.length) : 1; // Return 1 if no limit is provided let result = []; @@ -125,7 +155,7 @@ class OrbitDB { // Find the items from the sequence (list of operations) return sequence - .map((f) => await(OrbitDB.fetchPayload(this._ipfs, f.payload))) // IO - fetch the actual OP from ipfs. consider merging with LL. + // .map((f) => await(OrbitDB.fetchPayload(this._ipfs, f.payload))) // IO - fetch the actual OP from ipfs. consider merging with LL. .skipWhile((f) => key && f.key !== key) // Drop elements until we have the first one requested .map(_createLWWSet) // Return items as LWW (ignore values after the first found) .compact() // Remove nulls @@ -137,12 +167,19 @@ class OrbitDB { _write(channel, password, operation, key, value) { return new Promise((resolve, reject) => { DBOperation.create(this._ipfs, this._logs[channel], this.user, operation, key, value) - .then((hash) => { + .then((res) => { Log.getIpfsHash(this._ipfs, this._logs[channel]).then((listHash) => { this.lastWrite = listHash; Cache.set(channel, listHash); + + // Cache the payload + let op = JSON.parse(JSON.stringify(res.op)); + Object.assign(op, { hash: res.node.payload }); + if(op.key === null) Object.assign(op, { key: res.node.payload }); + this._cached.push(op); + this.events[channel].emit('write', channel, listHash); - resolve(hash); + resolve(res.node.payload); }) }).catch(reject); }); diff --git a/src/db/Operation.js b/src/db/Operation.js index 40714f9..f78a016 100644 --- a/src/db/Operation.js +++ b/src/db/Operation.js @@ -7,13 +7,14 @@ const Post = require('../post/Post'); class Operation { static create(ipfs, log, user, operation, key, value, data) { return new Promise((resolve, reject) => { - let hash; + let post; Operation._createOperation(ipfs, user, operation, key, value) - .then((res) => { - hash = res; - return log.add(hash); + .then((op) => { + post = op.Post; + // console.log(op) + return log.add(op.Hash); }) - .then(() => resolve(hash)) + .then((node) => resolve({ node: node, op: post })) .catch(reject); }); } @@ -27,7 +28,7 @@ class Operation { by: user.id || 'empty' }; Post.create(ipfs, Post.Types.OrbitDBItem, data) - .then((op) => resolve(op.Hash)) + .then(resolve) .catch(reject); }); } diff --git a/test/client.test.js b/test/client.test.js new file mode 100644 index 0000000..344ee54 --- /dev/null +++ b/test/client.test.js @@ -0,0 +1,524 @@ +// 'use strict'; + +// const _ = require('lodash'); +// const assert = require('assert'); +// const async = require('asyncawait/async'); +// const await = require('asyncawait/await'); +// const OrbitClient = require('../src/Client'); + +// // Mute logging +// // require('log4js').setGlobalLogLevel('ERROR'); + +// // Orbit +// const username = 'testrunner'; +// const password = ''; + +// describe('Orbit Client', function() { +// this.timeout(15000); + +// let client, db; +// let channel = 'abcdefgh'; + +// before(async((done) => { +// client = await(OrbitClient.connect('localhost', 3333, username, password, null, { allowOffline: true })); +// db = client.channel(channel, '', false); +// db.delete(); +// done(); +// })); + +// after(() => { +// if(db) db.delete(); +// if(client) client.disconnect(); +// }); + +// describe('Add events', function() { +// beforeEach(() => { +// db.delete(); +// }); + +// it('adds an item to an empty channel', async((done) => { +// const head = await(db.add('hello')); +// assert.notEqual(head, null); +// assert.equal(head.startsWith('Qm'), true); +// assert.equal(head.length, 46); +// done(); +// })); + +// it('adds a new item to a channel with one item', async((done) => { +// const head = db.iterator().collect(); +// const second = await(db.add('hello')); +// assert.notEqual(second, null); +// assert.notEqual(second, head); +// assert.equal(second.startsWith('Qm'), true); +// assert.equal(second.length, 46); +// done(); +// })); + +// it('adds five items', async((done) => { +// for(let i = 0; i < 5; i ++) { +// let hash = await(db.add('hello')); +// assert.notEqual(hash, null); +// assert.equal(hash.startsWith('Qm'), true); +// assert.equal(hash.length, 46); +// } +// done(); +// })); + +// it('adds an item that is > 256 bytes', async((done) => { +// let msg = new Buffer(1024); +// msg.fill('a') +// const hash = await(db.add(msg.toString())); +// assert.notEqual(hash, null); +// assert.equal(hash.startsWith('Qm'), true); +// assert.equal(hash.length, 46); +// done(); +// })); +// }); + +// describe('Delete events', function() { +// beforeEach(() => { +// db.delete(); +// const items = db.iterator().collect(); +// assert.equal(items.length, 0); +// }); + +// it('deletes an item when only one item in the database', async((done) => { +// const head = await(db.add('hello1')); +// let item = db.iterator().collect(); +// const delop = await(db.del(head)); +// const items = db.iterator().collect(); +// assert.equal(delop.startsWith('Qm'), true); +// assert.equal(items.length, 0); +// done(); +// })); + +// it('deletes an item when two items in the database', async((done) => { +// await(db.add('hello1')); +// const head = await(db.add('hello2')); +// await(db.del(head)); +// const items = db.iterator().collect(); +// assert.equal(items.length, 1); +// assert.equal(items[0].value, 'hello1'); +// done(); +// })); + +// it('deletes an item between adds', async((done) => { +// const head = await(db.add('hello1')); +// await(db.add('hello2')); +// db.del(head); +// await(db.add('hello3')); +// const items = db.iterator().collect(); +// assert.equal(items.length, 1); +// assert.equal(items[0].key.startsWith('Qm'), true); +// assert.equal(items[0].hash.startsWith('Qm'), true); +// assert.equal(items[0].value, 'hello3'); +// done(); +// })); +// }); + +// describe('Iterator', function() { +// let items = []; +// const itemCount = 5; + +// beforeEach(async((done) => { +// items = []; +// db.delete(); +// for(let i = 0; i < itemCount; i ++) { +// const hash = await(db.add('hello' + i)); +// items.push(hash); +// } +// done(); +// })); + +// describe('Defaults', function() { +// it('returns an iterator', async((done) => { +// const iter = db.iterator(); +// const next = iter.next().value; +// assert.notEqual(iter, null); +// assert.notEqual(next, null); +// done(); +// })); + +// it('returns an item with the correct structure', async((done) => { +// const iter = db.iterator(); +// const next = iter.next().value; +// assert.notEqual(next, null); +// assert.notEqual(next.key, null); +// assert.equal(next.key.startsWith('Qm'), true); +// assert.equal(next.hash.startsWith('Qm'), true); +// assert.equal(next.value, 'hello4'); +// done(); +// })); + +// it('implements Iterator interface', async((done) => { +// const iter = db.iterator({ limit: -1 }); +// let messages = []; + +// for(let i of iter) +// messages.push(i.key); + +// assert.equal(messages.length, items.length); +// done(); +// })); + +// it('returns 1 item as default', async((done) => { +// const iter = db.iterator(); +// const first = iter.next().value; +// const second = iter.next().value; +// assert.equal(first.key, items[items.length - 1]); +// assert.equal(second, null); +// assert.equal(first.value, 'hello4'); +// done(); +// })); +// }); + +// describe('Collect', function() { +// it('returns all items', async((done) => { +// const messages = db.iterator({ limit: -1 }).collect(); +// assert.equal(messages.length, items.length); +// assert.equal(messages[0].value, 'hello0'); +// assert.equal(messages[messages.length - 1].value, 'hello4'); +// done(); +// })); + +// it('returns 1 item', async((done) => { +// const messages = db.iterator().collect(); +// assert.equal(messages.length, 1); +// done(); +// })); + +// it('returns 3 items', async((done) => { +// const messages = db.iterator({ limit: 3 }).collect(); +// assert.equal(messages.length, 3); +// done(); +// })); +// }); + +// describe('Options: limit', function() { +// it('returns 1 item when limit is 0', async((done) => { +// const iter = db.iterator({ limit: 1 }); +// const first = iter.next().value; +// const second = iter.next().value; +// assert.equal(first.key, _.last(items)); +// assert.equal(second, null); +// done(); +// })); + +// it('returns 1 item when limit is 1', async((done) => { +// const iter = db.iterator({ limit: 1 }); +// const first = iter.next().value; +// const second = iter.next().value; +// assert.equal(first.key, _.last(items)); +// assert.equal(second, null); +// done(); +// })); + +// it('returns 3 items', async((done) => { +// const iter = db.iterator({ limit: 3 }); +// const first = iter.next().value; +// const second = iter.next().value; +// const third = iter.next().value; +// const fourth = iter.next().value; +// assert.equal(first.key, items[items.length - 3]); +// assert.equal(second.key, items[items.length - 2]); +// assert.equal(third.key, items[items.length - 1]); +// assert.equal(fourth, null); +// done(); +// })); + +// it('returns all items', async((done) => { +// const messages = db.iterator({ limit: -1 }) +// .collect() +// .map((e) => e.key); + +// messages.reverse(); +// assert.equal(messages.length, items.length); +// assert.equal(messages[0], items[items.length - 1]); +// done(); +// })); + +// it('returns all items when limit is bigger than -1', async((done) => { +// const messages = db.iterator({ limit: -300 }) +// .collect() +// .map((e) => e.key); + +// assert.equal(messages.length, items.length); +// assert.equal(messages[0], items[0]); +// done(); +// })); + +// it('returns all items when limit is bigger than number of items', async((done) => { +// const messages = db.iterator({ limit: 300 }) +// .collect() +// .map((e) => e.key); + +// assert.equal(messages.length, items.length); +// assert.equal(messages[0], items[0]); +// done(); +// })); +// }); + +// describe('Options: reverse', function() { +// it('returns all items reversed', async((done) => { +// const messages = db.iterator({ limit: -1, reverse: true }) +// .collect() +// .map((e) => e.key); + +// assert.equal(messages.length, items.length); +// assert.equal(messages[0], items[0]); +// done(); +// })); +// }); + +// describe('Option: ranges', function() { +// describe('gt & gte', function() { +// it('returns 1 item when gte is the head', async((done) => { +// const messages = db.iterator({ gte: _.last(items), limit: -1 }) +// .collect() +// .map((e) => e.key); + +// assert.equal(messages.length, 1); +// assert.equal(messages[0], _.last(items)); +// done(); +// })); + +// it('returns 0 items when gt is the head', async((done) => { +// const messages = db.iterator({ gt: _.last(items) }).collect(); +// assert.equal(messages.length, 0); +// done(); +// })); + +// it('returns 2 item when gte is defined', async((done) => { +// const gte = items[items.length - 2]; +// const messages = db.iterator({ gte: gte, limit: -1 }) +// .collect() +// .map((e) => e.key); + +// assert.equal(messages.length, 2); +// assert.equal(messages[0], items[items.length - 2]); +// assert.equal(messages[1], items[items.length - 1]); +// done(); +// })); + +// it('returns all items when gte is the root item', async((done) => { +// const messages = db.iterator({ gte: items[0], limit: -1 }) +// .collect() +// .map((e) => e.key); + +// assert.equal(messages.length, items.length); +// assert.equal(messages[0], items[0]); +// assert.equal(messages[messages.length - 1], _.last(items)); +// done(); +// })); + +// it('returns items when gt is the root item', async((done) => { +// const messages = db.iterator({ gt: items[0], limit: -1 }) +// .collect() +// .map((e) => e.key); + +// assert.equal(messages.length, itemCount - 1); +// assert.equal(messages[0], items[1]); +// assert.equal(messages[3], _.last(items)); +// done(); +// })); + +// it('returns items when gt is defined', async((done) => { +// const messages = db.iterator({ limit: -1}) +// .collect() +// .map((e) => e.key); + +// const gt = messages[2]; + +// const messages2 = db.iterator({ gt: gt, limit: 100 }) +// .collect() +// .map((e) => e.key); + +// assert.equal(messages2.length, 2); +// assert.equal(messages2[0], messages[messages.length - 2]); +// assert.equal(messages2[1], messages[messages.length - 1]); +// done(); +// })); +// }); + +// describe('lt & lte', function() { +// it('returns one item after head when lt is the head', async((done) => { +// const messages = db.iterator({ lt: _.last(items) }) +// .collect() +// .map((e) => e.key); + +// assert.equal(messages.length, 1); +// assert.equal(messages[0], items[items.length - 2]); +// done(); +// })); + +// it('returns all items when lt is head and limit is -1', async((done) => { +// const messages = db.iterator({ lt: _.last(items), limit: -1 }) +// .collect() +// .map((e) => e.key); + +// assert.equal(messages.length, items.length - 1); +// assert.equal(messages[0], items[0]); +// assert.equal(messages[messages.length - 1], items[items.length - 2]); +// done(); +// })); + +// it('returns 3 items when lt is head and limit is 3', async((done) => { +// const messages = db.iterator({ lt: _.last(items), limit: 3 }) +// .collect() +// .map((e) => e.key); + +// assert.equal(messages.length, 3); +// assert.equal(messages[0], items[items.length - 4]); +// assert.equal(messages[2], items[items.length - 2]); +// done(); +// })); + +// it('returns null when lt is the root item', async((done) => { +// const messages = db.iterator({ lt: items[0] }).collect(); +// assert.equal(messages.length, 0); +// done(); +// })); + +// it('returns one item when lte is the root item', async((done) => { +// const messages = db.iterator({ lte: items[0] }) +// .collect() +// .map((e) => e.key); + +// assert.equal(messages.length, 1); +// assert.equal(messages[0], items[0]); +// done(); +// })); + +// it('returns all items when lte is the head', async((done) => { +// const messages = db.iterator({ lte: _.last(items), limit: -1 }) +// .collect() +// .map((e) => e.key); + +// assert.equal(messages.length, itemCount); +// assert.equal(messages[0], items[0]); +// assert.equal(messages[4], _.last(items)); +// done(); +// })); + +// it('returns 3 items when lte is the head', async((done) => { +// const messages = db.iterator({ lte: _.last(items), limit: 3 }) +// .collect() +// .map((e) => e.key); + +// assert.equal(messages.length, 3); +// assert.equal(messages[0], items[items.length - 3]); +// assert.equal(messages[1], items[items.length - 2]); +// assert.equal(messages[2], _.last(items)); +// done(); +// })); +// }); +// }); +// }); + +// describe('Delete', function() { +// it('deletes a channel from the local database', () => { +// const result = db.delete(); +// assert.equal(result, true); +// const iter = db.iterator(); +// assert.equal(iter.next().value, null); +// }); +// }); + +// describe('Key-Value Store', function() { +// before(() => { +// db.delete(); +// }); + +// afterEach(() => { +// db.delete(); +// }); + +// it('put', async((done) => { +// await(db.put('key1', 'hello!')); +// let all = db.iterator().collect(); +// assert.equal(all.length, 1); +// assert.equal(all[0].hash.startsWith('Qm'), true); +// assert.equal(all[0].key, 'key1'); +// assert.notEqual(all[0].meta, null); +// done(); +// })); + +// it('get', async((done) => { +// await(db.put('key1', 'hello!')); +// const value = db.get('key1'); +// assert.equal(value, 'hello!'); +// done(); +// })); + +// it('put updates a value', async((done) => { +// await(db.put('key1', 'hello!')); +// await(db.put('key1', 'hello again')); +// const value = db.get('key1'); +// assert.equal(value, 'hello again'); +// done(); +// })); + +// it('deletes a key', async((done) => { +// await(db.put('key1', 'hello!')); +// await(db.del('key1')); +// const value = db.get('key1'); +// assert.equal(value, null); +// done(); +// })); + +// it('deletes a key after multiple updates', async((done) => { +// await(db.put('key1', 'hello1')); +// await(db.put('key1', 'hello2')); +// await(db.put('key1', 'hello3')); +// await(db.del('key1')); +// const value = db.get('key1'); +// assert.equal(value, null); +// done(); +// })); + +// it('put - multiple keys', async((done) => { +// await(db.put('key1', 'hello1')); +// await(db.put('key2', 'hello2')); +// await(db.put('key3', 'hello3')); +// const all = db.iterator().collect(); +// assert.equal(all.length, 1); +// done(); +// })); + +// it('get - multiple keys', async((done) => { +// await(db.put('key1', 'hello1')); +// await(db.put('key2', 'hello2')); +// await(db.put('key3', 'hello3')); +// const v1 = db.get('key1'); +// const v2 = db.get('key2'); +// const v3 = db.get('key3'); +// assert.equal(v1, 'hello1'); +// assert.equal(v2, 'hello2'); +// assert.equal(v3, 'hello3'); +// done(); +// })); + +// it('get - integer value', async((done) => { +// await(db.put('key1', 123)); +// const v1 = db.get('key1'); +// assert.equal(v1, 123); +// done(); +// })); + +// it('get - object value', async((done) => { +// const val = { one: 'first', two: 2 }; +// await(db.put('key1', val)); +// const v1 = db.get('key1'); +// assert.equal(_.isEqual(v1, val), true); +// done(); +// })); + +// it('get - array value', async((done) => { +// const val = [1, 2, 3, 4, 5]; +// await(db.put('key1', val)); +// const v1 = db.get('key1'); +// assert.equal(_.isEqual(v1, val), true); +// done(); +// })); +// }); +// }); diff --git a/test/orbit-client-tests.js b/test/orbit-client-tests.js deleted file mode 100644 index 4293594..0000000 --- a/test/orbit-client-tests.js +++ /dev/null @@ -1,524 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const assert = require('assert'); -const async = require('asyncawait/async'); -const await = require('asyncawait/await'); -const OrbitClient = require('../src/Client'); - -// Mute logging -// require('log4js').setGlobalLogLevel('ERROR'); - -// Orbit -const username = 'testrunner'; -const password = ''; - -describe('Orbit Client', function() { - this.timeout(15000); - - let client, db; - let channel = 'abcdefgh'; - - before(async((done) => { - client = await(OrbitClient.connect('localhost', 3333, username, password, null, { allowOffline: true })); - db = client.channel(channel, '', false); - db.delete(); - done(); - })); - - after(() => { - if(db) db.delete(); - if(client) client.disconnect(); - }); - - describe('Add events', function() { - beforeEach(() => { - db.delete(); - }); - - it('adds an item to an empty channel', async((done) => { - const head = await(db.add('hello')); - assert.notEqual(head, null); - assert.equal(head.startsWith('Qm'), true); - assert.equal(head.length, 46); - done(); - })); - - it('adds a new item to a channel with one item', async((done) => { - const head = db.iterator().collect(); - const second = await(db.add('hello')); - assert.notEqual(second, null); - assert.notEqual(second, head); - assert.equal(second.startsWith('Qm'), true); - assert.equal(second.length, 46); - done(); - })); - - it('adds five items', async((done) => { - for(let i = 0; i < 5; i ++) { - let hash = await(db.add('hello')); - assert.notEqual(hash, null); - assert.equal(hash.startsWith('Qm'), true); - assert.equal(hash.length, 46); - } - done(); - })); - - it('adds an item that is > 256 bytes', async((done) => { - let msg = new Buffer(1024); - msg.fill('a') - const hash = await(db.add(msg.toString())); - assert.notEqual(hash, null); - assert.equal(hash.startsWith('Qm'), true); - assert.equal(hash.length, 46); - done(); - })); - }); - - describe('Delete events', function() { - beforeEach(() => { - db.delete(); - const items = db.iterator().collect(); - assert.equal(items.length, 0); - }); - - it('deletes an item when only one item in the database', async((done) => { - const head = await(db.add('hello1')); - let item = db.iterator().collect(); - const delop = await(db.del(head)); - const items = db.iterator().collect(); - assert.equal(delop.startsWith('Qm'), true); - assert.equal(items.length, 0); - done(); - })); - - it('deletes an item when two items in the database', async((done) => { - await(db.add('hello1')); - const head = await(db.add('hello2')); - await(db.del(head)); - const items = db.iterator().collect(); - assert.equal(items.length, 1); - assert.equal(items[0].value, 'hello1'); - done(); - })); - - it('deletes an item between adds', async((done) => { - const head = await(db.add('hello1')); - await(db.add('hello2')); - db.del(head); - await(db.add('hello3')); - const items = db.iterator().collect(); - assert.equal(items.length, 1); - assert.equal(items[0].key.startsWith('Qm'), true); - assert.equal(items[0].hash.startsWith('Qm'), true); - assert.equal(items[0].value, 'hello3'); - done(); - })); - }); - - describe('Iterator', function() { - let items = []; - const itemCount = 5; - - beforeEach(async((done) => { - items = []; - db.delete(); - for(let i = 0; i < itemCount; i ++) { - const hash = await(db.add('hello' + i)); - items.push(hash); - } - done(); - })); - - describe('Defaults', function() { - it('returns an iterator', async((done) => { - const iter = db.iterator(); - const next = iter.next().value; - assert.notEqual(iter, null); - assert.notEqual(next, null); - done(); - })); - - it('returns an item with the correct structure', async((done) => { - const iter = db.iterator(); - const next = iter.next().value; - assert.notEqual(next, null); - assert.notEqual(next.key, null); - assert.equal(next.key.startsWith('Qm'), true); - assert.equal(next.hash.startsWith('Qm'), true); - assert.equal(next.value, 'hello4'); - done(); - })); - - it('implements Iterator interface', async((done) => { - const iter = db.iterator({ limit: -1 }); - let messages = []; - - for(let i of iter) - messages.push(i.key); - - assert.equal(messages.length, items.length); - done(); - })); - - it('returns 1 item as default', async((done) => { - const iter = db.iterator(); - const first = iter.next().value; - const second = iter.next().value; - assert.equal(first.key, items[items.length - 1]); - assert.equal(second, null); - assert.equal(first.value, 'hello4'); - done(); - })); - }); - - describe('Collect', function() { - it('returns all items', async((done) => { - const messages = db.iterator({ limit: -1 }).collect(); - assert.equal(messages.length, items.length); - assert.equal(messages[0].value, 'hello0'); - assert.equal(messages[messages.length - 1].value, 'hello4'); - done(); - })); - - it('returns 1 item', async((done) => { - const messages = db.iterator().collect(); - assert.equal(messages.length, 1); - done(); - })); - - it('returns 3 items', async((done) => { - const messages = db.iterator({ limit: 3 }).collect(); - assert.equal(messages.length, 3); - done(); - })); - }); - - describe('Options: limit', function() { - it('returns 1 item when limit is 0', async((done) => { - const iter = db.iterator({ limit: 1 }); - const first = iter.next().value; - const second = iter.next().value; - assert.equal(first.key, _.last(items)); - assert.equal(second, null); - done(); - })); - - it('returns 1 item when limit is 1', async((done) => { - const iter = db.iterator({ limit: 1 }); - const first = iter.next().value; - const second = iter.next().value; - assert.equal(first.key, _.last(items)); - assert.equal(second, null); - done(); - })); - - it('returns 3 items', async((done) => { - const iter = db.iterator({ limit: 3 }); - const first = iter.next().value; - const second = iter.next().value; - const third = iter.next().value; - const fourth = iter.next().value; - assert.equal(first.key, items[items.length - 3]); - assert.equal(second.key, items[items.length - 2]); - assert.equal(third.key, items[items.length - 1]); - assert.equal(fourth, null); - done(); - })); - - it('returns all items', async((done) => { - const messages = db.iterator({ limit: -1 }) - .collect() - .map((e) => e.key); - - messages.reverse(); - assert.equal(messages.length, items.length); - assert.equal(messages[0], items[items.length - 1]); - done(); - })); - - it('returns all items when limit is bigger than -1', async((done) => { - const messages = db.iterator({ limit: -300 }) - .collect() - .map((e) => e.key); - - assert.equal(messages.length, items.length); - assert.equal(messages[0], items[0]); - done(); - })); - - it('returns all items when limit is bigger than number of items', async((done) => { - const messages = db.iterator({ limit: 300 }) - .collect() - .map((e) => e.key); - - assert.equal(messages.length, items.length); - assert.equal(messages[0], items[0]); - done(); - })); - }); - - describe('Options: reverse', function() { - it('returns all items reversed', async((done) => { - const messages = db.iterator({ limit: -1, reverse: true }) - .collect() - .map((e) => e.key); - - assert.equal(messages.length, items.length); - assert.equal(messages[0], items[0]); - done(); - })); - }); - - describe('Option: ranges', function() { - describe('gt & gte', function() { - it('returns 1 item when gte is the head', async((done) => { - const messages = db.iterator({ gte: _.last(items), limit: -1 }) - .collect() - .map((e) => e.key); - - assert.equal(messages.length, 1); - assert.equal(messages[0], _.last(items)); - done(); - })); - - it('returns 0 items when gt is the head', async((done) => { - const messages = db.iterator({ gt: _.last(items) }).collect(); - assert.equal(messages.length, 0); - done(); - })); - - it('returns 2 item when gte is defined', async((done) => { - const gte = items[items.length - 2]; - const messages = db.iterator({ gte: gte, limit: -1 }) - .collect() - .map((e) => e.key); - - assert.equal(messages.length, 2); - assert.equal(messages[0], items[items.length - 2]); - assert.equal(messages[1], items[items.length - 1]); - done(); - })); - - it('returns all items when gte is the root item', async((done) => { - const messages = db.iterator({ gte: items[0], limit: -1 }) - .collect() - .map((e) => e.key); - - assert.equal(messages.length, items.length); - assert.equal(messages[0], items[0]); - assert.equal(messages[messages.length - 1], _.last(items)); - done(); - })); - - it('returns items when gt is the root item', async((done) => { - const messages = db.iterator({ gt: items[0], limit: -1 }) - .collect() - .map((e) => e.key); - - assert.equal(messages.length, itemCount - 1); - assert.equal(messages[0], items[1]); - assert.equal(messages[3], _.last(items)); - done(); - })); - - it('returns items when gt is defined', async((done) => { - const messages = db.iterator({ limit: -1}) - .collect() - .map((e) => e.key); - - const gt = messages[2]; - - const messages2 = db.iterator({ gt: gt, limit: 100 }) - .collect() - .map((e) => e.key); - - assert.equal(messages2.length, 2); - assert.equal(messages2[0], messages[messages.length - 2]); - assert.equal(messages2[1], messages[messages.length - 1]); - done(); - })); - }); - - describe('lt & lte', function() { - it('returns one item after head when lt is the head', async((done) => { - const messages = db.iterator({ lt: _.last(items) }) - .collect() - .map((e) => e.key); - - assert.equal(messages.length, 1); - assert.equal(messages[0], items[items.length - 2]); - done(); - })); - - it('returns all items when lt is head and limit is -1', async((done) => { - const messages = db.iterator({ lt: _.last(items), limit: -1 }) - .collect() - .map((e) => e.key); - - assert.equal(messages.length, items.length - 1); - assert.equal(messages[0], items[0]); - assert.equal(messages[messages.length - 1], items[items.length - 2]); - done(); - })); - - it('returns 3 items when lt is head and limit is 3', async((done) => { - const messages = db.iterator({ lt: _.last(items), limit: 3 }) - .collect() - .map((e) => e.key); - - assert.equal(messages.length, 3); - assert.equal(messages[0], items[items.length - 4]); - assert.equal(messages[2], items[items.length - 2]); - done(); - })); - - it('returns null when lt is the root item', async((done) => { - const messages = db.iterator({ lt: items[0] }).collect(); - assert.equal(messages.length, 0); - done(); - })); - - it('returns one item when lte is the root item', async((done) => { - const messages = db.iterator({ lte: items[0] }) - .collect() - .map((e) => e.key); - - assert.equal(messages.length, 1); - assert.equal(messages[0], items[0]); - done(); - })); - - it('returns all items when lte is the head', async((done) => { - const messages = db.iterator({ lte: _.last(items), limit: -1 }) - .collect() - .map((e) => e.key); - - assert.equal(messages.length, itemCount); - assert.equal(messages[0], items[0]); - assert.equal(messages[4], _.last(items)); - done(); - })); - - it('returns 3 items when lte is the head', async((done) => { - const messages = db.iterator({ lte: _.last(items), limit: 3 }) - .collect() - .map((e) => e.key); - - assert.equal(messages.length, 3); - assert.equal(messages[0], items[items.length - 3]); - assert.equal(messages[1], items[items.length - 2]); - assert.equal(messages[2], _.last(items)); - done(); - })); - }); - }); - }); - - describe('Delete', function() { - it('deletes a channel from the local database', () => { - const result = db.delete(); - assert.equal(result, true); - const iter = db.iterator(); - assert.equal(iter.next().value, null); - }); - }); - - describe('Key-Value Store', function() { - before(() => { - db.delete(); - }); - - afterEach(() => { - db.delete(); - }); - - it('put', async((done) => { - await(db.put('key1', 'hello!')); - let all = db.iterator().collect(); - assert.equal(all.length, 1); - assert.equal(all[0].hash.startsWith('Qm'), true); - assert.equal(all[0].key, 'key1'); - assert.notEqual(all[0].meta, null); - done(); - })); - - it('get', async((done) => { - await(db.put('key1', 'hello!')); - const value = db.get('key1'); - assert.equal(value, 'hello!'); - done(); - })); - - it('put updates a value', async((done) => { - await(db.put('key1', 'hello!')); - await(db.put('key1', 'hello again')); - const value = db.get('key1'); - assert.equal(value, 'hello again'); - done(); - })); - - it('deletes a key', async((done) => { - await(db.put('key1', 'hello!')); - await(db.del('key1')); - const value = db.get('key1'); - assert.equal(value, null); - done(); - })); - - it('deletes a key after multiple updates', async((done) => { - await(db.put('key1', 'hello1')); - await(db.put('key1', 'hello2')); - await(db.put('key1', 'hello3')); - await(db.del('key1')); - const value = db.get('key1'); - assert.equal(value, null); - done(); - })); - - it('put - multiple keys', async((done) => { - await(db.put('key1', 'hello1')); - await(db.put('key2', 'hello2')); - await(db.put('key3', 'hello3')); - const all = db.iterator().collect(); - assert.equal(all.length, 1); - done(); - })); - - it('get - multiple keys', async((done) => { - await(db.put('key1', 'hello1')); - await(db.put('key2', 'hello2')); - await(db.put('key3', 'hello3')); - const v1 = db.get('key1'); - const v2 = db.get('key2'); - const v3 = db.get('key3'); - assert.equal(v1, 'hello1'); - assert.equal(v2, 'hello2'); - assert.equal(v3, 'hello3'); - done(); - })); - - it('get - integer value', async((done) => { - await(db.put('key1', 123)); - const v1 = db.get('key1'); - assert.equal(v1, 123); - done(); - })); - - it('get - object value', async((done) => { - const val = { one: 'first', two: 2 }; - await(db.put('key1', val)); - const v1 = db.get('key1'); - assert.equal(_.isEqual(v1, val), true); - done(); - })); - - it('get - array value', async((done) => { - const val = [1, 2, 3, 4, 5]; - await(db.put('key1', val)); - const v1 = db.get('key1'); - assert.equal(_.isEqual(v1, val), true); - done(); - })); - }); -}); diff --git a/test/orbit-db-test-cache.json b/test/orbit-db-test-cache.json new file mode 100644 index 0000000..e7d819b --- /dev/null +++ b/test/orbit-db-test-cache.json @@ -0,0 +1,3 @@ +{ + "orbit-db.test": "QmdQEoYzjGGQYTBueU6B7s6Qr19qRdxWRuj9QUiQP17NRt" +} diff --git a/test/orbit-db.test.js b/test/orbit-db.test.js new file mode 100644 index 0000000..cab03c1 --- /dev/null +++ b/test/orbit-db.test.js @@ -0,0 +1,187 @@ +'use strict'; + +const _ = require('lodash'); +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); +const sinon = require('sinon'); +const async = require('asyncawait/async'); +const await = require('asyncawait/await'); +const ipfsDaemon = require('orbit-common/lib/ipfs-daemon'); +const OrbitDB = require('../src/OrbitDB'); +const Log = require('ipfs-log'); + +// Mute logging +// require('log4js').setGlobalLogLevel('ERROR'); + +// Orbit +const username = 'testrunner'; +const password = ''; +const user = { username: username }; + +describe('OrbitDB', function() { + this.timeout(2000); + + let db, ipfs; + let channel = 'orbit-db.test'; + + before(async((done) => { + try { + const daemon = await(ipfsDaemon()); + ipfs = daemon.ipfs; + } catch(e) { + console.log(e); + assert.equal(e, null); + } + done(); + })); + + describe('constructor', function() { + it('sets defaults', async((done) => { + db = new OrbitDB(ipfs); + assert.notEqual(db._ipfs, null); + assert.notEqual(db._logs, null); + assert.notEqual(db.options, null); + assert.equal(db.lastWrite, null); + assert.equal(db._cached.length, 0); + done(); + })); + + it('sets options', async((done) => { + db = new OrbitDB(ipfs, { option1: 'hello', option2: 2 }); + assert.equal(db.options.option1, 'hello'); + assert.equal(db.options.option2, 2); + done(); + })); + }); + + describe('use', function() { + beforeEach(() => { + db = new OrbitDB(ipfs); + }); + + it('sets user', (done) => { + db.use(channel, user).then(() => { + assert.equal(db.user.username, username); + done(); + }); + }); + + it('creates an empty log for the channel', (done) => { + db.use(channel, user).then(() => { + assert(db._logs[channel]); + assert.equal(db._logs[channel].id, username); + assert.equal(db._logs[channel].items.length, 0); + done(); + }); + }); + + it('creates event emitter for the channel', (done) => { + const EventEmitter = require('events').EventEmitter; + db.use(channel, user).then(() => { + assert(db.events[channel]); + assert.equal(db.events[channel] instanceof EventEmitter, true); + done(); + }); + }); + }); + + describe('sync', function() { + let log, otherLogHash, otherDbHash; + + beforeEach(async((done) => { + log = await(Log.create(ipfs, username)); + await(log.add("one")); + await(log.add("two")); + await(log.add("three")); + otherLogHash = await(Log.getIpfsHash(ipfs, log)); + + const cacheFile = path.join(process.cwd(), '/test', 'orbit-db-test-cache.json'); + + let count = 0; + const db2 = new OrbitDB(ipfs); + await(db2.use(channel, user)); + db2.events[channel].on('write', async((channel, hash) => { + otherDbHash = hash; + if(count === 2) { + const obj = Object.defineProperty({}, channel, { + value: hash, + writable: true + }); + fs.writeFileSync(cacheFile, JSON.stringify(obj)); + + db = new OrbitDB(ipfs, { cacheFile: cacheFile }); + await(db.use(channel, user)); + done(); + } else { + count ++; + } + })); + await(db2.add(channel, '', "hello world 1")); + await(db2.add(channel, '', "hello world 2")); + await(db2.add(channel, '', "hello world 3")); + })); + + afterEach(() => { + db = null; + }); + + describe('events', function() { + it('emits \'loaded\' event when sync hash is null', async((done) => { + db.events[channel].on('loaded', (src, channelName) => done()); + db.sync(channel, null); + })); + + it('emits \'load\' event when sync starts', async((done) => { + db.events[channel].on('load', (src, channelName) => done()); + db.sync(channel, otherDbHash); + })); + + it('emits \'loaded\' event when sync finishes', async((done) => { + db.events[channel].on('loaded', (src, channelName) => done()); + db.sync(channel, otherDbHash); + })); + + it('emits \'sync\' event if items were merged', async((done) => { + db.events[channel].on('sync', (channelName, hash) => { + assert.equal(channelName, channel); + assert.equal(hash, otherDbHash); + done(); + }); + db.sync(channel, otherDbHash); + })); + + it('doesn\'t emit \'sync\' event if items weren\'t merged', async((done) => { + const h1 = await(Log.getIpfsHash(ipfs, log)); + console.log(h1, otherLogHash) + db._logs[channel] = log; + db.events[channel].on('sync', (channelName, hash) => { + assert.equal(false, true); + done(); + }); + db.events[channel].on('loaded', (src, channelName) => done()); + db.sync(channel, otherLogHash); + })); + }); + + describe('cache payloads', function() { + it('caches payloads', (done) => { + assert.equal(db._cached.length, 0); + db.events[channel].on('loaded', (src, channelName) => { + assert.equal(db._cached.length, 3); + done(); + }); + db.sync(channel, otherDbHash); + }); + + it('throws an error if fetching went wrong', (done) => { + db.events[channel].once('error', (e) => { + assert.equal(e, 'invalid ipfs ref path'); + done(); + }); + db.sync(channel, otherLogHash); + }); + }); + + }); +});