OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / gzip.test.js

155 lines · javascript ref:
/**
 * Response compression (lib/web/compress.js, wired into sendHtml + serveStatic).
 *
 * The daemon shipped everything uncompressed. Apache's mod_deflate on 7rnx
 * hid that from the browser but not from the wire that matters: the VPS had
 * to pull the full uncompressed response across the ~96ms VPN hop first.
 *
 * Pinned here: Accept-Encoding is parsed strictly, small bodies are left
 * alone, Vary is always sent, and an uncompressed path always remains.
 */

import test from 'brittle'
import zlib from '#node:zlib'
import { acceptsGzip, gzipIfWorthIt, __test } from '../lib/web/compress.js'
import { sendHtml } from '../lib/web/helpers.js'
import { serveStatic } from '../lib/web/static.js'

const { MIN_COMPRESS_BYTES } = __test

function mockRes (opts = {}) {
  return {
    acceptsGzip: opts.acceptsGzip,
    status: null,
    headers: null,
    body: null,
    writeHead (status, headers) {
      this.status = status
      this.headers = headers
    },
    end (body) { this.body = body },
    header (name) {
      const key = Object.keys(this.headers || {})
        .find((k) => k.toLowerCase() === name.toLowerCase())
      return key ? this.headers[key] : undefined
    }
  }
}

const big = '<div class="repo-card">a repo row</div>'.repeat(200)

test('acceptsGzip parses the header strictly', (t) => {
  t.ok(acceptsGzip({ headers: { 'accept-encoding': 'gzip' } }))
  t.ok(acceptsGzip({ headers: { 'accept-encoding': 'gzip, deflate, br' } }))
  t.ok(acceptsGzip({ headers: { 'accept-encoding': 'deflate, gzip' } }))
  t.ok(acceptsGzip({ headers: { 'accept-encoding': 'gzip;q=1.0, *;q=0.5' } }))
  t.ok(acceptsGzip({ headers: { 'accept-encoding': '*' } }), 'wildcard counts')

  t.absent(acceptsGzip({ headers: {} }), 'absent header')
  t.absent(acceptsGzip({ headers: { 'accept-encoding': '' } }), 'empty header')
  t.absent(acceptsGzip({ headers: { 'accept-encoding': 'deflate, br' } }))
  t.absent(acceptsGzip(null), 'no request at all')

  // Substring false-positives the old naive `.includes('gzip')` would hit.
  t.absent(acceptsGzip({ headers: { 'accept-encoding': 'notgzip' } }),
    'must not match gzip inside another token')
})

test('small bodies are left uncompressed', (t) => {
  t.is(gzipIfWorthIt('tiny'), null)
  t.is(gzipIfWorthIt('x'.repeat(MIN_COMPRESS_BYTES - 1)), null,
    'just under the threshold')
  t.ok(gzipIfWorthIt('x'.repeat(MIN_COMPRESS_BYTES)),
    'at the threshold it compresses')
  t.is(gzipIfWorthIt(null), null, 'null body')
})

test('compressed output round-trips to the original bytes', (t) => {
  const gz = gzipIfWorthIt(big)
  t.ok(gz, 'compressed')
  t.ok(gz.byteLength < Buffer.byteLength(big) / 2, 'meaningfully smaller')
  t.is(zlib.gunzipSync(Buffer.from(gz)).toString(), big,
    'gunzip returns exactly the input')
})

test('incompressible input is sent as-is rather than inflated', (t) => {
  // Already-gzipped bytes: re-compressing makes them bigger.
  const already = zlib.gzipSync(Buffer.from(big))
  t.is(gzipIfWorthIt(already), null,
    'must not ship a body larger than the original')
})

test('sendHtml compresses when the client accepts it', (t) => {
  const res = mockRes({ acceptsGzip: true })
  sendHtml(res, big)

  t.is(res.status, 200)
  t.is(res.header('content-encoding'), 'gzip')
  t.is(res.header('vary'), 'Accept-Encoding')
  t.ok(res.body.byteLength < Buffer.byteLength(big), 'body is smaller')
  t.is(zlib.gunzipSync(Buffer.from(res.body)).toString(), big, 'round-trips')
})

test('sendHtml sends plain HTML when the client does not', (t) => {
  const res = mockRes({ acceptsGzip: false })
  sendHtml(res, big)

  t.absent(res.header('content-encoding'), 'no encoding header')
  t.is(res.header('vary'), 'Accept-Encoding', 'Vary is sent regardless')
  t.is(res.body, big, 'untouched string body')
})

test('an unstamped res (no router) still renders', (t) => {
  // sendHtml is exported; anything calling it outside the router listener
  // has no res.acceptsGzip and must degrade to uncompressed, not crash.
  const res = mockRes()
  sendHtml(res, big)
  t.is(res.body, big)
  t.absent(res.header('content-encoding'))
})

test('sendHtml preserves a non-200 status', (t) => {
  const res = mockRes({ acceptsGzip: true })
  sendHtml(res, big, 404)
  t.is(res.status, 404)
})

test('static assets compress too', async (t) => {
  const res = mockRes()
  const handled = await serveStatic('vendor/marked.min.js', res, {
    url: '/static/vendor/marked.min.js',
    headers: { 'accept-encoding': 'gzip' }
  })

  t.ok(handled, 'served')
  t.is(res.header('content-encoding'), 'gzip')
  t.is(res.header('vary'), 'Accept-Encoding')
  t.ok(res.header('etag'), 'still validated')

  const plain = mockRes()
  await serveStatic('vendor/marked.min.js', plain, {
    url: '/static/vendor/marked.min.js',
    headers: {}
  })
  t.absent(plain.header('content-encoding'), 'no gzip when not accepted')
  t.ok(res.body.byteLength < plain.body.byteLength, 'gzipped is smaller')
})

test('a 304 carries no compressed body', async (t) => {
  const first = mockRes()
  await serveStatic('pgv3-flash.js', first, {
    url: '/static/pgv3-flash.js',
    headers: {}
  })
  const etag = first.header('etag')

  const res = mockRes()
  await serveStatic('pgv3-flash.js', res, {
    url: '/static/pgv3-flash.js',
    headers: { 'accept-encoding': 'gzip', 'if-none-match': etag }
  })
  t.is(res.status, 304)
  t.absent(res.body, 'no body on a 304')
  t.absent(res.header('content-encoding'), 'and no encoding claim')
})