Skip to content

Integration Tests

Integration Tests #319

name: Integration Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
schedule:
# Run integration tests nightly at 3 AM UTC to catch integration issues early
- cron: "0 3 * * *"
env:
REGISTRY: ghcr.io
IMAGE_PREFIX: ghcr.io/${{ github.repository_owner }}/nexuslims-test
jobs:
integration-test:
name: Integration Tests (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12"]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Determine image tag
id: image-tag
run: |
# Match the tag generation from build-test-images.yml
# For branches: use branch name
# For PRs: use pr-<number>
# For other: use sha
if [[ "${{ github.ref }}" == refs/heads/* ]]; then
TAG="${{ github.ref_name }}"
elif [[ "${{ github.event_name }}" == "pull_request" ]]; then
TAG="pr-${{ github.event.pull_request.number }}"
else
TAG="sha-${{ github.sha }}"
fi
# Replace / with - for valid Docker tags
TAG="${TAG//\//-}"
echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "Using image tag: ${TAG}"
- name: Pull pre-built test images
continue-on-error: true
run: |
docker pull ${{ env.IMAGE_PREFIX }}-nemo:${{ steps.image-tag.outputs.tag }} || echo "Tagged NEMO image not found"
docker pull ${{ env.IMAGE_PREFIX }}-cdcs:${{ steps.image-tag.outputs.tag }} || echo "Tagged CDCS image not found"
docker pull ${{ env.IMAGE_PREFIX }}-elabftw:${{ steps.image-tag.outputs.tag }} || echo "Tagged eLabFTW image not found"
- name: Tag images for local use
continue-on-error: true
run: |
docker tag ${{ env.IMAGE_PREFIX }}-nemo:${{ steps.image-tag.outputs.tag }} nexuslims-test-nemo:latest 2>/dev/null || true
docker tag ${{ env.IMAGE_PREFIX }}-cdcs:${{ steps.image-tag.outputs.tag }} nexuslims-test-cdcs:latest 2>/dev/null || true
docker tag ${{ env.IMAGE_PREFIX }}-elabftw:${{ steps.image-tag.outputs.tag }} nexuslims-test-elabftw:latest 2>/dev/null || true
- name: Start Docker services
working-directory: tests/integration/docker
env:
REGISTRY: ${{ env.REGISTRY }}
IMAGE_OWNER: ${{ github.repository_owner }}
IMAGE_TAG: ${{ steps.image-tag.outputs.tag }}
run: |
# Try to use pre-built images with the PR/branch tag from build-test-images.yml
if docker image inspect nexuslims-test-nemo:latest >/dev/null 2>&1 && \
docker image inspect nexuslims-test-cdcs:latest >/dev/null 2>&1 && \
docker image inspect nexuslims-test-elabftw:latest >/dev/null 2>&1; then
echo "Using pre-built images from registry (tag: ${{ steps.image-tag.outputs.tag }})"
docker compose -f docker-compose.ci.yml up -d
else
echo "Pre-built images not available - building locally"
docker compose up -d
fi
- name: Wait for services to be healthy
run: |
echo "Waiting for NEMO to be ready..."
timeout 180 bash -c 'until curl -f http://localhost:48000/ > /dev/null 2>&1; do echo -n "."; sleep 3; done'
echo "✓ NEMO is ready"
echo "Waiting for CDCS to be ready..."
timeout 240 bash -c 'until curl -f http://localhost:48080/ > /dev/null 2>&1; do echo -n "."; sleep 5; done'
echo "✓ CDCS is ready"
echo "Waiting for eLabFTW to be ready..."
timeout 240 bash -c 'until curl -fk https://localhost:48148/login.php > /dev/null 2>&1; do echo -n "."; sleep 5; done'
echo "✓ eLabFTW is ready"
echo "Waiting for MailPit to be ready..."
timeout 60 bash -c 'until curl -f http://localhost:48025/ > /dev/null 2>&1; do echo -n "."; sleep 2; done'
echo "✓ MailPit is ready"
- name: Show Docker service status
if: always()
working-directory: tests/integration/docker
run: docker compose ps
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y findutils
- name: Set up uv
uses: astral-sh/setup-uv@v4
with:
enable-cache: true
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: uv sync
- name: Run all tests (unit + integration)
run: |
uv run pytest tests/ \
-v \
--override-ini=addopts= \
--mpl \
--mpl-baseline-path=tests/unit/files/figs \
--cov=nexusLIMS \
--cov-report html:tests/coverage-combined \
--cov-report term-missing \
--cov-report=xml:coverage-combined.xml \
--tb=short \
--maxfail=5
env:
# Integration test environment variables
PYTEST_TIMEOUT: 600
- name: Upload combined test coverage
uses: actions/upload-artifact@v4
with:
name: coverage-combined-${{ matrix.python-version }}
path: |
.coverage
coverage-combined.xml
include-hidden-files: true
- name: Upload HTML coverage report
if: matrix.python-version == '3.11'
uses: actions/upload-artifact@v4
with:
name: coverage-combined-html
path: tests/coverage-combined
- name: Collect Docker container logs
if: always()
working-directory: tests/integration/docker
run: |
mkdir -p ../../docker-logs
# Debug: Show what containers exist
echo "=== Running Containers ==="
docker ps -a --filter "name=nexuslims-test" || true
echo ""
# Determine which compose file was used and collect logs
if docker image inspect nexuslims-test-nemo:latest >/dev/null 2>&1 && \
docker image inspect nexuslims-test-cdcs:latest >/dev/null 2>&1 && \
docker image inspect nexuslims-test-elabftw:latest >/dev/null 2>&1; then
COMPOSE_FILE="docker-compose.ci.yml"
else
COMPOSE_FILE="docker-compose.yml"
fi
echo "Collecting logs from compose stack (using $COMPOSE_FILE)..."
# Collect full compose logs (try both approaches)
docker compose -f "$COMPOSE_FILE" logs --no-color > ../../docker-logs/docker-compose.log 2>&1 || true
# Also try collecting by container name directly as fallback
for container in nexuslims-test-nemo nexuslims-test-cdcs nexuslims-test-cdcs-mongo nexuslims-test-cdcs-postgres nexuslims-test-cdcs-redis nexuslims-test-elabftw nexuslims-test-elabftw-mysql nexuslims-test-mailpit nexuslims-test-caddy-proxy; do
docker logs "$container" > "../../docker-logs/${container##*-}.log" 2>&1 || true
done
# Display summary to action logs
echo "=== Docker Logs Summary ==="
du -sh ../../docker-logs/* 2>/dev/null || echo "No logs collected"
echo ""
echo "=== Log Files Created ==="
ls -lh ../../docker-logs/ 2>/dev/null || echo "No log directory"
- name: Display Docker logs on failure
if: failure()
run: |
echo "=== Docker Compose Full Log ==="
cat docker-logs/docker-compose.log 2>/dev/null || echo "No compose log available"
echo ""
echo "=== NEMO Log ==="
cat docker-logs/nemo.log 2>/dev/null || echo "No NEMO log available"
echo ""
echo "=== CDCS Log ==="
cat docker-logs/cdcs.log 2>/dev/null || echo "No CDCS log available"
echo ""
echo "=== MongoDB Log ==="
cat docker-logs/mongo.log 2>/dev/null || echo "No MongoDB log available"
echo ""
echo "=== PostgreSQL Log ==="
cat docker-logs/postgres.log 2>/dev/null || echo "No PostgreSQL log available"
echo ""
echo "=== Redis Log ==="
cat docker-logs/redis.log 2>/dev/null || echo "No Redis log available"
echo ""
echo "=== eLabFTW Log ==="
cat docker-logs/elabftw.log 2>/dev/null || echo "No eLabFTW log available"
echo ""
echo "=== eLabFTW MySQL Log ==="
cat docker-logs/mysql.log 2>/dev/null || echo "No eLabFTW MySQL log available"
echo ""
echo "=== Mailpit Log ==="
cat docker-logs/mailpit.log 2>/dev/null || echo "No Mailpit log available"
echo ""
echo "=== Caddy Proxy Log ==="
cat docker-logs/proxy.log 2>/dev/null || echo "No Caddy Proxy log available"
- name: Upload Docker logs artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: docker-logs-${{ matrix.python-version }}
path: tests/docker-logs/
if-no-files-found: ignore
- name: Cleanup Docker services
if: always()
working-directory: tests/integration/docker
run: |
docker compose down -v --remove-orphans || true
upload-coverage:
name: Upload Combined Coverage to Codecov
needs: integration-test
runs-on: ubuntu-latest
if: always()
steps:
- uses: actions/checkout@v4
- name: Download all coverage artifacts
uses: actions/download-artifact@v4
with:
pattern: coverage-combined-*
path: coverage-reports
- name: Upload combined coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage-reports/**/*.xml
flags: combined
fail_ci_if_error: false