TradingAgents/tests/test_conftest_hierarchy.py

797 lines
28 KiB
Python

"""
Test suite for pytest conftest.py hierarchy and shared fixtures.
This module tests:
1. Root conftest.py fixtures are accessible from all test directories
2. Unit-specific fixtures are only available in tests/unit/
3. Integration-specific fixtures are only available in tests/integration/
4. Pytest markers are properly registered (no warnings)
5. Environment variable mocking properly clears state
6. Fixture scopes (function, session, module) work correctly
7. ChromaDB and LangChain mocking fixtures work properly
8. Fixture cleanup occurs correctly
Test Coverage:
- Unit tests for fixture accessibility
- Integration tests for fixture hierarchy
- Edge cases (missing env vars, cleanup failures)
- Fixture scope validation
- Marker registration validation
This is a TDD RED phase test - it will fail until conftest.py files are implemented.
"""
import os
import pytest
import sys
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock
from typing import Any, Dict
# ============================================================================
# Test Fixtures
# ============================================================================
@pytest.fixture
def clean_env():
"""Clean environment for testing environment fixtures."""
original_env = os.environ.copy()
yield
# Restore original environment
os.environ.clear()
os.environ.update(original_env)
@pytest.fixture
def pytest_config_dir(tmp_path):
"""Create a temporary pytest configuration directory."""
tests_dir = tmp_path / "tests"
tests_dir.mkdir()
unit_dir = tests_dir / "unit"
unit_dir.mkdir()
integration_dir = tests_dir / "integration"
integration_dir.mkdir()
return tests_dir
# ============================================================================
# Test Root Conftest Fixtures - Should be accessible from all test dirs
# ============================================================================
class TestRootConftestFixtures:
"""Test that root conftest.py fixtures are accessible everywhere."""
def test_mock_env_openrouter_fixture_exists(self):
"""Test that mock_env_openrouter fixture can be imported."""
# This will fail until conftest.py is created
with pytest.raises(NameError):
# Try to access the fixture (will fail in RED phase)
mock_env_openrouter
def test_mock_env_openai_fixture_exists(self):
"""Test that mock_env_openai fixture can be imported."""
with pytest.raises(NameError):
mock_env_openai
def test_mock_env_anthropic_fixture_exists(self):
"""Test that mock_env_anthropic fixture can be imported."""
with pytest.raises(NameError):
mock_env_anthropic
def test_mock_env_google_fixture_exists(self):
"""Test that mock_env_google fixture can be imported."""
with pytest.raises(NameError):
mock_env_google
def test_mock_env_empty_fixture_exists(self):
"""Test that mock_env_empty fixture can be imported."""
with pytest.raises(NameError):
mock_env_empty
def test_mock_langchain_classes_fixture_exists(self):
"""Test that mock_langchain_classes fixture can be imported."""
with pytest.raises(NameError):
mock_langchain_classes
def test_mock_chromadb_fixture_exists(self):
"""Test that mock_chromadb fixture can be imported."""
with pytest.raises(NameError):
mock_chromadb
def test_mock_memory_fixture_exists(self):
"""Test that mock_memory fixture can be imported."""
with pytest.raises(NameError):
mock_memory
def test_mock_openai_client_fixture_exists(self):
"""Test that mock_openai_client fixture can be imported."""
with pytest.raises(NameError):
mock_openai_client
def test_temp_output_dir_fixture_exists(self):
"""Test that temp_output_dir fixture can be imported."""
with pytest.raises(NameError):
temp_output_dir
def test_sample_config_fixture_exists(self):
"""Test that sample_config fixture can be imported."""
with pytest.raises(NameError):
sample_config
def test_openrouter_config_fixture_exists(self):
"""Test that openrouter_config fixture can be imported."""
with pytest.raises(NameError):
openrouter_config
# ============================================================================
# Test Environment Mocking Fixtures
# ============================================================================
class TestEnvironmentMockingFixtures:
"""Test that environment mocking fixtures work correctly."""
def test_mock_env_openrouter_sets_api_key(self, clean_env):
"""Test that mock_env_openrouter sets OPENROUTER_API_KEY."""
# Will fail until implemented
assert "OPENROUTER_API_KEY" not in os.environ
# After implementation, this should pass:
# with mock_env_openrouter:
# assert os.environ.get("OPENROUTER_API_KEY") == "sk-or-test-key-123"
def test_mock_env_openrouter_clears_other_keys(self, clean_env):
"""Test that mock_env_openrouter clears other API keys."""
os.environ["OPENAI_API_KEY"] = "should-be-cleared"
# After implementation:
# with mock_env_openrouter:
# assert "OPENAI_API_KEY" not in os.environ
assert "OPENAI_API_KEY" in os.environ # Fails until implemented
def test_mock_env_openai_sets_api_key(self, clean_env):
"""Test that mock_env_openai sets OPENAI_API_KEY."""
assert "OPENAI_API_KEY" not in os.environ
def test_mock_env_anthropic_sets_api_key(self, clean_env):
"""Test that mock_env_anthropic sets ANTHROPIC_API_KEY."""
assert "ANTHROPIC_API_KEY" not in os.environ
def test_mock_env_google_sets_api_key(self, clean_env):
"""Test that mock_env_google sets GOOGLE_API_KEY."""
assert "GOOGLE_API_KEY" not in os.environ
def test_mock_env_empty_clears_all_keys(self, clean_env):
"""Test that mock_env_empty clears all API keys."""
os.environ["OPENROUTER_API_KEY"] = "test"
os.environ["OPENAI_API_KEY"] = "test"
os.environ["ANTHROPIC_API_KEY"] = "test"
# After implementation, all should be cleared
assert len([k for k in os.environ if "API_KEY" in k]) > 0
def test_environment_fixtures_restore_state(self, clean_env):
"""Test that environment fixtures restore original state after use."""
original_key = "ORIGINAL_VALUE"
os.environ["TEST_KEY"] = original_key
# After implementation, test that state is restored:
# with mock_env_openrouter:
# assert os.environ.get("TEST_KEY") != original_key
# assert os.environ.get("TEST_KEY") == original_key
assert os.environ.get("TEST_KEY") == original_key
# ============================================================================
# Test LangChain Mocking Fixtures
# ============================================================================
class TestLangChainMockingFixtures:
"""Test that LangChain mocking fixtures work correctly."""
def test_mock_langchain_classes_provides_dict(self):
"""Test that mock_langchain_classes returns a dict with LLM mocks."""
# Will fail until implemented
with pytest.raises(NameError):
# After implementation, should return dict with keys: openai, anthropic, google
result = mock_langchain_classes
def test_mock_langchain_classes_has_openai_mock(self):
"""Test that mock_langchain_classes includes ChatOpenAI mock."""
# After implementation:
# assert "openai" in mock_langchain_classes
# assert isinstance(mock_langchain_classes["openai"], Mock)
pass
def test_mock_langchain_classes_has_anthropic_mock(self):
"""Test that mock_langchain_classes includes ChatAnthropic mock."""
pass
def test_mock_langchain_classes_has_google_mock(self):
"""Test that mock_langchain_classes includes ChatGoogleGenerativeAI mock."""
pass
def test_mock_langchain_classes_returns_mock_instances(self):
"""Test that mocked LLM classes return Mock instances when called."""
# After implementation:
# mocks = mock_langchain_classes
# instance = mocks["openai"]()
# assert isinstance(instance, Mock)
pass
# ============================================================================
# Test ChromaDB Mocking Fixtures
# ============================================================================
class TestChromaDBMockingFixtures:
"""Test that ChromaDB mocking fixtures work correctly."""
def test_mock_chromadb_patches_client(self):
"""Test that mock_chromadb patches chromadb.Client."""
# Will fail until implemented
with pytest.raises(NameError):
result = mock_chromadb
def test_mock_chromadb_returns_mock_client(self):
"""Test that mock_chromadb returns a mock client instance."""
# After implementation:
# client = mock_chromadb.return_value
# assert isinstance(client, Mock)
pass
def test_mock_chromadb_has_get_or_create_collection(self):
"""Test that mock client has get_or_create_collection method."""
# After implementation:
# client = mock_chromadb.return_value
# assert hasattr(client, "get_or_create_collection")
pass
def test_mock_chromadb_collection_has_count(self):
"""Test that mock collection has count method returning 0."""
# After implementation:
# client = mock_chromadb.return_value
# collection = client.get_or_create_collection.return_value
# assert collection.count.return_value == 0
pass
def test_mock_chromadb_supports_legacy_create_collection(self):
"""Test that mock client supports legacy create_collection for compatibility."""
# After implementation:
# client = mock_chromadb.return_value
# assert hasattr(client, "create_collection")
pass
# ============================================================================
# Test Memory Mocking Fixtures
# ============================================================================
class TestMemoryMockingFixtures:
"""Test that memory mocking fixtures work correctly."""
def test_mock_memory_patches_financial_situation_memory(self):
"""Test that mock_memory patches FinancialSituationMemory."""
with pytest.raises(NameError):
result = mock_memory
def test_mock_memory_returns_mock_instance(self):
"""Test that mock_memory returns a Mock instance."""
pass
def test_mock_openai_client_patches_openai(self):
"""Test that mock_openai_client patches OpenAI client."""
with pytest.raises(NameError):
result = mock_openai_client
def test_mock_openai_client_has_embeddings_create(self):
"""Test that mock OpenAI client has embeddings.create method."""
pass
# ============================================================================
# Test Fixture Scopes
# ============================================================================
class TestFixtureScopes:
"""Test that fixtures have correct scopes defined."""
def test_session_scoped_fixtures(self):
"""Test that session-scoped fixtures are defined correctly."""
# After implementation, check that certain fixtures are session-scoped
# This helps with performance by reusing expensive setup
pass
def test_function_scoped_fixtures(self):
"""Test that function-scoped fixtures are isolated per test."""
# After implementation, verify that function-scoped fixtures
# get fresh instances for each test
pass
def test_module_scoped_fixtures(self):
"""Test that module-scoped fixtures are shared within module."""
pass
# ============================================================================
# Test Pytest Markers
# ============================================================================
class TestPytestMarkers:
"""Test that pytest markers are properly registered."""
def test_slow_marker_registered(self):
"""Test that 'slow' marker is registered in conftest.py."""
# After implementation, pytest should not show warning about unknown marker
# This will be validated by running: pytest --markers
pass
def test_integration_marker_registered(self):
"""Test that 'integration' marker is registered."""
pass
def test_unit_marker_registered(self):
"""Test that 'unit' marker is registered."""
pass
def test_requires_api_key_marker_registered(self):
"""Test that 'requires_api_key' marker is registered."""
pass
def test_chromadb_marker_registered(self):
"""Test that 'chromadb' marker is registered."""
pass
# ============================================================================
# Test Unit-Specific Fixtures (should only be in tests/unit/conftest.py)
# ============================================================================
class TestUnitSpecificFixtures:
"""Test fixtures that should only be available in unit tests."""
def test_mock_akshare_fixture_exists(self):
"""Test that mock_akshare fixture exists for unit tests."""
# Will fail until unit/conftest.py is created
with pytest.raises(NameError):
mock_akshare
def test_mock_yfinance_fixture_exists(self):
"""Test that mock_yfinance fixture exists for unit tests."""
with pytest.raises(NameError):
mock_yfinance
def test_sample_dataframe_fixture_exists(self):
"""Test that sample_dataframe fixture exists for unit tests."""
with pytest.raises(NameError):
sample_dataframe
def test_mock_time_sleep_fixture_exists(self):
"""Test that mock_time_sleep fixture exists for unit tests."""
with pytest.raises(NameError):
mock_time_sleep
def test_mock_requests_fixture_exists(self):
"""Test that mock_requests fixture exists for unit tests."""
with pytest.raises(NameError):
mock_requests
def test_mock_subprocess_fixture_exists(self):
"""Test that mock_subprocess fixture exists for unit tests."""
with pytest.raises(NameError):
mock_subprocess
# ============================================================================
# Test Integration-Specific Fixtures (should only be in tests/integration/conftest.py)
# ============================================================================
class TestIntegrationSpecificFixtures:
"""Test fixtures that should only be available in integration tests."""
def test_live_chromadb_fixture_exists(self):
"""Test that live_chromadb fixture exists for integration tests."""
# Will fail until integration/conftest.py is created
with pytest.raises(NameError):
live_chromadb
def test_integration_temp_dir_fixture_exists(self):
"""Test that integration_temp_dir fixture exists."""
with pytest.raises(NameError):
integration_temp_dir
# ============================================================================
# Test Fixture Cleanup
# ============================================================================
class TestFixtureCleanup:
"""Test that fixtures properly clean up resources."""
def test_temp_output_dir_cleanup(self):
"""Test that temp_output_dir is cleaned up after test."""
# After implementation:
# temp_dir = temp_output_dir
# temp_path = Path(temp_dir)
# assert temp_path.exists() # Exists during test
# # After test completes, directory should be removed
pass
def test_mock_patches_are_reverted(self):
"""Test that mock patches are reverted after fixture exits."""
# Verify that patches don't leak between tests
pass
def test_chromadb_mocks_cleanup(self):
"""Test that ChromaDB mocks clean up properly."""
pass
# ============================================================================
# Test Configuration Fixtures
# ============================================================================
class TestConfigurationFixtures:
"""Test configuration-related fixtures."""
def test_sample_config_has_required_keys(self):
"""Test that sample_config fixture has all required configuration keys."""
# After implementation:
# config = sample_config
# assert "llm_provider" in config
# assert "deep_think_llm" in config
# assert "quick_think_llm" in config
# assert "data_vendors" in config
pass
def test_openrouter_config_sets_provider(self):
"""Test that openrouter_config sets llm_provider to openrouter."""
# After implementation:
# config = openrouter_config
# assert config["llm_provider"] == "openrouter"
pass
def test_openrouter_config_has_backend_url(self):
"""Test that openrouter_config includes backend_url."""
# After implementation:
# config = openrouter_config
# assert "backend_url" in config
# assert "openrouter.ai" in config["backend_url"]
pass
# ============================================================================
# Edge Case Tests
# ============================================================================
class TestEdgeCases:
"""Test edge cases and error conditions."""
def test_missing_env_var_in_mock(self):
"""Test behavior when expected environment variable is missing."""
# After implementation, test that fixtures handle missing vars gracefully
pass
def test_conflicting_env_vars(self):
"""Test behavior when multiple API key env vars are set."""
# Test priority order: OPENROUTER_API_KEY > OPENAI_API_KEY, etc.
pass
def test_fixture_with_none_value(self):
"""Test fixtures handle None values correctly."""
pass
def test_fixture_with_empty_dict(self):
"""Test fixtures handle empty dictionaries correctly."""
pass
def test_nested_fixture_dependencies(self):
"""Test that fixtures with dependencies on other fixtures work."""
# Some fixtures may depend on other fixtures
pass
# ============================================================================
# Integration Tests - Test Fixture Hierarchy
# ============================================================================
class TestFixtureHierarchy:
"""Test the conftest.py hierarchy structure."""
def test_root_conftest_exists(self):
"""Test that tests/conftest.py exists."""
conftest_path = Path(__file__).parent / "conftest.py"
# Will fail until conftest.py is created
assert conftest_path.exists(), "conftest.py should exist after implementation"
def test_unit_conftest_exists(self):
"""Test that tests/unit/conftest.py exists."""
unit_conftest = Path(__file__).parent / "unit" / "conftest.py"
assert unit_conftest.exists(), "unit/conftest.py should exist after implementation"
def test_integration_conftest_exists(self):
"""Test that tests/integration/conftest.py exists."""
integration_conftest = Path(__file__).parent / "integration" / "conftest.py"
assert integration_conftest.exists(), "integration/conftest.py should exist after implementation"
def test_root_fixtures_available_in_unit_tests(self):
"""Test that root conftest fixtures are accessible from unit tests."""
# After implementation, create a dummy unit test file and verify
# it can access root fixtures
pass
def test_root_fixtures_available_in_integration_tests(self):
"""Test that root conftest fixtures are accessible from integration tests."""
pass
def test_unit_fixtures_not_available_in_integration(self):
"""Test that unit-specific fixtures are not available in integration tests."""
# This ensures proper isolation
pass
def test_integration_fixtures_not_available_in_unit(self):
"""Test that integration-specific fixtures are not available in unit tests."""
# This ensures proper isolation
pass
# ============================================================================
# Test Fixture Documentation
# ============================================================================
class TestFixtureDocumentation:
"""Test that fixtures have proper documentation."""
def test_all_fixtures_have_docstrings(self):
"""Test that all fixtures in conftest.py have docstrings."""
# After implementation, verify all fixtures are documented
pass
def test_fixture_docstrings_describe_purpose(self):
"""Test that fixture docstrings describe their purpose."""
pass
def test_fixture_docstrings_describe_scope(self):
"""Test that fixture docstrings mention their scope if not 'function'."""
pass
# ============================================================================
# Performance Tests
# ============================================================================
class TestFixturePerformance:
"""Test fixture performance characteristics."""
def test_session_fixtures_only_created_once(self):
"""Test that session-scoped fixtures are only created once per session."""
# After implementation, verify session fixtures aren't recreated
pass
def test_expensive_mocks_are_cached(self):
"""Test that expensive mock setups are cached appropriately."""
pass
# ============================================================================
# Test Marker Usage
# ============================================================================
@pytest.mark.slow
class TestSlowMarker:
"""Test the @pytest.mark.slow marker works."""
def test_slow_marker_can_be_applied(self):
"""Test that slow marker can be applied to tests."""
# This test itself uses the marker
# Run with: pytest -m slow
pass
@pytest.mark.unit
class TestUnitMarker:
"""Test the @pytest.mark.unit marker works."""
def test_unit_marker_can_be_applied(self):
"""Test that unit marker can be applied to tests."""
# Run with: pytest -m unit
pass
@pytest.mark.integration
class TestIntegrationMarker:
"""Test the @pytest.mark.integration marker works."""
def test_integration_marker_can_be_applied(self):
"""Test that integration marker can be applied to tests."""
# Run with: pytest -m integration
pass
@pytest.mark.requires_api_key
class TestRequiresApiKeyMarker:
"""Test the @pytest.mark.requires_api_key marker works."""
def test_requires_api_key_marker_can_be_applied(self):
"""Test that requires_api_key marker can be applied to tests."""
# Run with: pytest -m "not requires_api_key" to skip
pass
@pytest.mark.chromadb
class TestChromaDBMarker:
"""Test the @pytest.mark.chromadb marker works."""
def test_chromadb_marker_can_be_applied(self):
"""Test that chromadb marker can be applied to tests."""
# Run with: pytest -m chromadb
pass
# ============================================================================
# Test Pytest.ini Configuration
# ============================================================================
class TestPytestIniConfiguration:
"""Test pytest.ini configuration for markers."""
def test_pytest_ini_exists(self):
"""Test that pytest.ini exists in project root."""
pytest_ini = Path(__file__).parent.parent / "pytest.ini"
# Will fail until pytest.ini is created
assert pytest_ini.exists(), "pytest.ini should exist after implementation"
def test_markers_registered_in_pytest_ini(self):
"""Test that all markers are registered in pytest.ini."""
# After implementation, verify markers section exists
# and includes: slow, unit, integration, requires_api_key, chromadb
pass
# ============================================================================
# Final Summary Test
# ============================================================================
class TestConftestHierarchySummary:
"""Summary test to verify complete conftest hierarchy."""
def test_all_12_root_fixtures_accessible(self):
"""Test that all 12 root fixtures from Phase 1 are accessible."""
# Expected root fixtures:
# 1. mock_env_openrouter
# 2. mock_env_openai
# 3. mock_env_anthropic
# 4. mock_env_google
# 5. mock_env_empty
# 6. mock_langchain_classes
# 7. mock_chromadb
# 8. mock_memory
# 9. mock_openai_client
# 10. temp_output_dir
# 11. sample_config
# 12. openrouter_config
expected_fixtures = [
"mock_env_openrouter",
"mock_env_openai",
"mock_env_anthropic",
"mock_env_google",
"mock_env_empty",
"mock_langchain_classes",
"mock_chromadb",
"mock_memory",
"mock_openai_client",
"temp_output_dir",
"sample_config",
"openrouter_config",
]
# Will fail until conftest.py is created
assert len(expected_fixtures) == 12
def test_all_6_unit_fixtures_accessible(self):
"""Test that all 6 unit-specific fixtures from Phase 2 are accessible."""
# Expected unit fixtures:
# 1. mock_akshare
# 2. mock_yfinance
# 3. sample_dataframe
# 4. mock_time_sleep
# 5. mock_requests
# 6. mock_subprocess
expected_fixtures = [
"mock_akshare",
"mock_yfinance",
"sample_dataframe",
"mock_time_sleep",
"mock_requests",
"mock_subprocess",
]
assert len(expected_fixtures) == 6
def test_all_2_integration_fixtures_accessible(self):
"""Test that all 2 integration-specific fixtures from Phase 3 are accessible."""
# Expected integration fixtures:
# 1. live_chromadb
# 2. integration_temp_dir
expected_fixtures = [
"live_chromadb",
"integration_temp_dir",
]
assert len(expected_fixtures) == 2
def test_all_5_markers_registered(self):
"""Test that all 5 pytest markers from Phase 5 are registered."""
# Expected markers:
# 1. slow
# 2. unit
# 3. integration
# 4. requires_api_key
# 5. chromadb
expected_markers = [
"slow",
"unit",
"integration",
"requires_api_key",
"chromadb",
]
assert len(expected_markers) == 5
# ============================================================================
# Expected Test Results (TDD RED Phase)
# ============================================================================
"""
EXPECTED TEST RESULTS (before implementation):
Total tests: ~100+
Expected failures: ~100+ (all should fail - this is RED phase)
Expected passes: 0 (no implementation exists yet)
Test execution command:
pytest tests/test_conftest_hierarchy.py --tb=line -q
After implementation (GREEN phase), all tests should pass.
Coverage target: 80%+ for conftest.py fixture infrastructure
Test categories:
- Root conftest fixtures: 12 tests
- Environment mocking: 8 tests
- LangChain mocking: 5 tests
- ChromaDB mocking: 5 tests
- Memory mocking: 4 tests
- Fixture scopes: 3 tests
- Pytest markers: 5 tests
- Unit-specific fixtures: 6 tests
- Integration-specific fixtures: 2 tests
- Fixture cleanup: 3 tests
- Configuration fixtures: 3 tests
- Edge cases: 5 tests
- Fixture hierarchy: 8 tests
- Fixture documentation: 3 tests
- Performance: 2 tests
- Marker usage: 5 tests (with actual markers applied)
- Pytest.ini: 2 tests
- Summary: 4 tests
Total: ~85+ individual test methods
Next steps:
1. Run this test suite - should see all tests fail (RED)
2. Implement tests/conftest.py with 12 shared fixtures
3. Implement tests/unit/conftest.py with 6 unit fixtures
4. Implement tests/integration/conftest.py with 2 integration fixtures
5. Update pytest.ini with marker registrations
6. Re-run tests - should see all tests pass (GREEN)
7. Migrate existing test files to use shared fixtures (REFACTOR)
"""