Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@
"@metcoder95/https-pem": "^1.0.0",
"@sinonjs/fake-timers": "^12.0.0",
"@types/node": "^22.0.0",
"abort-controller": "^3.0.0",
"borp": "^0.20.0",
"c8": "^10.0.0",
"cross-env": "^10.0.0",
Expand Down
302 changes: 144 additions & 158 deletions test/node-test/abort-controller.js
Original file line number Diff line number Diff line change
@@ -1,92 +1,155 @@
'use strict'

const { test } = require('node:test')
const { AbortController: NPMAbortController } = require('abort-controller')
const { Client, errors } = require('../..')
const { createServer } = require('node:http')
const { createReadStream } = require('node:fs')
const { wrapWithAsyncIterable } = require('../utils/async-iterators')
const { tspl } = require('@matteo.collina/tspl')
const { closeServerAsPromise } = require('../utils/node-http')

const controllers = [{
AbortControllerImpl: NPMAbortController,
controllerName: 'npm-abortcontroller-shim'
}]
test('Abort AbortController before creating request', async (t) => {
const p = tspl(t, { plan: 1 })

if (global.AbortController) {
controllers.push({
AbortControllerImpl: global.AbortController,
controllerName: 'native-abortcontroller'
const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
p.fail()
})
}
for (const { AbortControllerImpl, controllerName } of controllers) {
test(`Abort ${controllerName} before creating request`, async (t) => {
const p = tspl(t, { plan: 1 })
t.after(closeServerAsPromise(server))

const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
p.fail()
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const abortController = new AbortController()
t.after(client.destroy.bind(client))

abortController.abort()

client.request({ path: '/', method: 'GET', signal: abortController.signal }, (err, response) => {
p.ok(err instanceof errors.RequestAbortedError || err instanceof DOMException)
})
t.after(closeServerAsPromise(server))
})

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const abortController = new AbortControllerImpl()
t.after(client.destroy.bind(client))
await p.completed
})

abortController.abort()
test('Abort AbortController before sending request (no body)', async (t) => {
const p = tspl(t, { plan: 3 })

client.request({ path: '/', method: 'GET', signal: abortController.signal }, (err, response) => {
p.ok(err instanceof errors.RequestAbortedError || err instanceof DOMException)
let count = 0
const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
if (count === 1) {
p.fail('The second request should never be executed')
}
count += 1
res.end('hello')
})
t.after(closeServerAsPromise(server))

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const abortController = new AbortController()
t.after(client.destroy.bind(client))

client.request({ path: '/', method: 'GET' }, (err, response) => {
p.ifError(err)
const bufs = []
response.body.on('data', (buf) => {
bufs.push(buf)
})
response.body.on('end', () => {
p.strictEqual('hello', Buffer.concat(bufs).toString('utf8'))
})
})

await p.completed
client.request({ path: '/', method: 'GET', signal: abortController.signal }, (err, response) => {
p.ok(err instanceof errors.RequestAbortedError || err instanceof DOMException)
})

abortController.abort()
})

test(`Abort ${controllerName} before sending request (no body)`, async (t) => {
const p = tspl(t, { plan: 3 })
await p.completed
})

let count = 0
const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
if (count === 1) {
p.fail('The second request should never be executed')
}
count += 1
res.end('hello')
test('Abort AbortController while waiting response (no body)', async (t) => {
const p = tspl(t, { plan: 1 })

const abortController = new AbortController()
const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
abortController.abort()
res.setHeader('content-type', 'text/plain')
res.end('hello world')
})
t.after(closeServerAsPromise(server))

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.after(client.destroy.bind(client))

client.request({ path: '/', method: 'GET', signal: abortController.signal }, (err, response) => {
p.ok(err instanceof errors.RequestAbortedError || err instanceof DOMException)
})
t.after(closeServerAsPromise(server))
})

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
const abortController = new AbortControllerImpl()
t.after(client.destroy.bind(client))
await p.completed
})

