Files
MyIP/tests/asn-input.test.js
T
jason5ng32andClaude Fable 5.1 d5715104d5 Fix(asn): never cache an upstream fault as an empty answer
Review findings on the ASN Profile work, mostly one class of bug: a failed
upstream read as "no data", which the 7-day edge cache would then pin.

- AS Rank: a 200 carrying GraphQL errors is a failure, not an unknown AS.
- Radar: an answer built from partly failed segments is still shown but
  flagged `incomplete` and kept out of the cache, on the aggregate and on
  the standalone asn view. A 404 is Radar's "no such AS" and counts as
  empty, as does a segment that simply has no figures.
- Reputation: a malformed or non-200 answer is an error.
- Prefixes: a payload without `prefix_origins` is an error; [] stays valid.
- Radar is `empty` when only local CAIDA counts are present.
- requireValidASN enforces AS1–AS4294967295 and canonicalizes; the range
  rule lives once in common/asn-input.js, shared with the frontend.
- The PeeringDB listing lookup goes through fetchUpstream.
- New functions follow the const-arrow convention; test stubs compare
  parsed hostnames instead of URL substrings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 23:43:55 +08:00

77 lines
2.6 KiB
JavaScript

// Tests for common/asn-input.js — the AS-number grammar shared by the ASN
// guards, the Whois tool (prefix required) and ASN Profile (prefix
// optional) — and its frontend/utils/asn-input.js re-export.
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { MAX_ASN, parseAsnInput } from '../common/asn-input.js';
import * as bridge from '../frontend/utils/asn-input.js';
describe('frontend bridge', () => {
it('re-exports the shared implementation', () => {
assert.equal(bridge.parseAsnInput, parseAsnInput);
assert.equal(bridge.MAX_ASN, MAX_ASN);
});
});
describe('parseAsnInput', () => {
it('accepts AS-prefixed and bare numbers, any case, trimmed', () => {
assert.equal(parseAsnInput('AS13335'), 13335);
assert.equal(parseAsnInput('as13335'), 13335);
assert.equal(parseAsnInput('As13335'), 13335);
assert.equal(parseAsnInput('13335'), 13335);
assert.equal(parseAsnInput(' AS64500 '), 64500);
assert.equal(parseAsnInput(13335), 13335);
});
it('normalizes leading zeros', () => {
assert.equal(parseAsnInput('AS013335'), 13335);
});
it('honours the 32-bit range and rejects AS0', () => {
assert.equal(parseAsnInput(String(MAX_ASN)), 4294967295);
assert.equal(parseAsnInput('AS4294967296'), null);
assert.equal(parseAsnInput('AS0'), null);
assert.equal(parseAsnInput('0'), null);
});
it('rejects more than ten digits even when the value fits', () => {
assert.equal(parseAsnInput('00000013335'), null);
});
it('rejects anything else', () => {
for (const raw of ['', 'AS', 'AS 13335', 'ASN13335', '13335a', '-1', '1.5', 'AS13335/24', null, undefined, {}]) {
assert.equal(parseAsnInput(raw), null, String(raw));
}
});
});
describe('parseAsnInput — requirePrefix', () => {
const strict = (raw) => parseAsnInput(raw, { requirePrefix: true });
it('accepts the AS prefix in any case', () => {
assert.equal(strict('AS13335'), 13335);
assert.equal(strict('as13335'), 13335);
assert.equal(strict('As13335'), 13335);
assert.equal(strict(' aS64500 '), 64500);
});
it('rejects a bare number', () => {
assert.equal(strict('13335'), null);
assert.equal(strict(13335), null);
});
it('keeps the same range rules', () => {
assert.equal(strict('AS4294967295'), MAX_ASN);
assert.equal(strict('AS4294967296'), null);
assert.equal(strict('AS0'), null);
assert.equal(strict('AS00000013335'), null);
});
it('does not take IPs or domains', () => {
for (const raw of ['1.1.1.1', '2606:4700::1111', 'example.com', 'as13335.net']) {
assert.equal(strict(raw), null, raw);
}
});
});