from __future__ import annotations
import base64
import io
import json
import os
import pathlib
import stat
import subprocess
import sys
import tempfile
import time
import unittest
import zipfile
from dataclasses import dataclass
from typing import Dict, List, Optional, TypedDict, final
ROOT = pathlib.Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from model import JsonValue, SummaryJson, decode_json
class PullRequestData(TypedDict):
base: Dict[str, str]
head: Dict[str, str]
class RunData(TypedDict):
id: int
status: str
run_attempt: int
event: str
head_branch: str
pull_requests: List[PullRequestData]
updated_at: str
created_at: str
run_started_at: str
html_url: str
name: str
path: str
class ArtifactData(TypedDict):
id: int
run_id: int
name: str
expired: bool
class CommentData(TypedDict):
body: Optional[str]
class StateData(TypedDict):
schema_version: int
successful_scan_started_at: Optional[str]
pending_recurrences: List[JsonValue]
rejected_artifacts: List[JsonValue]
class IssueData(TypedDict):
number: int
state: str
closed_at: Optional[str]
body: Optional[str]
labels: List[str]
pull_request: Optional[Dict[str, JsonValue]]
comments_data: List[CommentData]
class BaseScenario(TypedDict):
repository: str
runs: List[RunData]
artifacts: List[ArtifactData]
artifact_zips: Dict[str, str]
issues: List[IssueData]
class Scenario(BaseScenario, total=False):
auth_ok: bool
fail_first_comment: bool
fail_first_reopen: bool
cap_first_query: bool
search_error: bool
search_numbers: List[int]
@dataclass(frozen=True)
class Call:
args: List[str]
gh_token: Optional[str]
github_token: Optional[str]
def json_mapping(value: str) -> Dict[str, JsonValue]:
decoded = decode_json(value)
if not isinstance(decoded, dict):
raise AssertionError("JSON value is not an object")
return decoded
def required_integer(values: Dict[str, JsonValue], key: str) -> int:
value = values.get(key)
if isinstance(value, bool) or not isinstance(value, int):
raise AssertionError("JSON field is not an integer")
return value
def required_string(values: Dict[str, JsonValue], key: str) -> str:
value = values.get(key)
if not isinstance(value, str):
raise AssertionError("JSON field is not a string")
return value
def optional_string(values: Dict[str, JsonValue], key: str) -> Optional[str]:
value = values.get(key)
if value is None or isinstance(value, str):
return value
raise AssertionError("JSON field is not a nullable string")
def json_mappings(value: JsonValue) -> List[Dict[str, JsonValue]]:
if not isinstance(value, list):
raise AssertionError("JSON value is not an array")
result: List[Dict[str, JsonValue]] = []
for item in value:
if not isinstance(item, dict):
raise AssertionError("JSON array item is not an object")
result.append(item)
return result
def parse_summary(value: str) -> SummaryJson:
values = json_mapping(value)
return SummaryJson(
scanned_runs=required_integer(values, "scanned_runs"),
downloaded_artifacts=required_integer(values, "downloaded_artifacts"),
rejected_artifacts=required_integer(values, "rejected_artifacts"),
detected_occurrences=required_integer(values, "detected_occurrences"),
creates=required_integer(values, "creates"),
comments=required_integer(values, "comments"),
reopens=required_integer(values, "reopens"),
noops=required_integer(values, "noops"),
conflicts=required_integer(values, "conflicts"),
exit_code=required_integer(values, "exit_code"),
)
def parse_calls(value: str) -> Call:
values = json_mapping(value)
arguments = values.get("args")
if not isinstance(arguments, list) or not all(isinstance(item, str) for item in arguments):
raise AssertionError("call arguments are not strings")
string_arguments = [item for item in arguments if isinstance(item, str)]
return Call(string_arguments, optional_string(values, "gh_token"), optional_string(values, "github_token"))
def artifact_zip(xml: bytes) -> str:
output = io.BytesIO()
with zipfile.ZipFile(output, "w") as archive:
archive.writestr("junit.xml", xml)
return base64.b64encode(output.getvalue()).decode("ascii")
def base_scenario() -> Scenario:
run = RunData(
id=7,
status="completed",
run_attempt=1,
event="pull_request",
head_branch="feature/do-not-use",
pull_requests=[{"base": {"ref": "main"}, "head": {"ref": "feature/do-not-use"}}],
updated_at="2026-08-02T00:00:00Z",
created_at="2026-08-01T00:00:00Z",
run_started_at="2026-08-02T00:00:00Z",
html_url="https://github.com/owner/repo/actions/runs/7",
name="Unit Test",
path=".github/workflows/unit-test.yml",
)
xml = b''
scenario = Scenario(
repository="owner/repo",
runs=[run],
artifacts=[{"id": 11, "run_id": 7, "name": "unit-test-junit-attempt-1", "expired": False}],
artifact_zips={"11": artifact_zip(xml)},
issues=[],
auth_ok=True,
fail_first_comment=False,
fail_first_reopen=False,
cap_first_query=False,
)
return scenario
def rendered_issue_body(fingerprint: str = "0" * 64) -> str:
from issues import render_new_issue
from model import Occurrence
item = Occurrence("owner/repo", ".github/workflows/unit-test.yml", "Unit Test", 7, 1, "https://github.com/owner/repo/actions/runs/7", "2026-08-02T00:00:00Z", "pkg::tests", "case", frozenset(("nextest retry",)), fingerprint)
_, chunks = render_new_issue("main", [item])
return chunks[0]
def rendered_update_chunk(fingerprint: str) -> str:
from issues import plan_issue
from model import Issue, Occurrence
item = Occurrence("owner/repo", ".github/workflows/unit-test.yml", "Unit Test", 7, 1, "https://github.com/owner/repo/actions/runs/7", "2026-08-02T00:00:00Z", "pkg::tests", "case", frozenset(("nextest retry",)), fingerprint)
return plan_issue("main", [item], [Issue(1, "open", rendered_issue_body(), (), None, ())]).chunks[0]
@final
class CliTest(unittest.TestCase):
temporary: tempfile.TemporaryDirectory[str] = tempfile.TemporaryDirectory()
directory: pathlib.Path = pathlib.Path()
env: Dict[str, str] = {}
def setUp(self) -> None:
self.temporary = tempfile.TemporaryDirectory()
self.addCleanup(self.temporary.cleanup)
self.directory = pathlib.Path(self.temporary.name)
fake = ROOT / "tests" / "fixtures" / "gh"
fake.chmod(fake.stat().st_mode | stat.S_IXUSR)
self.env = os.environ.copy()
self.env.update({
"PATH": str(fake.parent) + os.pathsep + self.env["PATH"],
"FAKE_GH_DIR": str(self.directory),
"GITHUB_REPOSITORY": "owner/repo",
"FLAKY_GOVERNANCE_LABELS": "flaky-test",
"FLAKY_GOVERNANCE_STATE_DIR": str(self.directory / "state"),
"FLAKY_GOVERNANCE_NOW": "2026-08-03T00:00:00Z",
})
def write_scenario(self, scenario: Scenario) -> None:
_ = (self.directory / "scenario.json").write_text(json.dumps(scenario), encoding="utf-8")
def invoke_python(self, *arguments: str, check: bool = True) -> subprocess.CompletedProcess[str]:
process = subprocess.run(
[sys.executable, str(ROOT / "flaky_governance.py")] + list(arguments),
cwd=str(self.directory), env=self.env, text=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, check=False,
)
if check and process.returncode != 0:
self.fail("command failed: %s\nstdout=%s\nstderr=%s" % (process.returncode, process.stdout, process.stderr))
return process
def summary(self, process: subprocess.CompletedProcess[str]) -> SummaryJson:
return parse_summary(process.stdout.strip().splitlines()[-1])
def calls(self) -> List[Call]:
path = self.directory / "calls.jsonl"
values: List[Call] = []
for line in path.read_text(encoding="utf-8").splitlines():
values.append(parse_calls(line))
return values
def test_pr_uses_base_branch_in_issue_key_and_content(self):
self.write_scenario(base_scenario())
process = self.invoke_python("--run-id", "7")
self.assertEqual(1, self.summary(process)["creates"])
remote = json_mapping((self.directory / "remote.json").read_text(encoding="utf-8"))
issue = json_mappings(remote.get("issues"))[0]
body = required_string(issue, "body")
self.assertIn("`main`", body)
self.assertNotIn("feature/do-not-use", body)
def test_second_live_run_is_idempotent(self):
self.write_scenario(base_scenario())
_ = self.invoke_python("--run-id", "7")
second = self.invoke_python("--run-id", "7")
self.assertEqual(1, self.summary(second)["noops"])
remote = json_mapping((self.directory / "remote.json").read_text(encoding="utf-8"))
issues = json_mappings(remote.get("issues"))
self.assertEqual(1, len(issues))
self.assertEqual([], json_mappings(issues[0].get("comments_data", [])))
def test_run_sh_token_precedence_and_stored_auth(self):
self.write_scenario(base_scenario())
env_file = ROOT / ".env"
try:
_ = env_file.write_text("GITHUB_REPOSITORY=owner/repo\nGITHUB_TOKEN=from-env\nFLAKY_GOVERNANCE_LABELS=flaky-test\nFLAKY_GOVERNANCE_STATE_DIR=%s\n" % (self.directory / "state-token"), encoding="utf-8")
token_env = dict(self.env, GH_TOKEN="inherited-gh", GITHUB_TOKEN="inherited-github")
_ = subprocess.run([str(ROOT / "run.sh"), "--run-id", "7", "--dry-run"], env=token_env, check=True, stdout=subprocess.PIPE, text=True)
self.assertTrue(any(call.gh_token == "from-env" for call in self.calls()))
_ = (self.directory / "calls.jsonl").unlink()
_ = env_file.write_text("GITHUB_REPOSITORY=owner/repo\nFLAKY_GOVERNANCE_LABELS=flaky-test\nFLAKY_GOVERNANCE_STATE_DIR=%s\n" % (self.directory / "state-auth"), encoding="utf-8")
_ = subprocess.run([str(ROOT / "run.sh"), "--run-id", "7", "--dry-run"], env=token_env, check=True, stdout=subprocess.PIPE, text=True)
calls = self.calls()
self.assertTrue(any(call.args[:2] == ["auth", "status"] for call in calls))
self.assertTrue(all(call.gh_token is None and call.github_token is None for call in calls))
finally:
if env_file.exists():
_ = env_file.unlink()
def test_run_sh_auth_failure_prints_final_summary(self):
scenario = base_scenario()
scenario["auth_ok"] = False
self.write_scenario(scenario)
env_file = ROOT / ".env"
try:
_ = env_file.write_text("GITHUB_REPOSITORY=owner/repo\nFLAKY_GOVERNANCE_LABELS=flaky-test\nFLAKY_GOVERNANCE_STATE_DIR=%s\n" % (self.directory / "state-auth-fail"), encoding="utf-8")
process = subprocess.run([str(ROOT / "run.sh"), "--dry-run"], env=self.env, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
self.assertEqual(1, process.returncode)
self.assertEqual(1, parse_summary(process.stdout.strip().splitlines()[-1])["exit_code"])
finally:
if env_file.exists():
_ = env_file.unlink()
def test_run_sh_dry_run_creates_only_configured_lock_state(self):
self.write_scenario(base_scenario())
env_file = ROOT / ".env"
state_dir = self.directory / "dry-state"
try:
_ = env_file.write_text("GITHUB_REPOSITORY=owner/repo\nGITHUB_TOKEN=token\nFLAKY_GOVERNANCE_LABELS=flaky-test\nFLAKY_GOVERNANCE_STATE_DIR=%s\n" % state_dir, encoding="utf-8")
_ = subprocess.run([str(ROOT / "run.sh"), "--run-id", "7", "--dry-run"], env=self.env, check=True, stdout=subprocess.PIPE, text=True)
self.assertEqual([state_dir / "run.lock"], list(state_dir.iterdir()))
self.assertFalse((state_dir / "state.json").exists())
finally:
if env_file.exists():
_ = env_file.unlink()
def test_run_sh_lock_contention_skips_successfully(self):
self.write_scenario(base_scenario())
env_file = ROOT / ".env"
state_dir = self.directory / "state-lock"
state_dir.mkdir()
holder = subprocess.Popen(["flock", str(state_dir / "run.lock"), "sleep", "5"])
try:
time.sleep(0.1)
_ = env_file.write_text("GITHUB_REPOSITORY=owner/repo\nGITHUB_TOKEN=token\nFLAKY_GOVERNANCE_LABELS=flaky-test\nFLAKY_GOVERNANCE_STATE_DIR=%s\n" % state_dir, encoding="utf-8")
process = subprocess.run([str(ROOT / "run.sh")], env=self.env, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
self.assertEqual(0, process.returncode)
self.assertIn("another invocation holds the lock", process.stdout)
self.assertEqual(0, parse_summary(process.stdout.strip().splitlines()[-1])["exit_code"])
self.assertFalse((self.directory / "calls.jsonl").exists())
finally:
holder.terminate()
_ = holder.wait()
if env_file.exists():
_ = env_file.unlink()
def test_run_sh_held_lock_skips_before_failed_auth_without_invoking_gh(self):
scenario = base_scenario()
scenario["auth_ok"] = False
self.write_scenario(scenario)
env_file = ROOT / ".env"
state_dir = self.directory / "state-lock-auth-fail"
state_dir.mkdir()
holder = subprocess.Popen(["flock", str(state_dir / "run.lock"), "sleep", "5"])
try:
time.sleep(0.1)
_ = env_file.write_text("GITHUB_REPOSITORY=owner/repo\nFLAKY_GOVERNANCE_LABELS=flaky-test\nFLAKY_GOVERNANCE_STATE_DIR=%s\n" % state_dir, encoding="utf-8")
process = subprocess.run([str(ROOT / "run.sh"), "--dry-run"], env=dict(self.env, GH_TOKEN="inherited-gh", GITHUB_TOKEN="inherited-github"), text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
self.assertEqual(0, process.returncode)
self.assertIn("another invocation holds the lock", process.stdout)
self.assertEqual(0, parse_summary(process.stdout.strip().splitlines()[-1])["exit_code"])
self.assertFalse((self.directory / "calls.jsonl").exists())
finally:
holder.terminate()
_ = holder.wait()
if env_file.exists():
_ = env_file.unlink()
def test_run_sh_dry_run_contends_on_configured_state_lock(self):
self.write_scenario(base_scenario())
env_file = ROOT / ".env"
state_dir = self.directory / "state-lock-dry"
state_dir.mkdir()
holder = subprocess.Popen(["flock", str(state_dir / "run.lock"), "sleep", "5"])
try:
time.sleep(0.1)
_ = env_file.write_text("GITHUB_REPOSITORY=owner/repo\nGITHUB_TOKEN=token\nFLAKY_GOVERNANCE_LABELS=flaky-test\nFLAKY_GOVERNANCE_STATE_DIR=%s\n" % state_dir, encoding="utf-8")
process = subprocess.run([str(ROOT / "run.sh"), "--dry-run"], env=self.env, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
self.assertEqual(0, process.returncode)
self.assertIn("another invocation holds the lock", process.stdout)
self.assertFalse((self.directory / "calls.jsonl").exists())
finally:
holder.terminate()
_ = holder.wait()
if env_file.exists():
_ = env_file.unlink()
def test_dry_run_has_no_issue_or_state_writes(self):
self.write_scenario(base_scenario())
process = self.invoke_python("--run-id", "7", "--dry-run")
summary = self.summary(process)
self.assertEqual(1, summary["creates"])
self.assertFalse((self.directory / "state").exists())
mutation_calls = [
call for call in self.calls()
if "--method" in call.args
and call.args[call.args.index("--hostname") + 2] != "graphql"
and call.args[call.args.index("--method") + 1] in ("POST", "PATCH")
]
self.assertEqual([], mutation_calls)
def test_create_logs_issue_title(self):
self.write_scenario(base_scenario())
process = self.invoke_python("--run-id", "7")
self.assertIn("issue title: Flaky test: case in pkg::tests", process.stderr)
def test_dry_run_logs_issue_body(self):
self.write_scenario(base_scenario())
process = self.invoke_python("--run-id", "7", "--dry-run")
self.assertIn("issue body:\nAutomated flaky test report.", process.stderr)
self.assertIn("- Repo: `owner/repo`\n- Branch: `main`", process.stderr)
def test_created_issue_uses_repeated_labels_array_fields(self):
self.write_scenario(base_scenario())
self.env["FLAKY_GOVERNANCE_LABELS"] = "flaky-test,component/test"
_ = self.invoke_python("--run-id", "7")
create = next(call for call in self.calls() if call.args and call.args[-1] != "GET" and call.args[call.args.index("--hostname") + 2].endswith("/issues") and "POST" in call.args)
fields = [create.args[index + 1] for index, argument in enumerate(create.args) if argument == "-f"]
self.assertEqual(["labels[]=flaky-test", "labels[]=component/test"], [field for field in fields if field.startswith("labels")])
def test_dry_run_reports_recurrence_comment_plan(self):
scenario = base_scenario()
scenario["issues"] = [{"number": 4, "state": "closed", "closed_at": "2026-08-01T00:00:00Z", "body": rendered_issue_body(), "labels": [], "pull_request": None, "comments_data": []}]
self.write_scenario(scenario)
process = self.invoke_python("--run-id", "7", "--dry-run")
self.assertEqual(1, self.summary(process)["comments"])
self.assertIn("plan reopen issue 4 with 1 comment", process.stderr)
def test_existing_issue_search_hit_avoids_complete_snapshot(self):
scenario = base_scenario()
scenario["issues"] = [{"number": 4, "state": "open", "closed_at": None, "body": rendered_issue_body(), "labels": [], "pull_request": None, "comments_data": []}]
self.write_scenario(scenario)
process = self.invoke_python("--run-id", "7", "--dry-run")
self.assertEqual(1, self.summary(process)["comments"])
endpoints = [call.args[call.args.index("--hostname") + 2] for call in self.calls() if "--hostname" in call.args]
self.assertIn("search/issues", endpoints)
self.assertIn("repos/owner/repo/issues/4", endpoints)
self.assertNotIn("graphql", endpoints)
def test_empty_issue_search_falls_back_to_complete_snapshot(self):
self.write_scenario(base_scenario())
process = self.invoke_python("--run-id", "7", "--dry-run")
self.assertEqual(1, self.summary(process)["creates"])
endpoints = [call.args[call.args.index("--hostname") + 2] for call in self.calls() if "--hostname" in call.args]
self.assertIn("search/issues", endpoints)
self.assertIn("graphql", endpoints)
def test_issue_search_failure_falls_back_to_complete_snapshot(self):
scenario = base_scenario()
scenario["search_error"] = True
self.write_scenario(scenario)
process = self.invoke_python("--run-id", "7", "--dry-run")
self.assertEqual(1, self.summary(process)["creates"])
endpoints = [call.args[call.args.index("--hostname") + 2] for call in self.calls() if "--hostname" in call.args]
self.assertIn("search/issues", endpoints)
self.assertIn("graphql", endpoints)
def test_ambiguous_issue_search_falls_back_to_complete_snapshot(self):
scenario = base_scenario()
scenario["issues"] = [
{"number": 4, "state": "open", "closed_at": None, "body": rendered_issue_body(), "labels": [], "pull_request": None, "comments_data": []},
{"number": 5, "state": "open", "closed_at": None, "body": "unrelated", "labels": [], "pull_request": None, "comments_data": []},
]
scenario["search_numbers"] = [4, 5]
self.write_scenario(scenario)
process = self.invoke_python("--run-id", "7", "--dry-run")
self.assertEqual(1, self.summary(process)["comments"])
endpoints = [call.args[call.args.index("--hostname") + 2] for call in self.calls() if "--hostname" in call.args]
self.assertIn("search/issues", endpoints)
self.assertIn("graphql", endpoints)
self.assertNotIn("repos/owner/repo/issues/4", endpoints)
def test_unverified_issue_search_hit_falls_back_to_complete_snapshot(self):
scenario = base_scenario()
scenario["issues"] = [
{"number": 4, "state": "open", "closed_at": None, "body": "unrelated", "labels": [], "pull_request": None, "comments_data": []},
{"number": 5, "state": "open", "closed_at": None, "body": rendered_issue_body(), "labels": [], "pull_request": None, "comments_data": []},
]
scenario["search_numbers"] = [4]
self.write_scenario(scenario)
process = self.invoke_python("--run-id", "7", "--dry-run")
self.assertEqual(1, self.summary(process)["comments"])
self.assertIn("plan comment issue 5", process.stderr)
endpoints = [call.args[call.args.index("--hostname") + 2] for call in self.calls() if "--hostname" in call.args]
self.assertIn("repos/owner/repo/issues/4", endpoints)
self.assertIn("graphql", endpoints)
self.assertEqual(1, endpoints.count("graphql"))
def test_partial_reopen_comment_retry_is_exactly_once(self):
scenario = base_scenario()
scenario["issues"] = [{"number": 4, "state": "closed", "closed_at": "2026-08-01T00:00:00Z", "body": rendered_issue_body(), "labels": [], "pull_request": None, "comments_data": []}]
scenario["fail_first_comment"] = True
self.write_scenario(scenario)
first = self.invoke_python("--run-id", "7", check=False)
self.assertEqual(1, first.returncode)
state = json_mapping((self.directory / "state" / "state.json").read_text(encoding="utf-8"))
self.assertEqual(1, len(json_mappings(state.get("pending_recurrences"))))
second = self.invoke_python("--run-id", "7")
self.assertEqual(1, self.summary(second)["comments"])
remote = json_mapping((self.directory / "remote.json").read_text(encoding="utf-8"))
comments = json_mappings(json_mappings(remote.get("issues"))[0].get("comments_data"))
self.assertEqual(1, len(comments))
self.assertIn("Automated reopen", required_string(comments[0], "body"))
def test_pending_recurrence_reopens_when_first_reopen_failed(self):
scenario = base_scenario()
scenario["issues"] = [{"number": 4, "state": "closed", "closed_at": "2026-08-01T00:00:00Z", "body": rendered_issue_body(), "labels": [], "pull_request": None, "comments_data": []}]
scenario["fail_first_reopen"] = True
self.write_scenario(scenario)
first = self.invoke_python("--run-id", "7", check=False)
self.assertEqual(1, first.returncode)
_ = self.invoke_python("--run-id", "7")
remote = json_mapping((self.directory / "remote.json").read_text(encoding="utf-8"))
issue = json_mappings(remote.get("issues"))[0]
self.assertEqual("open", required_string(issue, "state"))
self.assertEqual(1, len(json_mappings(issue.get("comments_data"))))
def test_pending_recurrence_uses_body_markers_and_posts_only_missing_chunks(self):
scenario = base_scenario()
first_marker = "a" * 64
second_marker = "b" * 64
scenario["runs"] = []
scenario["issues"] = [{"number": 4, "state": "open", "closed_at": None, "body": rendered_issue_body(first_marker), "labels": [], "pull_request": None, "comments_data": []}]
self.write_scenario(scenario)
state_dir = self.directory / "state"
state_dir.mkdir()
chunks = [rendered_update_chunk(first_marker), rendered_update_chunk(second_marker)]
state = {"schema_version": 1, "successful_scan_started_at": None, "pending_recurrences": [{"issue_number": 4, "previous_closed_at": "2026-08-01T00:00:00Z", "fingerprints": [first_marker, second_marker], "chunks": chunks}], "rejected_artifacts": []}
_ = (state_dir / "state.json").write_text(json.dumps(state), encoding="utf-8")
process = self.invoke_python("--since", "2026-08-01T00:00:00Z")
self.assertEqual(1, self.summary(process)["comments"])
remote = json_mapping((self.directory / "remote.json").read_text(encoding="utf-8"))
self.assertEqual([{"body": chunks[1]}], json_mappings(json_mappings(remote.get("issues"))[0].get("comments_data")))
def test_pending_recurrence_updates_markers_after_each_posted_chunk(self):
scenario = base_scenario()
marker = "c" * 64
scenario["runs"] = []
scenario["issues"] = [{"number": 4, "state": "open", "closed_at": None, "body": "", "labels": [], "pull_request": None, "comments_data": []}]
self.write_scenario(scenario)
state_dir = self.directory / "state"
state_dir.mkdir()
chunk = rendered_update_chunk(marker)
state = {"schema_version": 1, "successful_scan_started_at": None, "pending_recurrences": [{"issue_number": 4, "previous_closed_at": "2026-08-01T00:00:00Z", "fingerprints": [marker], "chunks": [chunk, chunk]}], "rejected_artifacts": []}
_ = (state_dir / "state.json").write_text(json.dumps(state), encoding="utf-8")
_ = self.invoke_python("--since", "2026-08-01T00:00:00Z")
remote = json_mapping((self.directory / "remote.json").read_text(encoding="utf-8"))
self.assertEqual([{"body": chunk}], json_mappings(json_mappings(remote.get("issues"))[0].get("comments_data")))
def test_pending_retry_ignores_unanchored_marker_shaped_text(self):
scenario = base_scenario()
fake_marker = "prefix suffix" % ("a" * 64)
scenario["issues"] = [{"number": 4, "state": "open", "closed_at": None, "body": rendered_issue_body(), "labels": [], "pull_request": None, "comments_data": [{"body": fake_marker}]}]
self.write_scenario(scenario)
state_dir = self.directory / "state"
state_dir.mkdir()
chunk = rendered_update_chunk("a" * 64)
state = {"schema_version": 1, "successful_scan_started_at": None, "pending_recurrences": [{"issue_number": 4, "previous_closed_at": "2026-08-01T00:00:00Z", "fingerprints": ["a" * 64], "chunks": [chunk]}], "rejected_artifacts": []}
_ = (state_dir / "state.json").write_text(json.dumps(state), encoding="utf-8")
process = self.invoke_python("--run-id", "7")
self.assertEqual(2, self.summary(process)["comments"])
remote = json_mapping((self.directory / "remote.json").read_text(encoding="utf-8"))
comments = json_mappings(json_mappings(remote.get("issues"))[0].get("comments_data"))
self.assertEqual(3, len(comments))
self.assertIn(chunk, [required_string(comment, "body") for comment in comments])
def test_duplicate_identity_rejects_input_but_reconciles_valid_test(self):
scenario = base_scenario()
xml = b''
scenario["artifact_zips"]["11"] = artifact_zip(xml)
self.write_scenario(scenario)
process = self.invoke_python("--run-id", "7", check=False)
self.assertEqual(2, process.returncode)
self.assertEqual(1, self.summary(process)["rejected_artifacts"])
self.assertEqual(1, self.summary(process)["creates"])
def test_readable_report_with_ambiguous_identity_is_not_permanently_skipped(self):
scenario = base_scenario()
xml = b''
scenario["artifact_zips"]["11"] = artifact_zip(xml)
self.write_scenario(scenario)
first = self.invoke_python(check=False)
self.assertEqual(2, first.returncode)
state_path = self.directory / "state" / "state.json"
rejected = json_mappings(json_mapping(state_path.read_text(encoding="utf-8")).get("rejected_artifacts"))
self.assertEqual("partial", required_string(rejected[0], "kind"))
second = self.invoke_python("--run-id", "7", check=False)
self.assertEqual(2, second.returncode)
self.assertEqual(1, self.summary(second)["noops"])
def test_selector_does_not_persist_partial_rejection(self):
scenario = base_scenario()
scenario["artifact_zips"]["11"] = artifact_zip(b'')
self.write_scenario(scenario)
state_dir = self.directory / "state"
state_dir.mkdir()
original = StateData(schema_version=1, successful_scan_started_at="2026-07-01T00:00:00Z", pending_recurrences=[], rejected_artifacts=[])
state_path = state_dir / "state.json"
_ = state_path.write_text(json.dumps(original), encoding="utf-8")
_ = self.invoke_python("--run-id", "7", check=False)
self.assertEqual(original, json_mapping(state_path.read_text(encoding="utf-8")))
def test_empty_pull_requests_run_is_skipped_without_rejection(self):
scenario = base_scenario()
scenario["runs"][0]["pull_requests"] = []
self.write_scenario(scenario)
process = self.invoke_python("--run-id", "7")
summary = self.summary(process)
self.assertEqual(0, summary["exit_code"])
self.assertEqual(0, summary["rejected_artifacts"])
self.assertEqual(0, summary["creates"])
self.assertIn("skipping run", process.stderr)
def test_disagreeing_pull_request_base_branches_are_rejected(self):
scenario = base_scenario()
scenario["runs"][0]["pull_requests"] = [
{"base": {"ref": "main"}, "head": {"ref": "feature/a"}},
{"base": {"ref": "release"}, "head": {"ref": "feature/b"}},
]
self.write_scenario(scenario)
process = self.invoke_python("--run-id", "7", check=False)
self.assertEqual(2, process.returncode)
self.assertEqual(1, self.summary(process)["rejected_artifacts"])
def test_incomplete_run_id_is_not_analyzed(self):
scenario = base_scenario()
scenario["runs"][0]["status"] = "in_progress"
self.write_scenario(scenario)
process = self.invoke_python("--run-id", "7", check=False)
self.assertEqual(1, process.returncode)
self.assertEqual(0, self.summary(process)["scanned_runs"])
def test_invalid_selector_prints_failure_summary(self):
self.write_scenario(base_scenario())
process = self.invoke_python("--run-id", "7", "--since", "2026-08-01T00:00:00Z", check=False)
self.assertEqual(1, process.returncode)
self.assertEqual(1, self.summary(process)["exit_code"])
def test_manual_selectors_do_not_change_daily_cursor(self):
self.write_scenario(base_scenario())
state_dir = self.directory / "state"
state_dir.mkdir()
state_path = state_dir / "state.json"
initial = StateData(schema_version=1, successful_scan_started_at="2026-07-01T00:00:00Z", pending_recurrences=[], rejected_artifacts=[])
_ = state_path.write_text(json.dumps(initial), encoding="utf-8")
_ = self.invoke_python("--run-id", "7")
self.assertEqual(initial["successful_scan_started_at"], optional_string(json_mapping(state_path.read_text(encoding="utf-8")), "successful_scan_started_at"))
_ = self.invoke_python("--since", "2026-08-01T00:00:00Z")
self.assertEqual(initial["successful_scan_started_at"], optional_string(json_mapping(state_path.read_text(encoding="utf-8")), "successful_scan_started_at"))
def test_default_run_commits_cursor_after_success(self):
self.write_scenario(base_scenario())
process = self.invoke_python()
self.assertEqual(1, self.summary(process)["creates"])
state = json_mapping((self.directory / "state" / "state.json").read_text(encoding="utf-8"))
self.assertEqual("2026-08-03T00:00:00Z", optional_string(state, "successful_scan_started_at"))
def test_discovery_cap_partitions_and_deduplicates(self):
scenario = base_scenario()
scenario["cap_first_query"] = True
self.write_scenario(scenario)
process = self.invoke_python("--since", "2026-08-01T00:00:00Z", "--dry-run")
self.assertEqual(1, self.summary(process)["scanned_runs"])
remote = json_mapping((self.directory / "remote.json").read_text(encoding="utf-8"))
self.assertGreaterEqual(required_integer(remote, "run_queries"), 3)
def test_discovery_queries_closed_open_partitions(self):
scenario = base_scenario()
scenario["cap_first_query"] = True
self.write_scenario(scenario)
_ = self.invoke_python("--since", "2026-08-01T00:00:00Z", "--dry-run")
created: List[str] = []
for call in self.calls():
args = call.args
if args and args[args.index("--hostname") + 2].endswith("/runs"):
created.extend(args[index + 1].split("=", 1)[1] for index, arg in enumerate(args) if arg == "-f" and args[index + 1].startswith("created="))
intervals = [tuple(value.split("..", 1)) for value in created]
self.assertTrue(all(interval[0] < interval[1] for interval in intervals))
self.assertFalse(any(first[1] == second[0] for first in intervals for second in intervals if first is not second))
def test_issue_enumeration_paginates_without_labels(self):
scenario = base_scenario()
scenario["issues"] = [
{"number": index, "state": "open", "closed_at": None, "body": "unrelated", "labels": [], "pull_request": None, "comments_data": []}
for index in range(1, 102)
]
self.write_scenario(scenario)
process = self.invoke_python("--run-id", "7", "--dry-run")
self.assertEqual(1, self.summary(process)["creates"])
issue_calls = [call for call in self.calls() if call.args and call.args[call.args.index("--hostname") + 2] == "graphql"]
self.assertEqual(2, len(issue_calls))
def test_issue_enumeration_does_not_paginate_through_pull_requests(self):
scenario = base_scenario()
scenario["issues"] = [
{"number": index, "state": "open", "closed_at": None, "body": "pull request", "labels": [], "pull_request": {}, "comments_data": []}
for index in range(1, 101)
]
scenario["issues"].append({"number": 101, "state": "open", "closed_at": None, "body": "issue", "labels": [], "pull_request": None, "comments_data": []})
self.write_scenario(scenario)
process = self.invoke_python("--run-id", "7", "--dry-run")
self.assertEqual(1, self.summary(process)["creates"])
issue_calls = [call for call in self.calls() if call.args and call.args[call.args.index("--hostname") + 2] == "graphql"]
self.assertEqual(1, len(issue_calls))
def test_nullable_issue_and_comment_bodies_are_harmless(self):
scenario = base_scenario()
scenario["issues"] = [
{"number": 1, "state": "open", "closed_at": None, "body": None, "labels": [], "pull_request": None, "comments_data": []},
{"number": 2, "state": "open", "closed_at": None, "body": rendered_issue_body(), "labels": [], "pull_request": None, "comments_data": [{"body": None}]},
]
self.write_scenario(scenario)
process = self.invoke_python("--run-id", "7")
self.assertEqual(1, self.summary(process)["comments"])
def test_multichunk_create_counts_followup_comments_in_dry_and_live_runs(self):
scenario = base_scenario()
scenario["runs"][0]["run_attempt"] = 30
scenario["runs"][0]["name"] = "workflow-" + "x" * 300
scenario["runs"][0]["html_url"] = "https://example.test/" + "x" * 2200
scenario["artifacts"] = []
scenario["artifact_zips"] = {}
for attempt in range(1, 31):
artifact_id = 100 + attempt
scenario["artifacts"].append({"id": artifact_id, "run_id": 7, "name": "unit-test-junit-attempt-%d" % attempt, "expired": False})
scenario["artifact_zips"][str(artifact_id)] = artifact_zip(b'')
self.write_scenario(scenario)
dry = self.invoke_python("--run-id", "7", "--dry-run")
live = self.invoke_python("--run-id", "7")
self.assertGreater(self.summary(dry)["comments"], 0)
self.assertEqual(self.summary(dry)["comments"], self.summary(live)["comments"])
def test_unrelated_issue_comments_are_not_fetched(self):
scenario = base_scenario()
scenario["runs"] = []
scenario["issues"] = [{"number": 1, "state": "open", "closed_at": None, "body": "unrelated", "labels": [], "pull_request": None, "comments_data": []}]
self.write_scenario(scenario)
_ = self.invoke_python("--since", "2026-08-01T00:00:00Z", "--dry-run")
self.assertFalse(any("/comments" in call.args[call.args.index("--hostname") + 2] for call in self.calls() if "--hostname" in call.args))
def test_no_artifact_and_conflict_are_logged_safely(self):
scenario = base_scenario()
scenario["artifacts"] = []
self.write_scenario(scenario)
no_artifact = self.invoke_python("--run-id", "7", "--dry-run")
self.assertIn("run 7 has no matching artifact", no_artifact.stderr)
scenario = base_scenario()
from issues import issue_key
key = issue_key("owner/repo", "main", "pkg::tests", "case")
scenario["issues"] = [{"number": 1, "state": "open", "body": rendered_issue_body(), "closed_at": None, "labels": [], "pull_request": None, "comments_data": []}, {"number": 2, "state": "open", "body": rendered_issue_body(), "closed_at": None, "labels": [], "pull_request": None, "comments_data": []}]
self.write_scenario(scenario)
conflict = self.invoke_python("--run-id", "7", "--dry-run")
self.assertIn("conflict for issue key %s" % key, conflict.stderr)
def test_live_comment_failure_summary_retains_progress(self):
scenario = base_scenario()
scenario["issues"] = [{"number": 4, "state": "open", "closed_at": None, "body": rendered_issue_body(), "labels": [], "pull_request": None, "comments_data": []}]
scenario["fail_first_comment"] = True
self.write_scenario(scenario)
process = self.invoke_python("--run-id", "7", check=False)
summary = self.summary(process)
self.assertEqual(1, process.returncode)
self.assertEqual(1, summary["comments"])
self.assertEqual(1, summary["detected_occurrences"])
if __name__ == "__main__":
_ = unittest.main()