Skip to content

Commit 032b8a4

Browse files
Merge pull request #43 from alexander-zuev/fix/api-spec
fix(api): fix a bug with api_spec retrieval
2 parents 141e958 + f235c0a commit 032b8a4

File tree

3 files changed

+142
-25
lines changed

3 files changed

+142
-25
lines changed

.github/workflows/docs/release-checklist.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Pre-release
3030
9. Update .env.example (if necessary) - [X]
3131

3232
Post-release
33-
10. Clean install from PyPi works
33+
10. Clean install from PyPi works - [X]
3434

3535

3636

supabase_mcp/services/api/spec_manager.py

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
# Constants
1111
SPEC_URL = "https://api.supabase.com/api/v1-json"
12-
LOCAL_SPEC_PATH = Path(__file__).parent / "services" / "api" / "specs" / "api_spec.json"
12+
LOCAL_SPEC_PATH = Path(__file__).parent / "specs" / "api_spec.json"
1313

1414

1515
class ApiDomain(str, Enum):
@@ -212,44 +212,50 @@ def _build_caches(self) -> None:
212212

213213

214214
# Example usage (assuming you have an instance of ApiSpecManager called 'spec_manager'):
215-
async def main():
216-
spec_manager = await ApiSpecManager.create()
215+
async def main() -> None:
216+
"""Test function to demonstrate ApiSpecManager functionality."""
217+
# Create a new instance of ApiSpecManager
217218
spec_manager = ApiSpecManager()
218-
spec_manager.spec = json.loads(LOCAL_SPEC_PATH) # Load your JSON here for testing
219-
spec_manager._build_caches()
220219

221-
# 1. Get all paths and methods
220+
# Load the spec
221+
await spec_manager.get_spec()
222+
223+
# Print the path to help debug
224+
print(f"Looking for spec at: {LOCAL_SPEC_PATH}")
225+
226+
# 1. Get all domains
227+
all_domains = spec_manager.get_all_domains()
228+
print("\nAll Domains:")
229+
print(all_domains)
230+
231+
# 2. Get all paths and methods
222232
all_paths = spec_manager.get_all_paths_and_methods()
223-
print("All Paths and Methods:")
224-
for path, methods in all_paths.items():
233+
print("\nAll Paths and Methods (sample):")
234+
# Just print a few to avoid overwhelming output
235+
for i, (path, methods) in enumerate(all_paths.items()):
236+
if i >= 5: # Limit to 5 paths
237+
break
225238
print(f" {path}:")
226239
for method, operation_id in methods.items():
227240
print(f" {method}: {operation_id}")
228241

229-
# 2. Get paths and methods for the "Auth" domain
230-
auth_paths = spec_manager.get_paths_and_methods_by_domain("Auth")
231-
print("\nAuth Paths and Methods:")
232-
for path, methods in auth_paths.items():
242+
# 3. Get paths and methods for the "Edge Functions" domain
243+
edge_paths = spec_manager.get_paths_and_methods_by_domain("Edge Functions")
244+
print("\nEdge Functions Paths and Methods:")
245+
for path, methods in edge_paths.items():
233246
print(f" {path}:")
234247
for method, operation_id in methods.items():
235248
print(f" {method}: {operation_id}")
236249

237-
# 3. Get all domains
238-
all_domains = spec_manager.get_all_domains()
239-
print("\nAll Domains:")
240-
print(all_domains)
241-
242250
# 4. Get the full spec for a specific path and method
243-
path = "/v1/projects/{ref}/config/auth"
251+
path = "/v1/projects/{ref}/functions"
244252
method = "GET"
245253
full_spec = spec_manager.get_spec_for_path_and_method(path, method)
246254
print(f"\nFull Spec for {method} {path}:")
247-
print(json.dumps(full_spec, indent=2))
248-
249-
# # Example of get_spec_part
250-
# schemas = spec_manager.get_spec_part("components", "schemas", "AuthConfigResponse")
251-
# print("\nAuthConfigResponse schema:")
252-
# print(json.dumps(schemas, indent=2))
255+
if full_spec:
256+
print(json.dumps(full_spec, indent=2)[:500] + "...") # Truncate for readability
257+
else:
258+
print("Spec not found for this path/method")
253259

254260

255261
if __name__ == "__main__":

