http-client-platform.contract.test.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /**
  2. * Contract test for IUniversalHttpClient.registerPlatform()
  3. *
  4. * This test validates the platform registration functionality of the Universal HTTP Client
  5. * according to the contract specification. Tests MUST FAIL until implementation is complete.
  6. */
  7. import { describe, test, expect, beforeEach, jest } from '@jest/globals'
  8. // Import types that will be implemented
  9. import type { IUniversalHttpClient, IPlatformAdapter, HealthStatus } from '@/types/httpClientCore'
  10. describe('IUniversalHttpClient.registerPlatform() Contract', () => {
  11. let httpClient: IUniversalHttpClient
  12. beforeEach(() => {
  13. // This will fail until IUniversalHttpClient is implemented
  14. // eslint-disable-next-line @typescript-eslint/no-require-imports
  15. const { HttpClientCore } = require('@/core/http-client/HttpClientCore')
  16. httpClient = new HttpClientCore()
  17. })
  18. describe('Platform Registration', () => {
  19. test('should register a new platform adapter successfully', () => {
  20. const mockAdapter: IPlatformAdapter = {
  21. platform: 'test-platform',
  22. baseUrl: 'https://api.test-platform.com',
  23. request: jest.fn(),
  24. prepareRequest: jest.fn(),
  25. setProxyConfig: jest.fn(),
  26. getProxyStatus: jest.fn(),
  27. processResponse: jest.fn(),
  28. checkHealth: jest.fn(),
  29. getConfig: jest.fn(),
  30. validateConfig: jest.fn().mockReturnValue(true)
  31. }
  32. expect(() => {
  33. httpClient.registerPlatform('test-platform', mockAdapter)
  34. }).not.toThrow()
  35. // Verify the platform is registered by checking if it's available for requests
  36. expect(httpClient.isPlatformRegistered?.('test-platform')).toBe(true)
  37. })
  38. test('should register multiple platform adapters', () => {
  39. const adapters = [
  40. {
  41. platform: 'platform-1',
  42. baseUrl: 'https://api.platform-1.com',
  43. request: jest.fn(),
  44. prepareRequest: jest.fn(),
  45. setProxyConfig: jest.fn(),
  46. getProxyStatus: jest.fn(),
  47. processResponse: jest.fn(),
  48. checkHealth: jest.fn(),
  49. getConfig: jest.fn(),
  50. validateConfig: jest.fn().mockReturnValue(true)
  51. },
  52. {
  53. platform: 'platform-2',
  54. baseUrl: 'https://api.platform-2.com',
  55. request: jest.fn(),
  56. prepareRequest: jest.fn(),
  57. setProxyConfig: jest.fn(),
  58. getProxyStatus: jest.fn(),
  59. processResponse: jest.fn(),
  60. checkHealth: jest.fn(),
  61. getConfig: jest.fn(),
  62. validateConfig: jest.fn().mockReturnValue(true)
  63. }
  64. ]
  65. adapters.forEach(adapter => {
  66. expect(() => {
  67. httpClient.registerPlatform(adapter.platform, adapter as IPlatformAdapter)
  68. }).not.toThrow()
  69. })
  70. // Verify both platforms are registered
  71. expect(httpClient.isPlatformRegistered?.('platform-1')).toBe(true)
  72. expect(httpClient.isPlatformRegistered?.('platform-2')).toBe(true)
  73. })
  74. test('should replace existing platform adapter when re-registered', () => {
  75. const originalAdapter: IPlatformAdapter = {
  76. platform: 'replaceable-platform',
  77. baseUrl: 'https://api.original.com',
  78. request: jest.fn(),
  79. prepareRequest: jest.fn(),
  80. setProxyConfig: jest.fn(),
  81. getProxyStatus: jest.fn(),
  82. processResponse: jest.fn(),
  83. checkHealth: jest.fn(),
  84. getConfig: jest.fn(),
  85. validateConfig: jest.fn().mockReturnValue(true)
  86. }
  87. const newAdapter: IPlatformAdapter = {
  88. platform: 'replaceable-platform',
  89. baseUrl: 'https://api.new.com',
  90. request: jest.fn(),
  91. prepareRequest: jest.fn(),
  92. setProxyConfig: jest.fn(),
  93. getProxyStatus: jest.fn(),
  94. processResponse: jest.fn(),
  95. checkHealth: jest.fn(),
  96. getConfig: jest.fn(),
  97. validateConfig: jest.fn().mockReturnValue(true)
  98. }
  99. // Register original adapter
  100. httpClient.registerPlatform('replaceable-platform', originalAdapter)
  101. expect(httpClient.isPlatformRegistered?.('replaceable-platform')).toBe(true)
  102. // Register new adapter with same platform name
  103. httpClient.registerPlatform('replaceable-platform', newAdapter)
  104. expect(httpClient.isPlatformRegistered?.('replaceable-platform')).toBe(true)
  105. // Verify the new adapter is being used (baseUrl should be from new adapter)
  106. const registeredAdapter = httpClient.getPlatformAdapter?.('replaceable-platform')
  107. expect(registeredAdapter?.baseUrl).toBe('https://api.new.com')
  108. })
  109. test('should validate adapter configuration before registration', () => {
  110. const invalidAdapter: IPlatformAdapter = {
  111. platform: 'invalid-platform',
  112. baseUrl: 'https://api.invalid.com',
  113. request: jest.fn(),
  114. prepareRequest: jest.fn(),
  115. setProxyConfig: jest.fn(),
  116. getProxyStatus: jest.fn(),
  117. processResponse: jest.fn(),
  118. checkHealth: jest.fn(),
  119. getConfig: jest.fn(),
  120. validateConfig: jest.fn().mockReturnValue(false) // Invalid configuration
  121. }
  122. expect(() => {
  123. httpClient.registerPlatform('invalid-platform', invalidAdapter)
  124. }).toThrow('Invalid platform configuration')
  125. })
  126. })
  127. describe('Platform Access', () => {
  128. test('should provide access to registered platform adapters', () => {
  129. const adapter: IPlatformAdapter = {
  130. platform: 'accessible-platform',
  131. baseUrl: 'https://api.accessible.com',
  132. request: jest.fn(),
  133. prepareRequest: jest.fn(),
  134. setProxyConfig: jest.fn(),
  135. getProxyStatus: jest.fn(),
  136. processResponse: jest.fn(),
  137. checkHealth: jest.fn(),
  138. getConfig: jest.fn(),
  139. validateConfig: jest.fn().mockReturnValue(true)
  140. }
  141. httpClient.registerPlatform('accessible-platform', adapter)
  142. const retrievedAdapter = httpClient.getPlatformAdapter?.('accessible-platform')
  143. expect(retrievedAdapter).toBeDefined()
  144. expect(retrievedAdapter?.platform).toBe('accessible-platform')
  145. expect(retrievedAdapter?.baseUrl).toBe('https://api.accessible.com')
  146. })
  147. test('should return list of registered platforms', () => {
  148. const platforms = ['platform-a', 'platform-b', 'platform-c']
  149. platforms.forEach(platform => {
  150. const adapter: IPlatformAdapter = {
  151. platform,
  152. baseUrl: `https://api.${platform}.com`,
  153. request: jest.fn(),
  154. prepareRequest: jest.fn(),
  155. setProxyConfig: jest.fn(),
  156. getProxyStatus: jest.fn(),
  157. processResponse: jest.fn(),
  158. checkHealth: jest.fn(),
  159. getConfig: jest.fn(),
  160. validateConfig: jest.fn().mockReturnValue(true)
  161. }
  162. httpClient.registerPlatform(platform, adapter)
  163. })
  164. const registeredPlatforms = httpClient.getRegisteredPlatforms?.()
  165. expect(registeredPlatforms).toEqual(expect.arrayContaining(platforms))
  166. expect(registeredPlatforms).toHaveLength(platforms.length)
  167. })
  168. test('should check if specific platform is registered', () => {
  169. const adapter: IPlatformAdapter = {
  170. platform: 'checkable-platform',
  171. baseUrl: 'https://api.checkable.com',
  172. request: jest.fn(),
  173. prepareRequest: jest.fn(),
  174. setProxyConfig: jest.fn(),
  175. getProxyStatus: jest.fn(),
  176. processResponse: jest.fn(),
  177. checkHealth: jest.fn(),
  178. getConfig: jest.fn(),
  179. validateConfig: jest.fn().mockReturnValue(true)
  180. }
  181. expect(httpClient.isPlatformRegistered?.('checkable-platform')).toBe(false)
  182. httpClient.registerPlatform('checkable-platform', adapter)
  183. expect(httpClient.isPlatformRegistered?.('checkable-platform')).toBe(true)
  184. expect(httpClient.isPlatformRegistered?.('non-existent-platform')).toBe(false)
  185. })
  186. })
  187. describe('Platform Management', () => {
  188. test('should support unregistering platforms', async () => {
  189. const adapter: IPlatformAdapter = {
  190. platform: 'removable-platform',
  191. baseUrl: 'https://api.removable.com',
  192. request: jest.fn(),
  193. prepareRequest: jest.fn(),
  194. setProxyConfig: jest.fn(),
  195. getProxyStatus: jest.fn(),
  196. processResponse: jest.fn(),
  197. checkHealth: jest.fn(),
  198. getConfig: jest.fn(),
  199. validateConfig: jest.fn().mockReturnValue(true)
  200. }
  201. httpClient.registerPlatform('removable-platform', adapter)
  202. expect(httpClient.isPlatformRegistered?.('removable-platform')).toBe(true)
  203. const result = await httpClient.unregisterPlatform?.('removable-platform')
  204. expect(result?.success).toBe(true)
  205. expect(httpClient.isPlatformRegistered?.('removable-platform')).toBe(false)
  206. })
  207. test('should support updating platform configuration', async () => {
  208. const adapter: IPlatformAdapter = {
  209. platform: 'updatable-platform',
  210. baseUrl: 'https://api.updatable.com',
  211. request: jest.fn(),
  212. prepareRequest: jest.fn(),
  213. setProxyConfig: jest.fn(),
  214. getProxyStatus: jest.fn(),
  215. processResponse: jest.fn(),
  216. checkHealth: jest.fn(),
  217. getConfig: jest.fn(),
  218. validateConfig: jest.fn().mockReturnValue(true)
  219. }
  220. httpClient.registerPlatform('updatable-platform', adapter)
  221. const updatedConfig = {
  222. baseUrl: 'https://api.updated.com',
  223. timeout: { connect: 10000, read: 60000, write: 30000 }
  224. }
  225. const result = await httpClient.updatePlatformConfig?.('updatable-platform', updatedConfig)
  226. expect(result?.success).toBe(true)
  227. const retrievedAdapter = httpClient.getPlatformAdapter?.('updatable-platform')
  228. expect(retrievedAdapter?.baseUrl).toBe('https://api.updated.com')
  229. })
  230. test('should provide platform configuration information', () => {
  231. const adapter: IPlatformAdapter = {
  232. platform: 'info-platform',
  233. baseUrl: 'https://api.info.com',
  234. request: jest.fn(),
  235. prepareRequest: jest.fn(),
  236. setProxyConfig: jest.fn(),
  237. getProxyStatus: jest.fn(),
  238. processResponse: jest.fn(),
  239. checkHealth: jest.fn(),
  240. getConfig: jest.fn().mockReturnValue({
  241. platform: 'info-platform',
  242. baseUrl: 'https://api.info.com',
  243. authConfig: { type: 'signature' },
  244. timeouts: { connect: 5000, read: 30000, write: 15000 }
  245. }),
  246. validateConfig: jest.fn().mockReturnValue(true)
  247. }
  248. httpClient.registerPlatform('info-platform', adapter)
  249. const platformInfo = httpClient.getPlatformInfo?.('info-platform')
  250. expect(platformInfo).toBeDefined()
  251. expect(platformInfo?.platform).toBe('info-platform')
  252. expect(platformInfo?.baseUrl).toBe('https://api.info.com')
  253. })
  254. })
  255. describe('Health and Status', () => {
  256. test('should provide overall client health status', async () => {
  257. const adapters = [
  258. {
  259. platform: 'healthy-platform-1',
  260. baseUrl: 'https://api.healthy1.com',
  261. checkHealth: jest.fn().mockResolvedValue({ status: 'healthy', responseTime: 50 })
  262. },
  263. {
  264. platform: 'healthy-platform-2',
  265. baseUrl: 'https://api.healthy2.com',
  266. checkHealth: jest.fn().mockResolvedValue({ status: 'healthy', responseTime: 75 })
  267. }
  268. ]
  269. adapters.forEach(adapter => {
  270. const fullAdapter: IPlatformAdapter = {
  271. ...adapter,
  272. request: jest.fn(),
  273. prepareRequest: jest.fn(),
  274. setProxyConfig: jest.fn(),
  275. getProxyStatus: jest.fn(),
  276. processResponse: jest.fn(),
  277. getConfig: jest.fn(),
  278. validateConfig: jest.fn().mockReturnValue(true)
  279. }
  280. httpClient.registerPlatform(adapter.platform, fullAdapter)
  281. })
  282. const health: HealthStatus = await httpClient.getHealth()
  283. expect(health).toBeDefined()
  284. expect(health.status).toBe('healthy')
  285. expect(health.platforms).toBeDefined()
  286. expect(Object.keys(health.platforms)).toHaveLength(2)
  287. expect(health.platforms['healthy-platform-1']?.status).toBe('healthy')
  288. expect(health.platforms['healthy-platform-2']?.status).toBe('healthy')
  289. })
  290. test('should detect unhealthy platforms', async () => {
  291. const adapters = [
  292. {
  293. platform: 'healthy-platform',
  294. baseUrl: 'https://api.healthy.com',
  295. checkHealth: jest.fn().mockResolvedValue({ status: 'healthy', responseTime: 50 })
  296. },
  297. {
  298. platform: 'unhealthy-platform',
  299. baseUrl: 'https://api.unhealthy.com',
  300. checkHealth: jest.fn().mockRejectedValue(new Error('Platform unavailable'))
  301. }
  302. ]
  303. adapters.forEach(adapter => {
  304. const fullAdapter: IPlatformAdapter = {
  305. ...adapter,
  306. request: jest.fn(),
  307. prepareRequest: jest.fn(),
  308. setProxyConfig: jest.fn(),
  309. getProxyStatus: jest.fn(),
  310. processResponse: jest.fn(),
  311. getConfig: jest.fn(),
  312. validateConfig: jest.fn().mockReturnValue(true)
  313. }
  314. httpClient.registerPlatform(adapter.platform, fullAdapter)
  315. })
  316. const health: HealthStatus = await httpClient.getHealth()
  317. expect(health).toBeDefined()
  318. expect(health.status).toBe('degraded') // Overall status should be degraded
  319. expect(health.platforms['healthy-platform']?.status).toBe('healthy')
  320. expect(health.platforms['unhealthy-platform']?.status).toBe('unhealthy')
  321. })
  322. })
  323. describe('Error Handling', () => {
  324. test('should reject registration with empty platform name', () => {
  325. const adapter: IPlatformAdapter = {
  326. platform: '',
  327. baseUrl: 'https://api.test.com',
  328. request: jest.fn(),
  329. prepareRequest: jest.fn(),
  330. setProxyConfig: jest.fn(),
  331. getProxyStatus: jest.fn(),
  332. processResponse: jest.fn(),
  333. checkHealth: jest.fn(),
  334. getConfig: jest.fn(),
  335. validateConfig: jest.fn().mockReturnValue(true)
  336. }
  337. expect(() => {
  338. httpClient.registerPlatform('', adapter)
  339. }).toThrow('Platform name cannot be empty')
  340. })
  341. test('should reject registration with mismatched platform name', () => {
  342. const adapter: IPlatformAdapter = {
  343. platform: 'adapter-platform',
  344. baseUrl: 'https://api.test.com',
  345. request: jest.fn(),
  346. prepareRequest: jest.fn(),
  347. setProxyConfig: jest.fn(),
  348. getProxyStatus: jest.fn(),
  349. processResponse: jest.fn(),
  350. checkHealth: jest.fn(),
  351. getConfig: jest.fn(),
  352. validateConfig: jest.fn().mockReturnValue(true)
  353. }
  354. expect(() => {
  355. httpClient.registerPlatform('different-platform', adapter)
  356. }).toThrow('Platform name mismatch')
  357. })
  358. test('should reject registration with null or undefined adapter', () => {
  359. expect(() => {
  360. httpClient.registerPlatform('test-platform', null as any)
  361. }).toThrow('Platform adapter cannot be null or undefined')
  362. expect(() => {
  363. httpClient.registerPlatform('test-platform', undefined as any)
  364. }).toThrow('Platform adapter cannot be null or undefined')
  365. })
  366. })
  367. describe('Resource Cleanup', () => {
  368. test('should properly clean up resources when client is closed', async () => {
  369. const adapter: IPlatformAdapter = {
  370. platform: 'cleanup-platform',
  371. baseUrl: 'https://api.cleanup.com',
  372. request: jest.fn(),
  373. prepareRequest: jest.fn(),
  374. setProxyConfig: jest.fn(),
  375. getProxyStatus: jest.fn(),
  376. processResponse: jest.fn(),
  377. checkHealth: jest.fn(),
  378. getConfig: jest.fn(),
  379. validateConfig: jest.fn().mockReturnValue(true)
  380. }
  381. httpClient.registerPlatform('cleanup-platform', adapter)
  382. expect(httpClient.isPlatformRegistered?.('cleanup-platform')).toBe(true)
  383. await httpClient.close()
  384. // After closing, platform should still be registered but client should be inactive
  385. expect(httpClient.isPlatformRegistered?.('cleanup-platform')).toBe(true)
  386. // Attempting to make requests should fail
  387. await expect(httpClient.request({
  388. platform: 'cleanup-platform',
  389. accountId: 'test',
  390. method: 'GET',
  391. url: '/test'
  392. })).rejects.toThrow('Client is closed')
  393. })
  394. })
  395. })