#!/bin/bash # Generate a pytest conftest.py with optional async/db/api fixture blocks. # # Usage: generate-conftest.sh [--async] [--db] [--api] # Input: optional flags selecting fixture blocks; writes ./tests/conftest.py # Output: the generated conftest.py file plus a one-line summary on stdout # Stderr: none (the overwrite confirmation prompt is read from the tty) # Exit: 0 ok (also --help, and when an existing file is kept at the prompt) # # Examples: # generate-conftest.sh # generate-conftest.sh --async --db # generate-conftest.sh --api set -e # --help / -h: print usage to stdout and exit 0 before any side effects (so the # prompt never fires and no file is touched). Behavior-preserving for all other # invocations — only -h/--help short-circuits here. for _arg in "$@"; do case "$_arg" in -h|--help) cat <<'EOF' Usage: generate-conftest.sh [--async] [--db] [--api] Generate ./tests/conftest.py with optional fixture blocks. If a conftest.py already exists, it prompts for confirmation (default: keep the existing file). Options: --async include async fixtures (event_loop, async_client) --db include database fixtures (db_engine, db_session) --api include API test fixtures (app, client, authenticated_client) Examples: generate-conftest.sh generate-conftest.sh --async --db generate-conftest.sh --api EOF exit 0 ;; esac done OUTPUT="tests/conftest.py" # Check if tests directory exists if [[ ! -d "tests" ]]; then echo "Creating tests directory..." mkdir -p tests fi # Check if conftest.py already exists if [[ -f "$OUTPUT" ]]; then read -p "conftest.py already exists. Overwrite? [y/N] " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 0 fi fi # Parse arguments ASYNC="" DB="" API="" while [[ $# -gt 0 ]]; do case $1 in --async) ASYNC=1 shift ;; --db) DB=1 shift ;; --api) API=1 shift ;; *) shift ;; esac done # Generate conftest.py cat > "$OUTPUT" << 'HEADER' """ Pytest configuration and fixtures. Generated by generate-conftest.sh """ import pytest HEADER # Add async imports if needed if [[ -n "$ASYNC" ]]; then cat >> "$OUTPUT" << 'ASYNC_IMPORTS' import asyncio ASYNC_IMPORTS fi # Add database imports if needed if [[ -n "$DB" ]]; then cat >> "$OUTPUT" << 'DB_IMPORTS' from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker DB_IMPORTS fi # Add API imports if needed if [[ -n "$API" ]]; then cat >> "$OUTPUT" << 'API_IMPORTS' from fastapi.testclient import TestClient # or: from flask.testing import FlaskClient API_IMPORTS fi # Add base fixtures cat >> "$OUTPUT" << 'BASE_FIXTURES' # ============================================================ # Test Configuration # ============================================================ def pytest_configure(config): """Register custom markers.""" config.addinivalue_line("markers", "slow: marks tests as slow") config.addinivalue_line("markers", "integration: marks integration tests") config.addinivalue_line("markers", "e2e: marks end-to-end tests") def pytest_collection_modifyitems(config, items): """Skip slow tests unless --slow flag is provided.""" if not config.getoption("--slow", default=False): skip_slow = pytest.mark.skip(reason="use --slow to run") for item in items: if "slow" in item.keywords: item.add_marker(skip_slow) def pytest_addoption(parser): """Add custom CLI options.""" parser.addoption( "--slow", action="store_true", default=False, help="run slow tests" ) # ============================================================ # Common Fixtures # ============================================================ @pytest.fixture def sample_data(): """Provide sample test data.""" return { "id": 1, "name": "Test", "active": True, } @pytest.fixture def temp_file(tmp_path): """Create a temporary file for testing.""" file_path = tmp_path / "test_file.txt" file_path.write_text("test content") return file_path BASE_FIXTURES # Add async fixtures if requested if [[ -n "$ASYNC" ]]; then cat >> "$OUTPUT" << 'ASYNC_FIXTURES' # ============================================================ # Async Fixtures # ============================================================ @pytest.fixture(scope="session") def event_loop(): """Create event loop for async tests.""" loop = asyncio.new_event_loop() yield loop loop.close() @pytest.fixture async def async_client(): """Async HTTP client fixture.""" import aiohttp async with aiohttp.ClientSession() as session: yield session ASYNC_FIXTURES fi # Add database fixtures if requested if [[ -n "$DB" ]]; then cat >> "$OUTPUT" << 'DB_FIXTURES' # ============================================================ # Database Fixtures # ============================================================ @pytest.fixture(scope="session") def db_engine(): """Create test database engine.""" engine = create_engine("sqlite:///:memory:") # Create tables here yield engine engine.dispose() @pytest.fixture def db_session(db_engine): """Create database session with transaction rollback.""" Session = sessionmaker(bind=db_engine) session = Session() yield session session.rollback() session.close() DB_FIXTURES fi # Add API fixtures if requested if [[ -n "$API" ]]; then cat >> "$OUTPUT" << 'API_FIXTURES' # ============================================================ # API Fixtures # ============================================================ @pytest.fixture def app(): """Create test application.""" from myapp import create_app app = create_app(testing=True) return app @pytest.fixture def client(app): """Create test client.""" return TestClient(app) @pytest.fixture def authenticated_client(client): """Create authenticated test client.""" # Add authentication logic here client.headers["Authorization"] = "Bearer test-token" return client API_FIXTURES fi echo "Generated $OUTPUT" echo "" echo "Options used:" [[ -n "$ASYNC" ]] && echo " --async: Async fixtures included" [[ -n "$DB" ]] && echo " --db: Database fixtures included" [[ -n "$API" ]] && echo " --api: API fixtures included" exit 0