diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..69a00a2 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,22 @@ +name: Tests + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install dependencies + run: pip install requests beautifulsoup4 pytest + + - name: Run tests + run: pytest tests/ -v diff --git a/requirements.txt b/requirements.txt index 612d278..a38cd3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ requests>=2.31,<3 beautifulsoup4>=4.12,<5 cartopy>=0.22,<1 matplotlib>=3.8,<4 +pytest>=7,<9 diff --git a/scripts/update_gpx.py b/scripts/update_gpx.py index 3c23ee6..4af470e 100644 --- a/scripts/update_gpx.py +++ b/scripts/update_gpx.py @@ -140,7 +140,7 @@ def sort_key(wpt_block): def write_gpx(existing_text, wpt_blocks): """Write the GPX file with sorted waypoints, preserving header/footer.""" header = '\n' - header += '\n' + header += '\n' footer = "\n" sorted_blocks = sorted(wpt_blocks, key=sort_key) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..7cd7533 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,25 @@ +"""Shared fixtures for the test suite.""" + +from pathlib import Path + +import pytest + +FIXTURES = Path(__file__).parent / "fixtures" + + +@pytest.fixture() +def naming_html(): + """Minimal HTML with protocol list items.""" + return (FIXTURES / "naming_page.html").read_text() + + +@pytest.fixture() +def sample_gpx_text(): + """GPX text with 4 sample waypoints.""" + return (FIXTURES / "tezos_sample.gpx").read_text() + + +@pytest.fixture() +def sample_gpx_path(): + """Path to the sample GPX fixture.""" + return str(FIXTURES / "tezos_sample.gpx") diff --git a/tests/fixtures/naming_page.html b/tests/fixtures/naming_page.html new file mode 100644 index 0000000..7929c99 --- /dev/null +++ b/tests/fixtures/naming_page.html @@ -0,0 +1,14 @@ + + + + + + diff --git a/tests/fixtures/tezos_sample.gpx b/tests/fixtures/tezos_sample.gpx new file mode 100644 index 0000000..be065d0 --- /dev/null +++ b/tests/fixtures/tezos_sample.gpx @@ -0,0 +1,19 @@ + + + + Athens, Greece + Capital of Greece + + + Babylon, Iraq + Ancient city in Iraq + + + Carthage, Tunisia + Ancient city in Tunisia + + + Edo (Tokyo), Japan + Former name of Tokyo, Japan + + diff --git a/tests/test_generate_map.py b/tests/test_generate_map.py new file mode 100644 index 0000000..b67688b --- /dev/null +++ b/tests/test_generate_map.py @@ -0,0 +1,68 @@ +"""Tests for scripts/generate_map.py pure functions.""" + +from scripts.generate_map import match_protocols, short_label + + +# ---------- short_label ---------- + +def test_short_label_strips_country(): + assert short_label("Athens, Greece") == "Athens" + + +def test_short_label_edo(): + assert short_label("Edo (Tokyo), Japan") == "Edo" + + +def test_short_label_quebec(): + assert short_label("Quebec City, Canada") == "Quebec" + + +def test_short_label_rio(): + assert short_label("Rio de Janeiro, Brazil") == "Rio" + + +def test_short_label_no_country(): + assert short_label("Athens") == "Athens" + + +def test_short_label_multi_part(): + assert short_label("Lima, Peru") == "Lima" + + +# ---------- match_protocols ---------- + +def test_match_protocols_basic(): + waypoints = [ + ("Athens, Greece", 37.9838, 23.7275), + ("Babylon, Iraq", 32.5439, 44.4208), + ] + protocols = { + "Athens": {"number": 4, "hash": "PtAthens", "mainnet": True}, + "Babylon": {"number": 5, "hash": "PsBabylon", "mainnet": True}, + } + matched = match_protocols(waypoints, protocols) + assert "Athens" in matched + assert "Babylon" in matched + assert matched["Athens"]["number"] == 4 + + +def test_match_protocols_parisc_alias(): + waypoints = [("Paris, France", 48.8566, 2.3522)] + protocols = { + "ParisC": {"number": 20, "hash": "PsParis", "mainnet": True}, + } + matched = match_protocols(waypoints, protocols) + assert "Paris" in matched + assert matched["Paris"]["number"] == 20 + + +def test_match_protocols_unmatched_waypoint(): + waypoints = [("Unknown City, Nowhere", 0.0, 0.0)] + protocols = {"Athens": {"number": 4}} + matched = match_protocols(waypoints, protocols) + assert len(matched) == 0 + + +def test_match_protocols_empty(): + matched = match_protocols([], {}) + assert matched == {} diff --git a/tests/test_update_gpx.py b/tests/test_update_gpx.py new file mode 100644 index 0000000..70510d7 --- /dev/null +++ b/tests/test_update_gpx.py @@ -0,0 +1,179 @@ +"""Tests for scripts/update_gpx.py pure functions.""" + +from unittest.mock import MagicMock, patch + +from scripts.update_gpx import ( + build_protocols_json, + build_wpt, + desc_for, + format_coord, + gpx_coords_for_protocols, + parse_wpts, + scrape_protocols, + sort_key, +) + + +# ---------- format_coord ---------- + +def test_format_coord_rounds_to_four_decimals(): + assert format_coord(37.98384) == "37.9838" + + +def test_format_coord_pads_short_decimals(): + assert format_coord(10.1) == "10.1000" + + +def test_format_coord_negative(): + assert format_coord(-77.0428) == "-77.0428" + + +# ---------- desc_for ---------- + +def test_desc_for_city_country(): + assert desc_for("Athens, Greece") == "City in Greece" + + +def test_desc_for_no_country(): + assert desc_for("Athens") == "Athens" + + +def test_desc_for_multiple_parts(): + assert desc_for("Quebec City, Quebec, Canada") == "City in Canada" + + +# ---------- build_wpt ---------- + +def test_build_wpt_structure(): + wpt = build_wpt("Athens, Greece", 37.9838, 23.7275) + assert '' in wpt + assert "Athens, Greece" in wpt + assert "City in Greece" in wpt + assert wpt.startswith(" ") + + +# ---------- parse_wpts ---------- + +def test_parse_wpts_extracts_blocks(sample_gpx_text): + blocks = parse_wpts(sample_gpx_text) + assert len(blocks) == 4 + assert "Athens, Greece" in blocks[0] + + +# ---------- sort_key ---------- + +def test_sort_key_extracts_lowercase_name(): + block = ' \n Athens, Greece\n Capital\n ' + assert sort_key(block) == "athens, greece" + + +def test_sort_key_no_name(): + assert sort_key("") == "" + + +# ---------- gpx_coords_for_protocols ---------- + +def test_gpx_coords_for_protocols_basic(sample_gpx_text): + scraped = [(4, "Athens"), (5, "Babylon"), (6, "Carthage")] + coords = gpx_coords_for_protocols(scraped, sample_gpx_text) + assert coords["Athens"] == (37.9838, 23.7275) + assert coords["Babylon"] == (32.5439, 44.4208) + assert coords["Carthage"] == (36.8508, 10.1833) + + +def test_gpx_coords_for_protocols_overrides(sample_gpx_text): + scraped = [(8, "Edo")] + coords = gpx_coords_for_protocols(scraped, sample_gpx_text) + assert coords["Edo"] == (35.6895, 139.6917) + + +def test_gpx_coords_for_protocols_missing(sample_gpx_text): + scraped = [(10, "Granada")] + coords = gpx_coords_for_protocols(scraped, sample_gpx_text) + assert "Granada" not in coords + + +# ---------- build_protocols_json ---------- + +def test_build_protocols_json_mainnet(): + scraped = [(4, "Athens"), (5, "Babylon")] + tzkt_data = { + "Athens": {"hash": "PtAthens", "firstLevel": 1, "activationDate": "2019-05-30T00:58:55Z"}, + "Babylon": {"hash": "PsBabylon", "firstLevel": 2, "activationDate": "2019-10-18T08:18:28Z"}, + } + gpx_coords = {"Athens": (37.9838, 23.7275), "Babylon": (32.5439, 44.4208)} + result = build_protocols_json(scraped, tzkt_data, gpx_coords) + assert result["Athens"]["number"] == 4 + assert result["Athens"]["hash"] == "PtAthens" + assert result["Athens"]["mainnet"] is True + assert result["Athens"]["lat"] == 37.9838 + assert result["Babylon"]["mainnet"] is True + + +def test_build_protocols_json_testnet_only(): + scraped = [(25, "Ushuaia")] + # TzKT is reachable (non-empty) but doesn't contain this protocol. + tzkt_data = {"Athens": {"hash": "PtAthens", "firstLevel": 1, "activationDate": "2019-05-30T00:58:55Z"}} + gpx_coords = {"Ushuaia": (-54.8073, -68.3084)} + testnet_info = {"Ushuaia": {"rpc_url": "https://rpc.ushuaianet.teztnets.com"}} + + with patch("scripts.update_gpx.fetch_protocol_hash", return_value="PsUshuaia"): + result = build_protocols_json(scraped, tzkt_data, gpx_coords, testnet_info) + assert result["Ushuaia"]["mainnet"] is False + assert result["Ushuaia"]["hash"] == "PsUshuaia" + + +def test_build_protocols_json_no_tzkt(): + scraped = [(4, "Athens")] + tzkt_data = {} + gpx_coords = {"Athens": (37.9838, 23.7275)} + result = build_protocols_json(scraped, tzkt_data, gpx_coords) + assert result["Athens"]["mainnet"] is None + assert result["Athens"]["hash"] is None + + +def test_build_protocols_json_no_coords(): + scraped = [(4, "Athens")] + tzkt_data = {"Athens": {"hash": "PtAthens", "firstLevel": 1, "activationDate": "2019-05-30T00:58:55Z"}} + gpx_coords = {} + result = build_protocols_json(scraped, tzkt_data, gpx_coords) + assert result["Athens"]["lat"] is None + assert result["Athens"]["lon"] is None + + +def test_build_protocols_json_alias_map(): + scraped = [(20, "ParisC")] + tzkt_data = { + "Paris C": {"hash": "PsParis", "firstLevel": 100, "activationDate": "2024-06-25T07:30:25Z"}, + } + gpx_coords = {"ParisC": (48.8566, 2.3522)} + result = build_protocols_json(scraped, tzkt_data, gpx_coords) + assert result["ParisC"]["hash"] == "PsParis" + assert result["ParisC"]["mainnet"] is True + + +# ---------- scrape_protocols ---------- + +def test_scrape_protocols_with_fixture(naming_html): + mock_resp = MagicMock() + mock_resp.text = naming_html + mock_resp.raise_for_status = MagicMock() + + with patch("scripts.update_gpx.requests.get", return_value=mock_resp): + result = scrape_protocols("https://example.com/naming.html") + + assert result == [(4, "Athens"), (5, "Babylon"), (6, "Carthage"), (7, "Delphi")] + + +def test_scrape_protocols_skips_below_004(naming_html): + mock_resp = MagicMock() + mock_resp.text = naming_html + mock_resp.raise_for_status = MagicMock() + + with patch("scripts.update_gpx.requests.get", return_value=mock_resp): + result = scrape_protocols("https://example.com/naming.html") + + names = [name for _, name in result] + assert "Genesis" not in names + assert "Demo_noops" not in names diff --git a/tezos.gpx b/tezos.gpx index 91e9525..44dda27 100644 --- a/tezos.gpx +++ b/tezos.gpx @@ -1,5 +1,5 @@ - + Athens, Greece Capital of Greece