Files
devplacepy/tests/unit/services/nodes/geo.py
T
retoorandClaude Sonnet 5.5 02ab747cd0
DevPlace CI / test (push) Failing after 44m45s
Code Farm: per-plot auto-replant pause and crop-lock check; community sync and node work
Continuous Delivery could never be turned off once a crop was planted. Add a
one-shot per-plot "stop after harvest" flag (POST /game/plot/stop, Devii tool
game_plot_stop, game_plots.stop_after_harvest), cleared by the next plant,
autoreplant claim or Refactor. Autoreplant now also respects crop locks through
the shared _crop_lock predicate. Plan and measurements in happyplanting.md.

Also includes the pending community sync, node network and related work in the
tree; the full suite passed (4999) with all of it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 02:23:43 +02:00

127 lines
5.1 KiB
Python

# retoor <retoor@molodetz.nl>
import gzip
from pathlib import Path
import pytest
from devplacepy.services.nodes import geo
from devplacepy.services.nodes.geo import GeoDatabase, GeoError, build, pack
ASN_ROWS = [
("1.0.0.0", "1.0.0.255", 13335, "Cloudflare, Inc."),
("8.8.8.0", "8.8.8.255", 15169, "Google LLC"),
("20.0.0.0", "20.0.0.255", 64500, "Home Fiber BV"),
("2001:db8::", "2001:db8::ffff", 64501, "Mobile Wireless NV"),
]
COUNTRY_ROWS = [
("1.0.0.0", "1.0.0.255", "AU"),
("8.8.8.0", "8.8.8.255", "US"),
("20.0.0.0", "20.0.0.255", "NL"),
("2001:db8::", "2001:db8::ffff", "ZZ"),
]
def lines(rows):
return [",".join(f'"{part}"' if "," in str(part) else str(part) for part in row) for row in rows]
def make(tmp_path, monkeypatch, checks=()):
monkeypatch.setattr(geo, "SPEC", geo.GeoSpec(min_asn_rows=1, min_country_rows=1, checks=checks))
target = tmp_path / "geo.db"
built = build(lines(ASN_ROWS), lines(COUNTRY_ROWS), target, "2026-10")
geo.install(built, target)
return GeoDatabase(target)
def test_pack_normalises_v4_mapped_and_rejects_garbage():
assert pack("::ffff:8.8.8.8") == pack("8.8.8.8")
assert pack("[2001:db8::1]")[0] == 6
with pytest.raises(ValueError):
pack("nope")
def test_lookup_hits_first_last_and_middle_of_a_range(tmp_path, monkeypatch):
database = make(tmp_path, monkeypatch)
for address in ("1.0.0.0", "1.0.0.77", "1.0.0.255"):
found = database.lookup(address)
assert (found.asn, found.country, found.asn_class) == (13335, "AU", "datacenter")
def test_lookup_between_and_outside_ranges_is_unknown(tmp_path, monkeypatch):
database = make(tmp_path, monkeypatch)
for address in ("1.0.1.0", "0.255.255.255", "9.9.9.9", "255.255.255.255"):
assert database.lookup(address).asn_class == "unknown"
def test_ipv6_and_zz_country(tmp_path, monkeypatch):
database = make(tmp_path, monkeypatch)
found = database.lookup("2001:db8::1234")
assert found.asn == 64501 and found.country == "" and found.asn_class == "mobile"
assert database.lookup("2001:db9::1").asn == 0
def test_classification_of_a_residential_range(tmp_path, monkeypatch):
assert make(tmp_path, monkeypatch).lookup("20.0.0.9").asn_class == "residential"
def test_missing_file_and_bad_input_degrade_to_unknown(tmp_path):
database = GeoDatabase(tmp_path / "absent.db")
assert not database.available()
assert database.lookup("8.8.8.8").asn_class == "unknown"
assert database.status()["available"] is False
assert GeoDatabase(tmp_path / "absent.db").lookup("").asn_class == "unknown"
def test_a_too_small_dataset_is_refused_and_leaves_no_file(tmp_path, monkeypatch):
monkeypatch.setattr(geo, "SPEC", geo.GeoSpec(min_asn_rows=10, min_country_rows=10, checks=()))
target = tmp_path / "geo.db"
with pytest.raises(GeoError):
build(lines(ASN_ROWS), lines(COUNTRY_ROWS), target, "2026-10")
assert not target.exists() and not target.with_suffix(".tmp").exists()
def test_a_failed_sanity_check_refuses_and_keeps_the_current_database(tmp_path, monkeypatch):
current = make(tmp_path, monkeypatch)
before = current.path.read_bytes()
monkeypatch.setattr(geo, "SPEC", geo.GeoSpec(min_asn_rows=1, min_country_rows=1, checks=(("8.8.8.8", 99999),)))
with pytest.raises(GeoError):
build(lines(ASN_ROWS), lines(COUNTRY_ROWS), tmp_path / "geo.db", "2026-11")
assert current.path.read_bytes() == before
def test_the_row_cap_is_enforced(tmp_path, monkeypatch):
monkeypatch.setattr(geo, "SPEC", geo.GeoSpec(max_rows=2, min_asn_rows=1, min_country_rows=1, checks=()))
with pytest.raises(GeoError):
build(lines(ASN_ROWS), lines(COUNTRY_ROWS), tmp_path / "geo.db", "2026-10")
def test_malformed_rows_are_skipped_not_fatal(tmp_path, monkeypatch):
monkeypatch.setattr(geo, "SPEC", geo.GeoSpec(min_asn_rows=1, min_country_rows=1, checks=()))
asn = lines(ASN_ROWS) + ["garbage", "a,b,c,d", "1.2.3.4,1.2.3.5,notanumber,x"]
built = build(asn, lines(COUNTRY_ROWS), tmp_path / "geo.db", "2026-10")
assert built["asn_rows"] == len(ASN_ROWS)
def test_status_reports_metadata_and_attribution(tmp_path, monkeypatch):
status = make(tmp_path, monkeypatch).status()
assert status["available"] and status["month"] == "2026-10" and "DB-IP" in status["attribution"]
def test_months_walk_back_over_a_year_boundary():
from datetime import datetime, timezone
assert geo.months(datetime(2026, 1, 15, tzinfo=timezone.utc)) == ["2026-01", "2025-12", "2025-11"]
def test_build_from_files_reads_gzip(tmp_path, monkeypatch):
monkeypatch.setattr(geo, "SPEC", geo.GeoSpec(min_asn_rows=1, min_country_rows=1, checks=()))
asn_file, country_file = tmp_path / "a.csv.gz", tmp_path / "c.csv.gz"
with gzip.open(asn_file, "wt") as handle:
handle.write("\n".join(lines(ASN_ROWS)))
with gzip.open(country_file, "wt") as handle:
handle.write("\n".join(lines(COUNTRY_ROWS)))
built = geo.build_from_files(asn_file, country_file, tmp_path / "geo.db", "2026-10")
geo.install(built, tmp_path / "geo.db")
assert GeoDatabase(tmp_path / "geo.db").lookup("8.8.8.8").asn == 15169