#!/usr/bin/env python3
"""Unit tests for the standalone tools (the tangled_* scripts, secrets, gateway-domain).
Run: python3 -m unittest tests.test_tools (from the repo root)
./tests/run.sh (discovers this file automatically)
NO NETWORK. Every HTTP boundary is injected as a fake `_fetch`, so these run offline and
in CI. The point is the DECISION logic — "did the pull get created", "is this backend
valid" — because that is where the defects have actually been.
The reason this file exists: tangled_pr.py judged success only by a response header, so a
create that answered without one read as a failure, the caller retried, and duplicate
pulls #397-#399 were filed for one branch. The tool was never tested; the bug survived
months of daily use and was found by counting pulls, not by running the script.
"""
import os, sys, unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import tangled_pr # noqa: E402
class TestPullNumbers(unittest.TestCase):
def test_extracts_every_linked_pull(self):
html = 'xy'
self.assertEqual(tangled_pr.pull_numbers(html), {417, 416})
def test_deduplicates_repeated_links(self):
html = '/pulls/12 /pulls/12 /pulls/12'
self.assertEqual(tangled_pr.pull_numbers(html), {12})
def test_ignores_the_new_pull_form(self):
# /pulls/new must not parse as a number — it is the page you POST to, not a pull
self.assertEqual(tangled_pr.pull_numbers('/pulls/new /pulls/9'), {9})
def test_empty_and_none_are_empty_not_an_error(self):
# an unreadable page must yield "cannot confirm", never a crash mid-create
self.assertEqual(tangled_pr.pull_numbers(""), set())
self.assertEqual(tangled_pr.pull_numbers(None), set())
class TestPageNamesBranch(unittest.TestCase):
def test_finds_the_branch(self):
self.assertTrue(tangled_pr.page_names_branch("
e2e-pds/R4-real
", "e2e-pds/R4-real"))
def test_absent_branch(self):
self.assertFalse(tangled_pr.page_names_branch("other
", "e2e-pds/R4-real"))
def test_unreadable_page_is_not_a_match(self):
self.assertFalse(tangled_pr.page_names_branch("", "b"))
self.assertFalse(tangled_pr.page_names_branch(None, "b"))
def test_empty_branch_never_matches(self):
# guards against a caller passing "" and matching every page
self.assertFalse(tangled_pr.page_names_branch("anything", ""))
class TestClassify(unittest.TestCase):
"""The regression that matters: a successful create with NO redirect header."""
def test_new_pull_naming_our_branch_is_created(self):
v, n = tangled_pr.classify({1, 2}, {1, 2, 3}, {3: True})
self.assertEqual((v, n), ("created", 3))
def test_created_even_though_the_header_said_nothing(self):
# THE BUG: classify never sees the response at all, so a missing HX-Redirect
# cannot turn a real create into a reported failure.
v, n = tangled_pr.classify({396}, {396, 397}, {397: True})
self.assertEqual(v, "created")
def test_no_new_pull_is_a_real_failure(self):
v, n = tangled_pr.classify({1, 2}, {1, 2}, {})
self.assertEqual((v, n), ("none", None))
def test_someone_elses_pull_is_not_ours(self):
v, n = tangled_pr.classify({1}, {1, 2}, {2: False})
self.assertEqual((v, n), ("unrelated", None))
def test_picks_the_highest_of_several_of_ours(self):
v, n = tangled_pr.classify({1}, {1, 2, 3}, {2: True, 3: True})
self.assertEqual((v, n), ("created", 3))
def test_a_pull_that_vanished_does_not_confuse_it(self):
# after ⊂ before (someone closed+deleted one mid-run): no new pull, so no create
v, n = tangled_pr.classify({1, 2}, {1}, {})
self.assertEqual((v, n), ("none", None))
class TestExistingPullScan(unittest.TestCase):
"""The pre-check that makes a retry harmless instead of duplicating."""
def _fetcher(self, pages):
def _f(url, cookie, timeout=30):
return pages.get(url.rsplit("/", 1)[-1], "")
return _f
def test_finds_an_existing_pull_for_the_branch(self):
f = self._fetcher({"396": "branch: feat/x", "395": "branch: other"})
got = tangled_pr.existing_pull_for("o", "r", "feat/x", "c", {395, 396}, _fetch=f)
self.assertEqual(got, 396)
def test_returns_none_when_the_branch_is_new(self):
f = self._fetcher({"396": "other", "395": "other"})
self.assertIsNone(tangled_pr.existing_pull_for("o", "r", "feat/x", "c", {395, 396}, _fetch=f))
def test_prefers_the_newest_match(self):
f = self._fetcher({"10": "feat/x", "20": "feat/x"})
self.assertEqual(tangled_pr.existing_pull_for("o", "r", "feat/x", "c", {10, 20}, _fetch=f), 20)
def test_scan_window_is_bounded(self):
# a match older than the window is missed BY DESIGN; the post-check is the backstop.
pages = {str(n): ("feat/x" if n == 1 else "other") for n in range(1, 30)}
f = self._fetcher(pages)
self.assertIsNone(
tangled_pr.existing_pull_for("o", "r", "feat/x", "c", set(range(1, 30)), scan=5, _fetch=f))
def test_unreadable_pages_do_not_claim_absence(self):
# every fetch fails -> None, and main() warns rather than silently creating a duplicate
f = lambda url, cookie, timeout=30: ""
self.assertIsNone(tangled_pr.existing_pull_for("o", "r", "feat/x", "c", {1, 2}, _fetch=f))
class TestFetchIsFailSoft(unittest.TestCase):
def test_network_error_returns_empty_not_raise(self):
# a create must not die between POST and verification
self.assertEqual(tangled_pr.fetch("http://127.0.0.1:1/nope", "c", timeout=1), "")
class TestCloseToolStateParsing(unittest.TestCase):
"""tangled_pr_close.state_of decides whether a pull is already closed."""
def setUp(self):
import tangled_pr_close
self.mod = tangled_pr_close
def test_badge_regex_reads_the_states(self):
import re
badges = lambda doc: set(re.findall(r'>\s*(Merged|Closed|Open)\s*<', doc))
self.assertEqual(badges(" Closed "), {"Closed"})
self.assertEqual(badges("OpenMerged"), {"Open", "Merged"})
self.assertEqual(badges("closed
"), set()) # case-sensitive on purpose
class TestGatewayBackendValidation(unittest.TestCase):
"""gateway-domain refuses hostname backends: the gateway can store one and never remove it."""
def setUp(self):
import importlib.util
p = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"tools", "gateway-domain.py")
if not os.path.exists(p):
self.skipTest("gateway-domain.py not present")
spec = importlib.util.spec_from_file_location("gwd", p)
self.gwd = importlib.util.module_from_spec(spec)
spec.loader.exec_module(self.gwd)
def test_accepts_ip_and_ip_port(self):
self.assertTrue(self.gwd._BACKEND.match("100.74.89.63"))
self.assertTrue(self.gwd._BACKEND.match("100.74.89.63:8443"))
def test_rejects_hostnames(self):
for bad in ("example.com", "host:443", "", "1.2.3", "1.2.3.4.5"):
self.assertIsNone(self.gwd._BACKEND.match(bad), f"should reject {bad!r}")
def test_allowed_tags_include_both_known_tags(self):
self.assertIn("tag:notplants-test-server", self.gwd.ALLOWED_TAGS)
if __name__ == "__main__":
unittest.main()