From 1c37be24f3de62061e757d8c5b010a5195e9b9ee Mon Sep 17 00:00:00 2001 From: Anders Rune Jensen Date: Mon, 6 Sep 2021 23:25:20 +0200 Subject: [PATCH] Refactor formats a bit and add test --- index.js | 56 ++++++++------- package.json | 3 + test/formats.js | 187 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 221 insertions(+), 25 deletions(-) create mode 100644 test/formats.js diff --git a/index.js b/index.js index a879f52..cf7e3df 100644 --- a/index.js +++ b/index.js @@ -47,62 +47,67 @@ function cleanClock (clock, isFeed) { exports.init = function (sbot, config) { const formats = { 'classic': { - // used in sbot.post & in ebt:stream to distinguish between - // messages and notes - isMsg(m) { - return Number.isInteger(m.sequence) && m.sequence > 0 && - typeof m.author == 'string' && m.content - }, - // used in request, block, cleanClock + // used in request, block, cleanClock, sbot.post isFeed: ref.isFeed, + // used in getAt + fromDB(msg) { + return msg ? msg.value : null + }, + // used in append + toDB(msgVal) { + return msgVal + }, + + // used in ebt:stream to distinguish between messages and notes + isMsg(msgVal) { + return Number.isInteger(msgVal.sequence) && msgVal.sequence > 0 && + typeof msgVal.author == 'string' && msgVal.content + }, // used in ebt:events - getMsgAuthor(msg) { - return msg.author + getMsgAuthor(msgVal) { + return msgVal.author }, // used in ebt:events - getMsgSequence(msg) { - return msg.sequence + getMsgSequence(msgVal) { + return msgVal.sequence }, - // used in getAt - getAtTransform(msg) { - return msg ? msg.value : null - } } } const ebts = {} function addEBT(formatName) { - const dirName = 'ebt' + (formatName === 'classic') ? '' : formatName + const dirName = 'ebt' + (formatName === 'classic' ? '' : formatName) const dir = config.path ? path.join(config.path, dirName) : null const store = Store(dir, null, toUrlFriendly) - const isFeed = formats[formatName].isFeed + const format = formats[formatName] const ebt = EBT(Object.assign({ + format: formatName, logging: config.ebt && config.ebt.logging, id: sbot.id, getClock (id, cb) { store.ensure(id, function () { const clock = store.get(id) || {} - cleanClock(clock, isFeed) + cleanClock(clock, format.isFeed) cb(null, clock) }) }, setClock (id, clock) { - cleanClock(clock, isFeed) + cleanClock(clock, format.isFeed) store.set(id, clock) }, getAt (pair, cb) { - sbot.getAtSequence([pair.id, pair.sequence], (err, data) => { - cb(err, formats[formatName].getAtTransform(data)) + sbot.getAtSequence([pair.id, pair.sequence], (err, msg) => { + cb(err, format.fromDB(msg)) }) }, - append (msg, cb) { - sbot.add(msg, (err, msg) => { + append (msgVal, cb) { + sbot.add(format.toDB(msgVal), (err, msg) => { cb(err && err.fatal ? err : null, msg) }) } - }, formats[formatName])) + }, format)) ebts[formatName] = ebt } @@ -132,8 +137,9 @@ exports.init = function (sbot, config) { sbot.post((msg) => { for (let format in ebts) { - if (formats[format].isMsg(msg.value)) + if (formats[format].isFeed(msg.value.author)) { ebts[format].onAppend(msg.value) + } } }) diff --git a/package.json b/package.json index 379cc6c..dd9a0d6 100644 --- a/package.json +++ b/package.json @@ -37,10 +37,13 @@ "rimraf": "^2.7.1", "rng": "^0.2.2", "secret-stack": "^6.4.0", + "ssb-bendy-butt": "^0.12.2", "ssb-client": "^4.9.0", "ssb-db": "^19.2.0", + "ssb-db2": "github:ssb-ngi-pointer/ssb-db2#support-bendy-butt", "ssb-generate": "^1.0.1", "ssb-keys": "^8.1.0", + "ssb-uri2": "^1.5.2", "ssb-validate": "^4.1.4", "standardx": "^7.0.0", "tap-spec": "^5.0.0", diff --git a/test/formats.js b/test/formats.js new file mode 100644 index 0000000..822b86c --- /dev/null +++ b/test/formats.js @@ -0,0 +1,187 @@ +const tape = require('tape') +const crypto = require('crypto') +const SecretStack = require('secret-stack') +const sleep = require('util').promisify(setTimeout) +const pify = require('promisify-4loc') +const u = require('./misc/util') + +const caps = require('ssb-caps') +const rimraf = require('rimraf') +const mkdirp = require('mkdirp') +const ssbKeys = require('ssb-keys') +const SSBURI = require('ssb-uri2') +const bendyButt = require('ssb-bendy-butt') + +function createSsbServer() { + return SecretStack({ appKey: caps.shs }) + .use(require('ssb-db2')) + .use(require('ssb-db2/compat/ebt')) + .use(require('../')) +} + +const CONNECTION_TIMEOUT = 500 // ms +const REPLICATION_TIMEOUT = 2 * CONNECTION_TIMEOUT + +const aliceDir = '/tmp/test-format-alice' +rimraf.sync(aliceDir) +mkdirp.sync(aliceDir) + +const alice = createSsbServer().call(null, { + path: aliceDir, + timeout: CONNECTION_TIMEOUT, + keys: u.keysFor('alice'), + ebt: { + logging: false + } +}) + +const bobDir = '/tmp/test-format-bob' +rimraf.sync(bobDir) +mkdirp.sync(bobDir) + +const bob = createSsbServer().call(null, { + path: bobDir, + timeout: CONNECTION_TIMEOUT, + keys: u.keysFor('bob') +}) + +console.log("alice", alice.id) +console.log("bob", bob.id) + +tape('multiple formats', async (t) => { + const bendyButtMethods = { + // used in request, block, cleanClock, sbot.post + isFeed: SSBURI.isBendyButtV1FeedSSBURI, + // used in getAt + fromDB(msg) { + return msg ? bendyButt.encode(msg.value) : null + }, + // used in append + toDB(msgVal) { + return bendyButt.decode(msgVal) + }, + + // used in ebt:stream to distinguish between messages and notes + isMsg(bbVal) { + if (!Buffer.isBuffer(bbVal)) return false + + const msgVal = bendyButt.decode(bbVal) + return msgVal && + Number.isInteger(msgVal.sequence) && msgVal.sequence > 0 && + typeof msgVal.author == 'string' && msgVal.content + }, + // used in ebt:events + getMsgAuthor(bbVal) { + //console.log("bb getMsgAuthor", Buffer.isBuffer(bbVal)) + if (Buffer.isBuffer(bbVal)) + return bendyButt.decode(bbVal).author + else + return bbVal.author + }, + // used in ebt:events + getMsgSequence(bbVal) { + //console.log("bb getMsgSequence", Buffer.isBuffer(bbVal)) + if (Buffer.isBuffer(bbVal)) + return bendyButt.decode(bbVal).sequence + else + return bbVal.sequence + } + } + + alice.ebt.registerFormat('bendybutt', bendyButtMethods) + bob.ebt.registerFormat('bendybutt', bendyButtMethods) + + // self replicate + alice.ebt.request(alice.id, true) + bob.ebt.request(bob.id, true) + + // publish normal messages + await Promise.all([ + pify(alice.db.publish)({ type: 'post', text: 'hello' }), + pify(bob.db.publish)({ type: 'post', text: 'hello' }) + ]) + + function getBBMsg(mainKeys) { + // fake some keys + const mfKeys = ssbKeys.generate() + const classicUri = SSBURI.fromFeedSigil(mfKeys.id) + const { type, /* format, */ data } = SSBURI.decompose(classicUri) + const bendybuttUri = SSBURI.compose({ type, format: 'bendybutt-v1', data }) + mfKeys.id = bendybuttUri + + const content = { + type: "metafeed/add", + feedpurpose: "main", + subfeed: mainKeys.id, + metafeed: mfKeys.id, + tangles: { + metafeed: { + root: null, + previous: null + } + } + } + + const bbmsg = bendyButt.encodeNew( + content, + mainKeys, + mfKeys, + 1, + null, + Date.now(), + null + ) + + return bendyButt.decode(bbmsg) + } + + const aliceBBMsg = getBBMsg(alice.config.keys) + const bobBBMsg = getBBMsg(bob.config.keys) + + const aliceMFId = aliceBBMsg.author + const bobMFId = bobBBMsg.author + + // self replicate + alice.ebt.request(aliceMFId, true, 'bendybutt') + bob.ebt.request(bobMFId, true, 'bendybutt') + + await Promise.all([ + pify(alice.add)(aliceBBMsg), + pify(bob.add)(bobBBMsg) + ]) + + alice.ebt.request(bob.id, true) + alice.ebt.request(bobMFId, true, 'bendybutt') + + bob.ebt.request(alice.id, true) + bob.ebt.request(aliceMFId, true, 'bendybutt') + + await pify(bob.connect)(alice.getAddress()) + + await sleep(REPLICATION_TIMEOUT) + t.pass('wait for replication to complete') + + const clockAlice = await pify(alice.ebt.clock)('classic') + const bbClockAlice = await pify(alice.ebt.clock)('bendybutt') + + t.equal(clockAlice[alice.id], 1, 'clock ok') + t.equal(clockAlice[bob.id], 1, 'clock ok') + + t.equal(bbClockAlice[aliceMFId], 1, 'clock ok') + t.equal(bbClockAlice[bobMFId], 1, 'clock ok') + + const clockBob = await pify(bob.ebt.clock)('classic') + const bbClockBob = await pify(bob.ebt.clock)('bendybutt') + + t.equal(clockBob[alice.id], 1, 'clock ok') + t.equal(clockBob[bob.id], 1, 'clock ok') + + t.equal(bbClockBob[aliceMFId], 1, 'clock ok') + t.equal(bbClockBob[bobMFId], 1, 'clock ok') + + await Promise.all([ + pify(alice.close)(true), + pify(bob.close)(true) + ]) + t.end() +})