Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c2b7f75
test
trevor-e Dec 12, 2025
44f628b
fix
trevor-e Dec 12, 2025
866f500
fix e2e test setup to avoid importing launchpad in test-runner
trevor-e Jan 8, 2026
13241b0
fix: exclude e2e tests from regular test job
trevor-e Jan 8, 2026
99185c5
fix: sanitize artifact IDs to prevent path traversal
trevor-e Jan 8, 2026
76b9f99
fix: create Kafka topic before running e2e tests
trevor-e Jan 8, 2026
e4e43c5
fix: wait for size analysis file before checking results
trevor-e Jan 8, 2026
92ce4cb
fix: use correct field name download_size in e2e tests
trevor-e Jan 8, 2026
6774d7d
fix: use path resolution pattern for CodeQL compliance
trevor-e Jan 8, 2026
0e44479
chore: exclude e2e tests from CodeQL scanning
trevor-e Jan 9, 2026
82d85a5
fix: use hashed filenames to prevent path injection
trevor-e Jan 10, 2026
fe64ffa
fix: validate chunk checksums to prevent path traversal
trevor-e Jan 12, 2026
532756b
fix: use path resolution pattern for CodeQL compliance
trevor-e Jan 12, 2026
1505119
fix: use hashed filenames for chunk storage
trevor-e Jan 12, 2026
ebc8aad
chore: split e2e job into separate steps for cleaner logs
trevor-e Jan 12, 2026
fdec1a0
test: add deliberate error to verify E2E catches failures
trevor-e Jan 12, 2026
4080337
Revert "test: add deliberate error to verify E2E catches failures"
trevor-e Jan 12, 2026
d190f58
test: add strict API contract validation to E2E tests
trevor-e Jan 12, 2026
ecf42e6
fix: update E2E tests to match actual API contract
trevor-e Jan 12, 2026
25de302
fix: insights is a dict keyed by category, not a list
trevor-e Jan 12, 2026
7cc65d4
test: use exact value assertions in E2E tests
trevor-e Jan 13, 2026
33b38cb
fix: strengthen E2E assertions with exact values
trevor-e Jan 13, 2026
3a47ae6
fix: correct iOS treemap root name (HackerNews, not HackerNews.app)
trevor-e Jan 13, 2026
81e3cfc
fix: use correct iOS treemap root size (install size, not download size)
trevor-e Jan 13, 2026
d654a23
fix: address PR review feedback for E2E tests
trevor-e Jan 13, 2026
61d9476
Potential fix for code scanning alert no. 40: Information exposure th…
trevor-e Jan 13, 2026
9bcadf8
fix: remove unused exception variable
trevor-e Jan 14, 2026
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
115 changes: 113 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ jobs:
-e KAFKA_BOOTSTRAP_SERVERS="kafka:9093" \
-e KAFKA_GROUP_ID="launchpad-test-ci" \
-e KAFKA_TOPICS="preprod-artifact-events" \
launchpad-test python -m pytest -n auto tests/ -v
launchpad-test python -m pytest -n auto tests/ --ignore=tests/e2e -v

- name: Show Kafka logs on failure
if: failure()
Expand Down Expand Up @@ -226,9 +226,120 @@ 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: Create Kafka topic
run: |
KAFKA_CONTAINER=$(docker ps -qf "name=kafka")
echo "Creating preprod-artifact-events topic..."
docker exec $KAFKA_CONTAINER kafka-topics --bootstrap-server localhost:9092 --create --topic preprod-artifact-events --partitions 1 --replication-factor 1 --if-not-exists
echo "Topic created successfully"

- name: Build E2E Docker images
run: docker compose -f docker-compose.e2e.yml build

- name: Start E2E services
run: |
# Start services in detached mode (minio, mock-sentry-api, launchpad)
docker compose -f docker-compose.e2e.yml up -d minio mock-sentry-api launchpad

# Wait for launchpad to be healthy
echo "Waiting for Launchpad to be healthy..."
LAUNCHPAD_READY=false
for i in {1..30}; do
if docker compose -f docker-compose.e2e.yml ps launchpad | grep -q "healthy"; then
echo "Launchpad is ready!"
LAUNCHPAD_READY=true
break
fi
echo "Waiting for Launchpad... attempt $i/30"
sleep 5
done

if [ "$LAUNCHPAD_READY" = "false" ]; then
echo "ERROR: Launchpad failed to become healthy"
docker compose -f docker-compose.e2e.yml logs launchpad
exit 1
fi

# Show running services
docker compose -f docker-compose.e2e.yml ps

- name: Run E2E tests
run: |
docker compose -f docker-compose.e2e.yml run --rm e2e-tests
timeout-minutes: 10

- name: Show service 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 "=== 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
120 changes: 120 additions & 0 deletions docker-compose.e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# 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:
- "9010:9000"
- "9011: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:
- "8001: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", "-L", "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/e2e/results:/app/results
command: pytest e2e_tests/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
29 changes: 29 additions & 0 deletions tests/e2e/Dockerfile.test-runner
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
FROM python:3.13-slim

WORKDIR /app

# Install system dependencies including build tools for confluent-kafka
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
gcc \
g++ \
librdkafka-dev \
&& 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 only E2E test files (not the main test suite)
# Copy to root to avoid pytest finding parent conftest.py
COPY tests/e2e /app/e2e_tests
COPY tests/_fixtures /app/fixtures

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

WORKDIR /app
11 changes: 11 additions & 0 deletions tests/e2e/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Conftest for E2E tests - overrides main conftest to avoid importing launchpad."""

import os

import pytest


@pytest.fixture(scope="session", autouse=True)
def setup_test_environment():
"""Set up test environment variables for E2E tests."""
os.environ.setdefault("LAUNCHPAD_ENV", "e2e-test")
26 changes: 26 additions & 0 deletions tests/e2e/mock-sentry-api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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 dependencies
RUN pip install --no-cache-dir \
fastapi==0.115.0 \
uvicorn[standard]==0.32.0 \
pydantic==2.9.2 \
boto3==1.35.0 \
python-multipart==0.0.20

# 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