#!/usr/bin/env python3 """Tests for Gitea API module.""" import json import os import sys import unittest from unittest.mock import patch, MagicMock from http.server import HTTPServer, BaseHTTPRequestHandler import threading sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from tools.gitea_api import GiteaClient, GiteaAPIError class TestGiteaClientInit(unittest.TestCase): """Test client initialization.""" def test_init_with_explicit_params(self): c = GiteaClient(base_url="http://localhost:3000", token="test123") self.assertEqual(c.base_url, "http://localhost:3000") self.assertEqual(c.token, "test123") def test_init_strips_trailing_slash(self): c = GiteaClient(base_url="http://localhost:3000/", token="test") self.assertEqual(c.base_url, "http://localhost:3000") def test_init_no_token_raises(self): with patch.dict(os.environ, {}, clear=True): os.environ.pop("GITEA_TOKEN", None) with self.assertRaises(ValueError): GiteaClient(token="") @patch.dict(os.environ, {"GITEA_TOKEN": "envtoken123", "GITEA_URL": "http://env:3000"}) def test_init_from_env(self): c = GiteaClient() self.assertEqual(c.token, "envtoken123") self.assertEqual(c.base_url, "http://env:3000") def test_headers(self): c = GiteaClient(base_url="http://test", token="tok123") h = c._headers() self.assertEqual(h["Authorization"], "token tok123") self.assertEqual(h["Content-Type"], "application/json") class TestGiteaAPIError(unittest.TestCase): """Test error class.""" def test_error_message(self): e = GiteaAPIError(401, "Unauthorized", "http://test/api") self.assertEqual(e.status_code, 401) self.assertIn("401", str(e)) self.assertIn("Unauthorized", str(e)) def test_error_no_url(self): e = GiteaAPIError(500, "Server Error") self.assertEqual(e.url, "") class MockGiteaHandler(BaseHTTPRequestHandler): """Mock Gitea API server for integration tests.""" def do_GET(self): if self.path == "/api/v1/user": self._json_response(200, {"login": "ezra", "id": 19}) elif self.path.startswith("/api/v1/repos/ezra/test/issues"): self._json_response(200, [ {"number": 1, "title": "Test issue", "state": "open", "labels": []}, ]) elif self.path.startswith("/api/v1/repos/ezra/test/labels"): self._json_response(200, [ {"id": 1, "name": "bug", "color": "#e11d48"}, ]) elif self.path.startswith("/api/v1/repos/ezra/test/milestones"): self._json_response(200, []) elif self.path == "/api/v1/user/repos?limit=50": self._json_response(200, [{"full_name": "ezra/test", "description": "test repo"}]) elif self.path == "/api/v1/repos/ezra/test": self._json_response(200, {"full_name": "ezra/test"}) elif self.path == "/api/v1/repos/ezra/notfound": self._json_response(404, {"message": "not found"}) else: self._json_response(404, {"message": "not found"}) def do_POST(self): content_len = int(self.headers.get("Content-Length", 0)) body = json.loads(self.rfile.read(content_len)) if content_len else {} if self.path == "/api/v1/repos/ezra/test/issues": self._json_response(201, { "number": 42, "title": body.get("title", ""), "state": "open", }) elif self.path.startswith("/api/v1/repos/ezra/test/issues/") and "/comments" in self.path: self._json_response(201, {"id": 1, "body": body.get("body", "")}) elif self.path == "/api/v1/repos/ezra/test/labels": self._json_response(201, {"id": 2, "name": body.get("name", ""), "color": body.get("color", "")}) elif self.path == "/api/v1/repos/ezra/test/milestones": self._json_response(201, {"id": 1, "title": body.get("title", "")}) else: self._json_response(404, {"message": "not found"}) def do_PATCH(self): content_len = int(self.headers.get("Content-Length", 0)) body = json.loads(self.rfile.read(content_len)) if content_len else {} if "/issues/" in self.path: self._json_response(200, {"number": 1, "state": body.get("state", "open")}) else: self._json_response(404, {"message": "not found"}) def _json_response(self, code, data): self.send_response(code) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps(data).encode()) def log_message(self, *args): pass # Silence request logging class TestGiteaClientIntegration(unittest.TestCase): """Integration tests with mock HTTP server.""" @classmethod def setUpClass(cls): cls.server = HTTPServer(("127.0.0.1", 0), MockGiteaHandler) cls.port = cls.server.server_address[1] cls.thread = threading.Thread(target=cls.server.serve_forever) cls.thread.daemon = True cls.thread.start() cls.client = GiteaClient( base_url=f"http://127.0.0.1:{cls.port}", token="testtoken", max_retries=1, ) @classmethod def tearDownClass(cls): cls.server.shutdown() def test_whoami(self): user = self.client.whoami() self.assertEqual(user["login"], "ezra") def test_validate_token(self): ok, name = self.client.validate_token() self.assertTrue(ok) self.assertEqual(name, "ezra") def test_list_issues(self): issues = self.client.list_issues("ezra", "test") self.assertEqual(len(issues), 1) self.assertEqual(issues[0]["title"], "Test issue") def test_create_issue(self): issue = self.client.create_issue("ezra", "test", "New issue", "Body text") self.assertEqual(issue["number"], 42) def test_close_issue(self): result = self.client.close_issue("ezra", "test", 1) self.assertEqual(result["state"], "closed") def test_add_comment(self): result = self.client.add_comment("ezra", "test", 1, "test comment") self.assertEqual(result["body"], "test comment") def test_list_labels(self): labels = self.client.list_labels("ezra", "test") self.assertEqual(len(labels), 1) self.assertEqual(labels[0]["name"], "bug") def test_create_label(self): label = self.client.create_label("ezra", "test", "feature", "0ea5e9") self.assertEqual(label["name"], "feature") def test_ensure_label_existing(self): label = self.client.ensure_label("ezra", "test", "bug", "e11d48") self.assertEqual(label["name"], "bug") def test_ensure_label_new(self): label = self.client.ensure_label("ezra", "test", "newlabel", "00ff00") self.assertEqual(label["name"], "newlabel") def test_list_repos(self): repos = self.client.list_repos() self.assertEqual(len(repos), 1) def test_get_repo(self): repo = self.client.get_repo("ezra", "test") self.assertEqual(repo["full_name"], "ezra/test") def test_404_raises(self): with self.assertRaises(GiteaAPIError) as ctx: self.client.get_repo("ezra", "notfound") self.assertEqual(ctx.exception.status_code, 404) def test_create_milestone(self): ms = self.client.create_milestone("ezra", "test", "v1.0") self.assertEqual(ms["title"], "v1.0") def test_ensure_milestone_new(self): ms = self.client.ensure_milestone("ezra", "test", "v2.0") self.assertEqual(ms["title"], "v2.0") if __name__ == "__main__": unittest.main()