libp2p-pubsub
Version:
310 lines • 12.8 kB
JavaScript
/* eslint max-nested-callbacks: ["error", 6] */
import { expect } from 'aegir/utils/chai.js';
import sinon from 'sinon';
import pWaitFor from 'p-wait-for';
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string';
import { PeerStreams } from '../src/peer-streams.js';
import { createPeerId, createMockRegistrar, ConnectionPair, mockRegistrar, PubsubImplementation } from './utils/index.js';
const protocol = '/pubsub/1.0.0';
const topic = 'test-topic';
const message = uint8ArrayFromString('hello');
describe('pubsub base implementation', () => {
describe('publish', () => {
let pubsub;
beforeEach(async () => {
const peerId = await createPeerId();
pubsub = new PubsubImplementation({
libp2p: {
peerId: peerId,
registrar: mockRegistrar
},
multicodecs: [protocol]
});
});
afterEach(() => pubsub.stop());
it('calls _publish for router to forward messages', async () => {
sinon.spy(pubsub, '_publish');
await pubsub.start();
await pubsub.publish(topic, message);
// @ts-expect-error .callCount is a added by sinon
expect(pubsub._publish.callCount).to.eql(1);
});
it('should sign messages on publish', async () => {
sinon.spy(pubsub, '_publish');
await pubsub.start();
await pubsub.publish(topic, message);
// Get the first message sent to _publish, and validate it
// @ts-expect-error .getCall is a added by sinon
const signedMessage = pubsub._publish.getCall(0).lastArg;
await expect(pubsub.validate(signedMessage)).to.eventually.be.undefined();
});
});
describe('subscribe', () => {
describe('basics', () => {
let pubsub;
beforeEach(async () => {
const peerId = await createPeerId();
pubsub = new PubsubImplementation({
multicodecs: [protocol],
libp2p: {
peerId: peerId,
registrar: mockRegistrar
}
});
await pubsub.start();
});
afterEach(() => pubsub.stop());
it('should add subscription', () => {
pubsub.subscribe(topic);
expect(pubsub.subscriptions.size).to.eql(1);
expect(pubsub.subscriptions.has(topic)).to.be.true();
});
});
describe('two nodes', () => {
let pubsubA, pubsubB;
let peerIdA, peerIdB;
const registrarRecordA = new Map();
const registrarRecordB = new Map();
beforeEach(async () => {
peerIdA = await createPeerId();
peerIdB = await createPeerId();
pubsubA = new PubsubImplementation({
multicodecs: [protocol],
libp2p: {
peerId: peerIdA,
registrar: createMockRegistrar(registrarRecordA)
}
});
pubsubB = new PubsubImplementation({
multicodecs: [protocol],
libp2p: {
peerId: peerIdB,
registrar: createMockRegistrar(registrarRecordB)
}
});
});
// start pubsub and connect nodes
beforeEach(async () => {
pubsubA.start();
pubsubB.start();
const onConnectA = registrarRecordA.get(protocol).onConnect;
const handlerB = registrarRecordB.get(protocol).handler;
// Notice peers of connection
const [c0, c1] = ConnectionPair();
await onConnectA(peerIdB, c0);
await handlerB({
protocol,
stream: c1.stream,
connection: {
remotePeer: peerIdA
}
});
});
afterEach(() => {
pubsubA.stop();
pubsubB.stop();
});
it('should send subscribe message to connected peers', async () => {
sinon.spy(pubsubA, '_sendSubscriptions');
sinon.spy(pubsubB, '_processRpcSubOpt');
pubsubA.subscribe(topic);
// Should send subscriptions to a peer
// @ts-expect-error .callCount is a added by sinon
expect(pubsubA._sendSubscriptions.callCount).to.eql(1);
// Other peer should receive subscription message
await pWaitFor(() => {
const subscribers = pubsubB.getSubscribers(topic);
return subscribers.length === 1;
});
// @ts-expect-error .callCount is a added by sinon
expect(pubsubB._processRpcSubOpt.callCount).to.eql(1);
});
});
});
describe('unsubscribe', () => {
describe('basics', () => {
let pubsub;
beforeEach(async () => {
const peerId = await createPeerId();
pubsub = new PubsubImplementation({
multicodecs: [protocol],
libp2p: {
peerId: peerId,
registrar: mockRegistrar
}
});
await pubsub.start();
});
afterEach(() => pubsub.stop());
it('should remove all subscriptions for a topic', () => {
pubsub.subscribe(topic);
pubsub.subscribe(topic);
expect(pubsub.subscriptions.size).to.eql(1);
pubsub.unsubscribe(topic);
expect(pubsub.subscriptions.size).to.eql(0);
});
});
describe('two nodes', () => {
let pubsubA, pubsubB;
let peerIdA, peerIdB;
const registrarRecordA = new Map();
const registrarRecordB = new Map();
beforeEach(async () => {
peerIdA = await createPeerId();
peerIdB = await createPeerId();
pubsubA = new PubsubImplementation({
multicodecs: [protocol],
libp2p: {
peerId: peerIdA,
registrar: createMockRegistrar(registrarRecordA)
}
});
pubsubB = new PubsubImplementation({
multicodecs: [protocol],
libp2p: {
peerId: peerIdB,
registrar: createMockRegistrar(registrarRecordB)
}
});
});
// start pubsub and connect nodes
beforeEach(async () => {
pubsubA.start();
pubsubB.start();
const onConnectA = registrarRecordA.get(protocol).onConnect;
const handlerB = registrarRecordB.get(protocol).handler;
// Notice peers of connection
const [c0, c1] = ConnectionPair();
await onConnectA(peerIdB, c0);
await handlerB({
protocol,
stream: c1.stream,
connection: {
remotePeer: peerIdA
}
});
});
afterEach(() => {
pubsubA.stop();
pubsubB.stop();
});
it('should send unsubscribe message to connected peers', async () => {
sinon.spy(pubsubA, '_sendSubscriptions');
sinon.spy(pubsubB, '_processRpcSubOpt');
pubsubA.subscribe(topic);
// Should send subscriptions to a peer
// @ts-expect-error .callCount is a property added by sinon
expect(pubsubA._sendSubscriptions.callCount).to.eql(1);
// Other peer should receive subscription message
await pWaitFor(() => {
const subscribers = pubsubB.getSubscribers(topic);
return subscribers.length === 1;
});
// @ts-expect-error .callCount is a property added by sinon
expect(pubsubB._processRpcSubOpt.callCount).to.eql(1);
// Unsubscribe
pubsubA.unsubscribe(topic);
// Should send subscriptions to a peer
// @ts-expect-error .callCount is a property added by sinon
expect(pubsubA._sendSubscriptions.callCount).to.eql(2);
// Other peer should receive subscription message
await pWaitFor(() => {
const subscribers = pubsubB.getSubscribers(topic);
return subscribers.length === 0;
});
// @ts-expect-error .callCount is a property added by sinon
expect(pubsubB._processRpcSubOpt.callCount).to.eql(2);
});
it('should not send unsubscribe message to connected peers if not subscribed', () => {
sinon.spy(pubsubA, '_sendSubscriptions');
sinon.spy(pubsubB, '_processRpcSubOpt');
// Unsubscribe
pubsubA.unsubscribe(topic);
// Should send subscriptions to a peer
// @ts-expect-error .callCount is a property added by sinon
expect(pubsubA._sendSubscriptions.callCount).to.eql(0);
});
});
});
describe('getTopics', () => {
let peerId;
let pubsub;
beforeEach(async () => {
peerId = await createPeerId();
pubsub = new PubsubImplementation({
multicodecs: [protocol],
libp2p: {
peerId: peerId,
registrar: mockRegistrar
}
});
await pubsub.start();
});
afterEach(() => pubsub.stop());
it('returns the subscribed topics', () => {
let subsTopics = pubsub.getTopics();
expect(subsTopics).to.have.lengthOf(0);
pubsub.subscribe(topic);
subsTopics = pubsub.getTopics();
expect(subsTopics).to.have.lengthOf(1);
expect(subsTopics[0]).to.eql(topic);
});
});
describe('getSubscribers', () => {
let peerId;
let pubsub;
beforeEach(async () => {
peerId = await createPeerId();
pubsub = new PubsubImplementation({
multicodecs: [protocol],
libp2p: {
peerId: peerId,
registrar: mockRegistrar
}
});
});
afterEach(() => pubsub.stop());
it('should fail if pubsub is not started', () => {
const topic = 'topic-test';
try {
pubsub.getSubscribers(topic);
}
catch (err) {
expect(err).to.exist();
expect(err.code).to.eql('ERR_NOT_STARTED_YET');
return;
}
throw new Error('should fail if pubsub is not started');
});
it('should fail if no topic is provided', async () => {
// start pubsub
await pubsub.start();
try {
// @ts-expect-error invalid params
pubsub.getSubscribers();
}
catch (err) {
expect(err).to.exist();
expect(err.code).to.eql('ERR_NOT_VALID_TOPIC');
return;
}
throw new Error('should fail if no topic is provided');
});
it('should get peer subscribed to one topic', async () => {
const topic = 'topic-test';
// start pubsub
await pubsub.start();
let peersSubscribed = pubsub.getSubscribers(topic);
expect(peersSubscribed).to.be.empty();
// Set mock peer subscribed
const peer = new PeerStreams({ id: peerId, protocol: 'a-protocol' });
const id = peer.id.toString();
pubsub.topics.set(topic, new Set([id]));
pubsub.peers.set(id, peer);
peersSubscribed = pubsub.getSubscribers(topic);
expect(peersSubscribed).to.not.be.empty();
expect(peersSubscribed[0]).to.eql(id);
});
});
});
//# sourceMappingURL=pubsub.spec.js.map