From a28a417e88d9d81f568a48dcd61c4ec9d56d1507 Mon Sep 17 00:00:00 2001 From: martin-mfg <2026226+martin-mfg@users.noreply.github.com> Date: Mon, 19 Jan 2026 20:52:26 +0100 Subject: [PATCH] fix tests, add CI, fix on mac, extend docs (#25) * tests: fix float problems and PAT problem * extend CONTRIBUTING.md * extend build documentation, add GH action, fix build on MacOS * fix build on Vercel * update .gitignore and CONTRIBUTING.md * fix all tests * add backend tests to CI * make e2e tests work * add debug log to e2e test * ignore copied tests in CI --- .github/CONTRIBUTING.md | 13 ++- .github/workflows/basic-build.yml | 37 ++++++++ .gitignore | 1 + backend/jest.config.js | 13 ++- backend/package-lock.json | 66 ++++--------- backend/src/common/retryer.js | 5 +- backend/tests/bench/utils.js | 14 +++ backend/tests/calculateRank.test.js | 25 +++-- backend/tests/e2e/e2e.test.js | 3 +- backend/tests/fetchStats.test.js | 17 ++++ backend/tests/fetchTopLanguages.test.js | 5 +- backend/tests/renderTopLanguagesCard.test.js | 98 ++++++++++++-------- backend/tests/retryer.test.js | 4 +- backend/tests/setup.jest.js | 2 + docs/deploy.md | 3 + vercel-preparation.sh | 20 ++-- 16 files changed, 209 insertions(+), 117 deletions(-) create mode 100644 .github/workflows/basic-build.yml diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index e724e49e..5d40893f 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -2,12 +2,15 @@ ## Local Development -To run and test GitHub-Stats-Extended, you need to follow a few simple steps: +To set up the project GitHub-Stats-Extended locally, run the following commands: -1. create [your own deployment](../docs/deploy.md) -2. optional: add an SQL database; by using e.g. the ["Nile" integration](https://vercel.com/marketplace/nile) or by manually setting the environment variable `POSTGRES_URL` -3. optional: [create your own OAuth App](https://github.com/settings/developers) and set environment variables `OAUTH_REDIRECT_URI`, `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` on Vercel accordingly -4. optional: in addition to the Vercel project based on the `backend` folder, create a second project based on the `frontend/frontend` folder. No environment variables needed. +```bash +./vercel-preparation.sh +(cd ./backend/ && npm install) +(cd ./frontend/frontend/ && yarn install && yarn build-trends) +``` + +The easiest way to run and test the project is to deploy it to Vercel as described in the [deployment guide](../docs/deploy.md). ## Themes Contribution diff --git a/.github/workflows/basic-build.yml b/.github/workflows/basic-build.yml new file mode 100644 index 00000000..29e0bcd8 --- /dev/null +++ b/.github/workflows/basic-build.yml @@ -0,0 +1,37 @@ +name: Basic Build + +on: + workflow_dispatch: # Allows you to run this manually from the Actions tab + push: + pull_request: + +jobs: + execute: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + + steps: + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: latest + + - name: Install Yarn + run: npm install -g yarn + + - name: Checkout code + uses: actions/checkout@v6 + + - name: Install and Build + run: | + chmod +x ./vercel-preparation.sh + ./vercel-preparation.sh + (cd ./backend/ && npm install) + (cd ./frontend/frontend/ && yarn install && yarn build-trends) + + - name: Run Backend Tests + run: | + (cd ./backend/ && npm test) diff --git a/.gitignore b/.gitignore index e5d394a7..37497ffa 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ backend/*.lock backend/coverage backend/benchmarks backend/vercel_token +backend-copy frontend/frontend/.env frontend/frontend/src/backend diff --git a/backend/jest.config.js b/backend/jest.config.js index 8b2eb258..4977ac60 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -4,10 +4,19 @@ export default { testEnvironment: "jsdom", coverageProvider: "v8", setupFiles: ["/tests/setup.jest.js"], - testPathIgnorePatterns: ["/node_modules/", "/tests/e2e/"], - modulePathIgnorePatterns: ["/node_modules/", "/tests/e2e/"], + testPathIgnorePatterns: [ + "/node_modules/", + "/tests/e2e/", + "/.vercel/", + ], + modulePathIgnorePatterns: [ + "/node_modules/", + "/tests/e2e/", + "/.vercel/", + ], coveragePathIgnorePatterns: [ "/node_modules/", "/tests/E2E/", + "/.vercel/", ], }; diff --git a/backend/package-lock.json b/backend/package-lock.json index bd7fd6b4..7f7d4258 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -42,7 +42,7 @@ "prettier": "^3.7.3" }, "engines": { - "node": ">=22" + "node": "24.x" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -3422,18 +3422,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/@eslint/js": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", - "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -6331,6 +6319,17 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/pg": { "version": "8.16.2", "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.2.tgz", @@ -6429,17 +6428,6 @@ "node": ">= 10.x" } }, - "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -6491,12 +6479,6 @@ "node": ">=8" } }, - "node_modules/platform": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", - "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", - "dev": true - }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -10403,12 +10385,6 @@ "optionator": "^0.9.3" }, "dependencies": { - "@eslint/js": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", - "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", - "dev": true - }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -12555,6 +12531,12 @@ } } }, + "path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "dev": true + }, "pg": { "version": "8.16.2", "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.2.tgz", @@ -12622,12 +12604,6 @@ } } }, - "path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "dev": true - }, "picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -12661,12 +12637,6 @@ "find-up": "^4.0.0" } }, - "platform": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", - "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", - "dev": true - }, "postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", diff --git a/backend/src/common/retryer.js b/backend/src/common/retryer.js index 87fb3019..46c01e24 100644 --- a/backend/src/common/retryer.js +++ b/backend/src/common/retryer.js @@ -36,14 +36,13 @@ const retryer = async (fetcher, username, variables) => { ); PATs = patNames.map((name) => ({ name, value: process.env[name] })); } - const RETRIES = process.env.NODE_ENV === "test" ? 7 : PATs.length; - if (!RETRIES) { + if (!PATs.length) { throw new CustomError("No GitHub API tokens found", CustomError.NO_TOKENS); } const startPAT = getRandomInt(PATs.length); - for (let retries = 0; retries < RETRIES; retries++) { + for (let retries = 0; retries < PATs.length; retries++) { const currentPAT = PATs[(startPAT + retries) % PATs.length]; try { diff --git a/backend/tests/bench/utils.js b/backend/tests/bench/utils.js index e1353025..f3b1e03e 100644 --- a/backend/tests/bench/utils.js +++ b/backend/tests/bench/utils.js @@ -131,3 +131,17 @@ export const runAndLogStats = async ( return stats; }; + +export function approxNumber(expected, precision = 10) { + return { + asymmetricMatch(actual) { + if (typeof actual !== "number" || typeof expected !== "number") + return false; + const epsilon = Math.pow(10, -precision); + return Math.abs(actual - expected) < epsilon; + }, + toAsymmetricMatcher() { + return `≈ ${expected} (precision ${precision})`; + }, + }; +} diff --git a/backend/tests/calculateRank.test.js b/backend/tests/calculateRank.test.js index 662dd267..34c2617e 100644 --- a/backend/tests/calculateRank.test.js +++ b/backend/tests/calculateRank.test.js @@ -1,6 +1,7 @@ import { describe, expect, it } from "@jest/globals"; import "@testing-library/jest-dom"; import { calculateRank } from "../src/calculateRank.js"; +import { approxNumber } from "./bench/utils.js"; describe("Test calculateRank", () => { it("new user gets C rank", () => { @@ -30,7 +31,10 @@ describe("Test calculateRank", () => { stars: 25, followers: 5, }), - ).toStrictEqual({ level: "B-", percentile: 65.02918514848255 }); + ).toStrictEqual({ + level: "B-", + percentile: approxNumber(65.02918514848255), + }); }); it("median user gets B+ rank", () => { @@ -45,7 +49,7 @@ describe("Test calculateRank", () => { stars: 50, followers: 10, }), - ).toStrictEqual({ level: "B+", percentile: 46.09375 }); + ).toStrictEqual({ level: "B+", percentile: approxNumber(46.09375) }); }); it("average user gets B+ rank (include_all_commits)", () => { @@ -60,7 +64,7 @@ describe("Test calculateRank", () => { stars: 50, followers: 10, }), - ).toStrictEqual({ level: "B+", percentile: 46.09375 }); + ).toStrictEqual({ level: "B+", percentile: approxNumber(46.09375) }); }); it("advanced user gets A rank", () => { @@ -75,7 +79,10 @@ describe("Test calculateRank", () => { stars: 200, followers: 40, }), - ).toStrictEqual({ level: "A", percentile: 20.841471354166664 }); + ).toStrictEqual({ + level: "A", + percentile: approxNumber(20.841471354166664), + }); }); it("expert user gets A+ rank", () => { @@ -90,7 +97,10 @@ describe("Test calculateRank", () => { stars: 800, followers: 160, }), - ).toStrictEqual({ level: "A+", percentile: 5.575988339442828 }); + ).toStrictEqual({ + level: "A+", + percentile: approxNumber(5.575988339442828), + }); }); it("sindresorhus gets S rank", () => { @@ -105,6 +115,9 @@ describe("Test calculateRank", () => { stars: 600000, followers: 50000, }), - ).toStrictEqual({ level: "S", percentile: 0.4578556547153667 }); + ).toStrictEqual({ + level: "S", + percentile: approxNumber(0.4578556547153667), + }); }); }); diff --git a/backend/tests/e2e/e2e.test.js b/backend/tests/e2e/e2e.test.js index 63e5e4fd..68c0e09a 100644 --- a/backend/tests/e2e/e2e.test.js +++ b/backend/tests/e2e/e2e.test.js @@ -105,11 +105,10 @@ const GIST_DATA = { const CACHE_BURST_STRING = `v=${new Date().getTime()}`; describe("Fetch Cards", () => { - let VERCEL_PREVIEW_URL; + let VERCEL_PREVIEW_URL = "https://github-stats-extended.vercel.app"; beforeAll(() => { process.env.NODE_ENV = "development"; - VERCEL_PREVIEW_URL = process.env.VERCEL_PREVIEW_URL; }); test("retrieve stats card", async () => { diff --git a/backend/tests/fetchStats.test.js b/backend/tests/fetchStats.test.js index a644c69f..c1ad5e4d 100644 --- a/backend/tests/fetchStats.test.js +++ b/backend/tests/fetchStats.test.js @@ -1,3 +1,10 @@ +/** @jest-environment node */ +/* +Jest must use node for these tests because jsdom doesn't support the structuredClone call in stats.js. +https://jestjs.io/docs/configuration#testenvironment-string +https://github.com/jsdom/jsdom/issues/3363 +*/ + import { afterEach, beforeEach, describe, expect, it } from "@jest/globals"; import "@testing-library/jest-dom"; import axios from "axios"; @@ -519,6 +526,11 @@ describe("Test fetchStats", () => { totalStars: 300, totalDiscussionsStarted: 0, totalDiscussionsAnswered: 0, + totalIssuesAuthored: 0, + totalIssuesCommented: 0, + totalPRsAuthored: 0, + totalPRsCommented: 0, + totalPRsReviewed: 0, rank, }); }); @@ -552,6 +564,11 @@ describe("Test fetchStats", () => { totalStars: 300, totalDiscussionsStarted: 0, totalDiscussionsAnswered: 0, + totalIssuesAuthored: 0, + totalIssuesCommented: 0, + totalPRsAuthored: 0, + totalPRsCommented: 0, + totalPRsReviewed: 0, rank, }); }); diff --git a/backend/tests/fetchTopLanguages.test.js b/backend/tests/fetchTopLanguages.test.js index e9764a25..b45fc03e 100644 --- a/backend/tests/fetchTopLanguages.test.js +++ b/backend/tests/fetchTopLanguages.test.js @@ -3,6 +3,7 @@ import "@testing-library/jest-dom"; import axios from "axios"; import MockAdapter from "axios-mock-adapter"; import { fetchTopLanguages } from "../src/fetchers/top-languages.js"; +import { approxNumber } from "./bench/utils.js"; const mock = new MockAdapter(axios); @@ -70,13 +71,13 @@ describe("FetchTopLanguages", () => { color: "#0f0", count: 2, name: "HTML", - size: 20.000000000000004, + size: approxNumber(20.0), }, javascript: { color: "#0ff", count: 2, name: "javascript", - size: 20.000000000000004, + size: approxNumber(20.0), }, }); }); diff --git a/backend/tests/renderTopLanguagesCard.test.js b/backend/tests/renderTopLanguagesCard.test.js index 58532fa5..dfea99ea 100644 --- a/backend/tests/renderTopLanguagesCard.test.js +++ b/backend/tests/renderTopLanguagesCard.test.js @@ -22,6 +22,7 @@ import { } from "../src/cards/top-languages.js"; import { themes } from "../themes/index.js"; +import { approxNumber } from "./bench/utils.js"; const langs = { HTML: { @@ -120,89 +121,104 @@ describe("Test renderTopLanguages helper functions", () => { }); it("degreesToRadians", () => { - expect(degreesToRadians(0)).toBe(0); - expect(degreesToRadians(90)).toBe(Math.PI / 2); - expect(degreesToRadians(180)).toBe(Math.PI); - expect(degreesToRadians(270)).toBe((3 * Math.PI) / 2); - expect(degreesToRadians(360)).toBe(2 * Math.PI); + expect(degreesToRadians(0)).toBeCloseTo(0); + expect(degreesToRadians(90)).toBeCloseTo(Math.PI / 2); + expect(degreesToRadians(180)).toBeCloseTo(Math.PI); + expect(degreesToRadians(270)).toBeCloseTo((3 * Math.PI) / 2); + expect(degreesToRadians(360)).toBeCloseTo(2 * Math.PI); }); it("radiansToDegrees", () => { - expect(radiansToDegrees(0)).toBe(0); - expect(radiansToDegrees(Math.PI / 2)).toBe(90); - expect(radiansToDegrees(Math.PI)).toBe(180); - expect(radiansToDegrees((3 * Math.PI) / 2)).toBe(270); - expect(radiansToDegrees(2 * Math.PI)).toBe(360); + expect(radiansToDegrees(0)).toBeCloseTo(0); + expect(radiansToDegrees(Math.PI / 2)).toBeCloseTo(90); + expect(radiansToDegrees(Math.PI)).toBeCloseTo(180); + expect(radiansToDegrees((3 * Math.PI) / 2)).toBeCloseTo(270); + expect(radiansToDegrees(2 * Math.PI)).toBeCloseTo(360); }); it("polarToCartesian", () => { - expect(polarToCartesian(100, 100, 60, 0)).toStrictEqual({ x: 160, y: 100 }); + expect(polarToCartesian(100, 100, 60, 0)).toStrictEqual({ + x: approxNumber(160), + y: approxNumber(100), + }); expect(polarToCartesian(100, 100, 60, 45)).toStrictEqual({ - x: 142.42640687119285, - y: 142.42640687119285, + x: approxNumber(142.42640687119285), + y: approxNumber(142.42640687119285), }); expect(polarToCartesian(100, 100, 60, 90)).toStrictEqual({ - x: 100, - y: 160, + x: approxNumber(100), + y: approxNumber(160), }); expect(polarToCartesian(100, 100, 60, 135)).toStrictEqual({ - x: 57.573593128807154, - y: 142.42640687119285, + x: approxNumber(57.573593128807154), + y: approxNumber(142.42640687119285), }); expect(polarToCartesian(100, 100, 60, 180)).toStrictEqual({ - x: 40, - y: 100.00000000000001, + x: approxNumber(40), + y: approxNumber(100), }); expect(polarToCartesian(100, 100, 60, 225)).toStrictEqual({ - x: 57.57359312880714, - y: 57.573593128807154, + x: approxNumber(57.57359312880714), + y: approxNumber(57.573593128807154), }); expect(polarToCartesian(100, 100, 60, 270)).toStrictEqual({ - x: 99.99999999999999, - y: 40, + x: approxNumber(100), + y: approxNumber(40), }); expect(polarToCartesian(100, 100, 60, 315)).toStrictEqual({ - x: 142.42640687119285, - y: 57.57359312880714, + x: approxNumber(142.42640687119285), + y: approxNumber(57.57359312880714), }); expect(polarToCartesian(100, 100, 60, 360)).toStrictEqual({ - x: 160, - y: 99.99999999999999, + x: approxNumber(160), + y: approxNumber(100), }); }); it("cartesianToPolar", () => { expect(cartesianToPolar(100, 100, 160, 100)).toStrictEqual({ - radius: 60, - angleInDegrees: 0, + radius: approxNumber(60), + angleInDegrees: approxNumber(0), }); expect( cartesianToPolar(100, 100, 142.42640687119285, 142.42640687119285), - ).toStrictEqual({ radius: 60.00000000000001, angleInDegrees: 45 }); + ).toStrictEqual({ + radius: approxNumber(60), + angleInDegrees: approxNumber(45), + }); expect(cartesianToPolar(100, 100, 100, 160)).toStrictEqual({ - radius: 60, - angleInDegrees: 90, + radius: approxNumber(60), + angleInDegrees: approxNumber(90), }); expect( cartesianToPolar(100, 100, 57.573593128807154, 142.42640687119285), - ).toStrictEqual({ radius: 60, angleInDegrees: 135 }); + ).toStrictEqual({ + radius: approxNumber(60), + angleInDegrees: approxNumber(135), + }); expect(cartesianToPolar(100, 100, 40, 100.00000000000001)).toStrictEqual({ - radius: 60, - angleInDegrees: 180, + radius: approxNumber(60), + angleInDegrees: approxNumber(180), }); expect( cartesianToPolar(100, 100, 57.57359312880714, 57.573593128807154), - ).toStrictEqual({ radius: 60, angleInDegrees: 225 }); + ).toStrictEqual({ + radius: approxNumber(60), + angleInDegrees: approxNumber(225), + }); expect(cartesianToPolar(100, 100, 99.99999999999999, 40)).toStrictEqual({ - radius: 60, - angleInDegrees: 270, + radius: approxNumber(60), + angleInDegrees: approxNumber(270), }); expect( cartesianToPolar(100, 100, 142.42640687119285, 57.57359312880714), - ).toStrictEqual({ radius: 60.00000000000001, angleInDegrees: 315 }); + ).toStrictEqual({ + radius: approxNumber(60), + angleInDegrees: approxNumber(315), + }); expect(cartesianToPolar(100, 100, 160, 99.99999999999999)).toStrictEqual({ - radius: 60, - angleInDegrees: 360, + radius: approxNumber(60), + angleInDegrees: approxNumber(360), }); }); diff --git a/backend/tests/retryer.test.js b/backend/tests/retryer.test.js index 6f2bc2ed..b71e41f4 100644 --- a/backend/tests/retryer.test.js +++ b/backend/tests/retryer.test.js @@ -2,7 +2,7 @@ import { describe, expect, it, jest } from "@jest/globals"; import "@testing-library/jest-dom"; -import { RETRIES, retryer } from "../src/common/retryer.js"; +import { retryer } from "../src/common/retryer.js"; import { logger } from "../src/common/log.js"; const fetcher = jest.fn((variables, token) => { @@ -75,7 +75,7 @@ describe("Test Retryer", () => { try { await retryer(fetcherFail, {}); } catch (err) { - expect(fetcherFail).toHaveBeenCalledTimes(RETRIES + 1); + expect(fetcherFail).toHaveBeenCalledTimes(2); // @ts-ignore expect(err.message).toBe("Downtime due to GitHub API rate limiting"); } diff --git a/backend/tests/setup.jest.js b/backend/tests/setup.jest.js index 08947667..7c10c446 100644 --- a/backend/tests/setup.jest.js +++ b/backend/tests/setup.jest.js @@ -3,3 +3,5 @@ import { TextEncoder, TextDecoder } from "util"; Object.assign(global, { TextDecoder, TextEncoder }); +process.env.PAT_1 = "dummyPAT1"; +process.env.PAT_2 = "dummyPAT2"; diff --git a/docs/deploy.md b/docs/deploy.md index 866f69bf..968fa148 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -62,6 +62,9 @@ Click on the deploy button to get started! ![](https://files.catbox.moe/0yclio.png) 11. As `Root directory` select the `backend` folder. 12. Click deploy, and you're good to go. See your domains to use the API! +13. optional: add an SQL database; by using e.g. the ["Nile" integration](https://vercel.com/marketplace/nile) or by manually setting the environment variable `POSTGRES_URL` +14. optional: [create your own OAuth App](https://github.com/settings/developers) and set environment variables `OAUTH_REDIRECT_URI`, `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` on Vercel accordingly +15. optional: in addition to the Vercel project based on the `backend` folder, create a second project based on the `frontend/frontend` folder. No environment variables needed. diff --git a/vercel-preparation.sh b/vercel-preparation.sh index bd6fbde9..cadc1365 100755 --- a/vercel-preparation.sh +++ b/vercel-preparation.sh @@ -1,7 +1,15 @@ -# move into the folder of this script -cd "$(dirname "$0")" || exit 1 +#!/bin/bash -mkdir --parents backend/.vercel/output/functions/api.func/ -cp --recursive backend/* backend/.vercel/output/functions/api.func/ -cp --recursive backend/.vercel/output/functions/api.func/_dot_vercel_copy/output/ backend/.vercel/ -cp --recursive backend frontend/frontend/src/backend/ \ No newline at end of file +# defensive error handling +set -euo pipefail + +# move into the folder of this script +cd "$(dirname "$0")" + +mkdir -p backend/.vercel/output/functions/api.func/ +# copying `backend` to `backend/.vercel/...` directly may cause problems +cp -R backend/. backend-copy/ +# `shopt` includes dot-files in the `mv` operation +(shopt -s dotglob && mv backend-copy/* backend/.vercel/output/functions/api.func/) +cp -R backend/.vercel/output/functions/api.func/_dot_vercel_copy/output backend/.vercel/ +cp -R backend frontend/frontend/src/backend/