testing/cucumber/features/payg/README.md
End-to-end coverage for the PAYG shadow charging engine (PR #6519 / PR-S3
in notes/PAYG_DESIGN.md §7.5).
./testing/test-payg.sh
That script:
testing/compose/docker-compose-saas.yml (Stirling-PDF with
STIRLING_FLAVOR=saas + a Postgres holding the stirling_pdf schema)testing/compose/payg/saas-seed.sql into the test postgres,
creating a payg-cucumber-team flipped to wallet_policy.engine = 'PAYG_SHADOW' and a test user with API key payg-cucumber-keypython -m behave features/paygdocker-compose-tests-saas.yml on CI)| Scenario | Validates |
|---|---|
| First tool call writes a CHARGED row | Filter + interceptor fire end-to-end; shadow row written |
| Lineage join — second call on output | JobService.joinOrOpen lineage matching; no new shadow row |
| First-step 5xx refunds + closes the process | markFirstStepFailed flips row to REFUNDED, job to CLOSED |
| 4xx leaves the row CHARGED | "customer paid for the attempt" semantics |
| ZIP-returning tool records per-PDF OUTPUT | PaygOutputExtractor unpacks + records signatures |
| Multi-file input writes a single shadow row | Multi-input group sizing |
X-Stirling-Automation sets PIPELINE source | Header → JobSource detection |
The 5xx scenario drives the refund path through PaygCucumberThrowController
— a @Profile("payg-cucumber") stub in app/saas/.../payg/test/ that always
throws. The profile is activated by docker-compose-saas.yml's
SPRING_PROFILES_ACTIVE=saas,payg-cucumber and is never set in production.
One part of the shadow engine can't reasonably be driven from this suite
and is verified by hand each time its code path changes. The procedure
lives in notes/PAYG_DESIGN.md §7.5.2 "PAYG cucumber: manual-only
scenarios".
PAYG_FILTER_ENABLED=false). Needs a docker-container
restart mid-suite; orchestrating that in behave is more harness fragility
than it's worth for a flag that's only flipped during incident response.The scenarios reuse testing/cucumber/exampleFiles/:
ghost1.pdf — single-page reference PDFtables.pdf — multi-page input for split / ZIP scenariosIf those filenames change in the main cucumber harness, update
features/steps/payg_step_definitions.py SINGLE_PAGE_PDF / THREE_PAGE_PDF
constants to match.