Archive the completed pytest test suite implementation change. The change has been moved to the archive and the project-setup spec has been updated with all test requirements and scenarios. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
19 KiB
project-setup Specification
Purpose
TBD - created by archiving change add-graphql-member-api. Update Purpose after archive.
Requirements
Requirement: Python project structure with uv dependency management
The project MUST use Python 3.11+ with uv for fast, reliable dependency management and provide a standard src/ layout for code organization.
Scenario: Developer initializes new development environment
Given a developer has cloned the repository
When they run uv sync
Then all dependencies are installed in a virtual environment
And the environment is ready for development within 30 seconds
Scenario: Developer runs the application
Given dependencies are installed
When developer runs uv run uvicorn src.main:app --reload
Then the FastAPI server starts on http://localhost:8000
And GraphQL playground is available at http://localhost:8000/graphql
Requirement: Project configuration in pyproject.toml
The project MUST define all metadata, dependencies, and tool configurations in pyproject.toml following modern Python packaging standards.
Dependencies required:
- fastapi >= 0.104.0 (Web framework)
- strawberry-graphql[fastapi] >= 0.215.0 (GraphQL integration)
- sqlalchemy[asyncio] >= 2.0.0 (ORM with async support)
- aiosqlite >= 0.19.0 (Async SQLite driver)
- alembic >= 1.12.0 (Database migrations)
- pydantic-settings >= 2.0.0 (Configuration management)
- uvicorn[standard] >= 0.24.0 (ASGI server)
Development dependencies required:
- pytest >= 7.4.0 (Test framework)
- pytest-asyncio >= 0.21.0 (Async test support)
- black >= 23.0.0 (Code formatting)
- ruff >= 0.1.0 (Linting)
- isort >= 5.12.0 (Import sorting)
Scenario: Dependencies are declared with version constraints
Given pyproject.toml exists When developer inspects [project.dependencies] Then all required packages are listed with minimum versions And version constraints allow patch/minor updates
Scenario: Development tools are configured
Given pyproject.toml contains tool configurations
When developer runs black .
Then code is formatted with line length 88
When developer runs ruff check .
Then code is linted against configured rules
Requirement: Source directory structure following layered architecture
The project MUST organize code into src/ directory with clear separation of concerns across API, business logic, and data layers.
Required directory structure:
src/
├── __init__.py
├── main.py # FastAPI application, startup/shutdown
├── config.py # Settings and configuration
├── database.py # Database connection, session factory
├── models/ # SQLAlchemy ORM models
│ ├── __init__.py
│ └── member.py
├── schemas/ # Strawberry GraphQL types
│ ├── __init__.py
│ └── member.py
└── resolvers/ # GraphQL query/mutation resolvers
├── __init__.py
└── member.py
Scenario: Code is organized by architectural layer
Given the src/ directory exists When a developer navigates the codebase Then models/ contains only SQLAlchemy ORM definitions And schemas/ contains only Strawberry GraphQL type definitions And resolvers/ contains only GraphQL resolver functions And each module has clear, single responsibility
Scenario: Main application entry point is defined
Given src/main.py exists
When the file is imported
Then it exports a FastAPI app instance
And app includes GraphQL route at /graphql
And app includes startup event to verify database connection
Requirement: Testing infrastructure with pytest
The project MUST provide pytest configuration for running async tests with database fixtures and code coverage reporting.
Scenario: Async tests can be executed
Given pytest and pytest-asyncio are installed
When developer runs pytest
Then all tests in tests/ directory are discovered
And async test functions execute correctly
And test results are displayed with pass/fail status
Scenario: Database fixtures are available for tests
Given tests/conftest.py defines database fixtures
When a test function requests db_session fixture
Then an isolated test database session is provided
And session is rolled back after test completion
And no test data persists between test runs
Requirement: Code quality tooling configuration
The project MUST configure black, ruff, and isort for consistent code formatting and linting with settings in pyproject.toml.
Black configuration:
- Line length: 88 characters
- Target version: Python 3.11
- Skip string normalization: false
Ruff configuration:
- Line length: 88 characters
- Select: E, F, W, I (pycodestyle, pyflakes, warnings, isort)
- Ignore: E501 (line too long, handled by black)
Isort configuration:
- Profile: black (compatible settings)
- Multi-line output: 3 (vertical hanging indent)
Scenario: Code formatting is enforced
Given black is configured in pyproject.toml
When developer runs black --check .
Then all Python files are checked for formatting
And exit code is 0 if all files are formatted correctly
And exit code is 1 if any files need formatting
Scenario: Code quality checks pass
Given ruff is configured in pyproject.toml
When developer runs ruff check .
Then all Python files are linted
And no errors are reported for compliant code
And clear error messages are shown for violations
Requirement: Environment configuration with .env support
The project MUST support environment-based configuration using .env files with pydantic-settings for type-safe config values.
Scenario: Default configuration works for development
Given no .env file exists When application starts Then it uses default SQLite database path And it runs in debug mode And application starts successfully
Scenario: Environment variables override defaults
Given .env file contains DATABASE_URL=sqlite+aiosqlite:///./test.db
When application loads configuration
Then settings.database_url equals "sqlite+aiosqlite:///./test.db"
And custom database path is used
Scenario: Configuration is type-safe
Given src/config.py defines Settings class When invalid configuration value is provided Then pydantic validation raises clear error And application fails fast on startup
Requirement: Development scripts for common tasks
The project MUST provide executable scripts for database seeding and common development tasks.
Required scripts:
- scripts/seed.py - Populate database with sample member
Scenario: Seed script creates sample data
Given database schema exists from migrations
When developer runs uv run python scripts/seed.py
Then sample member is created in database
And script is idempotent (safe to run multiple times)
And success message is displayed
Requirement: Automated Test Suite with Pytest
The project SHALL provide a comprehensive test suite using pytest to verify all features and prevent regressions.
Scenario: Run test suite successfully
- GIVEN the project is set up with dependencies installed
- WHEN the developer runs
uv run pytest - THEN all tests SHALL execute
- AND test results SHALL be displayed with pass/fail status
- AND the command SHALL exit with code 0 if all tests pass
Scenario: Run tests with verbose output
- GIVEN the project has tests implemented
- WHEN the developer runs
uv run pytest --verbose - THEN detailed test output SHALL be displayed
- AND each test name and result SHALL be shown
Scenario: Run specific test categories
- GIVEN tests are organized in subdirectories
- WHEN the developer runs
uv run pytest tests/unit - THEN only unit tests SHALL execute
- WHEN the developer runs
uv run pytest tests/integration - THEN only integration tests SHALL execute
Requirement: Test Organization Structure
Tests SHALL be organized in a clear directory structure separating different test types.
Scenario: Test directory structure exists
- GIVEN the project repository
- WHEN inspecting the directory structure
- THEN a
tests/directory SHALL exist at the project root - AND subdirectories
tests/unit/,tests/integration/,tests/e2e/, andtests/mcp/SHALL exist - AND a
tests/conftest.pyfile SHALL exist for shared fixtures
Scenario: Test files follow naming convention
- GIVEN test files in the
tests/directory - WHEN examining file names
- THEN all test files SHALL be named
test_*.py - AND test file names SHALL correspond to the modules they test (e.g.,
test_validation.pyforvalidation.py)
Requirement: Shared Test Fixtures
The test suite SHALL provide reusable fixtures for common test setup via conftest.py.
Scenario: Database fixture provides in-memory SQLite
- GIVEN tests need database access
- WHEN a test function requests the
dbfixture - THEN an in-memory SQLite database SHALL be created
- AND the database SHALL have all tables created via Alembic migrations or direct schema creation
- AND the database SHALL be cleaned up after the test completes
Scenario: Async session fixture provides database sessions
- GIVEN tests need to interact with the database
- WHEN a test function requests the
async_sessionfixture - THEN an async SQLAlchemy session SHALL be provided
- AND the session SHALL be connected to the test database
- AND the session SHALL be closed after the test completes
Scenario: GraphQL client fixture provides test client
- GIVEN tests need to make GraphQL requests
- WHEN a test function requests the
graphql_clientfixture - THEN a FastAPI test client SHALL be provided
- AND the client SHALL be configured to use the test database
- AND the client SHALL support async GraphQL queries and mutations
Requirement: Unit Tests for Validation Logic
Unit tests SHALL verify validation functions work correctly with valid and invalid inputs.
Scenario: Test firstName validation with valid input
- GIVEN a valid firstName "Alice"
- WHEN
validate_first_name("Alice")is called - THEN no exception SHALL be raised
Scenario: Test firstName validation with empty string
- GIVEN an empty firstName ""
- WHEN
validate_first_name("")is called - THEN a ValidationError SHALL be raised
- AND the error message SHALL indicate firstName cannot be empty
Scenario: Test email validation with valid email
- GIVEN a valid email "test@example.com"
- WHEN
validate_email("test@example.com")is called - THEN no exception SHALL be raised
Scenario: Test email validation with invalid email
- GIVEN an invalid email "not-an-email"
- WHEN
validate_email("not-an-email")is called - THEN a ValidationError SHALL be raised
- AND the error message SHALL indicate invalid email format
Scenario: Test email validation with None
- GIVEN email is None
- WHEN
validate_email(None)is called - THEN no exception SHALL be raised (None is allowed)
Scenario: Test phone validation with valid E.164 number
- GIVEN a valid phone "+41791234567"
- WHEN
validate_phone("+41791234567")is called - THEN no exception SHALL be raised
Scenario: Test phone validation with invalid number
- GIVEN an invalid phone "123"
- WHEN
validate_phone("123")is called - THEN a ValidationError SHALL be raised
Scenario: Test phone validation with None
- GIVEN phone is None
- WHEN
validate_phone(None)is called - THEN no exception SHALL be raised (None is allowed)
Requirement: Integration Tests for Member Model
Integration tests SHALL verify database operations with the Member model.
Scenario: Create member with minimal data
- GIVEN a test database session
- WHEN a Member is created with only firstName="Alice"
- THEN the member SHALL be persisted to the database
- AND the member SHALL have an auto-generated ID
- AND created_at and updated_at timestamps SHALL be populated
- AND all optional fields SHALL be None
Scenario: Create member with complete data
- GIVEN a test database session
- WHEN a Member is created with all fields populated
- THEN the member SHALL be persisted with all data
- AND all fields SHALL match the provided values
Scenario: Member timestamps are set correctly
- GIVEN a newly created member
- THEN created_at SHALL be set to current time
- AND updated_at SHALL be set to current time
- AND created_at SHALL equal updated_at for new members
Requirement: Integration Tests for GraphQL Queries
Integration tests SHALL verify GraphQL query resolvers return correct data.
Scenario: Query single member by ID
- GIVEN a member exists with ID 1
- WHEN the
member(id: 1)query is executed - THEN the member data SHALL be returned
- AND all requested fields SHALL match the database record
Scenario: Query member with non-existent ID
- GIVEN no member exists with ID 999
- WHEN the
member(id: 999)query is executed - THEN None SHALL be returned
- AND no error SHALL be raised
Scenario: List all members
- GIVEN multiple members exist in the database
- WHEN the
membersquery is executed - THEN an array of all members SHALL be returned
- AND members SHALL be sorted by last_name (nulls last), then first_name
Scenario: List members when database is empty
- GIVEN no members exist in the database
- WHEN the
membersquery is executed - THEN an empty array SHALL be returned
Requirement: Integration Tests for GraphQL Mutations
Integration tests SHALL verify GraphQL mutation resolvers create, update, and delete members correctly.
Scenario: Create member with minimal data
- GIVEN CreateMemberInput with only firstName="Bob"
- WHEN the
createMembermutation is executed - THEN a new member SHALL be created in the database
- AND the member SHALL have firstName="Bob"
- AND all optional fields SHALL be None
- AND the mutation SHALL return the created member with ID
Scenario: Create member with complete data
- GIVEN CreateMemberInput with all fields populated
- WHEN the
createMembermutation is executed - THEN a new member SHALL be created with all data
- AND all fields SHALL match the input
Scenario: Create member with invalid email
- GIVEN CreateMemberInput with email="not-an-email"
- WHEN the
createMembermutation is executed - THEN a ValidationError SHALL be raised
- AND no database record SHALL be created
Scenario: Update member successfully
- GIVEN a member exists with ID 1
- WHEN the
updateMembermutation is executed with id=1 and email="new@example.com" - THEN the member's email SHALL be updated to "new@example.com"
- AND the updated_at timestamp SHALL be refreshed
- AND all other fields SHALL remain unchanged
Scenario: Update member with non-existent ID
- GIVEN no member exists with ID 999
- WHEN the
updateMembermutation is executed with id=999 - THEN a MemberNotFoundError SHALL be raised
- AND the error message SHALL indicate member not found
Scenario: Delete member successfully
- GIVEN a member exists with ID 1
- WHEN the
deleteMembermutation is executed with id=1 - THEN the member SHALL be removed from the database
- AND the mutation SHALL return true
- AND subsequent queries for that member SHALL return None
Scenario: Delete member with non-existent ID
- GIVEN no member exists with ID 999
- WHEN the
deleteMembermutation is executed with id=999 - THEN a MemberNotFoundError SHALL be raised
Requirement: E2E Tests for GraphQL API
End-to-end tests SHALL verify complete GraphQL request/response flows over HTTP.
Scenario: Execute GraphQL query via HTTP POST
- GIVEN the FastAPI application with GraphQL endpoint
- WHEN a POST request is sent to /graphql with a query
- THEN the query SHALL be executed
- AND a JSON response SHALL be returned with data or errors
Scenario: GraphQL introspection query
- GIVEN the GraphQL API is running
- WHEN an introspection query for __schema is executed
- THEN the complete schema SHALL be returned
- AND all types, queries, and mutations SHALL be included
Scenario: GraphQL type introspection
- GIVEN the GraphQL API is running
- WHEN an introspection query for __type(name: "Member") is executed
- THEN the Member type definition SHALL be returned
- AND all fields with their types SHALL be included
Requirement: MCP Server Tests
Tests SHALL verify MCP server tool definitions and execution.
Scenario: MCP server initializes successfully
- GIVEN a valid CLUBBER_API_URL
- WHEN the MCP server is instantiated
- THEN the server SHALL initialize without errors
- AND the GraphQL client SHALL be configured
Scenario: List tools returns all 6 tools
- GIVEN an initialized MCP server
- WHEN
list_tools()is called - THEN 6 tools SHALL be returned
- AND tools SHALL include: list_members, get_member, create_member, update_member, get_graphql_schema, execute_graphql_query
Scenario: Execute list_members tool
- GIVEN members exist in the database
- WHEN the
list_memberstool is invoked - THEN a formatted list of all members SHALL be returned
Scenario: Execute get_member tool
- GIVEN a member exists with ID 1
- WHEN the
get_membertool is invoked with id=1 - THEN the member's data SHALL be returned as formatted text
Scenario: Execute create_member tool
- GIVEN valid member input data
- WHEN the
create_membertool is invoked - THEN a new member SHALL be created via the GraphQL API
- AND the created member data SHALL be returned
Scenario: Execute get_graphql_schema tool
- GIVEN the GraphQL API is accessible
- WHEN the
get_graphql_schematool is invoked - THEN the complete schema SHALL be retrieved via introspection
- AND formatted schema information SHALL be returned
Scenario: Execute execute_graphql_query tool with simple query
- GIVEN a valid GraphQL query string
- WHEN the
execute_graphql_querytool is invoked - THEN the query SHALL be executed against the API
- AND formatted JSON results SHALL be returned
Scenario: Execute execute_graphql_query tool with variables
- GIVEN a GraphQL query with variables
- WHEN the
execute_graphql_querytool is invoked with query and variables - THEN the query SHALL be executed with variable substitution
- AND results SHALL be returned
Requirement: Test Documentation in README
The README SHALL document how to run tests and understand test organization.
Scenario: README contains test running instructions
- GIVEN the README.md file
- WHEN reading the documentation
- THEN a "Running Tests" section SHALL exist
- AND the section SHALL document the
uv run pytestcommand - AND examples of running specific test categories SHALL be provided
Scenario: README documents test organization
- GIVEN the README.md file
- WHEN reading the documentation
- THEN a "Test Organization" section SHALL exist
- AND the section SHALL explain the tests/ directory structure
- AND the section SHALL describe unit, integration, e2e, and mcp test categories