# Regression canaries — E2E self-tests for the cc-ci server

A standing pytest suite that drives the **real** cc-ci lifecycle harness against pinned canary
recipes and verifies both halves of the server's job:

1. **Good canaries** — healthy apps are reported GREEN (install + upgrade + backup/restore pass).
2. **Bad canary** — broken apps are caught RED; a false-green makes the regression test itself fail.

These tests run the full cold lifecycle on the live cc-ci server. They are **slow** (minutes per
canary) and **opt-in** — kept out of the per-commit fast path by the `canary` marker.

---

## How to run

Run on the cc-ci server (abra + Docker + Swarm required):

```bash
ssh cc-ci
cd /root/cc-ci            # or wherever the repo is checked out
cc-ci-run python -m pytest tests/regression/ -m canary -v
```

Or a single canary:

```bash
cc-ci-run python -m pytest tests/regression/ -m canary -k good-simple -v
```

From the orchestrator:

```bash
ssh cc-ci "cd /root/cc-ci && cc-ci-run python -m pytest tests/regression/ -m canary -v"
```

---

## Canaries

| ID | Recipe | Purpose | Expected verdict |
|----|--------|---------|-----------------|
| `good-simple` | `custom-html-tiny` | Minimal static server — fast signal | GREEN |
| `good-significant` | `lasuite-docs` | Multi-service (backend + Postgres + Collabora + OIDC) | GREEN |
| `bad-false-green` | `custom-html` @ `v5-stale-docroot` | App is UP but serves wrong Content-Type — catches false-green | RED |

### Why the bad canary exists

The scariest regression is a **false-green**: the server reports PASS while the app is broken.
We already saw a fabricated full-PASS during the build. The `bad-false-green` canary pins a known-
broken fixture (`v5-stale-docroot`: nginx serves `.txt` as `application/octet-stream`). The
harness's `test_content_type_html_and_txt` catches this and returns RED (build #75 was RED for
exactly this fixture).

The regression test asserts `rc != 0`. If the harness ever wrongly returns green for this fixture,
that assert fires — false-green is caught before any merge.

---

## What each canary verifies

### Per-tier semantic assertions (the "teeth")

The tests assert MORE than the harness exit code: they check that **specific named assertions**
ran and got the expected result. This guards against a different failure mode — a tier that
nominally "passes" because the assertion was silently removed or made vacuous.

| Stage | Test name | What it proves |
|-------|-----------|---------------|
| install | `test_serving` | Generic HTTP readiness check actually ran |
| install | `test_serving_and_frontend` | Lasuite-docs frontend (SPA shell) actually loaded |
| custom | `test_content_type` | Content-type assertion actually ran (bad canary only) |

If a tier assertion is removed: the named test disappears from `results.json` → the semantic
check fires → the regression suite catches the removal.

### Additional structural assertions (good canaries)

- `install` tier: "pass" (not fail, not skip)
- No tier is "fail" (skips acceptable for recipes without backup/custom tests)
- `flags.clean_teardown = True` (no leftover containers/volumes/secrets)
- `flags.no_secret_leak = True` (no secret value in the results artifact)

---

## Cadence policy

**Do NOT run on every commit or PR.** These are slow and resource-heavy. Run them:

- Before a **release** of the cc-ci server (after a batch of server changes).
- As a **polishing pass** or pre-merge check for significant server refactors.
- On-demand when you suspect a regression: `pytest -m canary`.

They are NOT wired to the per-commit Drone pipeline. If adding a `!testme`-style trigger for the
cc-ci repo, gate it behind a deliberate label (e.g. `run-canaries`) — not an automatic run on
every push.

---

## How to add a canary

1. Identify a recipe that is already deployable and has pinned version tags.
2. Decide the expected verdict (GREEN or RED) and which tier assertions have teeth.
3. Add an entry to `CANARIES` in `test_canaries.py`:

```python
{
    "id": "good-myrecipe",
    "recipe": "my-recipe",
    "src": "recipe-maintainers/my-recipe",
    "ref": "<pinned-sha>",           # pin to a specific commit for stability
    "expected_green": True,
    "stage_pass_checks": [
        ("install", "test_serving"),  # verify this named test ran and passed
    ],
    "stage_fail_checks": [],
}
```

4. Run the canary once to confirm it passes:
   `cc-ci-run python -m pytest tests/regression/ -m canary -k good-myrecipe -v`

5. Update the pin comment with the date and the recipe version it was pinned at.

---

## Pin maintenance

Canary refs are pinned to specific SHAs for stability. When a recipe publishes a new release:

1. Update the `"ref"` SHA in the canary definition (use the new main-branch HEAD).
2. Update the pin comment with the new date/version.
3. Re-run the canary to confirm GREEN before committing the pin update.

The bad canary (`v5-stale-docroot`) is a stable fixture branch — update only if the branch is
deleted. If deleted, recreate the pattern: an app that is up + passes lifecycle tiers but fails
one functional assertion.
