Skip to content

Commit

Permalink
Merge pull request #2 from orbitdb/tests
Browse files Browse the repository at this point in the history
Add more tests
  • Loading branch information
haydenyoung authored Jan 19, 2024
2 parents f026277 + 69a8149 commit 4df4ba2
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 11 deletions.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
"build:tests": "rm -f test/browser/bundle.js* && webpack --config ./conf/webpack.tests.config.js",
"prepublishOnly": "npm run build",
"lint": "standard --env=mocha",
"lint:fix": "standard --fix"
"lint:fix": "standard --fix",
"webrtc": "node ./test/utils/relay.js",
"webrtc:background": "node ./test/utils/relay.js &"
},
"repository": {
"type": "git",
Expand All @@ -39,7 +41,7 @@
"ignore": [
"test/browser/**"
]
},
},
"devDependencies": {
"cross-env": "^7.0.3",
"mocha": "^10.2.0",
Expand Down
15 changes: 9 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,25 @@ const startOrbitDB = async ({ id, identity, identities, directory } = {}) => {
directory = directory || '.'
const blockstore = new LevelBlockstore(`${directory}/ipfs/blocks`)
const ipfs = await createHelia({ libp2p, blockstore, blockBrokers: [bitswap()] })
return createOrbitDB({ ipfs, id, identity, identities, directory })
const orbitdb = await createOrbitDB({ ipfs, id, identity, identities, directory })
return orbitdb
}

/**
* Stops the OrbitDB peer and associated services.
* @function stopOrbitDB
* @param {Object} instance The instance of OrbitDB to stop.
* @param {Object} orbitdb The OrbitDB instance to stop.
*/
const stopOrbitDB = async (instance) => {
await instance.stop()
await instance.ipfs.stop()
const stopOrbitDB = async (orbitdb) => {
await orbitdb.stop()
await orbitdb.ipfs.stop()
await orbitdb.ipfs.blockstore.unwrap().unwrap().close()
}

export {
startOrbitDB,
stopOrbitDB,
DefaultLibp2pOptions,
DefaultLibp2pBrowserOptions
DefaultLibp2pBrowserOptions,
isBrowser
}
2 changes: 1 addition & 1 deletion test/.mocharc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"exit": true,
"bail": false,
"slow": 1000,
"exclude": ["test/browser/**/*.js"],
"exclude": ["test/browser/**/*.js", "test/utils/**/*.js"],
"timeout": 30000
}
117 changes: 115 additions & 2 deletions test/quickstart.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { startOrbitDB, stopOrbitDB } from '../src/index.js'
import { deepStrictEqual } from 'assert'
import { startOrbitDB, stopOrbitDB, isBrowser } from '../src/index.js'
import { strictEqual, notStrictEqual, deepStrictEqual } from 'assert'
import { rimraf } from 'rimraf'
import connectIpfsNodes from './utils/connect-nodes.js'
import waitFor from './utils/wait-for.js'

describe('Starting OrbitDB', function () {
it('starts OrbitDB with a preconfigured Helia instance', async () => {
Expand All @@ -15,3 +17,114 @@ describe('Starting OrbitDB', function () {
await rimraf('./ipfs')
})
})

describe('Multiple nodes', function () {
it('starts and connects two OrbitDB instances', async () => {
const orbitdb1 = await startOrbitDB({ directory: './orbitdb1' })
const orbitdb2 = await startOrbitDB({ directory: './orbitdb2' })
await connectIpfsNodes(orbitdb1.ipfs, orbitdb2.ipfs)
await stopOrbitDB(orbitdb1)
await stopOrbitDB(orbitdb2)
await rimraf('./orbitdb1')
await rimraf('./orbitdb2')
})

it('throws an error if trying to start two OrbitDB instances with same directory', async () => {
// Skip this test in browser tests
if (isBrowser()) {
return
}

let err

const orbitdb1 = await startOrbitDB({ directory: './orbitdb' })

try {
await startOrbitDB({ directory: './orbitdb' })
} catch (e) {
err = e
}

notStrictEqual(err, undefined)
strictEqual(err.message, 'Database is not open')

await stopOrbitDB(orbitdb1)
await rimraf('./orbitdb')
})

it('replicates a database between two OrbitDB instances', async () => {
let replicated = false

const orbitdb1 = await startOrbitDB({ directory: './orbitdb1' })
const orbitdb2 = await startOrbitDB({ directory: './orbitdb2' })

await connectIpfsNodes(orbitdb1.ipfs, orbitdb2.ipfs)

const db1 = await orbitdb1.open('db2')

const onJoin = () => (replicated = true)
const db2 = await orbitdb2.open(db1.address)
db2.events.on('join', onJoin)

await db1.add('A')
await db1.add('B')
await db1.add('C')

await waitFor(() => replicated, () => true)

deepStrictEqual((await db1.all()).map(e => e.value), ['A', 'B', 'C'])
deepStrictEqual((await db2.all()).map(e => e.value), ['A', 'B', 'C'])

await stopOrbitDB(orbitdb1)
await stopOrbitDB(orbitdb2)
await rimraf('./orbitdb1')
await rimraf('./orbitdb2')
})

it.only('replicates a database between four OrbitDB instances', async () => {
let replicated1 = false
let replicated2 = false

const orbitdb1 = await startOrbitDB({ directory: './orbitdb1' })
const orbitdb2 = await startOrbitDB({ directory: './orbitdb2' })
const orbitdb3 = await startOrbitDB({ directory: './orbitdb3' })
const orbitdb4 = await startOrbitDB({ directory: './orbitdb4' })

await connectIpfsNodes(orbitdb1.ipfs, orbitdb2.ipfs)
await connectIpfsNodes(orbitdb2.ipfs, orbitdb3.ipfs)
await connectIpfsNodes(orbitdb3.ipfs, orbitdb4.ipfs)

const db1 = await orbitdb1.open('db2')
const db2 = await orbitdb2.open(db1.address)

await db1.add('A')
await db1.add('B')
await db1.add('C')

const db3 = await orbitdb3.open(db1.address)
const onJoin1 = () => (replicated1 = true)
db3.events.on('join', onJoin1)

await waitFor(() => replicated1, () => true)

const db4 = await orbitdb4.open(db1.address)
const onJoin2 = () => (replicated2 = true)
db4.events.on('join', onJoin2)

await waitFor(() => replicated2, () => true)

deepStrictEqual((await db1.all()).map(e => e.value), ['A', 'B', 'C'])
deepStrictEqual((await db2.all()).map(e => e.value), ['A', 'B', 'C'])
deepStrictEqual((await db3.all()).map(e => e.value), ['A', 'B', 'C'])
deepStrictEqual((await db4.all()).map(e => e.value), ['A', 'B', 'C'])

await stopOrbitDB(orbitdb1)
await stopOrbitDB(orbitdb2)
await stopOrbitDB(orbitdb3)
await stopOrbitDB(orbitdb4)
await rimraf('./orbitdb1')
await rimraf('./orbitdb2')
await rimraf('./orbitdb3')
await rimraf('./orbitdb4')
})
})
31 changes: 31 additions & 0 deletions test/utils/connect-nodes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { multiaddr } from '@multiformats/multiaddr'
import { WebRTC } from '@multiformats/multiaddr-matcher'
import waitFor from './wait-for.js'

const defaultFilter = () => true

const isBrowser = () => typeof window !== 'undefined'

const connectIpfsNodes = async (ipfs1, ipfs2, options = {
filter: defaultFilter
}) => {
if (isBrowser()) {
const relayId = '12D3KooWAJjbRkp8FPF5MKgMU53aUTxWkqvDrs4zc1VMbwRwfsbE'

await ipfs1.libp2p.dial(multiaddr(`/ip4/127.0.0.1/tcp/12345/ws/p2p/${relayId}`))

let address1

await waitFor(() => {
address1 = ipfs1.libp2p.getMultiaddrs().filter(ma => WebRTC.matches(ma)).pop()
return address1 != null
}, () => true)

await ipfs2.libp2p.dial(address1)
} else {
await ipfs2.libp2p.peerStore.save(ipfs1.libp2p.peerId, { multiaddrs: ipfs1.libp2p.getMultiaddrs().filter(options.filter) })
await ipfs2.libp2p.dial(ipfs1.libp2p.peerId)
}
}

export default connectIpfsNodes
56 changes: 56 additions & 0 deletions test/utils/relay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { yamux } from '@chainsafe/libp2p-yamux'
import { createLibp2p } from 'libp2p'
import { noise } from '@chainsafe/libp2p-noise'
import { circuitRelayServer } from '@libp2p/circuit-relay-v2'
import { webSockets } from '@libp2p/websockets'
import * as filters from '@libp2p/websockets/filters'
import { identify } from '@libp2p/identify'
import { createFromPrivKey } from '@libp2p/peer-id-factory'
import { unmarshalPrivateKey } from '@libp2p/crypto/keys'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'

// output of: console.log(server.peerId.privateKey.toString('hex'))
const relayPrivKey = '08011240821cb6bc3d4547fcccb513e82e4d718089f8a166b23ffcd4a436754b6b0774cf07447d1693cd10ce11ef950d7517bad6e9472b41a927cd17fc3fb23f8c70cd99'
// the peer id of the above key
// const relayId = '12D3KooWAJjbRkp8FPF5MKgMU53aUTxWkqvDrs4zc1VMbwRwfsbE'

const encoded = uint8ArrayFromString(relayPrivKey, 'hex')
const privateKey = await unmarshalPrivateKey(encoded)
const peerId = await createFromPrivKey(privateKey)

const server = await createLibp2p({
peerId,
addresses: {
listen: ['/ip4/0.0.0.0/tcp/12345/ws']
},
transports: [
webSockets({
filter: filters.all
})
],
connectionEncryption: [noise()],
streamMuxers: [yamux()],
services: {
identify: identify(),
relay: circuitRelayServer({
reservations: {
maxReservations: 5000,
reservationTtl: 1000,
defaultDataLimit: BigInt(1024 * 1024 * 1024)
}
})
}
})

server.addEventListener('peer:connect', async event => {
console.log('peer:connect', event.detail)
})

server.addEventListener('peer:disconnect', async event => {
console.log('peer:disconnect', event.detail)
server.peerStore.delete(event.detail)
})

console.log(server.peerId.toString())
console.log('p2p addr: ', server.getMultiaddrs().map((ma) => ma.toString()))
// generates a deterministic address: /ip4/127.0.0.1/tcp/33519/ws/p2p/12D3KooWAJjbRkp8FPF5MKgMU53aUTxWkqvDrs4zc1VMbwRwfsbE
12 changes: 12 additions & 0 deletions test/utils/wait-for.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const waitFor = async (valueA, toBeValueB, pollInterval = 100) => {
return new Promise((resolve) => {
const interval = setInterval(async () => {
if (await valueA() === await toBeValueB()) {
clearInterval(interval)
resolve()
}
}, pollInterval)
})
}

export default waitFor

0 comments on commit 4df4ba2

Please sign in to comment.