11/**
2- * Tests for createAuthClient
2+ * Tests for createAuthClient (backed by official better-auth client)
33 */
44
55import { describe , it , expect , vi , beforeEach } from 'vitest' ;
66import { createAuthClient } from '../createAuthClient' ;
77import type { AuthClient } from '../types' ;
88
9- describe ( 'createAuthClient' , ( ) => {
10- let client : AuthClient ;
11- let mockFetch : ReturnType < typeof vi . fn > ;
12-
13- beforeEach ( ( ) => {
14- mockFetch = vi . fn ( ) ;
15- client = createAuthClient ( { baseURL : '/api/auth' , fetchFn : mockFetch } ) ;
9+ /**
10+ * Helper: creates a mock fetch that routes requests based on URL
11+ * and records every call for inspection.
12+ */
13+ function createMockFetch ( handlers : Record < string , { status ?: number ; body : unknown } > ) {
14+ const calls : Array < { url : string ; method : string ; body : string | null } > = [ ] ;
15+ const mockFn = vi . fn ( async ( input : string | URL | Request , init ?: RequestInit ) => {
16+ let url : string ;
17+ if ( typeof input === 'string' ) {
18+ url = input ;
19+ } else if ( input instanceof URL ) {
20+ url = input . toString ( ) ;
21+ } else {
22+ url = input . url ;
23+ }
24+ calls . push ( { url, method : init ?. method ?? 'GET' , body : init ?. body as string | null } ) ;
25+ for ( const [ pattern , handler ] of Object . entries ( handlers ) ) {
26+ if ( url . includes ( pattern ) ) {
27+ return new Response ( JSON . stringify ( handler . body ) , {
28+ status : handler . status ?? 200 ,
29+ headers : { 'Content-Type' : 'application/json' } ,
30+ } ) ;
31+ }
32+ }
33+ return new Response ( JSON . stringify ( { message : 'Not found' } ) , {
34+ status : 404 ,
35+ headers : { 'Content-Type' : 'application/json' } ,
36+ } ) ;
1637 } ) ;
38+ return { mockFn, calls } ;
39+ }
1740
41+ describe ( 'createAuthClient' , ( ) => {
1842 it ( 'creates a client with all expected methods' , ( ) => {
43+ const { mockFn } = createMockFetch ( { } ) ;
44+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
1945 expect ( client ) . toHaveProperty ( 'signIn' ) ;
2046 expect ( client ) . toHaveProperty ( 'signUp' ) ;
2147 expect ( client ) . toHaveProperty ( 'signOut' ) ;
@@ -26,156 +52,150 @@ describe('createAuthClient', () => {
2652 } ) ;
2753
2854 it ( 'signIn sends POST to /sign-in/email' , async ( ) => {
29- const mockResponse = {
30- user : { id : '1' , name : 'Test' , email : 'test@test.com' } ,
31- session : { token : 'tok123' } ,
32- } ;
33- mockFetch . mockResolvedValueOnce ( {
34- ok : true ,
35- json : ( ) => Promise . resolve ( mockResponse ) ,
55+ const { mockFn , calls } = createMockFetch ( {
56+ '/sign-in/email' : {
57+ body : {
58+ user : { id : '1' , name : 'Test' , email : 'test@test.com' } ,
59+ session : { token : 'tok123' , id : 's1' , userId : '1' , expiresAt : '2025-01-01' } ,
60+ } ,
61+ } ,
3662 } ) ;
63+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
3764
3865 const result = await client . signIn ( { email : 'test@test.com' , password : 'pass123' } ) ;
3966
40- expect ( mockFetch ) . toHaveBeenCalledWith (
41- '/api/auth/sign-in/email' ,
42- expect . objectContaining ( {
43- method : 'POST' ,
44- credentials : 'include' ,
45- body : JSON . stringify ( { email : 'test@test.com' , password : 'pass123' } ) ,
46- } ) ,
47- ) ;
67+ expect ( calls ) . toHaveLength ( 1 ) ;
68+ expect ( calls [ 0 ] . url ) . toContain ( '/api/auth/sign-in/email' ) ;
69+ expect ( calls [ 0 ] . method ) . toBe ( 'POST' ) ;
70+ expect ( JSON . parse ( calls [ 0 ] . body ! ) ) . toMatchObject ( { email : 'test@test.com' , password : 'pass123' } ) ;
4871 expect ( result . user . email ) . toBe ( 'test@test.com' ) ;
4972 expect ( result . session . token ) . toBe ( 'tok123' ) ;
5073 } ) ;
5174
5275 it ( 'signUp sends POST to /sign-up/email' , async ( ) => {
53- const mockResponse = {
54- user : { id : '2' , name : 'New User' , email : 'new@test.com' } ,
55- session : { token : 'tok456' } ,
56- } ;
57- mockFetch . mockResolvedValueOnce ( {
58- ok : true ,
59- json : ( ) => Promise . resolve ( mockResponse ) ,
76+ const { mockFn , calls } = createMockFetch ( {
77+ '/sign-up/email' : {
78+ body : {
79+ user : { id : '2' , name : 'New User' , email : 'new@test.com' } ,
80+ session : { token : 'tok456' , id : 's2' , userId : '2' , expiresAt : '2025-01-01' } ,
81+ } ,
82+ } ,
6083 } ) ;
84+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
6185
6286 const result = await client . signUp ( { name : 'New User' , email : 'new@test.com' , password : 'pass123' } ) ;
6387
64- expect ( mockFetch ) . toHaveBeenCalledWith (
65- '/api/auth/sign-up/email' ,
66- expect . objectContaining ( { method : 'POST' } ) ,
67- ) ;
88+ expect ( calls ) . toHaveLength ( 1 ) ;
89+ expect ( calls [ 0 ] . url ) . toContain ( '/api/auth/sign-up/email' ) ;
90+ expect ( calls [ 0 ] . method ) . toBe ( 'POST' ) ;
91+ expect ( JSON . parse ( calls [ 0 ] . body ! ) ) . toMatchObject ( { email : 'new@test.com' , name : 'New User' } ) ;
6892 expect ( result . user . name ) . toBe ( 'New User' ) ;
6993 } ) ;
7094
7195 it ( 'signOut sends POST to /sign-out' , async ( ) => {
72- mockFetch . mockResolvedValueOnce ( {
73- ok : true ,
74- json : ( ) => Promise . resolve ( { } ) ,
96+ const { mockFn, calls } = createMockFetch ( {
97+ '/sign-out' : { body : { success : true } } ,
7598 } ) ;
99+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
76100
77101 await client . signOut ( ) ;
78102
79- expect ( mockFetch ) . toHaveBeenCalledWith (
80- '/api/auth/sign-out' ,
81- expect . objectContaining ( { method : 'POST' } ) ,
82- ) ;
103+ expect ( calls ) . toHaveLength ( 1 ) ;
104+ expect ( calls [ 0 ] . url ) . toContain ( '/api/auth/sign-out' ) ;
105+ expect ( calls [ 0 ] . method ) . toBe ( 'POST' ) ;
83106 } ) ;
84107
85108 it ( 'getSession sends GET to /get-session' , async ( ) => {
86- const mockSession = {
87- user : { id : '1' , name : 'Test' , email : 'test@test.com' } ,
88- session : { token : 'tok789' } ,
89- } ;
90- mockFetch . mockResolvedValueOnce ( {
91- ok : true ,
92- json : ( ) => Promise . resolve ( mockSession ) ,
109+ const { mockFn , calls } = createMockFetch ( {
110+ '/get-session' : {
111+ body : {
112+ user : { id : '1' , name : 'Test' , email : 'test@test.com' } ,
113+ session : { token : 'tok789' , id : 's1' , userId : '1' , expiresAt : '2025-01-01' } ,
114+ } ,
115+ } ,
93116 } ) ;
117+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
94118
95119 const result = await client . getSession ( ) ;
96120
97- expect ( mockFetch ) . toHaveBeenCalledWith (
98- '/api/auth/get-session' ,
99- expect . objectContaining ( { method : 'GET' } ) ,
100- ) ;
121+ expect ( calls ) . toHaveLength ( 1 ) ;
122+ expect ( calls [ 0 ] . url ) . toContain ( '/api/auth/get-session' ) ;
123+ expect ( calls [ 0 ] . method ) . toBe ( 'GET' ) ;
101124 expect ( result ?. user . id ) . toBe ( '1' ) ;
102125 } ) ;
103126
104127 it ( 'getSession returns null on failure' , async ( ) => {
105- mockFetch . mockRejectedValueOnce ( new Error ( 'Network error' ) ) ;
128+ const { mockFn } = createMockFetch ( {
129+ '/get-session' : { status : 401 , body : { message : 'Unauthorized' } } ,
130+ } ) ;
131+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
106132
107133 const result = await client . getSession ( ) ;
108134 expect ( result ) . toBeNull ( ) ;
109135 } ) ;
110136
111- it ( 'forgotPassword sends POST to /forgot-password' , async ( ) => {
112- mockFetch . mockResolvedValueOnce ( {
113- ok : true ,
114- json : ( ) => Promise . resolve ( { } ) ,
137+ it ( 'forgotPassword sends POST to /forget-password' , async ( ) => {
138+ const { mockFn, calls } = createMockFetch ( {
139+ '/forget-password' : { body : { status : true } } ,
115140 } ) ;
141+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
116142
117143 await client . forgotPassword ( 'test@test.com' ) ;
118144
119- expect ( mockFetch ) . toHaveBeenCalledWith (
120- '/api/auth/forgot-password' ,
121- expect . objectContaining ( {
122- method : 'POST' ,
123- body : JSON . stringify ( { email : 'test@test.com' } ) ,
124- } ) ,
125- ) ;
145+ expect ( calls ) . toHaveLength ( 1 ) ;
146+ expect ( calls [ 0 ] . url ) . toContain ( '/api/auth/forget-password' ) ;
147+ expect ( calls [ 0 ] . method ) . toBe ( 'POST' ) ;
148+ expect ( JSON . parse ( calls [ 0 ] . body ! ) ) . toMatchObject ( { email : 'test@test.com' } ) ;
126149 } ) ;
127150
128151 it ( 'resetPassword sends POST to /reset-password' , async ( ) => {
129- mockFetch . mockResolvedValueOnce ( {
130- ok : true ,
131- json : ( ) => Promise . resolve ( { } ) ,
152+ const { mockFn, calls } = createMockFetch ( {
153+ '/reset-password' : { body : { status : true } } ,
132154 } ) ;
155+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
133156
134157 await client . resetPassword ( 'token123' , 'newpass' ) ;
135158
136- expect ( mockFetch ) . toHaveBeenCalledWith (
137- '/api/auth/reset-password' ,
138- expect . objectContaining ( {
139- method : 'POST' ,
140- body : JSON . stringify ( { token : 'token123' , newPassword : 'newpass' } ) ,
141- } ) ,
142- ) ;
159+ expect ( calls ) . toHaveLength ( 1 ) ;
160+ expect ( calls [ 0 ] . url ) . toContain ( '/api/auth/reset-password' ) ;
161+ expect ( calls [ 0 ] . method ) . toBe ( 'POST' ) ;
162+ expect ( JSON . parse ( calls [ 0 ] . body ! ) ) . toMatchObject ( { token : 'token123' , newPassword : 'newpass' } ) ;
143163 } ) ;
144164
145165 it ( 'throws error with server message on non-OK response' , async ( ) => {
146- mockFetch . mockResolvedValueOnce ( {
147- ok : false ,
148- status : 401 ,
149- json : ( ) => Promise . resolve ( { message : 'Invalid credentials' } ) ,
166+ const { mockFn } = createMockFetch ( {
167+ '/sign-in/email' : {
168+ status : 401 ,
169+ body : { message : 'Invalid credentials' , code : 'INVALID_CREDENTIALS' } ,
170+ } ,
150171 } ) ;
172+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
151173
152174 await expect ( client . signIn ( { email : 'x' , password : 'y' } ) ) . rejects . toThrow ( 'Invalid credentials' ) ;
153175 } ) ;
154176
155- it ( 'throws generic error when response has no message' , async ( ) => {
156- mockFetch . mockResolvedValueOnce ( {
157- ok : false ,
158- status : 500 ,
159- json : ( ) => Promise . reject ( new Error ( 'parse error' ) ) ,
177+ it ( 'throws error on non-OK response without message' , async ( ) => {
178+ const { mockFn } = createMockFetch ( {
179+ '/sign-in/email' : { status : 500 , body : { } } ,
160180 } ) ;
181+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
161182
162- await expect ( client . signIn ( { email : 'x' , password : 'y' } ) ) . rejects . toThrow (
163- 'Auth request failed with status 500' ,
164- ) ;
183+ await expect ( client . signIn ( { email : 'x' , password : 'y' } ) ) . rejects . toThrow ( ) ;
165184 } ) ;
166185
167186 it ( 'updateUser sends POST to /update-user and returns user' , async ( ) => {
168- mockFetch . mockResolvedValueOnce ( {
169- ok : true ,
170- json : ( ) => Promise . resolve ( { user : { id : '1' , name : 'Updated' , email : 'test@test.com' } } ) ,
187+ const { mockFn, calls } = createMockFetch ( {
188+ '/update-user' : {
189+ body : { user : { id : '1' , name : 'Updated' , email : 'test@test.com' } } ,
190+ } ,
171191 } ) ;
192+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
172193
173194 const result = await client . updateUser ( { name : 'Updated' } ) ;
174195
196+ expect ( calls ) . toHaveLength ( 1 ) ;
197+ expect ( calls [ 0 ] . url ) . toContain ( '/api/auth/update-user' ) ;
198+ expect ( calls [ 0 ] . method ) . toBe ( 'POST' ) ;
175199 expect ( result . name ) . toBe ( 'Updated' ) ;
176- expect ( mockFetch ) . toHaveBeenCalledWith (
177- '/api/auth/update-user' ,
178- expect . objectContaining ( { method : 'POST' } ) ,
179- ) ;
180200 } ) ;
181201} ) ;
0 commit comments