diff --git a/.github/workflows/setup-tests-with-custom-base-port.yaml b/.github/workflows/setup-tests-with-custom-base-port.yaml index 95a04f289..846da8e8f 100644 --- a/.github/workflows/setup-tests-with-custom-base-port.yaml +++ b/.github/workflows/setup-tests-with-custom-base-port.yaml @@ -48,5 +48,12 @@ jobs: tail: true wait-for: 120s log-output-if: true - - name: Run tests + - name: Run tests (first attempt) + id: run-tests-first-attempt + continue-on-error: ${{ (github.head_ref || github.ref_name) != 'main' && (github.head_ref || github.ref_name) != 'dev' }} + run: pnpm run test run --reporter=verbose + + # These tests are often flakey, as a temporary measure we retry them once. + - name: Run tests (retry once on non-dev/main branches) + if: ${{ (github.head_ref || github.ref_name) != 'main' && (github.head_ref || github.ref_name) != 'dev' && steps.run-tests-first-attempt.outcome == 'failure' }} run: pnpm run test run --reporter=verbose diff --git a/.github/workflows/setup-tests.yaml b/.github/workflows/setup-tests.yaml index fe81c28ec..7abf3eadb 100644 --- a/.github/workflows/setup-tests.yaml +++ b/.github/workflows/setup-tests.yaml @@ -46,5 +46,12 @@ jobs: tail: true wait-for: 120s log-output-if: true - - name: Run tests + - name: Run tests (first attempt) + id: run-tests-first-attempt + continue-on-error: ${{ (github.head_ref || github.ref_name) != 'main' && (github.head_ref || github.ref_name) != 'dev' }} + run: pnpm run test run --reporter=verbose + + # These tests are often flakey, as a temporary measure we retry them once. + - name: Run tests (retry once on non-dev/main branches) + if: ${{ (github.head_ref || github.ref_name) != 'main' && (github.head_ref || github.ref_name) != 'dev' && steps.run-tests-first-attempt.outcome == 'failure' }} run: pnpm run test run --reporter=verbose diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/sign-up-rules.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/sign-up-rules.test.ts index c2c490161..bf727c078 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/auth/sign-up-rules.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/sign-up-rules.test.ts @@ -3,6 +3,39 @@ import { describe } from "vitest"; import { it } from "../../../../../helpers"; import { Auth, InternalApiKey, Project, backendContext, niceBackendFetch } from "../../../../backend-helpers"; +const expectOAuthSignUpRejectedRedirect = ( + expect: typeof import("vitest").expect, + response: { status: number, headers: Headers }, +) => { + expect(response.status).toBe(307); + + const location = response.headers.get("location"); + expect(location).toBeTruthy(); + if (location == null) { + throw new Error("OAuth callback rejection redirect location is missing"); + } + + const locationUrl = new URL(location); + expect(locationUrl.origin).toBe("http://stack-test.localhost"); + expect(locationUrl.pathname).toBe("/some-callback-url"); + expect(locationUrl.searchParams.get("error")).toBe("server_error"); + expect(locationUrl.searchParams.get("errorCode")).toBe("SIGN_UP_REJECTED"); + expect(locationUrl.searchParams.get("error_description")).toBe("Your sign up was rejected by an administrator's sign-up rule."); + expect(locationUrl.searchParams.get("message")).toBe("Your sign up was rejected by an administrator's sign-up rule."); + + const detailsRaw = locationUrl.searchParams.get("details"); + expect(detailsRaw).toBeTruthy(); + if (detailsRaw == null) { + throw new Error("OAuth callback rejection redirect details are missing"); + } + const details: unknown = JSON.parse(detailsRaw); + if (typeof details !== "object" || details == null || !("message" in details)) { + throw new Error("OAuth callback rejection redirect details are malformed"); + } + expect(details.message).toBe("Your sign up was rejected by an administrator's sign-up rule."); + expect(response.headers.get("set-cookie")).toMatch(/stack-oauth-inner-/); +}; + describe("sign-up rules", () => { // ========================================== // BASIC RULE BEHAVIOR @@ -1181,10 +1214,7 @@ describe("sign-up rules", () => { // OAuth signup should be rejected const { response } = await Auth.OAuth.getMaybeFailingAuthorizationCode(); - expect(response.status).toBe(403); - expect(response.body).toMatchObject({ - code: 'SIGN_UP_REJECTED', - }); + expectOAuthSignUpRejectedRedirect(expect, response); }); it("should match oauthProvider condition for specific OAuth provider", async ({ expect }) => { @@ -1211,10 +1241,7 @@ describe("sign-up rules", () => { // Spotify OAuth signup should be rejected const { response } = await Auth.OAuth.getMaybeFailingAuthorizationCode(); - expect(response.status).toBe(403); - expect(response.body).toMatchObject({ - code: 'SIGN_UP_REJECTED', - }); + expectOAuthSignUpRejectedRedirect(expect, response); }); it("should allow OAuth signup when rule blocks different provider", async ({ expect }) => { @@ -1460,10 +1487,7 @@ describe("sign-up rules", () => { }); const { response } = await Auth.OAuth.getMaybeFailingAuthorizationCode(); - expect(response.status).toBe(403); - expect(response.body).toMatchObject({ - code: 'SIGN_UP_REJECTED', - }); + expectOAuthSignUpRejectedRedirect(expect, response); }); // ==========================================