feat: bypass Cloudflare block & convert close/mark issue shell scripts to Python
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
"""Tests for the python CLI scripts close_issue.py and mark_issue.py.
|
||||
|
||||
All tests mock credentials and API requests so no real network calls are made.
|
||||
"""
|
||||
import sys
|
||||
import unittest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
# The modules under test live in the repo root
|
||||
sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent.parent))
|
||||
|
||||
import close_issue
|
||||
import mark_issue
|
||||
|
||||
FAKE_AUTH = "Basic dGVzdDp0ZXN0"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# close_issue.py
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestCloseIssueCLI(unittest.TestCase):
|
||||
|
||||
def test_missing_argument_exits(self):
|
||||
with self.assertRaises(SystemExit):
|
||||
close_issue.main([])
|
||||
|
||||
def test_invalid_argument_type_exits(self):
|
||||
with self.assertRaises(SystemExit):
|
||||
close_issue.main(["not_a_number"])
|
||||
|
||||
@patch("close_issue.api_request")
|
||||
@patch("close_issue.get_auth_header", return_value=FAKE_AUTH)
|
||||
def test_successful_close(self, _auth, mock_api):
|
||||
rc = close_issue.main(["42"])
|
||||
self.assertEqual(rc, 0)
|
||||
mock_api.assert_called_once()
|
||||
url = mock_api.call_args[0][1]
|
||||
self.assertIn("/issues/42", url)
|
||||
self.assertEqual(mock_api.call_args[0][3], {"state": "closed"})
|
||||
|
||||
@patch("close_issue.api_request")
|
||||
@patch("close_issue.get_auth_header", return_value=FAKE_AUTH)
|
||||
def test_remote_override(self, _auth, mock_api):
|
||||
rc = close_issue.main(["--remote", "prgs", "12"])
|
||||
self.assertEqual(rc, 0)
|
||||
url = mock_api.call_args[0][1]
|
||||
self.assertIn("gitea.prgs.cc", url)
|
||||
self.assertIn("/issues/12", url)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# mark_issue.py
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestMarkIssueCLI(unittest.TestCase):
|
||||
|
||||
def test_missing_argument_exits(self):
|
||||
with self.assertRaises(SystemExit):
|
||||
mark_issue.main([])
|
||||
|
||||
def test_invalid_action_exits(self):
|
||||
with self.assertRaises(SystemExit):
|
||||
mark_issue.main(["10", "bogus_action"])
|
||||
|
||||
@patch("mark_issue.api_request")
|
||||
@patch("mark_issue.get_auth_header", return_value=FAKE_AUTH)
|
||||
def test_successful_start(self, _auth, mock_api):
|
||||
# First call is GET labels, second is POST label
|
||||
mock_api.side_effect = [
|
||||
[{"id": 101, "name": "status:in-progress"}],
|
||||
[{"name": "status:in-progress"}],
|
||||
]
|
||||
rc = mark_issue.main(["15", "start"])
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertEqual(mock_api.call_count, 2)
|
||||
|
||||
# Verify GET labels call
|
||||
get_call = mock_api.call_args_list[0]
|
||||
self.assertEqual(get_call[0][0], "GET")
|
||||
self.assertIn("/labels?limit=100", get_call[0][1])
|
||||
|
||||
# Verify POST labels call
|
||||
post_call = mock_api.call_args_list[1]
|
||||
self.assertEqual(post_call[0][0], "POST")
|
||||
self.assertIn("/issues/15/labels", post_call[0][1])
|
||||
self.assertEqual(post_call[0][3], {"labels": [101]})
|
||||
|
||||
@patch("mark_issue.api_request")
|
||||
@patch("mark_issue.get_auth_header", return_value=FAKE_AUTH)
|
||||
def test_successful_done(self, _auth, mock_api):
|
||||
# First call is GET labels, second is DELETE label
|
||||
mock_api.side_effect = [
|
||||
[{"id": 101, "name": "status:in-progress"}],
|
||||
None,
|
||||
]
|
||||
rc = mark_issue.main(["15", "done"])
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertEqual(mock_api.call_count, 2)
|
||||
|
||||
# Verify DELETE labels call
|
||||
delete_call = mock_api.call_args_list[1]
|
||||
self.assertEqual(delete_call[0][0], "DELETE")
|
||||
self.assertIn("/issues/15/labels/101", delete_call[0][1])
|
||||
|
||||
@patch("mark_issue.api_request")
|
||||
@patch("mark_issue.get_auth_header", return_value=FAKE_AUTH)
|
||||
def test_label_not_found(self, _auth, mock_api):
|
||||
# GET labels returns no status:in-progress label
|
||||
mock_api.return_value = [{"id": 1, "name": "bug"}]
|
||||
rc = mark_issue.main(["15", "start"])
|
||||
self.assertEqual(rc, 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,77 +0,0 @@
|
||||
"""Tests for the shell scripts (close_issue.sh, mark_issue.sh).
|
||||
|
||||
These are integration-style tests that verify the scripts':
|
||||
- argument validation and error messages
|
||||
- exit codes on bad input
|
||||
- correct curl command construction (via a mock curl wrapper)
|
||||
|
||||
We do NOT make real API calls. Instead, the tests verify that the scripts
|
||||
fail-fast with proper error messages when given no arguments.
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import unittest
|
||||
|
||||
REPO_ROOT = str(__import__("pathlib").Path(__file__).resolve().parent.parent)
|
||||
|
||||
|
||||
def _run_script(script, args=None, env_override=None):
|
||||
"""Run a shell script and return (returncode, stdout, stderr)."""
|
||||
cmd = [os.path.join(REPO_ROOT, script)]
|
||||
if args:
|
||||
cmd.extend(args)
|
||||
env = os.environ.copy()
|
||||
if env_override:
|
||||
env.update(env_override)
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, env=env, timeout=10,
|
||||
)
|
||||
return result.returncode, result.stdout, result.stderr
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# close_issue.sh
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestCloseIssue(unittest.TestCase):
|
||||
|
||||
def test_no_args_prints_usage_and_fails(self):
|
||||
rc, stdout, stderr = _run_script("close_issue.sh")
|
||||
self.assertNotEqual(rc, 0)
|
||||
self.assertIn("usage:", stderr)
|
||||
|
||||
def test_usage_mentions_issue_number(self):
|
||||
rc, stdout, stderr = _run_script("close_issue.sh")
|
||||
self.assertIn("issue_number", stderr)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# mark_issue.sh
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestMarkIssue(unittest.TestCase):
|
||||
|
||||
def test_no_args_prints_usage_and_fails(self):
|
||||
rc, stdout, stderr = _run_script("mark_issue.sh")
|
||||
self.assertNotEqual(rc, 0)
|
||||
self.assertIn("usage:", stderr)
|
||||
|
||||
def test_usage_mentions_start_done(self):
|
||||
rc, stdout, stderr = _run_script("mark_issue.sh")
|
||||
self.assertIn("start|done", stderr)
|
||||
|
||||
def test_invalid_action_fails(self):
|
||||
"""Providing an issue number but an invalid action should fail.
|
||||
|
||||
Note: this test may fail if `git credential fill` hangs waiting for
|
||||
input. In CI without credentials, it will error out with a non-zero
|
||||
exit code, which is what we're testing for — the script should not
|
||||
succeed with an invalid action.
|
||||
"""
|
||||
rc, stdout, stderr = _run_script("mark_issue.sh", ["999", "bogus"])
|
||||
# The script will either fail at credential lookup (no keychain in CI)
|
||||
# or at the invalid action case statement. Either way, it should not
|
||||
# exit 0.
|
||||
self.assertNotEqual(rc, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user