import { readFile } from 'node:fs/promises' import path from 'node:path' import { fileURLToPath } from 'node:url' import test from 'brittle' import b4a from 'b4a' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor async function loadWhoisBin() { const runtime = await readFile(path.join(__dirname, '../lib/runtime.js'), 'utf8') const body = await readFile(path.join(__dirname, '../src/whois.js'), 'utf8') return new AsyncFunction( 'ctx', 'argv', `${runtime}\n${body}\nif (typeof run === 'function') return await run(ctx, argv)\n` ) } function createCtx() { const logs = [] const errs = [] return { b4a, exitCode: 0, vfs: { env: {} }, console: { log: (m) => logs.push(String(m)), error: (m) => errs.push(String(m)) }, _logs: logs, _errs: errs } } function responseJson(body, status = 200) { return new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/rdap+json' } }) } async function withPatchedFetch(stub, fn) { const oldFetch = globalThis.fetch globalThis.fetch = stub try { return await fn() } finally { globalThis.fetch = oldFetch } } test('/bin/whois domain lookup prints summary', async (t) => { const run = await loadWhoisBin() const ctx = createCtx() const calls = [] await withPatchedFetch(async (url) => { calls.push(String(url)) if (String(url).includes('/rdap/dns.json')) { return responseJson({ services: [[['com'], ['https://rdap.test/']]] }) } return responseJson({ objectClassName: 'domain', ldhName: 'example.com', handle: 'EXAMPLE1', status: ['active'], nameservers: [{ ldhName: 'ns1.example.com' }], entities: [ { roles: ['registrar'], vcardArray: ['vcard', [['fn', {}, 'text', 'Test Registrar']]] } ] }) }, async () => { await run(ctx, ['whois', 'example.com']) }) t.is(ctx.exitCode, 0) t.alike(calls, [ 'https://data.iana.org/rdap/dns.json', 'https://rdap.test/domain/example.com' ]) t.ok(ctx._logs.join('\n').includes('Registrar: Test Registrar')) t.ok(ctx._logs.join('\n').includes('Nameservers: ns1.example.com')) }) test('/bin/whois IP lookup resolves via ipv4 bootstrap', async (t) => { const run = await loadWhoisBin() const ctx = createCtx() await withPatchedFetch(async (url) => { if (String(url).includes('/rdap/ipv4.json')) { return responseJson({ services: [[['203.0.113.0/24'], ['https://arin.test/rdap']]] }) } return responseJson({ objectClassName: 'ip network', startAddress: '203.0.113.0', endAddress: '203.0.113.255', ipVersion: 'v4', handle: 'NET-203-0-113-0-1' }) }, async () => { await run(ctx, ['whois', '203.0.113.10']) }) t.is(ctx.exitCode, 0) t.ok(ctx._logs.join('\n').includes('IP Version: v4')) t.ok(ctx._logs.join('\n').includes('Range: 203.0.113.0 - 203.0.113.255')) }) test('/bin/whois ASN lookup resolves autnum and supports --json', async (t) => { const run = await loadWhoisBin() const ctx = createCtx() await withPatchedFetch(async (url) => { if (String(url).includes('/rdap/asn.json')) { return responseJson({ services: [[['13335-13335'], ['https://asn.test/']]] }) } return responseJson({ objectClassName: 'autnum', handle: 'AS13335', startAutnum: 13335, endAutnum: 13335, name: 'CLOUDFLARENET' }) }, async () => { await run(ctx, ['whois', '--json', 'AS13335']) }) t.is(ctx.exitCode, 0) t.ok(ctx._logs.join('\n').includes('"objectClassName": "autnum"')) t.absent(ctx._logs.join('\n').includes('ASN Range:')) }) test('/bin/whois exits non-zero when no bootstrap service matches', async (t) => { const run = await loadWhoisBin() const ctx = createCtx() await withPatchedFetch(async (url) => { if (String(url).includes('/rdap/dns.json')) { return responseJson({ services: [[['net'], ['https://rdap.test/']]] }) } return responseJson({}) }, async () => { await run(ctx, ['whois', 'example.com']) }) t.is(ctx.exitCode, 1) t.ok(ctx._errs.join('\n').includes('no RDAP service found')) }) test('/bin/whois handles upstream HTTP failures', async (t) => { const run = await loadWhoisBin() const ctx = createCtx() await withPatchedFetch(async (url) => { if (String(url).includes('/rdap/dns.json')) { return responseJson({ services: [[['com'], ['https://rdap.test/']]] }) } return responseJson({ errorCode: 404 }, 404) }, async () => { await run(ctx, ['whois', 'example.com']) }) t.is(ctx.exitCode, 1) t.ok(ctx._errs.join('\n').includes('HTTP error')) }) test('/bin/whois validates arguments and help output', async (t) => { const run = await loadWhoisBin() const helpCtx = createCtx() await run(helpCtx, ['whois', '--help']) t.is(helpCtx.exitCode, 0) t.ok(helpCtx._logs.join('\n').includes('usage: whois')) const badCtx = createCtx() await run(badCtx, ['whois', '-Z']) t.is(badCtx.exitCode, 1) t.ok(badCtx._errs.join('\n').includes('unknown option')) const missingCtx = createCtx() await run(missingCtx, ['whois']) t.is(missingCtx.exitCode, 1) t.ok(missingCtx._errs.join('\n').includes('expected exactly one target')) })