Skip to content
Open
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
230 changes: 230 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
---
name: Integration Tests

on:
# Manual trigger for on-demand runs.
# NOTE: we always test mindersec/minder@main to prevent
# impersonation attacks where a fork's malicious Makefile/compose
# code could run with access to this repo's secrets.
workflow_dispatch:
inputs:
test_tags:
description: 'Robot Framework tag filter (e.g. "core", "smoke")'
required: false
default: ''

# Weekly scheduled run
schedule:
- cron: "0 6 * * 1" # Every Monday at 6:00 AM UTC

# Run on PRs to validate test infrastructure changes
pull_request:
paths:
- "scripts/**"
- "minder-tests/**"
- "resources/**"
- "smoke-test-config*.yaml"
- ".github/workflows/integration-tests.yml"
- "requirements.txt"

concurrency:
group: integration-tests-${{ github.ref }}
cancel-in-progress: true

jobs:
integration-test:
name: Run Integration Tests
runs-on: ubuntu-latest
timeout-minutes: 30

permissions:
contents: read

steps:
# ------------------------------------------------------------------
# Checkout smoke-tests.
# We test against mindersec/minder@main using pre-built images —
# we do NOT run any Makefile from the minder repo.
# ------------------------------------------------------------------
- name: Checkout smoke-tests
uses: actions/checkout@v4

# ------------------------------------------------------------------
# Install test dependencies directly on the runner.
# No container image needed — the runner already has Python.
# ------------------------------------------------------------------
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: pip

- name: Install Robot Framework and dependencies
run: pip install -r requirements.txt