client.request({ path: '/', method: 'GET' }, (err, response) => {
p.ifError(err)
const bufs = []
response.body.on('data', (buf) => {
bufs.push(buf)
})
response.body.on('end', () => {
p.strictEqual('hello', Buffer.concat(bufs).toString('utf8'))
})
})
test('Abort AbortController while waiting response (write headers started) (no body)', async (t) => {
const p = tspl(t, { plan: 1 })

client.request({ path: '/', method: 'GET', signal: abortController.signal }, (err, response) => {
p.ok(err instanceof errors.RequestAbortedError || err instanceof DOMException)
})
const abortController = new AbortController()
const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
res.writeHead(200, { 'content-type': 'text/plain' })
res.flushHeaders()
abortController.abort()
res.end('hello world')
})
t.after(closeServerAsPromise(server))

abortController.abort()
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.after(client.destroy.bind(client))

client.request({ path: '/', method: 'GET', signal: abortController.signal }, (err, response) => {
p.ok(err instanceof errors.RequestAbortedError || err instanceof DOMException)
})
})

await p.completed
await p.completed
})

test('Abort AbortController while waiting response (write headers and write body started) (no body)', async (t) => {
const p = tspl(t, { plan: 2 })

const abortController = new AbortController()
const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
res.writeHead(200, { 'content-type': 'text/plain' })
res.write('hello')
})
t.after(closeServerAsPromise(server))

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.after(client.destroy.bind(client))

client.request({ path: '/', method: 'GET', signal: abortController.signal }, (err, response) => {
p.ifError(err)
response.body.on('data', () => {
abortController.abort()
})
response.body.on('error', err => {
p.ok(err instanceof errors.RequestAbortedError || err instanceof DOMException)
})
})
})

test(`Abort ${controllerName} while waiting response (no body)`, async (t) => {
await p.completed
})

