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+ const url = typeof input === 'string' ? input : input instanceof URL ? input . toString ( ) : input . url ;
17+ calls . push ( { url, method : init ?. method ?? 'GET' , body : init ?. body as string | null } ) ;
18+ for ( const [ pattern , handler ] of Object . entries ( handlers ) ) {
19+ if ( url . includes ( pattern ) ) {
20+ return new Response ( JSON . stringify ( handler . body ) , {
21+ status : handler . status ?? 200 ,
22+ headers : { 'Content-Type' : 'application/json' } ,
23+ } ) ;
24+ }
25+ }
26+ return new Response ( JSON . stringify ( { message : 'Not found' } ) , {
27+ status : 404 ,
28+ headers : { 'Content-Type' : 'application/json' } ,
29+ } ) ;
1630 } ) ;
31+ return { mockFn, calls } ;
32+ }
1733
34+ describe ( 'createAuthClient' , ( ) => {
1835 it ( 'creates a client with all expected methods' , ( ) => {
36+ const { mockFn } = createMockFetch ( { } ) ;
37+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
1938 expect ( client ) . toHaveProperty ( 'signIn' ) ;
2039 expect ( client ) . toHaveProperty ( 'signUp' ) ;
2140 expect ( client ) . toHaveProperty ( 'signOut' ) ;
@@ -26,156 +45,150 @@ describe('createAuthClient', () => {
2645 } ) ;
2746
2847 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 ) ,
48+ const { mockFn , calls } = createMockFetch ( {
49+ '/sign-in/email' : {
50+ body : {
51+ user : { id : '1' , name : 'Test' , email : 'test@test.com' } ,
52+ session : { token : 'tok123' , id : 's1' , userId : '1' , expiresAt : '2025-01-01' } ,
53+ } ,
54+ } ,
3655 } ) ;
56+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
3757
3858 const result = await client . signIn ( { email : 'test@test.com' , password : 'pass123' } ) ;
3959
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- ) ;
60+ expect ( calls ) . toHaveLength ( 1 ) ;
61+ expect ( calls [ 0 ] . url ) . toContain ( '/api/auth/sign-in/email' ) ;
62+ expect ( calls [ 0 ] . method ) . toBe ( 'POST' ) ;
63+ expect ( JSON . parse ( calls [ 0 ] . body ! ) ) . toMatchObject ( { email : 'test@test.com' , password : 'pass123' } ) ;
4864 expect ( result . user . email ) . toBe ( 'test@test.com' ) ;
4965 expect ( result . session . token ) . toBe ( 'tok123' ) ;
5066 } ) ;
5167
5268 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 ) ,
69+ const { mockFn , calls } = createMockFetch ( {
70+ '/sign-up/email' : {
71+ body : {
72+ user : { id : '2' , name : 'New User' , email : 'new@test.com' } ,
73+ session : { token : 'tok456' , id : 's2' , userId : '2' , expiresAt : '2025-01-01' } ,
74+ } ,
75+ } ,
6076 } ) ;
77+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
6178
6279 const result = await client . signUp ( { name : 'New User' , email : 'new@test.com' , password : 'pass123' } ) ;
6380
64- expect ( mockFetch ) . toHaveBeenCalledWith (
65- '/api/auth/sign-up/email' ,
66- expect . objectContaining ( { method : 'POST' } ) ,
67- ) ;
81+ expect ( calls ) . toHaveLength ( 1 ) ;
82+ expect ( calls [ 0 ] . url ) . toContain ( '/api/auth/sign-up/email' ) ;
83+ expect ( calls [ 0 ] . method ) . toBe ( 'POST' ) ;
84+ expect ( JSON . parse ( calls [ 0 ] . body ! ) ) . toMatchObject ( { email : 'new@test.com' , name : 'New User' } ) ;
6885 expect ( result . user . name ) . toBe ( 'New User' ) ;
6986 } ) ;
7087
7188 it ( 'signOut sends POST to /sign-out' , async ( ) => {
72- mockFetch . mockResolvedValueOnce ( {
73- ok : true ,
74- json : ( ) => Promise . resolve ( { } ) ,
89+ const { mockFn, calls } = createMockFetch ( {
90+ '/sign-out' : { body : { success : true } } ,
7591 } ) ;
92+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
7693
7794 await client . signOut ( ) ;
7895
79- expect ( mockFetch ) . toHaveBeenCalledWith (
80- '/api/auth/sign-out' ,
81- expect . objectContaining ( { method : 'POST' } ) ,
82- ) ;
96+ expect ( calls ) . toHaveLength ( 1 ) ;
97+ expect ( calls [ 0 ] . url ) . toContain ( '/api/auth/sign-out' ) ;
98+ expect ( calls [ 0 ] . method ) . toBe ( 'POST' ) ;
8399 } ) ;
84100
85101 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 ) ,
102+ const { mockFn , calls } = createMockFetch ( {
103+ '/get-session' : {
104+ body : {
105+ user : { id : '1' , name : 'Test' , email : 'test@test.com' } ,
106+ session : { token : 'tok789' , id : 's1' , userId : '1' , expiresAt : '2025-01-01' } ,
107+ } ,
108+ } ,
93109 } ) ;
110+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
94111
95112 const result = await client . getSession ( ) ;
96113
97- expect ( mockFetch ) . toHaveBeenCalledWith (
98- '/api/auth/get-session' ,
99- expect . objectContaining ( { method : 'GET' } ) ,
100- ) ;
114+ expect ( calls ) . toHaveLength ( 1 ) ;
115+ expect ( calls [ 0 ] . url ) . toContain ( '/api/auth/get-session' ) ;
116+ expect ( calls [ 0 ] . method ) . toBe ( 'GET' ) ;
101117 expect ( result ?. user . id ) . toBe ( '1' ) ;
102118 } ) ;
103119
104120 it ( 'getSession returns null on failure' , async ( ) => {
105- mockFetch . mockRejectedValueOnce ( new Error ( 'Network error' ) ) ;
121+ const { mockFn } = createMockFetch ( {
122+ '/get-session' : { status : 401 , body : { message : 'Unauthorized' } } ,
123+ } ) ;
124+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
106125
107126 const result = await client . getSession ( ) ;
108127 expect ( result ) . toBeNull ( ) ;
109128 } ) ;
110129
111- it ( 'forgotPassword sends POST to /forgot-password' , async ( ) => {
112- mockFetch . mockResolvedValueOnce ( {
113- ok : true ,
114- json : ( ) => Promise . resolve ( { } ) ,
130+ it ( 'forgotPassword sends POST to /forget-password' , async ( ) => {
131+ const { mockFn, calls } = createMockFetch ( {
132+ '/forget-password' : { body : { status : true } } ,
115133 } ) ;
134+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
116135
117136 await client . forgotPassword ( 'test@test.com' ) ;
118137
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- ) ;
138+ expect ( calls ) . toHaveLength ( 1 ) ;
139+ expect ( calls [ 0 ] . url ) . toContain ( '/api/auth/forget-password' ) ;
140+ expect ( calls [ 0 ] . method ) . toBe ( 'POST' ) ;
141+ expect ( JSON . parse ( calls [ 0 ] . body ! ) ) . toMatchObject ( { email : 'test@test.com' } ) ;
126142 } ) ;
127143
128144 it ( 'resetPassword sends POST to /reset-password' , async ( ) => {
129- mockFetch . mockResolvedValueOnce ( {
130- ok : true ,
131- json : ( ) => Promise . resolve ( { } ) ,
145+ const { mockFn, calls } = createMockFetch ( {
146+ '/reset-password' : { body : { status : true } } ,
132147 } ) ;
148+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
133149
134150 await client . resetPassword ( 'token123' , 'newpass' ) ;
135151
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- ) ;
152+ expect ( calls ) . toHaveLength ( 1 ) ;
153+ expect ( calls [ 0 ] . url ) . toContain ( '/api/auth/reset-password' ) ;
154+ expect ( calls [ 0 ] . method ) . toBe ( 'POST' ) ;
155+ expect ( JSON . parse ( calls [ 0 ] . body ! ) ) . toMatchObject ( { token : 'token123' , newPassword : 'newpass' } ) ;
143156 } ) ;
144157
145158 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' } ) ,
159+ const { mockFn } = createMockFetch ( {
160+ '/sign-in/email' : {
161+ status : 401 ,
162+ body : { message : 'Invalid credentials' , code : 'INVALID_CREDENTIALS' } ,
163+ } ,
150164 } ) ;
165+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
151166
152167 await expect ( client . signIn ( { email : 'x' , password : 'y' } ) ) . rejects . toThrow ( 'Invalid credentials' ) ;
153168 } ) ;
154169
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' ) ) ,
170+ it ( 'throws error on non-OK response without message' , async ( ) => {
171+ const { mockFn } = createMockFetch ( {
172+ '/sign-in/email' : { status : 500 , body : { } } ,
160173 } ) ;
174+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
161175
162- await expect ( client . signIn ( { email : 'x' , password : 'y' } ) ) . rejects . toThrow (
163- 'Auth request failed with status 500' ,
164- ) ;
176+ await expect ( client . signIn ( { email : 'x' , password : 'y' } ) ) . rejects . toThrow ( ) ;
165177 } ) ;
166178
167179 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' } } ) ,
180+ const { mockFn, calls } = createMockFetch ( {
181+ '/update-user' : {
182+ body : { user : { id : '1' , name : 'Updated' , email : 'test@test.com' } } ,
183+ } ,
171184 } ) ;
185+ const client = createAuthClient ( { baseURL : 'http://localhost/api/auth' , fetchFn : mockFn } ) ;
172186
173187 const result = await client . updateUser ( { name : 'Updated' } ) ;
174188
189+ expect ( calls ) . toHaveLength ( 1 ) ;
190+ expect ( calls [ 0 ] . url ) . toContain ( '/api/auth/update-user' ) ;
191+ expect ( calls [ 0 ] . method ) . toBe ( 'POST' ) ;
175192 expect ( result . name ) . toBe ( 'Updated' ) ;
176- expect ( mockFetch ) . toHaveBeenCalledWith (
177- '/api/auth/update-user' ,
178- expect . objectContaining ( { method : 'POST' } ) ,
179- ) ;
180193 } ) ;
181194} ) ;
0 commit comments