Route georelay updates through review

This commit is contained in:
jack
2026-07-10 17:08:04 -04:00
parent 733098bb63
commit 74f64e685f
7 changed files with 1137 additions and 76 deletions
@@ -0,0 +1,61 @@
import re
from pathlib import Path
import unittest
REPOSITORY_ROOT = Path(__file__).resolve().parents[2]
WORKFLOW_PATH = REPOSITORY_ROOT / ".github/workflows/fetch_georelays.yml"
class FetchGeoRelaysWorkflowTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.workflow = WORKFLOW_PATH.read_text(encoding="utf-8")
def test_write_capable_checkout_action_is_immutable(self) -> None:
checkout = re.search(r"uses: actions/checkout@([0-9a-f]+)", self.workflow)
self.assertIsNotNone(checkout)
self.assertRegex(checkout.group(1), r"^[0-9a-f]{40}$")
self.assertIn("persist-credentials: false", self.workflow)
def test_pr_failure_has_single_issue_fallback_with_review_metadata(self) -> None:
required_fragments = [
"issues: write",
"TRACKING_ISSUE_TITLE: GeoRelay update awaiting pull request",
"gh pr create",
"gh issue create",
"gh issue edit",
"compare/main...${UPDATE_BRANCH}?expand=1",
"Upstream commit: $SOURCE_COMMIT",
"Data rows: $DATA_ROWS",
"Unique normalized relays: $UNIQUE_RELAYS",
"SHA-256: $DATA_SHA256",
'[[ -n "$issue_url" ]]',
]
for fragment in required_fragments:
with self.subTest(fragment=fragment):
self.assertIn(fragment, self.workflow)
confirmed = self.workflow.index('[[ -n "$issue_url" ]]')
success_summary = self.workflow.index(
"Published GeoRelay tracking issue fallback: $issue_url"
)
self.assertLess(confirmed, success_summary)
def test_obsolete_review_state_is_cleaned_without_pushing_main(self) -> None:
self.assertIn("gh pr close", self.workflow)
self.assertIn("gh issue close", self.workflow)
self.assertIn('git push origin --delete "$UPDATE_BRANCH"', self.workflow)
self.assertIn('git switch -C "$UPDATE_BRANCH"', self.workflow)
self.assertNotIn("git push origin main", self.workflow)
self.assertNotIn("git push --force origin main", self.workflow)
def test_workflow_runs_all_validator_tests(self) -> None:
self.assertIn(
'python3 -m unittest discover -s scripts/tests -p "test_*.py" -v',
self.workflow,
)
if __name__ == "__main__":
unittest.main()
+186
View File
@@ -0,0 +1,186 @@
import tempfile
from pathlib import Path
import sys
import unittest
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import validate_georelays as validator
def csv_bytes(rows: list[str]) -> bytes:
return ("Relay URL,Latitude,Longitude\n" + "\n".join(rows) + "\n").encode()
class ValidateGeoRelaysTests(unittest.TestCase):
def test_validates_and_deduplicates_secure_relay_addresses(self) -> None:
data = csv_bytes(
[
"relay.example.com,10,20",
"wss://relay.example.com:443/,10,20",
"https://second.example.org,11,21",
]
)
summary = validator.validate_bytes(data, minimum_unique_relays=2)
self.assertEqual(summary.data_rows, 3)
self.assertEqual(summary.unique_relays, 2)
def test_rejects_insecure_or_non_host_relay_urls(self) -> None:
bad_addresses = [
"http://relay.example.com",
"ws://relay.example.com",
"wss://user@relay.example.com",
"wss://relay.example.com/path",
"wss://relay.example.com?",
"wss://relay.example.com#",
"relay.example.com:0",
"relay.example.com:99999",
"localhost",
"127.0.0.1",
"relay_example.com",
"relay\u202e.example.com",
]
for address in bad_addresses:
with self.subTest(address=address):
with self.assertRaises(validator.ValidationError):
validator.validate_bytes(
csv_bytes([f"{address},10,20"]),
minimum_unique_relays=1,
)
def test_rejects_malformed_rows_and_unsafe_coordinates(self) -> None:
bad_rows = [
"relay.example.com,10",
"relay.example.com,NaN,20",
"relay.example.com,1_0,20",
"relay.example.com,\u0661\u0660,20",
"relay.example.com,\uff11\uff10,20",
"relay.example.com,91,20",
"relay.example.com,10,-181",
"relay.example.com,10,20,extra",
'"relay.example.com",10,20',
]
for row in bad_rows:
with self.subTest(row=row):
with self.assertRaises(validator.ValidationError):
validator.validate_bytes(csv_bytes([row]), minimum_unique_relays=1)
def test_accepts_ascii_coordinate_forms_supported_by_swift_double(self) -> None:
summary = validator.validate_bytes(
csv_bytes(
[
"one.example.com,+1,-.5",
"two.example.com,1.e1,2E+1",
"three.example.com,01,20.",
]
),
minimum_unique_relays=3,
)
self.assertEqual(summary.unique_relays, 3)
def test_rejects_conflicts_limits_and_large_baseline_deltas(self) -> None:
with self.assertRaises(validator.ValidationError):
validator.validate_bytes(
csv_bytes(["relay.example.com,10,20", "relay.example.com,11,21"]),
minimum_unique_relays=1,
)
with self.assertRaises(validator.ValidationError):
validator.validate_bytes(b"x" * 20, maximum_bytes=10, minimum_unique_relays=1)
with self.assertRaises(validator.ValidationError):
validator.validate_bytes(
csv_bytes(["one.example.com,1,1", "two.example.com,2,2"]),
minimum_unique_relays=3,
)
baseline = csv_bytes(
[f"relay-{index}.example.com,{index % 80},{index % 170}" for index in range(120)]
)
shrunken = csv_bytes(
[f"relay-{index}.example.com,{index % 80},{index % 170}" for index in range(59)]
)
with self.assertRaises(validator.ValidationError):
validator.validate_update(shrunken, baseline)
smaller_baseline = csv_bytes(
[f"relay-{index}.example.com,{index % 80},{index % 170}" for index in range(60)]
)
expanded = csv_bytes(
[f"relay-{index}.example.com,{index % 80},{index % 170}" for index in range(121)]
)
with self.assertRaises(validator.ValidationError):
validator.validate_update(expanded, smaller_baseline)
def test_update_requires_exact_normalized_baseline_entry_overlap(self) -> None:
baseline_rows = [
f"relay-{index}.example.com,{index % 80},{index % 170}"
for index in range(60)
]
baseline = csv_bytes(baseline_rows)
disjoint = csv_bytes(
[
f"attacker-{index}.example.com,{index % 80},{index % 170}"
for index in range(60)
]
)
rewritten_coordinates = csv_bytes(
[
f"relay-{index}.example.com,{(index % 80) + 0.5},{index % 170}"
for index in range(60)
]
)
for candidate in (disjoint, rewritten_coordinates):
with self.subTest(candidate=candidate[:80]):
with self.assertRaisesRegex(
validator.ValidationError,
"exact relay-coordinate entries",
):
validator.validate_update(candidate, baseline)
half_retained = csv_bytes(
[
f"wss://relay-{index}.example.com:443/,{index % 80},{index % 170}"
for index in range(30)
]
+ [
f"replacement-{index}.example.com,{index % 80},{index % 170}"
for index in range(30)
]
)
summary = validator.validate_update(half_retained, baseline)
self.assertEqual(summary.unique_relays, 60)
def test_cli_copies_only_validated_data_and_emits_review_metadata(self) -> None:
rows = [f"relay-{index}.example.com,{index % 80},{index % 170}" for index in range(60)]
data = csv_bytes(rows)
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
candidate = root / "candidate.csv"
baseline = root / "baseline.csv"
output = root / "output.csv"
github_output = root / "github-output.txt"
candidate.write_bytes(data)
baseline.write_bytes(data)
result = validator.main(
[
"--input", str(candidate),
"--baseline", str(baseline),
"--output", str(output),
"--github-output", str(github_output),
]
)
self.assertEqual(result, 0)
self.assertEqual(output.read_bytes(), data)
metadata = github_output.read_text()
self.assertIn("unique_relays=60", metadata)
self.assertIn("sha256=", metadata)
if __name__ == "__main__":
unittest.main()