function waitingWithBody (body, type) {
test(`Abort AbortController while waiting response (with body ${type})`, async (t) => {
const p = tspl(t, { plan: 1 })

const abortController = new AbortControllerImpl()
const abortController = new AbortController()
const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
abortController.abort()
res.setHeader('content-type', 'text/plain')
Expand All @@ -98,18 +161,24 @@ for (const { AbortControllerImpl, controllerName } of controllers) {
const client = new Client(`http://localhost:${server.address().port}`)
t.after(client.destroy.bind(client))

client.request({ path: '/', method: 'GET', signal: abortController.signal }, (err, response) => {
client.request({ path: '/', method: 'POST', body, signal: abortController.signal }, (err, response) => {
p.ok(err instanceof errors.RequestAbortedError || err instanceof DOMException)
})
})

await p.completed
})
}

waitingWithBody('hello', 'string')
waitingWithBody(createReadStream(__filename), 'stream')
waitingWithBody(new Uint8Array([42]), 'Uint8Array')
waitingWithBody(wrapWithAsyncIterable(createReadStream(__filename)), 'async-iterator')

test(`Abort ${controllerName} while waiting response (write headers started) (no body)`, async (t) => {
function writeHeadersStartedWithBody (body, type) {
test(`Abort AbortController while waiting response (write headers started) (with body ${type})`, async (t) => {
const p = tspl(t, { plan: 1 })

const abortController = new AbortControllerImpl()
const abortController = new AbortController()
const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
res.writeHead(200, { 'content-type': 'text/plain' })
res.flushHeaders()
Expand All @@ -122,18 +191,24 @@ for (const { AbortControllerImpl, controllerName } of controllers) {
const client = new Client(`http://localhost:${server.address().port}`)
t.after(client.destroy.bind(client))

client.request({ path: '/', method: 'GET', signal: abortController.signal }, (err, response) => {
client.request({ path: '/', method: 'POST', body, signal: abortController.signal }, (err, response) => {
p.ok(err instanceof errors.RequestAbortedError || err instanceof DOMException)
})
})

await p.completed
})
}

test(`Abort ${controllerName} while waiting response (write headers and write body started) (no body)`, async (t) => {
writeHeadersStartedWithBody('hello', 'string')
writeHeadersStartedWithBody(createReadStream(__filename), 'stream')
writeHeadersStartedWithBody(new Uint8Array([42]), 'Uint8Array')
writeHeadersStartedWithBody(wrapWithAsyncIterable(createReadStream(__filename)), 'async-iterator')

function writeBodyStartedWithBody (body, type) {
test(`Abort AbortController while waiting response (write headers and write body started) (with body ${type})`, async (t) => {
const p = tspl(t, { plan: 2 })

const abortController = new AbortControllerImpl()
const abortController = new AbortController()
const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
res.writeHead(200, { 'content-type': 'text/plain' })
res.write('hello')
Expand All @@ -144,7 +219,7 @@ for (const { AbortControllerImpl, controllerName } of controllers) {
const client = new Client(`http://localhost:${server.address().port}`)
t.after(client.destroy.bind(client))

client.request({ path: '/', method: 'GET', signal: abortController.signal }, (err, response) => {
client.request({ path: '/', method: 'POST', body, signal: abortController.signal }, (err, response) => {
p.ifError(err)
response.body.on('data', () => {
abortController.abort()
Expand All @@ -154,100 +229,11 @@ for (const { AbortControllerImpl, controllerName } of controllers) {
})
})
})

await p.completed
})

function waitingWithBody (body, type) {
test(`Abort ${controllerName} while waiting response (with body ${type})`, async (t) => {
const p = tspl(t, { plan: 1 })

const abortController = new AbortControllerImpl()
const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
abortController.abort()
res.setHeader('content-type', 'text/plain')
res.end('hello world')
})
t.after(closeServerAsPromise(server))

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.after(client.destroy.bind(client))

client.request({ path: '/', method: 'POST', body, signal: abortController.signal }, (err, response) => {
p.ok(err instanceof errors.RequestAbortedError || err instanceof DOMException)
})
})
await p.completed
})
}

waitingWithBody('hello', 'string')
waitingWithBody(createReadStream(__filename), 'stream')
waitingWithBody(new Uint8Array([42]), 'Uint8Array')
waitingWithBody(wrapWithAsyncIterable(createReadStream(__filename)), 'async-iterator')

function writeHeadersStartedWithBody (body, type) {
test(`Abort ${controllerName} while waiting response (write headers started) (with body ${type})`, async (t) => {
const p = tspl(t, { plan: 1 })

const abortController = new AbortControllerImpl()
const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
res.writeHead(200, { 'content-type': 'text/plain' })
res.flushHeaders()
abortController.abort()
res.end('hello world')
})
t.after(closeServerAsPromise(server))

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.after(client.destroy.bind(client))

client.request({ path: '/', method: 'POST', body, signal: abortController.signal }, (err, response) => {
p.ok(err instanceof errors.RequestAbortedError || err instanceof DOMException)
})
})
await p.completed
})
}

writeHeadersStartedWithBody('hello', 'string')
writeHeadersStartedWithBody(createReadStream(__filename), 'stream')
writeHeadersStartedWithBody(new Uint8Array([42]), 'Uint8Array')
writeHeadersStartedWithBody(wrapWithAsyncIterable(createReadStream(__filename)), 'async-iterator')

function writeBodyStartedWithBody (body, type) {
test(`Abort ${controllerName} while waiting response (write headers and write body started) (with body ${type})`, async (t) => {
const p = tspl(t, { plan: 2 })

const abortController = new AbortControllerImpl()
const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
res.writeHead(200, { 'content-type': 'text/plain' })
res.write('hello')
})
t.after(closeServerAsPromise(server))

server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.after(client.destroy.bind(client))

client.request({ path: '/', method: 'POST', body, signal: abortController.signal }, (err, response) => {
p.ifError(err)
response.body.on('data', () => {
abortController.abort()
})
response.body.on('error', err => {
p.ok(err instanceof errors.RequestAbortedError || err instanceof DOMException)
})
})
})
await p.completed
})
}

writeBodyStartedWithBody('hello', 'string')
writeBodyStartedWithBody(createReadStream(__filename), 'stream')
writeBodyStartedWithBody(new Uint8Array([42]), 'Uint8Array')
writeBodyStartedWithBody(wrapWithAsyncIterable(createReadStream(__filename), 'async-iterator'))
}

writeBodyStartedWithBody('hello', 'string')
writeBodyStartedWithBody(createReadStream(__filename), 'stream')
writeBodyStartedWithBody(new Uint8Array([42]), 'Uint8Array')
writeBodyStartedWithBody(wrapWithAsyncIterable(createReadStream(__filename), 'async-iterator'))
Loading
Loading