qerrors
Version:
Intelligent error handling middleware with AI-powered analysis, environment validation, caching, and production-ready logging. Provides OpenAI-based error suggestions, queue management, retry mechanisms, and comprehensive configuration options for Node.js
180 lines (165 loc) • 8.58 kB
JavaScript
const test = require('node:test'); //node test runner
const assert = require('node:assert/strict'); //strict assertion helpers
const qtests = require('qtests'); //qtests stubbing utilities
const qerrors = require('../lib/qerrors'); //module under test
const logger = require('../lib/logger'); //winston logger stubbed during tests
function createRes() { //construct minimal Express-like response mock
return {
headersSent: false, //simulates whether headers have been sent
statusCode: null, //captured status for assertions
payload: null, //body content returned by status/json/send
status(code) { this.statusCode = code; return this; }, //chainable setter
json(data) { this.payload = data; return this; }, //capture JSON payload
send(html) { this.payload = html; return this; } //capture HTML output
};
}
async function stubDeps(loggerFn, analyzeFn) { //create combined stub utility for tests
const realLogger = await logger; //wait for logger instance
const restoreLogger = qtests.stubMethod(realLogger, 'error', loggerFn); //stub logger.error with provided function
const restoreAnalyze = qtests.stubMethod(qerrors, 'analyzeError', analyzeFn); //stub analyzeError with provided function
return () => { //return unified restore
restoreLogger(); //restore logger.error after each test
restoreAnalyze(); //restore analyzeError after each test
};
}
// Scenario: standard JSON error handling and next() invocation
test('qerrors logs and responds with json then calls next', async () => {
let logged; //capture logger output for assertions
const restore = await stubDeps((err) => { logged = err; }, async () => 'adv'); //stub logger and analyze with helper
const res = createRes(); //mock response object
const req = { headers: {} }; //minimal request object
const err = new Error('boom'); //sample error to handle
let nextArg; //store argument passed to next()
const next = (e) => { nextArg = e; }; //spy for next()
try {
await qerrors(err, 'ctx', req, res, next);
await new Promise(r => setTimeout(r, 0)); //wait for queued analysis completion
} finally {
restore(); //restore all stubs after test
}
assert.ok(err.uniqueErrorName); //unique id added to error
assert.equal(res.statusCode, 500); //status defaults to 500
assert.deepEqual(res.payload.error.uniqueErrorName, err.uniqueErrorName); //response includes id
assert.deepEqual(logged.uniqueErrorName, err.uniqueErrorName); //logged same id
assert.equal(nextArg, err); //next called with error
});
// Scenario: send HTML when browser requests it
test('qerrors sends html when accept header requests it', async () => {
const restore = await stubDeps(() => {}, async () => {}); //stub logger and analyze with helper
const res = createRes(); //mock response object to capture html
const req = { headers: { accept: 'text/html' } }; //request asking for html
const err = new Error('boom'); //sample error to send
try {
await qerrors(err, 'ctx', req, res);
await new Promise(r => setTimeout(r, 0)); //wait for queued analysis completion
} finally {
restore(); //restore all stubs after test
}
assert.equal(res.statusCode, 500); //html response uses error status
assert.ok(typeof res.payload === 'string'); //payload is html string
});
// Scenario: sanitize html output to avoid injection
test('qerrors escapes html content', async () => {
const restore = await stubDeps(() => {}, async () => {}); //stub logger and analyze with helper
const res = createRes(); //mock response object
const req = { headers: { accept: 'text/html' } }; //requesting html
const err = new Error('<script>boom</script>'); //error message containing html
err.stack = '<script>stack</script>'; //custom stack with html
try {
await qerrors(err, 'ctx', req, res);
await new Promise(r => setTimeout(r, 0)); //wait for queued analysis completion
} finally {
restore(); //restore stubs after test
}
assert.ok(!res.payload.includes('<script>')); //ensure raw tag removed
assert.ok(res.payload.includes('<script>')); //escaped content present
});
// Scenario: use statusCode from error object in json response
test('qerrors honors error.statusCode in json', async () => {
const restore = await stubDeps(() => {}, async () => {}); //stub logger and analyze with helper
const res = createRes(); //mock response for json
const req = { headers: {} }; //no html accept header
const err = new Error('not found'); //error with custom status
err.statusCode = 404; //status code to verify
try {
await qerrors(err, 'ctx', req, res); //invoke handler with status
await new Promise(r => setTimeout(r, 0)); //wait for queued analysis completion
} finally {
restore(); //restore stubs after test
}
assert.equal(res.statusCode, 404); //expect custom code set in response
assert.equal(res.payload.error.statusCode, 404); //json includes status code
});
// Scenario: use statusCode from error object in html response
test('qerrors honors error.statusCode in html', async () => {
const restore = await stubDeps(() => {}, async () => {}); //stub logger and analyze with helper
const res = createRes(); //mock response for html
const req = { headers: { accept: 'text/html' } }; //html accept header
const err = new Error('not found'); //error with custom status
err.statusCode = 404; //status code to verify
try {
await qerrors(err, 'ctx', req, res); //invoke handler with status and html
await new Promise(r => setTimeout(r, 0)); //wait for queued analysis completion
} finally {
restore(); //restore stubs after test
}
assert.equal(res.statusCode, 404); //expect custom code set in response
assert.ok(res.payload.includes('Error: 404')); //html output reflects code
});
// Scenario: skip response when headers already sent
test('qerrors does nothing when headers already sent', async () => {
const restore = await stubDeps(() => {}, async () => {}); //stub logger and analyze with helper
const res = createRes(); //mock response object with headers already sent
res.headersSent = true; //simulate Express sending headers prior
const err = new Error('boom'); //error to pass into handler
let nextCalled = false; //track if next() invoked
try {
await qerrors(err, 'ctx', {}, res, () => { nextCalled = true; });
await new Promise(r => setTimeout(r, 0)); //wait for queued analysis completion
} finally {
restore(); //restore all stubs after test
}
assert.equal(res.statusCode, null); //handler skipped sending response
assert.equal(nextCalled, false); //next not called when headersSent
});
// Scenario: operate without Express objects
test('qerrors handles absence of req res and next', async () => {
let logged; //capture logger output
const restore = await stubDeps((err) => { logged = err; }, async () => {}); //stub logger and analyze with helper
const err = new Error('boom'); //error for generic usage
try {
await qerrors(err);
await new Promise(r => setTimeout(r, 0)); //wait for queued analysis completion
} finally {
restore(); //restore all stubs after test
}
assert.ok(err.uniqueErrorName); //middleware assigned id
assert.equal(logged.context, 'unknown context'); //default context logged
});
// Scenario: still call next when res is undefined
test('qerrors calls next without res', async () => {
const restore = await stubDeps(() => {}, async () => {}); //stub logger and analyze with helper
const err = new Error('boom'); //error when res missing
let nextArg; //captured arg for next()
try {
await qerrors(err, 'ctx', undefined, undefined, (e) => { nextArg = e; });
await new Promise(r => setTimeout(r, 0)); //wait for queued analysis completion
} finally {
restore(); //restore all stubs after test
}
assert.equal(nextArg, err); //next receives original error
});
// Scenario: warn and exit when called without an error
test('qerrors exits if no error provided', async () => {
const restore = await stubDeps(() => {}, async () => {}); //stub logger and analyze with helper
let warned = false; //track if warn was called
const restoreWarn = qtests.stubMethod(console, 'warn', () => { warned = true; }); //use qtests to stub console.warn
try {
await qerrors(null, 'ctx');
await new Promise(r => setTimeout(r, 0)); //wait for queued analysis completion
assert.equal(warned, true); //warn called when missing error
} finally {
restoreWarn(); //restore console.warn after test
restore(); //restore all stubs after test
}
});