CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
google-oauth-env.test.ts
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 27d5fd3 | 1 | import { describe, expect, test } from "bun:test"; |
| 9887dd0 | 2 | import { |
| 3 | googleOauthConfigFromEnv, | |
| 4 | resolveGoogleOauthConfig, | |
| 5 | } from "../lib/sso"; | |
| 6 | import type { SsoConfig } from "../db/schema"; | |
| 7 | ||
| 8 | /** Minimal SsoConfig row builder for precedence tests. */ | |
| 9 | function row(overrides: Partial<SsoConfig>): SsoConfig { | |
| 10 | const now = new Date(); | |
| 11 | return { | |
| 12 | id: "google", | |
| 13 | enabled: false, | |
| 14 | providerName: "Google", | |
| 15 | issuer: "https://accounts.google.com", | |
| 16 | authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth", | |
| 17 | tokenEndpoint: "https://oauth2.googleapis.com/token", | |
| 18 | userinfoEndpoint: "https://openidconnect.googleapis.com/v1/userinfo", | |
| 19 | clientId: null, | |
| 20 | clientSecret: null, | |
| 21 | scopes: "openid email profile", | |
| 22 | allowedEmailDomains: null, | |
| 23 | autoCreateUsers: true, | |
| 24 | createdAt: now, | |
| 25 | updatedAt: now, | |
| 26 | ...overrides, | |
| 27 | } as SsoConfig; | |
| 28 | } | |
| 29 | ||
| 30 | const ENV_CFG = googleOauthConfigFromEnv({ | |
| 31 | GOOGLE_OAUTH_CLIENT_ID: "env-id.apps.googleusercontent.com", | |
| 32 | GOOGLE_OAUTH_CLIENT_SECRET: "env-secret", | |
| 33 | }); | |
| 27d5fd3 | 34 | |
| 35 | describe("googleOauthConfigFromEnv", () => { | |
| 36 | test("returns null when credentials are absent", () => { | |
| 37 | expect(googleOauthConfigFromEnv({})).toBeNull(); | |
| 38 | expect( | |
| 39 | googleOauthConfigFromEnv({ GOOGLE_OAUTH_CLIENT_ID: "id-only" }) | |
| 40 | ).toBeNull(); | |
| 41 | expect( | |
| 42 | googleOauthConfigFromEnv({ GOOGLE_OAUTH_CLIENT_SECRET: "secret-only" }) | |
| 43 | ).toBeNull(); | |
| 44 | expect( | |
| 45 | googleOauthConfigFromEnv({ | |
| 46 | GOOGLE_OAUTH_CLIENT_ID: " ", | |
| 47 | GOOGLE_OAUTH_CLIENT_SECRET: "s", | |
| 48 | }) | |
| 49 | ).toBeNull(); | |
| 50 | }); | |
| 51 | ||
| 52 | test("builds an enabled config from the env pair", () => { | |
| 53 | const cfg = googleOauthConfigFromEnv({ | |
| 54 | GOOGLE_OAUTH_CLIENT_ID: "abc.apps.googleusercontent.com", | |
| 55 | GOOGLE_OAUTH_CLIENT_SECRET: "shh", | |
| 56 | }); | |
| 57 | expect(cfg).not.toBeNull(); | |
| 58 | expect(cfg!.enabled).toBe(true); | |
| 59 | expect(cfg!.id).toBe("google"); | |
| 60 | expect(cfg!.clientId).toBe("abc.apps.googleusercontent.com"); | |
| 61 | expect(cfg!.clientSecret).toBe("shh"); | |
| 62 | expect(cfg!.authorizationEndpoint).toBe( | |
| 63 | "https://accounts.google.com/o/oauth2/v2/auth" | |
| 64 | ); | |
| 65 | expect(cfg!.tokenEndpoint).toBe("https://oauth2.googleapis.com/token"); | |
| 66 | expect(cfg!.userinfoEndpoint).toBe( | |
| 67 | "https://openidconnect.googleapis.com/v1/userinfo" | |
| 68 | ); | |
| 69 | expect(cfg!.scopes).toBe("openid email profile"); | |
| 70 | expect(cfg!.autoCreateUsers).toBe(true); | |
| 71 | expect(cfg!.allowedEmailDomains).toBeNull(); | |
| 72 | }); | |
| 73 | ||
| 74 | test("honours optional knobs", () => { | |
| 75 | const cfg = googleOauthConfigFromEnv({ | |
| 76 | GOOGLE_OAUTH_CLIENT_ID: "id", | |
| 77 | GOOGLE_OAUTH_CLIENT_SECRET: "secret", | |
| 78 | GOOGLE_OAUTH_AUTO_CREATE: "0", | |
| 79 | GOOGLE_OAUTH_ALLOWED_DOMAINS: "example.com,corp.example.com", | |
| 80 | }); | |
| 81 | expect(cfg!.autoCreateUsers).toBe(false); | |
| 82 | expect(cfg!.allowedEmailDomains).toBe("example.com,corp.example.com"); | |
| 83 | }); | |
| 84 | }); | |
| 9887dd0 | 85 | |
| 86 | describe("resolveGoogleOauthConfig — precedence", () => { | |
| 87 | test("an enabled, fully-credentialed admin row wins over env", () => { | |
| 88 | const dbRow = row({ | |
| 89 | enabled: true, | |
| 90 | clientId: "db-id", | |
| 91 | clientSecret: "db-secret", | |
| 92 | }); | |
| 93 | const live = resolveGoogleOauthConfig(dbRow, ENV_CFG); | |
| 94 | expect(live).toBe(dbRow); | |
| 95 | expect(live!.clientId).toBe("db-id"); | |
| 96 | }); | |
| 97 | ||
| 98 | test("a DISABLED credentialed row does NOT shadow the env bootstrap", () => { | |
| 99 | // Regression: a half-finished /admin/google-oauth save (creds entered, | |
| 100 | // Enable left off) used to suppress a working GOOGLE_OAUTH_* bootstrap, | |
| 101 | // leaving "Sign in with Google" dark with a misleading "not enabled". | |
| 102 | const dbRow = row({ | |
| 103 | enabled: false, | |
| 104 | clientId: "db-id", | |
| 105 | clientSecret: "db-secret", | |
| 106 | }); | |
| 107 | const live = resolveGoogleOauthConfig(dbRow, ENV_CFG); | |
| 108 | expect(live).toBe(ENV_CFG); | |
| 109 | expect(live!.enabled).toBe(true); | |
| 110 | }); | |
| 111 | ||
| 112 | test("a credential-less row never shadows the env bootstrap", () => { | |
| 113 | const live = resolveGoogleOauthConfig(row({ enabled: true }), ENV_CFG); | |
| 114 | expect(live).toBe(ENV_CFG); | |
| 115 | }); | |
| 116 | ||
| 117 | test("env bootstrap alone (no DB row) is live", () => { | |
| 118 | expect(resolveGoogleOauthConfig(null, ENV_CFG)).toBe(ENV_CFG); | |
| 119 | }); | |
| 120 | ||
| 121 | test("with no env, a disabled row is returned as-is (caller gates on it)", () => { | |
| 122 | const dbRow = row({ | |
| 123 | enabled: false, | |
| 124 | clientId: "db-id", | |
| 125 | clientSecret: "db-secret", | |
| 126 | }); | |
| 127 | expect(resolveGoogleOauthConfig(dbRow, null)).toBe(dbRow); | |
| 128 | }); | |
| 129 | ||
| 130 | test("nothing configured anywhere resolves to null", () => { | |
| 131 | expect(resolveGoogleOauthConfig(null, null)).toBeNull(); | |
| 132 | }); | |
| 133 | }); |