@@ -10,6 +10,17 @@ import { cookies } from 'next/headers'
1010
1111import type { Database } from './types'
1212
13+ /**
14+ * Check if E2E test mode is enabled
15+ * When true, auth checks return mock data to bypass OAuth
16+ *
17+ * NOTE: Uses APP_ENV (server-side) instead of NEXT_PUBLIC_ENABLE_MSW_MOCK
18+ * because NEXT_PUBLIC_* vars are inlined at build time and may not be
19+ * available at runtime on the server.
20+ */
21+ const isE2ETestMode = ( ) =>
22+ process . env . APP_ENV === 'test' || process . env . NODE_ENV === 'test'
23+
1324/**
1425 * Create Supabase client for use in Server Components
1526 *
@@ -27,7 +38,7 @@ import type { Database } from './types'
2738export async function createClient ( ) {
2839 const cookieStore = await cookies ( )
2940
30- return createServerClient < Database > (
41+ const supabase = createServerClient < Database > (
3142 process . env . NEXT_PUBLIC_SUPABASE_URL ! ,
3243 process . env . NEXT_PUBLIC_SUPABASE_ANON_KEY ! ,
3344 {
@@ -53,6 +64,59 @@ export async function createClient() {
5364 } ,
5465 } ,
5566 )
67+
68+ // In E2E test mode, wrap auth methods to return mock data
69+ const testMode = isE2ETestMode ( )
70+ console . log (
71+ '[createClient] APP_ENV:' ,
72+ process . env . APP_ENV ,
73+ 'NODE_ENV:' ,
74+ process . env . NODE_ENV ,
75+ 'isE2ETestMode:' ,
76+ testMode ,
77+ )
78+ if ( testMode ) {
79+ // Use a Proxy to intercept auth methods while preserving all other client functionality
80+ const mockedAuth = {
81+ getUser : async ( ) => ( {
82+ data : { user : MOCK_USER_FOR_E2E } ,
83+ error : null ,
84+ } ) ,
85+ getSession : async ( ) => ( {
86+ data : {
87+ session : {
88+ access_token : 'mock-access-token-for-testing' ,
89+ token_type : 'bearer' ,
90+ expires_in : 3600 ,
91+ expires_at : Math . floor ( Date . now ( ) / 1000 ) + 3600 ,
92+ refresh_token : 'mock-refresh-token-for-testing' ,
93+ user : MOCK_USER_FOR_E2E ,
94+ } ,
95+ } ,
96+ error : null ,
97+ } ) ,
98+ }
99+
100+ return new Proxy ( supabase , {
101+ get ( target , prop ) {
102+ if ( prop === 'auth' ) {
103+ // Return a proxy for auth that intercepts getUser/getSession
104+ return new Proxy ( target . auth , {
105+ get ( authTarget , authProp ) {
106+ if ( authProp === 'getUser' ) return mockedAuth . getUser
107+ if ( authProp === 'getSession' ) return mockedAuth . getSession
108+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
109+ return ( authTarget as any ) [ authProp ]
110+ } ,
111+ } )
112+ }
113+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114+ return ( target as any ) [ prop ]
115+ } ,
116+ } ) as typeof supabase
117+ }
118+
119+ return supabase
56120}
57121
58122/**
@@ -136,10 +200,47 @@ export async function createServerActionClient() {
136200 )
137201}
138202
203+ /**
204+ * Mock user for E2E testing when MSW is enabled
205+ */
206+ const MOCK_USER_FOR_E2E = {
207+ id : 'test-user-id-12345' ,
208+ aud : 'authenticated' ,
209+ role : 'authenticated' ,
210+ email : 'test@example.com' ,
211+ email_confirmed_at : new Date ( ) . toISOString ( ) ,
212+ phone : '' ,
213+ confirmed_at : new Date ( ) . toISOString ( ) ,
214+ last_sign_in_at : new Date ( ) . toISOString ( ) ,
215+ app_metadata : { provider : 'github' , providers : [ 'github' ] } ,
216+ user_metadata : {
217+ avatar_url : 'https://avatars.githubusercontent.com/u/12345' ,
218+ full_name : 'Test User' ,
219+ preferred_username : 'testuser' ,
220+ user_name : 'testuser' ,
221+ } ,
222+ identities : [ ] ,
223+ created_at : new Date ( ) . toISOString ( ) ,
224+ updated_at : new Date ( ) . toISOString ( ) ,
225+ is_anonymous : false ,
226+ } as const
227+
139228/**
140229 * Get current user (server-side)
230+ *
231+ * In E2E test mode (NEXT_PUBLIC_ENABLE_MSW_MOCK=true + APP_ENV=test),
232+ * returns a mock user to bypass OAuth flow.
141233 */
142234export async function getUser ( ) {
235+ // E2E test mode: return mock user to bypass OAuth
236+ const isMSWEnabled =
237+ process . env . NEXT_PUBLIC_ENABLE_MSW_MOCK === 'true' &&
238+ ( process . env . APP_ENV === 'test' || process . env . NODE_ENV === 'test' )
239+
240+ if ( isMSWEnabled ) {
241+ return MOCK_USER_FOR_E2E
242+ }
243+
143244 const supabase = await createClient ( )
144245 const {
145246 data : { user } ,
@@ -156,8 +257,27 @@ export async function getUser() {
156257
157258/**
158259 * Get current session (server-side)
260+ *
261+ * In E2E test mode (NEXT_PUBLIC_ENABLE_MSW_MOCK=true + APP_ENV=test),
262+ * returns a mock session to bypass OAuth flow.
159263 */
160264export async function getSession ( ) {
265+ // E2E test mode: return mock session to bypass OAuth
266+ const isMSWEnabled =
267+ process . env . NEXT_PUBLIC_ENABLE_MSW_MOCK === 'true' &&
268+ ( process . env . APP_ENV === 'test' || process . env . NODE_ENV === 'test' )
269+
270+ if ( isMSWEnabled ) {
271+ return {
272+ access_token : 'mock-access-token-for-testing' ,
273+ token_type : 'bearer' ,
274+ expires_in : 3600 ,
275+ expires_at : Math . floor ( Date . now ( ) / 1000 ) + 3600 ,
276+ refresh_token : 'mock-refresh-token-for-testing' ,
277+ user : MOCK_USER_FOR_E2E ,
278+ }
279+ }
280+
161281 const supabase = await createClient ( )
162282 const {
163283 data : { session } ,
0 commit comments