diff --git a/src/titmas_action_gate/service.py b/src/titmas_action_gate/service.py index 90d7164..df6bc08 100644 --- a/src/titmas_action_gate/service.py +++ b/src/titmas_action_gate/service.py @@ -5,6 +5,7 @@ import hashlib import hmac import json +from dataclasses import dataclass from datetime import datetime, timedelta from pathlib import Path from typing import Any @@ -20,7 +21,6 @@ from .provider import GitHubProvider from .signing import HmacRecordSigner from .store import AppendOnlyStore -from dataclasses import dataclass @dataclass(frozen=True) diff --git a/tests/test_pr_gate.py b/tests/test_pr_gate.py index a53a9f5..627aa10 100644 --- a/tests/test_pr_gate.py +++ b/tests/test_pr_gate.py @@ -10,6 +10,7 @@ from contextlib import redirect_stdout from datetime import timedelta from pathlib import Path +from unittest import mock import yaml @@ -18,7 +19,7 @@ from titmas_action_gate.cli import main as cli_main from titmas_action_gate.evidence import AGENT_EVIDENCE_VERSION, AGENT_EVIDENCE_WHEEL_SHA256, AgentEvidenceAdapter from titmas_action_gate.policy import PolicyEngine -from titmas_action_gate.pr_gate import PUBLIC_EXIT_CODES, _missing_evidence_result, verify_pull_request +from titmas_action_gate.pr_gate import PUBLIC_EXIT_CODES, _missing_evidence_result, resolve_pull_request_context, verify_pull_request ROOT = Path(__file__).resolve().parents[1] REPOSITORY = "joy7758/titmas-merge-gate-sandbox" @@ -402,5 +403,79 @@ def test_cli_returns_public_nonzero_exit_code_and_writes_outputs(self) -> None: self.assertTrue((output / "summary.md").is_file()) +class ResolvePullRequestContextTests(unittest.TestCase): + def test_explicit_arguments(self): + context = resolve_pull_request_context( + repository="repo", + pull_request=123, + head_sha="sha", + execution_identity="identity", + environment={} + ) + self.assertEqual(context.repository, "repo") + self.assertEqual(context.pull_request, 123) + self.assertEqual(context.head_sha, "sha") + self.assertEqual(context.execution_identity, "identity") + + def test_environment_variables_titmas_precedence(self): + env = { + "TITMAS_CURRENT_REPOSITORY": "env_repo", + "GITHUB_REPOSITORY": "gh_repo", + "TITMAS_CURRENT_PULL_REQUEST": "456", + "TITMAS_CURRENT_HEAD_SHA": "env_sha", + "GITHUB_SHA": "gh_sha", + "TITMAS_EXECUTION_IDENTITY": "env_identity", + } + context = resolve_pull_request_context(environment=env) + self.assertEqual(context.repository, "env_repo") + self.assertEqual(context.pull_request, 456) + self.assertEqual(context.head_sha, "env_sha") + self.assertEqual(context.execution_identity, "env_identity") + + def test_environment_variables_github_fallback(self): + env = { + "GITHUB_REPOSITORY": "gh_repo", + "GITHUB_SHA": "gh_sha", + } + context = resolve_pull_request_context(environment=env) + self.assertEqual(context.repository, "gh_repo") + self.assertIsNone(context.pull_request) + self.assertEqual(context.head_sha, "gh_sha") + self.assertEqual(context.execution_identity, "") + + def test_github_event_path_pull_request(self): + with tempfile.TemporaryDirectory() as tempdir: + event_path = Path(tempdir) / "event.json" + event_path.write_text(json.dumps({"pull_request": {"number": 789}})) + env = { + "GITHUB_EVENT_PATH": str(event_path) + } + context = resolve_pull_request_context(environment=env) + self.assertEqual(context.pull_request, 789) + + def test_invalid_titmas_current_pull_request(self): + env = { + "TITMAS_CURRENT_PULL_REQUEST": "not-a-number", + "GITHUB_EVENT_PATH": "/nonexistent" + } + context = resolve_pull_request_context(environment=env) + self.assertIsNone(context.pull_request) + + def test_invalid_github_event_path_pull_request(self): + with tempfile.TemporaryDirectory() as tempdir: + event_path = Path(tempdir) / "event.json" + event_path.write_text(json.dumps({"pull_request": {"number": "not-a-number"}})) + env = { + "GITHUB_EVENT_PATH": str(event_path) + } + context = resolve_pull_request_context(environment=env) + self.assertIsNone(context.pull_request) + + def test_missing_environment_defaults_to_os_environ(self): + with mock.patch.dict(os.environ, {"TITMAS_CURRENT_REPOSITORY": "os_repo"}, clear=True): + context = resolve_pull_request_context() + self.assertEqual(context.repository, "os_repo") + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_security_argument_injection.py b/tests/test_security_argument_injection.py index 3990256..4d8bb42 100644 --- a/tests/test_security_argument_injection.py +++ b/tests/test_security_argument_injection.py @@ -1,9 +1,8 @@ import unittest -import subprocess -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch from titmas_action_gate.provider import GhCliProvider -from titmas_action_gate.errors import ActionGateError + class ProviderSecurityTests(unittest.TestCase): @patch("subprocess.run") diff --git a/tests/test_workflow.py b/tests/test_workflow.py index 61c356d..461a3bf 100644 --- a/tests/test_workflow.py +++ b/tests/test_workflow.py @@ -1,8 +1,8 @@ +import json import tempfile import unittest from pathlib import Path -import json from titmas_action_gate.workflow import validate_agentteams_template, write_demo_report @@ -83,7 +83,7 @@ def test_write_demo_report(self): self.assertEqual(result_path, output_file) self.assertTrue(output_file.exists()) - with open(output_file, "r", encoding="utf-8") as f: + with open(output_file, encoding="utf-8") as f: content = json.load(f) self.assertEqual(content, report_data) @@ -95,7 +95,7 @@ def test_write_demo_report_creates_directories(self): write_demo_report(report_data, output_file) self.assertTrue(output_file.exists()) - with open(output_file, "r", encoding="utf-8") as f: + with open(output_file, encoding="utf-8") as f: content = json.load(f) self.assertEqual(content, report_data)