| """Simple test configuration for Archon - Essential tests only.""" |
|
|
| import os |
| import uuid |
| from unittest.mock import MagicMock, patch |
|
|
| import httpx |
| import pytest |
| import respx |
| from fastapi.testclient import TestClient |
|
|
| |
| os.environ["TEST_MODE"] = "true" |
| os.environ["TESTING"] = "true" |
| |
| os.environ["SUPABASE_URL"] = "https://test.supabase.co" |
| os.environ["SUPABASE_SERVICE_KEY"] = "test-key" |
| |
| os.environ["ARCHON_SERVER_PORT"] = "8181" |
| os.environ["ARCHON_MCP_PORT"] = "8051" |
| os.environ["ARCHON_AGENTS_PORT"] = "8052" |
|
|
| |
| from unittest.mock import AsyncMock |
|
|
| |
| |
| mock_genai_response = MagicMock() |
| mock_genai_response.text = "Mocked GenAI Response in Traditional Chinese (繁體中文)" |
|
|
| |
| mock_genai_client_instance = MagicMock() |
| mock_genai_client_instance.models.generate_content.return_value = mock_genai_response |
| mock_genai_client_instance.aio.models.generate_content = AsyncMock(return_value=mock_genai_response) |
|
|
| |
| mock_openai_response = MagicMock() |
| mock_openai_choice = MagicMock() |
| mock_openai_message = MagicMock() |
| mock_openai_message.content = "Mocked OpenAI Response in Traditional Chinese (繁體中文)" |
| mock_openai_choice.message = mock_openai_message |
| mock_openai_response.choices = [mock_openai_choice] |
|
|
| |
| mock_embeddings_response = MagicMock() |
| mock_embedding_data = MagicMock() |
| mock_embedding_data.embedding = [0.1] * 384 |
| mock_embeddings_response.data = [mock_embedding_data] |
|
|
| |
| mock_openai_client_instance = MagicMock() |
| mock_openai_client_instance.chat.completions.create = AsyncMock(return_value=mock_openai_response) |
| mock_openai_client_instance.embeddings.create = AsyncMock(return_value=mock_embeddings_response) |
|
|
| mock_openai_sync_client_instance = MagicMock() |
| mock_openai_sync_client_instance.chat.completions.create.return_value = mock_openai_response |
| mock_openai_sync_client_instance.embeddings.create.return_value = mock_embeddings_response |
|
|
| |
| mock_litellm_response = { |
| "choices": [ |
| { |
| "message": { |
| "content": "Mocked LiteLLM Summary Response" |
| } |
| } |
| ] |
| } |
|
|
| |
| mock_run_result = MagicMock() |
| mock_run_result.data = "Mocked Agent Response" |
| mock_run_result.output = "Mocked Agent Response" |
| mock_run_result.usage = MagicMock() |
| mock_run_result.usage.input_tokens = 10 |
| mock_run_result.usage.output_tokens = 10 |
| mock_run_result.model_used = "mock-model" |
|
|
| |
|
|
| |
| def check_internet_connection(): |
| import socket |
| try: |
| socket.setdefaulttimeout(1) |
| socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect(("8.8.8.8", 53)) |
| return True |
| except OSError: |
| return False |
|
|
| def pytest_configure(config): |
| config.addinivalue_line("markers", "integration: mark test as integration test") |
|
|
| def pytest_runtest_setup(item): |
| if "integration" in item.keywords: |
| api_key = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY") |
| if not api_key or not check_internet_connection(): |
| pytest.skip("Skipping integration test: No internet or API keys found.") |
| |
|
|
| mock_client = MagicMock() |
| mock_table = MagicMock() |
| mock_select = MagicMock() |
| mock_execute = MagicMock() |
| mock_execute.data = [] |
| mock_select.execute.return_value = mock_execute |
| mock_select.eq.return_value = mock_select |
| mock_select.order.return_value = mock_select |
| mock_table.select.return_value = mock_select |
| mock_client.table.return_value = mock_table |
|
|
| import sys |
| from pathlib import Path |
|
|
| |
| |
| src_path = str(Path(__file__).parent.parent / "src") |
| if src_path not in sys.path: |
| sys.path.insert(0, src_path) |
| |
|
|
| |
| _global_patches = [ |
| patch("supabase.create_client", return_value=mock_client), |
| patch("server.services.client_manager.get_supabase_client", return_value=mock_client), |
| patch("server.utils.get_supabase_client", return_value=mock_client), |
| patch("google.genai.Client", return_value=mock_genai_client_instance), |
| patch("openai.OpenAI", return_value=mock_openai_sync_client_instance), |
| patch("openai.AsyncOpenAI", return_value=mock_openai_client_instance), |
| patch("litellm.completion", AsyncMock(return_value=mock_litellm_response)), |
| patch("pydantic_ai.Agent.run", AsyncMock(return_value=mock_run_result)), |
| patch("pydantic_ai.Agent.run_sync", MagicMock(return_value=mock_run_result)), |
| ] |
|
|
| print("\n--- DEBUG CONTEST.PY IMPORT ENVIRONMENT ---") |
| print(f"sys.path: {sys.path}") |
| print(f"sys.modules keys (first 20): {list(sys.modules.keys())[:20]}") |
| print(f"sys.modules['server']: {sys.modules.get('server')}") |
| print(f"sys.modules['server.services']: {sys.modules.get('server.services')}") |
| print("--- END DEBUG ---") |
|
|
| for p in _global_patches: |
| p.start() |
|
|
|
|
| |
|
|
|
|
|
|
| class MockQueryBuilder: |
| def __init__(self, data: list, table_name: str, client: 'StatefulMockSupabaseClient', action: str, action_data: any = None): |
| self.data = data |
| self.table_name = table_name |
| self.client = client |
| self.action = action |
| self.action_data = action_data |
| self.filters = [] |
| self.orders = [] |
| self.limit_val = None |
|
|
| def eq(self, field: str, value: any): |
| self.filters.append(lambda x: x.get(field) == value) |
| return self |
|
|
| def neq(self, field: str, value: any): |
| self.filters.append(lambda x: x.get(field) != value) |
| return self |
|
|
| def ilike(self, field: str, pattern: str): |
| clean_pat = pattern.replace("%", "").lower() |
| self.filters.append(lambda x: clean_pat in str(x.get(field, "")).lower()) |
| return self |
|
|
| def contains(self, field: str, value: any): |
| def _check_contains(x): |
| val = x.get(field) |
| if isinstance(val, list): |
| if isinstance(value, list): |
| return all(item in val for item in value) |
| return value in val |
| return False |
| self.filters.append(_check_contains) |
| return self |
|
|
| def or_(self, filter_str: str): |
| |
| return self |
|
|
| def order(self, field: str, desc: bool = False): |
| self.orders.append((field, desc)) |
| return self |
|
|
| def limit(self, limit_val: int): |
| self.limit_val = limit_val |
| return self |
|
|
| def execute(self): |
| target_records = self.data |
|
|
| filtered_records = [] |
| for r in target_records: |
| match = True |
| for f in self.filters: |
| try: |
| if not f(r): |
| match = False |
| break |
| except Exception: |
| match = False |
| break |
| if match: |
| filtered_records.append(r) |
|
|
| for field, desc in self.orders: |
| try: |
| filtered_records.sort(key=lambda x: x.get(field), reverse=desc) |
| except Exception: |
| pass |
|
|
| if self.limit_val is not None: |
| filtered_records = filtered_records[:self.limit_val] |
|
|
| response = MagicMock() |
|
|
| if self.action == 'select': |
| response.data = [dict(r) for r in filtered_records] |
|
|
| elif self.action == 'insert': |
| inserted = [] |
| if isinstance(self.action_data, list): |
| for item in self.action_data: |
| new_item = dict(item) |
| if "id" not in new_item: |
| new_item["id"] = str(uuid.uuid4()) |
| self.data.append(new_item) |
| inserted.append(new_item) |
| else: |
| new_item = dict(self.action_data) |
| if "id" not in new_item: |
| new_item["id"] = str(uuid.uuid4()) |
| self.data.append(new_item) |
| inserted.append(new_item) |
| response.data = inserted |
|
|
| elif self.action == 'update': |
| updated = [] |
| for r in filtered_records: |
| r.update(self.action_data) |
| updated.append(dict(r)) |
| response.data = updated |
|
|
| elif self.action == 'delete': |
| for r in filtered_records: |
| if r in self.data: |
| self.data.remove(r) |
| response.data = [dict(r) for r in filtered_records] |
|
|
| return response |
|
|
|
|
| class MockTable: |
| def __init__(self, data: list, table_name: str, client: 'StatefulMockSupabaseClient'): |
| self.data = data |
| self.table_name = table_name |
| self.client = client |
|
|
| def select(self, fields: str = "*"): |
| return MockQueryBuilder(self.data, self.table_name, self.client, 'select') |
|
|
| def insert(self, data: dict | list): |
| return MockQueryBuilder(self.data, self.table_name, self.client, 'insert', data) |
|
|
| def update(self, data: dict): |
| return MockQueryBuilder(self.data, self.table_name, self.client, 'update', data) |
|
|
| def delete(self): |
| return MockQueryBuilder(self.data, self.table_name, self.client, 'delete') |
|
|
|
|
| class StatefulMockSupabaseClient: |
| def __init__(self): |
| self._db = {} |
| self.auth = MagicMock() |
| self.auth.get_user.return_value = None |
| self.storage = MagicMock() |
|
|
| def table(self, table_name: str): |
| if table_name not in self._db: |
| self._db[table_name] = [] |
| return MockTable(self._db[table_name], table_name, self) |
|
|
| |
|
|
|
|
| @pytest.fixture(autouse=True) |
| def ensure_test_environment(): |
| """Ensure test environment is properly set for each test.""" |
| |
| os.environ["TEST_MODE"] = "true" |
| os.environ["TESTING"] = "true" |
| os.environ["SUPABASE_URL"] = "https://test.supabase.co" |
| os.environ["SUPABASE_SERVICE_KEY"] = "test-key" |
| os.environ["ARCHON_SERVER_PORT"] = "8181" |
| os.environ["ARCHON_MCP_PORT"] = "8051" |
| os.environ["ARCHON_AGENTS_PORT"] = "8052" |
| yield |
|
|
|
|
| @pytest.fixture(scope="function", autouse=True) |
| def mock_supabase_db(): |
| """Shared stateful Supabase database emulator that intercepts network requests.""" |
| client = StatefulMockSupabaseClient() |
| with respx.mock(assert_all_called=False) as respx_mock: |
| |
| respx_mock.route(host__regex=r".*googleapis\.com").pass_through() |
|
|
| respx_mock.route(host__regex=r".*supabase\.co").mock( |
| side_effect=httpx.ConnectError("Blocked connection to Supabase during hermetic tests.") |
| ) |
| yield client |
|
|
|
|
| @pytest.fixture(autouse=True) |
| def prevent_real_db_calls(mock_supabase_db): |
| """Automatically prevent any real database calls in all tests.""" |
| with patch("supabase.create_client", return_value=mock_supabase_db): |
| with patch("server.services.client_manager.get_supabase_client", return_value=mock_supabase_db): |
| with patch("server.utils.get_supabase_client", return_value=mock_supabase_db): |
| yield |
|
|
|
|
| @pytest.fixture |
| def mock_supabase_client(mock_supabase_db): |
| """Mock Supabase client for testing.""" |
| return mock_supabase_db |
|
|
|
|
| @pytest.fixture |
| def client(mock_supabase_client): |
| """FastAPI test client with mocked database.""" |
| |
| with patch( |
| "server.services.client_manager.get_supabase_client", |
| return_value=mock_supabase_client, |
| ): |
| with patch( |
| "server.utils.get_supabase_client", |
| return_value=mock_supabase_client, |
| ): |
| with patch( |
| "src.server.utils.get_supabase_client", |
| return_value=mock_supabase_client, |
| ): |
| with patch( |
| "server.services.credential_service.create_client", |
| return_value=mock_supabase_client, |
| create=True, |
| ): |
| with patch("supabase.create_client", return_value=mock_supabase_client): |
| from unittest.mock import AsyncMock |
|
|
| import server.main as server_main |
| from server.auth.dependencies import get_current_user |
|
|
| |
| server_main._initialization_complete = True |
| app = server_main.app |
|
|
| |
| mock_schema_check = AsyncMock(return_value={"valid": True, "message": "Schema is up to date"}) |
|
|
| |
| app.dependency_overrides[get_current_user] = lambda: { |
| "id": "test-admin", |
| "role": "admin", |
| "email": "admin@test.com", |
| } |
|
|
| with patch("server.main._check_database_schema", new=mock_schema_check, create=True): |
| return TestClient(app) |
|
|
|
|
| @pytest.fixture |
| def test_project(): |
| """Simple test project data.""" |
| return {"title": "Test Project", "description": "A test project for essential tests"} |
|
|
|
|
| @pytest.fixture |
| def test_task(): |
| """Simple test task data.""" |
| return { |
| "title": "Test Task", |
| "description": "A test task for essential tests", |
| "status": "todo", |
| "assignee": "User", |
| } |
|
|
|
|
| @pytest.fixture |
| def test_knowledge_item(): |
| """Simple test knowledge item data.""" |
| return { |
| "url": "https://example.com/test", |
| "title": "Test Knowledge Item", |
| "content": "This is test content for knowledge base", |
| "source_id": "test-source", |
| } |
|
|