2025-11-20 13:26:43 +01:00
|
|
|
# project-setup Specification
|
2025-11-20 11:29:06 +01:00
|
|
|
|
2025-11-20 13:26:43 +01:00
|
|
|
## Purpose
|
|
|
|
|
TBD - created by archiving change add-graphql-member-api. Update Purpose after archive.
|
|
|
|
|
## Requirements
|
2025-11-20 11:29:06 +01:00
|
|
|
### 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
|
|
|
|
|
|
2025-11-21 20:32:09 +01:00
|
|
|
### 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
|
|
|
|
|
|