OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git / test / unit / response-helpers.test.js

114 lines · javascript ref:
/**
 * 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>'), '&lt;script&gt;')
})

test('escapeHtml - escapes ampersands', (t) => {
  t.is(escapeHtml('a&b'), 'a&amp;b')
})

test('escapeHtml - escapes quotes', (t) => {
  t.is(escapeHtml('"hello"'), '&quot;hello&quot;')
  t.is(escapeHtml("'hello'"), '&#039;hello&#039;')
})

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, '')
})