mirror of
https://github.com/TheFunny/TelegramTwitterMediaBot.git
synced 2026-10-16 03:02:16 +00:00
ci: run the offline suite once per commit
ci.yml also triggered on push to master/tags, so every such push ran the suite twice: once standalone, once as docker.yml's publish gate. Drop the push trigger; the gate becomes the only master/tag run, and docker waits on it — one suite run per commit, and a red suite still blocks the image. The gate runs on every non-PR push (its own `changes` job skips a docs-only diff), so a push touching no build input is still tested. A release tag runs the live-network suite through the gate, via an explicit `live` input computed by the caller (the called workflow's github.ref is not a dependable tag check).
This commit is contained in:
+21
-13
@@ -1,19 +1,22 @@
|
||||
name: CI
|
||||
|
||||
# Test/lint gate (offline, no secrets) on every push/PR, plus a live-network
|
||||
# job that exercises the real source sites and the token-gated pixiv tests.
|
||||
# Test/lint gate (offline, no secrets). Standalone on pull requests, schedule
|
||||
# and manual dispatch; a master/tag push does not trigger it directly — that
|
||||
# push is tested once, by docker.yml's `ci` gate, which calls this workflow
|
||||
# with `gate: true` and blocks the image on the result.
|
||||
#
|
||||
# Layering:
|
||||
# changes — decides whether anything but documentation changed; a docs-only
|
||||
# push/PR skips `test` (which then reports as skipped, not missing).
|
||||
# diff skips `test` (which then reports as skipped, not missing).
|
||||
# test — fmt + clippy + the full offline unit suite + a release-profile
|
||||
# build + cargo-audit dependency gate. Runs on every push and PR,
|
||||
# including forks (it needs no secrets).
|
||||
# build + cargo-audit dependency gate. Runs on PRs, and for a
|
||||
# master/tag push under the docker gate; no secrets needed.
|
||||
# live — the #[ignore]d live-network tests plus the pixiv tests that are
|
||||
# gated on PIXIV_REFRESH_TOKEN. Runs on schedule / manual dispatch
|
||||
# / tag pushes only, because pull requests from forks cannot read
|
||||
# repository secrets. continue-on-error keeps a flaky external site
|
||||
# from blocking, while the run still records the outcome.
|
||||
# gated on PIXIV_REFRESH_TOKEN. Runs on schedule / manual dispatch,
|
||||
# and on a release tag (through the docker gate), because pull
|
||||
# requests from forks cannot read repository secrets.
|
||||
# continue-on-error keeps a flaky external site from blocking,
|
||||
# while the run still records the outcome.
|
||||
#
|
||||
# Every action is pinned to a commit SHA (Dependabot keeps the pins current);
|
||||
# `dtolnay/rust-toolchain` deliberately stays on its channel ref, because the
|
||||
@@ -26,9 +29,6 @@ name: CI
|
||||
# PIXIV_REFRESH_TOKEN is absent or empty (empty = unset CI secret).
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
tags: ['v*']
|
||||
pull_request:
|
||||
schedule:
|
||||
# Weekly probe of the live endpoints, so external API changes surface.
|
||||
@@ -43,6 +43,14 @@ on:
|
||||
gate:
|
||||
type: boolean
|
||||
default: false
|
||||
# Passed by docker.yml for a release tag: the caller computes it from its
|
||||
# own `github.ref`, so the called workflow needs no ref check of its own.
|
||||
# A string (not boolean) on purpose: a boolean input fed by an expression
|
||||
# arrives as the string "true"/"false", and "false" is truthy in `if`.
|
||||
live:
|
||||
description: Run the live-network suite too (docker passes "true" on a release tag).
|
||||
type: string
|
||||
default: 'false'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -143,7 +151,7 @@ jobs:
|
||||
|
||||
live:
|
||||
needs: test
|
||||
if: ${{ !inputs.gate && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v')) }}
|
||||
if: ${{ inputs.live == 'true' || (!inputs.gate && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
continue-on-error: true
|
||||
|
||||
@@ -116,11 +116,17 @@ jobs:
|
||||
# commit whose tests are red. Pull requests skip it: their own CI run covers
|
||||
# the code, and the docker job there only proves the image still builds.
|
||||
ci:
|
||||
needs: should-build
|
||||
if: needs.should-build.outputs.build == 'true' && github.event_name != 'pull_request'
|
||||
# The offline suite's only master/tag run (`ci.yml` has no push trigger).
|
||||
# Every non-PR push runs it — the gate's own `changes` job skips the heavy
|
||||
# work for a docs-only diff — independently of whether the image itself is
|
||||
# rebuilt.
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: ./.github/workflows/ci.yml
|
||||
with:
|
||||
gate: true
|
||||
# A release tag also runs the live-network suite: a tag push is trusted,
|
||||
# so the fork-PR reason to skip it (no secrets) does not apply.
|
||||
live: ${{ startsWith(github.ref, 'refs/tags/v') && 'true' || 'false' }}
|
||||
|
||||
# No `actions/checkout` here on purpose: `docker/build-push-action` defaults
|
||||
# to the Git context (`https://github.com/<owner>/<repo>.git#<ref>`), so
|
||||
|
||||
@@ -89,7 +89,7 @@ Docker: `docker build -t tgxmb .` then `docker run --rm -d --name tgxmb --env-fi
|
||||
| `Dockerfile` | Multi-stage: cached dep layer via stub sources + `touch *.rs` mtime bump (cargo's freshness is mtime-based and `cargo clean -p` removes 0 files — the touch is what forces the real sources to rebuild while deps stay cached), static ffmpeg from ffmpeg.martin-riedl.de (`FFMPEG_URL` arg, optional `FFMPEG_SHA256` checksum, `unzip -t` integrity check), `debian:bookworm-slim` runtime, entrypoint. Runtime ships **no libssl/libcrypto/CA bundle** — rustls webpki-roots handles all TLS, and the static ffmpeg only processes local files (downloads go through reqwest) |
|
||||
|`docker-entrypoint.sh` | Privilege drop: `useradd` with `LOCAL_USER_ID` (default 9001) + `setpriv` (no gosu on bookworm-slim); rejects a non-numeric or `0` `LOCAL_USER_ID`, which would otherwise survive the drop and run the bot root |
|
||||
| `docker-compose.yml` | The deployment composition, committed as-is: every instance value (token, admins, site credentials, domain) is a `${VAR}` substitution read from the gitignored `.env` beside it, so the file needs no per-deployment edit — and a variable not listed in a service's `environment:` never reaches that container. Ships nginx-proxy + acme-companion: webhook mode needs TLS termination in front (teloxide's axum listener is HTTP-only; `WEBHOOK_CERT` only feeds `set_webhook`), bot exposes `VIRTUAL_HOST`/`VIRTUAL_PORT` on the shared `proxy` network, no host port; container names `nginx-proxy`/`acme-companion`/`tgxmb`, start order via `depends_on` (proxy → acme → bot) |
|
||||
| `.github/workflows/docker.yml` | CI: build+push to Docker Hub on tag `v*`/master, plus a build-only check on PRs touching the build inputs; the push is gated on `ci.yml`'s offline suite (reused via `workflow_call` with `gate: true`, so a red suite blocks the image); verifies a release tag matches both crate versions; buildx gha cache (`cache-from` always, `cache-to` except on PRs, scope `tgxmb-build`, `mode=max`) so cargo deps + ffmpeg layers are restored across runs; `FFMPEG_URL`/`FFMPEG_SHA256` come from repo variables when set |
|
||||
| `.github/workflows/docker.yml` | CI: build+push to Docker Hub on tag `v*`/master, plus a build-only check on PRs touching the build inputs; the push is gated on `ci.yml`'s offline suite (reused via `workflow_call` with `gate: true` — `ci.yml` has no `push` trigger, so for a master/tag push this gate is the *only* place the suite runs, and the image waits on it); verifies a release tag matches both crate versions; buildx gha cache (`cache-from` always, `cache-to` except on PRs, scope `tgxmb-build`, `mode=max`) so cargo deps + ffmpeg layers are restored across runs; `FFMPEG_URL`/`FFMPEG_SHA256` come from repo variables when set |
|
||||
| `README.md` | Feature docs + command table (Chinese) |
|
||||
|
||||
## Runtime/Tooling Preferences
|
||||
@@ -109,6 +109,6 @@ Docker: `docker build -t tgxmb .` then `docker run --rm -d --name tgxmb --env-fi
|
||||
- No mocking framework anywhere (no mockito/wiremock/mockall). Conventions: pure-function units (regex parsing, serde round-trips, chunking, retry math) tested synchronously; async tests use real dependencies — file-backed SQLite via `tempfile` (`queue.rs::new_queue()` helper), live network fetches. Tests that must go through a **real `Bot`** (its URL/multipart building, the per-chat limiter and the bot-wide budget) talk to a stand-in API instead (`media_sender::test_support::fake_api::FakeApi`, a `tokio` TCP listener that records every call and answers the smallest result each method needs — teloxide keys methods by payload type, so the recorded name is `SendMediaGroup`, not `sendMediaGroup`): a media group, the edit-before-forward prompt through the real callback path, and `handlers::handle_message` (the context-taking body of `message_handler`, split out for exactly this).
|
||||
- Live-network tests exist in `site/twitter/interface.rs` (5), `site/bsky/interface.rs` (1), `site/misskey/interface.rs` (1), `site/bilibili/interface.rs` (5), `site/pixiv/api.rs` (1) and `site/download.rs` (1: the pixiv download below); the bot crate adds one live test each in `handlers/repair.rs` and `handlers/urls.rs`; `photo.rs` adds one `#[ignore = "heavy: …"]` test. `site/download.rs`'s pixiv download test (`live_download_media_pixiv_original_with_referer`) is gated **both ways** — `#[ignore = "live network: …"]` *and* an early return without `PIXIV_REFRESH_TOKEN` — so a local `cargo test --workspace` stays fully offline and the pixiv CDN flake surfaces only in the live job. `disabled_site_is_reported_not_ignored` (same file) is gated the other way round: it asserts `fetch` answers `FetchError::Disabled { site: "pixiv" }` for a pixiv link and early-returns when `PIXIV_REFRESH_TOKEN` **is** set (the site is then enabled). Test gating convention (enforced by `.github/workflows/ci.yml`): pure unit tests always run; live-network tests carry `#[ignore = "live network: ..."]` (run via `cargo test --workspace -- --ignored live`); token-gated pixiv tests early-return when `PIXIV_REFRESH_TOKEN` is absent **or empty** (an unset GitHub secret arrives as `""` — `is_err()` alone would run them tokenless and fail), and the bilibili live tests early-return when the API answers risk control (`-352`, which bilibili applies per IP by request volume). Run the full offline suite with `cargo test --workspace`.
|
||||
- Fixtures are inline `serde_json::json!` builder fns (`fixture()`, `thread_json()`, `illust_json()`), not files. The shared `CLIENT` sets `pool_max_idle_per_host(0)` under `#[cfg(test)]` to avoid cross-runtime `DispatchGone`.
|
||||
- **CI** — `.github/workflows/ci.yml` (actions pinned to commit SHAs, `--locked` on every cargo invocation, `concurrency` cancels superseded runs, `RUST_BACKTRACE=1`) runs (behind a `changes` gate job, so a push/PR whose entire diff is markdown skips it instead of burning four minutes on nothing) `cargo fmt --check` + `cargo clippy --workspace --all-targets --locked -- -D warnings` + `cargo test --workspace --locked` + a release-profile `cargo build --release --locked` + an `actions-rust-lang/audit` dependency-vulnerability gate (offline, no secrets, on every push/PR) and a `live` job (schedule/manual/tag only, `continue-on-error`) that runs the `#[ignore]`d `live` tests in **both** crates — `PIXIV_REFRESH_TOKEN`/`TWITTER_AUTH_TOKEN`/`BILIBILI_COOKIE` pass through as secrets, and a summary step lists every `SKIP` a test printed so a green live run cannot mean zero coverage. `.github/workflows/docker.yml` builds and pushes the image on master/tag and runs a **build-only check on pull requests touching the build inputs**; its `should-build` gate skips a branch push that is already tagged (`git tag --points-at` — the tag run builds it, so push both refs together) or that touched no build input at all, while a tag push always builds (`Dockerfile`, entrypoint, manifests, `.dockerignore`); a release tag must match both crate versions or the build stops, and `FFMPEG_URL`/`FFMPEG_SHA256` are taken from repository variables when set (a release can pin an exact ffmpeg build). `.github/dependabot.yml` keeps crates, the pinned actions and the Docker base images current.
|
||||
- **CI** — `.github/workflows/ci.yml` (actions pinned to commit SHAs, `--locked` on every cargo invocation, `concurrency` cancels a superseded standalone run while a gate run never cancels itself, `RUST_BACKTRACE=1`) runs (behind a `changes` gate job, so a PR — or a gated push — whose entire diff is markdown skips it instead of burning four minutes on nothing) `cargo fmt --check` + `cargo clippy --workspace --all-targets --locked -- -D warnings` + `cargo test --workspace --locked` + a release-profile `cargo build --release --locked` + an `actions-rust-lang/audit` dependency-vulnerability gate (offline, no secrets) and a `live` job (schedule/manual, plus a release tag through the docker gate; `continue-on-error`) that runs the `#[ignore]`d `live` tests in **both** crates — `PIXIV_REFRESH_TOKEN`/`TWITTER_AUTH_TOKEN`/`BILIBILI_COOKIE` pass through as secrets, and a summary step lists every `SKIP` a test printed so a green live run cannot mean zero coverage. `ci.yml` has **no `push` trigger**: a master/tag push is tested once, by `docker.yml`'s `ci` job (`workflow_call`, `gate: true`), which the image build then waits on — one suite run per commit, and a red suite blocks the push. `.github/workflows/docker.yml` builds and pushes the image on master/tag and runs a **build-only check on pull requests touching the build inputs**; its `should-build` gate skips a branch push that is already tagged (`git tag --points-at` — the tag run builds it, so push both refs together) or that touched no build input at all, while a tag push always builds (`Dockerfile`, entrypoint, manifests, `.dockerignore`); a release tag must match both crate versions or the build stops, and `FFMPEG_URL`/`FFMPEG_SHA256` are taken from repository variables when set (a release can pin an exact ffmpeg build). `.github/dependabot.yml` keeps crates, the pinned actions and the Docker base images current.
|
||||
- Untested and hard to test without a mock seam: `handlers/statics.rs`; `config.rs` only partly (the webhook truth table, TTL fallback and blank-secret parsing are pinned, the rest of the env parsing is not); `db.rs` is covered for the migration chain but not for pool behaviour under contention; `main.rs` is covered where it was split out (`periodic_sweep`, `sweep_temp_dir`) but not for startup/shutdown or its `dptree` branch tree (the handlers themselves are, through the stand-in API); in `x-media`: `media.rs`, `lib.rs`, all `model.rs` (their serde shapes are exercised indirectly by the adapter fixtures that deserialize into them). Everything else — `handlers/{mod,callback,inline,urls}.rs`, `send/*`, `ctx.rs`, `state.rs`, `queue.rs`, `link_cache.rs`, `rate_limit.rs`, `commands.rs` (its executor, through scripted outcomes) — is driven through `TestStores`/`ctx::test_support` and the scripted `MockSender`.
|
||||
- No coverage tracking.
|
||||
|
||||
Reference in New Issue
Block a user