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
80 changes: 69 additions & 11 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ This directory contains CI/CD workflows for building images, packaging Helm char
| [`docker-build-reusable.yaml`](.github/workflows/docker-build-reusable.yaml) | Reusable job: build and push a single Docker image | workflow_call |
| [`helm-push.yaml`](.github/workflows/helm-push.yaml) | Lint, package, and (on push to main/tags) push Helm charts to GHCR (OCI) | push (main, tags), pull_request (paths filter), workflow_dispatch |
| [`helm-package-reusable.yaml`](.github/workflows/helm-package-reusable.yaml) | Reusable job: lint, package, and push a single Helm chart | workflow_call |
| [`test.yaml`](.github/workflows/test.yaml) | Orchestrate integration tests across projects | push (main, corto paths), pull_request, workflow_call, workflow_dispatch |
| [`test-reusable.yaml`](.github/workflows/test-reusable.yaml) | Reusable job: run integration tests for a single project directory | workflow_call |
| [`test.yaml`](.github/workflows/test.yaml) | Directory-based no-secrets pytest for corto, lungo, recruiter | push (main), pull_request, workflow_call, workflow_dispatch |
| [`test-reusable.yaml`](.github/workflows/test-reusable.yaml) | Reusable job: run pytest for one project directory and path set | workflow_call |
| [`test-subprojects-reusable.yaml`](.github/workflows/test-subprojects-reusable.yaml) | Path-filter job: which agent projects changed | workflow_call |
| [`fe-ci.yaml`](.github/workflows/fe-ci.yaml) | Typecheck, ESLint, and Prettier for the Lungo frontend | push (main, frontend paths), pull_request (main, frontend paths) |
| [`version-override-test.yaml`](.github/workflows/version-override-test.yaml) | Example invocation of reusable tests with dependency/image overrides | workflow_dispatch |
| [`docs.yaml`](.github/workflows/docs.yaml) | Publish MkDocs site to GitHub Pages (gh-pages) | push (main, README.md path) |
Expand Down Expand Up @@ -70,21 +71,79 @@ Reusable workflow called by `helm-push.yaml` for each matrix entry. Accepts:
| `package_name` | Package name for the chart (required) |
| `push` | Whether to push to GHCR OCI registry (default: false) |

## test
## test (Python Tests)

Orchestrates integration tests by calling `test-reusable.yaml` for each project (currently corto and lungo). Also exposed as a reusable workflow and accepts overrides for dependencies and Docker images.
Runs directory-based **no-secrets** pytest for each changed agent project. Check names in the PR UI: **`tests / corto`**, **`tests / lungo`**, **`tests / recruiter`**.

## test-reusable
LLM tests live under `tests/integration_llm/` and are **local-only** (not run in CI) until project leadership re-enables them.

### Test directories

| Directory | Meaning | CI |
|-----------|---------|-----|
| `tests/unit/` | Mocks only | Yes |
| `tests/live/` | Subprocess uvicorn/A2A (lungo only) | Yes |
| `tests/integration/` | Docker-compose session; no LLM | Yes |
| `tests/integration_llm/` | Docker + LLM credentials | No (local manual) |

### Per-project CI pytest paths

| Project | `test_paths` |
|---------|--------------|
| corto | `tests/unit tests/integration` |
| lungo | `tests/unit tests/live tests/integration` |
| recruiter | `tests/unit tests/integration` |
Comment on lines +93 to +95

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Question: Shouldn't these be analogous with the naming above?

Also shouldn't we use single word folder names instead?


CI always passes explicit paths (never bare `pytest`, which would collect `integration_llm/` via `testpaths = ["tests"]`).

The reusable job sets `WORKFLOW_API_KEY` for lungo live tests (not a repository secret).

Local commands:

```bash
# CI-equivalent (no secrets)
cd coffeeAGNTCY/coffee_agents/corto && uv run pytest tests/unit tests/integration -q
cd coffeeAGNTCY/coffee_agents/lungo && uv run pytest tests/unit tests/live tests/integration -q
cd coffeeAGNTCY/coffee_agents/recruiter && uv run pytest tests/unit tests/integration -q

# LLM (local only, needs .env)
cd coffeeAGNTCY/coffee_agents/lungo && uv run pytest tests/integration_llm -q
```

Do not run `pytest tests/` as a single full-suite invocation when both `integration/` and `integration_llm/` exist — session fixtures may load twice via `pytest_plugins`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Question: why is this a requirement?
I thought it's going to be disjunctive sets that together gives a whole.


Reusable job that runs `pytest` via `uv` for a single project directory. Exposes inputs for dependency and Docker image overrides.
### Branch protection

| Remove (legacy) | Add (required on PRs) |
|-----------------|----------------------|
| `integration-tests-*` | `tests / *` |
| `fast / *`, `integration / *` | `tests / *` |
Comment on lines +115 to +120

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Question: What is this for?


