text:
'use strict'
import { stub } from 'sinon'
import { describe, it, beforeEach, afterEach } from 'mocha'
import { expect } from 'chai'
import { URL } from 'url'
import { ipfsUri, ipfsContentPath, createIpfsPathValidator, sameGateway, safeHostname } from '../../../add-on/src/lib/ipfs-path.js'
import { initState } from '../../../add-on/src/lib/state.js'
import createDnslinkResolver from '../../../add-on/src/lib/dnslink.js'
import { optionDefaults } from '../../../add-on/src/lib/options.js'
import { spoofCachedDnslink } from './dnslink.test.js'

function spoofIpfsResolve (ipfs, path, value) {
  const resolve = stub(ipfs, 'resolve')
  resolve.withArgs(path).resolves(value)
  resolve.throws((arg) => new Error(`Unexpected stubbed call ipfs.resolve(${arg})`))
}

// https://github.com/ipfs/ipfs-companion/issues/303
describe('ipfs-path.js', function () {
  let ipfs, state, dnslinkResolver, ipfsPathValidator

  beforeEach(function () {
    global.URL = URL
    state = Object.assign(initState(optionDefaults), { peerCount: 1 })
    ipfs = {
      name: {
        resolve (arg) {
          throw new Error(`Unexpected call ipfs.name.resolve(${arg})`)
        }
      },
      resolve (arg) {
        throw new Error(`Unexpected call ipfs.resolve(${arg})`)
      }
    }
    dnslinkResolver = createDnslinkResolver(() => state)
    ipfsPathValidator = createIpfsPathValidator(() => state, () => ipfs, dnslinkResolver)
  })

  afterEach(function () {
    if (ipfs.name.resolve.reset) ipfs.name.resolve.reset()
    if (ipfs.resolve.reset) ipfs.resolve.reset()
  })

  // TODO: move to some lib?
  describe('ipfsContentPath', function () {
    it('should detect /ipfs/ path in URL from a public gateway', function () {
      const url = 'https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar'
      expect(ipfsContentPath(url)).to.equal('/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar')
    })
    it('should detect /ipfs/ path in detached IPFS path', function () {
      const path = '/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar'
      expect(ipfsContentPath(path)).to.equal('/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar')
    })
    it('should detect /ipns/ path in URL from a public gateway', function () {
      const url = 'https://ipfs.io/ipns/libp2p.io/bundles/'
      expect(ipfsContentPath(url)).to.equal('/ipns/libp2p.io/bundles/')
    })
    it('should detect /ipns/ path in detached IPFS path', function () {
      const path = '/ipns/libp2p.io/bundles/'
      expect(ipfsContentPath(path)).to.equal('/ipns/libp2p.io/bundles/')
    })
    it('should drop search and hash from URL by default', function () {
      const url = 'https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest'
      expect(ipfsContentPath(url)).to.equal('/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR')
    })
    it('should keep search and hash from URL if keepURIParams=true', function () {
      const url = 'https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest'
      expect(ipfsContentPath(url, { keepURIParams: true })).to.equal('/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
    })
    it('should drop search and hash in detached IPFS path by default', function () {
      const path = '/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest'
      expect(ipfsContentPath(path)).to.equal('/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR')
    })
    it('should preserve search and hash in detached IPFS path if keepURIParams=true', function () {
      const path = '/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest'
      expect(ipfsContentPath(path, { keepURIParams: true })).to.equal('/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
    })
    it('should decode special characters in URL', function () {
      const url = 'https://ipfs.io/ipfs/Qmb8wsGZNXt5VXZh1pEmYynjB6Euqpq3HYyeAdw2vScTkQ/1%20-%20Barrel%20-%20Part%201'
      expect(ipfsContentPath(url)).to.equal('/ipfs/Qmb8wsGZNXt5VXZh1pEmYynjB6Euqpq3HYyeAdw2vScTkQ/1 - Barrel - Part 1')
    })
    it('should decode special characters in path', function () {
      const path = '/ipfs/Qmb8wsGZNXt5VXZh1pEmYynjB6Euqpq3HYyeAdw2vScTkQ/1%20-%20Barrel%20-%20Part%201'
      expect(ipfsContentPath(path)).to.equal('/ipfs/Qmb8wsGZNXt5VXZh1pEmYynjB6Euqpq3HYyeAdw2vScTkQ/1 - Barrel - Part 1')
    })
    it('should resolve CID-in-subdomain URL to IPFS path', function () {
      const url = 'https://bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa.ipfs.dweb.link/wiki/Mars.html?argTest#hashTest'
      expect(ipfsContentPath(url)).to.equal('/ipfs/bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa/wiki/Mars.html')
    })
    it('should resolve CID(libp2p-key)-in-subdomain URL to IPNS path', function () {
      const url = 'https://k2k4r8ncs1yoluq95unsd7x2vfhgve0ncjoggwqx9vyh3vl8warrcp15.ipns.dweb.link/wiki/Mars.html?argTest#hashTest'
      expect(ipfsContentPath(url)).to.equal('/ipns/k2k4r8ncs1yoluq95unsd7x2vfhgve0ncjoggwqx9vyh3vl8warrcp15/wiki/Mars.html')
    })
    it('should resolve dnslink-in-subdomain URL to IPNS path', function () {
      const url = 'http://en.wikipedia-on-ipfs.org.ipns.localhost:8080/wiki/Mars.html?argTest#hashTest'
      expect(ipfsContentPath(url)).to.equal('/ipns/en.wikipedia-on-ipfs.org/wiki/Mars.html')
    })
    it('should resolve inlined-dnslink-in-subdomain URL to IPNS path', function () {
      const url = 'https://en-wikipedia--on--ipfs-org.ipns.dweb.link/wiki/Mars.html?argTest#hashTest'
      expect(ipfsContentPath(url)).to.equal('/ipns/en.wikipedia-on-ipfs.org/wiki/Mars.html')
    })
    it('should return null if there is no valid path for input URL', function () {
      const url = 'https://foo.io/invalid/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest'
      expect(ipfsContentPath(url)).to.equal(null)
    })
    it('should return null if there is no valid path for input path', function () {
      const path = '/invalid/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR'
      expect(ipfsContentPath(path)).to.equal(null)
    })
  })

  describe('safeHostname', function () {
    it('should return URL.hostname on http URL', function () {
      const url = 'https://example.com:8080/path/file.txt'
      expect(safeHostname(url)).to.equal('example.com')
    })
    it('should return null on error', function () {
      const url = ''
      expect(safeHostname(url)).to.equal(null)
    })
  })

  // TODO: move to some lib?
  describe('ipfsUri', function () {
    it('should detect /ipfs/ path in URL from a public gateway', function () {
      const url = 'https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar'
      expect(ipfsUri(url)).to.equal('ipfs://QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar')
    })
    it('should detect /ipfs/ path in detached IPFS path', function () {
      const path = '/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar'
      expect(ipfsUri(path)).to.equal('ipfs://QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar')
    })
    it('should detect /ipns/ path in URL from a public gateway', function () {
      const url = 'https://ipfs.io/ipns/libp2p.io/bundles/'
      expect(ipfsUri(url)).to.equal('ipns://libp2p.io/bundles/')
    })
    it('should detect /ipns/ path in detached IPFS path', function () {
      const path = '/ipns/libp2p.io/bundles/'
      expect(ipfsUri(path)).to.equal('ipns://libp2p.io/bundles/')
    })
    it('should keep search and hash from original URL', function () {
      const url = 'https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest'
      expect(ipfsUri(url)).to.equal('ipfs://QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
    })
    it('should preserve search and hash in detached IPFS path', function () {
      const path = '/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest'
      expect(ipfsUri(path)).to.equal('ipfs://QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
    })
    it('should decode special characters in URL', function () {
      const url = 'https://ipfs.io/ipfs/Qmb8wsGZNXt5VXZh1pEmYynjB6Euqpq3HYyeAdw2vScTkQ/1%20-%20Barrel%20-%20Part%201'
      expect(ipfsUri(url)).to.equal('ipfs://Qmb8wsGZNXt5VXZh1pEmYynjB6Euqpq3HYyeAdw2vScTkQ/1 - Barrel - Part 1')
    })
    it('should decode special characters in path', function () {
      const path = '/ipfs/Qmb8wsGZNXt5VXZh1pEmYynjB6Euqpq3HYyeAdw2vScTkQ/1%20-%20Barrel%20-%20Part%201'
      expect(ipfsUri(path)).to.equal('ipfs://Qmb8wsGZNXt5VXZh1pEmYynjB6Euqpq3HYyeAdw2vScTkQ/1 - Barrel - Part 1')
    })
    it('should resolve CID-in-subdomain URL to IPFS path', function () {
      const url = 'https://bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa.ipfs.dweb.link/wiki/Mars.html?argTest#hashTest'
      expect(ipfsUri(url)).to.equal('ipfs://bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa/wiki/Mars.html?argTest#hashTest')
    })
    it('should return null if there is no valid path for input URL', function () {
      const url = 'https://foo.io/invalid/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest'
      expect(ipfsUri(url)).to.equal(null)
    })
    it('should return null if there is no valid path for input path', function () {
      const path = '/invalid/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR'
      expect(ipfsUri(path)).to.equal(null)
    })
  })

  describe('sameGateway', function () {
    it('should return true on direct host match', function () {
      const url = 'https://127.0.0.1:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar'
      const gw = 'http://127.0.0.1:8080'
      expect(sameGateway(url, gw)).to.equal(true)
    })
    it('should return true on localhost/127.0.0.1 host match', function () {
      const url = 'https://localhost:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar'
      const gw = 'http://127.0.0.1:8080'
      expect(sameGateway(url, gw)).to.equal(true)
    })
    it('should return true on 127.0.0.1/localhost host match', function () {
      const url = 'https://127.0.0.1:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar'
      const gw = 'http://localhost:8080'
      expect(sameGateway(url, gw)).to.equal(true)
    })
    it('should return true on localhost subdomain host match', function () {
      const url = 'http://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi.ipfs.localhost:8080/foo/bar'
      const gw = 'http://localhost:8080'
      expect(sameGateway(url, gw)).to.equal(true)
    })
    it('should return true on RPC webui path 127.0.0.1/0.0.0.0 host match', function () {
      // this is misconfiguration, but handling it in sameGateway ensures users who do this dont waste our time debugging
      const url = 'http://0.0.0.0:5001/webui'
      const api = 'http://127.0.0.1:5001'
      expect(sameGateway(url, api)).to.equal(true)
    })
    it('should return true on RPC /api/v0 path 127.0.0.1/0.0.0.0 host match', function () {
      // this is misconfiguration, but handling it in sameGateway ensures users who do this dont waste our time debugging
      const url = 'http://0.0.0.0:5001/api/v0/id'
      const api = 'http://127.0.0.1:5001'
      expect(sameGateway(url, api)).to.equal(true)
    })
    it('should return false on hostname match but different port', function () {
      const url = 'https://localhost:8081/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar'
      const gw = 'http://localhost:8080'
      expect(sameGateway(url, gw)).to.equal(false)
    })
  })

  describe('validIpfsOrIpns', function () {
    // this is just a smoke test, extensive tests are in is-ipfs package
    it('should return true for IPFS NURI', function () {
      const path = '/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest'
      expect(ipfsPathValidator.validIpfsOrIpns(path)).to.equal(true)
    })
    it('should return false for non-IPFS NURI', function () {
      const path = '/ipfs/NotAValidCid'
      expect(ipfsPathValidator.validIpfsOrIpns(path)).to.equal(false)
    })
    // this is just a smoke test, extensive tests are in is-ipfs package
    it('should return true for URL at IPFS Gateway', function () {
      const url = 'https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest'
      expect(ipfsPathValidator.validIpfsOrIpns(url)).to.equal(true)
    })
    it('should return false for non-IPFS URL', function () {
      const url = 'https://ipfs.io/ipfs/NotACid?argTest#hashTest'
      expect(ipfsPathValidator.validIpfsOrIpns(url)).to.equal(false)
    })
  })

  describe('publicIpfsOrIpnsResource', function () {
    it('should return true for URL at Public IPFS Gateway', function () {
      const url = `${state.pubGwURL}ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest`
      expect(ipfsPathValidator.publicIpfsOrIpnsResource(url)).to.equal(true)
    })
    it('should return false for URL at Local IPFS Gateway', function () {
      const url = `${state.gwURL}ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest`
      expect(ipfsPathValidator.publicIpfsOrIpnsResource(url)).to.equal(false)
    })
    it('should return false for IPFS URL at API port', function () {
      const url = `${state.apiURL}ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest`
      expect(ipfsPathValidator.publicIpfsOrIpnsResource(url)).to.equal(false)
      expect(ipfsPathValidator.validIpfsOrIpns(url)).to.equal(true)
    })
    it('should return false for non-IPFS URL', function () {
      const url = 'https://ipfs.io/ipfs/NotACid?argTest#hashTest'
      expect(ipfsPathValidator.publicIpfsOrIpnsResource(url)).to.equal(false)
      expect(ipfsPathValidator.validIpfsOrIpns(url)).to.equal(false)
    })
    describe('isIpfsPageActionsContext', function () {
      it('should return true for URL at Public IPFS Gateway', function () {
        const url = `${state.pubGwURL}ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest`
        expect(ipfsPathValidator.isIpfsPageActionsContext(url)).to.equal(true)
      })
      it('should return true for URL at Local IPFS Gateway', function () {
        const url = `${state.gwURL}ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest`
        expect(ipfsPathValidator.isIpfsPageActionsContext(url)).to.equal(true)
      })
      it('should return false for IPFS URL at API port', function () {
        const url = `${state.apiURL}ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest`
        expect(ipfsPathValidator.isIpfsPageActionsContext(url)).to.equal(false)
        expect(ipfsPathValidator.validIpfsOrIpns(url)).to.equal(true)
      })
      it('should return false for non-IPFS URL', function () {
        const url = 'https://ipfs.io/ipfs/NotACid?argTest#hashTest'
        expect(ipfsPathValidator.publicIpfsOrIpnsResource(url)).to.equal(false)
        expect(ipfsPathValidator.validIpfsOrIpns(url)).to.equal(false)
      })
    })
  })

  describe('isIpfsPageActionsContext', function () {
    it('should return true for URL at a Gateway', function () {
      const url = 'https://example.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest'
      expect(ipfsPathValidator.isIpfsPageActionsContext(url)).to.equal(true)
    })
    it('should return true for URL at a Gateway with Base32 CIDv1 in subdomain', function () {
      // context-actions are shown on publick gateways that use CID in subdomain as well
      const url = 'http://bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy.ipfs.dweb.link/'
      expect(ipfsPathValidator.isIpfsPageActionsContext(url)).to.equal(true)
    })
    it('should return false for URL at a Gateway with Base58 CIDv0 in subdomain', function () {
      // should not be allowed, but who knows
      const url = 'http://QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR.ipfs.dweb.link/'
      expect(ipfsPathValidator.isIpfsPageActionsContext(url)).to.equal(false)
    })
    it('should return false for non-IPFS URL', function () {
      const url = 'https://example.com/ipfs/NotACid?argTest#hashTest'
      expect(ipfsPathValidator.isIpfsPageActionsContext(url)).to.equal(false)
    })
  })

  describe('isRedirectPageActionsContext', function () {
    it('should return true for /ipfs/ URL at a Gateway', function () {
      const url = 'https://example.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest'
      expect(ipfsPathValidator.isRedirectPageActionsContext(url)).to.equal(true)
    })
    it('should return true for /ipns/ URL at Local Gateway', function () {
      const url = `${state.gwURL}ipns/docs.ipfs.io/?argTest#hashTest`
      expect(ipfsPathValidator.isRedirectPageActionsContext(url)).to.equal(true)
    })
    it('should return false for /ipfs/ URL at Local Gateway', function () {
      const url = `${state.gwURL}ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest`
      expect(ipfsPathValidator.isRedirectPageActionsContext(url)).to.equal(false)
    })
    it('should return false for IPFS content loaded from IPFS API port', function () {
      const url = `${state.apiURL}ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest`
      expect(ipfsPathValidator.isRedirectPageActionsContext(url)).to.equal(false)
    })
    it('should return true for URL at IPFS Gateway with Base32 CIDv1 in subdomain', function () {
      // context-actions are shown on publick gateways that use CID in subdomain as well
      const url = 'http://bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy.ipfs.dweb.link/'
      expect(ipfsPathValidator.isRedirectPageActionsContext(url)).to.equal(true)
    })
    it('should return true for non-IPFS HTTP URL', function () {
      const url = 'http://en.wikipedia.org/wiki/Main_Page'
      expect(ipfsPathValidator.isRedirectPageActionsContext(url)).to.equal(true)
    })
    it('should return true for non-IPFS HTTPS URL', function () {
      const url = 'https://en.wikipedia.org/wiki/Main_Page'
      expect(ipfsPathValidator.isRedirectPageActionsContext(url)).to.equal(true)
    })
    it('should return false for non-HTTP URL', function () {
      const url = 'moz-extension://85076b5e-900c-428f-4bf6-e6c1a33042a7/blank-page.html'
      expect(ipfsPathValidator.isRedirectPageActionsContext(url)).to.equal(false)
    })
  })

  describe('resolveToPublicUrl', function () {
    it('should resolve URL with CID-in-subdomain to itself', function () {
      const url = 'https://bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa.ipfs.dweb.link/wiki/Mars.html?argTest#hashTest'
      expect(ipfsPathValidator.resolveToPublicUrl(url)).to.equal(url)
    })
    it('should resolve URL with /ipfs/ path to the default public gateway', function () {
      const url = 'https://example.com/ipfs/bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa/wiki/Mars.html?argTest#hashTest'
      expect(ipfsPathValidator.resolveToPublicUrl(url)).to.equal(`${state.pubGwURL}ipfs/bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa/wiki/Mars.html?argTest#hashTest`)
    })
    it('should resolve /ipfs/ path to itself attached to the default public gateway', function () {
      const path = '/ipfs/bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa/wiki/Mars.html?argTest#hashTest'
      expect(ipfsPathValidator.resolveToPublicUrl(path)).to.equal(`${state.pubGwURL}ipfs/bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa/wiki/Mars.html?argTest#hashTest`)
    })
    it('should resolve URL with /ipns/ path to the default public gateway', function () {
      const url = 'https://example.com/ipns/docs.ipfs.io/?argTest#hashTest'
      expect(ipfsPathValidator.resolveToPublicUrl(url)).to.equal(`${state.pubGwURL}ipns/docs.ipfs.io/?argTest#hashTest`)
    })
    it('should resolve /ipns/ path to itself at the default public gateway', function () {
      const path = '/ipns/docs.ipfs.io/?argTest#hashTest'
      expect(ipfsPathValidator.resolveToPublicUrl(path)).to.equal(`${state.pubGwURL}ipns/docs.ipfs.io/?argTest#hashTest`)
    })
    it('should resolve non-IPFS URL to itself (DNSLink websites)', function () {
      const url = 'https://example.com/foo/bar/?argTest#hashTest'
      expect(ipfsPathValidator.resolveToPublicUrl(url)).to.equal(url)
    })
    it('should resolve to null if input is an invalid path', function () {
      const path = '/foo/bar/?argTest#hashTest'
      expect(ipfsPathValidator.resolveToPublicUrl(path)).to.equal(null)
    })
    it('should resolve to null if input is an invalid URL', function () {
      const url = 'example.com/foo/bar/?argTest#hashTest'
      expect(ipfsPathValidator.resolveToPublicUrl(url)).to.equal(null)
    })
  })

  describe('resolveToIpfsPath', function () {
    it('should resolve URL with CID-in-subdomain to /ipfs/<cid> path', function () {
      const url = 'https://bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa.ipfs.dweb.link/wiki/Mars.html?argTest#hashTest'
      expect(ipfsPathValidator.resolveToIpfsPath(url)).to.equal('/ipfs/bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa/wiki/Mars.html?argTest#hashTest')
    })
    it('should resolve URL with /ipfs/ path to the path itself', function () {
      const url = 'https://example.com/ipfs/bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa/wiki/Mars.html?argTest#hashTest'
      expect(ipfsPathValidator.resolveToIpfsPath(url)).to.equal('/ipfs/bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa/wiki/Mars.html?argTest#hashTest')
    })
    it('should resolve /ipfs/ path to itself', function () {
      const path = '/ipfs/bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa/wiki/Mars.html?argTest#hashTest'
      expect(ipfsPathValidator.resolveToIpfsPath(path)).to.equal(path)
    })
    it('should resolve URL with /ipns/ path to the path itself', function () {
      const url = 'https://example.com/ipns/docs.ipfs.io/?argTest#hashTest'
      expect(ipfsPathValidator.resolveToIpfsPath(url)).to.equal('/ipns/docs.ipfs.io/?argTest#hashTest')
    })
    it('should resolve /ipns/ path to itself', function () {
      const path = '/ipns/docs.ipfs.io/?argTest#hashTest'
      expect(ipfsPathValidator.resolveToIpfsPath(path)).to.equal(path)
    })
    it('should resolve URL of a DNSLink website to null if the value if DNSLink is not in cache', function () {
      const url = 'https://docs.ipfs.io/guides/concepts/dnslink/?argTest#hashTest'
      spoofCachedDnslink(new URL(url).hostname, dnslinkResolver, undefined)
      expect(ipfsPathValidator.resolveToIpfsPath(url)).to.equal(null)
    })
    it('should resolve URL of a DNSLink website to /ipns/<fqdn> path if DNSLink is in cache', function () {
      const url = 'https://docs.ipfs.io/guides/concepts/dnslink/?argTest#hashTest'
      const dnslinkValue = '/ipns/QmRV5iNhGoxBaAcbucMAW9WtVHbeehXhAdr5CZQDhL55Xk'
      spoofCachedDnslink(new URL(url).hostname, dnslinkResolver, dnslinkValue)
      expect(ipfsPathValidator.resolveToIpfsPath(url)).to.equal('/ipns/docs.ipfs.io/guides/concepts/dnslink/?argTest#hashTest')
    })
    it('should resolve to null if input is an invalid path', function () {
      const path = '/foo/bar/?argTest#hashTest'
      expect(ipfsPathValidator.resolveToIpfsPath(path)).to.equal(null)
    })
    it('should resolve to null if input is an invalid URL', function () {
      const url = 'example.com/foo/bar/?argTest#hashTest'
      expect(ipfsPathValidator.resolveToIpfsPath(url)).to.equal(null)
    })
  })

  describe('async resolveToImmutableIpfsPath', function () {
    it('should resolve URL with CID-in-subdomain to the same value as resolveToIpfsPath', async function () {
      const url = 'https://bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa.ipfs.dweb.link/wiki/Mars.html?argTest#hashTest'
      expect(await ipfsPathValidator.resolveToImmutableIpfsPath(url)).to.equal(ipfsPathValidator.resolveToIpfsPath(url))
    })
    it('should resolve URL with /ipfs/ path to the same value as resolveToIpfsPath', async function () {
      const url = 'https://example.com/ipfs/bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa/wiki/Mars.html?argTest#hashTest'
      expect(await ipfsPathValidator.resolveToImmutableIpfsPath(url)).to.equal(ipfsPathValidator.resolveToIpfsPath(url))
    })
    it('should resolve /ipfs/ path to the same value as resolveToIpfsPath', async function () {
      const path = '/ipfs/bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa/wiki/Mars.html?argTest#hashTest'
      expect(await ipfsPathValidator.resolveToImmutableIpfsPath(path)).to.equal(ipfsPathValidator.resolveToIpfsPath(path))
    })
    it('should resolve URL with /ipns/ path to the immutable /ipfs/ path', async function () {
      const url = 'https://example.com/ipns/docs.ipfs.io/?argTest#hashTest'
      const ipnsPointer = '/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR'
      spoofIpfsResolve(ipfs, '/ipns/docs.ipfs.io', ipnsPointer)
      expect(await ipfsPathValidator.resolveToImmutableIpfsPath(url)).to.equal(ipnsPointer + '/?argTest#hashTest')
    })
    it('should resolve /ipns/ path to the immutable /ipfs/ one', async function () {
      const ipnsPointer = '/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR'
      spoofIpfsResolve(ipfs, '/ipns/libp2p.io', ipnsPointer)
      const path = '/ipns/libp2p.io/?argTest#hashTest'
      expect(await ipfsPathValidator.resolveToImmutableIpfsPath(path)).to.equal(ipnsPointer + '/?argTest#hashTest')
    })
    it('should resolve URL of a DNSLink website to null if the value if DNSLink is not in cache', async function () {
      const url = 'https://docs.ipfs.io/guides/concepts/dnslink/?argTest#hashTest'
      spoofCachedDnslink(new URL(url).hostname, dnslinkResolver, undefined)
      expect(await ipfsPathValidator.resolveToImmutableIpfsPath(url)).to.equal(null)
      expect(await ipfsPathValidator.resolveToImmutableIpfsPath(url)).to.equal(ipfsPathValidator.resolveToIpfsPath(url))
    })
    it('should resolve URL of a DNSLink website to the immutable /ipfs/ address behind mutable /ipns/ DNSLink in cache', async function () {
      const url = 'https://docs.ipfs.io/guides/concepts/dnslink/?argTest#hashTest'
      // Use IPNS in DNSLINK to ensure resolveToImmutableIpfsPath does resursive resolv to immutable address
      const dnslinkValue = '/ipns/QmRV5iNhGoxBaAcbucMAW9WtVHbeehXhAdr5CZQDhL55Xk'
      const ipnsPointer = '/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR'
      const { hostname } = new URL(url)
      spoofCachedDnslink(hostname, dnslinkResolver, dnslinkValue)
      // We need to spoof IPNS lookup for /ipns/<fqdn> because value from DNSLink cache
      // may be out of date and resolveToImmutableIpfsPath does additional resolv
      // to return latest IPNS value
      spoofIpfsResolve(ipfs, `/ipns/${hostname}`, ipnsPointer)
      expect(await ipfsPathValidator.resolveToImmutableIpfsPath(url)).to.equal(ipnsPointer + '/guides/concepts/dnslink/?argTest#hashTest')
    })
    it('should resolve to null if input is an invalid path', async function () {
      const path = '/foo/bar/?argTest#hashTest'
      expect(await ipfsPathValidator.resolveToImmutableIpfsPath(path)).to.equal(null)
      expect(await ipfsPathValidator.resolveToImmutableIpfsPath(path)).to.equal(ipfsPathValidator.resolveToIpfsPath(path))
    })
    it('should resolve to null if input is an invalid URL', async function () {
      const url = 'example.com/foo/bar/?argTest#hashTest'
      expect(await ipfsPathValidator.resolveToImmutableIpfsPath(url)).to.equal(null)
      expect(await ipfsPathValidator.resolveToImmutableIpfsPath(url)).to.equal(ipfsPathValidator.resolveToIpfsPath(url))
    })
  })

  describe('async resolveToCid', function () {
    it('should resolve URL with CID-in-subdomain', async function () {
      const url = 'https://bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa.ipfs.dweb.link/wiki/Mars.html?argTest#hashTest'
      const expectedCid = 'bafkreig6ltd5dojmrhlsweuiaxt3ag2p5644wwcyjmqt6gajmbrbylke4m'
      spoofIpfsResolve(ipfs, '/ipfs/bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa/wiki/Mars.html', '/ipfs/' + expectedCid)
      expect(await ipfsPathValidator.resolveToCid(url)).to.equal(expectedCid)
    })
    it('should resolve URL with /ipfs/ path', async function () {
      const url = 'https://example.com/ipfs/bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa/wiki/Mars.html?argTest#hashTest'
      const expectedCid = 'bafkreig6ltd5dojmrhlsweuiaxt3ag2p5644wwcyjmqt6gajmbrbylke4m'
      spoofIpfsResolve(ipfs, '/ipfs/bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa/wiki/Mars.html', '/ipfs/' + expectedCid)
      expect(await ipfsPathValidator.resolveToCid(url)).to.equal(expectedCid)
    })
    it('should resolve /ipfs/ path', async function () {
      const path = '/ipfs/bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa/wiki/Mars.html?argTest#hashTest'
      const expectedCid = 'bafkreig6ltd5dojmrhlsweuiaxt3ag2p5644wwcyjmqt6gajmbrbylke4m'
      spoofIpfsResolve(ipfs, '/ipfs/bafybeicgmdpvw4duutrmdxl4a7gc52sxyuk7nz5gby77afwdteh3jc5bqa/wiki/Mars.html', '/ipfs/' + expectedCid)
      expect(await ipfsPathValidator.resolveToCid(path)).to.equal(expectedCid)
    })
    it('should resolve URL with /ipns/ path', async function () {
      const url = 'https://example.com/ipns/docs.ipfs.io/foo/bar?argTest#hashTest'
      const expectedCid = 'QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR'
      spoofIpfsResolve(ipfs, '/ipns/docs.ipfs.io/foo/bar', `/ipfs/${expectedCid}`)
      expect(await ipfsPathValidator.resolveToCid(url)).to.equal(expectedCid)
    })
    it('should resolve /ipns/ path to the immutable /ipfs/ one', async function () {
      const path = '/ipns/libp2p.io/foo/bar?argTest#hashTest'
      const expectedCid = 'QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR'
      spoofIpfsResolve(ipfs, '/ipns/libp2p.io/foo/bar', `/ipfs/${expectedCid}`)
      expect(await ipfsPathValidator.resolveToCid(path)).to.equal(expectedCid)
    })
    it('should resolve URL of a DNSLink website to null if the value if DNSLink is not in cache', async function () {
      const url = 'https://docs.ipfs.io/guides/concepts/dnslink/?argTest#hashTest'
      spoofCachedDnslink(new URL(url).hostname, dnslinkResolver, undefined)
      expect(await ipfsPathValidator.resolveToCid(url)).to.equal(null)
    })
    it('should resolve URL of a DNSLink website if DNSLink is in cache', async function () {
      const url = 'https://docs.ipfs.io/guides/concepts/dnslink/?argTest#hashTest'
      // Use IPNS in DNSLINK to ensure resolveToImmutableIpfsPath does resursive resolv to immutable address
      const expectedCid = 'QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR'
      const dnslinkValue = '/ipns/QmRV5iNhGoxBaAcbucMAW9WtVHbeehXhAdr5CZQDhL55Xk'
      const { hostname } = new URL(url)
      spoofCachedDnslink(hostname, dnslinkResolver, dnslinkValue)
      // Note the DNSLink value is ignored, and /ipns/<fqdn> is passed to ipfs.resolv internally
      // This ensures the latest pointer is returned, instead of stale value from DNSLink cache
      spoofIpfsResolve(ipfs, '/ipns/docs.ipfs.io/guides/concepts/dnslink/', `/ipfs/${expectedCid}`)
      expect(await ipfsPathValidator.resolveToCid(url)).to.equal(expectedCid)
    })
    // TODO: remove when https://github.com/ipfs/js-ipfs/issues/1918 is addressed
    it('should resolve URL of a DNSLink website if DNSLink is in cache (js-ipfs fallback)', async function () {
      const url = 'https://docs.ipfs.io/guides/concepts/dnslink/?argTest#hashTest'
      // Use IPNS in DNSLINK to ensure resolveToImmutableIpfsPath does resursive resolv to immutable address
      const expectedCid = 'QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR'
      const dnslinkValue = '/ipns/QmRV5iNhGoxBaAcbucMAW9WtVHbeehXhAdr5CZQDhL55Xk'
      const { hostname } = new URL(url)
      spoofCachedDnslink(hostname, dnslinkResolver, dnslinkValue)
      // Note the DNSLink value is ignored, and /ipns/<fqdn> is passed to ipfs.resolv internally
      // This ensures the latest pointer is returned, instead of stale value from DNSLink cache
      // js-ipfs v0.34 does not support DNSLinks in ipfs.name.resolve: https://github.com/ipfs/js-ipfs/issues/1918
      const resolve = stub(ipfs, 'resolve')
      resolve.withArgs('/ipns/docs.ipfs.io/guides/concepts/dnslink/').throws(new Error('resolve non-IPFS names is not implemented'))
      // until it is implemented, we have a workaround that falls back to value from dnslink
      resolve.withArgs('/ipns/QmRV5iNhGoxBaAcbucMAW9WtVHbeehXhAdr5CZQDhL55Xk/guides/concepts/dnslink/').resolves(`/ipfs/${expectedCid}`)
      resolve.throws((arg) => new Error(`Unexpected stubbed call ipfs.resolve(${arg})`))
      expect(await ipfsPathValidator.resolveToCid(url)).to.equal(expectedCid)
    })
    it('should resolve to null if input is an invalid path', async function () {
      const path = '/foo/bar/?argTest#hashTest'
      expect(await ipfsPathValidator.resolveToCid(path)).to.equal(null)
    })
    it('should resolve to null if input is an invalid URL', async function () {
      const url = 'example.com/foo/bar/?argTest#hashTest'
      expect(await ipfsPathValidator.resolveToCid(url)).to.equal(null)
    })
  })
})


text:
'use strict'
import { describe, it, before, beforeEach, after } from 'mocha'
import sinon from 'sinon'
import { expect } from 'chai'
import { URL } from 'url'
import browser from 'sinon-chrome'
import { initState } from '../../../add-on/src/lib/state.js'
import createRuntimeChecks from '../../../add-on/src/lib/runtime-checks.js'
import { createRequestModifier, redirectOptOutHint } from '../../../add-on/src/lib/ipfs-request.js'
import createDnslinkResolver from '../../../add-on/src/lib/dnslink.js'
import { createIpfsPathValidator } from '../../../add-on/src/lib/ipfs-path.js'
import { optionDefaults } from '../../../add-on/src/lib/options.js'

const url2request = (string) => {
  return { url: string, type: 'main_frame' }
}

const fakeRequestId = () => {
  return Math.floor(Math.random() * 100000).toString()
}

const expectNoRedirect = (modifyRequest, request) => {
  expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
  expect(modifyRequest.onHeadersReceived(request)).to.equal(undefined)
}

const nodeTypes = ['external', 'embedded']

describe('modifyRequest.onBeforeRequest:', function () {
  let state, dnslinkResolver, ipfsPathValidator, modifyRequest, runtime

  before(function () {
    global.URL = URL
    global.browser = browser
    browser.runtime.id = 'testid'
    browser.runtime.getURL.returns('chrome-extension://testid/')
  })

  beforeEach(async function () {
    state = Object.assign(initState(optionDefaults), {
      ipfsNodeType: 'external',
      peerCount: 1,
      redirect: true,
      dnslinkPolicy: false, // dnslink testi suite is in ipfs-request-dnslink.test.js
      catchUnhandledProtocols: true,
      gwURLString: 'http://localhost:8080',
      gwURL: new URL('http://localhost:8080'),
      pubGwURLString: 'https://ipfs.io',
      pubGwURL: new URL('https://ipfs.io'),
      pubSubdomainGwURLString: 'https://dweb.link',
      pubSubdomainGwURL: new URL('https://dweb.link')
    })
    const getState = () => state
    const getIpfs = () => {}
    dnslinkResolver = createDnslinkResolver(getState)
    runtime = Object.assign({}, await createRuntimeChecks(browser)) // make it mutable for tests
    ipfsPathValidator = createIpfsPathValidator(getState, getIpfs, dnslinkResolver)
    modifyRequest = createRequestModifier(getState, dnslinkResolver, ipfsPathValidator, runtime)
  })

  describe('request for a path matching /ipfs/{CIDv0}', function () {
    describe('with external node', function () {
      beforeEach(function () {
        state.ipfsNodeType = 'external'
      })
      it('should be served from custom gateway if redirect is enabled', function () {
        const request = url2request('https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
        expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('http://localhost:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
      })
    })
    describe('with embedded node', function () {
      beforeEach(function () {
        state.ipfsNodeType = 'embedded'
      })
      it('should be served from public gateway if redirect is enabled', function () {
        const request = url2request('https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
        expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
      })
    })
    describe('with every node type', function () {
      // tests in which results should be the same for all node types
      nodeTypes.forEach(function (nodeType) {
        beforeEach(function () {
          state.ipfsNodeType = nodeType
        })
        it(`should be left untouched if redirect is disabled (${nodeType} node)`, function () {
          state.redirect = false
          const request = url2request('https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
          expectNoRedirect(modifyRequest, request)
        })
        it(`should be left untouched if redirect is enabled but global active flag is OFF (${nodeType} node)`, function () {
          state.active = false
          state.redirect = true
          const request = url2request('https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
          expectNoRedirect(modifyRequest, request)
        })
        it(`should be left untouched if URL includes opt-out hint (${nodeType} node)`, function () {
          // A safe way for preloading data at arbitrary gateways - it should arrive at original destination
          const request = url2request('https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?x-ipfs-companion-no-redirect#hashTest')
          expectNoRedirect(modifyRequest, request)
          expect(redirectOptOutHint).to.equal('x-ipfs-companion-no-redirect')
        })
        it(`should be left untouched if request is for subresource on a page loaded from URL that includes opt-out hint (${nodeType} node)`, function () {
          // ensure opt-out works for subresources (Firefox only for now)
          const subRequest = {
            type: 'script',
            url: 'https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest',
            originUrl: 'https://example.com/?x-ipfs-companion-no-redirect#hashTest'
          }
          expectNoRedirect(modifyRequest, subRequest)
        })
        it(`should be left untouched if CID is invalid (${nodeType} node)`, function () {
          const request = url2request('https://google.com/ipfs/notacid?argTest#hashTest')
          expectNoRedirect(modifyRequest, request)
        })
        it(`should be left untouched if its is a HEAD preload with explicit opt-out in URL hash (${nodeType} node)`, function () {
          // HTTP HEAD is a popular way for preloading data at arbitrary gateways, so we have a dedicated test to make sure it works as expected
          const headRequest = { url: 'https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#x-ipfs-companion-no-redirect', method: 'HEAD' }
          expectNoRedirect(modifyRequest, headRequest)
        })
      })
    })
  })

  describe('XHR request for a path matching /ipfs/{CIDv0} coming from 3rd party Origin', function () {
    describe('with external node', function () {
      beforeEach(function () {
        state.ipfsNodeType = 'external'
      })
      it('should be served from custom gateway if fetched from the same origin and redirect is enabled in Firefox', function () {
        runtime.isFirefox = true
        const xhrRequest = { url: 'https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest', type: 'xmlhttprequest', originUrl: 'https://google.com/' }
        expect(modifyRequest.onBeforeRequest(xhrRequest).redirectUrl).to.equal('http://127.0.0.1:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
      })
      it('should be served from custom gateway if fetched from the same origin and redirect is enabled in Chromium', function () {
        runtime.isFirefox = false
        const xhrRequest = { url: 'https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest', type: 'xmlhttprequest', initiator: 'https://google.com/' }
        expect(modifyRequest.onBeforeRequest(xhrRequest).redirectUrl).to.equal('http://127.0.0.1:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
      })
      it('should be served from custom gateway if XHR is cross-origin and redirect is enabled in Chromium', function () {
        runtime.isFirefox = false
        const xhrRequest = { url: 'https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest', type: 'xmlhttprequest', initiator: 'https://www.nasa.gov/foo.html', requestId: fakeRequestId() }
        expect(modifyRequest.onBeforeRequest(xhrRequest).redirectUrl).to.equal('http://127.0.0.1:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
      })
      it('should be served from custom gateway if XHR is cross-origin and redirect is enabled in Firefox', function () {
        runtime.isFirefox = true
        const xhrRequest = { url: 'https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest', type: 'xmlhttprequest', originUrl: 'https://www.nasa.gov/foo.html', requestId: fakeRequestId() }
        expect(modifyRequest.onBeforeRequest(xhrRequest).redirectUrl).to.equal('http://127.0.0.1:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
      })
    })
    describe('with embedded node', function () {
      beforeEach(function () {
        state.ipfsNodeType = 'embedded'
      })
      it('should be served from public gateway if fetched from the same origin and redirect is enabled in Firefox', function () {
        runtime.isFirefox = true
        const xhrRequest = { url: 'https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest', type: 'xmlhttprequest', originUrl: 'https://google.com/' }
        expect(modifyRequest.onBeforeRequest(xhrRequest).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
      })
      it('should be served from public gateway if fetched from the same origin and redirect is enabled in Chromium', function () {
        runtime.isFirefox = false
        const xhrRequest = { url: 'https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest', type: 'xmlhttprequest', initiator: 'https://google.com/' }
        expect(modifyRequest.onBeforeRequest(xhrRequest).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
      })
      it('should be served from public gateway if XHR is cross-origin and redirect is enabled in Chromium', function () {
        runtime.isFirefox = false
        const xhrRequest = { url: 'https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest', type: 'xmlhttprequest', initiator: 'https://www.nasa.gov/foo.html', requestId: fakeRequestId() }
        expect(modifyRequest.onBeforeRequest(xhrRequest).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
      })
      it('should be served from public gateway if XHR is cross-origin and redirect is enabled in Firefox', function () {
        runtime.isFirefox = true
        const xhrRequest = { url: 'https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest', type: 'xmlhttprequest', originUrl: 'https://www.nasa.gov/foo.html', requestId: fakeRequestId() }
        expect(modifyRequest.onBeforeRequest(xhrRequest).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
      })
    })
    describe('with external node when runtime.requiresXHRCORSfix', function () {
      beforeEach(function () {
        state.ipfsNodeType = 'external'
        browser.runtime.getBrowserInfo = () => Promise.resolve({ name: 'Firefox', version: '68.0.0' })
      })
      it('should be served from custom gateway if fetched from the same origin and redirect is enabled in Firefox', function () {
        runtime.isFirefox = true
        const xhrRequest = { url: 'https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest', type: 'xmlhttprequest', originUrl: 'https://google.com/' }
        expect(modifyRequest.onBeforeRequest(xhrRequest).redirectUrl).to.equal('http://127.0.0.1:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
      })
      it('should be served from custom gateway if fetched from the same origin and redirect is enabled in non-Firefox', function () {
        runtime.isFirefox = false
        const xhrRequest = { url: 'https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest', type: 'xmlhttprequest', initiator: 'https://google.com/' }
        expect(modifyRequest.onBeforeRequest(xhrRequest).redirectUrl).to.equal('http://127.0.0.1:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
      })
      it('should be served from custom gateway if XHR is cross-origin and redirect is enabled in non-Firefox', function () {
        runtime.isFirefox = false
        const xhrRequest = { url: 'https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest', type: 'xmlhttprequest', initiator: 'https://www.nasa.gov/foo.html', requestId: fakeRequestId() }
        expect(modifyRequest.onBeforeRequest(xhrRequest).redirectUrl).to.equal('http://127.0.0.1:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
      })
      it('should be served from custom gateway via late redirect in onHeadersReceived if XHR is cross-origin and redirect is enabled in Firefox', function () {
        // Context for CORS XHR problems in Firefox: https://github.com/ipfs-shipyard/ipfs-companion/issues/436
        runtime.isFirefox = true
        const xhrRequest = { url: 'https://google.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest', type: 'xmlhttprequest', originUrl: 'https://www.nasa.gov/foo.html', requestId: fakeRequestId() }
        // onBeforeRequest should not change anything, as it will trigger false-positive CORS error
        expect(modifyRequest.onBeforeRequest(xhrRequest)).to.equal(undefined)
        // onHeadersReceived is after CORS validation happens, so its ok to cancel and redirect late
        expect(modifyRequest.onHeadersReceived(xhrRequest).redirectUrl).to.equal('http://127.0.0.1:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
      })
    })
  })

  describe('request for a path matching /ipns/{path}', function () {
    describe('with external node', function () {
      beforeEach(function () {
        state.ipfsNodeType = 'external'
      })
      it('should be served from custom gateway if {path} points to a FQDN with existing dnslink', function () {
        const request = url2request('https://google.com/ipns/en.wikipedia-on-ipfs.org/index.html?argTest#hashTest')
        // stub the existence of valid dnslink
        const fqdn = 'en.wikipedia-on-ipfs.org'
        dnslinkResolver.readDnslinkFromTxtRecord = sinon.stub().withArgs(fqdn).returns('/ipfs/Qmazvovg6Sic3m9igZMKoAPjkiVZsvbWWc8ZvgjjK1qMss')
        // pretend API is online and we can do dns lookups with it
        state.peerCount = 1
        expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('http://localhost:8080/ipns/en.wikipedia-on-ipfs.org/index.html?argTest#hashTest')
      })
      it('should be served from custom gateway if {path} starts with a valid PeerID', function () {
        const request = url2request('https://google.com/ipns/QmSWnBwMKZ28tcgMFdihD8XS7p6QzdRSGf71cCybaETSsU/index.html?argTest#hashTest')
        dnslinkResolver.readDnslinkFromTxtRecord = sinon.stub().returns(false)
        expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('http://localhost:8080/ipns/QmSWnBwMKZ28tcgMFdihD8XS7p6QzdRSGf71cCybaETSsU/index.html?argTest#hashTest')
      })
    })

    describe('with embedded node', function () {
      beforeEach(function () {
        state.ipfsNodeType = 'embedded'
      })
      it('should be served from public gateway if {path} points to a FQDN with existing dnslink', function () {
        const request = url2request('https://google.com/ipns/en.wikipedia-on-ipfs.org/index.html?argTest#hashTest')
        // stub the existence of valid dnslink
        const fqdn = 'en.wikipedia-on-ipfs.org'
        dnslinkResolver.readDnslinkFromTxtRecord = sinon.stub().withArgs(fqdn).returns('/ipfs/Qmazvovg6Sic3m9igZMKoAPjkiVZsvbWWc8ZvgjjK1qMss')
        // pretend API is online and we can do dns lookups with it
        state.peerCount = 1
        expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/en.wikipedia-on-ipfs.org/index.html?argTest#hashTest')
      })
      it('should be served from public gateway if {path} starts with a valid PeerID', function () {
        const request = url2request('https://google.com/ipns/QmSWnBwMKZ28tcgMFdihD8XS7p6QzdRSGf71cCybaETSsU/index.html?argTest#hashTest')
        dnslinkResolver.readDnslinkFromTxtRecord = sinon.stub().returns(false)
        expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/QmSWnBwMKZ28tcgMFdihD8XS7p6QzdRSGf71cCybaETSsU/index.html?argTest#hashTest')
      })
    })

    describe('with every node type', function () {
      // tests in which results should be the same for all node types
      nodeTypes.forEach(function (nodeType) {
        beforeEach(function () {
          state.ipfsNodeType = nodeType
        })
        it(`should be left untouched if redirect is disabled' (${nodeType} node)`, function () {
          state.redirect = false
          const request = url2request('https://google.com/ipns/ipfs.io?argTest#hashTest')
          expectNoRedirect(modifyRequest, request)
        })
        it(`should be left untouched if FQDN is not a real domain nor a valid CID (${nodeType} node)`, function () {
          const request = url2request('https://google.com/ipns/notafqdnorcid?argTest#hashTest')
          dnslinkResolver.readDnslinkFromTxtRecord = sinon.stub().returns(false)
          expectNoRedirect(modifyRequest, request)
        })
      })
    })
  })

  describe('request to a subdomain gateway', function () {
    const cid = 'bafybeigxjv2o4jse2lajbd5c7xxl5rluhyqg5yupln42252e5tcao7hbge'
    const peerid = 'bafzbeigxjv2o4jse2lajbd5c7xxl5rluhyqg5yupln42252e5tcao7hbge'

    // Tests use different CID in X-Ipfs-Path header just to ensure it does not
    // override the one from path
    const fakeXIpfsPathHdrVal = '/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ'

    describe('with external node', function () {
      beforeEach(function () {
        state.ipfsNodeType = 'external'
        // dweb.link is the default subdomain gw
      })
      it('should be redirected to localhost gateway (*.ipfs on default gw)', function () {
        state.redirect = true
        const request = url2request(`https://${cid}.ipfs.dweb.link/`)

        // X-Ipfs-Path to ensure value from URL takes a priority
        request.responseHeaders = [{ name: 'X-Ipfs-Path', value: fakeXIpfsPathHdrVal }]

        /// We expect redirect to path-based gateway because go-ipfs >=0.5 will
        // return redirect to a subdomain, and we don't want to break users
        // running older versions of go-ipfs by loading subdomain first and
        // failing.
        expect(modifyRequest.onBeforeRequest(request).redirectUrl)
          .to.equal(`http://localhost:8080/ipfs/${cid}/`)
      })
      it('should be redirected to localhost gateway (*.ipfs on 3rd party gw)', function () {
        state.redirect = true
        const request = url2request(`https://${cid}.ipfs.cf-ipfs.com/`)
        request.responseHeaders = [{ name: 'X-Ipfs-Path', value: fakeXIpfsPathHdrVal }]
        expect(modifyRequest.onBeforeRequest(request).redirectUrl)
          .to.equal(`http://localhost:8080/ipfs/${cid}/`)
      })
      it('should be redirected to localhost gateway and keep URL encoding of original path', function () {
        state.redirect = true
        const request = url2request('https://bafybeigfejjsuq5im5c3w3t3krsiytszhfdc4v5myltcg4myv2n2w6jumy.ipfs.dweb.link/%3Ffilename=test.jpg?arg=val')
        request.responseHeaders = [{ name: 'X-Ipfs-Path', value: fakeXIpfsPathHdrVal }]
        expect(modifyRequest.onBeforeRequest(request).redirectUrl)
          .to.equal('http://localhost:8080/ipfs/bafybeigfejjsuq5im5c3w3t3krsiytszhfdc4v5myltcg4myv2n2w6jumy/%3Ffilename=test.jpg?arg=val')
      })
      it('should be redirected to localhost gateway (*.ipns on default gw)', function () {
        state.redirect = true
        const request = url2request(`https://${peerid}.ipns.dweb.link/`)
        request.responseHeaders = [{ name: 'X-Ipfs-Path', value: '/ipns/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ' }]
        expect(modifyRequest.onBeforeRequest(request).redirectUrl)
          .to.equal(`http://localhost:8080/ipns/${peerid}/`)
      })
    })

    describe('with embedded node', function () {
      beforeEach(function () {
        state.ipfsNodeType = 'embedded'
        // dweb.link is the default subdomain gw
      })
      it('should be left untouched for *.ipfs at default public subdomain gw', function () {
        state.redirect = true
        const request = url2request(`https://${cid}.ipfs.dweb.link/`)
        request.responseHeaders = [{ name: 'X-Ipfs-Path', value: fakeXIpfsPathHdrVal }]
        expectNoRedirect(modifyRequest, request)
      })
      it('should be redirected to user-prefered public gateway if 3rd party subdomain gw', function () {
        state.redirect = true
        const request = url2request(`https://${cid}.ipfs.cf-ipfs.com/`)
        request.responseHeaders = [{ name: 'X-Ipfs-Path', value: fakeXIpfsPathHdrVal }]
        expect(modifyRequest.onBeforeRequest(request).redirectUrl)
          .to.equal(`https://${cid}.ipfs.dweb.link/`)
      })
      it('should be left untouched for *.ipns at default public subdomain gw', function () {
        state.redirect = true
        const request = url2request(`https://${peerid}.ipns.dweb.link/`)
        request.responseHeaders = [{ name: 'X-Ipfs-Path', value: '/ipns/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ' }]
        expectNoRedirect(modifyRequest, request)
      })
    })
  })

  // tests in which results should be the same for all node types
  nodeTypes.forEach(function (nodeType) {
    beforeEach(function () {
      state.ipfsNodeType = nodeType
      state.redirect = true
    })
    describe(`with ${nodeType} node:`, function () {
      describe('request for IPFS path at the localhost', function () {
        // we do not touch local requests, as it may interfere with other nodes running at the same machine
        // or could produce false-positives such as redirection from localhost:5001/ipfs/path to localhost:8080/ipfs/path
        it('should fix localhost Kubo RPC hostname to IP', function () {
          const request = url2request('http://localhost:5001/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ/')
          // expectNoRedirect(modifyRequest, request)
          expect(modifyRequest.onBeforeRequest(request).redirectUrl)
            .to.equal('http://127.0.0.1:5001/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ/')
        })
        it('should be left untouched if localhost Gateway is used', function () {
          const request = url2request('http://localhost:8080/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ/')
          expectNoRedirect(modifyRequest, request)
        })
        it('should fix 127.0.0.1 Gateway to localhost', function () {
          const request = url2request('http://127.0.0.1:8080/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ/')
          // expectNoRedirect(modifyRequest, request)
          expect(modifyRequest.onBeforeRequest(request).redirectUrl)
            .to.equal('http://localhost:8080/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ/')
        })
        it('should fix 0.0.0.0 to localhost IP API', function () {
          // https://github.com/ipfs-shipyard/ipfs-companion/issues/867
          const request = url2request('http://0.0.0.0:5001/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ/')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl)
            .to.equal('http://127.0.0.1:5001/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ/')
        })
        it('should be left untouched if /webui on localhost Kubo RPC port', function () {
          // https://github.com/ipfs/ipfs-companion/issues/291
          const request = url2request('http://localhost:5001/webui')
          // expectNoRedirect(modifyRequest, request)
          expect(modifyRequest.onBeforeRequest(request).redirectUrl)
            .to.equal('http://127.0.0.1:5001/webui')
        })
        it('should be left untouched if localhost Kubo RPC IP is used, even when x-ipfs-path is present', function () {
          // https://github.com/ipfs-shipyard/ipfs-companion/issues/604
          const request = url2request('http://127.0.0.1:5001/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ/')
          request.responseHeaders = [{ name: 'X-Ipfs-Path', value: '/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DDIFF' }]
          expectNoRedirect(modifyRequest, request)
        })
        it('should be left untouched if [::1] is used', function () {
          // https://github.com/ipfs/ipfs-companion/issues/291
          const request = url2request('http://[::1]:5001/ipfs/QmPhnvn747LqwPYMJmQVorMaGbMSgA7mRRoyyZYz3DoZRQ/')
          expectNoRedirect(modifyRequest, request)
        })
        it('should be redirected to localhost (subdomain in go-ipfs >0.5) if type=main_frame and  127.0.0.1 (path gw) is used un URL', function () {
          state.redirect = true
          state.useSubdomains = true
          expect(state.gwURL.hostname).to.equal('localhost')
          const cid = 'QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR'
          const request = url2request(`http://127.0.0.1:8080/ipfs/${cid}?arg=val#hash`)
          request.type = 'main_frame' // explicit
          expect(modifyRequest.onBeforeRequest(request).redirectUrl)
            .to.equal(`http://localhost:8080/ipfs/${cid}?arg=val#hash`)
        })
      })
    })
  })

  describe('request for IPFS Path with redirect to custom gateway at port 80', function () {
    beforeEach(function () {
      state.ipfsNodeType = 'external'
      state.redirect = true
    })
    it('should work for HTTP GW without explicit port in URL', function () {
      state.gwURLString = 'http://foo:80/'
      state.gwURL = new URL(state.gwURLString)
      const request = url2request('https://bar.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
      expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('http://foo/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
    })
    it('should work for HTTPS GW without explicit port in URL', function () {
      state.gwURLString = 'https://foo:443/'
      state.gwURL = new URL(state.gwURLString)
      const request = url2request('https://bar.com/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
      expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://foo/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
    })
  })

  describe('Recovers Page if node is unreachable', function () {
    beforeEach(function () {
      global.browser = browser
      state.ipfsNodeType = 'external'
      state.redirect = true
      state.peerCount = -1 // this simulates Kubo RPC being offline
      state.gwURLString = 'http://localhost:8080'
      state.gwURL = new URL('http://localhost:8080')
      state.pubGwURLString = 'https://ipfs.io'
      state.pubGwURL = new URL('https://ipfs.io')
    })
    it('should present recovery page if node is offline and redirect is enabled', function () {
      expect(state.nodeActive).to.be.equal(false)
      state.redirect = true
      const request = url2request('https://localhost:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar')
      expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('chrome-extension://testid/dist/recovery/recovery.html#https%3A%2F%2Fipfs.io%2Fipfs%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%2Ffoo%2Fbar')
    })
    it('should present recovery page if node is offline and redirect is disabled', function () {
      expect(state.nodeActive).to.be.equal(false)
      state.redirect = false
      const request = url2request('https://localhost:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar')
      expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('chrome-extension://testid/dist/recovery/recovery.html#https%3A%2F%2Fipfs.io%2Fipfs%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%2Ffoo%2Fbar')
    })
    it('should not show recovery page if node is offline, redirect is enabled, but non-gateway URL failed to load from the same port', function () {
      // this covers https://github.com/ipfs/ipfs-companion/issues/1162 and https://twitter.com/unicomp21/status/1626244123102679041
      state.redirect = true
      expect(state.nodeActive).to.be.equal(false)
      const request = url2request('https://localhost:8080/')
      expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
    })
    it('should not show recovery page if extension is disabled', function () {
      // allows user to quickly avoid anything similar to https://github.com/ipfs/ipfs-companion/issues/1162
      state.active = false
      expect(state.nodeActive).to.be.equal(false)
      const request = url2request('https://localhost:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar')
      expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
    })
  })

  after(function () {
    delete global.URL
    delete global.browser
    browser.flush()
  })
})

repo_name: ipfs/ipfs-companion

repo_language: JavaScript

mime_type: text/x-diff


text:
'use strict'
import { describe, it, before, beforeEach, after, afterEach } from 'mocha'
import sinon from 'sinon'
import { assert } from 'chai'
import { URL } from 'url'
import browser from 'sinon-chrome'
import { initState } from '../../../add-on/src/lib/state.js'
import createRuntimeChecks from '../../../add-on/src/lib/runtime-checks.js'
import { createRequestModifier } from '../../../add-on/src/lib/ipfs-request.js'
import createDnslinkResolver from '../../../add-on/src/lib/dnslink.js'
import { createIpfsPathValidator } from '../../../add-on/src/lib/ipfs-path.js'
import { optionDefaults } from '../../../add-on/src/lib/options.js'

browser.runtime.id = 'testid'

const url2request = (url, type = 'main_frame', tabId = new Date().valueOf()) => {
  return { url, type, tabId }
}
const urlRequestWithStatus = (url, statusCode = 200, type = 'main_frame') => {
  return { ...url2request(url, type), statusCode }
}

const urlRequestWithNetworkError = (url, error = 'net::ERR_CONNECTION_TIMED_OUT', type = 'main_frame') => {
  return { ...url2request(url, type), error }
}

describe('requestHandler.onCompleted:', function () { // HTTP-level errors
  let state, dnslinkResolver, ipfsPathValidator, requestHandler, runtime

  before(function () {
    global.URL = URL
    browser.tabs = { ...browser.tabs, getCurrent: sinon.stub().resolves({ id: 20 }) }
    global.browser = browser
    browser.runtime.id = 'testid'
  })

  beforeEach(async function () {
    state = initState(optionDefaults)
    const getState = () => state
    const getIpfs = () => {}
    dnslinkResolver = createDnslinkResolver(getState)
    runtime = Object.assign({}, await createRuntimeChecks(browser)) // make it mutable for tests
    ipfsPathValidator = createIpfsPathValidator(getState, getIpfs, dnslinkResolver)
    requestHandler = createRequestModifier(getState, dnslinkResolver, ipfsPathValidator, runtime)
  })

  describe('with recoverFailedHttpRequests=true', function () {
    beforeEach(function () {
      state.recoverFailedHttpRequests = true
      state.dnslinkPolicy = false
    })
    it('should do nothing if broken request is for the default subdomain gateway', async function () {
      const request = urlRequestWithStatus('https://QmYzZgeWE7r8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h.ipfs.dweb.link/wiki/', 500)
      await requestHandler.onCompleted(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
    })
    it('should redirect to default subdomain gateway on broken subdomain gateway request', async function () {
      const request = urlRequestWithStatus('http://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq.ipfs.brokenexample.com/wiki/', 500)
      await requestHandler.onCompleted(request)
      assert.ok(browser.tabs.update.withArgs(request.tabId, { url: 'https://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq.ipfs.dweb.link/wiki/', active: true }).calledOnce, 'tabs.update should be called with default subdomain gateway URL')
    })
    it('should do nothing if broken request is a non-IPFS request', async function () {
      const request = urlRequestWithStatus('https://wikipedia.org', 500)
      await requestHandler.onCompleted(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
    })
    it('should do nothing if broken request is a local request to 127.0.0.1/ipfs', async function () {
      const request = urlRequestWithStatus('http://127.0.0.1:8080/ipfs/QmYzZgeWE7r8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 500)
      await requestHandler.onCompleted(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
    })
    it('should do nothing if broken request is a local request to localhost/ipfs', async function () {
      const request = urlRequestWithStatus('http://localhost:8080/ipfs/QmYzZgeWE7r8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 500)
      await requestHandler.onCompleted(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
    })
    it('should do nothing if broken request is a local request to *.ipfs.localhost', async function () {
      const request = urlRequestWithStatus('http://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhw.ipfs.localhost:8080/', 500)
      await requestHandler.onCompleted(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
    })
    it('should do nothing if broken request is to the default public gateway', async function () {
      const request = urlRequestWithStatus('https://ipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 500)
      await requestHandler.onCompleted(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
    })
    it('should do nothing if broken request is not a \'main_frame\' request', async function () {
      const request = urlRequestWithStatus('https://nondefaultipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 500, 'stylesheet')
      await requestHandler.onCompleted(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
    })
    it('should recover from unreachable third party public gateway by reopening on the public gateway', async function () {
      const request = urlRequestWithStatus('https://nondefaultipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 500)
      await requestHandler.onCompleted(request)
      assert.ok(browser.tabs.update.withArgs(request.tabId, { url: 'https://ipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', active: true }).calledOnce, 'tabs.update should be called with IPFS default public gateway URL')
    })
  })

  describe('with recoverFailedHttpRequests=false', function () {
    beforeEach(function () {
      state.recoverFailedHttpRequests = false
      state.dnslinkPolicy = false
    })
    it('should do nothing on failed subdomain gateway request', async function () {
      const request = urlRequestWithStatus('https://QmYzZgeWE7r8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h.ipfs.brokendomain.com/wiki/', 500)
      await requestHandler.onCompleted(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
    })
    it('should do nothing on broken non-default public gateway IPFS request', async function () {
      const request = urlRequestWithStatus('https://nondefaultipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 500)
      await requestHandler.onCompleted(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
    })
  })

  afterEach(function () {
    browser.tabs.update.reset()
  })

  after(function () {
    delete global.url
    delete global.browser
    browser.flush()
  })
})

describe('requestHandler.onErrorOccurred:', function () { // network errors
  let state, dnslinkResolver, ipfsPathValidator, requestHandler, runtime

  before(function () {
    global.URL = URL
    browser.tabs = { ...browser.tabs, query: sinon.stub().resolves([{ id: 20 }]) }
    global.browser = browser
  })

  beforeEach(async function () {
    state = initState(optionDefaults)
    const getState = () => state
    const getIpfs = () => {}
    dnslinkResolver = createDnslinkResolver(getState)
    runtime = Object.assign({}, await createRuntimeChecks(browser)) // make it mutable for tests
    ipfsPathValidator = createIpfsPathValidator(getState, getIpfs, dnslinkResolver)
    requestHandler = createRequestModifier(getState, dnslinkResolver, ipfsPathValidator, runtime)
  })

  describe('with recoverFailedHttpRequests=true', function () {
    beforeEach(function () {
      state.recoverFailedHttpRequests = true
      state.dnslinkPolicy = false
    })
    it('should do nothing if failed request is for the default subdomain gateway', async function () {
      const request = urlRequestWithStatus('https://QmYzZgeWE7r8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h.ipfs.dweb.link/wiki/', 500)
      await requestHandler.onErrorOccurred(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
    })
    it('should redirect to default subdomain gateway on failed subdomain gateway request', async function () {
      const request = urlRequestWithStatus('http://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq.ipfs.brokenexample.com/wiki/', 500)
      await requestHandler.onErrorOccurred(request)
      assert.ok(browser.tabs.update.withArgs(request.tabId, { url: 'https://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq.ipfs.dweb.link/wiki/', active: true }).calledOnce, 'tabs.update should be called with default subdomain gateway URL')
    })
    it('should do nothing if failed request is a non-IPFS request', async function () {
      const request = urlRequestWithNetworkError('https://wikipedia.org')
      await requestHandler.onErrorOccurred(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
    })
    it('should do nothing if failed request is a non-public IPFS request', async function () {
      const request = urlRequestWithNetworkError('http://127.0.0.1:8080/ipfs/QmYzZgeWE7r8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h')
      await requestHandler.onErrorOccurred(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
    })
    it('should do nothing if failed request is to the default public gateway', async function () {
      const request = urlRequestWithNetworkError('https://ipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h')
      await requestHandler.onErrorOccurred(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
    })
    it('should do nothing if failed request is not a \'main_frame\' request', async function () {
      const requestType = 'stylesheet'
      const request = urlRequestWithNetworkError('https://nondefaultipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 'net::ERR_NAME_NOT_RESOLVED', requestType)
      await requestHandler.onErrorOccurred(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
    })
    it('should recover from unreachable third party public gateway by reopening on the public gateway', async function () {
      const request = urlRequestWithNetworkError('https://nondefaultipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h')
      await requestHandler.onErrorOccurred(request)
      assert.ok(browser.tabs.update.withArgs(request.tabId, { url: 'https://ipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', active: true }).calledOnce, 'tabs.update should be called with IPFS default public gateway URL')
    })
    it('should recover from unreachable HTTP server by reopening DNSLink on the active gateway', async function () {
      state.dnslinkPolicy = 'best-effort'
      dnslinkResolver.setDnslink('en.wikipedia-on-ipfs.org', '/ipfs/QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco')
      // avoid DNS failures when recovering to local gateweay (if available)
      const expectedUrl = 'http://localhost:8080/ipns/en.wikipedia-on-ipfs.org/'
      const request = urlRequestWithNetworkError('https://en.wikipedia-on-ipfs.org/')
      await requestHandler.onErrorOccurred(request)
      assert.ok(browser.tabs.update.withArgs(request.tabId, { url: expectedUrl, active: true }).calledOnce, 'tabs.update should be called with DNSLink on local gateway URL')
      dnslinkResolver.clearCache()
    })
    it('should recover from failed DNS for .eth opening it on EthDNS gateway at .eth.link', async function () {
      state.dnslinkPolicy = 'best-effort'
      dnslinkResolver.setDnslink('almonit.eth', false)
      dnslinkResolver.setDnslink('almonit.eth.link', '/ipfs/QmPH7VMnfFKvrr7kLXNRwuxjYRLWnfcxPvnWs8ipyWAQK2')
      const dnsFailure = 'net::ERR_NAME_NOT_RESOLVED' // chrome code
      const expectedUrl = 'https://almonit.eth.link/'
      const request = urlRequestWithNetworkError('https://almonit.eth', dnsFailure)
      await requestHandler.onErrorOccurred(request)
      assert.ok(browser.tabs.update.withArgs(request.tabId, { url: expectedUrl, active: true }).calledOnce, 'tabs.update should be called with ENS resource on local gateway URL')
      dnslinkResolver.clearCache()
    })
  })

  describe('with recoverFailedHttpRequests=false', function () {
    beforeEach(function () {
      state.recoverFailedHttpRequests = false
      state.dnslinkPolicy = false
    })
    it('should do nothing on unreachable third party public gateway', async function () {
      const request = urlRequestWithNetworkError('https://nondefaultipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h')
      await requestHandler.onErrorOccurred(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
    })
    it('should do nothing on failed subdomain gateway request', async function () {
      const request = urlRequestWithStatus('https://QmYzZgeWE7r8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h.ipfs.brokendomain.com/wiki/', 500)
      await requestHandler.onErrorOccurred(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
    })
    it('should do nothing on unreachable HTTP server with DNSLink', async function () {
      state.dnslinkPolicy = 'best-effort'
      dnslinkResolver.setDnslink('en.wikipedia-on-ipfs.org', '/ipfs/QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco')
      const request = urlRequestWithNetworkError('https://en.wikipedia-on-ipfs.org')
      await requestHandler.onErrorOccurred(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
      dnslinkResolver.clearCache()
    })
    it('should do nothing on failed non-default public gateway IPFS request', async function () {
      state.dnslinkPolicy = 'best-effort'
      dnslinkResolver.setDnslink('almonit.eth', false)
      dnslinkResolver.setDnslink('almonit.eth.link', '/ipfs/QmPH7VMnfFKvrr7kLXNRwuxjYRLWnfcxPvnWs8ipyWAQK2')
      const dnsFailure = 'net::ERR_NAME_NOT_RESOLVED' // chrome code
      const request = urlRequestWithNetworkError('https://almonit.eth', dnsFailure)
      await requestHandler.onErrorOccurred(request)
      assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called')
      dnslinkResolver.clearCache()
    })
  })

  afterEach(function () {
    browser.tabs.update.reset()
  })

  after(function () {
    delete global.url
    delete global.browser
    browser.flush()
  })
})


text:
'use strict'
import { describe, it, before, beforeEach, after } from 'mocha'
import { expect } from 'chai'
import { URL } from 'url'
import browser from 'sinon-chrome'
import { initState } from '../../../add-on/src/lib/state.js'
import createRuntimeChecks from '../../../add-on/src/lib/runtime-checks.js'
import { createRequestModifier } from '../../../add-on/src/lib/ipfs-request.js'
import createDnslinkResolver from '../../../add-on/src/lib/dnslink.js'
import { createIpfsPathValidator } from '../../../add-on/src/lib/ipfs-path.js'
import { optionDefaults } from '../../../add-on/src/lib/options.js'

const url2request = (string) => {
  return { url: string, type: 'main_frame' }
}

const nodeTypes = ['external', 'embedded']

describe('modifyRequest.onBeforeRequest:', function () {
  let state, dnslinkResolver, ipfsPathValidator, modifyRequest, runtime

  before(function () {
    global.URL = URL
    global.browser = browser
    browser.runtime.id = 'testid'
  })

  beforeEach(async function () {
    state = Object.assign(initState(optionDefaults), {
      ipfsNodeType: 'external',
      peerCount: 1,
      redirect: true,
      dnslinkPolicy: false, // dnslink test suite is in ipfs-request-dnslink.test.js
      catchUnhandledProtocols: true,
      gwURLString: 'http://127.0.0.1:8080',
      pubGwURLString: 'https://ipfs.io'
    })
    const getState = () => state
    const getIpfs = () => {}
    dnslinkResolver = createDnslinkResolver(getState)
    runtime = Object.assign({}, await createRuntimeChecks(browser)) // make it mutable for tests
    ipfsPathValidator = createIpfsPathValidator(getState, getIpfs, dnslinkResolver)
    modifyRequest = createRequestModifier(getState, dnslinkResolver, ipfsPathValidator, runtime)
  })

  // tests in which results should be the same for all node types
  nodeTypes.forEach(function (nodeType) {
    beforeEach(function () {
      state.ipfsNodeType = nodeType
    })
    describe(`with ${nodeType} node:`, function () {
      describe('request made via redirect-based protocol handler from manifest.json/protocol_handlers', function () {
        // Note: requests done with custom protocol handler are always  normalized to public gateway
        // (if external node is enabled, redirect will happen in next iteration of modifyRequest)
        beforeEach(function () {
          // ..however to make tests easier we disable redirect from public to custom gateway
          // (with this modifyRequest will return undefined for invalid URIs)
          state.redirect = false
        })

        // without web+ prefix (Firefox > 59: https://github.com/ipfs-shipyard/ipfs-companion/issues/164#issuecomment-356301174)
        it('should not be normalized if ipfs:/{CID}', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=ipfs%3A%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
        it('should be normalized if ipfs://{CID}', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=ipfs%3A%2F%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
        })
        it('should not be normalized if ipns:/{fqdn}', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=ipns%3A%2Fipfs.io%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
        it('should be normalized if ipns://{fqdn}', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=ipns%3A%2F%2Fipfs.io%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/ipfs.io?argTest#hashTest')
        })
        it('should be normalized if ipfs://{fqdn}', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=ipfs%3A%2F%2Fipfs.io%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/ipfs.io?argTest#hashTest')
        })
        it('should be normalized if dweb:/ipfs/{CID}', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=dweb%3A%2Fipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
        })
        it('should not be normalized if dweb://ipfs/{CID}', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=dweb%3A%2F%2Fipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
        it('should be normalized if dweb:/ipns/{foo}', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=dweb%3A%2Fipns/ipfs.io%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).equal('https://ipfs.io/ipns/ipfs.io?argTest#hashTest')
        })
        it('should not be normalized if dweb://ipns/{foo}', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=dweb%3A%2F%2Fipns/ipfs.io%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })

        // web+ prefixed versions (Firefox < 59 and Chrome)
        it('should not be normalized if web+ipfs:/{CID}', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=web%2Bipfs%3A%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
        it('should be normalized if web+ipfs://{CID}', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=web%2Bipfs%3A%2F%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
        })
        it('should not be normalized if web+ipns:/{foo}', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=web%2Bipns%3A%2Fipfs.io%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
        it('should be normalized if web+ipns://{foo}', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=web%2Bipns%3A%2F%2Fipfs.io%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/ipfs.io?argTest#hashTest')
        })
        it('should be normalized if web+dweb:/ipfs/{CID}', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=web%2Bdweb%3A%2Fipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
        })
        it('should not be normalized if web+dweb://ipfs/{CID}', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=web%2Bdweb%3A%2F%2Fipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
        it('should be normalized if web+dweb:/ipns/{foo}', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=web%2Bdweb%3A%2Fipns/ipfs.io%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).equal('https://ipfs.io/ipns/ipfs.io?argTest#hashTest')
        })
        it('should not be normalized if web+dweb://ipns/{foo}', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=web%2Bdweb%3A%2F%2Fipns/ipfs.io%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
        it('should not be normalized if web+{foo}:/bar', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=web%2Bfoo%3A%2Fbar%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
        it('should not be normalized if web+{foo}://bar', function () {
          const request = url2request('https://dweb.link/ipfs/?uri=web%2Bfoo%3A%2F%2Fbar%3FargTest%23hashTest')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
      })

      describe('catching unhandled custom protocol request', function () {
        it('should not be normalized if ipfs:/{CID}', function () {
          const request = url2request('https://duckduckgo.com/?q=ipfs%3A%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest&foo=bar')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
        it('should be normalized if ipfs://{CID}', function () {
          const request = url2request('https://duckduckgo.com/?q=ipfs%3A%2F%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest&foo=bar')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
        })
        it('should not be normalized if ipns:/{foo}', function () {
          const request = url2request('https://duckduckgo.com/?q=ipns%3A%2Fipns.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hashTest')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
        it('should be normalized if ipns://{foo}', function () {
          const request = url2request('https://duckduckgo.com/?q=ipns%3A%2F%2Fipns.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hashTest')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/ipns.io/index.html?arg=foo&bar=buzz#hashTest')
        })
        it('should be normalized if ipfs://{fqdn}', function () {
          const request = url2request('https://duckduckgo.com/?q=ipfs%3A%2F%2Fipns.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hashTest')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/ipns.io/index.html?arg=foo&bar=buzz#hashTest')
        })
        it('should be normalized if dweb:/ipfs/{CID}', function () {
          const request = url2request('https://duckduckgo.com/?q=dweb%3A%2Fipfs%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3Farg%3Dfoo%26bar%3Dbuzz%23hash&ia=software')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?arg=foo&bar=buzz#hash')
        })
        it('should not be normalized if dweb://ipfs/{CID}', function () {
          const request = url2request('https://duckduckgo.com/?q=dweb%3A%2F%2Fipfs%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3Farg%3Dfoo%26bar%3Dbuzz%23hash&ia=software')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
        it('should be normalized if dweb:/ipns/{foo}', function () {
          const request = url2request('https://duckduckgo.com/?q=dweb%3A%2Fipns%2Fipfs.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hash&ia=web')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/ipfs.io/index.html?arg=foo&bar=buzz#hash')
        })
        it('should not be normalized if dweb://ipns/{foo}', function () {
          const request = url2request('https://duckduckgo.com/?q=dweb%3A%2F%2Fipns%2Fipfs.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hash&ia=web')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })

        it('should not be normalized if web+ipfs:/{CID}', function () {
          const request = url2request('https://duckduckgo.com/?q=web%2Bipfs%3A%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest&foo=bar')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
        it('should be normalized if web+ipfs://{CID}', function () {
          const request = url2request('https://duckduckgo.com/?q=web%2Bipfs%3A%2F%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest&foo=bar')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?argTest#hashTest')
        })
        it('should not be normalized if web+ipns:/{foo}', function () {
          const request = url2request('https://duckduckgo.com/?q=web%2Bipns%3A%2Fipns.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hashTest')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
        it('should be normalized if web+ipns://{foo}', function () {
          const request = url2request('https://duckduckgo.com/?q=web%2Bipns%3A%2F%2Fipns.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hashTest')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/ipns.io/index.html?arg=foo&bar=buzz#hashTest')
        })
        it('should be normalized if web+dweb:/ipfs/{CID}', function () {
          const request = url2request('https://duckduckgo.com/?q=web%2Bdweb%3A%2Fipfs%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3Farg%3Dfoo%26bar%3Dbuzz%23hash&ia=software')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?arg=foo&bar=buzz#hash')
        })
        it('should not be normalized if web+dweb://ipfs/{CID}', function () {
          const request = url2request('https://duckduckgo.com/?q=web%2Bdweb%3A%2F%2Fipfs%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3Farg%3Dfoo%26bar%3Dbuzz%23hash&ia=software')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
        it('should be normalized if web+dweb:/ipns/{foo}', function () {
          const request = url2request('https://duckduckgo.com/?q=web%2Bdweb%3A%2Fipns%2Fipfs.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hash&ia=web')
          expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('https://ipfs.io/ipns/ipfs.io/index.html?arg=foo&bar=buzz#hash')
        })
        it('should not be normalized if web+dweb://ipns/{foo}', function () {
          const request = url2request('https://duckduckgo.com/?q=web%2Bdweb%3A%2F%2Fipns%2Fipfs.io%2Findex.html%3Farg%3Dfoo%26bar%3Dbuzz%23hash&ia=web')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })

        it('should not be normalized if disabled in Preferences', function () {
          state.catchUnhandledProtocols = false
          const request = url2request('https://duckduckgo.com/?q=ipfs%3A%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest&foo=bar')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
        it('should not be normalized if CID is invalid', function () {
          state.catchUnhandledProtocols = false
          const request = url2request('https://duckduckgo.com/?q=ipfs%3A%2FnotARealIpfsPathWithCid%3FargTest%23hashTest&foo=bar')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
        it('should not be normalized if presence of %3A%2F is a false-positive', function () {
          state.catchUnhandledProtocols = false
          const request = url2request('https://duckduckgo.com/?q=foo%3A%2Fbar%3FargTest%23hashTest&foo=bar')
          expect(modifyRequest.onBeforeRequest(request)).to.equal(undefined)
        })
        it('should not be normalized if request.type != main_frame', function () {
          const mediaRequest = { url: 'https://duckduckgo.com/?q=ipfs%3A%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest&foo=bar', type: 'media' }
          expect(modifyRequest.onBeforeRequest(mediaRequest)).to.equal(undefined)
        })
      })
    })
  })

  after(function () {
    delete global.URL
    delete global.browser
    browser.flush()
  })
})


text:
'use strict'
import { describe, it, before, after } from 'mocha'
import { expect } from 'chai'
import { useFakeTimers } from 'sinon'
import browser from 'sinon-chrome'
import { formatImportDirectory } from '../../../add-on/src/lib/ipfs-import.js'

describe('ipfs-import.js', function () {
  before(function () {
    browser.runtime.id = 'testid'
    global.document = {}
    global.browser = browser
    // ipfs-import depends on webextension/polyfill which can't be imported
    // in a non-browser environment unless global.browser is stubbed

    // need to force Date to return a particular date
    global.clock = useFakeTimers({
      now: new Date(2017, 10, 5, 12, 1, 1)
    })
  })

  describe('formatImportDirectory', function () {
    it('should change nothing if path is properly formatted and date wildcards are not provided', function () {
      const path = '/ipfs-companion-imports/my-directory/'
      expect(formatImportDirectory(path)).to.equal('/ipfs-companion-imports/my-directory/')
    })

    it('should replace two successive slashes with a single slash', function () {
      const path = '/ipfs-companion-imports//my-directory/'
      expect(formatImportDirectory(path)).to.equal('/ipfs-companion-imports/my-directory/')
    })

    it('should replace multiple slashes with a single slash', function () {
      const path = '/ipfs-companion-imports/////////my-directory/'
      expect(formatImportDirectory(path)).to.equal('/ipfs-companion-imports/my-directory/')
    })

    it('should add trailing slash if not present', function () {
      const path = '/ipfs-companion-imports/my-directory'
      expect(formatImportDirectory(path)).to.equal('/ipfs-companion-imports/my-directory/')
    })

    it('should replace date wildcards with padded dates', function () {
      const path = '/ipfs-companion-imports/%Y-%M-%D_%h%m%s/'
      expect(formatImportDirectory(path)).to.equal('/ipfs-companion-imports/2017-11-05_120101/')
    })
  })
  // TODO: complete tests
  // describe('openFilesAtWebUI', function () {
  // })
  //
  // describe('openFilesAtGateway', function () {
  // })
  //
  // describe('importFiles', function () {
  // })

  after(function () {
    global.browser.flush()
    global.clock.restore()
    delete global.document
    delete global.browser
  })
})