Files
clubber/openspec/specs/project-setup/spec.md
T
gurixandClaude 48d84bdc0a chore: archive add-pytest-tests change
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>
2025-11-21 20:32:09 +01:00

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/, and tests/mcp/ SHALL exist
  • AND a tests/conftest.py file 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.py for validation.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 db fixture
  • 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_session fixture
  • 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_client fixture
  • 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 members query 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 members query 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 createMember mutation 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 createMember mutation 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 createMember mutation 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 updateMember mutation 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 updateMember mutation 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 deleteMember mutation 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 deleteMember mutation 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_members tool 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_member tool 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_member tool 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_schema tool 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_query tool 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_query tool 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 pytest command
  • 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