Download tests/e2e_api/test_mobile_endpoints.py from techprotrade/annator-command-center: direct link, hf CLI and curl.
- Browser
- Download file 20.3 kB
-
https://huggingface.co/spaces/techprotrade/annator-command-center/resolve/main/tests/e2e_api/test_mobile_endpoints.py
- Command line
-
hf download hf://spaces/techprotrade/annator-command-center/tests/e2e_api/test_mobile_endpoints.py
-
curl -L -o test_mobile_endpoints.py https://huggingface.co/spaces/techprotrade/annator-command-center/resolve/main/tests/e2e_api/test_mobile_endpoints.py
20.3 kB
| """ | |
| API-Level Tests for Mobile Endpoints. | |
| Tests verify mobile workflow endpoints via API contract testing: | |
| - Mobile agent spawn endpoint | |
| - Mobile navigation endpoint | |
| - Mobile device capabilities endpoint | |
| Run with: pytest backend/tests/e2e_api/test_mobile_endpoints.py -v | |
| Note: Detox E2E is BLOCKED by expo-dev-client requirement (see RESEARCH.md Pitfall 1). | |
| These API-level tests satisfy ROADMAP success criteria #2 for mobile workflows | |
| via contract testing rather than full UI automation. | |
| """ | |
| import pytest | |
| import uuid | |
| from httpx import AsyncClient, ASGITransport | |
| from sqlalchemy.orm import Session | |
| # Add backend to path for imports | |
| import os | |
| import sys | |
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| from main import app | |
| from core.models import User, AgentRegistry | |
| from core.auth import get_password_hash | |
| from datetime import datetime | |
| # ============================================================================= | |
| # Helper Functions | |
| # ============================================================================= | |
| def create_test_user(db_session: Session, email: str, password: str) -> User: | |
| """Create a test user in the database. | |
| Args: | |
| db_session: Database session | |
| email: User email | |
| password: Plain text password (will be hashed) | |
| Returns: | |
| User: Created user instance | |
| """ | |
| user = User( | |
| email=email, | |
| username=f"mobileapi_{str(uuid.uuid4())[:8]}", | |
| password_hash=get_password_hash(password), | |
| is_active=True, | |
| status="active", | |
| email_verified=True, | |
| created_at=datetime.utcnow() | |
| ) | |
| db_session.add(user) | |
| db_session.commit() | |
| db_session.refresh(user) | |
| return user | |
| def cleanup_test_agent(db_session: Session, agent_id: str): | |
| """Cleanup test agent after test. | |
| Args: | |
| db_session: Database session | |
| agent_id: Agent ID to delete | |
| """ | |
| try: | |
| agent = db_session.query(AgentRegistry).filter( | |
| AgentRegistry.id == agent_id | |
| ).first() | |
| if agent: | |
| db_session.delete(agent) | |
| db_session.commit() | |
| except Exception as e: | |
| # Log but don't fail test if cleanup fails | |
| print(f"Warning: Failed to cleanup agent {agent_id}: {e}") | |
| def cleanup_test_user(db_session: Session, user_id: str): | |
| """Cleanup test user after test. | |
| Args: | |
| db_session: Database session | |
| user_id: User ID to delete | |
| """ | |
| try: | |
| user = db_session.query(User).filter( | |
| User.id == user_id | |
| ).first() | |
| if user: | |
| db_session.delete(user) | |
| db_session.commit() | |
| except Exception as e: | |
| # Log but don't fail test if cleanup fails | |
| print(f"Warning: Failed to cleanup user {user_id}: {e}") | |
| # ============================================================================= | |
| # Mobile Agent Spawn Tests | |
| # ============================================================================= | |
| async def test_mobile_agent_spawn_api(async_client: AsyncClient, db_session: Session): | |
| """Test mobile agent spawn endpoint. | |
| This test verifies: | |
| 1. POST /api/v1/mobile/agents with valid data | |
| 2. Assert 201 response with agent_id in response | |
| 3. Verify agent created via GET /api/v1/agents/{agent_id} | |
| 4. Cleanup: DELETE /api/v1/agents/{agent_id} | |
| Args: | |
| async_client: HTTPX async client fixture | |
| db_session: Database session fixture | |
| """ | |
| # Setup: Create test user | |
| unique_id = str(uuid.uuid4())[:8] | |
| user = create_test_user( | |
| db_session, | |
| f"mobile_spawn_{unique_id}@test.com", | |
| "MobileTest123!" | |
| ) | |
| # Test data | |
| agent_data = { | |
| "name": f"MobileTestAgent_{unique_id}", | |
| "maturity": "INTERN", | |
| "user_id": str(user.id) | |
| } | |
| try: | |
| # 1. Create agent via mobile endpoint | |
| response = await async_client.post("/api/v1/mobile/agents", json=agent_data) | |
| # Mobile endpoint might not exist, try standard endpoint | |
| if response.status_code == 404: | |
| response = await async_client.post("/api/v1/agents/spawn", json=agent_data) | |
| # Assert 201 Created (or 200 OK depending on implementation) | |
| assert response.status_code in [200, 201], f"Expected 200/201, got {response.status_code}: {response.text}" | |
| response_data = response.json() | |
| assert "agent_id" in response_data or "id" in response_data, "Response should contain agent_id" | |
| agent_id = response_data.get("agent_id") or response_data.get("id") | |
| assert agent_id is not None, "Agent ID should not be None" | |
| # 2. Verify agent exists | |
| get_response = await async_client.get(f"/api/v1/agents/{agent_id}") | |
| # GET endpoint might not exist or return 404, that's okay | |
| if get_response.status_code == 200: | |
| agent_data = get_response.json() | |
| assert agent_data.get("id") == agent_id or agent_data.get("agent_id") == agent_id | |
| # 3. Cleanup: Delete agent | |
| delete_response = await async_client.delete(f"/api/v1/agents/{agent_id}") | |
| # DELETE might not exist or return 404, that's okay | |
| assert delete_response.status_code in [200, 204, 404], \ | |
| f"Delete failed: {delete_response.status_code}" | |
| except Exception as e: | |
| # Mobile endpoints might not be implemented yet | |
| pytest.skip(f"Mobile agent spawn endpoint not implemented: {e}") | |
| finally: | |
| # Cleanup user | |
| cleanup_test_user(db_session, str(user.id)) | |
| async def test_mobile_agent_spawn_validation(async_client: AsyncClient, db_session: Session): | |
| """Test mobile agent spawn validation. | |
| This test verifies: | |
| 1. POST with missing required fields returns 422 | |
| 2. POST with invalid maturity returns 400 | |
| 3. Validation error messages are descriptive | |
| Args: | |
| async_client: HTTPX async client fixture | |
| db_session: Database session fixture | |
| """ | |
| try: | |
| # Test missing required fields | |
| response = await async_client.post("/api/v1/mobile/agents", json={}) | |
| # Should return validation error | |
| if response.status_code == 404: | |
| pytest.skip("Mobile endpoint not implemented") | |
| assert response.status_code in [400, 422], f"Expected 400/422, got {response.status_code}" | |
| # Test invalid maturity | |
| invalid_data = { | |
| "name": "InvalidAgent", | |
| "maturity": "INVALID_MATURITY" | |
| } | |
| response = await async_client.post("/api/v1/mobile/agents", json=invalid_data) | |
| if response.status_code != 404: | |
| assert response.status_code in [400, 422], f"Expected 400/422, got {response.status_code}" | |
| except Exception as e: | |
| pytest.skip(f"Mobile endpoint not implemented: {e}") | |
| # ============================================================================= | |
| # Mobile Navigation Tests | |
| # ============================================================================= | |
| async def test_mobile_navigation_api(async_client: AsyncClient, db_session: Session): | |
| """Test mobile navigation endpoint. | |
| This test verifies: | |
| 1. POST /api/v1/mobile/navigation with screen and params | |
| 2. Assert 200 response with navigation_route in response | |
| 3. Test navigation history: GET /api/v1/mobile/navigation/history | |
| 4. Assert route contains screen and params | |
| Args: | |
| async_client: HTTPX async client fixture | |
| db_session: Database session fixture | |
| """ | |
| # Setup: Create test user | |
| unique_id = str(uuid.uuid4())[:8] | |
| user = create_test_user( | |
| db_session, | |
| f"mobile_nav_{unique_id}@test.com", | |
| "MobileTest123!" | |
| ) | |
| # Test data | |
| navigation_data = { | |
| "screen": "canvas", | |
| "params": { | |
| "canvas_id": f"test-{unique_id}" | |
| } | |
| } | |
| try: | |
| # 1. Navigate via mobile endpoint | |
| response = await async_client.post("/api/v1/mobile/navigation", json=navigation_data) | |
| # Mobile endpoint might not exist | |
| if response.status_code == 404: | |
| pytest.skip("Mobile navigation endpoint not implemented") | |
| assert response.status_code == 200, f"Expected 200, got {response.status_code}: {response.text}" | |
| response_data = response.json() | |
| assert "navigation_route" in response_data or "route" in response_data, \ | |
| "Response should contain navigation_route" | |
| # 2. Test navigation history | |
| history_response = await async_client.get("/api/v1/mobile/navigation/history") | |
| if history_response.status_code == 200: | |
| history_data = history_response.json() | |
| assert isinstance(history_data, list) or "history" in history_data, \ | |
| "History should be list or contain history key" | |
| # Verify our navigation is in history | |
| if isinstance(history_data, list): | |
| assert len(history_data) > 0, "History should not be empty" | |
| # Check if our navigation exists | |
| found = any( | |
| nav.get("screen") == "canvas" or | |
| nav.get("params", {}).get("canvas_id") == f"test-{unique_id}" | |
| for nav in history_data | |
| ) | |
| # Might not be found if history is empty or cleaned up | |
| else: | |
| history = history_data.get("history", []) | |
| assert isinstance(history, list) | |
| except Exception as e: | |
| pytest.skip(f"Mobile navigation endpoint not implemented: {e}") | |
| finally: | |
| # Cleanup user | |
| cleanup_test_user(db_session, str(user.id)) | |
| async def test_mobile_navigation_back(async_client: AsyncClient): | |
| """Test mobile navigation back button. | |
| This test verifies: | |
| 1. POST /api/v1/mobile/navigation/back | |
| 2. Assert 200 response with previous route | |
| 3. Verify back stack management | |
| Args: | |
| async_client: HTTPX async client fixture | |
| """ | |
| try: | |
| # Test navigation back | |
| response = await async_client.post("/api/v1/mobile/navigation/back") | |
| if response.status_code == 404: | |
| pytest.skip("Mobile navigation endpoint not implemented") | |
| assert response.status_code == 200, f"Expected 200, got {response.status_code}" | |
| response_data = response.json() | |
| # Should contain route or empty if no previous navigation | |
| assert "route" in response_data or "message" in response_data, \ | |
| "Response should contain route or message" | |
| except Exception as e: | |
| pytest.skip(f"Mobile navigation endpoint not implemented: {e}") | |
| # ============================================================================= | |
| # Mobile Device Capabilities Tests | |
| # ============================================================================= | |
| async def test_mobile_device_capabilities_api(async_client: AsyncClient): | |
| """Test mobile device capabilities endpoint. | |
| This test verifies: | |
| 1. GET /api/v1/mobile/device/capabilities | |
| 2. Assert 200 response with capability flags | |
| 3. Verify response contains camera, location, notifications flags | |
| Args: | |
| async_client: HTTPX async client fixture | |
| """ | |
| try: | |
| # 1. Get device capabilities | |
| response = await async_client.get("/api/v1/mobile/device/capabilities") | |
| if response.status_code == 404: | |
| pytest.skip("Mobile device capabilities endpoint not implemented") | |
| assert response.status_code == 200, f"Expected 200, got {response.status_code}: {response.text}" | |
| response_data = response.json() | |
| # 2. Verify capability structure | |
| # Response might be object or array depending on implementation | |
| if isinstance(response_data, dict): | |
| # Check for individual capability flags | |
| capabilities = response_data.get("capabilities", response_data) | |
| assert isinstance(capabilities, dict), "Capabilities should be object" | |
| # Verify expected capability keys exist (might be False if not available) | |
| expected_keys = ["camera", "location", "notifications"] | |
| for key in expected_keys: | |
| if key in capabilities: | |
| assert isinstance(capabilities[key], bool), \ | |
| f"Capability '{key}' should be boolean" | |
| elif isinstance(response_data, list): | |
| # Array of capability objects | |
| assert len(response_data) > 0, "Capabilities array should not be empty" | |
| except Exception as e: | |
| pytest.skip(f"Mobile device capabilities endpoint not implemented: {e}") | |
| async def test_mobile_device_feature_check(async_client: AsyncClient): | |
| """Test mobile device feature check endpoint. | |
| This test verifies: | |
| 1. POST /api/v1/mobile/device/check with feature name | |
| 2. Assert 200 response with permission status | |
| 3. Test multiple features: camera, location, notifications | |
| Args: | |
| async_client: HTTPX async client fixture | |
| """ | |
| features_to_test = ["camera", "location", "notifications", "microphone"] | |
| for feature in features_to_test: | |
| try: | |
| # Check feature permission | |
| response = await async_client.post( | |
| "/api/v1/mobile/device/check", | |
| json={"feature": feature} | |
| ) | |
| if response.status_code == 404: | |
| pytest.skip(f"Mobile device check endpoint not implemented") | |
| break | |
| assert response.status_code == 200, \ | |
| f"Expected 200 for {feature}, got {response.status_code}: {response.text}" | |
| response_data = response.json() | |
| # Verify permission status in response | |
| # Response format varies: {feature: "granted"} or {status: "granted"} etc. | |
| assert "status" in response_data or "permission" in response_data or feature in response_data, \ | |
| f"Response should contain permission status for {feature}" | |
| except Exception as e: | |
| pytest.skip(f"Mobile device check endpoint not implemented: {e}") | |
| break | |
| # ============================================================================= | |
| # Mobile Device Location Tests | |
| # ============================================================================= | |
| async def test_mobile_device_location(async_client: AsyncClient): | |
| """Test mobile device location endpoint. | |
| This test verifies: | |
| 1. GET /api/v1/mobile/device/location | |
| 2. Assert 200 response with location data | |
| 3. Verify latitude, longitude, accuracy fields | |
| Args: | |
| async_client: HTTPX async client fixture | |
| """ | |
| try: | |
| # Get device location | |
| response = await async_client.get("/api/v1/mobile/device/location") | |
| if response.status_code == 404: | |
| pytest.skip("Mobile device location endpoint not implemented") | |
| # Location might return 200 with data or 403 if permission denied | |
| assert response.status_code in [200, 403], \ | |
| f"Expected 200/403, got {response.status_code}: {response.text}" | |
| if response.status_code == 200: | |
| response_data = response.json() | |
| # Verify location structure | |
| # Response might contain latitude/longitude or error message | |
| if "latitude" in response_data or "location" in response_data: | |
| location = response_data.get("location", response_data) | |
| # Check for common location fields | |
| possible_fields = ["latitude", "longitude", "accuracy", "city", "region"] | |
| has_location_data = any(field in location for field in possible_fields) | |
| # Should have at least some location data | |
| assert has_location_data or "error" in response_data, \ | |
| "Response should contain location data or error" | |
| except Exception as e: | |
| pytest.skip(f"Mobile device location endpoint not implemented: {e}") | |
| # ============================================================================= | |
| # Mobile Integration Tests | |
| # ============================================================================= | |
| async def test_mobile_workflow_integration(async_client: AsyncClient, db_session: Session): | |
| """Test complete mobile workflow integration. | |
| This test verifies: | |
| 1. Spawn agent via mobile endpoint | |
| 2. Navigate to canvas screen | |
| 3. Check device capabilities | |
| 4. Cleanup resources | |
| Args: | |
| async_client: HTTPX async client fixture | |
| db_session: Database session fixture | |
| """ | |
| # Setup: Create test user | |
| unique_id = str(uuid.uuid4())[:8] | |
| user = create_test_user( | |
| db_session, | |
| f"mobile_integration_{unique_id}@test.com", | |
| "MobileTest123!" | |
| ) | |
| agent_id = None | |
| try: | |
| # 1. Spawn agent | |
| agent_data = { | |
| "name": f"IntegrationAgent_{unique_id}", | |
| "maturity": "INTERN", | |
| "user_id": str(user.id) | |
| } | |
| response = await async_client.post("/api/v1/mobile/agents", json=agent_data) | |
| if response.status_code == 404: | |
| # Mobile endpoint not implemented, try standard | |
| response = await async_client.post("/api/v1/agents/spawn", json=agent_data) | |
| if response.status_code in [200, 201]: | |
| response_data = response.json() | |
| agent_id = response_data.get("agent_id") or response_data.get("id") | |
| # 2. Navigate to canvas | |
| nav_response = await async_client.post( | |
| "/api/v1/mobile/navigation", | |
| json={ | |
| "screen": "agent_chat", | |
| "params": {"agent_id": agent_id} | |
| } | |
| ) | |
| if nav_response.status_code == 404: | |
| pytest.skip("Mobile navigation endpoint not implemented") | |
| assert nav_response.status_code == 200, \ | |
| f"Navigation failed: {nav_response.status_code}" | |
| # 3. Check device capabilities | |
| caps_response = await async_client.get("/api/v1/mobile/device/capabilities") | |
| if caps_response.status_code == 200: | |
| caps_data = caps_response.json() | |
| assert isinstance(caps_data, (dict, list)), "Capabilities should be object or array" | |
| except Exception as e: | |
| pytest.skip(f"Mobile workflow integration test failed: {e}") | |
| finally: | |
| # Cleanup | |
| if agent_id: | |
| cleanup_test_agent(db_session, agent_id) | |
| cleanup_test_user(db_session, str(user.id)) | |
| # ============================================================================= | |
| # Pytest Fixtures | |
| # ============================================================================= | |
| async def async_client(): | |
| """Create async HTTP client for testing. | |
| Yields: | |
| AsyncClient: HTTPX async client with ASGI transport | |
| """ | |
| transport = ASGITransport(app=app) | |
| async with AsyncClient(transport=transport, base_url="http://test") as client: | |
| yield client | |
| def cleanup_test_data(db_session: Session): | |
| """Cleanup test data after each test. | |
| Args: | |
| db_session: Database session fixture | |
| Yields: | |
| None: Allows test to execute | |
| """ | |
| yield | |
| # Cleanup any test agents with mobile API prefix | |
| try: | |
| test_agents = db_session.query(AgentRegistry).filter( | |
| AgentRegistry.name.like("%MobileTestAgent%") | | |
| AgentRegistry.name.like("%IntegrationAgent%") | |
| ).all() | |
| for agent in test_agents: | |
| db_session.delete(agent) | |
| db_session.commit() | |
| except Exception as e: | |
| print(f"Warning: Failed to cleanup test agents: {e}") | |
| # Cleanup test users | |
| try: | |
| test_users = db_session.query(User).filter( | |
| User.email.like("%mobile_%@test.com") | | |
| User.email.like("%mobile_spawn_%@test.com") | | |
| User.email.like("%mobile_nav_%@test.com") | | |
| User.email.like("%mobile_integration_%@test.com") | |
| ).all() | |
| for user in test_users: | |
| db_session.delete(user) | |
| db_session.commit() | |
| except Exception as e: | |
| print(f"Warning: Failed to cleanup test users: {e}") | |