test_hedging_sessions_get.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. /**
  2. * Contract test for GET /api/v1/hedging/sessions
  3. * Tests the API contract for listing hedging sessions
  4. */
  5. import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
  6. import axios, { AxiosInstance } from 'axios';
  7. import { ListHedgingSessionsResponse } from '../../src/types/hedging';
  8. describe('GET /api/v1/hedging/sessions', () => {
  9. let client: AxiosInstance;
  10. const baseURL = 'http://localhost:3000/api/v1/hedging';
  11. beforeAll(() => {
  12. client = axios.create({
  13. baseURL,
  14. headers: {
  15. 'Content-Type': 'application/json',
  16. 'Authorization': 'Bearer test-api-key'
  17. },
  18. timeout: 5000
  19. });
  20. });
  21. afterAll(() => {
  22. // Cleanup if needed
  23. });
  24. describe('Basic Functionality', () => {
  25. it('should return list of hedging sessions', async () => {
  26. try {
  27. const response = await client.get('/sessions');
  28. // Should return 200 OK
  29. expect(response.status).toBe(200);
  30. // Response should match schema
  31. const responseData: ListHedgingSessionsResponse = response.data;
  32. expect(responseData.success).toBe(true);
  33. expect(responseData.sessions).toBeDefined();
  34. expect(Array.isArray(responseData.sessions)).toBe(true);
  35. expect(responseData.total).toBeDefined();
  36. expect(typeof responseData.total).toBe('number');
  37. expect(responseData.limit).toBeDefined();
  38. expect(responseData.offset).toBeDefined();
  39. // Each session should have required fields
  40. responseData.sessions.forEach(session => {
  41. expect(session.id).toBeDefined();
  42. expect(session.name).toBeDefined();
  43. expect(session.status).toBeDefined();
  44. expect(['pending', 'active', 'paused', 'completed', 'failed']).toContain(session.status);
  45. expect(session.accounts).toBeDefined();
  46. expect(Array.isArray(session.accounts)).toBe(true);
  47. expect(session.volumeTarget).toBeDefined();
  48. expect(session.volumeGenerated).toBeDefined();
  49. expect(session.startTime).toBeDefined();
  50. });
  51. } catch (error) {
  52. // This test should fail initially since the endpoint doesn't exist yet
  53. expect(error.response?.status).toBe(404);
  54. }
  55. });
  56. it('should return empty list when no sessions exist', async () => {
  57. try {
  58. const response = await client.get('/sessions');
  59. const responseData: ListHedgingSessionsResponse = response.data;
  60. expect(responseData.success).toBe(true);
  61. expect(responseData.sessions).toEqual([]);
  62. expect(responseData.total).toBe(0);
  63. } catch (error) {
  64. // This test should fail initially since the endpoint doesn't exist yet
  65. expect(error.response?.status).toBe(404);
  66. }
  67. });
  68. });
  69. describe('Query Parameters', () => {
  70. it('should filter sessions by status', async () => {
  71. try {
  72. const response = await client.get('/sessions?status=active');
  73. expect(response.status).toBe(200);
  74. const responseData: ListHedgingSessionsResponse = response.data;
  75. expect(responseData.success).toBe(true);
  76. // All returned sessions should have active status
  77. responseData.sessions.forEach(session => {
  78. expect(session.status).toBe('active');
  79. });
  80. } catch (error) {
  81. // This test should fail initially since the endpoint doesn't exist yet
  82. expect(error.response?.status).toBe(404);
  83. }
  84. });
  85. it('should filter sessions by account ID', async () => {
  86. try {
  87. const response = await client.get('/sessions?accountId=account-1');
  88. expect(response.status).toBe(200);
  89. const responseData: ListHedgingSessionsResponse = response.data;
  90. expect(responseData.success).toBe(true);
  91. // All returned sessions should include the specified account
  92. responseData.sessions.forEach(session => {
  93. expect(session.accounts).toContain('account-1');
  94. });
  95. } catch (error) {
  96. // This test should fail initially since the endpoint doesn't exist yet
  97. expect(error.response?.status).toBe(404);
  98. }
  99. });
  100. it('should support pagination with limit and offset', async () => {
  101. try {
  102. const response = await client.get('/sessions?limit=10&offset=20');
  103. expect(response.status).toBe(200);
  104. const responseData: ListHedgingSessionsResponse = response.data;
  105. expect(responseData.success).toBe(true);
  106. expect(responseData.limit).toBe(10);
  107. expect(responseData.offset).toBe(20);
  108. expect(responseData.sessions.length).toBeLessThanOrEqual(10);
  109. } catch (error) {
  110. // This test should fail initially since the endpoint doesn't exist yet
  111. expect(error.response?.status).toBe(404);
  112. }
  113. });
  114. it('should use default pagination values', async () => {
  115. try {
  116. const response = await client.get('/sessions');
  117. expect(response.status).toBe(200);
  118. const responseData: ListHedgingSessionsResponse = response.data;
  119. expect(responseData.success).toBe(true);
  120. expect(responseData.limit).toBe(50); // Default limit
  121. expect(responseData.offset).toBe(0); // Default offset
  122. } catch (error) {
  123. // This test should fail initially since the endpoint doesn't exist yet
  124. expect(error.response?.status).toBe(404);
  125. }
  126. });
  127. it('should handle multiple query parameters', async () => {
  128. try {
  129. const response = await client.get('/sessions?status=active&accountId=account-1&limit=5&offset=0');
  130. expect(response.status).toBe(200);
  131. const responseData: ListHedgingSessionsResponse = response.data;
  132. expect(responseData.success).toBe(true);
  133. expect(responseData.limit).toBe(5);
  134. expect(responseData.offset).toBe(0);
  135. // All returned sessions should match both filters
  136. responseData.sessions.forEach(session => {
  137. expect(session.status).toBe('active');
  138. expect(session.accounts).toContain('account-1');
  139. });
  140. } catch (error) {
  141. // This test should fail initially since the endpoint doesn't exist yet
  142. expect(error.response?.status).toBe(404);
  143. }
  144. });
  145. });
  146. describe('Error Handling', () => {
  147. it('should reject request without authorization header', async () => {
  148. const clientWithoutAuth = axios.create({
  149. baseURL,
  150. headers: {
  151. 'Content-Type': 'application/json'
  152. }
  153. });
  154. try {
  155. await clientWithoutAuth.get('/sessions');
  156. fail('Should have rejected unauthorized request');
  157. } catch (error) {
  158. expect(error.response?.status).toBe(401);
  159. }
  160. });
  161. it('should reject request with invalid authorization token', async () => {
  162. const clientWithInvalidAuth = axios.create({
  163. baseURL,
  164. headers: {
  165. 'Content-Type': 'application/json',
  166. 'Authorization': 'Bearer invalid-token'
  167. }
  168. });
  169. try {
  170. await clientWithInvalidAuth.get('/sessions');
  171. fail('Should have rejected request with invalid token');
  172. } catch (error) {
  173. expect(error.response?.status).toBe(401);
  174. }
  175. });
  176. it('should handle invalid query parameters gracefully', async () => {
  177. try {
  178. const response = await client.get('/sessions?status=invalid_status');
  179. expect(response.status).toBe(400);
  180. expect(response.data.success).toBe(false);
  181. expect(response.data.error.code).toBe('INVALID_QUERY_PARAMETER');
  182. } catch (error) {
  183. // This test should fail initially since the endpoint doesn't exist yet
  184. expect(error.response?.status).toBe(404);
  185. }
  186. });
  187. it('should handle invalid pagination parameters', async () => {
  188. try {
  189. const response = await client.get('/sessions?limit=-1&offset=-5');
  190. expect(response.status).toBe(400);
  191. expect(response.data.success).toBe(false);
  192. expect(response.data.error.code).toBe('INVALID_QUERY_PARAMETER');
  193. } catch (error) {
  194. // This test should fail initially since the endpoint doesn't exist yet
  195. expect(error.response?.status).toBe(404);
  196. }
  197. });
  198. });
  199. describe('Rate Limiting', () => {
  200. it('should handle rate limiting gracefully', async () => {
  201. // Make multiple requests quickly to test rate limiting
  202. const requests = Array(10).fill(null).map(() => client.get('/sessions'));
  203. try {
  204. const responses = await Promise.all(requests);
  205. // All requests should succeed (rate limit not exceeded)
  206. responses.forEach(response => {
  207. expect(response.status).toBe(200);
  208. });
  209. } catch (error) {
  210. // If rate limited, should return 429
  211. if (error.response?.status === 429) {
  212. expect(error.response.status).toBe(429);
  213. expect(error.response.data.error.code).toBe('RATE_LIMIT_EXCEEDED');
  214. } else {
  215. // Otherwise, endpoint doesn't exist yet
  216. expect(error.response?.status).toBe(404);
  217. }
  218. }
  219. });
  220. });
  221. });