diff --git a/packages/verified-fetch/package.json b/packages/verified-fetch/package.json index 08b1affc..aa0ef5cf 100644 --- a/packages/verified-fetch/package.json +++ b/packages/verified-fetch/package.json @@ -90,6 +90,7 @@ "@helia/json": "^3.0.1", "@helia/utils": "^0.0.2", "@ipld/car": "^5.3.0", + "@libp2p/interface-compliance-tests": "^5.3.2", "@libp2p/logger": "^4.0.7", "@libp2p/peer-id-factory": "^4.0.7", "@sgtpooki/file-type": "^1.0.1", diff --git a/packages/verified-fetch/src/utils/parse-url-string.ts b/packages/verified-fetch/src/utils/parse-url-string.ts index d869f77c..ad76f8ba 100644 --- a/packages/verified-fetch/src/utils/parse-url-string.ts +++ b/packages/verified-fetch/src/utils/parse-url-string.ts @@ -136,13 +136,14 @@ export async function parseUrlString ({ urlString, ipns, logger }: ParseUrlStrin */ const pathParts = [] + if (resolvedPath != null && resolvedPath.length > 0) { + pathParts.push(resolvedPath) + } + if (urlPath.length > 0) { pathParts.push(urlPath) } - if (resolvedPath != null && resolvedPath.length > 0) { - pathParts.push(resolvedPath) - } const path = pathParts.join('/') return { diff --git a/packages/verified-fetch/test/utils/parse-url-string.spec.ts b/packages/verified-fetch/test/utils/parse-url-string.spec.ts new file mode 100644 index 00000000..0e8fbc50 --- /dev/null +++ b/packages/verified-fetch/test/utils/parse-url-string.spec.ts @@ -0,0 +1,185 @@ +import { matchPeerId } from '@libp2p/interface-compliance-tests/matchers' +import { defaultLogger } from '@libp2p/logger' +import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import { expect } from 'aegir/chai' +import { CID } from 'multiformats/cid' +import { stubInterface } from 'sinon-ts' +import { parseUrlString } from '../../src/utils/parse-url-string.js' +import type { IPNS } from '@helia/ipns' +import type { ComponentLogger } from '@libp2p/interface' +import type { StubbedInstance } from 'sinon-ts' + +describe('parse-url-string', () => { + let logger: ComponentLogger + let ipns: StubbedInstance + + beforeEach(() => { + logger = defaultLogger() + ipns = stubInterface() + }) + + it('should parse an ipfs:// url', async () => { + const cid = CID.parse('QmQJ8fxavY54CUsxMSx9aE9Rdcmvhx8awJK2jzJp4iAqCr') + + await expect(parseUrlString({ + urlString: `ipfs://${cid}`, + ipns, + logger + })).to.eventually.deep.equal({ + protocol: 'ipfs', + path: '', + cid, + query: {} + }) + }) + + it('should parse an ipfs:// url with a path', async () => { + const cid = CID.parse('QmQJ8fxavY54CUsxMSx9aE9Rdcmvhx8awJK2jzJp4iAqCr') + const path = 'foo/bar/baz.txt' + + await expect(parseUrlString({ + urlString: `ipfs://${cid}/${path}`, + ipns, + logger + })).to.eventually.deep.equal({ + protocol: 'ipfs', + path, + cid, + query: {} + }) + }) + + it('should parse an ipfs:// url with a path and a query', async () => { + const cid = CID.parse('QmQJ8fxavY54CUsxMSx9aE9Rdcmvhx8awJK2jzJp4iAqCr') + const path = 'foo/bar/baz.txt' + + await expect(parseUrlString({ + urlString: `ipfs://${cid}/${path}?format=car&download=true&filename=qux.zip`, + ipns, + logger + })).to.eventually.deep.equal({ + protocol: 'ipfs', + path, + cid, + query: { + format: 'car', + download: true, + filename: 'qux.zip' + } + }) + }) + + it('should parse an ipns:// url', async () => { + const cid = CID.parse('QmQJ8fxavY54CUsxMSx9aE9Rdcmvhx8awJK2jzJp4iAqCr') + const peerId = await createEd25519PeerId() + + ipns.resolve.withArgs(matchPeerId(peerId)).resolves({ + cid, + path: '' + }) + + await expect(parseUrlString({ + urlString: `ipns://${peerId}`, + ipns, + logger + })).to.eventually.deep.equal({ + protocol: 'ipns', + path: '', + cid, + query: {} + }) + }) + + it('should parse an ipns:// url with a path', async () => { + const cid = CID.parse('QmQJ8fxavY54CUsxMSx9aE9Rdcmvhx8awJK2jzJp4iAqCr') + const peerId = await createEd25519PeerId() + const path = 'foo/bar/baz.txt' + + ipns.resolve.withArgs(matchPeerId(peerId)).resolves({ + cid, + path: '' + }) + + await expect(parseUrlString({ + urlString: `ipns://${peerId}/${path}`, + ipns, + logger + })).to.eventually.deep.equal({ + protocol: 'ipns', + path, + cid, + query: {} + }) + }) + + it('should parse an ipns:// url that resolves to a path', async () => { + const cid = CID.parse('QmQJ8fxavY54CUsxMSx9aE9Rdcmvhx8awJK2jzJp4iAqCr') + const peerId = await createEd25519PeerId() + const path = 'foo/bar/baz.txt' + + ipns.resolve.withArgs(matchPeerId(peerId)).resolves({ + cid, + path + }) + + await expect(parseUrlString({ + urlString: `ipns://${peerId}`, + ipns, + logger + })).to.eventually.deep.equal({ + protocol: 'ipns', + path, + cid, + query: {} + }) + }) + + it('should parse an ipns:// url with a path that resolves to a path', async () => { + const cid = CID.parse('QmQJ8fxavY54CUsxMSx9aE9Rdcmvhx8awJK2jzJp4iAqCr') + const peerId = await createEd25519PeerId() + const recordPath = 'foo' + const requestPath = 'bar/baz.txt' + + ipns.resolve.withArgs(matchPeerId(peerId)).resolves({ + cid, + path: recordPath + }) + + await expect(parseUrlString({ + urlString: `ipns://${peerId}/${requestPath}`, + ipns, + logger + })).to.eventually.deep.equal({ + protocol: 'ipns', + path: `${recordPath}/${requestPath}`, + cid, + query: {} + }) + }) + + it('should parse an ipns:// url with a path and a query', async () => { + const cid = CID.parse('QmQJ8fxavY54CUsxMSx9aE9Rdcmvhx8awJK2jzJp4iAqCr') + const peerId = await createEd25519PeerId() + const path = 'foo/bar/baz.txt' + + ipns.resolve.withArgs(matchPeerId(peerId)).resolves({ + cid, + path + }) + + await expect(parseUrlString({ + urlString: `ipns://${peerId}?format=car&download=true&filename=qux.zip`, + ipns, + logger + })).to.eventually.deep.equal({ + protocol: 'ipns', + path, + cid, + query: { + format: 'car', + download: true, + filename: 'qux.zip' + } + }) + }) +})