node/test/parallel/test-quic-error-class.mjs
James M Snell cf91d181fb quic: complete the internal implementation of QUIC
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
PR-URL: https://github.com/nodejs/node/pull/62876
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
2026-05-05 19:16:19 -07:00

160 lines
5.1 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';
const { strictEqual, throws, ok } = assert;
if (!hasQuic) {
skip('QUIC is not enabled');
}
const { QuicError } = await import('node:quic');
// Sanity: QuicError is a function (class) and extends Error.
strictEqual(typeof QuicError, 'function');
ok(new QuicError('msg', { errorCode: 0n }) instanceof Error);
// Required arguments.
// `message` must be a string -> validateString throws ERR_INVALID_ARG_TYPE.
throws(() => new QuicError(), { code: 'ERR_INVALID_ARG_TYPE' });
// `options` defaults to an empty object, so `errorCode` is missing.
throws(() => new QuicError('msg'), { code: 'ERR_MISSING_ARGS' });
// Explicit non-object options -> validateObject throws ERR_INVALID_ARG_TYPE.
throws(() => new QuicError('msg', null), { code: 'ERR_INVALID_ARG_TYPE' });
// Empty options -> errorCode missing -> ERR_MISSING_ARGS.
throws(() => new QuicError('msg', {}), { code: 'ERR_MISSING_ARGS' });
// `message` must be a string.
throws(() => new QuicError(42, { errorCode: 0n }), {
code: 'ERR_INVALID_ARG_TYPE',
});
// `errorCode` must be bigint or number.
throws(() => new QuicError('msg', { errorCode: 'oops' }), {
code: 'ERR_INVALID_ARG_TYPE',
});
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.
throws(() => new QuicError('msg', { errorCode: null }), {
code: 'ERR_INVALID_ARG_TYPE',
});
// `errorCode` range checks.
throws(() => new QuicError('msg', { errorCode: -1 }), {
code: 'ERR_OUT_OF_RANGE',
});
throws(() => new QuicError('msg', { errorCode: -1n }), {
code: 'ERR_OUT_OF_RANGE',
});
// 2**62 is the first invalid value (max varint is 2**62 - 1).
throws(() => new QuicError('msg', { errorCode: 1n << 62n }), {
code: 'ERR_OUT_OF_RANGE',
});
// `type` validation.
throws(() => new QuicError('msg', { errorCode: 0n, type: 'bogus' }), {
code: 'ERR_INVALID_ARG_VALUE',
});
throws(() => new QuicError('msg', { errorCode: 0n, type: 42 }), {
code: 'ERR_INVALID_ARG_VALUE',
});
// `code` (Node.js error code) must be a string when supplied.
throws(() => new QuicError('msg', { errorCode: 0n, code: 42 }), {
code: 'ERR_INVALID_ARG_TYPE',
});
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 });
strictEqual(err.message, 'something broke');
strictEqual(err.code, 'ERR_QUIC_STREAM_ABORTED');
strictEqual(err.errorCode, 0x42n);
strictEqual(err.type, 'application');
ok(err instanceof Error);
ok(err instanceof QuicError);
}
// 2. Number errorCode, coerced to bigint.
{
const err = new QuicError('numeric code', { errorCode: 5 });
strictEqual(err.errorCode, 5n);
strictEqual(typeof err.errorCode, 'bigint');
}
// 3. Boundary: 0n is allowed.
{
const err = new QuicError('zero', { errorCode: 0n });
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 });
strictEqual(err.errorCode, max);
}
// 5. Explicit type='transport'.
{
const err = new QuicError('transport-level', {
errorCode: 0x1n,
type: 'transport',
});
strictEqual(err.type, 'transport');
}
// 6. Explicit type='application' (default).
{
const err = new QuicError('app-level', {
errorCode: 0x102n,
type: 'application',
});
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',
});
strictEqual(err.code, 'ERR_CUSTOM_QUIC_FAILURE');
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.
throws(() => { err.errorCode = 0xffn; }, { name: 'TypeError' });
throws(() => { err.type = 'transport'; }, { name: 'TypeError' });
strictEqual(err.errorCode, 0x1n);
strictEqual(err.type, 'application');
}