Skip to content

Commit

Permalink
test: port node-fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
ronag committed Aug 5, 2021
1 parent f1e485c commit 611a562
Show file tree
Hide file tree
Showing 15 changed files with 3,861 additions and 134 deletions.
18 changes: 17 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,23 @@ function makeDispatcher (fn) {
module.exports.setGlobalDispatcher = setGlobalDispatcher
module.exports.getGlobalDispatcher = getGlobalDispatcher

module.exports.fetch = makeDispatcher(api.fetch)
const _fetch = makeDispatcher(api.fetch)
module.exports.fetch = async function fetch (...args) {
try {
return await _fetch(...args)
} catch (err) {
// TODO (fix): This is a little weird. Spec compliant?
if (err.code === 'ERR_INVALID_URL') {
const er = new TypeError('Invalid URL')
er.cause = err
throw er
}
throw err
}
}
module.exports.Headers = require('./lib/api/headers')
module.exports.Response = require('./lib/api/response')

module.exports.request = makeDispatcher(api.request)
module.exports.stream = makeDispatcher(api.stream)
module.exports.pipeline = makeDispatcher(api.pipeline)
Expand Down
134 changes: 3 additions & 131 deletions lib/api/api-fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,150 +2,22 @@

'use strict'

const Headers = require('./headers')
const { kHeadersList } = require('../core/symbols')
const Headers = require('./headers')
const Response = require('./response')
const { Readable } = require('stream')
const { METHODS, STATUS_CODES } = require('http')
const { METHODS } = require('http')
const {
InvalidArgumentError,
NotSupportedError,
RequestAbortedError
} = require('../core/errors')
const util = require('../core/util')
const { addSignal, removeSignal } = require('./abort-signal')
const { Blob } = require('buffer')

const kType = Symbol('type')
const kStatus = Symbol('status')
const kStatusText = Symbol('status text')
const kUrlList = Symbol('url list')
const kHeaders = Symbol('headers')
const kBody = Symbol('body')

let ReadableStream
let TransformStream

class Response {
constructor ({
type,
url,
body,
statusCode,
headers,
context
}) {
this[kType] = type || 'default'
this[kStatus] = statusCode || 0
this[kStatusText] = STATUS_CODES[statusCode] || ''
this[kUrlList] = Array.isArray(url) ? url : (url ? [url] : [])
this[kHeaders] = headers || new Headers()
this[kBody] = body || null

if (context && context.history) {
this[kUrlList].push(...context.history)
}
}

get type () {
return this[kType]
}

get url () {
const length = this[kUrlList].length
return length === 0 ? '' : this[kUrlList][length - 1].toString()
}

get redirected () {
return this[kUrlList].length > 1
}

get status () {
return this[kStatus]
}

get ok () {
return this[kStatus] >= 200 && this[kStatus] <= 299
}

get statusText () {
return this[kStatusText]
}

get headers () {
return this[kHeaders]
}

async blob () {
const chunks = []
if (this.body) {
if (this.bodyUsed || this.body.locked) {
throw new TypeError('unusable')
}

for await (const chunk of this.body) {
chunks.push(chunk)
}
}

return new Blob(chunks, { type: this.headers.get('Content-Type') || '' })
}

async arrayBuffer () {
const blob = await this.blob()
return await blob.arrayBuffer()
}

async text () {
const blob = await this.blob()
return await blob.text()
}

async json () {
return JSON.parse(await this.text())
}

async formData () {
// TODO: Implement.
throw new NotSupportedError('formData')
}

get body () {
return this[kBody]
}

get bodyUsed () {
return util.isDisturbed(this.body)
}

clone () {
let body = null

if (this[kBody]) {
if (util.isDisturbed(this[kBody])) {
throw new TypeError('disturbed')
}

if (this[kBody].locked) {
throw new TypeError('locked')
}

// https://fetch.spec.whatwg.org/#concept-body-clone
const [out1, out2] = this[kBody].tee()

this[kBody] = out1
body = out2
}

return new Response({
type: this[kType],
statusCode: this[kStatus],
url: this[kUrlList],
headers: this[kHeaders],
body
})
}
}

class FetchHandler {
constructor (opts, callback) {
if (!opts || typeof opts !== 'object') {
Expand Down
2 changes: 1 addition & 1 deletion lib/api/headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function isHeaders (object) {
function fill (headers, object) {
if (isHeaders(object)) {
// Object is instance of Headers
headers[kHeadersList] = Array.splice(object[kHeadersList])
headers[kHeadersList] = [...object[kHeadersList]]
} else if (Array.isArray(object)) {
// Support both 1D and 2D arrays of header entries
if (Array.isArray(object[0])) {
Expand Down
142 changes: 142 additions & 0 deletions lib/api/response.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
'use strict'

const Headers = require('./headers')
const { Blob } = require('buffer')
const { STATUS_CODES } = require('http')
const {
NotSupportedError
} = require('../core/errors')
const util = require('../core/util')

const kType = Symbol('type')
const kStatus = Symbol('status')
const kStatusText = Symbol('status text')
const kUrlList = Symbol('url list')
const kHeaders = Symbol('headers')
const kBody = Symbol('body')
const kBodyUsed = Symbol('body used')

class Response {
constructor ({
type,
url,
body,
statusCode,
headers,
context
}) {
this[kType] = type || 'default'
this[kStatus] = statusCode || 0
this[kStatusText] = STATUS_CODES[statusCode] || ''
this[kUrlList] = Array.isArray(url) ? url : (url ? [url] : [])
this[kHeaders] = headers || new Headers()
this[kBody] = body || null
this[kBodyUsed] = false

if (context && context.history) {
this[kUrlList].push(...context.history)
}
}

get type () {
return this[kType]
}

get url () {
const length = this[kUrlList].length
return length === 0 ? '' : this[kUrlList][length - 1].toString()
}

get redirected () {
return this[kUrlList].length > 1
}

get status () {
return this[kStatus]
}

get ok () {
return this[kStatus] >= 200 && this[kStatus] <= 299
}

get statusText () {
return this[kStatusText]
}

get headers () {
return this[kHeaders]
}

async blob () {
const chunks = []
if (this.body) {
if (this.bodyUsed || this.body.locked) {
throw new TypeError('unusable')
}

this[kBodyUsed] = true
for await (const chunk of this.body) {
chunks.push(chunk)
}
}

return new Blob(chunks, { type: this.headers.get('Content-Type') || '' })
}

async arrayBuffer () {
const blob = await this.blob()
return await blob.arrayBuffer()
}

async text () {
const blob = await this.blob()
return await blob.text()
}

async json () {
return JSON.parse(await this.text())
}

async formData () {
// TODO: Implement.
throw new NotSupportedError('formData')
}

get body () {
return this[kBody]
}

get bodyUsed () {
return util.isDisturbed(this.body) || this[kBodyUsed]
}

clone () {
let body = null

if (this[kBody]) {
if (util.isDisturbed(this[kBody])) {
throw new TypeError('disturbed')
}

if (this[kBody].locked) {
throw new TypeError('locked')
}

// https://fetch.spec.whatwg.org/#concept-body-clone
const [out1, out2] = this[kBody].tee()

this[kBody] = out1
body = out2
}

return new Response({
type: this[kType],
statusCode: this[kStatus],
url: this[kUrlList],
headers: this[kHeaders],
body
})
}
}

module.exports = Response
16 changes: 15 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"build:wasm": "node build/wasm.js --docker",
"lint": "standard | snazzy",
"lint:fix": "standard --fix | snazzy",
"test": "tap test/*.js --no-coverage && jest test/jest/test",
"test": "tap test/*.js --no-coverage && mocha test/node-fetch && jest test/jest/test",
"test:node-fetch": "mocha test/node-fetch",
"test:tdd": "tap test/*.js -w --no-coverage-report",
"test:typescript": "tsd",
"coverage": "standard | snazzy && tap test/*.js",
Expand All @@ -46,17 +47,27 @@
"prepare": "husky install",
"fuzz": "jsfuzz test/fuzzing/fuzz.js corpus"
},
"eslintConfig": {},
"devDependencies": {
"@sinonjs/fake-timers": "^7.0.5",
"@types/node": "^15.0.2",
"abort-controller": "^3.0.0",
"abortcontroller-polyfill": "^1.7.3",
"busboy": "^0.3.1",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"chai-iterator": "^3.0.2",
"chai-string": "^1.5.0",
"concurrently": "^6.1.0",
"cronometro": "^0.8.0",
"delay": "^5.0.0",
"docsify-cli": "^4.4.2",
"https-pem": "^2.0.0",
"husky": "^6.0.0",
"jest": "^27.0.5",
"jsfuzz": "^1.0.15",
"mocha": "^9.0.3",
"p-timeout": "^3.2.0",
"pre-commit": "^1.2.2",
"proxy": "^1.0.2",
"proxyquire": "^2.1.3",
Expand All @@ -72,6 +83,9 @@
"node": ">=12.18"
},
"standard": {
"env": [
"mocha"
],
"ignore": [
"lib/llhttp/constants.js",
"lib/llhttp/utils.js"
Expand Down
Loading

0 comments on commit 611a562

Please sign in to comment.