laplace-api 4.7.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,87 +5,73 @@ import {
5
5
  FundsClient,
6
6
  FundType,
7
7
  HistoricalFundPricePeriod,
8
- Fund,
9
- FundStats,
10
- FundDistribution,
11
- FundAssetCategory,
12
- FundHistoricalPrice,
13
- FundContentType
14
8
  } from "../client/funds";
15
9
  import { Region } from "../client/collections";
16
- import { AssetType } from "../client/stocks";
17
10
 
18
- const mockFundsResponse: Fund[] = [
11
+ const mockFundsResponse= [
19
12
  {
20
- assetType: AssetType.Stock,
21
- name: "Ak Portföy BIST 30 Endeksi Hisse Senedi Fonu",
22
- symbol: "SPP",
23
- active: true,
24
- managementFee: 0.003,
25
- riskLevel: 6,
26
- fundType: FundType.STOCK_UMBRELLA_FUND,
27
- ownerSymbol: "AKP"
28
- },
29
- {
30
- assetType: AssetType.Stock,
31
- name: "İş Portföy BIST 30 Endeksi Hisse Senedi Fonu",
32
- symbol: "TI3",
33
- active: true,
34
- managementFee: 0.0035,
35
- riskLevel: 6,
36
- fundType: FundType.STOCK_UMBRELLA_FUND,
37
- ownerSymbol: "IYP"
13
+ "assetType": "fund",
14
+ "name": "NEO PORTFÖY KATILIM HİSSE SENEDİ FONU (HİSSE SENEDİ YOĞUN FON)",
15
+ "symbol": "NKM",
16
+ "active": true,
17
+ "managementFee": 2.8,
18
+ "riskLevel": 0,
19
+ "fundType": "STOCK_UMBRELLA_FUND",
20
+ "ownerSymbol": "VVP"
38
21
  }
39
22
  ];
40
23
 
41
- const mockFundStatsResponse: FundStats = {
42
- yearBeta: 0.95,
43
- yearStdev: 0.12,
44
- ytdReturn: 0.15,
45
- yearMomentum: 0.08,
46
- yearlyReturn: 0.25,
47
- monthlyReturn: 0.03,
48
- fiveYearReturn: 1.85,
49
- sixMonthReturn: 0.18,
50
- threeYearReturn: 0.95,
51
- threeMonthReturn: 0.09
24
+ const mockFundStatsResponse = {
25
+ "monthlyReturn": -2.3367,
26
+ "threeMonthReturn": 3.8059,
27
+ "sixMonthReturn": 12.3031,
28
+ "ytdReturn": -0.8037,
29
+ "yearlyReturn": 31.9956,
30
+ "threeYearReturn": 281.8466,
31
+ "fiveYearReturn": 867.6593,
32
+ "yearMomentum": 0,
33
+ "yearBeta": 0.000068,
34
+ "yearStdev": 0.077
52
35
  };
53
36
 
54
- const mockFundDistributionResponse: FundDistribution = {
55
- categories: [
37
+ const mockFundDistributionResponse = {
38
+ "categories": [
56
39
  {
57
- category: FundAssetCategory.EQUITY,
58
- percentage: 95.5,
59
- assets: [
40
+ "assets": [
41
+ {
42
+ "type": "BIST_STOCK",
43
+ "symbol": "ASELS",
44
+ "wholePercentage": 30,
45
+ "categoryPercentage": 60
46
+ },
60
47
  {
61
- type: FundContentType.BIST_STOCK,
62
- symbol: "GARAN",
63
- wholePercentage: 9.8,
64
- categoryPercentage: 10.26
48
+ "type": "OTHER_STOCK",
49
+ "symbol": "NVDA",
50
+ "wholePercentage": 20,
51
+ "categoryPercentage": 40
65
52
  }
66
- ]
53
+ ],
54
+ "category": "EQUITY",
55
+ "percentage": 50
56
+ },
57
+ {
58
+ "category": "GOVERNMENT_BOND",
59
+ "percentage": 30
67
60
  },
68
61
  {
69
- category: FundAssetCategory.LIQUID_DEPOSIT,
70
- percentage: 4.5
62
+ "category": "CURRENCY",
63
+ "percentage": 20
71
64
  }
72
65
  ]
73
66
  };
74
67
 
75
- const mockHistoricalPricesResponse: FundHistoricalPrice[] = [
76
- {
77
- price: 15.25,
78
- aum: 1250000000,
79
- date: "2024-03-14T10:00:00Z",
80
- shareCount: 82000000,
81
- investorCount: 25000
82
- },
68
+ const mockHistoricalPricesResponse = [
83
69
  {
84
- price: 15.15,
85
- aum: 1245000000,
86
- date: "2024-03-13T10:00:00Z",
87
- shareCount: 82180000,
88
- investorCount: 24950
70
+ "date": "2026-02-20T00:00:00Z",
71
+ "price": 1.004711,
72
+ "aum": 5242278660.64,
73
+ "investorCount": 48900,
74
+ "shareCount": 5217695760
89
75
  }
90
76
  ];
91
77
 
@@ -119,6 +105,7 @@ describe("Funds Client", () => {
119
105
  expect(typeof fund.assetType).toBe("string");
120
106
  expect(typeof fund.managementFee).toBe("number");
121
107
  expect(typeof fund.riskLevel).toBe("number");
108
+ expect(typeof fund.active).toBe("boolean");
122
109
  expect(typeof fund.ownerSymbol).toBe("string");
123
110
  expect(Object.values(FundType)).toContain(fund.fundType);
124
111
  });
@@ -191,127 +178,209 @@ describe("Funds Client", () => {
191
178
  });
192
179
 
193
180
  describe("Mock Tests", () => {
181
+ let client: FundsClient;
182
+ let cli: { request: jest.Mock };
183
+
194
184
  beforeEach(() => {
195
- jest.clearAllMocks();
185
+ cli = { request: jest.fn() };
186
+
187
+ const config = (global as any).testSuite.config as LaplaceConfiguration;
188
+ const logger: Logger = {
189
+ info: jest.fn(),
190
+ error: jest.fn(),
191
+ warn: jest.fn(),
192
+ debug: jest.fn(),
193
+ } as unknown as Logger;
194
+
195
+ client = new FundsClient(config, logger, cli as any);
196
196
  });
197
-
197
+
198
198
  describe("getFunds", () => {
199
- test("should return funds list with mock data", async () => {
200
- jest.spyOn(client, 'getFunds').mockResolvedValue(mockFundsResponse);
201
-
202
- const resp = await client.getFunds(Region.Tr, 1, 10);
203
-
204
- expect(resp).toHaveLength(2);
205
-
206
- const firstFund = resp[0];
207
- expect(firstFund.symbol).toBe("SPP");
208
- expect(firstFund.name).toBe("Ak Portföy BIST 30 Endeksi Hisse Senedi Fonu");
209
- expect(firstFund.assetType).toBe(AssetType.Stock);
210
- expect(firstFund.managementFee).toBe(0.003);
211
- expect(firstFund.riskLevel).toBe(6);
212
- expect(firstFund.fundType).toBe(FundType.STOCK_UMBRELLA_FUND);
213
- expect(firstFund.ownerSymbol).toBe("AKP");
214
-
215
- expect(client.getFunds).toHaveBeenCalledWith(Region.Tr, 1, 10);
199
+ test("calls correct endpoint/params (without page) and matches raw response", async () => {
200
+ cli.request.mockResolvedValueOnce({ data: mockFundsResponse });
201
+
202
+ const resp = await client.getFunds(Region.Tr, 10);
203
+
204
+ expect(cli.request).toHaveBeenCalledTimes(1);
205
+ const call = cli.request.mock.calls[0][0];
206
+
207
+ expect(call.method).toBe("GET");
208
+ expect(call.url).toBe("/api/v1/fund");
209
+ expect(call.params).toEqual({
210
+ region: Region.Tr,
211
+ pageSize: 10,
212
+ });
213
+
214
+ expect(resp).toHaveLength(1);
215
+
216
+ const f = resp[0];
217
+ expect(f.assetType).toBe("fund");
218
+ expect(f.name).toBe(
219
+ "NEO PORTFÖY KATILIM HİSSE SENEDİ FONU (HİSSE SENEDİ YOĞUN FON)"
220
+ );
221
+ expect(f.symbol).toBe("NKM");
222
+ expect(f.active).toBe(true);
223
+ expect(f.managementFee).toBe(2.8);
224
+ expect(f.riskLevel).toBe(0);
225
+ expect(f.fundType).toBe("STOCK_UMBRELLA_FUND");
226
+ expect(f.ownerSymbol).toBe("VVP");
216
227
  });
217
-
218
- test("should handle pagination with mock data", async () => {
219
- jest.spyOn(client, 'getFunds').mockResolvedValue([mockFundsResponse[0]]);
220
-
221
- const resp = await client.getFunds(Region.Tr, 1, 1);
222
-
228
+
229
+ test("calls correct endpoint/params (with page) and matches raw response", async () => {
230
+ cli.request.mockResolvedValueOnce({ data: mockFundsResponse });
231
+
232
+ const resp = await client.getFunds(Region.Tr, 10, 2);
233
+
234
+ expect(cli.request).toHaveBeenCalledTimes(1);
235
+ const call = cli.request.mock.calls[0][0];
236
+
237
+ expect(call.method).toBe("GET");
238
+ expect(call.url).toBe("/api/v1/fund");
239
+ expect(call.params).toEqual({
240
+ region: Region.Tr,
241
+ pageSize: 10,
242
+ page: 2,
243
+ });
244
+
223
245
  expect(resp).toHaveLength(1);
224
- expect(resp[0].symbol).toBe("SPP");
225
-
226
- expect(client.getFunds).toHaveBeenCalledWith(Region.Tr, 1, 1);
246
+ expect(resp[0].symbol).toBe("NKM");
247
+ });
248
+
249
+ test("bubbles up request error", async () => {
250
+ cli.request.mockRejectedValueOnce(new Error("Bad request"));
251
+
252
+ await expect(client.getFunds(Region.Tr, 10)).rejects.toThrow("Bad request");
227
253
  });
228
254
  });
229
-
255
+
230
256
  describe("getFundStats", () => {
231
- test("should return fund statistics with mock data", async () => {
232
- jest.spyOn(client, 'getFundStats').mockResolvedValue(mockFundStatsResponse);
233
-
234
- const resp = await client.getFundStats("SPP", Region.Tr);
235
-
236
- expect(resp.yearBeta).toBe(0.95);
237
- expect(resp.yearStdev).toBe(0.12);
238
- expect(resp.ytdReturn).toBe(0.15);
239
- expect(resp.yearMomentum).toBe(0.08);
240
- expect(resp.yearlyReturn).toBe(0.25);
241
- expect(resp.monthlyReturn).toBe(0.03);
242
- expect(resp.fiveYearReturn).toBe(1.85);
243
- expect(resp.sixMonthReturn).toBe(0.18);
244
- expect(resp.threeYearReturn).toBe(0.95);
245
- expect(resp.threeMonthReturn).toBe(0.09);
246
-
247
- expect(client.getFundStats).toHaveBeenCalledWith("SPP", Region.Tr);
257
+ test("calls correct endpoint/params and matches raw response", async () => {
258
+ cli.request.mockResolvedValueOnce({ data: mockFundStatsResponse });
259
+
260
+ const resp = await client.getFundStats("NKM", Region.Tr);
261
+
262
+ expect(cli.request).toHaveBeenCalledTimes(1);
263
+ const call = cli.request.mock.calls[0][0];
264
+
265
+ expect(call.method).toBe("GET");
266
+ expect(call.url).toBe("/api/v1/fund/stats");
267
+ expect(call.params).toEqual({ symbol: "NKM", region: Region.Tr });
268
+
269
+ expect(resp.monthlyReturn).toBe(-2.3367);
270
+ expect(resp.threeMonthReturn).toBe(3.8059);
271
+ expect(resp.sixMonthReturn).toBe(12.3031);
272
+ expect(resp.ytdReturn).toBe(-0.8037);
273
+ expect(resp.yearlyReturn).toBe(31.9956);
274
+ expect(resp.threeYearReturn).toBe(281.8466);
275
+ expect(resp.fiveYearReturn).toBe(867.6593);
276
+ expect(resp.yearMomentum).toBe(0);
277
+ expect(resp.yearBeta).toBe(0.000068);
278
+ expect(resp.yearStdev).toBe(0.077);
248
279
  });
249
-
250
- test("should handle invalid fund symbol error", async () => {
251
- jest.spyOn(client, 'getFundStats').mockRejectedValue(new Error("Fund not found"));
252
-
253
- await expect(client.getFundStats("INVALID_FUND", Region.Tr))
254
- .rejects.toThrow("Fund not found");
280
+
281
+ test("bubbles up request error", async () => {
282
+ cli.request.mockRejectedValueOnce(new Error("Fund not found"));
283
+
284
+ await expect(client.getFundStats("INVALID", Region.Tr)).rejects.toThrow(
285
+ "Fund not found"
286
+ );
255
287
  });
256
288
  });
257
-
289
+
258
290
  describe("getFundDistribution", () => {
259
- test("should return fund distribution with mock data", async () => {
260
- jest.spyOn(client, 'getFundDistribution').mockResolvedValue(mockFundDistributionResponse);
261
-
262
- const resp = await client.getFundDistribution("SPP", Region.Tr);
263
-
264
- expect(resp.categories).toHaveLength(2);
265
-
266
- const equityCategory = resp.categories[0];
267
- expect(equityCategory.category).toBe(FundAssetCategory.EQUITY);
268
- expect(equityCategory.percentage).toBe(95.5);
269
- expect(equityCategory.assets).toBeDefined();
270
- expect(equityCategory.assets![0].symbol).toBe("GARAN");
271
-
272
- const liquidCategory = resp.categories[1];
273
- expect(liquidCategory.category).toBe(FundAssetCategory.LIQUID_DEPOSIT);
274
- expect(liquidCategory.percentage).toBe(4.5);
275
-
276
- expect(client.getFundDistribution).toHaveBeenCalledWith("SPP", Region.Tr);
291
+ test("calls correct endpoint/params and matches raw response", async () => {
292
+ cli.request.mockResolvedValueOnce({ data: mockFundDistributionResponse });
293
+
294
+ const resp = await client.getFundDistribution("NKM", Region.Tr);
295
+
296
+ expect(cli.request).toHaveBeenCalledTimes(1);
297
+ const call = cli.request.mock.calls[0][0];
298
+
299
+ expect(call.method).toBe("GET");
300
+ expect(call.url).toBe("/api/v1/fund/distribution");
301
+ expect(call.params).toEqual({ symbol: "NKM", region: Region.Tr });
302
+
303
+ expect(resp.categories).toHaveLength(3);
304
+
305
+ const c0 = resp.categories[0];
306
+ expect(c0.category).toBe("EQUITY");
307
+ expect(c0.percentage).toBe(50);
308
+ expect(Array.isArray(c0.assets)).toBe(true);
309
+ expect(c0.assets!).toHaveLength(2);
310
+
311
+ expect(c0.assets![0].type).toBe("BIST_STOCK");
312
+ expect(c0.assets![0].symbol).toBe("ASELS");
313
+ expect(c0.assets![0].wholePercentage).toBe(30);
314
+ expect(c0.assets![0].categoryPercentage).toBe(60);
315
+
316
+ expect(c0.assets![1].type).toBe("OTHER_STOCK");
317
+ expect(c0.assets![1].symbol).toBe("NVDA");
318
+ expect(c0.assets![1].wholePercentage).toBe(20);
319
+ expect(c0.assets![1].categoryPercentage).toBe(40);
320
+
321
+ const c1 = resp.categories[1];
322
+ expect(c1.category).toBe("GOVERNMENT_BOND");
323
+ expect(c1.percentage).toBe(30);
324
+ expect(c1.assets).toBeUndefined();
325
+
326
+ const c2 = resp.categories[2];
327
+ expect(c2.category).toBe("CURRENCY");
328
+ expect(c2.percentage).toBe(20);
329
+ expect(c2.assets).toBeUndefined();
330
+ });
331
+
332
+ test("bubbles up request error", async () => {
333
+ cli.request.mockRejectedValueOnce(new Error("Invalid symbol"));
334
+
335
+ await expect(client.getFundDistribution("INVALID", Region.Tr)).rejects.toThrow(
336
+ "Invalid symbol"
337
+ );
277
338
  });
278
339
  });
279
-
340
+
280
341
  describe("getHistoricalFundPrices", () => {
281
- test("should return historical prices with mock data", async () => {
282
- jest.spyOn(client, 'getHistoricalFundPrices').mockResolvedValue(mockHistoricalPricesResponse);
283
-
342
+ test("calls correct endpoint/params and matches raw response", async () => {
343
+ cli.request.mockResolvedValueOnce({ data: mockHistoricalPricesResponse });
344
+
284
345
  const resp = await client.getHistoricalFundPrices(
285
- "SPP",
286
- Region.Tr,
287
- HistoricalFundPricePeriod.OneMonth
288
- );
289
-
290
- expect(resp).toHaveLength(2);
291
-
292
- const firstPrice = resp[0];
293
- expect(firstPrice.price).toBe(15.25);
294
- expect(firstPrice.aum).toBe(1250000000);
295
- expect(firstPrice.date).toBe("2024-03-14T10:00:00Z");
296
- expect(firstPrice.shareCount).toBe(82000000);
297
- expect(firstPrice.investorCount).toBe(25000);
298
-
299
- expect(client.getHistoricalFundPrices).toHaveBeenCalledWith(
300
- "SPP",
346
+ "NKM",
301
347
  Region.Tr,
302
348
  HistoricalFundPricePeriod.OneMonth
303
349
  );
350
+
351
+ expect(cli.request).toHaveBeenCalledTimes(1);
352
+ const call = cli.request.mock.calls[0][0];
353
+
354
+ expect(call.method).toBe("GET");
355
+ expect(call.url).toBe("/api/v1/fund/price");
356
+ expect(call.params).toEqual({
357
+ symbol: "NKM",
358
+ region: Region.Tr,
359
+ period: HistoricalFundPricePeriod.OneMonth,
360
+ });
361
+
362
+ expect(resp).toHaveLength(1);
363
+
364
+ const p = resp[0];
365
+ expect(p.date).toBe("2026-02-20T00:00:00Z");
366
+ expect(p.price).toBe(1.004711);
367
+ expect(p.aum).toBe(5242278660.64);
368
+ expect(p.investorCount).toBe(48900);
369
+ expect(p.shareCount).toBe(5217695760);
304
370
  });
305
-
306
- test("should handle invalid period error", async () => {
307
- jest.spyOn(client, 'getHistoricalFundPrices').mockRejectedValue(new Error("Invalid period"));
308
-
309
- await expect(client.getHistoricalFundPrices(
310
- "SPP",
311
- Region.Tr,
312
- "INVALID_PERIOD" as HistoricalFundPricePeriod
313
- )).rejects.toThrow("Invalid period");
371
+
372
+ test("bubbles up request error", async () => {
373
+ cli.request.mockRejectedValueOnce(new Error("Invalid period"));
374
+
375
+ await expect(
376
+ client.getHistoricalFundPrices(
377
+ "NKM",
378
+ Region.Tr,
379
+ "INVALID_PERIOD" as HistoricalFundPricePeriod
380
+ )
381
+ ).rejects.toThrow("Invalid period");
314
382
  });
315
383
  });
316
384
  });
385
+
317
386
  });
@@ -1,4 +1,5 @@
1
1
  import { Collection, CollectionDetail } from "../client/collections";
2
+ import { Stock } from "../client/stocks";
2
3
 
3
4
  export const validateCollection = (collection: Collection) => {
4
5
  expect(typeof collection.id).toBe("string");
@@ -17,27 +18,19 @@ export const validateCollection = (collection: Collection) => {
17
18
  expect(typeof region).toBe("string");
18
19
  });
19
20
  }
20
-
21
- if (collection.assetClass !== undefined) {
22
- expect(typeof collection.assetClass).toBe("string");
23
- }
24
-
25
- if (collection.image !== undefined) {
26
- expect(typeof collection.image).toBe("string");
27
- }
28
-
29
- if (collection.order !== undefined) {
30
- expect(typeof collection.order).toBe("number");
21
+
22
+ if (collection.locale != null) {
23
+ expect(typeof collection.locale).toBe("string");
31
24
  }
32
25
 
33
- if (collection.status !== undefined) {
34
- expect(typeof collection.status).toBe("string");
26
+ if (collection.assetClass != null) {
27
+ expect(typeof collection.assetClass).toBe("string");
35
28
  }
36
29
 
37
- if (collection.metaData !== undefined) {
38
- expect(typeof collection.metaData).toBe("object");
39
- expect(collection.metaData).not.toBeNull();
40
- }
30
+ if (collection.image != null) expect(typeof collection.image).toBe("string");
31
+ if (collection.order != null) expect(typeof collection.order).toBe("number");
32
+ if (collection.status != null) expect(typeof collection.status).toBe("string");
33
+ if (collection.metaData != null) expect(typeof collection.metaData).toBe("object");
41
34
  };
42
35
 
43
36
  export const validateCollectionDetail = (collectionDetail: CollectionDetail) => {
@@ -46,13 +39,16 @@ export const validateCollection = (collection: Collection) => {
46
39
  expect(Array.isArray(collectionDetail.stocks)).toBe(true);
47
40
  expect(collectionDetail.stocks.length).toBeGreaterThan(0);
48
41
 
49
- const firstStock = collectionDetail.stocks[0];
50
- expect(typeof firstStock.id).toBe("string");
51
- expect(typeof firstStock.assetType).toBe("string");
52
- expect(typeof firstStock.name).toBe("string");
53
- expect(typeof firstStock.symbol).toBe("string");
54
- expect(typeof firstStock.sectorId).toBe("string");
55
- expect(typeof firstStock.industryId).toBe("string");
56
- expect(typeof firstStock.updatedDate).toBe("string");
57
- expect(typeof firstStock.active).toBe("boolean");
58
- };
42
+ collectionDetail.stocks.forEach(validateStockLite);
43
+ };
44
+
45
+ function validateStockLite(s: Stock) {
46
+ expect(typeof s.id).toBe("string");
47
+ expect(typeof s.assetType).toBe("string");
48
+ expect(typeof s.name).toBe("string");
49
+ expect(typeof s.symbol).toBe("string");
50
+ expect(typeof s.sectorId).toBe("string");
51
+ expect(typeof s.industryId).toBe("string");
52
+ expect(typeof s.updatedDate).toBe("string");
53
+ expect(typeof s.active).toBe("boolean");
54
+ }
@@ -1,15 +1,15 @@
1
1
  import { Logger } from "winston";
2
2
  import { LaplaceConfiguration } from "../utilities/configuration";
3
3
  import "./client_test_suite";
4
- import { KeyInsightClient, KeyInsight } from "../client/key-insights";
4
+ import { KeyInsightClient } from "../client/key-insights";
5
5
  import { Region } from "../client/collections";
6
6
 
7
- const mockTRKeyInsightResponse: KeyInsight = {
7
+ const mockTRKeyInsightResponse = {
8
8
  symbol: "TOASO",
9
9
  insight: "Tofaş'ın net kârı, güçlü operasyonel performans ve yüksek ihracat gelirleri sayesinde geçen yılın aynı dönemine göre %85 artış gösterdi."
10
10
  };
11
11
 
12
- const mockUSKeyInsightResponse: KeyInsight = {
12
+ const mockUSKeyInsightResponse = {
13
13
  symbol: "AAPL",
14
14
  insight: "Apple's revenue growth was driven by strong iPhone sales and continued expansion in services, with significant market share gains in emerging markets."
15
15
  };
@@ -59,52 +59,87 @@ describe("Key Insight", () => {
59
59
  });
60
60
 
61
61
  describe("Mock Tests", () => {
62
+ let client: KeyInsightClient;
63
+ let cli: { request: jest.Mock };
64
+
62
65
  beforeEach(() => {
63
- jest.clearAllMocks();
66
+ cli = { request: jest.fn() };
67
+
68
+ const config = (global as any).testSuite.config as LaplaceConfiguration;
69
+ const logger: Logger = {
70
+ info: jest.fn(),
71
+ error: jest.fn(),
72
+ warn: jest.fn(),
73
+ debug: jest.fn(),
74
+ } as unknown as Logger;
75
+
76
+ client = new KeyInsightClient(config, logger, cli as any);
64
77
  });
65
-
78
+
66
79
  describe("getKeyInsights", () => {
67
- test("should return TR key insights with mock data", async () => {
68
- jest.spyOn(client, 'getKeyInsights').mockResolvedValue(mockTRKeyInsightResponse);
69
-
80
+ test("TR: calls correct endpoint/params and matches raw response", async () => {
81
+ cli.request.mockResolvedValueOnce({ data: mockTRKeyInsightResponse });
82
+
70
83
  const resp = await client.getKeyInsights("TOASO", Region.Tr);
71
-
72
- expect(resp).toBeDefined();
84
+
85
+ expect(cli.request).toHaveBeenCalledTimes(1);
86
+ const call = cli.request.mock.calls[0][0];
87
+
88
+ expect(call.method).toBe("GET");
89
+ expect(call.url).toBe("/api/v1/key-insights");
90
+ expect(call.params).toEqual({
91
+ symbol: "TOASO",
92
+ region: Region.Tr,
93
+ });
94
+
73
95
  expect(resp.symbol).toBe("TOASO");
74
96
  expect(resp.insight).toBe(mockTRKeyInsightResponse.insight);
75
-
76
- expect(client.getKeyInsights).toHaveBeenCalledWith("TOASO", Region.Tr);
77
97
  });
78
-
79
- test("should return US key insights with mock data", async () => {
80
- jest.spyOn(client, 'getKeyInsights').mockResolvedValue(mockUSKeyInsightResponse);
81
-
98
+
99
+ test("US: calls correct endpoint/params and matches raw response", async () => {
100
+ cli.request.mockResolvedValueOnce({ data: mockUSKeyInsightResponse });
101
+
82
102
  const resp = await client.getKeyInsights("AAPL", Region.Us);
83
-
84
- expect(resp).toBeDefined();
103
+
104
+ expect(cli.request).toHaveBeenCalledTimes(1);
105
+ const call = cli.request.mock.calls[0][0];
106
+
107
+ expect(call.method).toBe("GET");
108
+ expect(call.url).toBe("/api/v1/key-insights");
109
+ expect(call.params).toEqual({
110
+ symbol: "AAPL",
111
+ region: Region.Us,
112
+ });
113
+
85
114
  expect(resp.symbol).toBe("AAPL");
86
115
  expect(resp.insight).toBe(mockUSKeyInsightResponse.insight);
87
-
88
- expect(client.getKeyInsights).toHaveBeenCalledWith("AAPL", Region.Us);
89
116
  });
90
-
91
- test("should handle invalid symbol error", async () => {
92
- jest.spyOn(client, 'getKeyInsights').mockRejectedValue(new Error("Symbol not found"));
93
-
94
- await expect(client.getKeyInsights("INVALID_SYMBOL", Region.Tr))
95
- .rejects.toThrow("Symbol not found");
96
-
97
- expect(client.getKeyInsights).toHaveBeenCalledWith("INVALID_SYMBOL", Region.Tr);
117
+
118
+ test("bubbles up request error", async () => {
119
+ cli.request.mockRejectedValueOnce(new Error("Symbol not found"));
120
+
121
+ await expect(client.getKeyInsights("INVALID_SYMBOL", Region.Tr)).rejects.toThrow(
122
+ "Symbol not found"
123
+ );
124
+
125
+ expect(cli.request).toHaveBeenCalledTimes(1);
98
126
  });
99
-
100
- test("should handle empty symbol error", async () => {
101
- jest.spyOn(client, 'getKeyInsights').mockRejectedValue(new Error("Symbol cannot be empty"));
102
-
103
- await expect(client.getKeyInsights("", Region.Tr))
104
- .rejects.toThrow("Symbol cannot be empty");
105
-
106
- expect(client.getKeyInsights).toHaveBeenCalledWith("", Region.Tr);
127
+
128
+ test("empty symbol: bubbles up request error", async () => {
129
+ cli.request.mockRejectedValueOnce(new Error("Symbol cannot be empty"));
130
+
131
+ await expect(client.getKeyInsights("", Region.Tr)).rejects.toThrow(
132
+ "Symbol cannot be empty"
133
+ );
134
+
135
+ expect(cli.request).toHaveBeenCalledTimes(1);
136
+ const call = cli.request.mock.calls[0][0];
137
+
138
+ expect(call.method).toBe("GET");
139
+ expect(call.url).toBe("/api/v1/key-insights");
140
+ expect(call.params).toEqual({ symbol: "", region: Region.Tr });
107
141
  });
108
142
  });
109
143
  });
144
+
110
145
  });