Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 78 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,86 @@ jobs:
token: ${{ secrets.CODECOV_TOKEN }}
files: junit.xml

e2e:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"

- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: pyproject.toml

- name: Install dependencies
run: make install-dev

- name: Start Kafka with devservices
run: |
.venv/bin/devservices up --mode default

echo "Waiting for Kafka to be ready..."
KAFKA_READY=false
for i in {1..30}; do
KAFKA_CONTAINER=$(docker ps -qf "name=kafka")
if [ -z "$KAFKA_CONTAINER" ]; then
echo "Waiting for Kafka container to start... attempt $i/30"
sleep 2
continue
fi

HEALTH_STATUS=$(docker inspect --format='{{.State.Health.Status}}' $KAFKA_CONTAINER 2>/dev/null || echo "none")
if [ "$HEALTH_STATUS" = "healthy" ]; then
echo "Kafka is ready!"
KAFKA_READY=true
break
fi
echo "Waiting for Kafka health check (status: $HEALTH_STATUS)... attempt $i/30"
sleep 2
done

if [ "$KAFKA_READY" = "false" ]; then
echo "ERROR: Kafka failed to become healthy after 60 seconds"
echo "=== Docker containers ==="
docker ps -a
echo "=== Kafka logs ==="
docker logs $(docker ps -aqf "name=kafka") --tail 100 || echo "Could not get Kafka logs"
exit 1
fi

docker ps

- name: Run E2E tests
run: |
# Start E2E stack (will connect to devservices Kafka)
docker compose -f docker-compose.e2e.yml up --build --abort-on-container-exit --exit-code-from e2e-tests
timeout-minutes: 15

- name: Show E2E logs on failure
if: failure()
run: |
echo "=== Launchpad logs ==="
docker compose -f docker-compose.e2e.yml logs launchpad
echo "=== Mock API logs ==="
docker compose -f docker-compose.e2e.yml logs mock-sentry-api
echo "=== E2E test logs ==="
docker compose -f docker-compose.e2e.yml logs e2e-tests
echo "=== Kafka logs ==="
docker logs $(docker ps -qf "name=kafka") --tail 100 || echo "Could not get Kafka logs"

- name: Cleanup E2E environment
if: always()
run: docker compose -f docker-compose.e2e.yml down -v

build:
runs-on: ubuntu-latest
needs: [check, test]
needs: [check, test, e2e]

steps:
- name: Checkout code
Expand Down
23 changes: 23 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,29 @@ test-unit:
test-integration:
$(PYTHON_VENV) -m pytest -n auto tests/integration/ -v

test-e2e: ## Run E2E tests with Docker Compose (requires devservices up)
@echo "Ensuring devservices Kafka is running..."
@if ! docker ps | grep -q kafka; then \
echo "Starting devservices..."; \
devservices up --mode default; \
sleep 10; \
else \
echo "Kafka already running"; \
fi
@echo "Starting E2E test environment..."
docker compose -f docker-compose.e2e.yml up --build --abort-on-container-exit --exit-code-from e2e-tests
@echo "Cleaning up E2E environment..."
docker compose -f docker-compose.e2e.yml down -v

test-e2e-up: ## Start E2E environment (for debugging)
docker compose -f docker-compose.e2e.yml up --build

test-e2e-down: ## Stop E2E environment
docker compose -f docker-compose.e2e.yml down -v

test-e2e-logs: ## Show logs from E2E environment
docker compose -f docker-compose.e2e.yml logs -f

coverage:
$(PYTHON_VENV) -m pytest tests/unit/ tests/integration/ -v --cov --cov-branch --cov-report=xml --junitxml=junit.xml

Expand Down
123 changes: 123 additions & 0 deletions docker-compose.e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
version: '3.8'

# Note: This E2E setup leverages your existing devservices Kafka
# Run `devservices up` before starting these tests

services:
# MinIO for ObjectStore (S3-compatible)
minio:
image: minio/minio:latest
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
command: server /data --console-address ":9001"
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 10s
timeout: 5s
retries: 3
volumes:
- minio-data:/data
networks:
- launchpad-e2e

