name: Test Suite
on:
pull_request:
branches: [ main, develop ]
push:
branches: [ main, develop ]
workflow_dispatch:
inputs:
test_mode:
description: 'Test execution mode'
required: false
default: 'quick'
type: choice
options:
- 'quick'
- 'full'
- 'lint-only'
- 'docker-only'
permissions:
contents: read
security-events: write
actions: read
jobs:
lint:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.test_mode != 'docker-only' }}
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 black isort mypy
- name: Lint with flake8
run: |
# Stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# Exit-zero treats all errors as warnings
flake8 . --count --exit-zero --max-complexity=15 --max-line-length=127 --statistics
- name: Check code formatting with black
run: |
black --check --diff *.py tests/*.py
- name: Check import sorting with isort
run: |
isort --check-only --diff *.py tests/*.py
docker-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker image
run: |
docker build -t kafka-schema-registry-mcp:test .
- name: Test Docker image
run: |
# Test that the image can start and the Python module can be imported
docker run --rm kafka-schema-registry-mcp:test python -c "import kafka_schema_registry_unified_mcp; print('MCP server module loaded successfully')"
# Option 3: Enhanced Trivy configuration
- name: Security scan with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: 'kafka-schema-registry-mcp:test'
format: 'sarif'
output: 'trivy-results-raw.sarif'
severity: 'CRITICAL,HIGH'
ignore-unfixed: true
vuln-type: 'os,library'
scanners: 'vuln'
exit-code: '0' # Don't fail the build on vulnerabilities
# Option 2: Filter SARIF results to ensure only HIGH and CRITICAL are reported
- name: Filter SARIF results for HIGH and CRITICAL only
run: |
if [ -f "trivy-results-raw.sarif" ]; then
# Install jq if not available
which jq || sudo apt-get update && sudo apt-get install -y jq
# Filter SARIF to only include HIGH and CRITICAL severity
# SARIF uses levels: error=CRITICAL, warning=HIGH, note=MEDIUM, none=LOW
jq '
.runs[].results |= map(
select(.level == "error" or .level == "warning")
)
' trivy-results-raw.sarif > trivy-results.sarif
# Debug: Show filtered results count
echo "Filtered SARIF results:"
jq '.runs[].results | length' trivy-results.sarif
else
echo "⚠️ No Trivy SARIF results to filter"
fi
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v4
if: always() && github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
continue-on-error: true
with:
sarif_file: 'trivy-results.sarif'
- name: Display Trivy results summary
if: always()
run: |
echo "## Security Scan Summary" >> $GITHUB_STEP_SUMMARY
if [ -f trivy-results.sarif ]; then
# Extract vulnerability counts from SARIF
critical_count=$(jq '[.runs[0].results[]? | select(.level=="error")] | length' trivy-results.sarif 2>/dev/null || echo "0")
high_count=$(jq '[.runs[0].results[]? | select(.level=="warning")] | length' trivy-results.sarif 2>/dev/null || echo "0")
echo "- Critical vulnerabilities: $critical_count" >> $GITHUB_STEP_SUMMARY
echo "- High vulnerabilities: $high_count" >> $GITHUB_STEP_SUMMARY
echo "- ✅ **SARIF Filtering**: Applied to ensure only HIGH and CRITICAL appear in Security tab" >> $GITHUB_STEP_SUMMARY
echo "- ✅ **Unfixed vulnerabilities**: Ignored to reduce noise" >> $GITHUB_STEP_SUMMARY
if [ "$critical_count" -gt 0 ] || [ "$high_count" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "⚠️ Security vulnerabilities detected. Check the Security tab for details." >> $GITHUB_STEP_SUMMARY
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ No critical or high vulnerabilities found." >> $GITHUB_STEP_SUMMARY
fi
else
echo "⚠️ Security scan results not available." >> $GITHUB_STEP_SUMMARY
fi
- name: Generate Trivy HTML report
if: always()
run: |
docker run --rm -v "$PWD":/workspace aquasec/trivy:latest image \
--format template \
--template "@contrib/html.tpl" \
--output /workspace/trivy-report.html \
--severity CRITICAL,HIGH \
--ignore-unfixed \
--vuln-type os,library \
--scanners vuln \
kafka-schema-registry-mcp:test || true
- name: Upload Trivy reports as artifacts
uses: actions/upload-artifact@v6
if: always()
with:
name: trivy-security-reports
path: |
trivy-results.sarif
trivy-results-raw.sarif
trivy-report.html
retention-days: 30
test-quick:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.test_mode == 'quick' || (github.event.inputs.test_mode != 'full' && github.event.inputs.test_mode != 'lint-only' && github.event.inputs.test_mode != 'docker-only') }}
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-asyncio pytest-timeout
- name: Start test environment
run: |
cd tests
./start_test_environment.sh multi
# Wait for services to be healthy
echo "Waiting for services to be ready..."
timeout 120s bash -c '
until curl -sf http://localhost:38081/subjects >/dev/null 2>&1 && \
curl -sf http://localhost:38082/subjects >/dev/null 2>&1; do
echo "Waiting for Schema Registry services..."
sleep 5
done
'
# Verify MCP container is healthy
if docker ps --filter "name=mcp-server" --format "{{.Names}}" | grep -q "mcp-server"; then
echo "Waiting for MCP server to be healthy..."
timeout 60s bash -c '
until docker inspect --format="{{.State.Health.Status}}" mcp-server 2>/dev/null | grep -q "healthy"; do
echo "Waiting for MCP server health..."
sleep 2
done
'
fi
- name: Run quick tests
run: |
cd tests
./run_all_tests.sh --quick --no-cleanup
- name: Upload test results
uses: actions/upload-artifact@v6
if: always()
with:
name: quick-test-results
path: tests/results/
- name: Stop test environment
if: always()
run: |
cd tests
./stop_test_environment.sh clean
test-full:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.test_mode == 'full' }}
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-asyncio pytest-timeout pytest-cov
- name: Start test environment
run: |
cd tests
./start_test_environment.sh multi
# Wait for all services
echo "Waiting for services to be ready..."
timeout 120s bash -c '
until curl -sf http://localhost:38081/subjects >/dev/null 2>&1 && \
curl -sf http://localhost:38082/subjects >/dev/null 2>&1 && \
curl -sf http://localhost:38080/api/health >/dev/null 2>&1; do
echo "Waiting for all services..."
sleep 5
done
'
# Verify MCP container
if docker ps --filter "name=mcp-server" --format "{{.Names}}" | grep -q "mcp-server"; then
timeout 60s bash -c '
until docker inspect --format="{{.State.Health.Status}}" mcp-server 2>/dev/null | grep -q "healthy"; do
sleep 2
done
'
fi
- name: Run full test suite
run: |
cd tests
./run_all_tests.sh --no-cleanup
- name: Run MCP container tests separately
run: |
cd tests
python -m pytest test_mcp_container_integration.py -v --tb=short
- name: Generate coverage report
run: |
cd tests
python -m pytest --cov=.. --cov-report=xml --cov-report=html -v
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
files: ./tests/coverage.xml
flags: unittests
name: codecov-umbrella
- name: Upload test results
uses: actions/upload-artifact@v6
if: always()
with:
name: full-test-results
path: |
tests/results/
tests/htmlcov/
- name: Stop test environment
if: always()
run: |
cd tests
./stop_test_environment.sh clean
test-mcp-container:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.test_mode != 'lint-only' }}
needs: docker-build
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-asyncio pytest-timeout
- name: Start MCP test environment
run: |
cd tests
# GitHub Actions uses 'docker compose' (with space)
docker compose up -d
# Wait for all services to be healthy
echo "Waiting for services..."
timeout 120s bash -c '
until docker ps --filter "health=healthy" --format "{{.Names}}" | grep -q "schema-registry-dev" &&
docker ps --filter "health=healthy" --format "{{.Names}}" | grep -q "schema-registry-prod" &&
docker ps --filter "health=healthy" --format "{{.Names}}" | grep -q "mcp-server"; do
echo "Waiting for services to be healthy..."
docker ps --format "table {{.Names}}\t{{.Status}}"
sleep 5
done
'
- name: Test MCP container communication
run: |
cd tests
./test_mcp_container.sh
- name: Verify MCP server functionality
run: |
# Show container environment
echo "Container working directory:"
docker exec mcp-server pwd
echo -e "\nContainer Python path:"
docker exec mcp-server python -c "import sys; print('\\n'.join(sys.path))"
echo -e "\nContainer files:"
docker exec mcp-server ls -la
# Verify MCP server module loads correctly
docker exec mcp-server python -c "
import kafka_schema_registry_unified_mcp
print('✓ MCP server module loaded successfully')
"
# Test registry manager with proper imports
docker exec mcp-server python -c "
import os
print('Environment check:')
for k, v in os.environ.items():
if 'SCHEMA' in k:
print(f' {k}={v}')
try:
from schema_registry_common import RegistryManager
manager = RegistryManager()
registries = manager.list_registries()
print(f'✓ Registry manager loaded, found {len(registries)} registries')
except Exception as e:
print(f'✗ Error: {e}')
import traceback
traceback.print_exc()
"
- name: Collect container logs
if: failure()
run: |
docker logs mcp-server > mcp-server.log
docker logs schema-registry-dev > schema-registry-dev.log
docker logs schema-registry-prod > schema-registry-prod.log
- name: Upload logs
uses: actions/upload-artifact@v6
if: failure()
with:
name: container-logs
path: |
*.log
- name: Stop test environment
if: always()
run: |
cd tests
docker compose down -v
summary:
runs-on: ubuntu-latest
needs: [lint, docker-build, test-quick, test-mcp-container]
if: always()
steps:
- name: Test Summary
run: |
echo "## Test Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [[ "${{ needs.lint.result }}" == "success" ]]; then
echo "✅ Linting passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ Linting failed" >> $GITHUB_STEP_SUMMARY
fi
if [[ "${{ needs.docker-build.result }}" == "success" ]]; then
echo "✅ Docker build passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ Docker build failed" >> $GITHUB_STEP_SUMMARY
fi
if [[ "${{ needs.test-quick.result }}" == "success" ]]; then
echo "✅ Quick tests passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ Quick tests failed" >> $GITHUB_STEP_SUMMARY
fi
if [[ "${{ needs.test-mcp-container.result }}" == "success" ]]; then
echo "✅ MCP container tests passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ MCP container tests failed" >> $GITHUB_STEP_SUMMARY
fi