Skip to content

Commit

Permalink
feat: client tests
Browse files Browse the repository at this point in the history
  • Loading branch information
hacdias committed Oct 13, 2023
1 parent 2a958bc commit 10d77e2
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 22 deletions.
38 changes: 36 additions & 2 deletions packages/client/.aegir.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,51 @@ const options = {
test: {
before: async () => {
const providers = new Map()
const peers = new Map()
const ipnsGet = new Map()
const ipnsPut = new Map()
const echo = new EchoServer()
echo.polka.use(body.raw({ type: 'application/vnd.ipfs.ipns-record'}))
echo.polka.use(body.text())
echo.polka.post('/add-providers/:cid', (req, res) => {
providers.set(req.params.cid, req.body)
res.end()
})
echo.polka.get('/routing/v1/providers/:cid', (req, res) => {
const provs = providers.get(req.params.cid) ?? '[]'
const records = providers.get(req.params.cid) ?? '[]'
providers.delete(req.params.cid)

res.end(provs)
res.end(records)
})
echo.polka.post('/add-peers/:peerId', (req, res) => {
peers.set(req.params.peerId, req.body)
res.end()
})
echo.polka.get('/routing/v1/peers/:peerId', (req, res) => {
const records = peers.get(req.params.peerId) ?? '[]'
peers.delete(req.params.peerId)

res.end(records)
})
echo.polka.post('/add-ipns/:peerId', (req, res) => {
ipnsGet.set(req.params.peerId, req.body)
res.end()
})
echo.polka.get('/routing/v1/ipns/:peerId', (req, res) => {
const record = ipnsGet.get(req.params.peerId) ?? ''
ipnsGet.delete(req.params.peerId)

res.end(record)
})
echo.polka.put('/routing/v1/ipns/:peerId', (req, res) => {
ipnsPut.set(req.params.peerId, req.body)
res.end()
})
echo.polka.get('/get-ipns/:peerId', (req, res) => {
const record = ipnsPut.get(req.params.peerId) ?? ''
ipnsPut.delete(req.params.peerId)

res.end(record)
})

await echo.start()
Expand Down
1 change: 0 additions & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@
"browser-readablestream-to-it": "^2.0.3",
"ipns": "^7.0.1",
"it-all": "^3.0.2",
"it-to-buffer": "^4.0.3",
"iterable-ndjson": "^1.1.0",
"multiformats": "^12.1.1",
"p-defer": "^4.0.0",
Expand Down
41 changes: 22 additions & 19 deletions packages/client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { peerIdFromString } from '@libp2p/peer-id'
import { multiaddr } from '@multiformats/multiaddr'
import { anySignal } from 'any-signal'
import toIt from 'browser-readablestream-to-it'
import { unmarshal, type IPNSRecord, marshal } from 'ipns'
import toBuffer from 'it-to-buffer'
import { unmarshal, type IPNSRecord, marshal, peerIdToRoutingKey } from 'ipns'
import { ipnsValidator } from 'ipns/validator'
// @ts-expect-error no types
import ndjson from 'iterable-ndjson'
import defer from 'p-defer'
Expand Down Expand Up @@ -74,15 +74,15 @@ export class DefaultRoutingV1HttpApiClient implements RoutingV1HttpApiClient {
// https://specs.ipfs.tech/routing/http-routing-v1/
const resource = `${this.clientUrl}routing/v1/providers/${cid.toString()}`
const getOptions = { headers: { Accept: 'application/x-ndjson' }, signal }
const a = await fetch(resource, getOptions)
const res = await fetch(resource, getOptions)

if (a.body == null) {
if (res.body == null) {
throw new CodeError('Routing response had no body', 'ERR_BAD_RESPONSE')
}

const contentType = a.headers.get('Content-Type')
const contentType = res.headers.get('Content-Type')
if (contentType === 'application/json') {
const body = await a.json()
const body = await res.json()

for (const provider of body.Providers) {
const record = this.#handleProviderRecords(provider)
Expand All @@ -91,7 +91,7 @@ export class DefaultRoutingV1HttpApiClient implements RoutingV1HttpApiClient {
}
}
} else {
for await (const provider of ndjson(toIt(a.body))) {
for await (const provider of ndjson(toIt(res.body))) {
const record = this.#handleProviderRecords(provider)
if (record !== null) {
yield record
Expand Down Expand Up @@ -125,25 +125,25 @@ export class DefaultRoutingV1HttpApiClient implements RoutingV1HttpApiClient {
// https://specs.ipfs.tech/routing/http-routing-v1/
const resource = `${this.clientUrl}routing/v1/peers/${peerId.toCID().toString()}`
const getOptions = { headers: { Accept: 'application/x-ndjson' }, signal }
const a = await fetch(resource, getOptions)
const res = await fetch(resource, getOptions)

if (a.body == null) {
if (res.body == null) {
throw new CodeError('Routing response had no body', 'ERR_BAD_RESPONSE')
}

const contentType = a.headers.get('Content-Type')
const contentType = res.headers.get('Content-Type')
if (contentType === 'application/json') {
const body = await a.json()
const body = await res.json()

for (const peer of body.Peers) {
const record = this.#handlePeerRecords(peer)
const record = this.#handlePeerRecords(peerId, peer)
if (record !== null) {
yield record
}
}
} else {
for await (const peer of ndjson(toIt(a.body))) {
const record = this.#handlePeerRecords(peer)
for await (const peer of ndjson(toIt(res.body))) {
const record = this.#handlePeerRecords(peerId, peer)
if (record !== null) {
yield record
}
Expand Down Expand Up @@ -176,13 +176,14 @@ export class DefaultRoutingV1HttpApiClient implements RoutingV1HttpApiClient {
// https://specs.ipfs.tech/routing/http-routing-v1/
const resource = `${this.clientUrl}routing/v1/ipns/${peerId.toCID().toString()}`
const getOptions = { headers: { Accept: 'application/vnd.ipfs.ipns-record' }, signal }
const a = await fetch(resource, getOptions)
const res = await fetch(resource, getOptions)

if (a.body == null) {
if (res.body == null) {
throw new CodeError('GET ipns response had no body', 'ERR_BAD_RESPONSE')
}

const body = await toBuffer(toIt(a.body))
const body = new Uint8Array(await res.arrayBuffer())
await ipnsValidator(peerIdToRoutingKey(peerId), body)
return unmarshal(body)
} finally {
signal.clear()
Expand Down Expand Up @@ -241,12 +242,14 @@ export class DefaultRoutingV1HttpApiClient implements RoutingV1HttpApiClient {
return null
}

#handlePeerRecords (record: any): PeerRecord | null {
#handlePeerRecords (peerId: PeerId, record: any): PeerRecord | null {
if (record.Schema === 'peer') {
// Peer schema can have additional, user-defined, fields.
record.ID = peerIdFromString(record.ID)
record.Addrs = record.Addrs.map(multiaddr)
return record
if (peerId.equals(record.ID)) {
return record
}
}

return null
Expand Down
100 changes: 100 additions & 0 deletions packages/client/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { createEd25519PeerId } from '@libp2p/peer-id-factory'
import { expect } from 'aegir/chai'
import { create as createIpnsRecord, marshal as marshalIpnsRecord } from 'ipns'
import all from 'it-all'
import { CID } from 'multiformats/cid'
import { createRoutingV1HttpApiClient, type RoutingV1HttpApiClient } from '../src/index.js'
Expand Down Expand Up @@ -103,4 +104,103 @@ describe('routing-v1-http-api-client', () => {
const provs = await all(client.getProviders(cid))
expect(provs).to.be.empty()
})

it('should find peers and only accepts correct peer records', async () => {
const peerId = await createEd25519PeerId()

const records = [{
Protocol: 'transport-bitswap',
Schema: 'bitswap',
Metadata: 'gBI=',
ID: peerId.toString(),
Addrs: ['/ip4/41.41.41.41/tcp/1234']
}, {
Protocol: 'transport-saddle',
Schema: 'horse-ride',
Metadata: 'gBI=',
ID: peerId.toString(),
Addrs: ['/ip4/41.41.41.41/tcp/1234']
}, {
Protocols: ['transport-bitswap'],
Schema: 'peer',
Metadata: 'gBI=',
ID: peerId.toString(),
Addrs: ['/ip4/42.42.42.42/tcp/1234']
}, {
Protocols: ['transport-bitswap'],
Schema: 'peer',
Metadata: 'gBI=',
ID: (await createEd25519PeerId()).toString(),
Addrs: ['/ip4/42.42.42.42/tcp/1234']
}]

// load peer for the router to fetch
await fetch(`${process.env.ECHO_SERVER}/add-peers/${peerId.toCID().toString()}`, {
method: 'POST',
body: records.map(prov => JSON.stringify(prov)).join('\n')
})

const peerRecords = await all(client.getPeerInfo(peerId))
expect(peerRecords.map(peerRecord => ({
...peerRecord,
ID: peerRecord.ID.toString(),
Addrs: peerRecord.Addrs.map(ma => ma.toString())
}))).to.deep.equal([
records[2]
])
})

it('should get ipns record', async () => {
const cid = CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')
const peerId = await createEd25519PeerId()
const record = await createIpnsRecord(peerId, cid, 0, 1000)

// load record for the router to fetch
await fetch(`${process.env.ECHO_SERVER}/add-ipns/${peerId.toCID().toString()}`, {
method: 'POST',
headers: {
'Content-Type': 'application/vnd.ipfs.ipns-record'
},
body: marshalIpnsRecord(record)
})

const ipnsRecord = await client.getIPNS(peerId)
expect(marshalIpnsRecord(ipnsRecord)).to.equalBytes(marshalIpnsRecord(record))
})

it('get ipns record fails with bad record', async () => {
const cid = CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')
const peerId = await createEd25519PeerId()
const record = await createIpnsRecord(await createEd25519PeerId(), cid, 0, 1000)

// load record for the router to fetch
await fetch(`${process.env.ECHO_SERVER}/add-ipns/${peerId.toCID().toString()}`, {
method: 'POST',
headers: {
'Content-Type': 'application/vnd.ipfs.ipns-record'
},
body: marshalIpnsRecord(record)
})

await expect(client.getIPNS(peerId)).to.be.rejected()
})

it('should put ipns', async () => {
const cid = CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')
const peerId = await createEd25519PeerId()
const record = await createIpnsRecord(peerId, cid, 0, 1000)

await client.putIPNS(peerId, record)

// load record that our client just PUT to remote server
const res = await fetch(`${process.env.ECHO_SERVER}/get-ipns/${peerId.toCID().toString()}`, {
method: 'GET',
headers: {
Accept: 'application/vnd.ipfs.ipns-record'
}
})

const receivedRecord = new Uint8Array(await res.arrayBuffer())
expect(marshalIpnsRecord(record)).to.equalBytes(receivedRecord)
})
})

0 comments on commit 10d77e2

Please sign in to comment.