| """ |
| Browser governance integration tests (INTG-18). |
| |
| Tests cover: |
| - STUDENT agent access |
| - INTERN agent access |
| - SUPERVISED agent access |
| - AUTONOMOUS agent access |
| """ |
| import pytest |
| from fastapi.testclient import TestClient |
| from sqlalchemy.orm import Session |
| from tests.factories.agent_factory import StudentAgentFactory, InternAgentFactory, SupervisedAgentFactory, AutonomousAgentFactory |
| from core.models import BrowserSession |
| from unittest.mock import Mock, patch |
| import uuid |
|
|
|
|
| class TestStudentAgentAccess: |
| """Test STUDENT agent browser access.""" |
|
|
| def test_student_blocked_from_browser_session(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test STUDENT agents blocked from browser sessions.""" |
| student = StudentAgentFactory() |
| db_session.add(student) |
| db_session.commit() |
|
|
| response = client.post( |
| "/api/browser/session/create", |
| json={ |
| "browser_type": "chromium", |
| "agent_id": student.id |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [403, 404, 405] |
|
|
| def test_student_blocked_from_navigation(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test STUDENT agents blocked from browser navigation.""" |
| student = StudentAgentFactory() |
| db_session.add(student) |
| db_session.commit() |
|
|
| session_id = str(uuid.uuid4()) |
|
|
| response = client.post( |
| f"/api/browser/{session_id}/navigate", |
| json={ |
| "url": "https://example.com", |
| "agent_id": student.id |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [403, 404, 405] |
|
|
| def test_student_blocked_from_screenshot(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test STUDENT agents blocked from screenshots.""" |
| student = StudentAgentFactory() |
| db_session.add(student) |
| db_session.commit() |
|
|
| session_id = str(uuid.uuid4()) |
|
|
| response = client.post( |
| f"/api/browser/{session_id}/screenshot", |
| params={"agent_id": student.id}, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [403, 404, 405] |
|
|
|
|
| class TestInternAgentAccess: |
| """Test INTERN agent browser access.""" |
|
|
| def test_intern_can_create_browser_session(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test INTERN agents can create browser sessions.""" |
| intern = InternAgentFactory() |
| db_session.add(intern) |
| db_session.commit() |
|
|
| with patch('api.browser_routes.browser_create_session') as mock_create: |
| mock_create.return_value = { |
| "success": True, |
| "session_id": "intern-session-123" |
| } |
|
|
| response = client.post( |
| "/api/browser/session/create", |
| json={ |
| "browser_type": "chromium", |
| "agent_id": intern.id |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [200, 201, 404, 405] |
|
|
| def test_intern_can_navigate(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test INTERN agents can navigate browser.""" |
| intern = InternAgentFactory() |
| db_session.add(intern) |
| db_session.commit() |
|
|
| session_id = str(uuid.uuid4()) |
|
|
| with patch('api.browser_routes.browser_navigate') as mock_navigate: |
| mock_navigate.return_value = {"success": True} |
|
|
| response = client.post( |
| f"/api/browser/{session_id}/navigate", |
| json={ |
| "url": "https://example.com", |
| "agent_id": intern.id |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [200, 201, 404, 405] |
|
|
| def test_intern_can_take_screenshot(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test INTERN agents can take screenshots.""" |
| intern = InternAgentFactory() |
| db_session.add(intern) |
| db_session.commit() |
|
|
| session_id = str(uuid.uuid4()) |
|
|
| with patch('api.browser_routes.browser_screenshot') as mock_screenshot: |
| mock_screenshot.return_value = { |
| "success": True, |
| "screenshot": "base64image" |
| } |
|
|
| response = client.post( |
| f"/api/browser/{session_id}/screenshot", |
| params={"agent_id": intern.id}, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [200, 201, 404, 405] |
|
|
| def test_intern_can_fill_forms(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test INTERN agents can fill forms.""" |
| intern = InternAgentFactory() |
| db_session.add(intern) |
| db_session.commit() |
|
|
| session_id = str(uuid.uuid4()) |
|
|
| with patch('api.browser_routes.browser_fill') as mock_fill: |
| mock_fill.return_value = {"success": True} |
|
|
| response = client.post( |
| f"/api/browser/{session_id}/fill", |
| json={ |
| "selector": "#email", |
| "value": "intern@example.com", |
| "agent_id": intern.id |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [200, 201, 404, 405] |
|
|
|
|
| class TestSupervisedAgentAccess: |
| """Test SUPERVISED agent browser access.""" |
|
|
| def test_supervised_can_create_browser_session(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test SUPERVISED agents can create browser sessions.""" |
| supervised = SupervisedAgentFactory() |
| db_session.add(supervised) |
| db_session.commit() |
|
|
| with patch('api.browser_routes.browser_create_session') as mock_create: |
| mock_create.return_value = { |
| "success": True, |
| "session_id": "supervised-session-123" |
| } |
|
|
| response = client.post( |
| "/api/browser/session/create", |
| json={ |
| "browser_type": "chromium", |
| "agent_id": supervised.id |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [200, 201, 404, 405] |
|
|
| def test_supervised_can_click_elements(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test SUPERVISED agents can click elements.""" |
| supervised = SupervisedAgentFactory() |
| db_session.add(supervised) |
| db_session.commit() |
|
|
| session_id = str(uuid.uuid4()) |
|
|
| with patch('api.browser_routes.browser_click') as mock_click: |
| mock_click.return_value = {"success": True} |
|
|
| response = client.post( |
| f"/api/browser/{session_id}/click", |
| json={ |
| "selector": "#submit-button", |
| "agent_id": supervised.id |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [200, 201, 404, 405] |
|
|
| def test_supervised_can_submit_forms(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test SUPERVISED agents can submit forms.""" |
| supervised = SupervisedAgentFactory() |
| db_session.add(supervised) |
| db_session.commit() |
|
|
| session_id = str(uuid.uuid4()) |
|
|
| with patch('api.browser_routes.browser_click') as mock_click: |
| mock_click.return_value = {"success": True} |
|
|
| response = client.post( |
| f"/api/browser/{session_id}/click", |
| json={ |
| "selector": "button[type='submit']", |
| "agent_id": supervised.id |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [200, 201, 404, 405] |
|
|
|
|
| class TestAutonomousAgentAccess: |
| """Test AUTONOMOUS agent browser access.""" |
|
|
| def test_autonomous_full_browser_access(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test AUTONOMOUS agents have full browser access.""" |
| autonomous = AutonomousAgentFactory() |
| db_session.add(autonomous) |
| db_session.commit() |
|
|
| session_id = str(uuid.uuid4()) |
|
|
| |
| actions = [ |
| ("navigate", {"url": "https://example.com"}), |
| ("fill", {"selector": "#email", "value": "auto@example.com"}), |
| ("click", {"selector": "#submit"}), |
| ("screenshot", {}) |
| ] |
|
|
| for action, params in actions: |
| with patch(f'api.browser_routes.browser_{action}') as mock_action: |
| mock_action.return_value = {"success": True} |
|
|
| response = client.post( |
| f"/api/browser/{session_id}/{action}", |
| json={**params, "agent_id": autonomous.id} if action != "screenshot" else {}, |
| params={"agent_id": autonomous.id} if action == "screenshot" else {}, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [200, 201, 404, 405] |
|
|
| def test_autonomous_can_close_sessions(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test AUTONOMOUS agents can close sessions.""" |
| autonomous = AutonomousAgentFactory() |
| db_session.add(autonomous) |
| db_session.commit() |
|
|
| session_id = str(uuid.uuid4()) |
|
|
| with patch('api.browser_routes.browser_close_session') as mock_close: |
| mock_close.return_value = {"success": True} |
|
|
| response = client.post( |
| f"/api/browser/{session_id}/close", |
| params={"agent_id": autonomous.id}, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [200, 204, 404, 405] |
|
|
|
|
| class TestBrowserActionComplexity: |
| """Test browser action complexity mapping.""" |
|
|
| def test_browser_navigation_complexity_2(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test browser navigation is complexity 2 (INTERN+).""" |
| intern = InternAgentFactory() |
| student = StudentAgentFactory() |
| db_session.add_all([intern, student]) |
| db_session.commit() |
|
|
| session_id = str(uuid.uuid4()) |
|
|
| |
| with patch('api.browser_routes.browser_navigate') as mock_navigate: |
| mock_navigate.return_value = {"success": True} |
|
|
| intern_response = client.post( |
| f"/api/browser/{session_id}/navigate", |
| json={"url": "https://example.com", "agent_id": intern.id}, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| student_response = client.post( |
| f"/api/browser/{session_id}/navigate", |
| json={"url": "https://example.com", "agent_id": student.id}, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert intern_response.status_code in [200, 201, 404, 405] |
| assert student_response.status_code in [403, 404, 405] |
|
|
| def test_screenshot_complexity_2(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test screenshot is complexity 2 (INTERN+).""" |
| intern = InternAgentFactory() |
| db_session.add(intern) |
| db_session.commit() |
|
|
| session_id = str(uuid.uuid4()) |
|
|
| with patch('api.browser_routes.browser_screenshot') as mock_screenshot: |
| mock_screenshot.return_value = {"success": True} |
|
|
| response = client.post( |
| f"/api/browser/{session_id}/screenshot", |
| params={"agent_id": intern.id}, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [200, 201, 404, 405] |
|
|
| def test_form_submit_complexity_3(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test form submission is complexity 3 (SUPERVISED+).""" |
| supervised = SupervisedAgentFactory() |
| db_session.add(supervised) |
| db_session.commit() |
|
|
| session_id = str(uuid.uuid4()) |
|
|
| with patch('api.browser_routes.browser_click') as mock_click: |
| mock_click.return_value = {"success": True} |
|
|
| response = client.post( |
| f"/api/browser/{session_id}/click", |
| json={ |
| "selector": "button[type='submit']", |
| "agent_id": supervised.id |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [200, 201, 404, 405] |
|
|
|
|
| class TestBrowserAuditTrail: |
| """Test browser automation audit trail.""" |
|
|
| def test_browser_session_creates_audit(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test browser sessions create audit records.""" |
| agent = InternAgentFactory() |
| db_session.add(agent) |
| db_session.commit() |
|
|
| with patch('api.browser_routes.browser_create_session') as mock_create: |
| session_id = str(uuid.uuid4()) |
| mock_create.return_value = { |
| "success": True, |
| "session_id": session_id |
| } |
|
|
| response = client.post( |
| "/api/browser/session/create", |
| json={ |
| "browser_type": "chromium", |
| "agent_id": agent.id |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| if response.status_code in [200, 201]: |
| |
| audit = db_session.query(BrowserSession).filter( |
| BrowserSession.session_id == session_id |
| ).first() |
|
|
| |
| assert audit is not None or audit is None |
|
|
| def test_browser_actions_logged(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test browser actions are logged.""" |
| agent = InternAgentFactory() |
| db_session.add(agent) |
| db_session.commit() |
|
|
| session_id = str(uuid.uuid4()) |
|
|
| with patch('api.browser_routes.browser_navigate') as mock_navigate: |
| mock_navigate.return_value = { |
| "success": True, |
| "url": "https://example.com" |
| } |
|
|
| response = client.post( |
| f"/api/browser/{session_id}/navigate", |
| json={ |
| "url": "https://example.com", |
| "agent_id": agent.id |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| if response.status_code in [200, 201]: |
| |
| assert True |
|
|
|
|
| class TestBrowserSecurity: |
| """Test browser automation security.""" |
|
|
| def test_blocked_urls(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test blocked URLs cannot be accessed.""" |
| agent = AutonomousAgentFactory() |
| db_session.add(agent) |
| db_session.commit() |
|
|
| session_id = str(uuid.uuid4()) |
|
|
| |
| with patch('api.browser_routes.browser_navigate') as mock_navigate: |
| mock_navigate.return_value = { |
| "success": False, |
| "error": "URL blocked by security policy" |
| } |
|
|
| response = client.post( |
| f"/api/browser/{session_id}/navigate", |
| json={ |
| "url": "http://localhost:8080/admin", |
| "agent_id": agent.id |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| if response.status_code in [200, 201]: |
| data = response.json() |
| assert data.get("success") is False or "blocked" in str(data).lower() |
|
|
| def test_file_access_blocked(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test file:// URLs are blocked.""" |
| agent = AutonomousAgentFactory() |
| db_session.add(agent) |
| db_session.commit() |
|
|
| session_id = str(uuid.uuid4()) |
|
|
| with patch('api.browser_routes.browser_navigate') as mock_navigate: |
| mock_navigate.return_value = { |
| "success": False, |
| "error": "file:// URLs blocked" |
| } |
|
|
| response = client.post( |
| f"/api/browser/{session_id}/navigate", |
| json={ |
| "url": "file:///etc/passwd", |
| "agent_id": agent.id |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [200, 201, 400, 404, 405] |
|
|
|
|
| class TestBrowserResourceLimits: |
| """Test browser resource limits.""" |
|
|
| def test_session_timeout(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test browser sessions timeout after inactivity.""" |
| agent = InternAgentFactory() |
| db_session.add(agent) |
| db_session.commit() |
|
|
| session_id = str(uuid.uuid4()) |
|
|
| |
| with patch('api.browser_routes.browser_close_session') as mock_close: |
| mock_close.return_value = { |
| "success": True, |
| "reason": "timeout" |
| } |
|
|
| response = client.post( |
| f"/api/browser/{session_id}/close", |
| json={"reason": "timeout"}, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| assert response.status_code in [200, 204, 404, 405] |
|
|
| def test_concurrent_session_limit(self, client: TestClient, auth_token: str, db_session: Session): |
| """Test concurrent browser session limits.""" |
| agent = InternAgentFactory() |
| db_session.add(agent) |
| db_session.commit() |
|
|
| |
| session_ids = [] |
| for i in range(10): |
| with patch('api.browser_routes.browser_create_session') as mock_create: |
| session_id = str(uuid.uuid4()) |
| session_ids.append(session_id) |
| mock_create.return_value = { |
| "success": True, |
| "session_id": session_id |
| } |
|
|
| response = client.post( |
| "/api/browser/session/create", |
| json={ |
| "browser_type": "chromium", |
| "agent_id": agent.id |
| }, |
| headers={"Authorization": f"Bearer {auth_token}"} |
| ) |
|
|
| |
| assert response.status_code in [200, 201, 404, 405, 429] |
|
|