Skip to content

Commit 611a562

Browse files
committed
test: port node-fetch
1 parent f1e485c commit 611a562

15 files changed

+3861
-134
lines changed

index.js

+17-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,23 @@ function makeDispatcher (fn) {
8484
module.exports.setGlobalDispatcher = setGlobalDispatcher
8585
module.exports.getGlobalDispatcher = getGlobalDispatcher
8686

87-
module.exports.fetch = makeDispatcher(api.fetch)
87+
const _fetch = makeDispatcher(api.fetch)
88+
module.exports.fetch = async function fetch (...args) {
89+
try {
90+
return await _fetch(...args)
91+
} catch (err) {
92+
// TODO (fix): This is a little weird. Spec compliant?
93+
if (err.code === 'ERR_INVALID_URL') {
94+
const er = new TypeError('Invalid URL')
95+
er.cause = err
96+
throw er
97+
}
98+
throw err
99+
}
100+
}
101+
module.exports.Headers = require('./lib/api/headers')
102+
module.exports.Response = require('./lib/api/response')
103+
88104
module.exports.request = makeDispatcher(api.request)
89105
module.exports.stream = makeDispatcher(api.stream)
90106
module.exports.pipeline = makeDispatcher(api.pipeline)

lib/api/api-fetch.js

+3-131
Original file line numberDiff line numberDiff line change
@@ -2,150 +2,22 @@
22

33
'use strict'
44

5-
const Headers = require('./headers')
65
const { kHeadersList } = require('../core/symbols')
6+
const Headers = require('./headers')
7+
const Response = require('./response')
78
const { Readable } = require('stream')
8-
const { METHODS, STATUS_CODES } = require('http')
9+
const { METHODS } = require('http')
910
const {
1011
InvalidArgumentError,
1112
NotSupportedError,
1213
RequestAbortedError
1314
} = require('../core/errors')
1415
const util = require('../core/util')
1516
const { addSignal, removeSignal } = require('./abort-signal')
16-
const { Blob } = require('buffer')
17-
18-
const kType = Symbol('type')
19-
const kStatus = Symbol('status')
20-
const kStatusText = Symbol('status text')
21-
const kUrlList = Symbol('url list')
22-
const kHeaders = Symbol('headers')
23-
const kBody = Symbol('body')
2417

2518
let ReadableStream
2619
let TransformStream
2720

28-
class Response {
29-
constructor ({
30-
type,
31-
url,
32-
body,
33-
statusCode,
34-
headers,
35-
context
36-
}) {
37-
this[kType] = type || 'default'
38-
this[kStatus] = statusCode || 0
39-
this[kStatusText] = STATUS_CODES[statusCode] || ''
40-
this[kUrlList] = Array.isArray(url) ? url : (url ? [url] : [])
41-
this[kHeaders] = headers || new Headers()
42-
this[kBody] = body || null
43-
44-
if (context && context.history) {
45-
this[kUrlList].push(...context.history)
46-
}
47-
}
48-
49-
get type () {
50-
return this[kType]
51-
}
52-
53-
get url () {
54-
const length = this[kUrlList].length
55-
return length === 0 ? '' : this[kUrlList][length - 1].toString()
56-
}
57-
58-
get redirected () {
59-
return this[kUrlList].length > 1
60-
}
61-
62-
get status () {
63-
return this[kStatus]
64-
}
65-
66-
get ok () {
67-
return this[kStatus] >= 200 && this[kStatus] <= 299
68-
}
69-
70-
get statusText () {
71-
return this[kStatusText]
72-
}
73-
74-
get headers () {
75-
return this[kHeaders]
76-
}
77-
78-
async blob () {
79-
const chunks = []
80-
if (this.body) {
81-
if (this.bodyUsed || this.body.locked) {
82-
throw new TypeError('unusable')
83-
}
84-
85-
for await (const chunk of this.body) {
86-
chunks.push(chunk)
87-
}
88-
}
89-
90-
return new Blob(chunks, { type: this.headers.get('Content-Type') || '' })
91-
}
92-
93-
async arrayBuffer () {
94-
const blob = await this.blob()
95-
return await blob.arrayBuffer()
96-
}
97-
98-
async text () {
99-
const blob = await this.blob()
100-
return await blob.text()
101-
}
102-
103-
async json () {
104-
return JSON.parse(await this.text())
105-
}
106-
107-
async formData () {
108-
// TODO: Implement.
109-
throw new NotSupportedError('formData')
110-
}
111-
112-
get body () {
113-
return this[kBody]
114-
}
115-
116-
get bodyUsed () {
117-
return util.isDisturbed(this.body)
118-
}
119-
120-
clone () {
121-
let body = null
122-
123-
if (this[kBody]) {
124-
if (util.isDisturbed(this[kBody])) {
125-
throw new TypeError('disturbed')
126-
}
127-
128-
if (this[kBody].locked) {
129-
throw new TypeError('locked')
130-
}
131-
132-
// https://fetch.spec.whatwg.org/#concept-body-clone
133-
const [out1, out2] = this[kBody].tee()
134-
135-
this[kBody] = out1
136-
body = out2
137-
}
138-
139-
return new Response({
140-
type: this[kType],
141-
statusCode: this[kStatus],
142-
url: this[kUrlList],
143-
headers: this[kHeaders],
144-
body
145-
})
146-
}
147-
}
148-
14921
class FetchHandler {
15022
constructor (opts, callback) {
15123
if (!opts || typeof opts !== 'object') {

lib/api/headers.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ function isHeaders (object) {
4949
function fill (headers, object) {
5050
if (isHeaders(object)) {
5151
// Object is instance of Headers
52-
headers[kHeadersList] = Array.splice(object[kHeadersList])
52+
headers[kHeadersList] = [...object[kHeadersList]]
5353
} else if (Array.isArray(object)) {
5454
// Support both 1D and 2D arrays of header entries
5555
if (Array.isArray(object[0])) {

lib/api/response.js

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
'use strict'
2+
3+
const Headers = require('./headers')
4+
const { Blob } = require('buffer')
5+
const { STATUS_CODES } = require('http')
6+
const {
7+
NotSupportedError
8+
} = require('../core/errors')
9+
const util = require('../core/util')
10+
11+
const kType = Symbol('type')
12+
const kStatus = Symbol('status')
13+
const kStatusText = Symbol('status text')
14+
const kUrlList = Symbol('url list')
15+
const kHeaders = Symbol('headers')
16+
const kBody = Symbol('body')
17+
const kBodyUsed = Symbol('body used')
18+
19+
class Response {
20+
constructor ({
21+
type,
22+
url,
23+
body,
24+
statusCode,
25+
headers,
26+
context
27+
}) {
28+
this[kType] = type || 'default'
29+
this[kStatus] = statusCode || 0
30+
this[kStatusText] = STATUS_CODES[statusCode] || ''
31+
this[kUrlList] = Array.isArray(url) ? url : (url ? [url] : [])
32+
this[kHeaders] = headers || new Headers()
33+
this[kBody] = body || null
34+
this[kBodyUsed] = false
35+
36+
if (context && context.history) {
37+
this[kUrlList].push(...context.history)
38+
}
39+
}
40+
41+
get type () {
42+
return this[kType]
43+
}
44+
45+
get url () {
46+
const length = this[kUrlList].length
47+
return length === 0 ? '' : this[kUrlList][length - 1].toString()
48+
}
49+
50+
get redirected () {
51+
return this[kUrlList].length > 1
52+
}
53+
54+
get status () {
55+
return this[kStatus]
56+
}
57+
58+
get ok () {
59+
return this[kStatus] >= 200 && this[kStatus] <= 299
60+
}
61+
62+
get statusText () {
63+
return this[kStatusText]
64+
}
65+
66+
get headers () {
67+
return this[kHeaders]
68+
}
69+
70+
async blob () {
71+
const chunks = []
72+
if (this.body) {
73+
if (this.bodyUsed || this.body.locked) {
74+
throw new TypeError('unusable')
75+
}
76+
77+
this[kBodyUsed] = true
78+
for await (const chunk of this.body) {
79+
chunks.push(chunk)
80+
}
81+
}
82+
83+
return new Blob(chunks, { type: this.headers.get('Content-Type') || '' })
84+
}
85+
86+
async arrayBuffer () {
87+
const blob = await this.blob()
88+
return await blob.arrayBuffer()
89+
}
90+
91+
async text () {
92+
const blob = await this.blob()
93+
return await blob.text()
94+
}
95+
96+
async json () {
97+
return JSON.parse(await this.text())
98+
}
99+
100+
async formData () {
101+
// TODO: Implement.
102+
throw new NotSupportedError('formData')
103+
}
104+
105+
get body () {
106+
return this[kBody]
107+
}
108+
109+
get bodyUsed () {
110+
return util.isDisturbed(this.body) || this[kBodyUsed]
111+
}
112+
113+
clone () {
114+
let body = null
115+
116+
if (this[kBody]) {
117+
if (util.isDisturbed(this[kBody])) {
118+
throw new TypeError('disturbed')
119+
}
120+
121+
if (this[kBody].locked) {
122+
throw new TypeError('locked')
123+
}
124+
125+
// https://fetch.spec.whatwg.org/#concept-body-clone
126+
const [out1, out2] = this[kBody].tee()
127+
128+
this[kBody] = out1
129+
body = out2
130+
}
131+
132+
return new Response({
133+
type: this[kType],
134+
statusCode: this[kStatus],
135+
url: this[kUrlList],
136+
headers: this[kHeaders],
137+
body
138+
})
139+
}
140+
}
141+
142+
module.exports = Response

package.json

+15-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"build:wasm": "node build/wasm.js --docker",
3434
"lint": "standard | snazzy",
3535
"lint:fix": "standard --fix | snazzy",
36-
"test": "tap test/*.js --no-coverage && jest test/jest/test",
36+
"test": "tap test/*.js --no-coverage && mocha test/node-fetch && jest test/jest/test",
37+
"test:node-fetch": "mocha test/node-fetch",
3738
"test:tdd": "tap test/*.js -w --no-coverage-report",
3839
"test:typescript": "tsd",
3940
"coverage": "standard | snazzy && tap test/*.js",
@@ -46,17 +47,27 @@
4647
"prepare": "husky install",
4748
"fuzz": "jsfuzz test/fuzzing/fuzz.js corpus"
4849
},
50+
"eslintConfig": {},
4951
"devDependencies": {
5052
"@sinonjs/fake-timers": "^7.0.5",
5153
"@types/node": "^15.0.2",
5254
"abort-controller": "^3.0.0",
55+
"abortcontroller-polyfill": "^1.7.3",
56+
"busboy": "^0.3.1",
57+
"chai": "^4.3.4",
58+
"chai-as-promised": "^7.1.1",
59+
"chai-iterator": "^3.0.2",
60+
"chai-string": "^1.5.0",
5361
"concurrently": "^6.1.0",
5462
"cronometro": "^0.8.0",
63+
"delay": "^5.0.0",
5564
"docsify-cli": "^4.4.2",
5665
"https-pem": "^2.0.0",
5766
"husky": "^6.0.0",
5867
"jest": "^27.0.5",
5968
"jsfuzz": "^1.0.15",
69+
"mocha": "^9.0.3",
70+
"p-timeout": "^3.2.0",
6071
"pre-commit": "^1.2.2",
6172
"proxy": "^1.0.2",
6273
"proxyquire": "^2.1.3",
@@ -72,6 +83,9 @@
7283
"node": ">=12.18"
7384
},
7485
"standard": {
86+
"env": [
87+
"mocha"
88+
],
7589
"ignore": [
7690
"lib/llhttp/constants.js",
7791
"lib/llhttp/utils.js"

0 commit comments

Comments
 (0)