/**
* Unit tests for lib/response.js
*
* Tests HTTP response helpers, body parsing, and escapeHtml.
*/
import test from 'brittle'
import { escapeHtml, readBody } from '../../lib/response.js'
// ========================================
// escapeHtml
// ========================================
test('escapeHtml - escapes angle brackets', (t) => {
t.is(escapeHtml('<script>'), '<script>')
})
test('escapeHtml - escapes ampersands', (t) => {
t.is(escapeHtml('a&b'), 'a&b')
})
test('escapeHtml - escapes quotes', (t) => {
t.is(escapeHtml('"hello"'), '"hello"')
t.is(escapeHtml("'hello'"), ''hello'')
})
test('escapeHtml - handles combined injection', (t) => {
const input = '<img onerror="alert(1)" src=x>'
const result = escapeHtml(input)
t.not(result.includes('<'), 'no unescaped <')
t.not(result.includes('>'), 'no unescaped >')
t.not(result.includes('"'), 'no unescaped "')
})
test('escapeHtml - handles null/undefined/empty', (t) => {
t.is(escapeHtml(null), '')
t.is(escapeHtml(undefined), '')
t.is(escapeHtml(''), '')
})
test('escapeHtml - handles plain text unchanged', (t) => {
t.is(escapeHtml('hello world'), 'hello world')
t.is(escapeHtml('repo-name'), 'repo-name')
})
test('escapeHtml - handles numbers', (t) => {
t.is(escapeHtml(42), '42')
})
// ========================================
// readBody
// ========================================
test('readBody - reads stream chunks', async (t) => {
// Mock async iterable (simulates HTTP request body)
const chunks = [Buffer.from('hello'), Buffer.from(' world')]
const mockReq = {
[Symbol.asyncIterator] () {
let i = 0
return {
next () {
if (i < chunks.length) {
return Promise.resolve({ value: chunks[i++], done: false })
}
return Promise.resolve({ done: true })
}
}
}
}
const body = await readBody(mockReq)
t.is(body, 'hello world')
})
test('readBody - rejects body over 1MB', async (t) => {
const bigChunk = Buffer.alloc(1024 * 1024 + 1, 'x')
const mockReq = {
[Symbol.asyncIterator] () {
let sent = false
return {
next () {
if (!sent) {
sent = true
return Promise.resolve({ value: bigChunk, done: false })
}
return Promise.resolve({ done: true })
}
}
}
}
try {
await readBody(mockReq)
t.fail('should have thrown')
} catch (err) {
t.ok(err.message.includes('too large'), 'error mentions size limit')
}
})
test('readBody - handles empty body', async (t) => {
const mockReq = {
[Symbol.asyncIterator] () {
return {
next () {
return Promise.resolve({ done: true })
}
}
}
}
const body = await readBody(mockReq)
t.is(body, '')
})