UNPKG

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

144 lines (136 loc) 6.95 kB
const test = require('node:test'); //node test runner const assert = require('node:assert/strict'); //assert helpers const qtests = require('qtests'); //stub utility const qerrorsModule = require('../lib/qerrors'); //module under test const { postWithRetry, axiosInstance } = qerrorsModule; //target helper and axios function withRetryEnv(retry, base, max) { //temporarily set retry env vars const origRetry = process.env.QERRORS_RETRY_ATTEMPTS; //save attempts const origBase = process.env.QERRORS_RETRY_BASE_MS; //save base delay const origMax = process.env.QERRORS_RETRY_MAX_MS; //save cap delay if (retry === undefined) { delete process.env.QERRORS_RETRY_ATTEMPTS; } else { process.env.QERRORS_RETRY_ATTEMPTS = String(retry); } if (base === undefined) { delete process.env.QERRORS_RETRY_BASE_MS; } else { process.env.QERRORS_RETRY_BASE_MS = String(base); } if (max === undefined) { delete process.env.QERRORS_RETRY_MAX_MS; } else { process.env.QERRORS_RETRY_MAX_MS = String(max); } return () => { //restore env vars if (origRetry === undefined) { delete process.env.QERRORS_RETRY_ATTEMPTS; } else { process.env.QERRORS_RETRY_ATTEMPTS = origRetry; } if (origBase === undefined) { delete process.env.QERRORS_RETRY_BASE_MS; } else { process.env.QERRORS_RETRY_BASE_MS = origBase; } if (origMax === undefined) { delete process.env.QERRORS_RETRY_MAX_MS; } else { process.env.QERRORS_RETRY_MAX_MS = origMax; } }; } test('postWithRetry adds jitter to wait time', async () => { const restoreEnv = withRetryEnv(1, 100); //set small base for test let callCount = 0; //track axios.post calls const restoreAxios = qtests.stubMethod(axiosInstance, 'post', async () => { //stub axios callCount++; //increment on each call if (callCount === 1) { throw new Error('fail'); } //fail first return { ok: true }; //succeed second }); let waited; //capture wait duration const restoreTimeout = qtests.stubMethod(global, 'setTimeout', (fn, ms) => { waited = ms; fn(); }); //capture delay and run immediately const origRandom = Math.random; //keep original random Math.random = () => 0.5; //predictable jitter try { const res = await postWithRetry('url', {}); //call helper assert.equal(res.ok, true); //success after retry assert.equal(callCount, 2); //called twice assert.ok(waited >= 100 && waited < 200); //jitter range check } finally { Math.random = origRandom; //restore Math.random restoreTimeout(); //restore timeout restoreAxios(); //restore axios restoreEnv(); //restore env } }); test('postWithRetry uses defaults with invalid env', async () => { //invalid values fallback to defaults const restoreEnv = withRetryEnv('abc', 'abc'); //set invalid strings let callCount = 0; //track axios calls const restoreAxios = qtests.stubMethod(axiosInstance, 'post', async () => { //stub post callCount++; //increment each time if (callCount === 1) { throw new Error('fail'); } //first call fails return { ok: true }; //success second }); let waited; //capture delay used const restoreTimeout = qtests.stubMethod(global, 'setTimeout', (fn, ms) => { waited = ms; fn(); }); //intercept timeout const origRandom = Math.random; //save random Math.random = () => 0.5; //predictable jitter try { const res = await postWithRetry('url', {}); //invoke helper assert.equal(res.ok, true); //successful result assert.equal(callCount, 2); //one retry assert.ok(waited >= 100 && waited < 200); //default base of 100 used } finally { Math.random = origRandom; //restore random restoreTimeout(); //restore timeout restoreAxios(); //restore axios restoreEnv(); //restore env vars } }); test('postWithRetry enforces backoff cap', async () => { //cap ensures wait time not excessive const restoreEnv = withRetryEnv(1, 300, 400); //set base and cap let callCount = 0; //track axios calls const restoreAxios = qtests.stubMethod(axiosInstance, 'post', async () => { //stub post callCount++; //increment each time if (callCount === 1) { throw new Error('fail'); } //fail first return { ok: true }; //succeed second }); let waited; //capture capped wait const restoreTimeout = qtests.stubMethod(global, 'setTimeout', (fn, ms) => { waited = ms; fn(); }); //capture delay const origRandom = Math.random; //save random Math.random = () => 0.5; //predictable jitter try { const res = await postWithRetry('url', {}); //invoke helper assert.equal(res.ok, true); //successful result assert.equal(waited, 400); //delay capped at 400 } finally { Math.random = origRandom; //restore random restoreTimeout(); //restore timeout restoreAxios(); //restore axios restoreEnv(); //restore env } }); test('postWithRetry uses Retry-After header for rate limit', async () => { //header controls wait const restoreEnv = withRetryEnv(1, 100); //set base delay const err = new Error('rate'); //error for first attempt err.response = { status: 429, headers: { 'retry-after': '1' } }; //429 with header let count = 0; //track calls const restoreAxios = qtests.stubMethod(axiosInstance, 'post', async () => { //stub axios count++; //increment each call if (count === 1) { throw err; } //fail first attempt return { ok: true }; //succeed second }); let waited; //capture wait time const restoreTimeout = qtests.stubMethod(global, 'setTimeout', (fn, ms) => { waited = ms; fn(); }); //capture delay try { const res = await postWithRetry('url', {}); //invoke helper assert.equal(res.ok, true); //success after retry assert.equal(waited, 1000); //wait from header used } finally { restoreTimeout(); //restore timeout restoreAxios(); //restore axios restoreEnv(); //restore env } }); test('postWithRetry doubles delay when rate limit header missing', async () => { //extend backoff const restoreEnv = withRetryEnv(1, 100); //use default cap const err = new Error('unavail'); //error for first attempt err.response = { status: 503, headers: {} }; //503 without header let count = 0; //track calls const restoreAxios = qtests.stubMethod(axiosInstance, 'post', async () => { //stub axios count++; //increment each call if (count === 1) { throw err; } //fail first return { ok: true }; //succeed second }); let waited; //capture backoff const restoreTimeout = qtests.stubMethod(global, 'setTimeout', (fn, ms) => { waited = ms; fn(); }); //capture delay const origRandom = Math.random; //store random Math.random = () => 0.5; //predictable jitter try { const res = await postWithRetry('url', {}); //call helper assert.equal(res.ok, true); //succeeded after retry assert.equal(waited, 300); //100 base +50 jitter doubled } finally { Math.random = origRandom; //restore random restoreTimeout(); //restore timeout restoreAxios(); //restore axios restoreEnv(); //restore env } });