UNPKG

interface-ipfs-core

Version:

A test suite and interface you can use to implement a IPFS core interface.

200 lines (153 loc) 6.09 kB
/* eslint-env mocha */ import { expect } from 'aegir/chai' import { getDescribe, getIt } from '../utils/mocha.js' import { peerIdFromString, peerIdFromKeys } from '@libp2p/peer-id' import { isNode } from 'ipfs-utils/src/env.js' import * as ipns from 'ipns' import delay from 'delay' import last from 'it-last' import waitFor from '../utils/wait-for.js' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { ipnsValidator } from 'ipns/validator' const namespace = '/record/' const ipfsRef = '/ipfs/QmPFVLPmp9zv5Z5KUqLhe2EivAGccQW2r7M7jhVJGLZoZU' const daemonsOptions = { ipfsOptions: { EXPERIMENTAL: { ipnsPubsub: true } } } /** * @typedef {import('ipfsd-ctl').Factory} Factory * @typedef {import('@libp2p/interface-pubsub').Message} Message * @typedef {import('@libp2p/interfaces/events').EventHandler<Message>} EventHandler */ /** * @param {Factory} factory * @param {object} options */ export function testPubsub (factory, options) { const describe = getDescribe(options) const it = getIt(options) describe('.name.pubsub', () => { // TODO make this work in the browser and between daemon and in-proc in nodes if (!isNode) return let nodes /** @type {import('ipfs-core-types').IPFS} */ let nodeA /** @type {import('ipfs-core-types').IPFS} */ let nodeB /** @type {import('ipfs-core-types/src/root').IDResult} */ let idA /** @type {import('ipfs-core-types/src/root').IDResult} */ let idB before(async function () { this.timeout(120 * 1000) nodes = await Promise.all([ factory.spawn({ ...daemonsOptions }), factory.spawn({ ...daemonsOptions }) ]) nodeA = nodes[0].api nodeB = nodes[1].api const ids = await Promise.all([ nodeA.id(), nodeB.id() ]) idA = ids[0] idB = ids[1] await nodeA.swarm.connect(idB.addresses[0]) await waitFor(async () => { const res = await nodeB.swarm.peers() return res.map(p => p.peer.toString()).includes(idA.id.toString()) }, { name: 'node A dialed node B' }) }) after(() => factory.clean()) it('should publish and then resolve correctly', async function () { this.timeout(80 * 1000) const routingKey = ipns.peerIdToRoutingKey(idA.id) const topic = `${namespace}${uint8ArrayToString(routingKey, 'base64url')}` await nodeB.pubsub.subscribe(topic, () => {}) // wait for nodeA to see nodeB's subscription await waitFor(async () => { const peers = await nodeA.pubsub.peers(topic) return peers.map(p => p.toString()).includes(idB.id.toString()) }) await nodeA.name.publish(ipfsRef, { resolve: false }) await delay(1000) // guarantee record is written const res = await last(nodeB.name.resolve(idA.id)) expect(res).to.equal(ipfsRef) }) it('should self resolve, publish and then resolve correctly', async function () { this.timeout(6000) const emptyDirCid = '/ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn' const { path } = await nodeA.add(uint8ArrayFromString('pubsub records')) const resolvesEmpty = await last(nodeB.name.resolve(idB.id)) expect(resolvesEmpty).to.be.eq(emptyDirCid) const publish = await nodeB.name.publish(path) expect(publish).to.be.eql({ name: idB.id.toString(), value: `/ipfs/${path}` }) const resolveB = await last(nodeB.name.resolve(idB.id)) expect(resolveB).to.be.eq(`/ipfs/${path}`) await delay(1000) const resolveA = await last(nodeA.name.resolve(idB.id)) expect(resolveA).to.be.eq(`/ipfs/${path}`) }) it('should handle event on publish correctly', async function () { this.timeout(80 * 1000) const testAccountName = 'test-account' /** * @type {import('@libp2p/interface-pubsub').Message} */ let publishedMessage /** * @type {EventHandler} */ const checkMessage = (msg) => { publishedMessage = msg } const alreadySubscribed = () => { return Boolean(publishedMessage) } // Create account for publish const testAccount = await nodeA.key.gen(testAccountName, { type: 'rsa', size: 2048, 'ipns-base': 'b58mh' }) const routingKey = ipns.peerIdToRoutingKey(peerIdFromString(testAccount.id)) const topic = `${namespace}${uint8ArrayToString(routingKey, 'base64url')}` await nodeB.pubsub.subscribe(topic, checkMessage) // wait for nodeA to see nodeB's subscription await waitFor(async () => { const peers = await nodeA.pubsub.peers(topic) return peers.map(p => p.toString()).includes(idB.id.toString()) }) await nodeA.name.publish(ipfsRef, { resolve: false, key: testAccountName }) await waitFor(alreadySubscribed) // @ts-expect-error publishedMessage is set in handler if (!publishedMessage) { throw new Error('Pubsub handler not invoked') } const publishedMessageData = ipns.unmarshal(publishedMessage.data) if (publishedMessage.type !== 'signed') { throw new Error('Message was not signed') } if (publishedMessageData.pubKey == null) { throw new Error('Public key was missing from published message data') } const messageKey = publishedMessage.from const pubKeyPeerId = await peerIdFromKeys(publishedMessageData.pubKey) expect(pubKeyPeerId.toString()).not.to.equal(messageKey.toString()) expect(pubKeyPeerId.toString()).to.equal(testAccount.id) expect(publishedMessage.from.toString()).to.equal(idA.id.toString()) expect(messageKey.toString()).to.equal(idA.id.toString()) expect(uint8ArrayToString(publishedMessageData.value)).to.equal(ipfsRef) // Verify the signature await ipnsValidator(ipns.peerIdToRoutingKey(pubKeyPeerId), publishedMessage.data) }) }) }