The `sleep 4` / `sleep 3` after a decision change were magic numbers tied to updateIntervalSeconds / defaultDecisionSeconds. Replace them with waits on the actual condition: - After a ban/unban, poll with wait_for_status until the expected code shows up (stream propagation / live-mode cache TTL). - Captcha keeps status 200 before and after, so gate on the body marker via a new wait_for_body_contains helper. - Control assertions that must NOT change stay immediate (assert_status). Self-documenting, faster on the happy path (returns on the first poll that sees the change), and more robust under slow CI. No fixed sleeps remain. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Binary e2e suite (Traefik binary + mock LAPI)
This suite runs Traefik as a downloaded binary with the plugin loaded from
the local source tree, and replaces Crowdsec with a small HTTP mock
(mocklapi/, a stdlib-only Go command). No Docker, no real
Crowdsec.
It is what CI runs (make e2e_mock). A separate, local-only Docker
suite (real Traefik + Crowdsec, under tests/e2e/scenarios) is kept for
high-fidelity debugging against a real Crowdsec but is not exercised in CI; it
ships in its own PR (#333).
Scope — what this suite does and does NOT test
These tests validate the plugin's own behaviour: the request flow through the Traefik middleware, the live / none / stream modes, caching, trusted-IP bypass, and ban / captcha page rendering.
They deliberately do not test that Crowdsec or its AppSec engine work correctly — that is validated and owned by the upstream maintainer (@maxlerebourg), not by this plugin. The mock only emulates the slice of the LAPI HTTP contract the plugin consumes.
So please don't open issues here about Crowdsec/AppSec detection accuracy based on this suite: the AppSec scenario is intentionally absent, and the mock returns whatever decisions the test tells it to.
What runs
| Component | How |
|---|---|
| Traefik | Binary v3.7.1, downloaded into .cache/ (reused across local runs; re-downloaded on fresh CI runners) |
| Plugin | Loaded via experimental.localPlugins from the repo root (symlinked into plugins-local/) |
| LAPI | mocklapi — a stdlib-only Go command (its own nested module), compiled and cached under .cache/, driven through /admin endpoints instead of cscli |
| Backend | A plain HTTP responder built into the mock |
Fixed ports (override with env vars if needed): Traefik 8000, LAPI 8090,
backend 8091.
Running locally
Prerequisites: bash, curl, go, tar. On first use the Traefik binary is
fetched and the mock is compiled into .cache/. That cache is reused across
local runs; CI runs on fresh runners, so both are recreated on every CI run.
# one scenario
make e2e_mock_stream-mode
# or directly
./tests/e2e/mock/scenarios/stream-mode/run.sh
# the whole suite
make e2e_mock
Layout
mock/
lib/
common.sh # stack lifecycle, Traefik download, mock build, assertions, admin client
traefik.yml # static Traefik config (shared by all scenarios)
mocklapi/
go.mod # nested module — kept out of the plugin's build/lint/vendor
main.go # mock LAPI + backend
scenarios/
<name>/
dynamic.yml # Traefik dynamic config (router + bouncer middleware + backend)
run.sh # assertions for the scenario
*.html # optional fixtures (ban / captcha templates)
dynamic.yml uses placeholders (@@APIKEY@@, @@LAPI_HOST@@,
@@BACKEND_URL@@, @@SCENARIO_DIR@@) that common.sh substitutes at runtime.
Adding a scenario
- Create
scenarios/<name>/dynamic.ymlandrun.sh(copystream-mode/as a template). - In
run.sh, define abodyfunction with the assertions and callrun_scenario "<name>" "$HERE" body. - Drive decisions with
lapi_add_decision <ip> [type] [duration]andlapi_delete_decision <ip>. - Add
<name>toE2E_MOCK_SCENARIOSin theMakefile.