# Mock Sentry API server
mock-sentry-api:
build:
context: .
dockerfile: tests/e2e/mock-sentry-api/Dockerfile
ports:
- "8000:8000"
environment:
PYTHONUNBUFFERED: "1"
MINIO_ENDPOINT: "http://minio:9000"
MINIO_ACCESS_KEY: "minioadmin"
MINIO_SECRET_KEY: "minioadmin"
depends_on:
minio:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 10s
timeout: 5s
retries: 3
volumes:
- mock-api-data:/app/data
networks:
- launchpad-e2e
- devservices

# Launchpad service
launchpad:
build:
context: .
dockerfile: Dockerfile
args:
TEST_BUILD: "true" # Include test fixtures
ports:
- "2218:2218"
environment:
PYTHONUNBUFFERED: "1"
KAFKA_BOOTSTRAP_SERVERS: "kafka:9093"
KAFKA_GROUP_ID: "launchpad-e2e-test"
KAFKA_TOPICS: "preprod-artifact-events"
KAFKA_AUTO_OFFSET_RESET: "earliest"
LAUNCHPAD_HOST: "0.0.0.0"
LAUNCHPAD_PORT: "2218"
LAUNCHPAD_ENV: "e2e-test"
SENTRY_BASE_URL: "http://mock-sentry-api:8000"
OBJECTSTORE_URL: "http://minio:9000"
LAUNCHPAD_RPC_SHARED_SECRET: "test-secret-key-for-e2e"
SENTRY_DSN: "" # Disable Sentry SDK in tests
depends_on:
mock-sentry-api:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:2218/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
- launchpad-e2e
- devservices

# Test orchestrator
e2e-tests:
build:
context: .
dockerfile: tests/e2e/Dockerfile.test-runner
environment:
KAFKA_BOOTSTRAP_SERVERS: "kafka:9093"
MOCK_API_URL: "http://mock-sentry-api:8000"
LAUNCHPAD_URL: "http://launchpad:2218"
MINIO_ENDPOINT: "http://minio:9000"
MINIO_ACCESS_KEY: "minioadmin"
MINIO_SECRET_KEY: "minioadmin"
LAUNCHPAD_RPC_SHARED_SECRET: "test-secret-key-for-e2e"
depends_on:
launchpad:
condition: service_healthy
mock-sentry-api:
condition: service_healthy
volumes:
- ./tests:/app/tests
- ./tests/e2e/results:/app/results
command: pytest tests/e2e/test_e2e_flow.py -v --tb=short
networks:
- launchpad-e2e
- devservices

volumes:
minio-data:
mock-api-data:

networks:
launchpad-e2e:
name: launchpad-e2e
devservices:
name: devservices
external: true
25 changes: 25 additions & 0 deletions tests/e2e/Dockerfile.test-runner
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FROM python:3.13-slim

WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*

# Install Python test dependencies
RUN pip install --no-cache-dir \
pytest==8.3.3 \
pytest-asyncio==0.24.0 \
confluent-kafka==2.5.3 \
requests==2.32.3 \
boto3==1.35.0

# Copy test files
COPY tests/e2e /app/tests/e2e
COPY tests/_fixtures /app/tests/_fixtures

# Create results directory
RUN mkdir -p /app/results

WORKDIR /app
20 changes: 20 additions & 0 deletions tests/e2e/mock-sentry-api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM python:3.13-slim

WORKDIR /app

# Install dependencies
RUN pip install --no-cache-dir \
fastapi==0.115.0 \
uvicorn[standard]==0.32.0 \
pydantic==2.9.2 \
boto3==1.35.0

# Copy mock API server code
COPY tests/e2e/mock-sentry-api/server.py .

# Create data directory for storing artifacts and results
RUN mkdir -p /app/data/artifacts /app/data/results /app/data/chunks

EXPOSE 8000

CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8000"]
Loading
Loading