Some checks failed
Architecture Lint / Linter Tests (pull_request) Successful in 33s
Smoke Test / smoke (pull_request) Failing after 39s
Validate Config / YAML Lint (pull_request) Failing after 27s
Validate Config / JSON Validate (pull_request) Successful in 22s
Validate Config / Python Syntax & Import Check (pull_request) Failing after 21s
Validate Config / Python Test Suite (pull_request) Has been skipped
Validate Config / Shell Script Lint (pull_request) Failing after 24s
Validate Config / Cron Syntax Check (pull_request) Successful in 5s
Validate Config / Deploy Script Dry Run (pull_request) Successful in 6s
Validate Config / Playbook Schema Validation (pull_request) Successful in 10s
PR Checklist / pr-checklist (pull_request) Failing after 11m27s
Architecture Lint / Lint Repository (pull_request) Failing after 11s
186 lines
6.0 KiB
Python
186 lines
6.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Tests for pr_triage.py — issue #659."""
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
|
|
from pr_triage import categorize, refs, find_dupes, find_stale, to_markdown, to_json
|
|
|
|
|
|
class TestCategorize:
|
|
def test_training_data_pairs(self):
|
|
assert categorize("feat: 500 emotional weather pairs (#603)") == "training_data"
|
|
|
|
def test_training_data_scene(self):
|
|
assert categorize("feat: 100 jazz scene descriptions (#612)") == "training_data"
|
|
|
|
def test_training_data_corpus(self):
|
|
assert categorize("Add crisis manipulation corpus (#598)") == "training_data"
|
|
|
|
def test_bug_fix(self):
|
|
assert categorize("fix: broken import in cli.py") == "bug_fix"
|
|
|
|
def test_bug_resolve(self):
|
|
assert categorize("resolve: memory leak in session store") == "bug_fix"
|
|
|
|
def test_feature(self):
|
|
assert categorize("feat: add token budget tracker") == "feature"
|
|
|
|
def test_feature_new(self):
|
|
assert categorize("new: nightly pipeline scheduler") == "feature"
|
|
|
|
def test_docs(self):
|
|
assert categorize("docs: update README config format") == "docs"
|
|
|
|
def test_ops(self):
|
|
assert categorize("ops: deploy config to Ezra VPS") == "ops"
|
|
|
|
def test_ops_ci(self):
|
|
assert categorize("ci: add smoke test workflow") == "ops"
|
|
|
|
def test_security(self):
|
|
assert categorize("security: fix XSS in gallery panel") == "security"
|
|
|
|
def test_other(self):
|
|
assert categorize("chore: cleanup whitespace") == "other"
|
|
|
|
def test_empty(self):
|
|
assert categorize("") == "other"
|
|
|
|
def test_none(self):
|
|
assert categorize(None) == "other"
|
|
|
|
def test_case_insensitive(self):
|
|
assert categorize("FIX: Resolve import error") == "bug_fix"
|
|
|
|
|
|
class TestRefs:
|
|
def test_single(self):
|
|
assert refs({"title": "Fix #123", "body": ""}) == [123]
|
|
|
|
def test_multiple(self):
|
|
assert refs({"title": "#10", "body": "Related to #20 and #30"}) == [10, 20, 30]
|
|
|
|
def test_dedup(self):
|
|
assert refs({"title": "#100", "body": "Closes #100"}) == [100]
|
|
|
|
def test_none(self):
|
|
assert refs({"title": "No refs", "body": ""}) == []
|
|
|
|
def test_body_only(self):
|
|
assert refs({"title": "Fix", "body": "Closes #42"}) == [42]
|
|
|
|
def test_null_body(self):
|
|
assert refs({"title": "#7", "body": None}) == [7]
|
|
|
|
|
|
class TestFindDupes:
|
|
def test_no_dupes(self):
|
|
prs = [{"number": 1, "title": "#10", "body": ""},
|
|
{"number": 2, "title": "#11", "body": ""}]
|
|
assert find_dupes(prs) == {}
|
|
|
|
def test_duplicate(self):
|
|
prs = [{"number": 1, "title": "#10", "body": ""},
|
|
{"number": 2, "title": "#10", "body": ""}]
|
|
d = find_dupes(prs)
|
|
assert d[10] == [1, 2]
|
|
|
|
def test_triple(self):
|
|
prs = [{"number": i, "title": "#42", "body": ""} for i in range(1, 4)]
|
|
d = find_dupes(prs)
|
|
assert len(d[42]) == 3
|
|
|
|
def test_partial_overlap(self):
|
|
prs = [{"number": 1, "title": "#10 #20", "body": ""},
|
|
{"number": 2, "title": "#10", "body": ""}]
|
|
d = find_dupes(prs)
|
|
assert 10 in d
|
|
assert 20 not in d
|
|
|
|
|
|
class TestFindStale:
|
|
def test_clean(self):
|
|
prs = [{"number": 1, "title": "#10", "body": ""}]
|
|
assert find_stale(prs, set()) == []
|
|
|
|
def test_stale(self):
|
|
prs = [{"number": 1, "title": "#10", "body": ""}]
|
|
s = find_stale(prs, {10})
|
|
assert len(s) == 1
|
|
assert s[0]["stale_refs"] == [10]
|
|
|
|
def test_mixed(self):
|
|
prs = [{"number": 1, "title": "#10 #20", "body": ""}]
|
|
s = find_stale(prs, {10})
|
|
assert s[0]["stale_refs"] == [10]
|
|
|
|
def test_multiple_prs(self):
|
|
prs = [
|
|
{"number": 1, "title": "#10", "body": ""},
|
|
{"number": 2, "title": "#20", "body": ""},
|
|
]
|
|
s = find_stale(prs, {10, 20})
|
|
assert len(s) == 2
|
|
|
|
|
|
class TestToMarkdown:
|
|
def test_basic_structure(self):
|
|
a = {
|
|
"repo": "test/repo", "total_open": 3,
|
|
"total_files_changed": 10, "total_additions": 100, "total_deletions": 20,
|
|
"categories": {"feature": 2, "bug_fix": 1},
|
|
"category_details": {
|
|
"feature": [{"number": 1, "title": "feat: x", "refs": [], "head": "f1", "files": 2, "created": "2026-04-01"}],
|
|
"bug_fix": [],
|
|
},
|
|
"duplicates": {}, "stale_prs": [],
|
|
"closed_issues_checked": 50,
|
|
"safe_merge_candidates": 0,
|
|
"timestamp": "2026-04-14T12:00:00Z",
|
|
}
|
|
md = to_markdown(a)
|
|
assert "test/repo" in md
|
|
assert "3" in md
|
|
assert "feature" in md
|
|
assert "## PR Triage Report" in md
|
|
|
|
def test_duplicates_section(self):
|
|
a = {"repo": "x", "total_open": 2, "total_files_changed": 0,
|
|
"total_additions": 0, "total_deletions": 0,
|
|
"categories": {}, "category_details": {},
|
|
"duplicates": {42: [1, 2]}, "stale_prs": [],
|
|
"closed_issues_checked": 0, "safe_merge_candidates": 0,
|
|
"timestamp": "2026-01-01"}
|
|
md = to_markdown(a)
|
|
assert "Duplicate" in md
|
|
assert "#42" in md
|
|
|
|
def test_stale_section(self):
|
|
a = {"repo": "x", "total_open": 1, "total_files_changed": 0,
|
|
"total_additions": 0, "total_deletions": 0,
|
|
"categories": {}, "category_details": {},
|
|
"duplicates": {},
|
|
"stale_prs": [{"pr": 5, "title": "old fix", "stale_refs": [10]}],
|
|
"closed_issues_checked": 50, "safe_merge_candidates": 0,
|
|
"timestamp": "2026-01-01"}
|
|
md = to_markdown(a)
|
|
assert "#5" in md
|
|
assert "Stale" in md
|
|
|
|
|
|
class TestToJson:
|
|
def test_roundtrip(self):
|
|
a = {"repo": "test", "total_open": 0}
|
|
out = to_json(a)
|
|
assert json.loads(out)["repo"] == "test"
|
|
|
|
def test_complex(self):
|
|
a = {"repo": "x", "duplicates": {1: [2, 3]}, "stale_prs": []}
|
|
out = to_json(a)
|
|
d = json.loads(out)
|
|
assert d["duplicates"]["1"] == [2, 3]
|