Files
timmy-config/tests/test_pr_triage.py

162 lines
4.9 KiB
Python
Raw Normal View History

"""Tests for PR triage automation (#659)."""
from __future__ import annotations
import pytest
from datetime import datetime, timezone, timedelta
from scripts.pr_triage import categorize, refs, find_duplicates, health, is_safe_to_merge
class TestCategorize:
"""PR categorization from title/body/labels."""
def test_training_data(self):
pr = {"title": "Add DPO training data", "body": "", "labels": []}
assert categorize(pr) == "training-data"
def test_bug_fix(self):
pr = {"title": "fix: resolve crash on startup", "body": "", "labels": []}
assert categorize(pr) == "bug-fix"
def test_feature(self):
pr = {"title": "feat: add dark mode", "body": "", "labels": []}
assert categorize(pr) == "feature"
def test_maintenance(self):
pr = {"title": "refactor: simplify auth flow", "body": "", "labels": []}
assert categorize(pr) == "maintenance"
def test_other(self):
pr = {"title": "Update readme", "body": "", "labels": []}
assert categorize(pr) == "other"
class TestRefs:
"""Issue reference extraction."""
def test_extracts_from_title(self):
pr = {"title": "fix: resolve #123", "body": ""}
assert refs(pr) == [123]
def test_extracts_from_body(self):
pr = {"title": "Fix", "body": "Closes #456, refs #789"}
assert refs(pr) == [456, 789]
def test_no_refs(self):
pr = {"title": "Fix", "body": "No issue refs"}
assert refs(pr) == []
def test_multiple_refs(self):
pr = {"title": "#1 and #2", "body": "Also #3"}
assert refs(pr) == [1, 2, 3]
class TestFindDuplicates:
"""Duplicate PR detection."""
def test_ref_based_duplicates(self):
prs = [
{"number": 1, "title": "Fix #100", "body": "Closes #100"},
{"number": 2, "title": "Fix #100 too", "body": "Closes #100"},
]
dups = find_duplicates(prs)
assert len(dups) == 1
assert dups[0]["type"] == "ref"
def test_title_similarity_duplicates(self):
prs = [
{"number": 1, "title": "feat: add dark mode support", "body": ""},
{"number": 2, "title": "feat: add dark mode support", "body": "different body"},
]
dups = find_duplicates(prs)
assert len(dups) >= 1
assert any(d["type"] == "similarity" for d in dups)
def test_no_duplicates(self):
prs = [
{"number": 1, "title": "Fix auth bug", "body": "Closes #100"},
{"number": 2, "title": "Add dark mode", "body": "Closes #200"},
]
dups = find_duplicates(prs)
assert len(dups) == 0
class TestHealth:
"""PR health assessment."""
def _make_pr(self, **overrides):
now = datetime.now(timezone.utc).isoformat()
pr = {
"number": 1,
"title": "test",
"body": "Closes #100",
"created_at": now,
"updated_at": now,
"head": {"ref": "fix/test"},
"mergeable": True,
"user": {"login": "agent"},
"labels": [],
}
pr.update(overrides)
return pr
def test_basic_health(self):
pr = self._make_pr()
h = health(pr, {100: {"number": 100}})
assert h["pr"] == 1
assert h["refs"] == [100]
assert h["open_issues"] == [100]
assert h["age_days"] == 0
def test_stale_detection(self):
old = (datetime.now(timezone.utc) - timedelta(days=30)).isoformat()
pr = self._make_pr(created_at=old, updated_at=old)
h = health(pr, {})
assert h["stale_days"] >= 29
assert h["risk_score"] > 30
class TestIsSafeToMerge:
"""Auto-merge safety checks."""
def _make_health(self, **overrides):
h = {
"pr": 1, "title": "test", "head": "fix/test",
"category": "training-data", "refs": [100],
"open_issues": [100], "closed_issues": [],
"age_days": 1, "stale_days": 1,
"risk_score": 10, "mergeable": True,
"author": "agent", "labels": [],
}
h.update(overrides)
return h
def test_safe_training_data(self):
h = self._make_health()
ok, reason = is_safe_to_merge(h)
assert ok
def test_unsafe_not_training(self):
h = self._make_health(category="bug-fix")
ok, reason = is_safe_to_merge(h)
assert not ok
assert "not training-data" in reason
def test_unsafe_conflicts(self):
h = self._make_health(mergeable=False)
ok, reason = is_safe_to_merge(h)
assert not ok
assert "conflicts" in reason
def test_unsafe_too_stale(self):
h = self._make_health(stale_days=31)
ok, reason = is_safe_to_merge(h)
assert not ok
assert "stale" in reason
def test_unsafe_high_risk(self):
h = self._make_health(risk_score=60)
ok, reason = is_safe_to_merge(h)
assert not ok
assert "risk" in reason