fix(alerts): cap offline pushes per worker — episode damping lost to … #844
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Tests | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| jobs: | |
| test: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| # Tags are needed by the compose-pin drift test, which compares the | |
| # pinned image tag against the newest RELEASED version. checkout does | |
| # not fetch tags by default, so without this the test finds none and | |
| # skips — decorative in the one place it is meant to run. | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.14" | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22" | |
| # Nothing here parsed the JavaScript, and pytest cannot: appending a | |
| # syntax error to app.js leaves the whole suite green. What ships in that | |
| # state is a completely dead dashboard — app.js is one IIFE assigned to | |
| # CP, so a parse failure anywhere means CP is never defined and every | |
| # data-action button silently does nothing. No error page, no failed | |
| # request, just a UI where nothing works. | |
| - name: Parse the JavaScript | |
| run: | | |
| for f in app/static/js/*.js; do node --check "$f"; done | |
| # Behaviour pytest cannot reach. This one needs no browser: it runs the | |
| # real currency functions against controlled exchange-rate state, and | |
| # exists because formatCurrency once labelled an UNCONVERTED USD amount | |
| # with the viewer's currency symbol — $24.90 rendered as "£24.90". | |
| - name: Currency formatting behaviour | |
| run: node scripts/currency_check.mjs | |
| # Also browser-free. A string test can prove the Settings catch is no | |
| # longer empty; only running it proves BOTH panels get written and that | |
| # what lands in them says why (CashPilot-cn3). | |
| - name: Settings failure path | |
| run: node scripts/settings_failure_check.mjs | |
| # Also browser-free. The claim modal has to give THREE different answers | |
| # for what looks like one empty result; only running it proves which. | |
| - name: Claim modal honesty | |
| run: node scripts/claim_modal_check.mjs | |
| # Also behaviour pytest cannot reach, and for the same reason: these | |
| # renderers live in a <script> block inside a Jinja template, so a Python | |
| # assertion could only prove the source mentions a variable — never what | |
| # colour a given worker actually produces. | |
| - name: Fleet staleness behaviour | |
| run: node scripts/fleet_staleness_check.mjs | |
| # The Disk and GPU columns. Worth running rather than grepping for the | |
| # same reason as the rest: the first draft iterated `svc.instances`, which | |
| # is a COUNT, and `for...of` over a number throws — a string assertion | |
| # would have been perfectly happy while the services table went blank. | |
| - name: Host resource columns | |
| run: node scripts/host_resources_check.mjs | |
| # The four payout states. The regression this guards is "internal" (there | |
| # is no address, by design) rendering like "not set" (money may be going | |
| # nowhere). Both are a cell with no address in it; only the output tells | |
| # them apart, so only running the renderer can check it. | |
| - name: Payout destination states | |
| run: node scripts/payout_registry_check.mjs | |
| # The only check here that needs a real browser, because it measures a | |
| # property of the RENDERED page. A button's label colour is decided by the | |
| # cascade, so `.modal-body a` (0,1,1) silently outranked `.btn-success` | |
| # (0,1,0) and painted the claim modal's button cyan-on-green at 2.28:1 -- | |
| # invisible to every other test in this repo, all of which read values | |
| # rather than looking at the result. Asserting the stylesheet CONTAINS | |
| # ":not(.btn)" would prove a rule was typed, not that a button can be read. | |
| # ubuntu-latest ships Chrome, and the script exits 2 rather than 0 when no | |
| # browser is found, so a skipped run can never read as a pass. | |
| - name: Legibility (contrast + link styling, real browser) | |
| run: ./scripts/legibility_check.sh | |
| # From the LOCKFILE, which is what Docker ships (`uv sync --frozen` in the | |
| # Dockerfile) and what release.yml verifies against. Installing from the | |
| # unpinned requirements.txt meant the suite ran against a resolution | |
| # nobody else had: locally fastapi 0.136.1 / starlette 1.0.1, here | |
| # 0.141.1 / 1.3.1. On the newer one `include_router` stops adding routes | |
| # to `app.routes`, so a route sweep silently covered 65 of 76 — a real | |
| # behaviour difference that only ever appeared on CI (CashPilot-de1). | |
| - name: Install uv | |
| run: pip install uv --quiet | |
| - name: Install dependencies | |
| run: uv sync --frozen --extra dev | |
| - name: Run tests | |
| run: uv run pytest tests/ -v --tb=short | |
| - name: Run tests with coverage | |
| run: uv run pytest tests/ --cov=app --cov-report=term-missing --cov-report=xml --cov-fail-under=90 | |
| - name: Upload coverage reports | |
| uses: codecov/codecov-action@v7 | |
| with: | |
| file: ./coverage.xml | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| continue-on-error: true |