### Concurrency

**`tests / *`** jobs cancel in-progress runs on new pushes (`cancel-in-progress: true`).

### Re-enabling LLM CI (future)

When policy allows, add a separate workflow job that runs `pytest tests/integration_llm` with secrets on trusted triggers. The directory layout is already in place.

### workflow_call inputs

| Input | Description |
|-------|-------------|
| `test_corto`, `test_lungo`, `test_recruiter` | Subproject toggles (workflow_call / dispatch only) |
| `pip_overrides`, `pip_constraints`, `docker_overrides` | Dependency and image overrides |

## test-reusable

Inputs:
Runs `pytest` via `uv` for a single project directory and explicit path list.

| Input | Description |
|-------|-------------|
| `project_dir` | Path to the project directory to test (required) |
| `pip_overrides` | PEP 508 specs (one per line) forced into the lock (e.g. `httpx==0.27.2`) |
| `pip_constraints` | Constraint lines applied during resolution (e.g. `grpcio<1.65`) |
| `test_paths` | Space-separated pytest paths, e.g. `tests/unit tests/integration` (required) |
| `pip_overrides` | PEP 508 specs (one per line) forced into the lock |
| `pip_constraints` | Constraint lines applied during resolution |
| `docker_overrides` | Lines `service=image[:tag]` to patch docker-compose service images |

Example caller (see `version-override-test.yaml`):
Expand All @@ -93,7 +152,6 @@ Example caller (see `version-override-test.yaml`):
jobs:
integration:
uses: <org>/<repo>/.github/workflows/test.yaml@<ref>
secrets: inherit
with:
pip_overrides: |
httpx==0.27.2
Expand All @@ -109,7 +167,7 @@ Runs frontend checks (TypeScript typecheck, ESLint, Prettier via `npm run check`

## version-override-test

Demonstrates how to pin or constrain dependencies and override container images when calling the reusable test workflow.
Demonstrates how to pin or constrain dependencies and override container images when calling the reusable test workflow. Runs the no-secrets directory suites only (no LLM tests in CI).

## docs

Expand Down
42 changes: 24 additions & 18 deletions .github/workflows/test-reusable.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Integration Tests per Project
name: Python tests per project

on:
workflow_call:
Expand All @@ -7,6 +7,10 @@ on:
description: 'The directory of the project to test.'
required: true
type: string
test_paths:
description: 'Space-separated pytest paths (e.g. tests/unit tests/integration).'
required: true
type: string
pip_overrides:
description: 'One-per-line PEP 508 specs to force (shared across demos; applied only if declared).'
required: false
Expand All @@ -23,12 +27,17 @@ on:
type: string
default: ""

permissions:
contents: read

jobs:
integration-tests:
run-tests:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.project_dir }}
env:
WORKFLOW_API_KEY: TheAnswerIs42
steps:
- name: Checkout code
uses: actions/checkout@v6
Expand Down Expand Up @@ -57,20 +66,17 @@ jobs:
compose_file: docker-compose.yaml
docker_overrides: ${{ inputs.docker_overrides }}

- name: Resolve & install
run: |
uv sync --extra dev

- name: Create CI env file
run: |
cat > .env <<'EOF'
LLM_MODEL=${{ secrets.LLM_MODEL }}
AZURE_API_KEY=${{ secrets.AZURE_API_KEY }}
AZURE_API_BASE=${{ secrets.AZURE_API_BASE }}
AZURE_API_VERSION=${{ secrets.AZURE_API_VERSION }}
WORKFLOW_API_KEY=TheAnswerIs42
EOF

- name: Run integration tests
run: uv run pytest -v --durations=0 --durations-min=1.0 -r ap
- name: Resolve and install
run: uv sync --extra dev

- name: Run tests
run: |
set +e
uv run pytest ${{ inputs.test_paths }} --strict-markers -v --durations=0 --durations-min=1.0 -r ap
code=$?
set -e
if [[ $code -eq 5 ]]; then
echo "No tests collected; treating as success."
exit 0
fi
exit $code
131 changes: 131 additions & 0 deletions .github/workflows/test-subprojects-reusable.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
name: Choose subprojects to test

on:
workflow_call:
inputs:
test_corto:
description: 'Whether to test the Corto project (workflow_call / workflow_dispatch only).'
required: false
type: boolean
default: true
test_lungo:
description: 'Whether to test the Lungo project (workflow_call / workflow_dispatch only).'
required: false
type: boolean
default: true
test_recruiter:
description: 'Whether to test the Recruiter project (workflow_call / workflow_dispatch only).'
required: false
type: boolean
default: true
outputs:
test_corto:
description: 'Run Corto tests for this workflow run.'
value: ${{ jobs.subprojects.outputs.test_corto }}
test_lungo:
description: 'Run Lungo tests for this workflow run.'
value: ${{ jobs.subprojects.outputs.test_lungo }}
test_recruiter:
description: 'Run Recruiter tests for this workflow run.'
value: ${{ jobs.subprojects.outputs.test_recruiter }}

