import { test } from 'node:test'; import assert from 'node:assert/strict'; import { mkdtempSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { loadGeo } from '../src/geo.js'; const CSV = [ '1.0.0.0,1.0.0.255,AU', '5.9.0.0,5.9.255.255,DE', '5.10.0.0,5.10.0.9,ZZ', '8.8.8.0,8.8.8.255,US', '255.255.255.0,255.255.255.255,FR', '2001:db8::,2001:db8:ffff:ffff:ffff:ffff:ffff:ffff,AT', '2a01:4f8::,2a01:4f8:ffff:ffff:ffff:ffff:ffff:ffff,DE', '2a02:1::1,2a02:1::ff,CH', '', ].join('\n'); function fixture(content) { const dir = mkdtempSync(join(tmpdir(), 'geo-')); const f = join(dir, 'geo.csv'); writeFileSync(f, content); return f; } test('IPv4: Treffer und Grenzen', () => { const g = loadGeo(fixture(CSV)); assert.equal(g.size, 8); assert.equal(g.lookup('5.9.0.0'), 'DE'); assert.equal(g.lookup('5.9.123.4'), 'DE'); assert.equal(g.lookup('5.9.255.255'), 'DE'); assert.equal(g.lookup('5.8.255.255'), ''); assert.equal(g.lookup('5.10.0.0'), ''); assert.equal(g.lookup('1.0.0.255'), 'AU'); assert.equal(g.lookup('1.0.1.0'), ''); assert.equal(g.lookup('0.0.0.1'), ''); assert.equal(g.lookup('255.255.255.255'), 'FR'); assert.equal(g.lookup('8.8.8.8'), 'US'); }); test('IPv6: Treffer, Grenzen, Kurzschreibweise', () => { const g = loadGeo(fixture(CSV)); assert.equal(g.lookup('2a01:4f8::'), 'DE'); assert.equal(g.lookup('2a01:4f8:1:2:3:4:5:6'), 'DE'); assert.equal(g.lookup('2A01:04F8:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF'), 'DE'); assert.equal(g.lookup('2a01:4f9::'), ''); assert.equal(g.lookup('2a01:4f7:ffff:ffff:ffff:ffff:ffff:ffff'), ''); assert.equal(g.lookup('2001:db8::1'), 'AT'); assert.equal(g.lookup('2a02:1::1'), 'CH'); assert.equal(g.lookup('2a02:1::ff'), 'CH'); assert.equal(g.lookup('2a02:1::100'), ''); assert.equal(g.lookup('2a02:1::'), ''); assert.equal(g.lookup('::1'), ''); }); test('IPv4-gemappte IPv6-Adressen zählen als IPv4', () => { const g = loadGeo(fixture(CSV)); assert.equal(g.lookup('::ffff:5.9.1.1'), 'DE'); assert.equal(g.lookup('::FFFF:8.8.8.8'), 'US'); assert.equal(g.lookup('::ffff:808:808'), 'US'); assert.equal(g.lookup('::ffff:9.9.9.9'), ''); }); test('ZZ, ungültige Eingaben, unsortierte Datei', () => { const g = loadGeo(fixture(CSV)); assert.equal(g.lookup('5.10.0.5'), ''); for (const bad of ['', 'abc', '1.2.3', '256.1.1.1', '1.2.3.4.5', '2001:db8::zz', ':::', '1::2::3', null, undefined, 42]) { assert.equal(g.lookup(bad), '', String(bad)); } const u = loadGeo(fixture('8.8.8.0,8.8.8.255,US\n1.0.0.0,1.0.0.255,AU\n2a01:4f8::,2a01:4f8::ff,DE\n2001:db8::,2001:db8::ff,AT\n')); assert.equal(u.lookup('1.0.0.7'), 'AU'); assert.equal(u.lookup('8.8.8.8'), 'US'); assert.equal(u.lookup('2001:db8::7'), 'AT'); assert.equal(u.lookup('2a01:4f8::7'), 'DE'); }); test('fehlende Datei: alles leer, eine Warnung, kein Absturz', () => { const warns = []; const orig = console.warn; console.warn = (...a) => warns.push(a.join(' ')); try { const g = loadGeo('/nicht/vorhanden/geo.csv'); assert.equal(g.lookup('5.9.1.1'), ''); assert.equal(g.lookup('2a01:4f8::1'), ''); assert.equal(g.size, 0); } finally { console.warn = orig; } assert.equal(warns.length, 1); assert.match(warns[0], /nicht lesbar/); }); test('600k Zeilen: lädt zügig, Heap bleibt klein', () => { const lines = []; for (let i = 0; i < 400000; i++) lines.push(`${(i * 4096) >>> 24}.${((i * 4096) >>> 16) & 255}.${((i * 4096) >>> 8) & 255}.0,${(i * 4096) >>> 24}.${((i * 4096) >>> 16) & 255}.${((i * 4096 + 255) >>> 8) & 255}.255,${i % 7 ? 'DE' : 'AT'}`); for (let i = 0; i < 200000; i++) lines.push(`2a00:${(i >> 8).toString(16)}:${(i & 255).toString(16)}::,2a00:${(i >> 8).toString(16)}:${(i & 255).toString(16)}:ffff:ffff:ffff:ffff:ffff,${i % 5 ? 'DE' : 'FR'}`); const f = fixture(lines.join('\n')); lines.length = 0; globalThis.gc?.(); const before = process.memoryUsage().heapUsed; const rss0 = process.memoryUsage().rss; const t0 = Date.now(); const g = loadGeo(f); const ms = Date.now() - t0; globalThis.gc?.(); const grow = (process.memoryUsage().heapUsed - before) / 1048576; console.log(`# geo 600k: ${ms} ms, Heap +${grow.toFixed(1)} MB, RSS +${((process.memoryUsage().rss - rss0) / 1048576).toFixed(0)} MB, Zeilen ${g.size}`); assert.equal(g.size, 600000); assert.equal(g.lookup('0.0.16.5'), 'DE'); assert.equal(g.lookup('2a00:0:1::5'), 'DE'); assert.ok(grow < 100, `Heap +${grow} MB`); });