UNPKG

ipfs-interop

Version:

Interoperability Tests for IPFS

267 lines (229 loc) 8.95 kB
/* eslint max-nested-callbacks: ["error", 6] */ /* eslint-env mocha */ import pRetry from 'p-retry' import { expect } from 'aegir/chai' import { daemonFactory } from './utils/daemon-factory.js' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' import { isPeerId } from '@libp2p/interface-peer-id' import pTimeout from 'p-timeout' import delay from 'delay' /** * @typedef {import('ipfsd-ctl').Controller} Controller * @typedef {import('ipfsd-ctl').Factory} Factory * @typedef {import('ipfsd-ctl').ControllerOptions} ControllerOptions */ const retryOptions = { retries: 5 } /** * @param {string} topic * @param {Controller["peer"]} peer * @param {Controller} daemon */ const waitForTopicPeer = (topic, peer, daemon) => { return pRetry(async () => { const peers = await daemon.api.pubsub.peers(topic) if (!peers.map(p => p.toString()).includes(peer.id.toString())) { throw new Error(`Could not find peer ${peer.id}`) } }, retryOptions) } /** * @type {ControllerOptions} */ const daemonOptions = { test: true, args: ['--enable-pubsub-experiment'] } const timeout = 20e3 describe('pubsub', function () { this.timeout(60e3) /** @type {Record<string, ((fac: Factory) => Promise<Controller>)[]>} */ const tests = { 'publish from Go, subscribe on Go': [(factory) => factory.spawn({ ...daemonOptions, type: 'go' }), (factory) => factory.spawn({ ...daemonOptions, type: 'go' })], 'publish from JS, subscribe on JS': [(factory) => factory.spawn({ type: 'js' }), (factory) => factory.spawn({ type: 'js' })], 'publish from JS, subscribe on Go': [(factory) => factory.spawn({ type: 'js' }), (factory) => factory.spawn({ ...daemonOptions, type: 'go' })], 'publish from Go, subscribe on JS': [(factory) => factory.spawn({ ...daemonOptions, type: 'go' }), (factory) => factory.spawn({ type: 'js' })] } Object.keys(tests).forEach((name) => { describe(name, function () { /** @type {Controller} */ let daemon1 /** @type {Controller} */ let daemon2 /** @type {Factory} */ let factory before(async () => { factory = await daemonFactory() }) after(() => factory.clean()) before('spawn nodes', async function () { [daemon1, daemon2] = await Promise.all(tests[name].map(fn => fn(factory))) }) before('connect', async function () { await daemon1.api.swarm.connect(daemon2.peer.addresses[0]) await daemon2.api.swarm.connect(daemon1.peer.addresses[0]) const peers = await Promise.all([ daemon1.api.swarm.peers(), daemon2.api.swarm.peers() ]) expect(peers[0].map((p) => p.peer.toString())).to.include(daemon2.peer.id.toString()) expect(peers[1].map((p) => p.peer.toString())).to.include(daemon1.peer.id.toString()) }) after(() => factory.clean()) it('should exchange ascii data', function () { const data = uint8ArrayFromString('hello world') const topic = 'pubsub-ascii' /** @type {() => Promise<void>} */ const subscriber = () => new Promise((resolve, reject) => { daemon2.api.pubsub.subscribe(topic, (msg) => { try { if (msg.type !== 'signed') { throw new Error('Message was unsigned') } expect(uint8ArrayEquals(data, msg.data)).to.be.true() expect(msg).to.have.property('sequenceNumber') expect(msg.sequenceNumber).to.be.a('bigint') expect(msg).to.have.property('topic', topic) expect(isPeerId(msg.from)).to.be.true() expect(msg.from.toString()).to.equal(daemon1.peer.id.toString()) resolve() } catch (err) { reject(err) } }) }) const publisher = async () => { await waitForTopicPeer(topic, daemon2.peer, daemon1) await delay(1000) await daemon1.api.pubsub.publish(topic, data) } return Promise.all([ pTimeout(subscriber(), { milliseconds: timeout, message: 'subscriber timed out' }), pTimeout(publisher(), { milliseconds: timeout, message: 'publisher timed out' }) ]) }) it('should exchange non ascii data', function () { const data = uint8ArrayFromString('你好世界 zażółć gęślą jaźń') const topic = 'pubsub-non-ascii' /** @type {() => Promise<void>} */ const subscriber = () => new Promise((resolve, reject) => { daemon2.api.pubsub.subscribe(topic, (msg) => { try { if (msg.type !== 'signed') { throw new Error('Message was unsigned') } expect(uint8ArrayEquals(data, msg.data)).to.be.true() expect(msg).to.have.property('sequenceNumber') expect(msg.sequenceNumber).to.be.a('bigint') expect(msg).to.have.property('topic', topic) expect(isPeerId(msg.from)).to.be.true() expect(msg.from.toString()).to.equal(daemon1.peer.id.toString()) resolve() } catch (err) { reject(err) } }) }) const publisher = async () => { await waitForTopicPeer(topic, daemon2.peer, daemon1) await delay(1000) await daemon1.api.pubsub.publish(topic, data) } return Promise.all([ pTimeout(subscriber(), { milliseconds: timeout, message: 'subscriber timed out' }), pTimeout(publisher(), { milliseconds: timeout, message: 'publisher timed out' }) ]) }) it('should exchange binary data', function () { const data = uint8ArrayFromString('a36161636179656162830103056164a16466666666f400010203040506070809', 'base16') const topic = 'pubsub-binary' /** @type {() => Promise<void>} */ const subscriber = () => new Promise((resolve, reject) => { daemon2.api.pubsub.subscribe(topic, (msg) => { try { if (msg.type !== 'signed') { throw new Error('Message was unsigned') } expect(uint8ArrayEquals(data, msg.data)).to.be.true() expect(msg).to.have.property('sequenceNumber') expect(msg.sequenceNumber).to.be.a('bigint') expect(msg).to.have.property('topic', topic) expect(isPeerId(msg.from)).to.be.true() expect(msg.from.toString()).to.equal(daemon1.peer.id.toString()) resolve() } catch (err) { reject(err) } }) }) const publisher = async () => { await waitForTopicPeer(topic, daemon2.peer, daemon1) await delay(1000) await daemon1.api.pubsub.publish(topic, data) } return Promise.all([ pTimeout(subscriber(), { milliseconds: timeout, message: 'subscriber timed out' }), pTimeout(publisher(), { milliseconds: timeout, message: 'publisher timed out' }) ]) }) it('should exchange data over a topic with unicode and newlines', function () { const data = uint8ArrayFromString('你好世界\nzażółć\r\ngęślą\njaźń') const topic = 'pubsub\n你好世界\r\njaźń' /** @type {() => Promise<void>} */ const subscriber = () => new Promise((resolve, reject) => { daemon2.api.pubsub.subscribe(topic, (msg) => { try { if (msg.type !== 'signed') { throw new Error('Message was unsigned') } expect(uint8ArrayEquals(data, msg.data)).to.be.true() expect(msg).to.have.property('sequenceNumber') expect(msg.sequenceNumber).to.be.a('bigint') expect(msg).to.have.property('topic', topic) expect(isPeerId(msg.from)).to.be.true() expect(msg.from.toString()).to.equal(daemon1.peer.id.toString()) resolve() } catch (err) { reject(err) } }) }) const publisher = async () => { await waitForTopicPeer(topic, daemon2.peer, daemon1) await delay(1000) await daemon1.api.pubsub.publish(topic, data) } return Promise.all([ pTimeout(subscriber(), { milliseconds: timeout, message: 'subscriber timed out' }), pTimeout(publisher(), { milliseconds: timeout, message: 'publisher timed out' }) ]) }) }) }) })