From d2dbc6e1a29dbd2489e9ce9724601f4414b59c59 Mon Sep 17 00:00:00 2001 From: tiXor-code Date: Tue, 25 Aug 2026 21:23:40 +0300 Subject: [PATCH] ci: actually run the test suite This repo has 51 tests and no workflow has ever run them. Adds tests.yml (push to main + PRs) and a smoke-gate step in nightly.yml. Two decisions worth recording: paths allowlist, not paths-ignore. scrape.yml commits data/ every 15 minutes (~21-28 commits/day). A naive `on: push` fires on all of them and buries real failures. An allowlist is used because a future scraper output directory would silently re-enable that storm under paths-ignore but stays excluded here. The nightly step runs BEFORE the backfill, not after. Once db/termo.db exists, 15 skipif-gated tests un-skip and assert frozen DATA constants (universe_size == 947, median_pt_days == 22, top slug pt-modul-toporasi). universe_size is derived from data/harta.html, which the scraper rewrites from CMTEB's live map - so the day CMTEB adds one PT, a post-backfill pytest would fail the nightly and leave the site silently serving stale data. Running before the backfill keeps those 15 skipped and the step a ~10s code gate. Data invariants stay pipeline.validate's job, which already gates the release. Actions are pinned to SHAs (SEC005); permissions are contents: read only, since this job never commits, uploads or deploys. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/nightly.yml | 10 +++++++ .github/workflows/tests.yml | 53 +++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f60f9ab..6353a8e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -42,6 +42,16 @@ jobs: - run: uv sync + # Code smoke gate. Placement is deliberate: this runs BEFORE the backfill, + # while db/termo.db does not yet exist, so the 15 db-gated tests stay + # skipped and only the 36 pure-code tests run (~10s). Moving this AFTER + # the backfill would un-gate frozen data constants (universe_size == 947, + # median_pt_days == 22) and let a legitimate change in CMTEB's PT universe + # block the nightly release, leaving the site silently stale. Data + # invariants are pipeline.validate's job, further down. + - name: Unit tests (code smoke gate) + run: uv run pytest -q + - name: Backfill (full rebuild) run: rm -f db/termo.db && uv run python -m pipeline.backfill archive . db/termo.db diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..b4763e7 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,53 @@ +# Code gate for the pipeline. Runs the pytest suite on code changes only. +# +# The `paths` allowlist is load-bearing. scrape.yml commits data/ every 15 +# minutes (~21-28 commits/day), and a naive `on: push` would fire on every one +# of them, burying real failures in the run history. An allowlist is used rather +# than paths-ignore because a future scraper output directory would silently +# re-enable that storm under paths-ignore, but stays excluded here. +# +# NOTE: 15 of the 51 tests are skipif-gated on a local db/termo.db, which is +# gitignored and never exists on a runner. `36 passed, 15 skipped` is the +# expected result. Those 15 assert frozen data constants (universe_size == 947, +# median_pt_days == 22) and deliberately do NOT gate CI: they would fail on a +# legitimate upstream data change. Data invariants belong to pipeline.validate, +# which the nightly already runs before publishing. +name: Tests + +on: + push: + branches: [main] + paths: &code + - 'pipeline/**' + - 'scraper/**' + - 'scripts/**' + - 'tests/**' + - 'pyproject.toml' + - 'uv.lock' + - '.github/workflows/tests.yml' + pull_request: + paths: *code + workflow_dispatch: + +# Least privilege: this job never commits, never uploads a release and never +# fires the deploy hook, unlike nightly.yml and scrape.yml which need write. +permissions: + contents: read + +concurrency: + # Own group - must never collide with `scrape` or `nightly`. Per-ref so a PR + # and main do not cancel each other. + group: tests-${{ github.ref }} + cancel-in-progress: true + +jobs: + pytest: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + - uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5 + # --locked fails if uv.lock has drifted from pyproject.toml rather than + # silently re-resolving. + - run: uv sync --locked + - run: uv run pytest -q