UNPKG

@web4/bitdrive

Version:

Bitdrive is a secure, real time distributed file system

1,244 lines (1,102 loc) 32.2 kB
const test = require('tape') const ram = require('random-access-memory') const Chainstore = require('@web4/chainstore') const Replicator = require('./helpers/replicator') const create = require('./helpers/create') const bitdrive = require('../') test('basic read/write to/from a mount', t => { const r = new Replicator(t) const drive1 = create() const drive2 = create() r.replicate(drive1, drive2) drive2.ready(err => { t.error(err, 'no error') drive2.writeFile('b', 'hello', err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.readFile('a/b', (err, contents) => { t.error(err, 'no error') t.same(contents, Buffer.from('hello')) r.end() }) }) }) }) }) test('should emit metadata-feed and content-feed events for all mounts', t => { const r = new Replicator(t) const drive1 = create() const drive2 = create() r.replicate(drive1, drive2) var metadataCount = 0 var contentCount = 0 drive1.on('metadata-feed', () => { metadataCount++ }) drive1.on('content-feed', () => { contentCount++ }) drive2.ready(err => { t.error(err, 'no error') drive2.writeFile('hello', 'world', err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.readFile('a/hello', (err, content) => { t.error(err, 'no error') t.same(content, Buffer.from('world')) checkEvents() }) }) }) }) function checkEvents () { t.same(contentCount, 2) t.same(metadataCount, 2) r.end() } }) test('can delete a mount', t => { const r = new Replicator(t) const drive1 = create() const drive2 = create() r.replicate(drive1, drive2) drive2.ready(err => { t.error(err, 'no error') drive2.writeFile('b', 'hello', err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.readFile('a/b', (err, contents) => { t.error(err, 'no error') t.same(contents, Buffer.from('hello')) return deleteMount() }) }) }) }) function deleteMount () { drive1.unmount('a', err => { t.error(err, 'no error') drive1.readFile('a/b', (err, contents) => { t.true(err) t.same(err.errno, 2) r.end() }) }) } }) test('multiple flat mounts', t => { const r = new Replicator(t) const drive1 = create() const drive2 = create() const drive3 = create() var key1, key2 r.replicate(drive1, drive2) r.replicate(drive2, drive3) r.replicate(drive1, drive3) drive3.ready(err => { t.error(err, 'no error') drive2.ready(err => { t.error(err, 'no error') key1 = drive2.key key2 = drive3.key onready() }) }) function onready () { drive2.writeFile('a', 'hello', err => { t.error(err, 'no error') drive3.writeFile('b', 'world', err => { t.error(err, 'no error') onwrite() }) }) } function onwrite () { drive1.mount('a', key1, err => { t.error(err, 'no error') drive1.mount('b', key2, err => { t.error(err, 'no error') onmount() }) }) } function onmount () { drive1.readFile('a/a', (err, contents) => { t.error(err, 'no error') t.same(contents, Buffer.from('hello')) drive1.readFile('b/b', (err, contents) => { t.error(err, 'no error') t.same(contents, Buffer.from('world')) r.end() }) }) } }) test('recursive mounts', async t => { const r = new Replicator(t) const drive1 = create() const drive2 = create() const drive3 = create() var key1, key2 r.replicate(drive1, drive2) r.replicate(drive2, drive3) r.replicate(drive1, drive3) drive3.ready(err => { t.error(err, 'no error') drive2.ready(err => { t.error(err, 'no error') key1 = drive2.key key2 = drive3.key onready() }) }) function onready () { drive2.writeFile('a', 'hello', err => { t.error(err, 'no error') drive3.writeFile('b', 'world', err => { t.error(err, 'no error') onwrite() }) }) } function onwrite () { drive1.mount('a', key1, err => { t.error(err, 'no error') drive2.mount('b', key2, err => { t.error(err, 'no error') onmount() }) }) } function onmount () { drive1.readFile('a/a', (err, contents) => { t.error(err, 'no error') t.same(contents, Buffer.from('hello')) drive1.readFile('a/b/b', (err, contents) => { t.error(err, 'no error') t.same(contents, Buffer.from('world')) r.end() }) }) } }) test('readdir returns mounts', t => { const r = new Replicator(t) const drive1 = create() const drive2 = create() r.replicate(drive1, drive2) drive2.ready(err => { t.error(err, 'no error') drive1.mkdir('b', err => { t.error(err, 'no error') drive1.mkdir('b/a', err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.readdir('/', (err, dirs) => { t.error(err, 'no error') t.same(dirs, ['b', 'a']) r.end() }) }) }) }) }) }) test('cross-mount watch', t => { const r = new Replicator(t) const drive1 = create() const drive2 = create() r.replicate(drive1, drive2) var watchEvents = 0 drive2.ready(err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.watch('/', () => { if (++watchEvents === 1) r.end() }) drive2.writeFile('a', 'hello', err => { t.error(err, 'no error') }) }) }) }) test('cross-mount symlink', t => { const r = new Replicator(t) const drive1 = create() const drive2 = create() r.replicate(drive1, drive2) drive2.ready(err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') onmount() }) }) function onmount () { drive2.writeFile('b', 'hello world', err => { t.error(err, 'no error') drive1.symlink('a/b', 'c', err => { t.error(err, 'no error') drive1.readFile('c', (err, contents) => { t.error(err, 'no error') t.same(contents, Buffer.from('hello world')) r.end() }) }) }) } }) test('lists nested mounts, shared write capabilities', async t => { const store = new Chainstore(ram) store.ready(onready) function onready () { const drive1 = create({ corestore: store, namespace: 'd1' }) const drive2 = create({ corestore: store, namespace: 'd2' }) const drive3 = create({ corestore: store, namespace: 'd3' }) drive3.ready(err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.mount('a/b', drive3.key, err => { t.error(err, 'no error') onmount(drive1, drive2, drive3) }) }) }) } function onmount (drive1, drive2, drive3) { drive2.lstat('b', (err, stat) => { t.error(err, 'no error') drive1.readdir('a', (err, list) => { t.error(err, 'no error') t.same(list, ['b']) t.end() }) }) } }) test('nested mount readdir returns correct mount', async t => { const store = new Chainstore(ram) let expected = {} store.ready(onready) function onready () { const drive1 = create({ corestore: store, namespace: 'd1' }) const drive2 = create({ corestore: store, namespace: 'd2' }) const drive3 = create({ corestore: store, namespace: 'd3' }) drive3.ready(err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.mount('a/b', drive3.key, err => { t.error(err, 'no error') drive1.writeFile('a/b/c', 'hello', err => { t.error(err, 'no error') expected['a'] = drive1.key expected['a/b'] = drive2.key expected['a/b/c'] = drive3.key onmount(drive1, drive2, drive3) }) }) }) }) } function onmount (drive1, drive2, drive3) { drive1.readdir('/', { recursive: true, includeStats: true }, (err, list) => { t.error(err, 'no error') let seen = 0 for (let { name, stat, mount } of list) { t.true(expected[name] && expected[name].equals(mount.key), 'correct mount key') seen++ } t.same(seen, 3) t.end() }) } }) test('nested mount readdir returns correct mount starting in mountpoint', async t => { const store = new Chainstore(ram) let expected = {} store.ready(onready) function onready () { const drive1 = create({ corestore: store, namespace: 'd1' }) const drive2 = create({ corestore: store, namespace: 'd2' }) const drive3 = create({ corestore: store, namespace: 'd3' }) drive3.ready(err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.mount('a/b', drive3.key, err => { t.error(err, 'no error') drive1.writeFile('a/b/c', 'hello', err => { t.error(err, 'no error') expected['b'] = drive2.key expected['b/c'] = drive3.key onmount(drive1, drive2, drive3) }) }) }) }) } function onmount (drive1, drive2, drive3) { drive1.readdir('/a', { recursive: true, includeStats: true }, (err, list) => { t.error(err, 'no error') let seen = 0 for (let { name, stat, mount } of list) { t.true(expected[name] && expected[name].equals(mount.key), 'correct mount key') seen++ } t.same(seen, 2) t.end() }) } }) test('nested mount readdir returns correct stat modes', async t => { const store = new Chainstore(ram) let expected = {} store.ready(onready) function onready () { const drive1 = create({ corestore: store, namespace: 'd1' }) const drive2 = create({ corestore: store, namespace: 'd2' }) const drive3 = create({ corestore: store, namespace: 'd3' }) drive3.ready(err => { t.error(err, 'no error') drive1.mkdir('b', err => { t.error(err, 'no error') drive1.writeFile('b/c', 'hello', err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.mount('a/b', drive3.key, err => { t.error(err, 'no error') drive1.writeFile('a/b/c', 'hello', err => { t.error(err, 'no error') expected['a'] = 16877 expected['b'] = 16877 expected['b/c'] = 33188 expected['a/b'] = 16877 expected['a/b/c'] = 33188 onmount(drive1, drive2, drive3) }) }) }) }) }) }) } function onmount (drive1, drive2, drive3) { drive1.readdir('/', { recursive: true, includeStats: true }, (err, list) => { t.error(err, 'no error') let seen = 0 for (let { name, stat, mount } of list) { t.true(expected[name] && expected[name] === stat.mode, 'correct stat mode') seen++ } t.same(seen, 5) t.end() }) } }) test('nested mount readdir returns correct stat modes, non-recursive', async t => { const store = new Chainstore(ram) let expected = {} store.ready(onready) function onready () { const drive1 = create({ corestore: store, namespace: 'd1' }) const drive2 = create({ corestore: store, namespace: 'd2' }) const drive3 = create({ corestore: store, namespace: 'd3' }) drive3.ready(err => { t.error(err, 'no error') drive1.writeFile('b/c', 'hello', err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.mount('a/b', drive3.key, err => { t.error(err, 'no error') drive1.writeFile('a/b/c', 'hello', err => { t.error(err, 'no error') expected['a'] = 16877 expected['b'] = 16877 onmount(drive1, drive2, drive3) }) }) }) }) }) } function onmount (drive1, drive2, drive3) { drive1.readdir('/', { recursive: false, includeStats: true }, (err, list) => { t.error(err, 'no error') let seen = 0 for (let { name, stat, mount } of list) { t.true(expected[name] && expected[name] === stat.mode, 'correct stat mode') seen++ } t.same(seen, 2) t.end() }) } }) test('nested mount readdir returns correct inner paths, non-recursive', async t => { const store = new Chainstore(ram) let expected = {} store.ready(onready) function onready () { const drive1 = create({ corestore: store, namespace: 'd1' }) const drive2 = create({ corestore: store, namespace: 'd2' }) const drive3 = create({ corestore: store, namespace: 'd3' }) drive3.ready(err => { t.error(err, 'no error') drive1.writeFile('b/c', 'hello', err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.mount('a/b', drive3.key, err => { t.error(err, 'no error') drive1.writeFile('a/b/c', 'hello', err => { t.error(err, 'no error') expected['c'] = 'c' onmount(drive1, drive2, drive3) }) }) }) }) }) } function onmount (drive1, drive2, drive3) { drive1.readdir('/a/b', { recursive: false, includeStats: true }, (err, list) => { t.error(err, 'no error') let seen = 0 for (let { name, stat, mount, innerPath } of list) { t.true(expected[name] && expected[name] === innerPath, 'correct stat mode') seen++ } t.same(seen, 1) t.end() }) } }) test('nested mount readdir returns correct inner paths, recursive', async t => { const store = new Chainstore(ram) let expected = {} store.ready(onready) function onready () { const drive1 = create({ corestore: store, namespace: 'd1' }) const drive2 = create({ corestore: store, namespace: 'd2' }) const drive3 = create({ corestore: store, namespace: 'd3' }) drive3.ready(err => { t.error(err, 'no error') drive1.writeFile('b/c', 'hello', err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.mount('a/b', drive3.key, err => { t.error(err, 'no error') drive1.writeFile('a/b/c', 'hello', err => { t.error(err, 'no error') expected['a'] = 'a' expected['a/b'] = 'b' expected['b/c'] = 'b/c' expected['a/b/c'] = 'c' onmount(drive1, drive2, drive3) }) }) }) }) }) } function onmount (drive1, drive2, drive3) { drive1.readdir('/', { recursive: true, includeStats: true }, (err, list) => { t.error(err, 'no error') let seen = 0 for (let { name, stat, mount, innerPath } of list) { t.true(expected[name] && expected[name] === innerPath, 'correct stat mode') seen++ } t.same(seen, 4) t.end() }) } }) test('nested mount info returns correct mount keys and paths', async t => { const store = new Chainstore(ram) let expected = [] store.ready(onready) function onready () { const drive1 = create({ corestore: store, namespace: 'd1' }) const drive2 = create({ corestore: store, namespace: 'd2' }) const drive3 = create({ corestore: store, namespace: 'd3' }) drive3.ready(err => { t.error(err, 'no error') drive3.writeFile('c', 'hello', err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.mount('a/b', drive3.key, err => { t.error(err, 'no error') drive1.writeFile('a/b/c', 'hello', err => { t.error(err, 'no error') expected.push({ path: '/', key: drive1.key, mountPath: '/' }) expected.push({ path: 'a', key: drive2.key, mountPath: '/a' }) expected.push({ path: 'a/b', key: drive3.key, mountPath: '/a/b'}) expected.push({ path: 'a/b/c', key: drive3.key, mountPath: '/a/b' }) onmount(drive1, drive2, drive3) }) }) }) }) }) } function onmount (drive1, drive2, drive3) { validateNext() function validateNext () { if (!expected.length) return t.end() const { path, key, mountPath } = expected.pop() drive1.info(path, (err, info) => { t.error(err, 'no error') t.true(info.feed.key.equals(key)) t.same(info.mountPath, mountPath) return validateNext() }) } } }) test('independent corestores do not share write capabilities', t => { const r = new Replicator(t) const drive1 = create() const drive2 = create() r.replicate(drive1, drive2) drive2.ready(err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.writeFile('a/b', 'hello', err => { t.ok(err) drive1.readFile('a/b', (err, contents) => { t.ok(err) r.end() }) }) }) }) }) test('shared corestores will share write capabilities', async t => { const store = new Chainstore(ram) store.ready(onready) function onready () { const drive1 = create({ corestore: store, namespace: 'ns1' }) const drive2 = create({ corestore: store, namespace: 'ns2' }) drive2.ready(err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.writeFile('a/b', 'hello', err => { t.error(err, 'no error') drive1.readFile('a/b', (err, contents) => { t.error(err, 'no error') t.same(contents, Buffer.from('hello')) drive2.readFile('b', (err, contents) => { t.error(err, 'no error') t.same(contents, Buffer.from('hello')) t.end() }) }) }) }) }) } }) test('can mount unichains', async t => { const store = new Chainstore(ram) store.ready(onready) function onready () { const drive = create({ corestore: store }) var core = store.get() drive.ready(err => { t.error(err, 'no error') core.ready(err => { t.error(err, 'no error') core.append('hello', err => { t.error(err, 'no error') return onappend(drive, core) }) }) }) } function onappend (drive, core) { drive.mount('/a', core.key, { unichain: true }, err => { t.error(err, 'no error') drive.readFile('/a', (err, contents) => { t.error(err, 'no error') t.same(contents, Buffer.from('hello')) t.end() }) }) } }) test('truncate within mount (with shared write capabilities)', async t => { const store = new Chainstore(ram) store.ready(onready) function onready () { const drive1 = create({ corestore: store, namespace: 'ns1' }) const drive2 = create({ corestore: store, namespace: 'ns2' }) drive2.ready(err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.writeFile('a/b', 'hello', err => { t.error(err, 'no error') drive1.truncate('a/b', 1, err => { t.error(err, 'no error') drive1.readFile('a/b', (err, contents) => { t.error(err, 'no error') t.same(contents, Buffer.from('h')) drive2.readFile('b', (err, contents) => { t.error(err, 'no error') t.same(contents, Buffer.from('h')) t.end() }) }) }) }) }) }) } }) test('mount replication between bitdrives', async t => { const r = new Replicator(t) const store1 = new Chainstore(path => ram('cs1/' + path)) const store2 = new Chainstore(path => ram('cs2/' + path)) const store3 = new Chainstore(path => ram('cs3/' + path)) await new Promise(resolve => { store1.ready(() => { store2.ready(() => { store3.ready(resolve) }) }) }) const drive1 = create({ corestore: store1 }) const drive2 = create({ corestore: store2 }) var drive3 = null await new Promise(resolve => { drive1.ready(err => { t.error(err, 'no error') drive3 = create(drive1.key, { corestore: store3 }) drive2.ready(err => { t.error(err, 'no error') drive3.ready(err => { t.error(err, 'no error') r.replicate(drive1, drive2) r.replicate(drive2, drive3) r.replicate(drive1, drive3) onready() }) }) }) function onready () { drive1.writeFile('hey', 'hi', err => { t.error(err, 'no error') drive2.writeFile('hello', 'world', err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive3.ready(err => { t.error(err, 'no error') return setTimeout(onmount, 100) }) }) }) }) } function onmount () { drive3.readFile('hey', (err, contents) => { t.error(err, 'no error') t.same(contents, Buffer.from('hi')) drive3.readFile('a/hello', (err, contents) => { t.error(err, 'no error') t.same(contents, Buffer.from('world')) return resolve() }) }) } }) r.end() }) test('mount replication between bitdrives, multiple, nested mounts', async t => { const r = new Replicator(t) const [d1, d2] = await createMountee() const drive = await createMounter(d1, d2) await verify(drive) r.end() function createMountee () { const store = new Chainstore(path => ram('cs1/' + path)) const drive1 = create({ corestore: store, namespace: 'ns1' }) var drive2, drive3 return new Promise(resolve => { drive1.ready(err => { t.error(err, 'no error') drive2 = create({ corestore: store, namespace: 'ns2' }) drive3 = create({ corestore: store, namespace: 'ns3' }) drive2.ready(err => { t.error(err, 'no error') drive3.ready(err => { t.error(err, 'no error') return onready() }) }) }) function onready () { drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.mount('b', drive3.key, err => { t.error(err, 'no error') return onmount() }) }) } function onmount () { drive1.writeFile('a/dog', 'hello', err => { t.error(err, 'no error') drive1.writeFile('b/cat', 'goodbye', err => { t.error(err, 'no error') return resolve([drive2, drive3]) }) }) } }) } function createMounter (d2, d3) { const store = new Chainstore(path => ram('cs4/' + path)) return new Promise(resolve => { store.ready(() => { const drive1 = create({ corestore: store }) drive1.ready(err => { t.error(err, 'no error') r.replicate(drive1, d2) r.replicate(d2, d3) r.replicate(drive1, d3) drive1.mount('a', d2.key, err => { t.error(err, 'no error') drive1.mount('b', d3.key, err => { t.error(err, 'no error') setTimeout(() => resolve(drive1), 1000) }) }) }) }) }) } function verify (drive) { return new Promise(resolve => { drive.readFile('a/dog', (err, contents) => { t.error(err, 'no error') t.same(contents, Buffer.from('hello')) drive.readFile('b/cat', (err, contents) => { t.error(err, 'no error') t.same(contents, Buffer.from('goodbye')) return resolve() }) }) }) } }) test('can list in-memory mounts', async t => { const r = new Replicator(t) const drive1 = create() const drive2 = create() const drive3 = create() var key1, key2 r.replicate(drive1, drive2) r.replicate(drive2, drive3) r.replicate(drive1, drive3) drive3.ready(err => { t.error(err, 'no error') drive2.ready(err => { t.error(err, 'no error') key1 = drive2.key key2 = drive3.key onready() }) }) function onready () { drive2.writeFile('a', 'hello', err => { t.error(err, 'no error') drive3.writeFile('b', 'world', err => { t.error(err, 'no error') onwrite() }) }) } function onwrite () { drive1.mount('a', key1, err => { t.error(err, 'no error') drive1.mount('b', key2, err => { t.error(err, 'no error') onmount() }) }) } function onmount () { drive1.readFile('a/a', (err, contents) => { t.error(err, 'no error') t.true(contents) drive1.getAllMounts({ memory: true }, (err, mounts) => { t.error(err, 'no error') t.same(mounts.size, 3) t.true(mounts.get('/')) t.true(mounts.get('/a')) r.end() }) }) } }) test('getAllMounts with no mounts returns only the root mount', async t => { const drive1 = create() drive1.ready(err => { t.error(err, 'no error') drive1.getAllMounts({ memory: true}, (err, mounts) => { t.error(err, 'no error') t.true(mounts) t.same(mounts.size, 1) t.end() }) }) }) test('can list all mounts (including those not in memory)', async t => { const r = new Replicator(t) const drive1 = create() const drive2 = create() const drive3 = create() var key1, key2 r.replicate(drive1, drive2) r.replicate(drive2, drive3) r.replicate(drive1, drive3) drive3.ready(err => { t.error(err, 'no error') drive2.ready(err => { t.error(err, 'no error') key1 = drive2.key key2 = drive3.key onready() }) }) function onready () { drive2.writeFile('a', 'hello', err => { t.error(err, 'no error') drive3.writeFile('b', 'world', err => { t.error(err, 'no error') onwrite() }) }) } function onwrite () { drive1.mount('a', key1, err => { t.error(err, 'no error') drive1.mount('b', key2, err => { t.error(err, 'no error') onmount() }) }) } function onmount () { drive1.getAllMounts((err, mounts) => { t.error(err, 'no error') t.same(mounts.size, 3) t.true(mounts.get('/')) t.true(mounts.get('/a')) t.true(mounts.get('/b')) r.end() }) } }) test('can watch multiple mounts', async t => { const r = new Replicator(t) const drive1 = create() const drive2 = create() const drive3 = create() var key1, key2 r.replicate(drive1, drive2) r.replicate(drive2, drive3) r.replicate(drive1, drive3) drive3.ready(err => { t.error(err, 'no error') drive2.ready(err => { t.error(err, 'no error') key1 = drive2.key key2 = drive3.key onready() }) }) function onready () { drive1.mount('a', key1, err => { t.error(err, 'no error') drive1.mount('b', key2, err => { t.error(err, 'no error') onmount() }) }) } function onmount () { var changes = 0 const watcher = drive1.watch('', () => { changes++ }) watcher.on('ready', watchers => { t.same(watchers.length, 2) drive2.writeFile('a', 'hello', err => { t.error(err, 'no error') drive3.writeFile('b', 'world', err => { t.error(err, 'no error') setImmediate(() => { t.same(changes, 3) r.end() }) }) }) }) } }) test('can watch nested mounts', async t => { const r = new Replicator(t) const drive1 = create() const drive2 = create() const drive3 = create() var key1, key2 r.replicate(drive1, drive2) r.replicate(drive2, drive3) r.replicate(drive1, drive3) drive3.ready(err => { t.error(err, 'no error') drive2.ready(err => { t.error(err, 'no error') key1 = drive2.key key2 = drive3.key onready() }) }) function onready () { drive1.mount('a', key1, err => { t.error(err, 'no error') drive2.mount('b', key2, err => { t.error(err, 'no error') onmount() }) }) } function onmount () { var changes = 0 const watcher = drive1.watch('', () => { changes++ }) watcher.on('ready', watchers => { t.same(watchers.length, 2) drive2.writeFile('a', 'hello', err => { t.error(err, 'no error') drive3.writeFile('b', 'world', err => { t.error(err, 'no error') setImmediate(() => { t.same(changes, 3) r.end() }) }) }) }) } }) test('can watch cyclic mounts', async t => { const r = new Replicator(t) const drive1 = create() const drive2 = create() var key1, key2 r.replicate(drive1, drive2) drive1.ready(err => { t.error(err, 'no error') drive2.ready(err => { t.error(err, 'no error') key1 = drive1.key key2 = drive2.key onready() }) }) function onready () { drive1.mount('a', key2, err => { t.error(err, 'no error') drive2.mount('b', key1, err => { t.error(err, 'no error') onmount() }) }) } function onmount () { var changes = 0 const watcher = drive1.watch('', () => { changes++ }) watcher.on('ready', watchers => { t.same(watchers.length, 2) drive2.writeFile('c', 'hello', err => { t.error(err, 'no error') drive1.writeFile('c', 'world', err => { t.error(err, 'no error') setImmediate(() => { t.same(changes, 4) r.end() }) }) }) }) } }) test('readdir with noMounts will not traverse mounts', async t => { const r = new Replicator(t) const drive1 = create() const drive2 = create() r.replicate(drive1, drive2) drive2.ready(err => { t.error(err, 'no error') drive1.mkdir('b', err => { t.error(err, 'no error') drive1.mkdir('b/a', err => { t.error(err, 'no error') drive2.mkdir('c', err => { t.error(err, 'no error') drive1.mount('a', drive2.key, err => { t.error(err, 'no error') drive1.readdir('/', { recursive: true, noMounts: true}, (err, dirs) => { t.error(err, 'no error') t.same(dirs, ['b', 'b/a', 'a']) r.end() }) }) }) }) }) }) }) test('update does not clear the mount', function (t) { const drive = bitdrive(ram) const other = bitdrive(drive.corestore, null, { namespace: 'test' }) other.writeFile('/foo', 'bar', function (err) { t.error(err, 'no error') drive.mount('/bar', other.key, function (err) { t.error(err, 'no error') drive._update('/bar', {}, function (err) { t.error(err, 'no error') drive.readdir('/bar', function (err, files) { t.error(err, 'no error') t.same(files, ['foo']) t.end() }) }) }) }) }) test('can list in-memory mounts recursively') test('dynamically resolves cross-mount symlinks') test('symlinks cannot break the sandbox') test('versioned mount') test('watch will unwatch on umount')