apiHelpers.test.ts 7.6 KB

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