env:
CORTO_DIR: coffeeAGNTCY/coffee_agents/corto
LUNGO_DIR: coffeeAGNTCY/coffee_agents/lungo
RECRUITER_DIR: coffeeAGNTCY/coffee_agents/recruiter

jobs:
subprojects:
runs-on: ubuntu-latest
outputs:
test_corto: ${{ steps.choose-subprojects.outputs.test_corto }}
test_lungo: ${{ steps.choose-subprojects.outputs.test_lungo }}
test_recruiter: ${{ steps.choose-subprojects.outputs.test_recruiter }}
defaults:
run:
shell: bash
env:
WORKFLOW_FILES_LIST_JSON: '[".github/workflows/test.yaml", ".github/workflows/test-reusable.yaml", ".github/workflows/test-subprojects-reusable.yaml", ".github/actions/check-paths-have-changes", ".dockerignore"]'
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Log event info
run: |
echo "event_name=${{ github.event_name }}"
echo "ref=${{ github.ref }}"
echo "ref_name=${{ github.ref_name }}"

- name: Log workflow_call or dispatch inputs
if: ${{ github.event_name == 'workflow_call' || github.event_name == 'workflow_dispatch' }}
run: |
echo "test_corto=${{ inputs.test_corto }}"
echo "test_lungo=${{ inputs.test_lungo }}"
echo "test_recruiter=${{ inputs.test_recruiter }}"

- name: Prepare subproject paths JSON lists
if: ${{ github.event_name == 'pull_request' }}
id: prepare-path-lists
run: |
paths_json_corto=$(echo $WORKFLOW_FILES_LIST_JSON | jq -c -r --arg new_val "$CORTO_DIR" '[$new_val] + .')
echo "paths_json_corto=$paths_json_corto" | tee -a $GITHUB_OUTPUT

paths_json_lungo=$(echo $WORKFLOW_FILES_LIST_JSON | jq -c -r --arg new_val "$LUNGO_DIR" '[$new_val] + .')
echo "paths_json_lungo=$paths_json_lungo" | tee -a $GITHUB_OUTPUT

paths_json_recruiter=$(echo $WORKFLOW_FILES_LIST_JSON | jq -c -r --arg new_val "$RECRUITER_DIR" '[$new_val] + .')
echo "paths_json_recruiter=$paths_json_recruiter" | tee -a $GITHUB_OUTPUT

- name: Check paths have changes for Corto
if: ${{ github.event_name == 'pull_request' }}
id: check-paths-corto
uses: ./.github/actions/check-paths-have-changes
with:
base_sha: ${{ github.event.pull_request.base.sha }}
head_sha: ${{ github.event.pull_request.head.sha }}
paths_json_list: ${{ steps.prepare-path-lists.outputs.paths_json_corto }}

- name: Check paths have changes for Lungo
if: ${{ github.event_name == 'pull_request' }}
id: check-paths-lungo
uses: ./.github/actions/check-paths-have-changes
with:
base_sha: ${{ github.event.pull_request.base.sha }}
head_sha: ${{ github.event.pull_request.head.sha }}
paths_json_list: ${{ steps.prepare-path-lists.outputs.paths_json_lungo }}

- name: Check paths have changes for Recruiter
if: ${{ github.event_name == 'pull_request' }}
id: check-paths-recruiter
uses: ./.github/actions/check-paths-have-changes
with:
base_sha: ${{ github.event.pull_request.base.sha }}
head_sha: ${{ github.event.pull_request.head.sha }}
paths_json_list: ${{ steps.prepare-path-lists.outputs.paths_json_recruiter }}

- name: Determine test choices for subprojects
id: choose-subprojects
run: |
test_corto=false
test_lungo=false
test_recruiter=false

if [[ "${{ github.event_name }}" == "push" ]]; then
test_corto=true
test_lungo=true
test_recruiter=true
elif [[ "${{ github.event_name }}" == "workflow_call" || "${{ github.event_name }}" == "workflow_dispatch" ]]; then
test_corto=${{ inputs.test_corto }}
test_lungo=${{ inputs.test_lungo }}
test_recruiter=${{ inputs.test_recruiter }}
elif [[ "${{ github.event_name }}" == "pull_request" ]]; then
test_corto=${{ steps.check-paths-corto.outputs.changed }}
test_lungo=${{ steps.check-paths-lungo.outputs.changed }}
test_recruiter=${{ steps.check-paths-recruiter.outputs.changed }}
fi

echo "test_corto=$test_corto" | tee -a $GITHUB_OUTPUT
echo "test_lungo=$test_lungo" | tee -a $GITHUB_OUTPUT
echo "test_recruiter=$test_recruiter" | tee -a $GITHUB_OUTPUT
Loading