apiHelpers.test.ts259 lines · main
1import type { IncomingHttpHeaders } from 'node:http'
2import { beforeEach, describe, expect, it, vi } from 'vitest'
3
4import {
5 commaSeparatedStringIntoArray,
6 constructHeaders,
7 fromNodeHeaders,
8 toSnakeCase,
9 zBooleanString,
10} from './apiHelpers'
11
12vi.mock('@/lib/constants', () => ({
13 IS_PLATFORM: false,
14}))
15
16describe('apiHelpers', () => {
17 describe('constructHeaders', () => {
18 beforeEach(() => {
19 process.env.BRIVEN_SERVICE_KEY = 'test-service-key'
20 })
21
22 it('should return default headers when no headers are provided', () => {
23 const result = constructHeaders(null as any)
24 expect(result).toEqual({
25 'Content-Type': 'application/json',
26 Accept: 'application/json',
27 })
28 })
29
30 it('should clean and include only allowed headers', () => {
31 const inputHeaders = {
32 Accept: 'application/json',
33 Authorization: 'Bearer token',
34 'Content-Type': 'application/json',
35 'x-connection-encrypted': 'true',
36 cookie: 'test-cookie',
37 'User-Agent': 'test-agent',
38 Referer: 'test-referer',
39 }
40
41 const result = constructHeaders(inputHeaders)
42 expect(result).toEqual({
43 Accept: 'application/json',
44 Authorization: 'Bearer token',
45 'Content-Type': 'application/json',
46 'x-connection-encrypted': 'true',
47 cookie: 'test-cookie',
48 apiKey: 'test-service-key',
49 })
50 })
51
52 it('should remove undefined values from headers', () => {
53 const inputHeaders = {
54 Accept: undefined,
55 Authorization: 'Bearer token',
56 'Content-Type': 'application/json',
57 cookie: undefined,
58 }
59
60 const result = constructHeaders(inputHeaders)
61 expect(result).toEqual({
62 Authorization: 'Bearer token',
63 'Content-Type': 'application/json',
64 apiKey: 'test-service-key',
65 })
66 })
67 })
68
69 describe('toSnakeCase', () => {
70 it('should return null for null input', () => {
71 expect(toSnakeCase(null)).toBeNull()
72 })
73
74 it('should convert object keys to snake case', () => {
75 const input = {
76 firstName: 'John',
77 lastName: 'Doe',
78 contactInfo: {
79 emailAddress: 'john@example.com',
80 phoneNumber: '1234567890',
81 },
82 }
83
84 const expected = {
85 first_name: 'John',
86 last_name: 'Doe',
87 contact_info: {
88 email_address: 'john@example.com',
89 phone_number: '1234567890',
90 },
91 }
92
93 expect(toSnakeCase(input)).toEqual(expected)
94 })
95
96 it('should handle arrays of objects', () => {
97 const input = [
98 { firstName: 'John', lastName: 'Doe' },
99 { firstName: 'Jane', lastName: 'Smith' },
100 ]
101
102 const expected = [
103 { first_name: 'John', last_name: 'Doe' },
104 { first_name: 'Jane', last_name: 'Smith' },
105 ]
106
107 expect(toSnakeCase(input)).toEqual(expected)
108 })
109
110 it('should handle arrays of primitive values', () => {
111 const input = [1, 'test', true]
112 expect(toSnakeCase(input)).toEqual([1, 'test', true])
113 })
114
115 it('should handle nested arrays', () => {
116 const input = {
117 users: [
118 { firstName: 'John', contactInfo: { emailAddress: 'john@example.com' } },
119 { firstName: 'Jane', contactInfo: { emailAddress: 'jane@example.com' } },
120 ],
121 }
122
123 const expected = {
124 users: [
125 { first_name: 'John', contact_info: { email_address: 'john@example.com' } },
126 { first_name: 'Jane', contact_info: { email_address: 'jane@example.com' } },
127 ],
128 }
129
130 expect(toSnakeCase(input)).toEqual(expected)
131 })
132
133 it('should handle primitive values', () => {
134 expect(toSnakeCase('test')).toBe('test')
135 expect(toSnakeCase(123)).toBe(123)
136 expect(toSnakeCase(true)).toBe(true)
137 })
138 })
139
140 describe('zBooleanString', () => {
141 it('should transform "true" string to boolean true', () => {
142 const schema = zBooleanString()
143 const result = schema.parse('true')
144 expect(result).toBe(true)
145 })
146
147 it('should transform "false" string to boolean false', () => {
148 const schema = zBooleanString()
149 const result = schema.parse('false')
150 expect(result).toBe(false)
151 })
152
153 it('should throw error for invalid boolean string', () => {
154 const schema = zBooleanString()
155 expect(() => schema.parse('invalid')).toThrow('must be a boolean string')
156 })
157
158 it('should throw custom error message when provided', () => {
159 const customError = 'Custom boolean error'
160 const schema = zBooleanString(customError)
161 expect(() => schema.parse('invalid')).toThrow(customError)
162 })
163
164 it('should throw error for empty string', () => {
165 const schema = zBooleanString()
166 expect(() => schema.parse('')).toThrow('must be a boolean string')
167 })
168
169 it('should throw error for non-string input', () => {
170 const schema = zBooleanString()
171 expect(() => schema.parse(true)).toThrow()
172 expect(() => schema.parse(false)).toThrow()
173 expect(() => schema.parse(123)).toThrow()
174 })
175 })
176
177 describe('commaSeparatedStringIntoArray', () => {
178 it('should split comma-separated string into array', () => {
179 const result = commaSeparatedStringIntoArray('a,b,c')
180 expect(result).toEqual(['a', 'b', 'c'])
181 })
182
183 it('should trim whitespace from values', () => {
184 const result = commaSeparatedStringIntoArray('a, b , c')
185 expect(result).toEqual(['a', 'b', 'c'])
186 })
187
188 it('should filter out empty values', () => {
189 const result = commaSeparatedStringIntoArray('a,,b,')
190 expect(result).toEqual(['a', 'b'])
191 })
192
193 it('should handle single value', () => {
194 const result = commaSeparatedStringIntoArray('single')
195 expect(result).toEqual(['single'])
196 })
197
198 it('should handle empty string', () => {
199 const result = commaSeparatedStringIntoArray('')
200 expect(result).toEqual([])
201 })
202
203 it('should handle string with only commas', () => {
204 const result = commaSeparatedStringIntoArray(',,,')
205 expect(result).toEqual([])
206 })
207 })
208
209 describe('fromNodeHeaders', () => {
210 it('should convert simple node headers to fetch headers', () => {
211 const nodeHeaders: IncomingHttpHeaders = {
212 'content-type': 'application/json',
213 authorization: 'Bearer token',
214 }
215
216 const result = fromNodeHeaders(nodeHeaders)
217
218 expect(result.get('content-type')).toBe('application/json')
219 expect(result.get('authorization')).toBe('Bearer token')
220 })
221
222 it('should skip undefined values', () => {
223 const nodeHeaders: IncomingHttpHeaders = {
224 'content-type': 'application/json',
225 authorization: undefined,
226 accept: 'application/json',
227 }
228
229 const result = fromNodeHeaders(nodeHeaders)
230
231 expect(result.get('content-type')).toBe('application/json')
232 expect(result.get('authorization')).toBeNull()
233 expect(result.get('accept')).toBe('application/json')
234 })
235
236 it('should handle empty headers object', () => {
237 const nodeHeaders: IncomingHttpHeaders = {}
238 const result = fromNodeHeaders(nodeHeaders)
239
240 expect(Array.from(result.keys())).toEqual([])
241 })
242
243 it('should handle mixed array and string values', () => {
244 const nodeHeaders: IncomingHttpHeaders = {
245 'content-type': 'application/json',
246 'x-custom': ['value1', 'value2'],
247 authorization: 'Bearer token',
248 'x-empty': undefined,
249 }
250
251 const result = fromNodeHeaders(nodeHeaders)
252
253 expect(result.get('content-type')).toBe('application/json')
254 expect(result.get('authorization')).toBe('Bearer token')
255 expect(result.get('x-empty')).toBeNull()
256 expect(result.get('x-custom')).toBe('value1, value2')
257 })
258 })
259})