test_hedging_sessions_post.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. "use strict";
  2. /**
  3. * Contract test for POST /api/v1/hedging/sessions
  4. * Tests the API contract for creating hedging sessions
  5. */
  6. var __importDefault = (this && this.__importDefault) || function (mod) {
  7. return (mod && mod.__esModule) ? mod : { "default": mod };
  8. };
  9. Object.defineProperty(exports, "__esModule", { value: true });
  10. const globals_1 = require("@jest/globals");
  11. const axios_1 = __importDefault(require("axios"));
  12. (0, globals_1.describe)('POST /api/v1/hedging/sessions', () => {
  13. let client;
  14. const baseURL = 'http://localhost:3000/api/v1/hedging';
  15. (0, globals_1.beforeAll)(() => {
  16. client = axios_1.default.create({
  17. baseURL,
  18. headers: {
  19. 'Content-Type': 'application/json',
  20. 'Authorization': 'Bearer test-api-key'
  21. },
  22. timeout: 5000
  23. });
  24. });
  25. (0, globals_1.afterAll)(() => {
  26. // Cleanup if needed
  27. });
  28. (0, globals_1.describe)('Request Schema Validation', () => {
  29. (0, globals_1.it)('should accept valid hedging session creation request', async () => {
  30. const validRequest = {
  31. name: 'Test Hedging Session',
  32. accountIds: ['account-1', 'account-2'],
  33. volumeTarget: 10000,
  34. strategy: {
  35. symbol: 'ETH/USD',
  36. volumeDistribution: 'equal',
  37. priceRange: {
  38. min: 0.001,
  39. max: 0.01
  40. },
  41. timing: {
  42. minInterval: 30,
  43. maxInterval: 120,
  44. orderSize: {
  45. min: 100,
  46. max: 500
  47. }
  48. },
  49. riskLimits: {
  50. maxPositionSize: 0.1,
  51. stopLossThreshold: 0.05,
  52. maxSlippage: 0.02
  53. },
  54. orderTypes: {
  55. primary: 'limit',
  56. fallback: 'market'
  57. }
  58. }
  59. };
  60. try {
  61. const response = await client.post('/sessions', validRequest);
  62. // Should return 201 Created
  63. (0, globals_1.expect)(response.status).toBe(201);
  64. // Response should match schema
  65. const responseData = response.data;
  66. (0, globals_1.expect)(responseData.success).toBe(true);
  67. (0, globals_1.expect)(responseData.session).toBeDefined();
  68. (0, globals_1.expect)(responseData.session.id).toBeDefined();
  69. (0, globals_1.expect)(responseData.session.name).toBe(validRequest.name);
  70. (0, globals_1.expect)(responseData.session.status).toBe('pending');
  71. (0, globals_1.expect)(responseData.session.accounts).toEqual(validRequest.accountIds);
  72. (0, globals_1.expect)(responseData.session.strategy).toEqual(validRequest.strategy);
  73. (0, globals_1.expect)(responseData.session.volumeTarget).toBe(validRequest.volumeTarget);
  74. (0, globals_1.expect)(responseData.session.volumeGenerated).toBe(0);
  75. (0, globals_1.expect)(responseData.session.startTime).toBeDefined();
  76. (0, globals_1.expect)(responseData.session.riskBreaches).toEqual([]);
  77. (0, globals_1.expect)(responseData.session.orders).toEqual([]);
  78. }
  79. catch (error) {
  80. // This test should fail initially since the endpoint doesn't exist yet
  81. (0, globals_1.expect)(error.response?.status).toBe(404);
  82. }
  83. });
  84. (0, globals_1.it)('should reject request with invalid session name', async () => {
  85. const invalidRequest = {
  86. name: 'ab', // Too short (less than 3 characters)
  87. accountIds: ['account-1', 'account-2'],
  88. volumeTarget: 10000,
  89. strategy: {
  90. symbol: 'ETH/USD',
  91. volumeDistribution: 'equal',
  92. priceRange: { min: 0.001, max: 0.01 },
  93. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  94. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  95. orderTypes: { primary: 'limit', fallback: 'market' }
  96. }
  97. };
  98. try {
  99. await client.post('/sessions', invalidRequest);
  100. fail('Should have rejected invalid request');
  101. }
  102. catch (error) {
  103. (0, globals_1.expect)(error.response?.status).toBe(400);
  104. (0, globals_1.expect)(error.response?.data.success).toBe(false);
  105. (0, globals_1.expect)(error.response?.data.error.code).toBe('INVALID_STRATEGY');
  106. }
  107. });
  108. (0, globals_1.it)('should reject request with insufficient accounts', async () => {
  109. const invalidRequest = {
  110. name: 'Test Session',
  111. accountIds: ['account-1'], // Only one account (need at least 2)
  112. volumeTarget: 10000,
  113. strategy: {
  114. symbol: 'ETH/USD',
  115. volumeDistribution: 'equal',
  116. priceRange: { min: 0.001, max: 0.01 },
  117. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  118. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  119. orderTypes: { primary: 'limit', fallback: 'market' }
  120. }
  121. };
  122. try {
  123. await client.post('/sessions', invalidRequest);
  124. fail('Should have rejected invalid request');
  125. }
  126. catch (error) {
  127. (0, globals_1.expect)(error.response?.status).toBe(400);
  128. (0, globals_1.expect)(error.response?.data.success).toBe(false);
  129. (0, globals_1.expect)(error.response?.data.error.code).toBe('INSUFFICIENT_ACCOUNTS');
  130. }
  131. });
  132. (0, globals_1.it)('should reject request with negative volume target', async () => {
  133. const invalidRequest = {
  134. name: 'Test Session',
  135. accountIds: ['account-1', 'account-2'],
  136. volumeTarget: -1000, // Negative volume target
  137. strategy: {
  138. symbol: 'ETH/USD',
  139. volumeDistribution: 'equal',
  140. priceRange: { min: 0.001, max: 0.01 },
  141. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  142. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  143. orderTypes: { primary: 'limit', fallback: 'market' }
  144. }
  145. };
  146. try {
  147. await client.post('/sessions', invalidRequest);
  148. fail('Should have rejected invalid request');
  149. }
  150. catch (error) {
  151. (0, globals_1.expect)(error.response?.status).toBe(400);
  152. (0, globals_1.expect)(error.response?.data.success).toBe(false);
  153. (0, globals_1.expect)(error.response?.data.error.code).toBe('INVALID_STRATEGY');
  154. }
  155. });
  156. (0, globals_1.it)('should reject request with invalid strategy parameters', async () => {
  157. const invalidRequest = {
  158. name: 'Test Session',
  159. accountIds: ['account-1', 'account-2'],
  160. volumeTarget: 10000,
  161. strategy: {
  162. symbol: 'ETH/USD',
  163. volumeDistribution: 'equal',
  164. priceRange: {
  165. min: 0.01,
  166. max: 0.001 // min > max (invalid)
  167. },
  168. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  169. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  170. orderTypes: { primary: 'limit', fallback: 'market' }
  171. }
  172. };
  173. try {
  174. await client.post('/sessions', invalidRequest);
  175. fail('Should have rejected invalid request');
  176. }
  177. catch (error) {
  178. (0, globals_1.expect)(error.response?.status).toBe(400);
  179. (0, globals_1.expect)(error.response?.data.success).toBe(false);
  180. (0, globals_1.expect)(error.response?.data.error.code).toBe('INVALID_STRATEGY');
  181. }
  182. });
  183. });
  184. (0, globals_1.describe)('Authentication', () => {
  185. (0, globals_1.it)('should reject request without authorization header', async () => {
  186. const clientWithoutAuth = axios_1.default.create({
  187. baseURL,
  188. headers: {
  189. 'Content-Type': 'application/json'
  190. }
  191. });
  192. const validRequest = {
  193. name: 'Test Session',
  194. accountIds: ['account-1', 'account-2'],
  195. volumeTarget: 10000,
  196. strategy: {
  197. symbol: 'ETH/USD',
  198. volumeDistribution: 'equal',
  199. priceRange: { min: 0.001, max: 0.01 },
  200. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  201. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  202. orderTypes: { primary: 'limit', fallback: 'market' }
  203. }
  204. };
  205. try {
  206. await clientWithoutAuth.post('/sessions', validRequest);
  207. fail('Should have rejected unauthorized request');
  208. }
  209. catch (error) {
  210. (0, globals_1.expect)(error.response?.status).toBe(401);
  211. }
  212. });
  213. (0, globals_1.it)('should reject request with invalid authorization token', async () => {
  214. const clientWithInvalidAuth = axios_1.default.create({
  215. baseURL,
  216. headers: {
  217. 'Content-Type': 'application/json',
  218. 'Authorization': 'Bearer invalid-token'
  219. }
  220. });
  221. const validRequest = {
  222. name: 'Test Session',
  223. accountIds: ['account-1', 'account-2'],
  224. volumeTarget: 10000,
  225. strategy: {
  226. symbol: 'ETH/USD',
  227. volumeDistribution: 'equal',
  228. priceRange: { min: 0.001, max: 0.01 },
  229. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  230. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  231. orderTypes: { primary: 'limit', fallback: 'market' }
  232. }
  233. };
  234. try {
  235. await clientWithInvalidAuth.post('/sessions', validRequest);
  236. fail('Should have rejected request with invalid token');
  237. }
  238. catch (error) {
  239. (0, globals_1.expect)(error.response?.status).toBe(401);
  240. }
  241. });
  242. });
  243. (0, globals_1.describe)('Business Logic Validation', () => {
  244. (0, globals_1.it)('should reject request with inactive accounts', async () => {
  245. const requestWithInactiveAccount = {
  246. name: 'Test Session',
  247. accountIds: ['account-1', 'inactive-account'], // inactive-account is not active
  248. volumeTarget: 10000,
  249. strategy: {
  250. symbol: 'ETH/USD',
  251. volumeDistribution: 'equal',
  252. priceRange: { min: 0.001, max: 0.01 },
  253. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  254. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  255. orderTypes: { primary: 'limit', fallback: 'market' }
  256. }
  257. };
  258. try {
  259. await client.post('/sessions', requestWithInactiveAccount);
  260. fail('Should have rejected request with inactive account');
  261. }
  262. catch (error) {
  263. (0, globals_1.expect)(error.response?.status).toBe(400);
  264. (0, globals_1.expect)(error.response?.data.success).toBe(false);
  265. (0, globals_1.expect)(error.response?.data.error.code).toBe('ACCOUNT_NOT_ACTIVE');
  266. }
  267. });
  268. (0, globals_1.it)('should reject request with insufficient account balance', async () => {
  269. const requestWithInsufficientBalance = {
  270. name: 'Test Session',
  271. accountIds: ['account-1', 'poor-account'], // poor-account has insufficient balance
  272. volumeTarget: 1000000, // Very high volume target
  273. strategy: {
  274. symbol: 'ETH/USD',
  275. volumeDistribution: 'equal',
  276. priceRange: { min: 0.001, max: 0.01 },
  277. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  278. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  279. orderTypes: { primary: 'limit', fallback: 'market' }
  280. }
  281. };
  282. try {
  283. await client.post('/sessions', requestWithInsufficientBalance);
  284. fail('Should have rejected request with insufficient balance');
  285. }
  286. catch (error) {
  287. (0, globals_1.expect)(error.response?.status).toBe(400);
  288. (0, globals_1.expect)(error.response?.data.success).toBe(false);
  289. (0, globals_1.expect)(error.response?.data.error.code).toBe('INSUFFICIENT_BALANCE');
  290. }
  291. });
  292. });
  293. });
  294. //# sourceMappingURL=test_hedging_sessions_post.js.map