Skip to main content
Glama

Redmine MCP Server

by snowild
test_redmine_client.py8.91 kB
""" Redmine 客戶端測試 """ import os import pytest from unittest.mock import patch, Mock import requests from redmine_mcp.redmine_client import ( RedmineClient, RedmineAPIError, RedmineIssue, RedmineProject, get_client, reload_client ) class TestRedmineClient: """RedmineClient 類別測試""" def setup_method(self): """每個測試前的設置""" with patch.dict(os.environ, { 'REDMINE_DOMAIN': 'https://test.redmine.com', 'REDMINE_API_KEY': 'test_api_key' }): self.client = RedmineClient() def test_client_initialization(self): """測試客戶端初始化""" assert self.client.config.redmine_domain == 'https://test.redmine.com' assert self.client.session.headers['X-Redmine-API-Key'] == 'test_api_key' assert self.client.session.timeout == 30 @patch('requests.Session.request') def test_make_request_success(self, mock_request): """測試成功的 API 請求""" mock_response = Mock() mock_response.json.return_value = {'test': 'data'} mock_response.content = b'{"test": "data"}' mock_request.return_value = mock_response result = self.client._make_request('GET', '/test') assert result == {'test': 'data'} mock_request.assert_called_once() @patch('requests.Session.request') def test_make_request_timeout(self, mock_request): """測試請求逾時""" mock_request.side_effect = requests.exceptions.Timeout() with pytest.raises(RedmineAPIError, match="請求逾時"): self.client._make_request('GET', '/test') @patch('requests.Session.request') def test_make_request_connection_error(self, mock_request): """測試連線錯誤""" mock_request.side_effect = requests.exceptions.ConnectionError() with pytest.raises(RedmineAPIError, match="連線失敗"): self.client._make_request('GET', '/test') @patch('requests.Session.request') def test_make_request_http_error(self, mock_request): """測試 HTTP 錯誤""" mock_response = Mock() mock_response.status_code = 404 mock_response.json.return_value = {'errors': ['Not found']} mock_response.content = b'{"errors": ["Not found"]}' mock_http_error = requests.exceptions.HTTPError() mock_http_error.response = mock_response mock_response.raise_for_status.side_effect = mock_http_error mock_request.return_value = mock_response with pytest.raises(RedmineAPIError, match="HTTP 錯誤 404"): self.client._make_request('GET', '/test') @patch('requests.Session.request') def test_get_issue_success(self, mock_request): """測試成功取得議題""" mock_response = Mock() mock_response.json.return_value = { 'issue': { 'id': 1, 'subject': '測試議題', 'description': '測試描述', 'status': {'id': 1, 'name': '新建'}, 'priority': {'id': 2, 'name': '正常'}, 'project': {'id': 1, 'name': '測試專案'}, 'tracker': {'id': 1, 'name': 'Bug'}, 'author': {'id': 1, 'name': '測試用戶'}, 'done_ratio': 0 } } mock_response.content = b'content' mock_request.return_value = mock_response issue = self.client.get_issue(1) assert isinstance(issue, RedmineIssue) assert issue.id == 1 assert issue.subject == '測試議題' assert issue.status['name'] == '新建' @patch('requests.Session.request') def test_get_issue_not_found(self, mock_request): """測試議題不存在""" mock_response = Mock() mock_response.json.return_value = {} mock_response.content = b'{}' mock_request.return_value = mock_response with pytest.raises(RedmineAPIError, match="議題 1 不存在"): self.client.get_issue(1) @patch('requests.Session.request') def test_list_issues_success(self, mock_request): """測試列出議題""" mock_response = Mock() mock_response.json.return_value = { 'issues': [ { 'id': 1, 'subject': '議題1', 'description': '描述1', 'status': {'id': 1, 'name': '新建'}, 'priority': {'id': 2, 'name': '正常'}, 'project': {'id': 1, 'name': '專案1'}, 'tracker': {'id': 1, 'name': 'Bug'}, 'author': {'id': 1, 'name': '用戶1'} }, { 'id': 2, 'subject': '議題2', 'description': '描述2', 'status': {'id': 2, 'name': '進行中'}, 'priority': {'id': 3, 'name': '高'}, 'project': {'id': 1, 'name': '專案1'}, 'tracker': {'id': 2, 'name': 'Feature'}, 'author': {'id': 2, 'name': '用戶2'} } ] } mock_response.content = b'content' mock_request.return_value = mock_response issues = self.client.list_issues() assert len(issues) == 2 assert all(isinstance(issue, RedmineIssue) for issue in issues) assert issues[0].subject == '議題1' assert issues[1].subject == '議題2' @patch('requests.Session.request') def test_update_issue_success(self, mock_request): """測試更新議題""" mock_response = Mock() mock_response.content = b'' mock_request.return_value = mock_response result = self.client.update_issue(1, subject='新標題', status_id=2) assert result is True mock_request.assert_called_once() call_args = mock_request.call_args assert call_args[1]['json']['issue']['subject'] == '新標題' assert call_args[1]['json']['issue']['status_id'] == 2 def test_update_issue_no_fields(self): """測試更新議題但沒有提供欄位""" with pytest.raises(RedmineAPIError, match="沒有提供要更新的欄位"): self.client.update_issue(1) @patch('requests.Session.request') def test_get_project_success(self, mock_request): """測試取得專案""" mock_response = Mock() mock_response.json.return_value = { 'project': { 'id': 1, 'name': '測試專案', 'identifier': 'test-project', 'description': '專案描述', 'status': 1 } } mock_response.content = b'content' mock_request.return_value = mock_response project = self.client.get_project(1) assert isinstance(project, RedmineProject) assert project.id == 1 assert project.name == '測試專案' assert project.identifier == 'test-project' @patch('requests.Session.request') def test_test_connection_success(self, mock_request): """測試連線成功""" mock_response = Mock() mock_response.json.return_value = {'user': {'id': 1, 'login': 'test'}} mock_response.content = b'content' mock_request.return_value = mock_response result = self.client.test_connection() assert result is True @patch('requests.Session.request') def test_test_connection_failure(self, mock_request): """測試連線失敗""" mock_request.side_effect = RedmineAPIError("連線失敗") result = self.client.test_connection() assert result is False class TestClientSingleton: """測試客戶端單例模式""" def test_get_client_singleton(self): """測試 get_client 回傳同一個實例""" with patch.dict(os.environ, { 'REDMINE_DOMAIN': 'https://test.redmine.com', 'REDMINE_API_KEY': 'test_api_key' }): client1 = get_client() client2 = get_client() assert client1 is client2 def test_reload_client(self): """測試 reload_client 建立新實例""" with patch.dict(os.environ, { 'REDMINE_DOMAIN': 'https://test.redmine.com', 'REDMINE_API_KEY': 'test_api_key' }): client1 = get_client() client2 = reload_client() assert client1 is not client2 assert client1.config.redmine_domain == client2.config.redmine_domain

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/snowild/redmine-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server