name: CI # Single gated pipeline for the test contour (Stage 16). Gitea cannot express # cross-workflow `needs`, so the full test suite and the auto test-deploy live in # one workflow. # # Branch model (CLAUDE.md): feature branches are cut from `development`; a commit # to a feature branch triggers nothing. The pipeline runs on a PR into # `development` or `master` (the full test suite — the merge gate) and on a push # to `development` (after a merge). The deploy job runs only for `development` # (PR or merge), so a PR into `master` is test-only; the prod deploy is a manual # workflow (Stage 18). # # Console output is kept plain (NO_COLOR + `docker compose --ansi never` + # `--progress plain`) so the Gitea logs stay readable. on: pull_request: branches: [development, master] push: branches: [development] jobs: unit: runs-on: ubuntu-latest defaults: run: shell: bash env: # The engine consumes the published scrabble-solver module from this Gitea; # GOPRIVATE makes go fetch it directly (skipping the public proxy/checksum DB). GOPRIVATE: gitea.iliadenisov.ru/* DICT_VERSION: v1.0.0 steps: - name: Checkout uses: actions/checkout@v4 - name: Fetch dictionary DAWGs run: | mkdir -p "${GITHUB_WORKSPACE}/dawg" curl -fsSL -o /tmp/dawg.tar.gz "https://gitea.iliadenisov.ru/developer/scrabble-dictionary/releases/download/${DICT_VERSION}/scrabble-dawg-${DICT_VERSION}.tar.gz" tar xzf /tmp/dawg.tar.gz -C "${GITHUB_WORKSPACE}/dawg" ls -la "${GITHUB_WORKSPACE}/dawg" - name: Set up Go uses: actions/setup-go@v5 with: go-version-file: go.work cache: true - name: gofmt run: | unformatted="$(gofmt -l .)" if [ -n "$unformatted" ]; then echo "gofmt needed on:"; echo "$unformatted"; exit 1 fi - name: vet run: go vet ./backend/... ./pkg/... ./gateway/... ./platform/telegram/... - name: build run: go build ./backend/... ./pkg/... ./gateway/... ./platform/telegram/... - name: test env: BACKEND_DICT_DIR: ${{ github.workspace }}/dawg run: go test -count=1 ./backend/... ./pkg/... ./gateway/... ./platform/telegram/... integration: runs-on: ubuntu-latest defaults: run: shell: bash env: # Ryuk (testcontainers' reaper) does not start cleanly on every runner; the # suite's TestMain terminates its own container, so disable it. TESTCONTAINERS_RYUK_DISABLED: "true" GOPRIVATE: gitea.iliadenisov.ru/* DICT_VERSION: v1.0.0 steps: - name: Checkout uses: actions/checkout@v4 - name: Fetch dictionary DAWGs run: | mkdir -p "${GITHUB_WORKSPACE}/dawg" curl -fsSL -o /tmp/dawg.tar.gz "https://gitea.iliadenisov.ru/developer/scrabble-dictionary/releases/download/${DICT_VERSION}/scrabble-dawg-${DICT_VERSION}.tar.gz" tar xzf /tmp/dawg.tar.gz -C "${GITHUB_WORKSPACE}/dawg" ls -la "${GITHUB_WORKSPACE}/dawg" - name: Set up Go uses: actions/setup-go@v5 with: go-version-file: go.work cache: true - name: Integration tests # -count=1 disables the cache; -p=1 -parallel=1 keeps the container-backed # tests serial; the 15-minute timeout bounds a stuck container pull. env: BACKEND_DICT_DIR: ${{ github.workspace }}/dawg run: go test -tags=integration -count=1 -p=1 -parallel=1 -timeout=15m ./backend/... ui: runs-on: ubuntu-latest defaults: run: shell: bash working-directory: ui steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Node uses: actions/setup-node@v4 with: node-version: 22 - name: Install pnpm run: npm install -g pnpm@11.0.9 - name: Install deps run: pnpm install --frozen-lockfile - name: Type-check run: pnpm run check - name: Unit tests run: pnpm run test:unit - name: Build run: pnpm run build - name: Bundle-size budget run: node scripts/bundle-size.mjs - name: Install Playwright browsers run: pnpm exec playwright install chromium webkit timeout-minutes: 5 - name: E2E smoke (mock) run: pnpm run test:e2e timeout-minutes: 5 deploy: # Auto test-deploy on a PR into development and on the push that merges it. # A PR into master is test-only (this job is skipped); prod deploy is manual. needs: [unit, integration, ui] if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/development') || (github.event_name == 'pull_request' && github.base_ref == 'development') }} runs-on: ubuntu-latest defaults: run: shell: bash env: NO_COLOR: "1" DOCKER_CLI_HINTS: "false" steps: - name: Checkout uses: actions/checkout@v4 - name: Build and (re)deploy the test contour working-directory: deploy env: # Sensitive values -> secrets; non-sensitive -> variables. The compose # interpolates these unprefixed names (see deploy/.env.example). POSTGRES_PASSWORD: ${{ secrets.TEST_POSTGRES_PASSWORD }} AWG_CONF: ${{ secrets.TEST_AWG_CONF }} GM_BASICAUTH_HASH: ${{ secrets.TEST_GM_BASICAUTH_HASH }} GRAFANA_ADMIN_PASSWORD: ${{ secrets.TEST_GRAFANA_ADMIN_PASSWORD }} TELEGRAM_BOT_TOKEN_EN: ${{ secrets.TEST_TELEGRAM_BOT_TOKEN_EN }} TELEGRAM_BOT_TOKEN_RU: ${{ secrets.TEST_TELEGRAM_BOT_TOKEN_RU }} GM_BASICAUTH_USER: ${{ vars.TEST_GM_BASICAUTH_USER }} GRAFANA_ROOT_URL: ${{ vars.TEST_GRAFANA_ROOT_URL }} CADDY_SITE_ADDRESS: ${{ vars.TEST_CADDY_SITE_ADDRESS }} TELEGRAM_MINIAPP_URL: ${{ vars.TEST_TELEGRAM_MINIAPP_URL }} TELEGRAM_GAME_CHANNEL_ID_EN: ${{ vars.TEST_TELEGRAM_GAME_CHANNEL_ID_EN }} TELEGRAM_GAME_CHANNEL_ID_RU: ${{ vars.TEST_TELEGRAM_GAME_CHANNEL_ID_RU }} # The test contour always uses Telegram's test environment — pinned here, # not an operator variable. Stage 18's prod workflow leaves it false. TELEGRAM_TEST_ENV: "true" VITE_TELEGRAM_BOT_ID: ${{ vars.TEST_VITE_TELEGRAM_BOT_ID }} VITE_TELEGRAM_LINK: ${{ vars.TEST_VITE_TELEGRAM_LINK }} VITE_GATEWAY_URL: ${{ vars.TEST_VITE_GATEWAY_URL }} GATEWAY_DEFAULT_SUPPORTED_LANGUAGES: ${{ vars.TEST_GATEWAY_DEFAULT_SUPPORTED_LANGUAGES }} # Unset vars render empty -> the compose ":-" defaults apply. POSTGRES_DB: ${{ vars.TEST_POSTGRES_DB }} POSTGRES_USER: ${{ vars.TEST_POSTGRES_USER }} DICT_VERSION: ${{ vars.TEST_DICT_VERSION }} LOG_LEVEL: ${{ vars.TEST_LOG_LEVEL }} run: | docker compose --ansi never build --progress plain docker compose --ansi never up -d --remove-orphans - name: Probe the gateway through caddy run: | set -u for i in $(seq 1 20); do if docker run --rm --network edge alpine:3.20 wget -q -T 5 -O /dev/null http://scrabble/; then echo "healthy: GET http://scrabble/" exit 0 fi sleep 3 done echo "probe failed; recent gateway logs:" docker logs --tail 50 scrabble-gateway || true exit 1 - name: Prune dangling images if: always() run: docker image prune -f