/* eslint-env mocha */ import { expect } from 'aegir/chai' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import { TCP } from '@libp2p/tcp' import { Mplex } from '@libp2p/mplex' import { Plaintext } from '../../src/insecure/index.js' import { createPeerId } from '../utils/creators/peer.js' import { codes } from '../../src/errors.js' import type { PeerId } from '@libp2p/interface-peer-id' async function createNode (peerId: PeerId) { return await createLibp2pNode({ peerId, addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, transports: [ new TCP() ], streamMuxers: [ new Mplex() ], connectionEncryption: [ new Plaintext() ] }) } describe('Fetch', () => { let sender: Libp2pNode let receiver: Libp2pNode const PREFIX_A = '/moduleA/' const PREFIX_B = '/moduleB/' const DATA_A = { foobar: 'hello world' } const DATA_B = { foobar: 'goodnight moon' } const generateLookupFunction = function (prefix: string, data: Record<string, string>) { return async function (key: string): Promise<Uint8Array | null> { key = key.slice(prefix.length) // strip prefix from key const val = data[key] if (val != null) { return (new TextEncoder()).encode(val) } return null } } beforeEach(async () => { const peerIdA = await createPeerId() const peerIdB = await createPeerId() sender = await createNode(peerIdA) receiver = await createNode(peerIdB) await sender.start() await receiver.start() await Promise.all([ ...sender.getMultiaddrs().map(async addr => await receiver.dial(addr)), ...receiver.getMultiaddrs().map(async addr => await sender.dial(addr)) ]) }) afterEach(async () => { receiver.fetchService.unregisterLookupFunction(PREFIX_A) receiver.fetchService.unregisterLookupFunction(PREFIX_B) await sender.stop() await receiver.stop() }) it('fetch key that exists in receivers datastore', async () => { receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) const rawData = await sender.fetch(receiver.peerId, '/moduleA/foobar') if (rawData == null) { throw new Error('Value was not found') } const value = (new TextDecoder()).decode(rawData) expect(value).to.equal('hello world') }) it('Different lookups for different prefixes', async () => { receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) receiver.fetchService.registerLookupFunction(PREFIX_B, generateLookupFunction(PREFIX_B, DATA_B)) const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar') if (rawDataA == null) { throw new Error('Value was not found') } const valueA = (new TextDecoder()).decode(rawDataA) expect(valueA).to.equal('hello world') // Different lookup functions can be registered on different prefixes, and have different // values for the same key underneath the different prefix. const rawDataB = await sender.fetch(receiver.peerId, '/moduleB/foobar') if (rawDataB == null) { throw new Error('Value was not found') } const valueB = (new TextDecoder()).decode(rawDataB) expect(valueB).to.equal('goodnight moon') }) it('fetch key that does not exist in receivers datastore', async () => { receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) const result = await sender.fetch(receiver.peerId, '/moduleA/garbage') expect(result).to.equal(null) }) it('fetch key with unknown prefix throws error', async () => { receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) await expect(sender.fetch(receiver.peerId, '/moduleUNKNOWN/foobar')) .to.eventually.be.rejected.with.property('code', codes.ERR_INVALID_PARAMETERS) }) it('registering multiple handlers for same prefix errors', async () => { receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) expect(() => receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_B))) .to.throw().with.property('code', codes.ERR_KEY_ALREADY_EXISTS) }) it('can unregister handler', async () => { const lookupFunction = generateLookupFunction(PREFIX_A, DATA_A) receiver.fetchService.registerLookupFunction(PREFIX_A, lookupFunction) const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar') if (rawDataA == null) { throw new Error('Value was not found') } const valueA = (new TextDecoder()).decode(rawDataA) expect(valueA).to.equal('hello world') receiver.fetchService.unregisterLookupFunction(PREFIX_A, lookupFunction) await expect(sender.fetch(receiver.peerId, '/moduleA/foobar')) .to.eventually.be.rejectedWith(/No lookup function registered for key/) }) it('can unregister all handlers', async () => { const lookupFunction = generateLookupFunction(PREFIX_A, DATA_A) receiver.fetchService.registerLookupFunction(PREFIX_A, lookupFunction) const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar') if (rawDataA == null) { throw new Error('Value was not found') } const valueA = (new TextDecoder()).decode(rawDataA) expect(valueA).to.equal('hello world') receiver.fetchService.unregisterLookupFunction(PREFIX_A) await expect(sender.fetch(receiver.peerId, '/moduleA/foobar')) .to.eventually.be.rejectedWith(/No lookup function registered for key/) }) it('does not unregister wrong handlers', async () => { const lookupFunction = generateLookupFunction(PREFIX_A, DATA_A) receiver.fetchService.registerLookupFunction(PREFIX_A, lookupFunction) const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar') if (rawDataA == null) { throw new Error('Value was not found') } const valueA = (new TextDecoder()).decode(rawDataA) expect(valueA).to.equal('hello world') receiver.fetchService.unregisterLookupFunction(PREFIX_A, async () => { return null }) const rawDataB = await sender.fetch(receiver.peerId, '/moduleA/foobar') if (rawDataB == null) { throw new Error('Value was not found') } const valueB = (new TextDecoder()).decode(rawDataB) expect(valueB).to.equal('hello world') }) })