gurixandClaude da9286be73 test: Reorganize organization member tests into proper pytest structure
Fix test organization by moving ad-hoc test scripts into proper pytest tests
following project conventions. Added 4 new tests to integration and e2e layers
to provide comprehensive coverage for organization members.

Changes:
- tests/integration/test_member_mutations.py:
  * Add test_create_organization_member - Tests creating organizations with companyName
  * Add test_create_organization_with_contact_person - Tests organizations with contact info
  * Import MemberType from models and schemas

- tests/integration/test_member_queries.py:
  * Add test_query_mixed_member_types - Tests querying both individual and organization members
  * Import MemberType from models

- tests/e2e/test_graphql_api.py:
  * Add test_introspect_member_type_enum - Tests GraphQL introspection for MemberType enum
  * Verifies INDIVIDUAL and ORGANIZATION enum values

Deleted improper test files:
- test_organization_members.py (root) - Ad-hoc script using httpx directly
- test_mcp_changes.sh (root) - Shell script for MCP server testing

Test results:
- All 57 tests pass (up from 53)
- Organization members now tested at all layers: unit, integration, and e2e
- Tests follow pytest conventions with async patterns and shared fixtures
- Integrated with CI/CD pipeline (proper test/ directory structure)

The proper test structure ensures:
1. Unit tests validate business logic (validation layer)
2. Integration tests verify GraphQL resolvers and database persistence
3. E2E tests confirm full HTTP request/response flows
4. All tests use shared fixtures from conftest.py

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 16:28:05 +01:00
2025-12-04 08:34:12 +01:00
2025-11-19 22:13:05 +01:00
2025-11-19 22:13:05 +01:00
2025-12-04 08:34:12 +01:00

Clubber

A toolset for managing members of non-profit societies (Vereine) with AI-powered administration.

Overview

Clubber provides a GraphQL API for member management combined with an MCP (Model Context Protocol) server that enables AI assistants to help with society administration tasks.

Current Status: Early development - OpenSpec documentation and project structure in place.

Architecture

The system consists of two main components:

  1. GraphQL API - Core backend for member data management

    • Built with FastAPI and Strawberry GraphQL
    • Database-agnostic design (SQLite for development, PostgreSQL for production)
    • SQLAlchemy 2.0 ORM with async support
  2. MCP Server - AI assistant integration layer

    • Connects to the GraphQL API
    • Provides tools for AI agents to manage society operations
    • Service account authentication

Tech Stack

  • Python 3.11+ - Primary language for GraphQL API
  • TypeScript - Language for MCP Server
  • Node.js - Runtime for MCP Server
  • uv - Fast package and project manager for Python
  • npm - Package manager for TypeScript
  • FastAPI - Async web framework
  • Strawberry GraphQL - Type-safe GraphQL with Python type hints
  • SQLAlchemy 2.0 - Database ORM
  • Alembic - Database migrations

Project Structure

clubber/
├── src/                    # Application source code
│   ├── models/             # SQLAlchemy database models
│   ├── resolvers/          # GraphQL resolvers (queries and mutations)
│   ├── schemas/            # GraphQL schema definitions
│   ├── config.py           # Application configuration
│   ├── database.py         # Database setup and session management
│   ├── main.py             # FastAPI application entry point
│   ├── mcp_server.ts       # MCP server implementation (TypeScript)
│   └── validation.py       # Input validation logic
├── dist/                   # Compiled TypeScript code
├── node_modules/           # Node.js dependencies
├── package.json            # Node.js project configuration
├── tsconfig.json           # TypeScript configuration
├── tests/                  # Test suite
│   ├── unit/               # Unit tests (validation logic)
│   ├── integration/        # Integration tests (database, resolvers)
│   ├── e2e/                # End-to-end tests (API flows)
│   ├── mcp/                # MCP server tests
│   └── conftest.py         # Shared test fixtures
├── migrations/             # Alembic database migrations
│   └── versions/           # Migration version files
├── scripts/                # Utility scripts
│   └── seed.py             # Database seeding script
├── openspec/               # Specification-driven development
│   ├── specs/              # Current specifications
│   │   ├── database-layer/
│   │   ├── graphql-api/
│   │   ├── mcp-integration/
│   │   ├── member-crud/
│   │   ├── project-documentation/
│   │   └── project-setup/
│   └── changes/            # Change proposals
│       └── archive/        # Archived completed changes
├── pyproject.toml          # Project dependencies and configuration
├── alembic.ini             # Alembic migration configuration
├── uv.lock                 # Locked dependency versions
├── CLAUDE.md               # AI assistant instructions
└── README.md               # This file

Getting Started

