Set up Vitest with server (node) and client (jsdom) test projects. 79 tests across 8 files verify all Phase 0-1 security controls: escapeHtml sanitization, DOMPurify markdown XSS prevention, CSRF enforcement, security headers, rate limiting, auth guards, profile field allowlist, and login anti-enumeration. Updated SECURITY_EVALUATION.md with remediation status, implementation summary, and automated test coverage details.
72 lines
2.2 KiB
JavaScript
72 lines
2.2 KiB
JavaScript
import { IncomingMessage, ServerResponse } from 'node:http'
|
|
import { Socket } from 'node:net'
|
|
import { createEvent } from 'h3'
|
|
|
|
/**
|
|
* Create a real h3 event backed by real Node.js request/response objects.
|
|
*
|
|
* Options:
|
|
* method - HTTP method (default 'GET')
|
|
* path - Request path (default '/')
|
|
* headers - Object of request headers
|
|
* cookies - Object of cookie key/value pairs (serialized to cookie header)
|
|
* body - Request body (will be JSON-serialized)
|
|
* remoteAddress - Client IP (default '127.0.0.1')
|
|
*/
|
|
export function createMockEvent(options = {}) {
|
|
const {
|
|
method = 'GET',
|
|
path = '/',
|
|
headers = {},
|
|
cookies = {},
|
|
body = undefined,
|
|
remoteAddress = '127.0.0.1'
|
|
} = options
|
|
|
|
// Build cookie header from cookies object
|
|
const cookiePairs = Object.entries(cookies).map(([k, v]) => `${k}=${v}`)
|
|
if (cookiePairs.length > 0) {
|
|
headers.cookie = [headers.cookie, cookiePairs.join('; ')].filter(Boolean).join('; ')
|
|
}
|
|
|
|
// Build a real IncomingMessage
|
|
const socket = new Socket()
|
|
// remoteAddress is a getter-only property on Socket, so use defineProperty
|
|
Object.defineProperty(socket, 'remoteAddress', {
|
|
value: remoteAddress,
|
|
writable: true,
|
|
configurable: true
|
|
})
|
|
const req = new IncomingMessage(socket)
|
|
req.method = method
|
|
req.url = path
|
|
req.headers = Object.fromEntries(
|
|
Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v])
|
|
)
|
|
|
|
// If body is provided, push it into the request stream so readBody can consume it
|
|
if (body !== undefined) {
|
|
const bodyStr = typeof body === 'string' ? body : JSON.stringify(body)
|
|
req.headers['content-type'] = req.headers['content-type'] || 'application/json'
|
|
req.headers['content-length'] = Buffer.byteLength(bodyStr).toString()
|
|
req.push(bodyStr)
|
|
req.push(null)
|
|
}
|
|
|
|
const res = new ServerResponse(req)
|
|
|
|
// Capture response headers for test assertions
|
|
const setHeaders = {}
|
|
const originalSetHeader = res.setHeader.bind(res)
|
|
res.setHeader = (name, value) => {
|
|
setHeaders[name.toLowerCase()] = value
|
|
return originalSetHeader(name, value)
|
|
}
|
|
|
|
const event = createEvent(req, res)
|
|
|
|
// Attach captured headers for test access
|
|
event._testSetHeaders = setHeaders
|
|
|
|
return event
|
|
}
|