signal-bot/.github/workflows/ci.yml
Krzysztof kuhy Rudnicki 34b9900a90 fix: green CI, and a page that has actually been looked at
Two things the last commits could not have caught.

CI was red on every commit for as long as scripts/e2e.sh has existed, and
for two reasons, not one. ci.yml never installed ffmpeg or exiftool while
install_service.sh installs both for the host, so tool/e2e_fixtures.py could
not build its JPEG and MP4 fixtures. That step is added and verified by
running the binaries rather than by trusting apt's exit code.

Underneath it was a flake that ffmpeg alone would not have fixed. With the
tools present, e2e still failed two scenarios per run -- never the same two
twice, always one reporting "captured 0" and another "captured 2". The cause
is fake_signal/cli.py sleeping a fixed settle after each injection and then
reading the sends: a reply that fetches an image and shells out to exiftool
lands after that expires, so it arrives after the NEXT scenario's reset and
is counted against that one instead. It now waits for the count it expects,
with a deadline, and then for a beat of quiet -- returning the moment the
count arrives would make "expected 1, captured 2" unobservable, because the
surplus send is exactly the one that has not landed yet. A scenario expecting
nothing skips the poll and waits out the settle, which is the only way to
assert that nothing happens. --patience is exposed so the tests can drive the
give-up path without waiting it out; test_cli went from 45s to 0.4s. Two
consecutive full runs: 28 ok, 0 FAIL, exit 0.

And the page had no styling at all. Times New Roman on white, default
buttons, and the tagger's charcoal theme lost in the absorption -- the jsdom
suite asserts behaviour and cannot see any of that. @kuhyx/web-ui's tokens.css
is now imported and src/index.css lays the page out with those tokens only,
inventing no values.

The visual pass also found a real defect the tests could not: the active tab
was unreadable after being clicked. `button:hover` is more specific than a
bare attribute selector, so with the pointer still resting on the tab you
just clicked, the active rule lost and --on-fill dark text repainted onto
--surface-2 dark. Confirmed by reading the computed style, not by eye:
bg was rgb(56,49,46) where it should have been the accent. The active rule
now claims :hover too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01416TegJ3sa8xL5qGGgrygP
2026-08-31 15:38:24 +02:00

105 lines
3.4 KiB
YAML

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# scripts/install_service.sh installs both of these for the host and
# this file never did, so ./scripts/e2e.sh has failed on every commit
# for as long as it has existed: tool/e2e_fixtures.py builds its JPEG
# and MP4 fixtures with ffmpeg, and signal_bot/media_strip.py strips
# metadata with exiftool. Verified rather than trusted, because an
# apt exit code is not the same claim as "the binary resolves".
- name: Install ffmpeg and exiftool
run: |
sudo apt-get update
sudo apt-get install -y ffmpeg libimage-exiftool-perl
ffmpeg -version | head -1
exiftool -ver
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Set up Python
run: uv python install 3.14
- name: Install dependencies
run: |
uv venv .venv
uv pip install --python .venv/bin/python -e '.[dev]'
# Mirrors the no-noqa pre-commit hook. A suppression must not be able
# to reach main just because someone committed without hooks installed.
- name: block suppressions
run: |
if grep -rnEi '#\s*(noqa|type:\s*ignore|ruff:\s*noqa)' \
--include='*.py' signal_bot fake_signal tests; then
echo '::error::suppressions are not allowed; fix the finding or ask first'
exit 1
fi
# The name half of this check needs the operator's private data and so
# can only run locally; the shape half -- phone numbers, uuids, Signal
# group ids -- works anywhere and is what CI enforces on every push.
- name: no personal data
run: ./scripts/check_personal_data.sh --all
- name: ruff format
run: .venv/bin/ruff format --check .
- name: ruff check (ALL rules)
run: .venv/bin/ruff check .
- name: mypy --strict
run: .venv/bin/mypy signal_bot fake_signal
- name: shellcheck
# --severity=style is the floor CI enforces; the local pre-commit hook
# uses the same flag so a version skew cannot pass locally and fail here.
run: shellcheck -x --severity=style ./*.sh scripts/*.sh
- name: pytest (100% branch coverage)
run: .venv/bin/python -m pytest -q
# The real bot against the fake Signal server. No account, no network.
- name: end-to-end
run: ./scripts/e2e.sh
# Boot order reversed: the bot starts first and Signal appears later,
# which is what a reboot looks like and what put the unit in `failed`
# for eight hours on 2026-08-30.
- name: cold start
run: ./scripts/cold_start.sh
# The management page. Its own job rather than more steps on `check`: it
# needs a Node toolchain that the Python job has no use for, and the two
# halves fail for unrelated reasons.
web:
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "24.20.0"
- run: corepack enable
- name: install (exact pins, from the lockfile)
run: pnpm install --frozen-lockfile
- name: tsc --noEmit and eslint
run: pnpm lint
- name: vitest (100% coverage, no excludes)
run: pnpm coverage