test: add unit tests for ProxyAgent integration with better-auth (#7400)

This commit is contained in:
Alex Yang
2026-01-15 10:26:01 -08:00
parent 3decd1c14f
commit 8a01720fb1
3 changed files with 245 additions and 2 deletions

5
pnpm-lock.yaml generated
View File

@@ -1586,6 +1586,9 @@ importers:
openid-client:
specifier: ^6.8.1
version: 6.8.1
undici:
specifier: ^7.0.0
version: 7.14.0
vitest:
specifier: catalog:vitest
version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@25.0.6)(happy-dom@20.0.11)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.6)(typescript@5.9.3))(sass@1.97.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
@@ -20658,9 +20661,7 @@ snapshots:
metro-runtime: 0.83.3
transitivePeerDependencies:
- '@babel/core'
- bufferutil
- supports-color
- utf-8-validate
optional: true
'@react-native/normalize-colors@0.74.89': {}

View File

@@ -14,6 +14,7 @@
"better-auth": "workspace:*",
"msw": "^2.12.4",
"openid-client": "^6.8.1",
"undici": "^7.0.0",
"vitest": "catalog:vitest"
}
}

View File

@@ -0,0 +1,241 @@
/**
* Unit Test: Verify undici ProxyAgent works with better-auth OAuth flows
*
* This test demonstrates that when you set a global dispatcher with ProxyAgent,
* all fetch() calls made by better-auth during OAuth flows are routed through it.
* This is critical for corporate environments where all outbound HTTP requests
* must go through a proxy server.
*
* Reference: https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md
* Related issue: https://github.com/better-auth/better-auth/issues/7396
*/
import type { GoogleProfile } from "@better-auth/core/social-providers";
import { signJWT } from "better-auth/crypto";
import { getTestInstance } from "better-auth/test";
import type { Dispatcher } from "undici";
import { getGlobalDispatcher, MockAgent, setGlobalDispatcher } from "undici";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
const DEFAULT_SECRET = "better-auth-secret-123456789";
describe("ProxyAgent with better-auth OAuth", () => {
let originalDispatcher: Dispatcher;
let mockAgent: MockAgent;
beforeEach(() => {
// Save original dispatcher
originalDispatcher = getGlobalDispatcher();
// Create a mock agent to simulate proxy behavior
mockAgent = new MockAgent();
mockAgent.disableNetConnect();
// Set mock agent as global dispatcher (simulating ProxyAgent in production)
setGlobalDispatcher(mockAgent);
});
afterEach(async () => {
// Restore the original dispatcher after each test
await mockAgent.close();
setGlobalDispatcher(originalDispatcher);
});
it("should route GitHub OAuth requests through global dispatcher", async () => {
// Mock the GitHub OAuth token endpoint
const githubPool = mockAgent.get("https://github.com");
githubPool
.intercept({
path: "/login/oauth/access_token",
method: "POST",
})
.reply(
200,
new URLSearchParams({
access_token: "test_github_access_token",
token_type: "bearer",
scope: "user:email",
}).toString(),
{
headers: { "content-type": "application/x-www-form-urlencoded" },
},
);
// Mock the GitHub user API endpoint
const githubApiPool = mockAgent.get("https://api.github.com");
githubApiPool
.intercept({
path: "/user",
method: "GET",
})
.reply(200, {
id: 12345,
login: "testuser",
email: "test@example.com",
name: "Test User",
avatar_url: "https://avatars.githubusercontent.com/u/12345",
});
// Mock the GitHub user emails API endpoint
githubApiPool
.intercept({
path: "/user/emails",
method: "GET",
})
.reply(200, [
{
email: "test@example.com",
primary: true,
verified: true,
visibility: "public",
},
]);
// Create better-auth instance with GitHub OAuth
const { client } = await getTestInstance({
secret: DEFAULT_SECRET,
socialProviders: {
github: {
clientId: "test_client_id",
clientSecret: "test_client_secret",
},
},
});
const headers = new Headers();
// Step 1: Initiate OAuth sign in
const signInRes = await client.signIn.social(
{
provider: "github",
callbackURL: "/callback",
},
{
onSuccess(context) {
const setCookie = context.response.headers.get("set-cookie");
if (setCookie) {
headers.set("cookie", setCookie);
}
},
},
);
expect(signInRes.data?.url).toContain("github.com");
expect(signInRes.data?.redirect).toBe(true);
const state = new URL(signInRes.data!.url!).searchParams.get("state") || "";
// Step 2: Complete OAuth callback - this will trigger requests through the proxy
await client.$fetch("/callback/github", {
query: {
state,
code: "test_authorization_code",
},
headers,
method: "GET",
onError(context) {
// OAuth callback redirects on success
expect(context.response.status).toBe(302);
},
});
// Verify that better-auth made requests through our global dispatcher (proxy)
// If this test passes, it means all OAuth requests went through setGlobalDispatcher
expect(signInRes.data?.url).toBeDefined();
});
it("should route Google OAuth requests through global dispatcher", async () => {
// Pre-generate the ID token for Google
const googleProfile: GoogleProfile = {
email: "user@example.com",
email_verified: true,
name: "Test User",
picture: "https://lh3.googleusercontent.com/a-/test",
exp: 1234567890,
sub: "1234567890",
iat: 1234567890,
aud: "test",
azp: "test",
nbf: 1234567890,
iss: "https://accounts.google.com",
locale: "en",
jti: "test",
given_name: "Test",
family_name: "User",
};
const testIdToken = await signJWT(googleProfile, DEFAULT_SECRET);
// Mock Google's OAuth token endpoint
const googlePool = mockAgent.get("https://oauth2.googleapis.com");
googlePool
.intercept({
path: "/token",
method: "POST",
})
.reply(
200,
{
access_token: "test_google_access_token",
refresh_token: "test_refresh_token",
id_token: testIdToken,
expires_in: 3600,
token_type: "Bearer",
},
{
headers: { "content-type": "application/json" },
},
);
// Create better-auth instance with Google OAuth
const { client } = await getTestInstance({
secret: DEFAULT_SECRET,
socialProviders: {
google: {
clientId: "test_client_id",
clientSecret: "test_client_secret",
},
},
});
const headers = new Headers();
// Initiate Google OAuth sign in
const signInRes = await client.signIn.social(
{
provider: "google",
callbackURL: "/dashboard",
},
{
onSuccess(context) {
const setCookie = context.response.headers.get("set-cookie");
if (setCookie) {
headers.set("cookie", setCookie);
}
},
},
);
expect(signInRes.data?.url).toContain("google.com");
expect(signInRes.data?.redirect).toBe(true);
const state = new URL(signInRes.data!.url!).searchParams.get("state") || "";
// Complete OAuth callback - this triggers token exchange through proxy
await client.$fetch("/callback/google", {
query: {
state,
code: "test_google_auth_code",
},
headers,
method: "GET",
onError(context) {
// OAuth callback redirects on success
expect(context.response.status).toBe(302);
const location = context.response.headers.get("location");
expect(location).toContain("/dashboard");
},
});
// If this test passes, Google OAuth token exchange went through the global dispatcher
expect(signInRes.data?.url).toBeDefined();
});
});