tests/services/api/test_spec_manager.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,114 @@ async def test_get_spec_not_loaded(self, spec_manager_integration: ApiSpecManage
165165

166166
assert result == SAMPLE_SPEC
167167
mock_fetch.assert_called_once()
168+
169+
@pytest.mark.asyncio
170+
async def test_comprehensive_spec_retrieval(self, spec_manager_integration: ApiSpecManager):
171+
"""
172+
Comprehensive test of API spec retrieval and functionality.
173+
This test exactly mirrors the main() function to ensure all aspects work correctly.
174+
"""
175+
# Create a fresh instance to avoid any cached data from other tests
176+
from supabase_mcp.services.api.spec_manager import LOCAL_SPEC_PATH, ApiSpecManager
177+
178+
spec_manager = ApiSpecManager()
179+
180+
# Print the path being used (for debugging)
181+
print(f"\nTest is looking for spec at: {LOCAL_SPEC_PATH}")
182+
183+
# Load the spec
184+
spec = await spec_manager.get_spec()
185+
assert spec is not None, "Spec should be loaded successfully"
186+
187+
# 1. Test get_all_domains
188+
all_domains = spec_manager.get_all_domains()
189+
print(f"\nAll domains: {all_domains}")
190+
assert len(all_domains) > 0, "Should have at least one domain"
191+
192+
# Verify all expected domains are present
193+
expected_domains = [
194+
"Analytics",
195+
"Auth",
196+
"Database",
197+
"Domains",
198+
"Edge Functions",
199+
"Environments",
200+
"OAuth",
201+
"Organizations",
202+
"Projects",
203+
"Rest",
204+
"Secrets",
205+
"Storage",
206+
]
207+
for domain in expected_domains:
208+
assert domain in all_domains, f"Domain '{domain}' should be in the list of domains"
209+
210+
# 2. Test get_all_paths_and_methods
211+
all_paths = spec_manager.get_all_paths_and_methods()
212+
assert len(all_paths) > 0, "Should have at least one path"
213+
214+
# Sample a few paths to verify
215+
sample_paths = list(all_paths.keys())[:5]
216+
print("\nSample paths:")
217+
for path in sample_paths:
218+
print(f" {path}:")
219+
assert path.startswith("/v1/"), f"Path {path} should start with /v1/"
220+
assert len(all_paths[path]) > 0, f"Path {path} should have at least one method"
221+
for method, operation_id in all_paths[path].items():
222+
print(f" {method}: {operation_id}")
223+
assert method.lower() in ["get", "post", "put", "patch", "delete"], f"Method {method} should be valid"
224+
assert operation_id.startswith("v1-"), f"Operation ID {operation_id} should start with v1-"
225+
226+
# 3. Test get_paths_and_methods_by_domain for each domain
227+
for domain in expected_domains:
228+
domain_paths = spec_manager.get_paths_and_methods_by_domain(domain)
229+
assert len(domain_paths) > 0, f"Domain {domain} should have at least one path"
230+
print(f"\n{domain} domain has {len(domain_paths)} paths")
231+
232+
# 4. Test Edge Functions domain specifically
233+
edge_paths = spec_manager.get_paths_and_methods_by_domain("Edge Functions")
234+
print("\nEdge Functions Paths and Methods:")
235+
for path in edge_paths:
236+
print(f" {path}")
237+
for method, operation_id in edge_paths[path].items():
238+
print(f" {method}: {operation_id}")
239+
240+
# Verify specific Edge Functions paths exist
241+
expected_edge_paths = [
242+
"/v1/projects/{ref}/functions",
243+
"/v1/projects/{ref}/functions/{function_slug}",
244+
"/v1/projects/{ref}/functions/deploy",
245+
]
246+
for path in expected_edge_paths:
247+
assert path in edge_paths, f"Expected path {path} should be in Edge Functions domain"
248+
249+
# 5. Test get_spec_for_path_and_method
250+
# Test for Edge Functions
251+
path = "/v1/projects/{ref}/functions"
252+
method = "GET"
253+
full_spec = spec_manager.get_spec_for_path_and_method(path, method)
254+
assert full_spec is not None, f"Should find spec for {method} {path}"
255+
assert "operationId" in full_spec, "Spec should include operationId"
256+
assert full_spec["operationId"] == "v1-list-all-functions", "Should have correct operationId"
257+
258+
# Test for another domain (Auth)
259+
auth_path = "/v1/projects/{ref}/config/auth"
260+
auth_method = "GET"
261+
auth_spec = spec_manager.get_spec_for_path_and_method(auth_path, auth_method)
262+
assert auth_spec is not None, f"Should find spec for {auth_method} {auth_path}"
263+
assert "operationId" in auth_spec, "Auth spec should include operationId"
264+
265+
# 6. Test get_spec_part
266+
# Get a specific schema
267+
schema = spec_manager.get_spec_part("components", "schemas", "FunctionResponse")
268+
assert schema is not None, "Should find FunctionResponse schema"
269+
assert "properties" in schema, "Schema should have properties"
270+
271+
# 7. Test caching behavior
272+
# Call get_spec again - should use cached version
273+
import time
274+
275+
start_time = time.time()
276+
await spec_manager.get_spec()
277+
end_time = time.time()
278+
assert (end_time - start_time) < 0.1, "Cached spec retrieval should be fast"

0 commit comments

Comments
 (0)