"""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 import io import contextlib 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): with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()): 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_get_all", return_value=[{"id": 101, "name": "status:in-progress"}]) @patch("mark_issue.api_request") @patch("mark_issue.get_auth_header", return_value=FAKE_AUTH) def test_successful_start(self, _auth, mock_api, mock_get_all): mock_api.return_value = [{"name": "status:in-progress"}] with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()): rc = mark_issue.main(["15", "start"]) self.assertEqual(rc, 0) mock_get_all.assert_called() self.assertIn("/labels", mock_get_all.call_args[0][0]) # Verify POST labels call post_call = mock_api.call_args_list[0] 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_get_all", return_value=[{"id": 101, "name": "status:in-progress"}]) @patch("mark_issue.api_request") @patch("mark_issue.get_auth_header", return_value=FAKE_AUTH) def test_successful_done(self, _auth, mock_api, _get_all): mock_api.return_value = None rc = mark_issue.main(["15", "done"]) self.assertEqual(rc, 0) self.assertEqual(mock_api.call_count, 1) # Verify DELETE labels call delete_call = mock_api.call_args_list[0] self.assertEqual(delete_call[0][0], "DELETE") self.assertIn("/issues/15/labels/101", delete_call[0][1]) @patch("mark_issue.api_get_all", return_value=[{"id": 1, "name": "bug"}]) @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_all): # Paginated inventory returns no status:in-progress label rc = mark_issue.main(["15", "start"]) self.assertEqual(rc, 1) mock_api.assert_not_called() if __name__ == "__main__": unittest.main()