Files
node/test/parallel/test-quic-error-class.mjs
2026-07-27 19:04:04 +02:00

159 lines
5.3 KiB
JavaScript

// Flags: --experimental-quic --no-warnings
// Test: QuicError class. Validates the public surface of the new
// `QuicError` class exported from `node:quic`:
// * Required `message` and `options.errorCode`.
// * `errorCode` accepts bigint or number; coerces to bigint.
// * `errorCode` is range-checked against the QUIC 62-bit varint
// maximum.
// * `type` defaults to 'application' and is restricted to
// 'application' | 'transport'.
// * `code` defaults to 'ERR_QUIC_STREAM_ABORTED' and may be
// overridden with any Node.js-style error code string.
import { hasQuic, skip } from '../common/index.mjs';
import assert from 'node:assert';
if (!hasQuic) {
skip('QUIC is not enabled');
}
const { QuicError } = await import('node:quic');
// Sanity: QuicError is a function (class) and extends Error.
assert.strictEqual(typeof QuicError, 'function');
assert.ok(new QuicError('msg', { errorCode: 0n }) instanceof Error);
// Required arguments.
// `message` must be a string -> validateString throws ERR_INVALID_ARG_TYPE.
assert.throws(() => new QuicError(), { code: 'ERR_INVALID_ARG_TYPE' });
// `options` defaults to an empty object, so `errorCode` is missing.
assert.throws(() => new QuicError('msg'), { code: 'ERR_MISSING_ARGS' });
// Explicit non-object options -> validateObject throws ERR_INVALID_ARG_TYPE.
assert.throws(() => new QuicError('msg', null), { code: 'ERR_INVALID_ARG_TYPE' });
// Empty options -> errorCode missing -> ERR_MISSING_ARGS.
assert.throws(() => new QuicError('msg', {}), { code: 'ERR_MISSING_ARGS' });
// `message` must be a string.
assert.throws(() => new QuicError(42, { errorCode: 0n }), {
code: 'ERR_INVALID_ARG_TYPE',
});
// `errorCode` must be bigint or number.
assert.throws(() => new QuicError('msg', { errorCode: 'oops' }), {
code: 'ERR_INVALID_ARG_TYPE',
});
assert.throws(() => new QuicError('msg', { errorCode: true }), {
code: 'ERR_INVALID_ARG_TYPE',
});
// `null` is preserved by destructuring (only `undefined` triggers the
// default), so it flows through to the type check rather than the
// missing-args check.
assert.throws(() => new QuicError('msg', { errorCode: null }), {
code: 'ERR_INVALID_ARG_TYPE',
});
// `errorCode` range checks.
assert.throws(() => new QuicError('msg', { errorCode: -1 }), {
code: 'ERR_OUT_OF_RANGE',
});
assert.throws(() => new QuicError('msg', { errorCode: -1n }), {
code: 'ERR_OUT_OF_RANGE',
});
// 2**62 is the first invalid value (max varint is 2**62 - 1).
assert.throws(() => new QuicError('msg', { errorCode: 1n << 62n }), {
code: 'ERR_OUT_OF_RANGE',
});
// `type` validation.
assert.throws(() => new QuicError('msg', { errorCode: 0n, type: 'bogus' }), {
code: 'ERR_INVALID_ARG_VALUE',
});
assert.throws(() => new QuicError('msg', { errorCode: 0n, type: 42 }), {
code: 'ERR_INVALID_ARG_VALUE',
});
// `code` (Node.js error code) must be a string when supplied.
assert.throws(() => new QuicError('msg', { errorCode: 0n, code: 42 }), {
code: 'ERR_INVALID_ARG_TYPE',
});
assert.throws(() => new QuicError('msg', { errorCode: 0n, code: null }), {
code: 'ERR_INVALID_ARG_TYPE',
});
// Happy paths.
// 1. Bigint errorCode, default type, default Node code.
{
const err = new QuicError('something broke', { errorCode: 0x42n });
assert.strictEqual(err.message, 'something broke');
assert.strictEqual(err.code, 'ERR_QUIC_STREAM_ABORTED');
assert.strictEqual(err.errorCode, 0x42n);
assert.strictEqual(err.type, 'application');
assert.ok(err instanceof Error);
assert.ok(err instanceof QuicError);
}
// 2. Number errorCode, coerced to bigint.
{
const err = new QuicError('numeric code', { errorCode: 5 });
assert.strictEqual(err.errorCode, 5n);
assert.strictEqual(typeof err.errorCode, 'bigint');
}
// 3. Boundary: 0n is allowed.
{
const err = new QuicError('zero', { errorCode: 0n });
assert.strictEqual(err.errorCode, 0n);
}
// 4. Boundary: 2**62 - 1 is allowed (largest valid 62-bit varint).
{
const max = (1n << 62n) - 1n;
const err = new QuicError('max', { errorCode: max });
assert.strictEqual(err.errorCode, max);
}
// 5. Explicit type='transport'.
{
const err = new QuicError('transport-level', {
errorCode: 0x1n,
type: 'transport',
});
assert.strictEqual(err.type, 'transport');
}
// 6. Explicit type='application' (default).
{
const err = new QuicError('app-level', {
errorCode: 0x102n,
type: 'application',
});
assert.strictEqual(err.type, 'application');
}
// 7. Custom Node.js error code string via options.code.
{
const err = new QuicError('custom', {
errorCode: 0x10cn,
code: 'ERR_CUSTOM_QUIC_FAILURE',
});
assert.strictEqual(err.code, 'ERR_CUSTOM_QUIC_FAILURE');
assert.strictEqual(err.errorCode, 0x10cn);
}
// 8. Properties are read-only via getters (errorCode, type backed by
// private fields). The base Error's message is writable but the
// QuicError-specific accessors must not be assignable through the
// prototype.
{
const err = new QuicError('msg', { errorCode: 0x1n });
// The getters live on the prototype; assigning through the instance
// is silently ignored in non-strict mode (modules are strict, so
// this throws TypeError). We just assert the value is unchanged.
assert.throws(() => { err.errorCode = 0xffn; }, { name: 'TypeError' });
assert.throws(() => { err.type = 'transport'; }, { name: 'TypeError' });
assert.strictEqual(err.errorCode, 0x1n);
assert.strictEqual(err.type, 'application');
}