File
Blob: tests/client/sign-in-oauth.test.ts
| 1 | import { describe, expect, it } from "vitest"; |
| 2 | |
| 3 | import { |
| 4 | shouldRedirectSignedInFromSignIn, |
| 5 | signedOAuthQueryFromParams, |
| 6 | socialErrorCallbackURL, |
| 7 | } from "@/client/lib/sign-in-oauth"; |
| 8 | |
| 9 | describe("signedOAuthQueryFromParams", () => { |
| 10 | it("returns null when no signed query is present", () => { |
| 11 | expect(signedOAuthQueryFromParams(new URLSearchParams(""))).toBe(null); |
| 12 | expect(signedOAuthQueryFromParams(new URLSearchParams("foo=bar"))).toBe(null); |
| 13 | }); |
| 14 | |
| 15 | it("returns null when only client_id is present (no sig)", () => { |
| 16 | expect(signedOAuthQueryFromParams(new URLSearchParams("client_id=A"))).toBe(null); |
| 17 | }); |
| 18 | |
| 19 | it("returns null when only sig is present (no client_id)", () => { |
| 20 | expect(signedOAuthQueryFromParams(new URLSearchParams("sig=Z"))).toBe(null); |
| 21 | }); |
| 22 | |
| 23 | it("preserves the signed sequence through sig", () => { |
| 24 | const params = new URLSearchParams("client_id=A&response_type=code&state=S&sig=Z"); |
| 25 | expect(signedOAuthQueryFromParams(params)).toBe("client_id=A&response_type=code&state=S&sig=Z"); |
| 26 | }); |
| 27 | |
| 28 | it("skips a leading local error param while preserving signed order", () => { |
| 29 | const params = new URLSearchParams("error=social_unavailable&client_id=A&state=S&sig=Z"); |
| 30 | expect(signedOAuthQueryFromParams(params)).toBe("client_id=A&state=S&sig=Z"); |
| 31 | }); |
| 32 | |
| 33 | it("skips a leading local redirect param while preserving signed order", () => { |
| 34 | const params = new URLSearchParams("redirect=%2Faccount&client_id=A&sig=Z"); |
| 35 | expect(signedOAuthQueryFromParams(params)).toBe("client_id=A&sig=Z"); |
| 36 | }); |
| 37 | |
| 38 | it("ignores params after sig (stops at the first sig)", () => { |
| 39 | const params = new URLSearchParams("client_id=A&sig=Z&error=social_unavailable"); |
| 40 | expect(signedOAuthQueryFromParams(params)).toBe("client_id=A&sig=Z"); |
| 41 | }); |
| 42 | |
| 43 | it("preserves repeated keys in their original order", () => { |
| 44 | const params = new URLSearchParams("scope=openid&scope=email&client_id=A&sig=Z"); |
| 45 | expect(signedOAuthQueryFromParams(params)).toBe("scope=openid&scope=email&client_id=A&sig=Z"); |
| 46 | }); |
| 47 | }); |
| 48 | |
| 49 | describe("socialErrorCallbackURL", () => { |
| 50 | it("returns the bare error URL when no signed query is present", () => { |
| 51 | expect(socialErrorCallbackURL(null)).toBe("/sign-in?error=social_unavailable"); |
| 52 | }); |
| 53 | |
| 54 | it("places signed params first and the local error after sig", () => { |
| 55 | expect(socialErrorCallbackURL("client_id=A&sig=Z")).toBe("/sign-in?client_id=A&sig=Z&error=social_unavailable"); |
| 56 | }); |
| 57 | |
| 58 | it("survives a round trip through signedOAuthQueryFromParams", () => { |
| 59 | const signed = "client_id=A&response_type=code&state=S&sig=Z"; |
| 60 | const url = new URL(socialErrorCallbackURL(signed), "https://placeholder.invalid"); |
| 61 | expect(signedOAuthQueryFromParams(url.searchParams)).toBe(signed); |
| 62 | }); |
| 63 | }); |
| 64 | |
| 65 | describe("shouldRedirectSignedInFromSignIn", () => { |
| 66 | it("returns false while the session is pending", () => { |
| 67 | expect( |
| 68 | shouldRedirectSignedInFromSignIn({ sessionPending: true, signedInUserId: "u1", signedOAuthQuery: null }), |
| 69 | ).toBe(false); |
| 70 | expect( |
| 71 | shouldRedirectSignedInFromSignIn({ sessionPending: true, signedInUserId: null, signedOAuthQuery: null }), |
| 72 | ).toBe(false); |
| 73 | expect( |
| 74 | shouldRedirectSignedInFromSignIn({ |
| 75 | sessionPending: true, |
| 76 | signedInUserId: "u1", |
| 77 | signedOAuthQuery: "client_id=A&sig=Z", |
| 78 | }), |
| 79 | ).toBe(false); |
| 80 | }); |
| 81 | |
| 82 | it("returns false when the user is signed out", () => { |
| 83 | expect( |
| 84 | shouldRedirectSignedInFromSignIn({ sessionPending: false, signedInUserId: null, signedOAuthQuery: null }), |
| 85 | ).toBe(false); |
| 86 | }); |
| 87 | |
| 88 | it("returns true when a signed-in user has no signed query", () => { |
| 89 | expect( |
| 90 | shouldRedirectSignedInFromSignIn({ sessionPending: false, signedInUserId: "u1", signedOAuthQuery: null }), |
| 91 | ).toBe(true); |
| 92 | }); |
| 93 | |
| 94 | it("returns false when a signed-in user has a signed query (prompt=login path)", () => { |
| 95 | expect( |
| 96 | shouldRedirectSignedInFromSignIn({ |
| 97 | sessionPending: false, |
| 98 | signedInUserId: "u1", |
| 99 | signedOAuthQuery: "client_id=A&sig=Z", |
| 100 | }), |
| 101 | ).toBe(false); |
| 102 | }); |
| 103 | }); |