# ------------------------------------------------------------------
# Install minder CLI from the latest published GitHub release.
# Avoids building from source (~10 min saved).
# ------------------------------------------------------------------
- name: Install minder CLI
run: |
# Fetch the download URL for the linux amd64 tarball directly from
# the releases API — avoids guessing the exact filename format
# (goreleaser strips the 'v' prefix: v0.1.2 -> minder_0.1.2_linux_amd64.tar.gz)
ASSET_URL=$(curl -sf https://api.github.com/repos/mindersec/minder/releases/latest \
| jq -r '.assets[]
| select(.name | test("linux_amd64\\.tar\\.gz$"))
| .browser_download_url')

if [ -z "${ASSET_URL}" ]; then
echo "ERROR: could not find a linux_amd64 tarball in the latest release assets."
echo "Available assets:"
curl -sf https://api.github.com/repos/mindersec/minder/releases/latest \
| jq -r '.assets[].name'
exit 1
fi

echo "Downloading minder CLI from: ${ASSET_URL}"
curl -fsSL "${ASSET_URL}" | tar -xz -C /usr/local/bin minder
minder version

# ------------------------------------------------------------------
# Fetch the minder docker-compose.yaml from main and patch it to
# use pre-built ghcr.io images instead of local build directives.
# The minder compose file has `build: context: .` for the server
# and migrate services — we replace these with the published image.
# ------------------------------------------------------------------
- name: Fetch and patch Minder docker-compose
run: |
curl -sLo docker-compose.minder.yaml \
https://raw.githubusercontent.com/mindersec/minder/main/docker-compose.yaml

# Install yq to patch the compose file
sudo wget -qO /usr/local/bin/yq \
https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
sudo chmod +x /usr/local/bin/yq

# Replace build directives with ghcr.io pre-built images
yq e '
del(.services.minder.build) |
.services.minder.image = "ghcr.io/mindersec/minder:latest" |
del(.services.migrate.build) |
.services.migrate.image = "ghcr.io/mindersec/minder:latest"
Comment on lines +110 to +112

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two should be ghcr.io/mindersec/minder/server:latest

' -i docker-compose.minder.yaml

# ------------------------------------------------------------------
# minder needs a few config files and SSH keys that make bootstrap
# normally generates. Recreate the minimal set here.
# ------------------------------------------------------------------
- name: Generate minder runtime config
run: |
# Fetch config templates from minder main
curl -sLo server-config.yaml \
https://raw.githubusercontent.com/mindersec/minder/main/config/server-config.yaml.example
curl -sLo flags-config.yaml \
https://raw.githubusercontent.com/mindersec/minder/main/flags-config.yaml || echo "{}" > flags-config.yaml

# Generate the SSH keys minder expects
mkdir -p .ssh .secrets
openssl genrsa -out .ssh/access_token_rsa 2048 2>/dev/null
openssl rsa -in .ssh/access_token_rsa -pubout -out .ssh/access_token_rsa.pub 2>/dev/null
openssl genrsa -out .ssh/refresh_token_rsa 2048 2>/dev/null
openssl rsa -in .ssh/refresh_token_rsa -pubout -out .ssh/refresh_token_rsa.pub 2>/dev/null
openssl rand -base64 32 > .ssh/token_key_passphrase
# Placeholder GitHub App key (real one not needed for core tests)
openssl genrsa -out .secrets/github-app.pem 2048 2>/dev/null
echo "Config files and SSH keys ready."

# ------------------------------------------------------------------
# Start the stack. --wait blocks until all healthchecks pass.
# No separate health-check polling needed.
# ------------------------------------------------------------------
- name: Start Minder Docker stack
run: |
docker compose -f docker-compose.minder.yaml pull --quiet
docker compose -f docker-compose.minder.yaml up -d --wait

# ------------------------------------------------------------------
# Bootstrap: create test user, get offline token, enroll user,
# extract project ID. Script writes MINDER_PROJECT to $GITHUB_ENV.
# ------------------------------------------------------------------
- name: Bootstrap test user and project
run: ./scripts/bootstrap.sh
env:
KEYCLOAK_URL: http://localhost:8081
MINDER_API_URL: http://localhost:8080
MINDER_BINARY: /usr/local/bin/minder
OFFLINE_TOKEN_OUTPUT_PATH: ${{ github.workspace }}/offline.token
CLIENT_ID: smoke-test-client

# ------------------------------------------------------------------
# Run Robot Framework tests directly on the runner.
# MINDER_PROJECT is set by bootstrap.sh via $GITHUB_ENV and is
# available here as a shell env var — do NOT use ${{ env.X }} syntax
# for vars set in previous steps (those are static at parse time).
# ------------------------------------------------------------------
- name: Run smoke tests
run: |
EXTRA_ARGS=""
if [ -n "${{ github.event.inputs.test_tags }}" ]; then
EXTRA_ARGS="-i ${{ github.event.inputs.test_tags }}"
fi
robot \
--outputdir results \
--pythonpath . \
--xunit results/xoutput.xml \
--loglevel INFO \
${EXTRA_ARGS} \
minder-tests/
env:
MINDER_CONFIG: ${{ github.workspace }}/smoke-test-config.yaml
MINDER_OFFLINE_TOKEN_PATH: ${{ github.workspace }}/offline.token
MINDER_TEST_ORG: ${{ secrets.MINDER_TEST_ORG }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
# MINDER_PROJECT is injected automatically from $GITHUB_ENV
# (set by bootstrap.sh) — no need to reference it here explicitly

# ------------------------------------------------------------------
# Results
# ------------------------------------------------------------------
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: robot-test-results
path: results/
retention-days: 30

- name: Publish test report
# Only run if the results file was actually created (tests ran)
if: always() && hashFiles('results/xoutput.xml') != ''
uses: dorny/test-reporter@v1
with:
name: "Integration Test Results"
path: results/xoutput.xml
reporter: java-junit
fail-on-error: false

# ------------------------------------------------------------------
# Teardown
# All three steps guard against docker-compose.minder.yaml not
# existing (e.g. when an earlier step failed before creating it).
# ------------------------------------------------------------------
- name: Show Minder logs on failure
if: failure()
run: |
if [ -f docker-compose.minder.yaml ]; then
docker compose -f docker-compose.minder.yaml logs minder --tail=100
docker compose -f docker-compose.minder.yaml logs keycloak --tail=50
else
echo "docker-compose.minder.yaml not found — stack was never started."
fi

- name: Teardown Minder stack
if: always()
run: |
if [ -f docker-compose.minder.yaml ]; then
docker compose -f docker-compose.minder.yaml down -v --remove-orphans
else
echo "docker-compose.minder.yaml not found — nothing to tear down."
fi
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ config.yaml
log.html
output.xml
report.html
# integration test generated files
minder-project-id
109 changes: 108 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ task test

Since the tests run in a container, they need a `minder` Linux binary in the path.
If you're runninng on a non-Linux machine, you need to provide one with an environment variable:

```bash
MINDER_BINARY_PATH=/path/to/minder task test
```
Expand All @@ -30,6 +31,7 @@ tests that create, modify, or delete repositories and pull requests.
It is possible to specify the org specifying
`MINDER_TEST_ORG=<org-name>` when running `task test`, so the previous
example becomes

```bash
MINDER_BINARY_PATH=/path/to/minder MINDER_TEST_ORG=my-org-name task test
```
Expand All @@ -42,6 +44,7 @@ section for instructions on how to create repos in a test org.
The `task test` command will authenticate using an offline token, by default using the `offline.token` file in the current directory. If you want to test against a different environment, you need to provide a configuration file that contains the endpoints and credentials for the environments you want to test against.

For example, to run the tests against the staging environment, you can use the following command:

```bash
MINDER_CONFIG=$(pwd)/staging-config.yaml MINDER_OFFLINE_TOKEN_PATH=$(pwd)/staging-offline.token task test
```
Expand Down Expand Up @@ -71,6 +74,7 @@ Confusingly, the `issuer_url` needs to be `localhost` as that corresponds to the
### Using ruletypes from a local repository

If you want to run the tests against a local Minder instance with ruletypes from a local repository, you can pass the path to the ruletypes directory as an environment variable.

```bash
MINDER_RULETYPES_PATH=$(pwd)/path/to/ruletypes task test
```
Expand Down Expand Up @@ -125,7 +129,6 @@ Valid user login
Then the user is logged in
```


### Writing custom libraries

Custom libraries are written in the `resources` directory. Each library should have its own
Expand Down Expand Up @@ -155,3 +158,107 @@ Test with repo
Given a copy of repo stacklok/demo-repo-python ${test_repo}
Then assert stuff on ${test_repo}
```

### Running integration tests against a local Minder stack

The integration test mode runs the smoke tests against a full Minder stack
started via `docker compose` (from the main
[mindersec/minder](https://github.com/mindersec/minder) repo).

#### Prerequisites

- Docker / Podman
- [Task](https://taskfile.dev/#/installation)
- A cloned copy of the `mindersec/minder` repository
- `curl`, `jq` on the host
- The `minder` CLI binary on PATH (or set `MINDER_BINARY`)

#### Quick start

```bash
# 1. Install Robot Framework on your host (one-time)
pip install -r requirements.txt

# 2. Clone minder next to the smoke-tests repo (or set MINDER_REPO_PATH)
git clone git@github.com:mindersec/minder.git ../minder

# 3. Run the full integration test lifecycle
task integration-test
```

This will:

1. Start the Minder Docker stack (`docker compose up`)
2. Run `scripts/bootstrap.sh` to create a test user in Keycloak and generate
an offline token (no browser interaction needed)
3. Execute the Robot Framework test suite
4. Tear down the Docker stack

#### Running tests manually (step by step)

```bash
# Install Robot Framework (one-time)
pip install -r requirements.txt

# Start Minder stack and bootstrap test user
task integration-setup

# Run all tests against the running stack (no container needed)
task integration-run

# OR run only core tests (no GitHub provider needed):
task integration-run-core
# which is equivalent to:
task integration-run TEST_TAGS=core

# Tear down
task integration-teardown
```

> **Note on two execution modes:**
> - `task test` — runs tests **inside a Docker container** (good for local dev isolation, matches the original test runner)
> - `task integration-run` — runs tests **directly on the host** using the installed `robot` command (used by CI and for testing against a running stack with manual setup)

#### Test tags

Tests are tagged by their infrastructure requirements:

| Tag | Meaning |
|-----|---------|
| `smoke` | All smoke tests (default) |
| `core` | Tests that only need Minder API (no GitHub) |
| `github-required` | Tests that need a live GitHub org and token |
| `provider-required` | Tests that need an enrolled GitHub provider |

To run only core tests:

```bash
task test -- -i core
```

To exclude GitHub-dependent tests:

```bash
task test -- -e github-required
```

#### Environment variables for integration mode

| Variable | Default | Description |
|----------|---------|-------------|
| `MINDER_REPO_PATH` | `../minder` | Path to cloned `mindersec/minder` repo |
| `KEYCLOAK_URL` | `http://localhost:8081` | Keycloak base URL |
| `MINDER_API_URL` | `http://localhost:8080` | Minder HTTP API URL |
| `MINDER_BINARY` | `minder` | Path to the minder CLI binary |
| `TEST_USER` | `smoke-test-user` | Keycloak test user to create |
| `TEST_PASS` | `smoke-test-password` | Test user password |

#### CI Pipeline

Integration tests run automatically via GitHub Actions:

- **On demand**: via `workflow_dispatch`
- **Weekly**: Monday 06:00 UTC
- **On PR**: when CI infrastructure files change

See `.github/workflows/integration-tests.yml` for details.
Loading
Loading