1
1
import httpx
2
2
import pytest
3
+ from unittest .mock import AsyncMock , MagicMock , patch
3
4
4
5
from supabase_mcp .clients .management_client import ManagementAPIClient
5
6
from supabase_mcp .exceptions import APIClientError , APIConnectionError
7
+ from supabase_mcp .settings import Settings
6
8
7
9
8
10
@pytest .mark .asyncio (loop_scope = "module" )
9
- @pytest .mark .integration
10
11
class TestAPIClient :
11
- """Integration tests for the API client."""
12
-
13
- async def test_execute_get_request (self , api_client_integration : ManagementAPIClient ):
12
+ """Unit tests for the API client."""
13
+
14
+ @pytest .fixture
15
+ def mock_settings (self ):
16
+ """Create mock settings for testing."""
17
+ settings = MagicMock (spec = Settings )
18
+ settings .supabase_access_token = "test-token"
19
+ settings .supabase_project_ref = "test-project-ref"
20
+ settings .supabase_region = "us-east-1"
21
+ settings .query_api_url = "https://api.test.com"
22
+ settings .supabase_api_url = "https://api.supabase.com"
23
+ return settings
24
+
25
+ async def test_execute_get_request (self , mock_settings ):
14
26
"""Test executing a GET request to the API."""
15
- # Make a simple GET request to a public endpoint
16
- # Since /v1/health returns 404, we'll test the error handling instead
17
- path = "/v1/health"
18
-
19
- # Execute the request and expect a 404 error
20
- with pytest .raises (APIClientError ) as exc_info :
21
- await api_client_integration .execute_request (
22
- method = "GET" ,
23
- path = path ,
24
- )
25
-
26
- # Verify the error details
27
- assert exc_info .value .status_code == 404
28
- assert "Cannot GET /v1/health" in str (exc_info .value )
29
-
30
- # @pytest.mark.asyncio(loop_scope="function")
31
- async def test_request_preparation (self , api_client_integration : ManagementAPIClient ):
27
+ # Create client but don't mock the httpx client yet
28
+ client = ManagementAPIClient (settings = mock_settings )
29
+
30
+ # Setup mock response
31
+ mock_response = MagicMock (spec = httpx .Response )
32
+ mock_response .status_code = 404
33
+ mock_response .is_success = False
34
+ mock_response .headers = {"content-type" : "application/json" }
35
+ mock_response .json .return_value = {"message" : "Cannot GET /v1/health" }
36
+ mock_response .text = '{"message": "Cannot GET /v1/health"}'
37
+ mock_response .content = b'{"message": "Cannot GET /v1/health"}'
38
+
39
+ # Mock the send_request method to return our mock response
40
+ with patch .object (client , 'send_request' , return_value = mock_response ):
41
+ path = "/v1/health"
42
+
43
+ # Execute the request and expect a 404 error
44
+ with pytest .raises (APIClientError ) as exc_info :
45
+ await client .execute_request (
46
+ method = "GET" ,
47
+ path = path ,
48
+ )
49
+
50
+ # Verify the error details
51
+ assert exc_info .value .status_code == 404
52
+ assert "Cannot GET /v1/health" in str (exc_info .value )
53
+
54
+ async def test_request_preparation (self , mock_settings ):
32
55
"""Test that requests are properly prepared with headers and parameters."""
56
+ client = ManagementAPIClient (settings = mock_settings )
57
+
33
58
# Prepare a request with parameters
34
59
method = "GET"
35
60
path = "/v1/health"
36
61
request_params = {"param1" : "value1" , "param2" : "value2" }
37
62
38
63
# Prepare the request
39
- request = api_client_integration .prepare_request (
64
+ request = client .prepare_request (
40
65
method = method ,
41
66
path = path ,
42
67
request_params = request_params ,
@@ -50,37 +75,44 @@ async def test_request_preparation(self, api_client_integration: ManagementAPICl
50
75
assert "Content-Type" in request .headers
51
76
assert request .headers ["Content-Type" ] == "application/json"
52
77
53
- # @pytest.mark.asyncio(loop_scope="function")
54
- async def test_error_handling (self , api_client_integration : ManagementAPIClient ):
78
+ async def test_error_handling (self , mock_settings ):
55
79
"""Test handling of API errors."""
56
- # Make a request to a non-existent endpoint
57
- path = "/v1/nonexistent-endpoint"
58
-
59
- # Execute the request and expect an APIClientError (not APIResponseError)
60
- with pytest .raises (APIClientError ) as exc_info :
61
- await api_client_integration .execute_request (
62
- method = "GET" ,
63
- path = path ,
64
- )
65
-
66
- # Verify the error details
67
- assert exc_info .value .status_code == 404
68
- assert "Cannot GET /v1/nonexistent-endpoint" in str (exc_info .value )
69
-
70
- # @pytest.mark.asyncio(loop_scope="function")
71
- async def test_request_with_body (self , api_client_integration : ManagementAPIClient ):
80
+ client = ManagementAPIClient (settings = mock_settings )
81
+
82
+ # Setup mock response
83
+ mock_response = MagicMock (spec = httpx .Response )
84
+ mock_response .status_code = 404
85
+ mock_response .is_success = False
86
+ mock_response .headers = {"content-type" : "application/json" }
87
+ mock_response .json .return_value = {"message" : "Cannot GET /v1/nonexistent-endpoint" }
88
+ mock_response .text = '{"message": "Cannot GET /v1/nonexistent-endpoint"}'
89
+ mock_response .content = b'{"message": "Cannot GET /v1/nonexistent-endpoint"}'
90
+
91
+ with patch .object (client , 'send_request' , return_value = mock_response ):
92
+ path = "/v1/nonexistent-endpoint"
93
+
94
+ # Execute the request and expect an APIClientError
95
+ with pytest .raises (APIClientError ) as exc_info :
96
+ await client .execute_request (
97
+ method = "GET" ,
98
+ path = path ,
99
+ )
100
+
101
+ # Verify the error details
102
+ assert exc_info .value .status_code == 404
103
+ assert "Cannot GET /v1/nonexistent-endpoint" in str (exc_info .value )
104
+
105
+ async def test_request_with_body (self , mock_settings ):
72
106
"""Test executing a request with a body."""
73
- # This test would normally make a POST request with a body
74
- # Since we don't want to create real resources, we'll use a mock
75
- # or a safe endpoint that accepts POST but doesn't modify anything
76
-
77
- # For now, we'll just test the request preparation
107
+ client = ManagementAPIClient (settings = mock_settings )
108
+
109
+ # Test the request preparation
78
110
method = "POST"
79
- path = "/v1/health/check" # This is a hypothetical endpoint
111
+ path = "/v1/health/check"
80
112
request_body = {"test" : "data" , "nested" : {"value" : 123 }}
81
113
82
114
# Prepare the request
83
- request = api_client_integration .prepare_request (
115
+ request = client .prepare_request (
84
116
method = method ,
85
117
path = path ,
86
118
request_body = request_body ,
@@ -93,56 +125,64 @@ async def test_request_with_body(self, api_client_integration: ManagementAPIClie
93
125
assert "Content-Type" in request .headers
94
126
assert request .headers ["Content-Type" ] == "application/json"
95
127
96
- # @pytest.mark.asyncio(loop_scope="function")
97
- async def test_response_parsing (self , api_client_integration : ManagementAPIClient ):
128
+ async def test_response_parsing (self , mock_settings ):
98
129
"""Test parsing API responses."""
99
- # Make a request to a public endpoint that returns JSON
100
- path = "/v1/projects"
101
-
102
- # Execute the request
103
- response = await api_client_integration .execute_request (
104
- method = "GET" ,
105
- path = path ,
106
- )
107
-
108
- # Verify the response is parsed correctly
109
- assert isinstance (response , list )
110
- # The response is a list of projects, not a dict with a "projects" key
111
- assert len (response ) > 0
112
- assert "id" in response [0 ]
113
-
114
- # @pytest.mark.asyncio(loop_scope="function")
115
- async def test_request_retry_mechanism (self , monkeypatch , api_client_integration : ManagementAPIClient ):
116
- """Test that the tenacity retry mechanism works correctly for API requests."""
117
-
118
- # Create a simple mock that always raises a network error
119
- async def mock_send (* args , ** kwargs ):
120
- raise httpx .NetworkError ("Simulated network failure" , request = None )
121
-
122
- # Replace the client's send method with our mock
123
- monkeypatch .setattr (api_client_integration .client , "send" , mock_send )
124
-
125
- # Execute a request - this should trigger retries and eventually fail
126
- with pytest .raises (APIConnectionError ) as exc_info :
127
- await api_client_integration .execute_request (
130
+ client = ManagementAPIClient (settings = mock_settings )
131
+
132
+ # Setup mock response
133
+ mock_response = MagicMock (spec = httpx .Response )
134
+ mock_response .status_code = 200
135
+ mock_response .is_success = True
136
+ mock_response .headers = {"content-type" : "application/json" }
137
+ mock_response .json .return_value = [{"id" : "project1" , "name" : "Test Project" }]
138
+ mock_response .content = b'[{"id": "project1", "name": "Test Project"}]'
139
+
140
+ with patch .object (client , 'send_request' , return_value = mock_response ):
141
+ path = "/v1/projects"
142
+
143
+ # Execute the request
144
+ response = await client .execute_request (
128
145
method = "GET" ,
129
- path = "/v1/projects" ,
146
+ path = path ,
130
147
)
148
+
149
+ # Verify the response is parsed correctly
150
+ assert isinstance (response , list )
151
+ assert len (response ) > 0
152
+ assert "id" in response [0 ]
131
153
132
- # Verify the error message indicates retries were attempted
133
- assert "Network error after 3 retry attempts" in str (exc_info .value )
134
-
135
- # @pytest.mark.asyncio(loop_scope="function")
136
- async def test_request_without_access_token (
137
- self , monkeypatch : pytest .MonkeyPatch , api_client_integration : ManagementAPIClient
138
- ):
154
+ async def test_request_retry_mechanism (self , mock_settings ):
155
+ """Test that the tenacity retry mechanism works correctly for API requests."""
156
+ client = ManagementAPIClient (settings = mock_settings )
157
+
158
+ # Create a mock request object for the NetworkError
159
+ mock_request = MagicMock (spec = httpx .Request )
160
+ mock_request .method = "GET"
161
+ mock_request .url = "https://api.supabase.com/v1/projects"
162
+
163
+ # Mock the client's send method to always raise a network error
164
+ with patch .object (client .client , 'send' , side_effect = httpx .NetworkError ("Simulated network failure" , request = mock_request )):
165
+ # Execute a request - this should trigger retries and eventually fail
166
+ with pytest .raises (APIConnectionError ) as exc_info :
167
+ await client .execute_request (
168
+ method = "GET" ,
169
+ path = "/v1/projects" ,
170
+ )
171
+
172
+ # Verify the error message indicates retries were attempted
173
+ assert "Network error after 3 retry attempts" in str (exc_info .value )
174
+
175
+ async def test_request_without_access_token (self , mock_settings ):
139
176
"""Test that an exception is raised when attempting to send a request without an access token."""
140
- # Temporarily set the access token to None
141
- monkeypatch .setattr (api_client_integration .settings , "supabase_access_token" , None )
177
+ # Create client with no access token
178
+ mock_settings .supabase_access_token = None
179
+ client = ManagementAPIClient (settings = mock_settings )
142
180
143
181
# Attempt to execute a request - should raise an exception
144
- with pytest .raises (APIClientError ):
145
- await api_client_integration .execute_request (
182
+ with pytest .raises (APIClientError ) as exc_info :
183
+ await client .execute_request (
146
184
method = "GET" ,
147
185
path = "/v1/projects" ,
148
186
)
187
+
188
+ assert "Supabase access token is not configured" in str (exc_info .value )
0 commit comments