7.4 KiB
7.4 KiB
Pre-Deployment Test Process Design
Overview
A pre-deployment test pipeline that runs unit tests, builds Docker images, spins up a test environment, and runs integration tests against the containerized service.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Test Orchestration │
│ (Makefile) │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐
│ Unit Tests │ │ Docker Build │ │ Integration Tests │
│ (pytest) │ │ │ │ (pytest + MCP SDK) │
└──────────────┘ └──────────────┘ └──────────────────────┘
│
▼
┌───────────────────────────────────┐
│ docker-compose.test.yaml │
├───────────────┬───────────────────┤
│ grist-mcp │ mock-grist │
│ (SSE server) │ (Python HTTP) │
└───────────────┴───────────────────┘
Flow:
make pre-deployruns the full pipeline- Unit tests run first (fail fast)
- Docker images build (grist-mcp + mock-grist)
- docker-compose.test.yaml starts both containers on an isolated network
- Integration tests connect to grist-mcp via MCP SDK over SSE
- Mock Grist validates API calls and returns canned responses
- Teardown happens regardless of test outcome
Components
Mock Grist Server
Location: tests/integration/mock_grist/
tests/integration/mock_grist/
├── Dockerfile
├── server.py # FastAPI/Starlette app
└── expectations.py # Request validation logic
Behavior:
- Runs on port 8484 inside the test network
- Exposes Grist API endpoints:
/api/docs/{docId}/tables,/api/docs/{docId}/tables/{tableId}/records, etc. - Logs all requests to stdout (visible in test output)
- Returns realistic mock responses based on the endpoint
- Optionally validates request bodies (e.g., correct record format for add_records)
Configuration via environment:
MOCK_GRIST_STRICT=true- Fail on unexpected endpoints (default: false, just log warnings)- Response data is hardcoded but realistic (a few tables, some records)
Docker Compose Test Configuration
File: docker-compose.test.yaml
services:
grist-mcp:
build: .
ports:
- "3000:3000"
environment:
- CONFIG_PATH=/app/config.yaml
- GRIST_MCP_TOKEN=test-token
volumes:
- ./tests/integration/config.test.yaml:/app/config.yaml:ro
depends_on:
mock-grist:
condition: service_started
networks:
- test-net
mock-grist:
build: tests/integration/mock_grist
ports:
- "8484:8484"
networks:
- test-net
networks:
test-net:
driver: bridge
Key points:
- Isolated network so containers can communicate by service name
- grist-mcp waits for mock-grist to start
- Ports exposed to host so pytest can connect from outside
- No volumes for secrets - everything is test fixtures
Integration Tests
Location: tests/integration/
tests/integration/
├── conftest.py # Fixtures: MCP client, wait_for_ready
├── test_mcp_protocol.py # SSE connection, tool listing, basic protocol
├── test_tools_integration.py # Call each tool, verify Grist API interactions
├── config.test.yaml # Test configuration for grist-mcp
└── mock_grist/ # Mock server
conftest.py fixtures:
@pytest.fixture
async def mcp_client():
"""Connect to grist-mcp via SSE and yield the client."""
async with sse_client("http://localhost:3000/sse") as client:
yield client
@pytest.fixture(scope="session")
def wait_for_services():
"""Block until both containers are healthy."""
# Poll http://localhost:3000/health and http://localhost:8484/health
Test coverage:
test_list_tools- Connect, calllist_tools, verify all expected tools returnedtest_list_documents- Calllist_documentstool, verify response structuretest_get_records- Callget_records, verify mock-grist received correct API calltest_add_records- Calladd_records, verify request body sent to mock-grist
Makefile Orchestration
File: Makefile
.PHONY: test build integration pre-deploy clean help
help: ## Show this help
test: ## Run unit tests
build: ## Build Docker images
integration-up: ## Start integration test containers
integration-test: ## Run integration tests (containers must be up)
integration-down: ## Stop and remove test containers
integration: ## Full integration cycle (up, test, down)
pre-deploy: ## Full pre-deployment pipeline
clean: ## Remove all test artifacts
Key targets:
make test-uv run pytest tests/ -v --ignore=tests/integrationmake build-docker compose -f docker-compose.test.yaml buildmake integration-up-docker compose -f docker-compose.test.yaml up -dmake integration-test-uv run pytest tests/integration/ -vmake integration-down-docker compose -f docker-compose.test.yaml down -vmake integration- Runs up, test, down (with proper cleanup on failure)make pre-deploy- Runs test, build, integration in sequence
Failure handling:
integrationtarget uses trap or||pattern to ensuredownruns even if tests fail- Exit codes propagate so CI can detect failures
Files to Create
| File | Purpose |
|---|---|
Makefile |
Orchestration with help, test, build, integration, pre-deploy targets |
docker-compose.test.yaml |
grist-mcp + mock-grist on isolated network |
tests/integration/config.test.yaml |
Test configuration pointing to mock-grist |
tests/integration/conftest.py |
MCP client fixtures |
tests/integration/test_mcp_protocol.py |
Protocol compliance tests |
tests/integration/test_tools_integration.py |
Tool call validation tests |
tests/integration/mock_grist/Dockerfile |
Slim Python image |
tests/integration/mock_grist/server.py |
FastAPI mock server |
Usage
make pre-deploy # Full pipeline
make integration # Just integration tests
make test # Just unit tests