From 4d25a82448a070ff16af7d22c77944d2c1eef5bf Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Mon, 28 Sep 2026 15:01:41 +0700 Subject: [PATCH] chore(ci): raise the test hook timeout, fail an empty backup PGlite start-up alone takes about seven seconds on an ARM host, so the 10s beforeAll default failed whole files at random. The weekly backup fails instead of uploading an empty export, and both workflows run with read-only permissions; CI cancels superseded runs. --- .github/workflows/ci.yml | 8 ++++++++ .github/workflows/leaderboard-backup.yml | 17 ++++++++++++----- scripts/export-leaderboards.mjs | 7 +++++++ tests/debug-access.test.js | 4 +++- vitest.config.mjs | 4 ++++ vitest.integration.config.mjs | 4 ++++ 6 files changed, 38 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d1ce40..b1da2ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,14 @@ on: branches: [main, dev] pull_request: +permissions: + contents: read + +# A newer push to the same branch supersedes the run still going. +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + jobs: check: runs-on: ubuntu-latest diff --git a/.github/workflows/leaderboard-backup.yml b/.github/workflows/leaderboard-backup.yml index edb3b8b..a77ce75 100644 --- a/.github/workflows/leaderboard-backup.yml +++ b/.github/workflows/leaderboard-backup.yml @@ -1,10 +1,14 @@ # Weekly copy of every leaderboard, kept as a workflow artifact for 90 days. # Upstash's free plan has no scheduled backups, and the boards are the only -# player data the game holds. Needs three repository secrets: the Redis pair -# the app reads, KV_REST_API_URL and KV_REST_API_TOKEN (plus KEY_PREFIX if the -# deployment sets one), and BACKUP_PASSPHRASE. The file is encrypted with the -# passphrase before upload: on a public repository any signed-in GitHub user -# can download an artifact, and the boards list every player name. +# player data the game holds. Needs the repository secrets KV_REST_API_URL, +# KV_REST_API_TOKEN and BACKUP_PASSPHRASE, plus KEY_PREFIX if the deployment +# sets one. Only the KV_* names are passed through, not UPSTASH_REDIS_REST_*. +# An export that finds no boards fails the job rather than uploading an empty +# file. GitHub disables scheduled workflows after 60 days without repository +# activity; re-enable it from the Actions tab if it goes quiet. The file is +# encrypted with the passphrase before upload: on a public repository any +# signed-in GitHub user can download an artifact, and the boards list every +# player name. # Restore with: openssl enc -d -aes-256-cbc -pbkdf2 -in .enc -out name: Leaderboard backup @@ -13,6 +17,9 @@ on: - cron: '17 3 * * 1' workflow_dispatch: +permissions: + contents: read + jobs: export: runs-on: ubuntu-latest diff --git a/scripts/export-leaderboards.mjs b/scripts/export-leaderboards.mjs index 5f6c27a..968b842 100644 --- a/scripts/export-leaderboards.mjs +++ b/scripts/export-leaderboards.mjs @@ -19,6 +19,13 @@ const outPath = process.argv[2] ?? `leaderboard-backup-${stamp}.json`; const h = getUpstash(); const keys = [...(await scanKeys(h, 'leaderboard:*')), ...(await scanKeys(h, 'distance:*'))].sort(); +// Nothing found is a misconfiguration (a wrong KEY_PREFIX, the wrong +// database), not an empty game. Failing here keeps the weekly job from +// uploading an empty file and reporting success. +if (keys.length === 0) { + console.error(`No boards found under prefix "${h.prefix}"; check KEY_PREFIX and the Redis credentials.`); + process.exit(1); +} const boards = {}; let members = 0; diff --git a/tests/debug-access.test.js b/tests/debug-access.test.js index 156cc95..560480d 100644 --- a/tests/debug-access.test.js +++ b/tests/debug-access.test.js @@ -33,7 +33,9 @@ describe('debugAccessAllowed', () => { delete process.env.DEBUG_ACCESS_KEY; expect(debugAccessAllowed(request())).toBe(false); } finally { - process.env.NODE_ENV = original; + // Assigning undefined would store the string "undefined". + if (original === undefined) delete process.env.NODE_ENV; + else process.env.NODE_ENV = original; } }); diff --git a/vitest.config.mjs b/vitest.config.mjs index 2adb292..bff1324 100644 --- a/vitest.config.mjs +++ b/vitest.config.mjs @@ -4,6 +4,10 @@ export default defineConfig({ test: { environment: 'node', include: ['tests/**/*.test.js'], + // Seven files start PGlite (WASM Postgres) in beforeAll. Start-up alone + // measured ~7s on an ARM host, so with workers starting several at once + // the 10s default failed whole files at random. + hookTimeout: 60_000, // lib/upstash.js refuses to build a client without these. The tests mock the // SDK itself, so the values are never dialled -- they only have to exist. env: { diff --git a/vitest.integration.config.mjs b/vitest.integration.config.mjs index ae153d5..fa93d64 100644 --- a/vitest.integration.config.mjs +++ b/vitest.integration.config.mjs @@ -11,6 +11,10 @@ export default defineConfig({ // Real network round-trips, and the overflow tests issue a few hundred of // them, so the default timeout is too tight. testTimeout: 30_000, + // Seven files start PGlite (WASM Postgres) in beforeAll. Start-up alone + // measured ~7s on an ARM host, so with workers starting several at once + // the 10s default failed whole files at random. + hookTimeout: 60_000, // One file at a time: they all share a single Redis and each flushes the // keyspace between tests. fileParallelism: false,