"""E2E test fixtures and configuration for SSO MCP Server.
This module provides fixtures for end-to-end testing of the MCP server,
including mocked authentication, test data directories, and service instances.
"""
from __future__ import annotations
import os
import shutil
import tempfile
from pathlib import Path
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
import pytest
if TYPE_CHECKING:
from collections.abc import Generator
# Path to E2E test fixtures
E2E_FIXTURES_DIR = Path(__file__).parent / "fixtures"
@pytest.fixture
def e2e_temp_dir() -> Generator[Path, None, None]:
"""Create a temporary directory for E2E test files.
Yields:
Path to the temporary directory.
"""
with tempfile.TemporaryDirectory(prefix="e2e_test_") as tmpdir:
yield Path(tmpdir)
@pytest.fixture
def e2e_checklist_dir(e2e_temp_dir: Path) -> Path:
"""Create a temporary checklist directory with E2E test fixtures.
Copies checklist fixtures from tests/e2e/fixtures/checklists/ to a temp dir.
Args:
e2e_temp_dir: Temporary directory fixture.
Returns:
Path to directory containing test checklists.
"""
checklist_dir = e2e_temp_dir / "checklists"
source_dir = E2E_FIXTURES_DIR / "checklists"
if source_dir.exists():
shutil.copytree(source_dir, checklist_dir)
else:
# Fallback: create minimal test data
checklist_dir.mkdir(parents=True)
coding_content = """---
name: Coding Standards
description: Quality checklist for code implementation
---
# Coding Standards
- [ ] Variables use descriptive names
"""
(checklist_dir / "coding.md").write_text(coding_content)
return checklist_dir
@pytest.fixture
def e2e_process_dir(e2e_temp_dir: Path) -> Path:
"""Create a temporary process directory with E2E test fixtures.
Copies process fixtures from tests/e2e/fixtures/processes/ to a temp dir.
Args:
e2e_temp_dir: Temporary directory fixture.
Returns:
Path to directory containing test processes.
"""
process_dir = e2e_temp_dir / "processes"
source_dir = E2E_FIXTURES_DIR / "processes"
if source_dir.exists():
shutil.copytree(source_dir, process_dir)
else:
# Fallback: create minimal test data
process_dir.mkdir(parents=True)
process_content = """---
name: Code Review Process
description: Standard procedure for reviewing code changes
---
# Code Review Process
- [ ] PR has description
"""
(process_dir / "code-review.md").write_text(process_content)
return process_dir
@pytest.fixture
def e2e_token_cache_path(e2e_temp_dir: Path) -> Path:
"""Create a temporary token cache path for E2E tests.
Args:
e2e_temp_dir: Temporary directory fixture.
Returns:
Path to token cache file (may not exist initially).
"""
cache_dir = e2e_temp_dir / ".sso-mcp-server"
cache_dir.mkdir(parents=True, exist_ok=True)
return cache_dir / "token_cache.bin"
@pytest.fixture
def mock_msal_app() -> Generator[MagicMock, None, None]:
"""Mock MSAL PublicClientApplication for E2E tests.
This fixture mocks the Azure authentication flow to avoid
requiring real Azure credentials during testing.
Yields:
MagicMock instance representing the MSAL application.
"""
with patch("msal.PublicClientApplication") as mock_app_class:
mock_app = MagicMock()
# Configure mock behavior
mock_app.get_accounts.return_value = []
mock_app.acquire_token_interactive.return_value = {
"access_token": "mock-e2e-access-token",
"refresh_token": "mock-e2e-refresh-token",
"expires_in": 3600,
"token_type": "Bearer",
"id_token_claims": {
"preferred_username": "e2e-test@example.com",
"name": "E2E Test User",
},
}
mock_app.acquire_token_silent.return_value = None
mock_app_class.return_value = mock_app
yield mock_app
@pytest.fixture
def mock_browser() -> Generator[MagicMock, None, None]:
"""Mock browser opening for E2E tests.
Prevents actual browser windows from opening during tests.
Yields:
MagicMock instance representing webbrowser.open.
"""
with patch("webbrowser.open") as mock_open:
mock_open.return_value = True
yield mock_open
@pytest.fixture
def e2e_env_vars(
e2e_checklist_dir: Path,
e2e_process_dir: Path,
e2e_token_cache_path: Path,
) -> Generator[dict[str, str], None, None]:
"""Set up environment variables for E2E tests.
Args:
e2e_checklist_dir: Checklist directory fixture.
e2e_process_dir: Process directory fixture.
e2e_token_cache_path: Token cache path fixture.
Yields:
Dictionary of environment variables set for the test.
"""
env_vars = {
"AZURE_CLIENT_ID": "e2e-test-client-id-12345678",
"AZURE_TENANT_ID": "e2e-test-tenant-id-87654321",
"CHECKLIST_DIR": str(e2e_checklist_dir),
"PROCESS_DIR": str(e2e_process_dir),
"TOKEN_CACHE_PATH": str(e2e_token_cache_path),
"MCP_PORT": "18080", # Use different port for E2E tests
"LOG_LEVEL": "DEBUG",
"AUTH_MODE": "local",
}
with patch.dict(os.environ, env_vars, clear=False):
yield env_vars
@pytest.fixture
def authenticated_env(
e2e_env_vars: dict[str, str],
mock_msal_app: MagicMock,
mock_browser: MagicMock,
) -> Generator[dict[str, str], None, None]:
"""Set up a fully authenticated environment for E2E tests.
This combines environment variables with mocked authentication.
Args:
e2e_env_vars: Environment variables fixture.
mock_msal_app: Mocked MSAL application.
mock_browser: Mocked browser.
Yields:
Dictionary of environment variables.
"""
yield e2e_env_vars
# Pytest markers for E2E tests
def pytest_configure(config):
"""Configure pytest markers for E2E tests."""
config.addinivalue_line("markers", "e2e: End-to-end integration tests")
config.addinivalue_line("markers", "smoke: Quick critical path tests (P0)")
config.addinivalue_line("markers", "regression: Full regression suite (P0+P1)")
config.addinivalue_line("markers", "checklist: Checklist-related tests")
config.addinivalue_line("markers", "process: Process-related tests")
config.addinivalue_line("markers", "search: Search functionality tests")
config.addinivalue_line("markers", "auth: Authentication tests")