Prerequisites

  • Python 3.11 or higher
  • uv package manager

Setup

# Clone the repository
git clone ssh://git@codeberg.org/gurix/clubber.git
cd clubber

# Install uv if not already installed
curl -LsSf https://astral.sh/uv/install.sh | sh

# Install dependencies
uv sync

# Run database migrations
uv run alembic upgrade head

# (Optional) Seed the database with sample data
PYTHONPATH=. uv run python scripts/seed.py

Running the Server

Start the development server:

uv run uvicorn src.main:app --host 127.0.0.1 --port 8000 --reload

The --reload flag enables auto-restart when code changes are detected.

Access the API:

API Usage Examples

Member Data Model

Members have the following fields:

  • firstName (required) - Member's first name
  • lastName (optional) - Member's last name
  • street (optional) - Street address
  • apartmentNumber (optional) - Apartment or unit number
  • zip (optional) - Postal code
  • city (optional) - City
  • country (optional) - Country
  • email (optional) - Email address (validated when provided)
  • phone (optional) - Phone number in E.164 format (validated when provided)

Key Feature: Only firstName is required. All other fields are optional and can be populated later. Email and phone are validated only when provided (not when null/empty).

GraphQL Queries

List all members:

{
  members {
    id
    firstName
    lastName
    email
    phone
    city
  }
}

Get a single member:

{
  member(id: 1) {
    id
    firstName
    lastName
    email
    street
    city
    country
  }
}

GraphQL Mutations

Create a member (minimal - only firstName):

mutation {
  createMember(input: {
    firstName: "Alice"
  }) {
    id
    firstName
    email
  }
}

Create a member with all fields:

mutation {
  createMember(input: {
    firstName: "Bob"
    lastName: "Johnson"
    street: "123 Main St"
    apartmentNumber: "4B"
    zip: "12345"
    city: "Springfield"
    country: "USA"
    email: "bob.johnson@example.com"
    phone: "+14155551234"
  }) {
    id
    firstName
    lastName
    email
    phone
  }
}

Update a member:

mutation {
  updateMember(input: {
    id: 1
    email: "updated@example.com"
    phone: "+14155559999"
  }) {
    id
    firstName
    email
    phone
  }
}

Delete a member:

mutation {
  deleteMember(id: 2)
}

Using curl

Query members:

curl -X POST http://127.0.0.1:8000/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ members { id firstName lastName email } }"}'

Create a member:

curl -X POST http://127.0.0.1:8000/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "mutation { createMember(input: { firstName: \"Charlie\" }) { id firstName } }"}'

MCP Server

The MCP (Model Context Protocol) server enables AI assistants like Claude to manage society members through natural language interactions.

What is MCP?

The Model Context Protocol (MCP) is a standard protocol that allows AI assistants to use tools and access external systems. The Clubber MCP server provides 6 tools that connect to the GraphQL API:

Dedicated Member Tools (simple, focused operations):

  • list_members - List all members with their complete information
  • get_member - Get detailed information about a specific member by ID
  • create_member - Create a new member (only firstName required)
  • update_member - Update an existing member's information

General GraphQL Tools (flexible, for complex queries):

  • get_graphql_schema - Get the complete GraphQL schema via introspection
  • execute_graphql_query - Execute arbitrary GraphQL queries and mutations

Setup & Build

The MCP server connects to the GraphQL API, so ensure the API is running:

# Start the GraphQL API (Backend)
uv run uvicorn src.main:app --host 127.0.0.1 --port 8000

Then, install dependencies and build the MCP server:

npm install
npm run build

Note: You don't need to run the MCP server manually in a separate terminal. It will be started automatically by your AI client (Claude) based on the configuration below.

Integrating with Claude Code

To use the MCP server with Claude Code, add it to your MCP configuration:

For macOS/Linux (~/Library/Application Support/Claude/claude_desktop_config.json or ~/.config/claude/config.json):

{
  "mcpServers": {
    "clubber": {
      "command": "node",
      "args": ["/absolute/path/to/clubber/dist/mcp_server.js"],
      "env": {
        "CLUBBER_API_URL": "http://127.0.0.1:8000/graphql"
      }
    }
  }
}

