import os import sys import io import zipfile from fastapi.testclient import TestClient # Add workspace root to python path sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from main import app, get_supabase_client # Define mock classes to intercept Supabase client interactions class MockResponse: def __init__(self, data): self.data = data class MockQueryBuilder: def __init__(self, table_name, mock_client): self.table_name = table_name self.mock_client = mock_client self.filters = {} def select(self, columns): return self def eq(self, column, value): self.filters[column] = value return self def insert(self, data): if self.table_name == 'problems': if not isinstance(data, list): data = [data] inserted = [] for item in data: item_copy = item.copy() if 'id' not in item_copy: item_copy['id'] = f"mock-prob-id-{len(self.mock_client.problems) + 1}" self.mock_client.problems.append(item_copy) inserted.append(item_copy) self.last_result = inserted elif self.table_name == 'user_reviews': if not isinstance(data, list): data = [data] inserted = [] for item in data: item_copy = item.copy() if 'id' not in item_copy: item_copy['id'] = f"mock-rev-id-{len(self.mock_client.reviews) + 1}" self.mock_client.reviews.append(item_copy) inserted.append(item_copy) self.last_result = inserted return self def update(self, data): self.update_data = data return self def execute(self): if hasattr(self, 'last_result'): return MockResponse(self.last_result) if self.table_name == 'problems': name_filter = self.filters.get('name') id_filter = self.filters.get('id') # If update was called if hasattr(self, 'update_data'): updated = [] for p in self.mock_client.problems: if id_filter and p.get('id') == id_filter: p.update(self.update_data) updated.append(p) return MockResponse(updated) matches = [] for p in self.mock_client.problems: if name_filter and p.get('name') != name_filter: continue if id_filter and p.get('id') != id_filter: continue p_copy = p.copy() p_reviews = [r for r in self.mock_client.reviews if r.get('problem_id') == p.get('id')] p_copy['user_reviews'] = p_reviews matches.append(p_copy) return MockResponse(matches) elif self.table_name == 'user_reviews': return MockResponse(self.mock_client.reviews) return MockResponse([]) class MockSupabaseClient: def __init__(self): self.problems = [] self.reviews = [] self.current_user_id = "mock_user_123" self.auth = self def get_user(self, token): class MockUser: id = "mock_user_123" class MockUserRes: user = MockUser() return MockUserRes() def table(self, table_name): return MockQueryBuilder(table_name, self) # Create a shared mock client state shared_mock_client = MockSupabaseClient() # Override FastAPI dependency def mock_get_supabase_client(): return shared_mock_client app.dependency_overrides[get_supabase_client] = mock_get_supabase_client client = TestClient(app) def test_explorer_endpoints(): print("=== Running Explorer REST API Tests ===") headers = {"Authorization": "Bearer mock-token-123"} # 1. Create Problem 1 (Two Sum) print("\n1. Testing POST /api/explorer/problems/create (Two Sum)") create_payload = { "name": "Two Sum", "pattern": "Two Pointers", "difficulty": "Easy", "reference_code": "def twoSum(nums, target):\n pass", "description": "Find two numbers in the array that sum to target." } res = client.post("/api/explorer/problems/create", json=create_payload, headers=headers) print("Response status:", res.status_code) print("Response JSON:", res.json()) assert res.status_code == 200 assert res.json()["success"] is True prob1_id = res.json()["problem_id"] # Verify review was created assert len(shared_mock_client.reviews) == 1 assert shared_mock_client.reviews[0]["problem_id"] == prob1_id assert shared_mock_client.reviews[0]["box_level"] == 1 # 2. Try to create duplicate problem print("\n2. Testing duplicate prevention") res_dup = client.post("/api/explorer/problems/create", json=create_payload, headers=headers) print("Response status:", res_dup.status_code) print("Response JSON:", res_dup.json()) assert res_dup.status_code == 400 assert "already exists" in res_dup.json()["detail"] # 3. Create Problem 2 (Three Sum) print("\n3. Testing POST /api/explorer/problems/create (Three Sum)") create_payload_2 = { "name": "Three Sum", "pattern": "Two Pointers", "difficulty": "Medium", "reference_code": "def threeSum(nums):\n pass", "description": "Find all unique triplets that sum to zero." } res2 = client.post("/api/explorer/problems/create", json=create_payload_2, headers=headers) assert res2.status_code == 200 prob2_id = res2.json()["problem_id"] # 4. List Problems print("\n4. Testing GET /api/explorer/problems") res_list = client.get("/api/explorer/problems", headers=headers) print("Response status:", res_list.status_code) problems_list = res_list.json() print("List count:", len(problems_list)) for p in problems_list: print(f" - Problem: {p['name']}, Box: {p['box_level']}, Description: '{p['description']}', Code length: {len(p['reference_code'])}") assert res_list.status_code == 200 assert len(problems_list) == 2 assert problems_list[0]["box_level"] == 1 assert problems_list[0]["description"] == "Find two numbers in the array that sum to target." # 5. Update reference code for Two Sum print("\n5. Testing PUT /api/explorer/problems/{id}") new_code = "def twoSum(nums, target):\n # Optimized solution\n seen = {}\n for i, num in enumerate(nums):\n diff = target - num\n if diff in seen:\n return [seen[diff], i]\n seen[num] = i\n return []" update_payload = { "name": "Two Sum", "pattern": "Two Pointers", "difficulty": "Easy", "reference_code": new_code, "description": "Find two numbers in the array that sum to target (Optimized)." } res_update = client.put(f"/api/explorer/problems/{prob1_id}", json=update_payload, headers=headers) print("Response status:", res_update.status_code) print("Response JSON:", res_update.json()) assert res_update.status_code == 200 assert res_update.json()["success"] is True # Verify update in listing res_list2 = client.get("/api/explorer/problems", headers=headers) problems_list2 = res_list2.json() two_sum_updated = next(p for p in problems_list2 if p["id"] == prob1_id) print("Updated Two Sum code length:", len(two_sum_updated["reference_code"])) print("Updated Two Sum description:", two_sum_updated["description"]) assert "Optimized solution" in two_sum_updated["reference_code"] assert "Optimized" in two_sum_updated["description"] # 6. Export ZIP of solutions print("\n6. Testing GET /api/explorer/export (ZIP download)") res_export = client.get("/api/explorer/export", headers=headers) print("Response status:", res_export.status_code) print("Content-Type:", res_export.headers.get("content-type")) assert res_export.status_code == 200 assert res_export.headers.get("content-type") == "application/zip" # Validate Zip structure zip_bytes = io.BytesIO(res_export.content) with zipfile.ZipFile(zip_bytes, "r") as zf: namelist = zf.namelist() print("Files in ZIP:", namelist) assert "Two_Sum.py" in namelist assert "Three_Sum.py" in namelist # Read content from ZIP two_sum_zip_code = zf.read("Two_Sum.py").decode("utf-8") print("Two_Sum.py contents in ZIP:") print(two_sum_zip_code) assert "Optimized solution" in two_sum_zip_code assert "# Description:" in two_sum_zip_code assert "Find two numbers in the array that sum to target" in two_sum_zip_code print("\n=== ALL TESTS PASSED SUCCESSFULLY ===") if __name__ == "__main__": test_explorer_endpoints()