From 91099c9b82b0db733ac9a6f3e4415036f245c3d0 Mon Sep 17 00:00:00 2001 From: mixmix Date: Tue, 20 Sep 2022 16:54:41 +1200 Subject: [PATCH] make tests atomic, add persistence tests --- package.json | 4 +- test/api.js | 433 ------------------------- test/api.test.js | 331 +++++++++++++++++++ test/{keys.js => keys.test.js} | 0 test/{messages.js => messages.test.js} | 0 test/{query.js => query.test.js} | 0 test/testbot.js | 33 ++ test/{validate.js => validate.test.js} | 0 8 files changed, 366 insertions(+), 435 deletions(-) delete mode 100644 test/api.js create mode 100644 test/api.test.js rename test/{keys.js => keys.test.js} (100%) rename test/{messages.js => messages.test.js} (100%) rename test/{query.js => query.test.js} (100%) create mode 100644 test/testbot.js rename test/{validate.js => validate.test.js} (100%) diff --git a/package.json b/package.json index 4ad2675..28d0ca6 100644 --- a/package.json +++ b/package.json @@ -41,10 +41,10 @@ "ssb-db2": "^6.1.1", "ssb-caps": "^1.1.0", "tap-arc": "^0.3.5", - "tape": "^5.5.3" + "tape": "^5.6.0" }, "scripts": { - "test": "tape test/*.js | tap-arc --bail", + "test": "tape \"test/**/*.test.js\" | tap-arc --bail", "format-code": "prettier --write \"*.js\" \"test/*.js\"", "format-code-staged": "pretty-quick --staged --pattern \"*.js\" --pattern \"test/*.js\"", "coverage": "c8 --reporter=lcov npm run test" diff --git a/test/api.js b/test/api.js deleted file mode 100644 index ddfb535..0000000 --- a/test/api.js +++ /dev/null @@ -1,433 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Anders Rune Jensen -// -// SPDX-License-Identifier: Unlicense - -const test = require('tape') -const ssbKeys = require('ssb-keys') -const path = require('path') -const rimraf = require('rimraf') -const pull = require('pull-stream') -const SecretStack = require('secret-stack') -const caps = require('ssb-caps') -const { author, where, toCallback } = require('ssb-db2/operators') -const tape = require('tape') - -const dir = '/tmp/metafeeds-metafeed' -const mainKey = ssbKeys.loadOrCreateSync(path.join(dir, 'secret')) - -rimraf.sync(dir) - -let sbot = SecretStack({ appKey: caps.shs }) - .use(require('ssb-db2')) - .use(require('ssb-bendy-butt')) - .use(require('../')) - .call(null, { - keys: mainKey, - path: dir, - }) -let db = sbot.db - -test('getRoot() when there is nothing', (t) => { - sbot.metafeeds.getRoot((err, found) => { - t.error(err, 'no err for find()') - t.notOk(found, 'nothing found') - t.end() - }) -}) - -test('findOrCreate(null, ...) can create the root metafeed', (t) => { - db.query( - where(author(mainKey.id)), - toCallback((err, msgs) => { - t.equals(msgs.length, 0, 'empty db') - - sbot.metafeeds.findOrCreate(null, null, null, (err, mf) => { - t.error(err, 'no err for findOrCreate()') - // t.equals(mf.feeds.length, 1, '1 sub feed in the root metafeed') - // t.equals(mf.feeds[0].feedpurpose, 'main', 'it is the main feed') - t.equals(mf.seed.toString('hex').length, 64, 'seed length is okay') - t.equals(typeof mf.keys.id, 'string', 'key seems okay') - t.end() - }) - }) - ) -}) - -test('findOrCreate is idempotent', (t) => { - sbot.metafeeds.getRoot((err, mf) => { - t.error(err, 'no err for getRoot()') - t.equals(mf.seed.toString('hex').length, 64, 'seed length is okay') - t.equals(typeof mf.keys.id, 'string', 'key seems okay') - const originalSeed = mf.seed.toString('hex') - const originalID = mf.keys.id - - sbot.metafeeds.findOrCreate((err, mf) => { - t.error(err, 'no err for findOrCreate(null, ...)') - t.equals(mf.seed.toString('hex'), originalSeed, 'same seed') - t.equals(mf.keys.id, originalID, 'same ID') - - t.end() - }) - }) -}) - -tape('findOrCreate() a sub feed', (t) => { - sbot.metafeeds.getRoot((err, mf) => { - t.error(err, 'gets rootFeed') - - // lets create a new chess feed - sbot.metafeeds.findOrCreate( - mf, - (f) => f.feedpurpose === 'chess', - { - feedpurpose: 'chess', - feedformat: 'classic', - metadata: { score: 0 }, - }, - (err, feed) => { - t.equals(feed.feedpurpose, 'chess', 'it is the chess feed') - t.equals(feed.metadata.score, 0, 'it has metadata') - t.end() - } - ) - }) -}) - -tape('all FeedDetails have same format', (t) => { - sbot.metafeeds.getRoot((err, mf) => { - if (err) throw err - sbot.metafeeds.findOrCreate( - null, - () => true, - {}, - (err, _mf) => { - if (err) throw err - - t.deepEquals( - mf, - _mf, - 'getRoot and findOrCreate return the same root FeedDetails' - ) - - sbot.metafeeds.findOrCreate( - mf, - (f) => f.feedpurpose === 'chess', - { - feedpurpose: 'chess', - feedformat: 'classic', - metadata: { score: 0 }, - }, - (err, feed) => { - t.deepEquals( - Object.keys(mf).sort(), - Object.keys(feed).sort(), - 'root & chess FeedDetails have same data structure' - ) - t.end() - } - ) - } - ) - }) -}) - -tape('findOrCreate() a sub meta feed', (t) => { - sbot.metafeeds.findOrCreate((err, mf) => { - sbot.metafeeds.findOrCreate( - mf, - (f) => f.feedpurpose === 'indexes', - { feedpurpose: 'indexes', feedformat: 'bendybutt-v1' }, - (err, f) => { - t.error(err, 'no err') - t.equals(f.feedpurpose, 'indexes', 'it is the indexes subfeed') - t.true( - f.subfeed.startsWith('ssb:feed/bendybutt-v1/'), - 'has a bendy butt SSB URI' - ) - t.end() - } - ) - }) -}) - -let testIndexesMF -let testIndexFeed - -tape('findOrCreate() a subfeed under a sub meta feed', (t) => { - sbot.metafeeds.getRoot((err, rootMF) => { - sbot.metafeeds.findOrCreate( - rootMF, - (f) => f.feedpurpose === 'indexes', - { feedpurpose: 'indexes', feedformat: 'bendybutt-v1' }, - (err, indexesMF) => { - t.equals(indexesMF.feedpurpose, 'indexes', 'got the indexes meta feed') - testIndexesMF = indexesMF - - sbot.metafeeds.findOrCreate( - indexesMF, - (f) => f.feedpurpose === 'index', - { - feedpurpose: 'index', - feedformat: 'indexed-v1', - metadata: { query: 'foo' }, - }, - (err, f) => { - testIndexFeed = f.subfeed - t.error(err, 'no err') - t.equals(f.feedpurpose, 'index', 'it is the index subfeed') - t.equals(f.metadata.query, 'foo', 'query is okay') - t.true( - f.subfeed.startsWith('ssb:feed/indexed-v1/'), - 'feed format is indexed-v1' - ) - - t.end() - } - ) - } - ) - }) -}) - -test('findById and findByIdSync', (t) => { - sbot.metafeeds.findById(null, (err, details) => { - t.match(err.message, /feedId should be provided/, 'error about feedId') - t.notOk(details) - - sbot.metafeeds.findById(testIndexFeed, (err, details) => { - t.error(err, 'no err') - t.deepEquals(Object.keys(details), [ - 'feedformat', - 'feedpurpose', - 'metafeed', - 'metadata', - ]) - t.equals(details.feedpurpose, 'index') - t.equals(details.metafeed, testIndexesMF.keys.id) - t.equals(details.feedformat, 'indexed-v1') - - t.end() - }) - }) -}) - -test('branchStream', (t) => { - pull( - sbot.metafeeds.branchStream({ old: true, live: false }), - pull.collect((err, branches) => { - t.error(err, 'no err') - t.equal(branches.length, 5, '5 branches') - - t.equal(branches[0].length, 1, 'root mf alone') - t.equal(typeof branches[0][0][0], 'string', 'root mf alone') - t.equal(branches[0][0][1], null, 'root mf alone') - - t.equal(branches[1].length, 2, 'main branch') - t.equal(branches[1][1][1].feedpurpose, 'main', 'main branch') - - t.equal(branches[2].length, 2, 'chess branch') - t.equal(branches[2][1][1].feedpurpose, 'chess', 'chess branch') - - t.equal(branches[3].length, 2, 'indexes branch') - t.equal(branches[3][1][1].feedpurpose, 'indexes', 'indexes branch') - - t.equal(branches[4].length, 3, 'index branch') - t.equal(branches[4][2][1].feedpurpose, 'index', 'indexes branch') - - t.end() - }) - ) -}) - -test('restart sbot', (t) => { - sbot.close(true, () => { - sbot = SecretStack({ appKey: caps.shs }) - .use(require('ssb-db2')) - .use(require('ssb-bendy-butt')) - .use(require('../')) - .call(null, { - keys: mainKey, - path: dir, - }) - - sbot.metafeeds.findById(testIndexFeed, (err, details) => { - t.error(err, 'no err') - t.equals(details.feedpurpose, 'index') - t.equals(details.metafeed, testIndexesMF.keys.id) - t.equals(details.feedformat, 'indexed-v1') - - sbot.metafeeds.getRoot((err, mf) => { - t.error(err, 'no err') - t.ok(Buffer.isBuffer(mf.seed), 'has seed') - t.ok(mf.keys.id.startsWith('ssb:feed/bendybutt-v1/'), 'has key') - - pull( - sbot.metafeeds.branchStream({ - root: mf.keys.id, - old: true, - live: false, - }), - pull.collect((err, branches) => { - t.error(err, 'no err') - t.equal(branches.length, 5, '5 branches') - - t.equal(branches[0].length, 1, 'root mf alone') - t.equal(typeof branches[0][0][0], 'string', 'root mf alone') - t.equal(branches[0][0][1], null, 'root mf alone') - - t.equal(branches[1].length, 2, 'main branch') - t.equal(branches[1][1][1].feedpurpose, 'main', 'main branch') - - t.equal(branches[2].length, 2, 'chess branch') - t.equal(branches[2][1][1].feedpurpose, 'chess', 'chess branch') - - const indexesId = branches[3][1][0] - t.equal(branches[3].length, 2, 'indexes branch') - t.equal(branches[3][1][1].feedpurpose, 'indexes', 'indexes branch') - - t.equal(branches[4].length, 3, 'index branch') - t.equal(branches[4][2][1].feedpurpose, 'index', 'indexes branch') - - pull( - sbot.metafeeds.branchStream({ - root: indexesId, - old: true, - live: false, - }), - pull.collect((err, branches) => { - t.error(err, 'no err') - t.equal(branches.length, 2, '2 branches') - - t.equal(branches[0].length, 1, 'indexes branch') - t.equal( - branches[0][0][1].feedpurpose, - 'indexes', - 'indexes branch' - ) - - t.equal(branches[1].length, 2, 'index branch') - t.equal( - branches[1][1][1].feedpurpose, - 'index', - 'indexes branch' - ) - - t.end() - }) - ) - }) - ) - }) - }) - }) -}) - -tape('findAndTombstone and tombstoning branchStream', (t) => { - sbot.metafeeds.getRoot((err, mf) => { - pull( - sbot.metafeeds.branchStream({ tombstoned: true, old: false, live: true }), - pull.drain((branch) => { - t.equals(branch.length, 2) - t.equals(branch[0][0], mf.keys.id) - t.equals(branch[1][1].feedpurpose, 'chess') - t.equals(branch[1][1].reason, 'This game is too good') - - pull( - sbot.metafeeds.branchStream({ - tombstoned: true, - old: true, - live: false, - }), - pull.drain((branch) => { - t.equals(branch.length, 2) - t.equals(branch[0][0], mf.keys.id) - t.equals(branch[1][1].feedpurpose, 'chess') - t.equals(branch[1][1].reason, 'This game is too good') - - pull( - sbot.metafeeds.branchStream({ - tombstoned: false, - old: true, - live: false, - }), - pull.collect((err, branches) => { - t.error(err, 'no err') - t.equal(branches.length, 4, '4 branches') - - pull( - sbot.metafeeds.branchStream({ - tombstone: null, - old: true, - live: false, - }), - pull.collect((err, branches) => { - t.error(err, 'no err') - t.equal(branches.length, 5, '5 branches') - - t.end() - }) - ) - }) - ) - }) - ) - }) - ) - - sbot.metafeeds.findAndTombstone( - mf, - (f) => f.feedpurpose === 'chess', - 'This game is too good', - (err) => { - t.error(err, 'no err') - } - ) - }) -}) - -tape('findOrCreate() recps', (t) => { - const boxdir = '/tmp/metafeeds-metafeed-box2' - const boxKey = ssbKeys.loadOrCreateSync(path.join(boxdir, 'secret')) - - rimraf.sync(dir) - - let sbotBox2 = SecretStack({ appKey: caps.shs }) - .use(require('ssb-db2')) - .use(require('ssb-bendy-butt')) - .use(require('../')) - .call(null, { - keys: boxKey, - path: boxdir, - }) - - const testkey = Buffer.from( - '30720d8f9cbf37f6d7062826f6decac93e308060a8aaaa77e6a4747f40ee1a76', - 'hex' - ) - - sbotBox2.box2.setOwnDMKey(testkey) - - sbotBox2.metafeeds.findOrCreate((err, mf) => { - sbotBox2.metafeeds.findOrCreate( - mf, - (f) => f.feedpurpose === 'private', - { - feedpurpose: 'private', - feedformat: 'classic', - metadata: { - recps: [sbotBox2.id], - }, - }, - (err, f) => { - t.error(err, 'no err') - t.equal(f.feedpurpose, 'private') - t.equal(f.metadata.recps[0], sbotBox2.id) - sbotBox2.close(t.end) - } - ) - }) -}) - -tape('teardown', (t) => { - sbot.close(true, t.end) -}) diff --git a/test/api.test.js b/test/api.test.js new file mode 100644 index 0000000..a22bec4 --- /dev/null +++ b/test/api.test.js @@ -0,0 +1,331 @@ +// SPDX-FileCopyrightText: 2021 Anders Rune Jensen +// +// SPDX-License-Identifier: Unlicense + +const test = require('tape') +const pull = require('pull-stream') +const { author, where, toPromise } = require('ssb-db2/operators') +const { promisify: p } = require('util') +const Testbot = require('./testbot.js') + +test('getRoot / findOrCreate (root)', async (t) => { + let sbot = Testbot() + const { path } = sbot.config + + await p(sbot.metafeeds.getRoot)() + .then((found) => + t.equal(found, null, 'getRoot() finds nothing in blank db') + ) + .catch((err) => t.error(err, 'no err for getRoot()')) + + await sbot.db + .query(where(author(sbot.id)), toPromise()) + .then((msgs) => t.equals(msgs.length, 0, 'db is empty')) + .catch(t.error) + + const mf = await p(sbot.metafeeds.findOrCreate)().catch((err) => + t.error(err, 'no err for findOrCreate()') + ) + + // t.equals(mf.feeds.length, 1, '1 sub feed in the root metafeed') + // t.equals(mf.feeds[0].feedpurpose, 'main', 'it is the main feed') + t.equals(mf.seed.toString('hex').length, 64, 'seed length is okay') + t.equals(typeof mf.keys.id, 'string', 'key seems okay') + + const mf2 = await p(sbot.metafeeds.findOrCreate)() + const mf3 = await p(sbot.metafeeds.findOrCreate)(null, null, null) + const mf4 = await p(sbot.metafeeds.getRoot)() + + t.deepEqual(mf, mf2, 'findOrCreate is is idempotent (A)') + t.deepEqual(mf, mf3, 'findOrCreate is is idempotent (B)') + t.deepEqual(mf, mf4, 'findOrCreate + getRoot return same mf') + + console.log('persistence') + await p(sbot.close)() + sbot = Testbot({ path, rimraf: false }) + + const mf5 = await p(sbot.metafeeds.findOrCreate)() + const mf6 = await p(sbot.metafeeds.getRoot)() + + t.deepEqual(mf, mf5, 'findOrCreate') + t.deepEqual(mf, mf6, 'getRoot') + + await p(sbot.close)() + t.end() +}) + +// Mock a metafeed tree of shape: +// root +// - chess +// - indexes +// - about +async function setupTree(sbot) { + const rootMF = await p(sbot.metafeeds.findOrCreate)() + const chessMF = await p(sbot.metafeeds.findOrCreate)( + rootMF, + (f) => f.feedpurpose === 'chess', + { + feedpurpose: 'chess', + feedformat: 'classic', + metadata: { score: 0 }, + } + ) + const indexesMF = await p(sbot.metafeeds.findOrCreate)( + rootMF, + (f) => f.feedpurpose === 'indexes', + { feedpurpose: 'indexes', feedformat: 'bendybutt-v1' } + ) + const aboutMF = await p(sbot.metafeeds.findOrCreate)( + indexesMF, + (f) => f.feedpurpose === 'about', + { + feedpurpose: 'about', + feedformat: 'classic', + metadata: { query: 'foo' }, + } + ) + + return { rootMF, chessMF, indexesMF, aboutMF } +} + +test('findOrCreate() a sub feed', async (t) => { + const sbot = Testbot() + const { rootMF, chessMF, indexesMF, aboutMF } = await setupTree(sbot) + + // create a new chess subfeed + t.equals(chessMF.feedpurpose, 'chess', 'it is the chess feed') + t.equals(chessMF.metadata.score, 0, 'it has metadata') + + // create an "indexes" meta subfeed + t.equals(indexesMF.feedpurpose, 'indexes', 'it is the indexes subfeed') + t.true( + indexesMF.subfeed.startsWith('ssb:feed/bendybutt-v1/'), + 'has a bendy butt SSB URI' + ) + + // create an about subfeed under "indexes" + t.equals(aboutMF.feedpurpose, 'about', 'it is the about index subfeed') + t.equals(aboutMF.metadata.query, 'foo', 'query is okay') + t.true(aboutMF.subfeed.endsWith('.ed25519'), 'is a classic feed') + + sbot.close() + t.end() +}) + +test('findById', async (t) => { + let sbot = Testbot() + const { path } = sbot.config + const { aboutMF } = await setupTree(sbot) + + async function testFinds(sbot) { + await p(sbot.metafeeds.findById)(null) + .catch((err) => + t.match( + err.message, + /feedId should be provided/, + 'findById requires feedId' + ) + ) + .then((details) => t.error(details)) // there should be not details + + const details = await p(sbot.metafeeds.findById)(aboutMF.subfeed).catch( + t.error + ) + t.deepEquals( + Object.keys(details), + ['feedformat', 'feedpurpose', 'metafeed', 'metadata'], + 'has correct fields' + ) + t.equals(details.feedpurpose, 'about', 'purpose') + t.equals(details.feedformat, 'classic', 'format') + t.equals(details.metafeed, aboutMF.metafeed, 'keys.id') + } + + await testFinds(sbot) + + console.log('persistence') + await p(sbot.close)() + sbot = Testbot({ path, rimraf: false }) + await testFinds(sbot) + + sbot.close() + t.end() +}) + +// NOT advanced +test('branchStream', async (t) => { + let sbot = Testbot() + const { path } = sbot.config + + const { indexesMF } = await setupTree(sbot) + + async function testBranchStream(sbot) { + return new Promise((resolve, reject) => { + pull( + sbot.metafeeds.branchStream({ old: true, live: false }), + pull.collect((err, branches) => { + if (err) reject(err) + + t.equal(branches.length, 5, '5 branches') + + t.equal(branches[0].length, 1, 'root mf alone') + t.equal(typeof branches[0][0][0], 'string', 'root mf alone') + t.equal(branches[0][0][1], null, 'root mf alone') + + t.equal(branches[1].length, 2, 'main branch') + t.equal(branches[1][1][1].feedpurpose, 'main', 'main branch') + + t.equal(branches[2].length, 2, 'chess branch') + t.equal(branches[2][1][1].feedpurpose, 'chess', 'chess branch') + + t.equal(branches[3].length, 2, 'indexes branch') + t.equal(branches[3][1][1].feedpurpose, 'indexes', 'indexes branch') + + t.equal(branches[4].length, 3, 'about branch') + t.equal(branches[4][2][1].feedpurpose, 'about', 'indexes branch') + + pull( + sbot.metafeeds.branchStream({ + root: indexesMF.subfeed, + old: true, + live: false, + }), + pull.collect((err, branches) => { + if (err) reject(err) + + t.equal(branches.length, 2, '2 branches') + + t.equal(branches[0].length, 1, 'indexes branch') + t.equal( + branches[0][0][1].feedpurpose, + 'indexes', + 'indexes branch' + ) + + t.equal(branches[1].length, 2, 'index branch') + t.equal(branches[1][1][1].feedpurpose, 'about', 'indexes branch') + }) + ) + + resolve() + }) + ) + }) + } + + await testBranchStream(sbot).catch(t.error) + + console.log('persistence') + await p(sbot.close)() + sbot = Testbot({ path, rimraf: false }) + await testBranchStream(sbot).catch(t.error) + + sbot.close() + t.end() +}) + +test('findAndTombstone and tombstoning branchStream', async (t) => { + let sbot = Testbot() + const { path } = sbot.config + + const { rootMF } = await setupTree(sbot) + + let todo = 3 + pull( + sbot.metafeeds.branchStream({ tombstoned: true, old: false, live: true }), + pull.drain((branch) => { + t.equals(branch.length, 2, 'live') + t.equals(branch[0][0], rootMF.keys.id, 'live') + t.equals(branch[1][1].feedpurpose, 'chess', 'live') + t.equals(branch[1][1].reason, 'This game is too good', 'live') + + testOldStreams(sbot) + }) + ) + + function testOldStreams(sbot) { + pull( + sbot.metafeeds.branchStream({ tombstoned: true, old: true, live: false }), + pull.drain((branch) => { + t.equals(branch.length, 2) + t.equals(branch[0][0], rootMF.keys.id, 'tombstoned: true') + t.equals(branch[1][1].feedpurpose, 'chess', 'tombstoned: true') + t.equals( + branch[1][1].reason, + 'This game is too good', + 'tombstoned: true' + ) + todo-- + }) + ) + + pull( + sbot.metafeeds.branchStream({ + tombstoned: false, + old: true, + live: false, + }), + pull.collect((err, branches) => { + if (err) throw err + t.equal(branches.length, 4, 'tombstoned: false') + todo-- + }) + ) + + pull( + sbot.metafeeds.branchStream({ tombstoned: null, old: true, live: false }), + pull.collect((err, branches) => { + if (err) throw err + t.equal(branches.length, 5, 'tombstoned: null') + todo-- + }) + ) + } + + await p(sbot.metafeeds.findAndTombstone)( + rootMF, + (f) => f.feedpurpose === 'chess', + 'This game is too good' + ) + + while (todo > 0) await p(setTimeout)(100) + + console.log('(persistence)') + await p(sbot.close)() + sbot = Testbot({ path, rimraf: false }) + + todo = 3 + testOldStreams(sbot) + while (todo > 0) await p(setTimeout)(100) + + sbot.close() + t.end() +}) + +test('findOrCreate() recps', async (t) => { + const sbot = Testbot() + + const testkey = Buffer.from( + '30720d8f9cbf37f6d7062826f6decac93e308060a8aaaa77e6a4747f40ee1a76', + 'hex' + ) + sbot.box2.setOwnDMKey(testkey) + + const mf = await p(sbot.metafeeds.findOrCreate)() + const f = await p(sbot.metafeeds.findOrCreate)( + mf, + (f) => f.feedpurpose === 'private', + { + feedpurpose: 'private', + feedformat: 'classic', + metadata: { + recps: [sbot.id], + }, + } + ) + + t.equal(f.feedpurpose, 'private') + t.equal(f.metadata.recps[0], sbot.id) + sbot.close() + t.end() +}) diff --git a/test/keys.js b/test/keys.test.js similarity index 100% rename from test/keys.js rename to test/keys.test.js diff --git a/test/messages.js b/test/messages.test.js similarity index 100% rename from test/messages.js rename to test/messages.test.js diff --git a/test/query.js b/test/query.test.js similarity index 100% rename from test/query.js rename to test/query.test.js diff --git a/test/testbot.js b/test/testbot.js new file mode 100644 index 0000000..f83eb3d --- /dev/null +++ b/test/testbot.js @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2022 Mix Irving +// +// SPDX-License-Identifier: Unlicense + +const SecretStack = require('secret-stack') +const ssbKeys = require('ssb-keys') +const path = require('path') +const rimraf = require('rimraf') +const caps = require('ssb-caps') + +let count = 0 + +// opts.path (optional) +// opts.name (optional) - convenience method for deterministic opts.path +// opts.keys (optional) +// opts.rimraf (optional) - clear the directory before start (default: true) + +module.exports = function createSbot(opts = {}) { + const dir = opts.path || `/tmp/metafeeds-metafeed-${opts.name || count++}` + if (opts.rimraf !== false) rimraf.sync(dir) + + const keys = opts.keys || ssbKeys.loadOrCreateSync(path.join(dir, 'secret')) + + const stack = SecretStack({ appKey: caps.shs }) + .use(require('ssb-db2')) + .use(require('ssb-bendy-butt')) + .use(require('../')) + + return stack({ + path: dir, + keys, + }) +} diff --git a/test/validate.js b/test/validate.test.js similarity index 100% rename from test/validate.js rename to test/validate.test.js