@vafast/api-client 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,262 @@
1
+ import { describe, expect, it, beforeEach, mock } from 'bun:test'
2
+ import { VafastApiClient } from '../src'
3
+
4
+ // Mock fetch
5
+ const mockFetch = mock(() =>
6
+ Promise.resolve(new Response(JSON.stringify({ success: true }), {
7
+ status: 200,
8
+ headers: { 'Content-Type': 'application/json' }
9
+ }))
10
+ )
11
+
12
+ // Mock global fetch
13
+ global.fetch = mockFetch
14
+
15
+ describe('VafastApiClient', () => {
16
+ let client: VafastApiClient
17
+
18
+ beforeEach(() => {
19
+ client = new VafastApiClient({
20
+ baseURL: 'https://api.example.com',
21
+ timeout: 5000,
22
+ retries: 2
23
+ })
24
+ mockFetch.mockClear()
25
+ })
26
+
27
+ describe('Constructor', () => {
28
+ it('should create client with default config', () => {
29
+ const defaultClient = new VafastApiClient()
30
+ expect(defaultClient).toBeDefined()
31
+ })
32
+
33
+ it('should create client with custom config', () => {
34
+ expect(client).toBeDefined()
35
+ })
36
+
37
+ it('should merge config correctly', () => {
38
+ const customClient = new VafastApiClient({
39
+ baseURL: 'https://custom.api.com',
40
+ timeout: 10000
41
+ })
42
+ expect(customClient).toBeDefined()
43
+ })
44
+ })
45
+
46
+ describe('Middleware Management', () => {
47
+ it('should add middleware', () => {
48
+ const middleware = {
49
+ name: 'test',
50
+ onRequest: async (req: Request) => req
51
+ }
52
+
53
+ client.addMiddleware(middleware)
54
+ expect(client).toBeDefined()
55
+ })
56
+
57
+ it('should remove middleware', () => {
58
+ const middleware = {
59
+ name: 'test',
60
+ onRequest: async (req: Request) => req
61
+ }
62
+
63
+ client.addMiddleware(middleware)
64
+ client.removeMiddleware('test')
65
+ expect(client).toBeDefined()
66
+ })
67
+
68
+ it('should handle non-existent middleware removal', () => {
69
+ client.removeMiddleware('non-existent')
70
+ expect(client).toBeDefined()
71
+ })
72
+ })
73
+
74
+ describe('Interceptor Management', () => {
75
+ it('should add interceptor', () => {
76
+ const interceptor = {
77
+ request: async (config: any) => config
78
+ }
79
+
80
+ client.addInterceptor(interceptor)
81
+ expect(client).toBeDefined()
82
+ })
83
+
84
+ it('should remove interceptor', () => {
85
+ const interceptor = {
86
+ request: async (config: any) => config
87
+ }
88
+
89
+ client.addInterceptor(interceptor)
90
+ client.removeInterceptor(0)
91
+ expect(client).toBeDefined()
92
+ })
93
+
94
+ it('should handle invalid interceptor index', () => {
95
+ client.removeInterceptor(-1)
96
+ client.removeInterceptor(999)
97
+ expect(client).toBeDefined()
98
+ })
99
+ })
100
+
101
+ describe('Cache Management', () => {
102
+ it('should clear cache', () => {
103
+ client.clearCache()
104
+ const stats = client.getCacheStats()
105
+ expect(stats.size).toBe(0)
106
+ expect(stats.keys).toEqual([])
107
+ })
108
+
109
+ it('should get cache stats', () => {
110
+ const stats = client.getCacheStats()
111
+ expect(stats).toHaveProperty('size')
112
+ expect(stats).toHaveProperty('keys')
113
+ expect(Array.isArray(stats.keys)).toBe(true)
114
+ })
115
+ })
116
+
117
+ describe('Configuration Methods', () => {
118
+ it('should set cache config', () => {
119
+ const result = client.setCacheConfig({
120
+ enabled: true,
121
+ ttl: 300000,
122
+ maxSize: 100,
123
+ strategy: 'memory'
124
+ })
125
+ expect(result).toBe(client)
126
+ })
127
+
128
+ it('should set retry config', () => {
129
+ const result = client.setRetryConfig({
130
+ enabled: true,
131
+ maxRetries: 5,
132
+ retryDelay: 1000,
133
+ backoffMultiplier: 2,
134
+ retryableStatuses: [500, 502, 503]
135
+ })
136
+ expect(result).toBe(client)
137
+ })
138
+
139
+ it('should set log config', () => {
140
+ const result = client.setLogConfig({
141
+ enabled: true,
142
+ level: 'info',
143
+ format: 'json',
144
+ includeHeaders: true,
145
+ includeBody: false
146
+ })
147
+ expect(result).toBe(client)
148
+ })
149
+ })
150
+
151
+ describe('HTTP Methods', () => {
152
+ it('should make GET request', async () => {
153
+ const response = await client.get('/users', { page: 1, limit: 10 })
154
+ expect(response).toBeDefined()
155
+ expect(mockFetch).toHaveBeenCalled()
156
+ })
157
+
158
+ it('should make POST request', async () => {
159
+ const response = await client.post('/users', { name: 'John', email: 'john@example.com' })
160
+ expect(response).toBeDefined()
161
+ expect(mockFetch).toHaveBeenCalled()
162
+ })
163
+
164
+ it('should make PUT request', async () => {
165
+ const response = await client.put('/users/123', { name: 'John Updated' })
166
+ expect(response).toBeDefined()
167
+ expect(mockFetch).toHaveBeenCalled()
168
+ })
169
+
170
+ it('should make DELETE request', async () => {
171
+ const response = await client.delete('/users/123')
172
+ expect(response).toBeDefined()
173
+ expect(mockFetch).toHaveBeenCalled()
174
+ })
175
+
176
+ it('should make PATCH request', async () => {
177
+ const response = await client.patch('/users/123', { name: 'John Patched' })
178
+ expect(response).toBeDefined()
179
+ expect(mockFetch).toHaveBeenCalled()
180
+ })
181
+
182
+ it('should make HEAD request', async () => {
183
+ const response = await client.head('/users')
184
+ expect(response).toBeDefined()
185
+ expect(mockFetch).toHaveBeenCalled()
186
+ })
187
+
188
+ it('should make OPTIONS request', async () => {
189
+ const response = await client.options('/users')
190
+ expect(response).toBeDefined()
191
+ expect(mockFetch).toHaveBeenCalled()
192
+ })
193
+ })
194
+
195
+ describe('Request Configuration', () => {
196
+ it('should handle custom headers', async () => {
197
+ const response = await client.get('/users', undefined, {
198
+ headers: { 'X-Custom-Header': 'test' }
199
+ })
200
+ expect(response).toBeDefined()
201
+ })
202
+
203
+ it('should handle timeout configuration', async () => {
204
+ const response = await client.get('/users', undefined, {
205
+ timeout: 10000
206
+ })
207
+ expect(response).toBeDefined()
208
+ })
209
+
210
+ it('should handle retry configuration', async () => {
211
+ const response = await client.get('/users', undefined, {
212
+ retries: 5,
213
+ retryDelay: 2000
214
+ })
215
+ expect(response).toBeDefined()
216
+ })
217
+ })
218
+
219
+ describe('Error Handling', () => {
220
+ it('should handle network errors gracefully', async () => {
221
+ // Mock fetch to throw error
222
+ const errorFetch = mock(() => Promise.reject(new Error('Network error')))
223
+ global.fetch = errorFetch
224
+
225
+ const response = await client.get('/users')
226
+ expect(response.error).toBeDefined()
227
+ expect(response.data).toBeNull()
228
+
229
+ // Restore original mock
230
+ global.fetch = mockFetch
231
+ })
232
+
233
+ it('should handle HTTP error statuses', async () => {
234
+ const errorFetch = mock(() =>
235
+ Promise.resolve(new Response('Not Found', { status: 404 }))
236
+ )
237
+ global.fetch = errorFetch
238
+
239
+ const response = await client.get('/users')
240
+ expect(response.status).toBe(404)
241
+
242
+ // Restore original mock
243
+ global.fetch = mockFetch
244
+ })
245
+ })
246
+
247
+ describe('URL Building', () => {
248
+ it('should build URLs correctly with baseURL', async () => {
249
+ const customClient = new VafastApiClient({
250
+ baseURL: 'https://api.example.com'
251
+ })
252
+
253
+ await customClient.get('/users')
254
+ expect(mockFetch).toHaveBeenCalled()
255
+ })
256
+
257
+ it('should handle absolute URLs', async () => {
258
+ await client.get('https://external.api.com/users')
259
+ expect(mockFetch).toHaveBeenCalled()
260
+ })
261
+ })
262
+ })
@@ -0,0 +1,55 @@
1
+ import { describe, expect, it, beforeEach } from 'bun:test'
2
+ import { VafastApiClient } from '../src'
3
+
4
+ describe('VafastApiClient', () => {
5
+ let client: VafastApiClient
6
+
7
+ beforeEach(() => {
8
+ client = new VafastApiClient({
9
+ baseURL: 'https://api.example.com',
10
+ timeout: 5000,
11
+ retries: 2
12
+ })
13
+ })
14
+
15
+ it('should create client with default config', () => {
16
+ const defaultClient = new VafastApiClient()
17
+ expect(defaultClient).toBeDefined()
18
+ })
19
+
20
+ it('should create client with custom config', () => {
21
+ expect(client).toBeDefined()
22
+ })
23
+
24
+ it('should add and remove middleware', () => {
25
+ const middleware = {
26
+ name: 'test',
27
+ onRequest: async (req: Request) => req
28
+ }
29
+
30
+ client.addMiddleware(middleware)
31
+ expect(client.getCacheStats()).toBeDefined()
32
+
33
+ client.removeMiddleware('test')
34
+ expect(client.getCacheStats()).toBeDefined()
35
+ })
36
+
37
+ it('should add and remove interceptor', () => {
38
+ const interceptor = {
39
+ request: async (config: any) => config
40
+ }
41
+
42
+ client.addInterceptor(interceptor)
43
+ expect(client.getCacheStats()).toBeDefined()
44
+
45
+ client.removeInterceptor(0)
46
+ expect(client.getCacheStats()).toBeDefined()
47
+ })
48
+
49
+ it('should clear cache', () => {
50
+ client.clearCache()
51
+ const stats = client.getCacheStats()
52
+ expect(stats.size).toBe(0)
53
+ expect(stats.keys).toEqual([])
54
+ })
55
+ })
@@ -0,0 +1,304 @@
1
+ import { describe, expect, it, beforeEach, mock } from 'bun:test'
2
+ import {
3
+ createTypedClient,
4
+ createRouteBasedClient,
5
+ createSimpleClient,
6
+ type TypedApiClient
7
+ } from '../src'
8
+
9
+ // Mock VafastApiClient
10
+ const MockVafastApiClient = mock(() => ({
11
+ get: mock(() => Promise.resolve({ data: 'test', error: null, status: 200, headers: new Headers(), response: new Response() })),
12
+ post: mock(() => Promise.resolve({ data: 'created', error: null, status: 201, headers: new Headers(), response: new Response() })),
13
+ put: mock(() => Promise.resolve({ data: 'updated', error: null, status: 200, headers: new Headers(), response: new Response() })),
14
+ delete: mock(() => Promise.resolve({ data: 'deleted', error: null, status: 200, headers: new Headers(), response: new Response() })),
15
+ patch: mock(() => Promise.resolve({ data: 'patched', error: null, status: 200, headers: new Headers(), response: new Response() })),
16
+ head: mock(() => Promise.resolve({ data: null, error: null, status: 200, headers: new Headers(), response: new Response() })),
17
+ options: mock(() => Promise.resolve({ data: null, error: null, status: 200, headers: new Headers(), response: new Response() }))
18
+ }))
19
+
20
+ // Mock server type
21
+ interface MockServer {
22
+ routes: {
23
+ '/users': {
24
+ GET: { query: { page?: number; limit?: number } }
25
+ POST: { body: { name: string; email: string } }
26
+ }
27
+ '/users/:id': {
28
+ GET: { params: { id: string } }
29
+ PUT: { params: { id: string }; body: Partial<{ name: string; email: string }> }
30
+ DELETE: { params: { id: string } }
31
+ }
32
+ '/posts': {
33
+ GET: { query: { author?: string; category?: string } }
34
+ POST: { body: { title: string; content: string; authorId: string } }
35
+ }
36
+ '/posts/:id': {
37
+ GET: { params: { id: string } }
38
+ PUT: { params: { id: string }; body: Partial<{ title: string; content: string }> }
39
+ DELETE: { params: { id: string } }
40
+ }
41
+ }
42
+ }
43
+
44
+ describe('Typed Client', () => {
45
+ let mockServer: MockServer
46
+
47
+ beforeEach(() => {
48
+ mockServer = {
49
+ routes: {
50
+ '/users': {
51
+ GET: { query: { page: 1, limit: 10 } },
52
+ POST: { body: { name: 'John', email: 'john@example.com' } }
53
+ },
54
+ '/users/:id': {
55
+ GET: { params: { id: '123' } },
56
+ PUT: { params: { id: '123' }, body: { name: 'John Updated' } },
57
+ DELETE: { params: { id: '123' } }
58
+ },
59
+ '/posts': {
60
+ GET: { query: { author: 'user123', category: 'tech' } },
61
+ POST: { body: { title: 'Test Post', content: 'Content', authorId: 'user123' } }
62
+ },
63
+ '/posts/:id': {
64
+ GET: { params: { id: '456' } },
65
+ PUT: { params: { id: '456' }, body: { title: 'Updated Post' } },
66
+ DELETE: { params: { id: '456' } }
67
+ }
68
+ }
69
+ }
70
+ })
71
+
72
+ describe('createTypedClient', () => {
73
+ it('should create typed client from server', () => {
74
+ const typedClient = createTypedClient<MockServer>(mockServer as any, {
75
+ baseURL: 'https://api.example.com'
76
+ })
77
+ expect(typedClient).toBeDefined()
78
+ })
79
+
80
+ it('should support HTTP method calls', async () => {
81
+ const typedClient = createTypedClient<MockServer>(mockServer as any)
82
+
83
+ // Test GET method
84
+ const getResponse = await typedClient.get('/users', { page: 1, limit: 10 })
85
+ expect(getResponse).toBeDefined()
86
+ // Note: The actual response will depend on the implementation
87
+ expect(getResponse).toHaveProperty('data')
88
+
89
+ // Test POST method
90
+ const postResponse = await typedClient.post('/users', { name: 'Jane', email: 'jane@example.com' })
91
+ expect(postResponse).toBeDefined()
92
+ expect(postResponse).toHaveProperty('data')
93
+
94
+ // Test PUT method
95
+ const putResponse = await typedClient.put('/users/123', { name: 'Jane Updated' })
96
+ expect(putResponse).toBeDefined()
97
+ expect(putResponse).toHaveProperty('data')
98
+
99
+ // Test DELETE method
100
+ const deleteResponse = await typedClient.delete('/users/123')
101
+ expect(deleteResponse).toBeDefined()
102
+ expect(deleteResponse).toHaveProperty('data')
103
+
104
+ // Test PATCH method
105
+ const patchResponse = await typedClient.patch('/users/123', { name: 'Jane Patched' })
106
+ expect(patchResponse).toBeDefined()
107
+ expect(patchResponse).toHaveProperty('data')
108
+
109
+ // Test HEAD method
110
+ const headResponse = await typedClient.head('/users')
111
+ expect(headResponse).toBeDefined()
112
+ expect(headResponse).toHaveProperty('data')
113
+
114
+ // Test OPTIONS method
115
+ const optionsResponse = await typedClient.options('/users')
116
+ expect(optionsResponse).toBeDefined()
117
+ expect(optionsResponse).toHaveProperty('data')
118
+ })
119
+
120
+ it('should support path-based calls', async () => {
121
+ const typedClient = createTypedClient<MockServer>(mockServer as any)
122
+
123
+ // Test path segments
124
+ const usersClient = (typedClient as any).users
125
+ expect(usersClient).toBeDefined()
126
+
127
+ // Test nested path segments
128
+ const postsClient = (typedClient as any).posts
129
+ expect(postsClient).toBeDefined()
130
+ })
131
+
132
+ it('should handle dynamic path parameters', async () => {
133
+ const typedClient = createTypedClient<MockServer>(mockServer as any)
134
+
135
+ // Test dynamic path with parameters
136
+ const userClient = (typedClient as any).users
137
+ if (typeof userClient === 'function') {
138
+ const response = await userClient({ id: '123' })
139
+ expect(response).toBeDefined()
140
+ }
141
+ })
142
+ })
143
+
144
+ describe('createRouteBasedClient', () => {
145
+ it('should create route-based client', () => {
146
+ const routeClient = createRouteBasedClient<MockServer>(mockServer as any, {
147
+ baseURL: 'https://api.example.com'
148
+ })
149
+ expect(routeClient).toBeDefined()
150
+ })
151
+
152
+ it('should support dynamic path handling', async () => {
153
+ const routeClient = createRouteBasedClient<MockServer>(mockServer as any)
154
+
155
+ // Test dynamic path creation
156
+ const dynamicPath = (routeClient as any).users
157
+ expect(dynamicPath).toBeDefined()
158
+
159
+ if (typeof dynamicPath === 'function') {
160
+ const response = await dynamicPath({ id: '123' })
161
+ expect(response).toBeDefined()
162
+ }
163
+ })
164
+ })
165
+
166
+ describe('createSimpleClient', () => {
167
+ it('should create simple client', () => {
168
+ const simpleClient = createSimpleClient<MockServer>(mockServer as any, {
169
+ baseURL: 'https://api.example.com'
170
+ })
171
+ expect(simpleClient).toBeDefined()
172
+ })
173
+
174
+ it('should have all HTTP methods', () => {
175
+ const simpleClient = createSimpleClient<MockServer>(mockServer as any)
176
+
177
+ expect(typeof simpleClient.get).toBe('function')
178
+ expect(typeof simpleClient.post).toBe('function')
179
+ expect(typeof simpleClient.put).toBe('function')
180
+ expect(typeof simpleClient.delete).toBe('function')
181
+ expect(typeof simpleClient.patch).toBe('function')
182
+ expect(typeof simpleClient.head).toBe('function')
183
+ expect(typeof simpleClient.options).toBe('function')
184
+ })
185
+
186
+ it('should make HTTP requests correctly', async () => {
187
+ const simpleClient = createSimpleClient<MockServer>(mockServer as any)
188
+
189
+ // Test GET request
190
+ const getResponse = await simpleClient.get('/users', { page: 1, limit: 10 })
191
+ expect(getResponse).toBeDefined()
192
+ expect(getResponse).toHaveProperty('data')
193
+
194
+ // Test POST request
195
+ const postResponse = await simpleClient.post('/users', { name: 'John', email: 'john@example.com' })
196
+ expect(postResponse).toBeDefined()
197
+ expect(postResponse).toHaveProperty('data')
198
+
199
+ // Test PUT request
200
+ const putResponse = await simpleClient.put('/users/123', { name: 'John Updated' })
201
+ expect(putResponse).toBeDefined()
202
+ expect(putResponse).toHaveProperty('data')
203
+
204
+ // Test DELETE request
205
+ const deleteResponse = await simpleClient.delete('/users/123')
206
+ expect(deleteResponse).toBeDefined()
207
+ expect(deleteResponse).toHaveProperty('data')
208
+ })
209
+
210
+ it('should handle query parameters', async () => {
211
+ const simpleClient = createSimpleClient<MockServer>(mockServer as any)
212
+
213
+ const response = await simpleClient.get('/users', { page: 2, limit: 20, search: 'john' })
214
+ expect(response).toBeDefined()
215
+ })
216
+
217
+ it('should handle request body', async () => {
218
+ const simpleClient = createSimpleClient<MockServer>(mockServer as any)
219
+
220
+ const response = await simpleClient.post('/users', {
221
+ name: 'Jane Doe',
222
+ email: 'jane@example.com',
223
+ age: 25
224
+ })
225
+ expect(response).toBeDefined()
226
+ })
227
+
228
+ it('should handle custom request config', async () => {
229
+ const simpleClient = createSimpleClient<MockServer>(mockServer as any)
230
+
231
+ const response = await simpleClient.get('/users', { page: 1, limit: 10 }, {
232
+ headers: { 'X-Custom-Header': 'test' },
233
+ timeout: 10000
234
+ })
235
+ expect(response).toBeDefined()
236
+ })
237
+ })
238
+
239
+ describe('Type Safety', () => {
240
+ it('should maintain type safety for server types', () => {
241
+ // This test ensures TypeScript compilation works correctly
242
+ const typedClient: TypedApiClient<MockServer> = createTypedClient<MockServer>(mockServer as any)
243
+ expect(typedClient).toBeDefined()
244
+ })
245
+
246
+ it('should support generic type constraints', () => {
247
+ // Test with different server types
248
+ interface SimpleServer {
249
+ routes: {
250
+ '/health': {
251
+ GET: {}
252
+ }
253
+ }
254
+ }
255
+
256
+ const simpleServer: SimpleServer = {
257
+ routes: {
258
+ '/health': { GET: {} }
259
+ }
260
+ }
261
+
262
+ const client = createTypedClient<SimpleServer>(simpleServer as any)
263
+ expect(client).toBeDefined()
264
+ })
265
+ })
266
+
267
+ describe('Error Handling', () => {
268
+ it('should handle client creation errors gracefully', () => {
269
+ // Test with invalid server
270
+ const invalidClient = createTypedClient(null as any)
271
+ expect(invalidClient).toBeDefined()
272
+ })
273
+
274
+ it('should handle missing routes gracefully', () => {
275
+ const emptyServer = { routes: {} }
276
+ const client = createTypedClient(emptyServer as any)
277
+ expect(client).toBeDefined()
278
+ })
279
+ })
280
+
281
+ describe('Integration', () => {
282
+ it('should work with different server configurations', () => {
283
+ const configs = [
284
+ { baseURL: 'https://api1.example.com' },
285
+ { baseURL: 'https://api2.example.com', timeout: 5000 },
286
+ { baseURL: 'https://api3.example.com', retries: 3 }
287
+ ]
288
+
289
+ configs.forEach(config => {
290
+ const client = createSimpleClient<MockServer>(mockServer as any, config)
291
+ expect(client).toBeDefined()
292
+ })
293
+ })
294
+
295
+ it('should support method chaining patterns', () => {
296
+ const client = createSimpleClient<MockServer>(mockServer as any)
297
+
298
+ // Test that methods return the client for chaining
299
+ expect(client).toBeDefined()
300
+ expect(typeof client.get).toBe('function')
301
+ expect(typeof client.post).toBe('function')
302
+ })
303
+ })
304
+ })