Files
5chan/src/lib/__tests__/peer-geo.test.ts
T
Tommaso CasaburiandGitHub 804c14fc35 Improve P2P stats peer locations and Kubo display (#1139)
* feat(p2p stats): plot peers by ip location

* fix(p2p stats): show own node map marker

* feat(p2p stats): support full node peers

* fix(p2p stats): clean up kubo stats

* fix(p2p stats): address review feedback
2026-05-24 17:32:05 +07:00

246 lines
9.2 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import { COUNTRY_CENTROIDS } from '../../data/country-centroids';
import {
extractIpFromAddress,
extractIpv4FromAddress,
extractIpv6FromAddress,
fetchOwnIpCountryCode,
fetchOwnPublicEndpoint,
fetchPeerMapLocation,
getApproximateCountryCode,
getApproximateLatLon,
getFirstPublicIpFromAddresses,
isPrivateOrReservedIpv4,
} from '../peer-geo';
const addr = (ip: string) => `/ip4/${ip}/tcp/4001/ws/p2p/12D3KooWExample`;
describe('extractIpv4FromAddress', () => {
it('extracts the IPv4 from a multiaddr', () => {
expect(extractIpv4FromAddress('/ip4/147.75.84.175/tcp/4001/ws')).toBe('147.75.84.175');
});
it('returns null for IPv6 and plain DNS multiaddrs', () => {
expect(extractIpv4FromAddress('/ip6/2606:4700::1111/tcp/4001')).toBeNull();
expect(extractIpv4FromAddress('/dns4/relay.example.org/tcp/443/wss')).toBeNull();
});
it('extracts an IPv4 embedded with dashes in a DNS hostname', () => {
expect(extractIpv4FromAddress('/dns4/91-234-56-78.host.example/tcp/443/wss')).toBe('91.234.56.78');
expect(extractIpv4FromAddress('/dns4/ip-203-0-113-9.provider.net/tcp/443/wss')).toBe('203.0.113.9');
});
});
describe('extractIpv6FromAddress', () => {
it('extracts the IPv6 from a multiaddr', () => {
expect(extractIpv6FromAddress('/ip6/2001:4860:4860::8888/tcp/4001/ws')).toBe('2001:4860:4860::8888');
expect(extractIpFromAddress('/ip6/2001:4860:4860::8888/tcp/4001/ws')).toBe('2001:4860:4860::8888');
});
});
describe('isPrivateOrReservedIpv4', () => {
it('flags private and reserved ranges', () => {
for (const ip of ['10.0.0.1', '172.16.5.4', '192.168.1.10', '127.0.0.1', '169.254.1.1', '100.64.0.1', '0.0.0.0', '239.255.0.1']) {
expect(isPrivateOrReservedIpv4(ip)).toBe(true);
}
});
it('treats public addresses as routable', () => {
for (const ip of ['8.8.8.8', '147.75.84.175', '1.1.1.1', '80.80.80.80']) {
expect(isPrivateOrReservedIpv4(ip)).toBe(false);
}
});
});
describe('getApproximateLatLon', () => {
it('returns null when the peer cannot be placed offline', () => {
expect(getApproximateLatLon('/ip4/192.168.1.5/tcp/4001')).toBeNull();
expect(getApproximateLatLon('/dns4/relay.example.org/tcp/443/wss')).toBeNull();
expect(getApproximateLatLon('/ip6/2606:4700::1111/tcp/4001')).toBeNull();
});
it('is deterministic for the same address', () => {
expect(getApproximateLatLon(addr('8.8.8.8'))).toEqual(getApproximateLatLon(addr('8.8.8.8')));
});
it('snaps a peer to the centroid of its flag country', () => {
for (const ip of ['8.8.8.8', '80.80.80.80', '1.1.1.1', '41.0.0.1', '200.0.0.1', '194.110.247.146', '91.234.199.189']) {
const country = getApproximateCountryCode(addr(ip));
expect(country).toBeDefined();
const centroid = COUNTRY_CENTROIDS[country!];
expect(centroid).toBeDefined();
const loc = getApproximateLatLon(addr(ip))!;
// Marker sits at the country centroid, within the small placement jitter.
expect(Math.abs(loc.lat - centroid.lat)).toBeLessThanOrEqual(1);
expect(Math.abs(loc.lon - centroid.lon)).toBeLessThanOrEqual(1.3);
}
});
it('stays within valid coordinate bounds', () => {
const loc = getApproximateLatLon(addr('203.0.113.7'));
expect(loc).not.toBeNull();
expect(loc!.lat).toBeGreaterThanOrEqual(-85);
expect(loc!.lat).toBeLessThanOrEqual(85);
expect(loc!.lon).toBeGreaterThanOrEqual(-180);
expect(loc!.lon).toBeLessThanOrEqual(180);
});
});
describe('getFirstPublicIpFromAddresses', () => {
it('returns the first public IP from multiaddrs', () => {
expect(getFirstPublicIpFromAddresses(['/ip4/127.0.0.1/tcp/4001', '/ip4/147.75.84.175/tcp/4001/ws'])).toBe('147.75.84.175');
expect(getFirstPublicIpFromAddresses(['/ip4/127.0.0.1/tcp/4001', '/ip6/2001:4860:4860::8888/tcp/443'])).toBe('2001:4860:4860::8888');
});
it('returns undefined when only private addresses are present', () => {
expect(getFirstPublicIpFromAddresses(['/ip4/127.0.0.1/tcp/4001', '/ip4/10.0.0.5/tcp/4001', '/ip6/fd00::1/tcp/4001'])).toBeUndefined();
});
});
describe('fetchOwnPublicEndpoint', () => {
it('caches the fetched public endpoint', async () => {
const fetchMock = vi.fn(async (url: string | URL | Request) => ({
ok: true,
json: async () =>
String(url).startsWith('https://free.freeipapi.com/api/json/')
? {
cityName: 'Mountain View',
countryCode: 'US',
latitude: 37.422,
longitude: -122.085,
regionName: 'California',
}
: { country: 'US', ip: '2001:4860:4860::8888' },
}));
vi.stubGlobal('fetch', fetchMock);
await expect(fetchOwnPublicEndpoint()).resolves.toMatchObject({
countryCode: 'us',
ip: '2001:4860:4860::8888',
location: {
countryCode: 'us',
label: 'Mountain View, California, US',
lat: 37.422,
lon: -122.085,
source: 'geoip',
},
});
await expect(fetchOwnPublicEndpoint()).resolves.toMatchObject({ countryCode: 'us', ip: '2001:4860:4860::8888' });
expect(fetchMock).toHaveBeenCalledTimes(2);
vi.unstubAllGlobals();
});
});
describe('fetchOwnIpCountryCode', () => {
it("resolves and caches the accurate country for the node's own ip", async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ country: 'VN', ip: '172.225.56.8' }),
});
vi.stubGlobal('fetch', fetchMock);
await expect(fetchOwnIpCountryCode('172.225.56.8')).resolves.toBe('vn');
await expect(fetchOwnIpCountryCode('172.225.56.8')).resolves.toBe('vn');
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock.mock.calls[0][0]).toBe('https://api.country.is/172.225.56.8');
vi.unstubAllGlobals();
});
it('does not cache a result from an aborted lookup', async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ country: 'VN', ip: '203.0.113.50' }),
});
vi.stubGlobal('fetch', fetchMock);
const controller = new AbortController();
controller.abort();
// An aborted request is cancellation, not a real result, so it must not be cached.
await fetchOwnIpCountryCode('203.0.113.50', controller.signal);
// A later non-aborted call must perform a fresh lookup instead of a cached blank.
await expect(fetchOwnIpCountryCode('203.0.113.50')).resolves.toBe('vn');
expect(fetchMock).toHaveBeenCalledTimes(2);
vi.unstubAllGlobals();
});
});
describe('fetchPeerMapLocation', () => {
it('resolves and caches a city-level peer location', async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({
cityName: 'Haarlem',
countryCode: 'NL',
ipAddress: '91.234.199.189',
latitude: 52.3874,
longitude: 4.64622,
regionName: 'North Holland',
}),
});
vi.stubGlobal('fetch', fetchMock);
await expect(fetchPeerMapLocation('/ip4/91.234.199.189/tcp/4001')).resolves.toMatchObject({
countryCode: 'nl',
label: 'Haarlem, North Holland, NL',
lat: 52.3874,
lon: 4.64622,
source: 'geoip',
});
await expect(fetchPeerMapLocation('/ip4/91.234.199.189/tcp/4001')).resolves.toMatchObject({
lat: 52.3874,
lon: 4.64622,
source: 'geoip',
});
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock.mock.calls[0][0]).toBe('https://free.freeipapi.com/api/json/91.234.199.189');
vi.unstubAllGlobals();
});
it('falls back to the offline country estimate when lookup fails', async () => {
const fetchMock = vi.fn().mockResolvedValue({ ok: false });
vi.stubGlobal('fetch', fetchMock);
const location = await fetchPeerMapLocation(addr('80.80.80.80'));
const centroid = COUNTRY_CENTROIDS[location!.countryCode!];
expect(location).toMatchObject({ source: 'coarse' });
expect(centroid).toBeDefined();
expect(Math.abs(location!.lat - centroid.lat)).toBeLessThanOrEqual(1);
expect(Math.abs(location!.lon - centroid.lon)).toBeLessThanOrEqual(1.3);
vi.unstubAllGlobals();
});
it('does not call GeoIP for private peer addresses', async () => {
const fetchMock = vi.fn();
vi.stubGlobal('fetch', fetchMock);
await expect(fetchPeerMapLocation('/ip4/10.0.0.1/tcp/4001')).resolves.toBeUndefined();
expect(fetchMock).not.toHaveBeenCalled();
vi.unstubAllGlobals();
});
});
describe('getApproximateCountryCode', () => {
it('returns undefined when the peer cannot be placed offline', () => {
expect(getApproximateCountryCode('/ip4/10.0.0.1/tcp/4001')).toBeUndefined();
expect(getApproximateCountryCode('/dns4/relay.example.org/tcp/443/wss')).toBeUndefined();
expect(getApproximateCountryCode('/ip6/2606:4700::1111/tcp/4001')).toBeUndefined();
});
it('is deterministic and returns a known 2-letter code for public peers', () => {
const code = getApproximateCountryCode(addr('8.8.8.8'));
expect(code).toBe(getApproximateCountryCode(addr('8.8.8.8')));
expect(code).toMatch(/^[a-z]{2}$/);
});
it('derives a code from an IPv4 embedded in a DNS hostname', () => {
expect(getApproximateCountryCode('/dns4/91-234-56-78.host.example/tcp/443/wss')).toMatch(/^[a-z]{2}$/);
});
});