Configuration Options:

  • CLUBBER_API_URL: URL of the GraphQL API (default: http://127.0.0.1:8000/graphql)

Note: Make sure to provide the absolute path to the dist/mcp_server.js file.

After adding the configuration, restart Claude Code. You can then use natural language to manage members:

  • "List all members in the society"
  • "Create a new member named Alice"
  • "Update member 5's email to alice@example.com"
  • "Show me details for member 3"

MCP Server Usage Examples

Once configured, you can interact with the MCP server through Claude Code:

Example 1: Create a member

User: Create a new member named Bob Smith with email bob@example.com

Claude uses create_member tool:
- firstName: "Bob"
- lastName: "Smith"
- email: "bob@example.com"

Result: Member created with ID 1

Example 2: Update member information

User: Update member 1's phone number to +41791234567

Claude uses update_member tool:
- id: 1
- phone: "+41791234567"

Result: Member updated successfully

Example 3: List all members

User: Show me all members in the society

Claude uses list_members tool and displays formatted results:
- Bob Smith (bob@example.com, +41791234567)
- Alice Johnson (alice@example.com)
...

Example 4: Discover GraphQL schema

User: What fields are available in the Member type?

Claude uses get_graphql_schema tool to discover:
- The complete schema structure
- All available types (Query, Mutation, Member, etc.)
- Field definitions with types and descriptions
- Available queries and mutations

Result: Shows Member type with all fields (id, firstName, lastName, email, etc.)

Example 5: Execute custom GraphQL query

User: Get only the first names and emails of all members

Claude uses execute_graphql_query tool with:
query: |
  query {
    members {
      firstName
      email
    }
  }

Result: Returns JSON with only the requested fields

Example 6: Execute query with variables

User: Get member 5's contact information

Claude uses execute_graphql_query tool with:
query: |
  query GetMember($id: Int!) {
    member(id: $id) {
      firstName
      lastName
      email
      phone
      city
    }
  }
variables: {"id": 5}

Result: Returns member 5's contact details

When to Use Which Tools

Use dedicated member tools when:

  • Performing simple, common operations
  • The tool matches your exact need
  • You want a formatted, human-readable response

Use general GraphQL tools when:

  • Exploring what's possible (use get_graphql_schema)
  • Needing custom field selection
  • Combining multiple operations
  • Working with complex queries or filters (future)
  • You need the raw JSON response

Running Tests

This project uses pytest for automated testing. All tests are organized in the tests/ directory.

Run All Tests

uv run pytest

Run Specific Test Categories

# Unit tests only (validation logic)
uv run pytest tests/unit

# Integration tests only (database and resolvers)
uv run pytest tests/integration

# E2E tests only (complete API flows)
uv run pytest tests/e2e

# MCP server tests only
uv run pytest tests/mcp

Run Tests with Verbose Output

uv run pytest --verbose

Run Tests with Coverage

uv run pytest --cov=src --cov-report=term-missing

Test Organization

The test suite is organized into four categories:

Unit Tests (tests/unit/)

Pure logic tests for validation functions and utilities.

  • test_validation.py - Email, phone, and firstName validation

Integration Tests (tests/integration/)

Tests for database operations and GraphQL resolvers.

  • test_member_model.py - SQLAlchemy Member model
  • test_member_queries.py - GraphQL queries (member, members)
  • test_member_mutations.py - GraphQL mutations (create, update, delete)

E2E Tests (tests/e2e/)

Complete API request/response flows over HTTP.

  • test_graphql_api.py - GraphQL API endpoints, introspection

MCP Tests (tests/mcp/)

MCP server tools and functionality.

  • test_mcp_server.py - All 6 MCP tools (list_members, get_member, create_member, update_member, get_graphql_schema, execute_graphql_query)

Test Fixtures

Shared fixtures are defined in tests/conftest.py:

  • test_engine - In-memory SQLite database
  • async_session - Async database session
  • graphql_client - FastAPI test client for GraphQL

Development Workflow

This project uses OpenSpec for specification-driven development:

  1. Review existing specs in openspec/specs/
  2. Create change proposals in openspec/changes/ before implementing features
  3. Validate proposals with openspec validate --strict
  4. Implement changes following the proposal
  5. Archive completed changes after deployment

See openspec/AGENTS.md for detailed workflow instructions.

Working with Git Worktrees

We use git worktrees for parallel development:

# Create a worktree for a feature branch
git worktree add ../clubber-feature-name feature/feature-name

# Work in the worktree
cd ../clubber-feature-name

# Remove worktree after merge
git worktree remove ../clubber-feature-name

Contributing

  1. Read openspec/project.md for project conventions
  2. For new features, create an OpenSpec proposal first
  3. Follow the coding style (Black, isort, ruff)
  4. Write tests for new functionality
  5. Use conventional commits (feat:, fix:, docs:, etc.)

License

The MIT License

Contact

Markus Graf - info@markusgraf.ch

Acknowledgments

Built with support from:

S
Description
No description provided
Readme
247 KiB
Languages
Python 70.2%
JavaScript 29%
Mako 0.8%