laplace-api 4.3.3 → 4.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "laplace-api",
3
- "version": "4.3.3",
3
+ "version": "4.5.0",
4
4
  "description": "Client library for Laplace API for the US stock market and BIST (Istanbul stock market) fundamental financial data.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,3 +1,5 @@
1
+ import { OrderbookLiveData } from "./live-price";
2
+
1
3
  interface RawBISTStockLiveData {
2
4
  _id: number;
3
5
  symbol: string;
@@ -33,14 +35,15 @@ export enum LivePriceFeed {
33
35
  LiveUs = "live_price_us",
34
36
  DelayedBist = "delayed_price_tr",
35
37
  DelayedUs = "delayed_price_us",
36
- // DepthBist = "depth_tr",
38
+ DepthBist = "depth_tr",
37
39
  }
38
40
 
39
41
  type StockLiveDataType<T extends LivePriceFeed> = T extends
40
42
  | LivePriceFeed.LiveBist
41
43
  | LivePriceFeed.DelayedBist
42
- ? // | LivePriceFeed.DepthBist
43
- BISTStockLiveData
44
+ ? BISTStockLiveData
45
+ : T extends LivePriceFeed.DepthBist
46
+ ? OrderbookLiveData
44
47
  : USStockLiveData;
45
48
 
46
49
  export enum LogLevel {
@@ -94,14 +97,10 @@ export class LivePriceWebSocketClient {
94
97
  number,
95
98
  {
96
99
  symbols: string[];
97
- handler: (data: BISTStockLiveData | USStockLiveData) => void;
100
+ handler: (data: BISTStockLiveData | USStockLiveData | OrderbookLiveData) => void;
98
101
  feed: LivePriceFeed;
99
102
  }
100
103
  >();
101
- private symbolLastData = new Map<
102
- string,
103
- BISTStockLiveData | USStockLiveData
104
- >();
105
104
  private reconnectAttempts = 0;
106
105
  private reconnectTimeout: NodeJS.Timeout | null = null;
107
106
  private isClosed: boolean = false;
@@ -276,13 +275,11 @@ export class LivePriceWebSocketClient {
276
275
  WebSocketErrorType.MESSAGE_PARSE_ERROR
277
276
  );
278
277
  }
279
- let priceData: BISTStockLiveData | USStockLiveData;
278
+ let priceData: BISTStockLiveData | USStockLiveData | OrderbookLiveData;
280
279
 
281
280
  if (
282
281
  feed === LivePriceFeed.DelayedBist ||
283
282
  feed === LivePriceFeed.LiveBist
284
- // ||
285
- // feed === LivePriceFeed.DepthBist
286
283
  ) {
287
284
  const message = messageData as RawBISTStockLiveData;
288
285
  priceData = {
@@ -293,6 +290,13 @@ export class LivePriceWebSocketClient {
293
290
  timestamp: message?.d,
294
291
  percentChange: message?.c,
295
292
  } as BISTStockLiveData;
293
+ } else if (feed === LivePriceFeed.DepthBist) {
294
+ const message = messageData as OrderbookLiveData;
295
+ priceData = {
296
+ updated: message.updated,
297
+ deleted: message.deleted,
298
+ symbol: message?.symbol,
299
+ } as OrderbookLiveData;
296
300
  } else {
297
301
  const message = messageData as RawUSStockLiveData;
298
302
  priceData = {
@@ -302,7 +306,6 @@ export class LivePriceWebSocketClient {
302
306
  } as USStockLiveData;
303
307
  }
304
308
  if (priceData.symbol) {
305
- this.symbolLastData.set(priceData.symbol, priceData);
306
309
  const handlers = this.getHandlersForSymbol(
307
310
  priceData.symbol,
308
311
  feed
@@ -409,7 +412,7 @@ export class LivePriceWebSocketClient {
409
412
  const subscriptionId = this.subscriptionCounter++;
410
413
  let symbolsToAdd: string[] = [];
411
414
 
412
- const typedHandler = (data: BISTStockLiveData | USStockLiveData) => {
415
+ const typedHandler = (data: BISTStockLiveData | USStockLiveData | OrderbookLiveData) => {
413
416
  handler(data as StockLiveDataType<F>);
414
417
  };
415
418
 
@@ -423,12 +426,6 @@ export class LivePriceWebSocketClient {
423
426
  const symbolHandlers = this.getHandlersForSymbol(symbol, feed);
424
427
  if (symbolHandlers.length === 1) {
425
428
  symbolsToAdd.push(symbol);
426
- } else if (symbolHandlers.length > 1) {
427
- const lastData: BISTStockLiveData | USStockLiveData | undefined =
428
- this.symbolLastData.get(symbol);
429
- if (lastData) {
430
- typedHandler(lastData);
431
- }
432
429
  }
433
430
  }
434
431
  this.addSymbols(symbolsToAdd, feed);
@@ -445,7 +442,7 @@ export class LivePriceWebSocketClient {
445
442
  private getHandlersForSymbol(
446
443
  symbol: string,
447
444
  feed: LivePriceFeed
448
- ): ((data: BISTStockLiveData | USStockLiveData) => void)[] {
445
+ ): ((data: BISTStockLiveData | USStockLiveData | OrderbookLiveData) => void)[] {
449
446
  return Array.from(this.subscriptions.values())
450
447
  .filter((s) => s.symbols.includes(symbol) && s.feed === feed)
451
448
  .map((s) => s.handler);
@@ -26,6 +26,7 @@ export interface USStockPriceData {
26
26
 
27
27
  export type BISTStockStreamData = StreamMessage<BISTStockPriceData>;
28
28
  export type USStockStreamData = StreamMessage<USStockPriceData>;
29
+ export type BISTBidAskStreamData = StreamMessage<BISTBidAskData>;
29
30
 
30
31
  export enum OrderbookLevelSide {
31
32
  Bid = "bid",
@@ -40,6 +41,13 @@ export interface OrderbookLevel {
40
41
  side: OrderbookLevelSide;
41
42
  }
42
43
 
44
+ export interface BISTBidAskData {
45
+ d: string;
46
+ s: string;
47
+ ask: number;
48
+ bid: number;
49
+ }
50
+
43
51
  export interface OrderbookDeletedLevel {
44
52
  level: number;
45
53
  side: OrderbookLevelSide;
@@ -55,6 +63,7 @@ export enum PriceDataType {
55
63
  Live = "live",
56
64
  Delayed = "delayed",
57
65
  Orderbook = "orderbook",
66
+ Bids = "bids",
58
67
  }
59
68
 
60
69
  interface WebSocketUrlResponse {
@@ -87,6 +96,13 @@ interface UpdateUserDetailsParams {
87
96
  active: boolean;
88
97
  }
89
98
 
99
+ export interface SendWebsocketEventRequest {
100
+ externalUserID?: string;
101
+ event: Record<string, any>;
102
+ transient?: boolean;
103
+ broadCastToAll?: boolean;
104
+ }
105
+
90
106
  export interface ILivePriceClient<T> {
91
107
  close(): void;
92
108
  receive(): AsyncIterable<T>;
@@ -155,6 +171,13 @@ class LivePriceClientImpl<T> implements ILivePriceClient<T> {
155
171
  this.region
156
172
  }&stream=${streamId}`;
157
173
  break;
174
+ case PriceDataType.Bids:
175
+ url = `${
176
+ this.client["baseUrl"]
177
+ }/api/v1/stock/price/bids?filter=${symbols.join(",")}&region=${
178
+ this.region
179
+ }&stream=${streamId}`;
180
+ break;
158
181
  }
159
182
 
160
183
  const { events, cancel } = this.client.sendSSERequest<T>(url);
@@ -217,6 +240,27 @@ function getOrderbook<T>(
217
240
  return orderbookClient;
218
241
  }
219
242
 
243
+ function getBidAsk<T>(
244
+ client: Client,
245
+ symbols: string[],
246
+ region: Region
247
+ ): ILivePriceClient<T> {
248
+ if (!client) {
249
+ throw new Error("Client cannot be null");
250
+ }
251
+
252
+ const bidAskClient = new LivePriceClientImpl<T>(
253
+ client,
254
+ region,
255
+ PriceDataType.Bids
256
+ );
257
+ bidAskClient.subscribe(symbols).catch((error) => {
258
+ console.error("Failed to initialize bist bid ask client", error);
259
+ });
260
+
261
+ return bidAskClient;
262
+ }
263
+
220
264
  export function getLivePriceForBIST(
221
265
  client: Client,
222
266
  symbols: string[]
@@ -245,6 +289,13 @@ export function getOrderbookForBIST(
245
289
  return getOrderbook<OrderbookLiveData>(client, symbols, Region.Tr);
246
290
  }
247
291
 
292
+ export function getBidAskForBIST(
293
+ client: Client,
294
+ symbols: string[]
295
+ ): ILivePriceClient<BISTBidAskStreamData> {
296
+ return getBidAsk<BISTBidAskStreamData>(client, symbols, Region.Tr);
297
+ }
298
+
248
299
  export class LivePriceClient extends Client {
249
300
  getLivePriceForBIST(symbols: string[]): ILivePriceClient<BISTStockStreamData> {
250
301
  return getLivePriceForBIST(this, symbols);
@@ -264,6 +315,10 @@ export class LivePriceClient extends Client {
264
315
  return getOrderbookForBIST(this, symbols);
265
316
  }
266
317
 
318
+ getBidAskForBIST(symbols: string[]): ILivePriceClient<BISTBidAskStreamData> {
319
+ return getBidAskForBIST(this, symbols);
320
+ }
321
+
267
322
  async getClientWebsocketUrl(
268
323
  externalUserId: string,
269
324
  feeds: LivePriceFeed[]
@@ -301,4 +356,16 @@ export class LivePriceClient extends Client {
301
356
 
302
357
  return response;
303
358
  }
359
+
360
+ async sendWebsocketEvent(
361
+ request: SendWebsocketEventRequest
362
+ ): Promise<void> {
363
+ const url = new URL(`${this["baseUrl"]}/api/v1/ws/event`);
364
+
365
+ await this.sendRequest<void>({
366
+ method: "POST",
367
+ url: url.toString(),
368
+ data: request,
369
+ });
370
+ }
304
371
  }
@@ -149,6 +149,15 @@ export interface MarketState {
149
149
  stockSymbol?: string | null;
150
150
  }
151
151
 
152
+ export interface GenerateChartImageRequest {
153
+ symbol: string;
154
+ period?: HistoricalPricePeriod;
155
+ region: Region;
156
+ resolution?: HistoricalPriceInterval;
157
+ indicators?: string[];
158
+ chartType?: number;
159
+ }
160
+
152
161
  export class StockClient extends Client {
153
162
  async getAllStocks(region: Region, page: number|null = null, pageSize: number|null = null): Promise<Stock[]> {
154
163
  return this.sendRequest<Stock[]>({
@@ -278,4 +287,25 @@ export class StockClient extends Client {
278
287
  url: `/api/v1/state/${symbol}`,
279
288
  });
280
289
  }
290
+
291
+ async getStockChartImage(request: GenerateChartImageRequest): Promise<Blob> {
292
+ const params: Partial<GenerateChartImageRequest> = {
293
+ symbol: request.symbol,
294
+ region: request.region,
295
+ };
296
+
297
+ if (request.period) params.period = request.period;
298
+ if (request.resolution) params.resolution = request.resolution;
299
+ if (request.indicators) params.indicators = request.indicators;
300
+ if (request.chartType != null) params.chartType = request.chartType;
301
+
302
+ const data = await this.sendRequest<ArrayBuffer>({
303
+ method: "GET",
304
+ url: "/api/v1/stock/chart",
305
+ params,
306
+ responseType: "arraybuffer",
307
+ });
308
+
309
+ return new Blob([data], { type: "image/png" });
310
+ }
281
311
  }
@@ -1,7 +1,7 @@
1
1
  import { Logger } from "winston";
2
2
  import { LaplaceConfiguration } from "../utilities/configuration";
3
3
  import "./client_test_suite";
4
- import { BISTStockStreamData, LivePriceClient, OrderbookLiveData } from "../client/live-price";
4
+ import { BISTBidAskStreamData, BISTStockStreamData, LivePriceClient, OrderbookLiveData } from "../client/live-price";
5
5
 
6
6
  describe("LivePrice", () => {
7
7
  let client: LivePriceClient;
@@ -474,4 +474,61 @@ describe("LivePrice", () => {
474
474
  TEST_CONSTANTS.JEST_TIMEOUT
475
475
  );
476
476
  });
477
+
478
+ describe("GetBidAskForBIST", () => {
479
+ it(
480
+ "should receive BIST bid/ask data",
481
+ async () => {
482
+ const symbols = ["AKBNK"];
483
+ let receivedData: BISTBidAskStreamData | null = null;
484
+ let receivedError: Error | null = null;
485
+
486
+ const lc = client.getBidAskForBIST(symbols);
487
+ activeConnections.push(lc);
488
+
489
+ try {
490
+ const receiveChan = lc.receive();
491
+
492
+ const timeoutPromise = new Promise<void>((_, reject) => {
493
+ const timeout = setTimeout(
494
+ () => reject(new Error("Timeout waiting for bid/ask data")),
495
+ TEST_CONSTANTS.MAIN_TIMEOUT
496
+ );
497
+ activeTimeouts.push(timeout);
498
+ });
499
+
500
+ const dataPromise = (async () => {
501
+ try {
502
+ for await (const data of receiveChan) {
503
+ receivedData = data;
504
+ break;
505
+ }
506
+ } catch (error) {
507
+ console.log("Error in bid/ask data stream:", error);
508
+ }
509
+ })();
510
+
511
+ await Promise.race([dataPromise, timeoutPromise]);
512
+
513
+ if (receivedData != null) {
514
+ const tempReceivedData = (receivedData as BISTBidAskStreamData).d;
515
+ console.log("Received BIST bid/ask data:", tempReceivedData);
516
+ expect(tempReceivedData.s).toBeDefined();
517
+ expect(typeof tempReceivedData.s).toBe("string");
518
+ expect(typeof tempReceivedData.d).toBe("string");
519
+ expect(typeof tempReceivedData.ask).toBe("number");
520
+ expect(typeof tempReceivedData.bid).toBe("number");
521
+ } else {
522
+ console.log("Timeout waiting for BIST bid/ask data");
523
+ }
524
+ } catch (error) {
525
+ receivedError = error as Error;
526
+ console.log("Received bid/ask error:", receivedError.message);
527
+ } finally {
528
+ lc.close();
529
+ }
530
+ },
531
+ TEST_CONSTANTS.JEST_TIMEOUT
532
+ );
533
+ });
477
534
  });
@@ -125,14 +125,14 @@ const mockTickRulesResponse = {
125
125
 
126
126
  const mockEarningsTranscriptList: EarningsTranscriptListItem[] = [
127
127
  {
128
- symbol: "TUPRS",
128
+ symbol: "AAPL",
129
129
  year: 2024,
130
130
  quarter: 1,
131
131
  date: "2024-05-15",
132
- fiscal_year: 2024
132
+ fiscal_year: 2024,
133
133
  },
134
134
  {
135
- symbol: "TUPRS",
135
+ symbol: "AAPL",
136
136
  year: 2023,
137
137
  quarter: 4,
138
138
  date: "2024-02-20",
@@ -141,7 +141,7 @@ const mockEarningsTranscriptList: EarningsTranscriptListItem[] = [
141
141
  ];
142
142
 
143
143
  const mockEarningsTranscriptDetail: EarningsTranscriptWithSummary = {
144
- symbol: "TUPRS",
144
+ symbol: "AAPL",
145
145
  year: 2024,
146
146
  quarter: 1,
147
147
  date: "2024-05-15",
@@ -153,14 +153,14 @@ const mockEarningsTranscriptDetail: EarningsTranscriptWithSummary = {
153
153
  const mockMarketStates: MarketState[] = [
154
154
  {
155
155
  id: 1,
156
- marketSymbol: "XIST",
156
+ marketSymbol: "BIST",
157
157
  state: "OPEN",
158
158
  lastTimestamp: "2024-03-14T10:00:00Z",
159
159
  stockSymbol: "TUPRS"
160
160
  },
161
161
  {
162
162
  id: 2,
163
- marketSymbol: "XIST",
163
+ marketSymbol: "BIST",
164
164
  state: "CLOSED",
165
165
  lastTimestamp: "2024-03-14T18:00:00Z",
166
166
  stockSymbol: "GARAN"
@@ -174,12 +174,14 @@ const mockPaginatedMarketStates: PaginatedResponse<MarketState> = {
174
174
 
175
175
  const mockSingleMarketState: MarketState = {
176
176
  id: 1,
177
- marketSymbol: "XIST",
177
+ marketSymbol: "BIST",
178
178
  state: "OPEN",
179
179
  lastTimestamp: "2024-03-14T10:00:00Z",
180
180
  stockSymbol: "TUPRS"
181
181
  };
182
182
 
183
+ const mockChartImageBlob = new Blob(['mock chart image data'], { type: 'image/png' });
184
+
183
185
  describe("Stocks Client", () => {
184
186
  let client: StockClient;
185
187
 
@@ -387,7 +389,7 @@ describe("Stocks Client", () => {
387
389
 
388
390
  describe("getEarningsTranscripts", () => {
389
391
  test("should return earnings transcript list", async () => {
390
- const resp = await client.getEarningsTranscripts("TUPRS", Region.Tr);
392
+ const resp = await client.getEarningsTranscripts("AAPL", Region.Us);
391
393
 
392
394
  expect(Array.isArray(resp)).toBe(true);
393
395
 
@@ -404,7 +406,7 @@ describe("Stocks Client", () => {
404
406
 
405
407
  describe("getEarningsTranscript", () => {
406
408
  test("should return earnings transcript detail", async () => {
407
- const resp = await client.getEarningsTranscript("TUPRS", 2023, 4);
409
+ const resp = await client.getEarningsTranscript("AAPL", 2023, 4);
408
410
 
409
411
  expect(resp).toBeDefined();
410
412
  expect(typeof resp.symbol).toBe("string");
@@ -474,7 +476,7 @@ describe("Stocks Client", () => {
474
476
 
475
477
  describe("getState", () => {
476
478
  test("should return single market state", async () => {
477
- const resp = await client.getState("XIST");
479
+ const resp = await client.getState("BIST");
478
480
 
479
481
  expect(resp).toBeDefined();
480
482
  expect(typeof resp.id).toBe("number");
@@ -486,6 +488,18 @@ describe("Stocks Client", () => {
486
488
  }
487
489
  });
488
490
  });
491
+
492
+ describe("getStockChartImage", () => {
493
+ test("should return chart image blob", async () => {
494
+ const resp = await client.getStockChartImage({
495
+ symbol: "TUPRS",
496
+ region: Region.Tr,
497
+ });
498
+ expect(resp).toBeDefined();
499
+ expect(resp).toBeInstanceOf(Blob);
500
+ expect(resp.size).toBeGreaterThan(0);
501
+ }, 10000);
502
+ });
489
503
  });
490
504
 
491
505
  describe("Mock Tests", () => {
@@ -678,12 +692,12 @@ describe("Stocks Client", () => {
678
692
  test("should return earnings transcript list with mock data", async () => {
679
693
  jest.spyOn(client, 'getEarningsTranscripts').mockResolvedValue(mockEarningsTranscriptList);
680
694
 
681
- const resp = await client.getEarningsTranscripts("TUPRS", Region.Tr);
695
+ const resp = await client.getEarningsTranscripts("AAPL", Region.Us);
682
696
 
683
697
  expect(resp).toHaveLength(2);
684
698
 
685
699
  const firstTranscript = resp[0];
686
- expect(firstTranscript.symbol).toBe("TUPRS");
700
+ expect(firstTranscript.symbol).toBe("AAPL");
687
701
  expect(firstTranscript.year).toBe(2024);
688
702
  expect(firstTranscript.quarter).toBe(1);
689
703
  expect(firstTranscript.date).toBe("2024-05-15");
@@ -693,14 +707,18 @@ describe("Stocks Client", () => {
693
707
  expect(secondTranscript.year).toBe(2023);
694
708
  expect(secondTranscript.quarter).toBe(4);
695
709
 
696
- expect(client.getEarningsTranscripts).toHaveBeenCalledWith("TUPRS", Region.Tr);
710
+ expect(client.getEarningsTranscripts).toHaveBeenCalledWith(
711
+ "AAPL",
712
+ Region.Us
713
+ );
697
714
  });
698
715
 
699
716
  test("should handle API errors for earnings transcripts", async () => {
700
717
  jest.spyOn(client, 'getEarningsTranscripts').mockRejectedValue(new Error("Transcripts not found"));
701
718
 
702
- await expect(client.getEarningsTranscripts("INVALID", Region.Tr))
703
- .rejects.toThrow("Transcripts not found");
719
+ await expect(
720
+ client.getEarningsTranscripts("INVALID", Region.Us)
721
+ ).rejects.toThrow("Transcripts not found");
704
722
  });
705
723
  });
706
724
 
@@ -708,9 +726,9 @@ describe("Stocks Client", () => {
708
726
  test("should return earnings transcript detail with mock data", async () => {
709
727
  jest.spyOn(client, 'getEarningsTranscript').mockResolvedValue(mockEarningsTranscriptDetail);
710
728
 
711
- const resp = await client.getEarningsTranscript("TUPRS", 2024, 1);
729
+ const resp = await client.getEarningsTranscript("AAPL", 2024, 1);
712
730
 
713
- expect(resp.symbol).toBe("TUPRS");
731
+ expect(resp.symbol).toBe("AAPL");
714
732
  expect(resp.year).toBe(2024);
715
733
  expect(resp.quarter).toBe(1);
716
734
  expect(resp.date).toBe("2024-05-15");
@@ -718,14 +736,19 @@ describe("Stocks Client", () => {
718
736
  expect(resp.summary).toBe("Strong Q1 performance with 15% revenue growth");
719
737
  expect(resp.has_summary).toBe(true);
720
738
 
721
- expect(client.getEarningsTranscript).toHaveBeenCalledWith("TUPRS", 2024, 1);
739
+ expect(client.getEarningsTranscript).toHaveBeenCalledWith(
740
+ "AAPL",
741
+ 2024,
742
+ 1
743
+ );
722
744
  });
723
745
 
724
746
  test("should handle API errors for earnings transcript detail", async () => {
725
747
  jest.spyOn(client, 'getEarningsTranscript').mockRejectedValue(new Error("Transcript not found"));
726
748
 
727
- await expect(client.getEarningsTranscript("TUPRS", 2020, 1))
728
- .rejects.toThrow("Transcript not found");
749
+ await expect(
750
+ client.getEarningsTranscript("AAPL", 2020, 1)
751
+ ).rejects.toThrow("Transcript not found");
729
752
  });
730
753
  });
731
754
 
@@ -740,7 +763,7 @@ describe("Stocks Client", () => {
740
763
 
741
764
  const firstState = resp.items[0];
742
765
  expect(firstState.id).toBe(1);
743
- expect(firstState.marketSymbol).toBe("XIST");
766
+ expect(firstState.marketSymbol).toBe("BIST");
744
767
  expect(firstState.state).toBe("OPEN");
745
768
  expect(firstState.stockSymbol).toBe("TUPRS");
746
769
 
@@ -762,7 +785,7 @@ describe("Stocks Client", () => {
762
785
  const resp = await client.getStockState("TUPRS");
763
786
 
764
787
  expect(resp.id).toBe(1);
765
- expect(resp.marketSymbol).toBe("XIST");
788
+ expect(resp.marketSymbol).toBe("BIST");
766
789
  expect(resp.state).toBe("OPEN");
767
790
  expect(resp.lastTimestamp).toBe("2024-03-14T10:00:00Z");
768
791
  expect(resp.stockSymbol).toBe("TUPRS");
@@ -806,14 +829,14 @@ describe("Stocks Client", () => {
806
829
  test("should return single market state with mock data", async () => {
807
830
  jest.spyOn(client, 'getState').mockResolvedValue(mockSingleMarketState);
808
831
 
809
- const resp = await client.getState("XIST");
832
+ const resp = await client.getState("BIST");
810
833
 
811
834
  expect(resp.id).toBe(1);
812
- expect(resp.marketSymbol).toBe("XIST");
835
+ expect(resp.marketSymbol).toBe("BIST");
813
836
  expect(resp.state).toBe("OPEN");
814
837
  expect(resp.lastTimestamp).toBe("2024-03-14T10:00:00Z");
815
838
 
816
- expect(client.getState).toHaveBeenCalledWith("XIST");
839
+ expect(client.getState).toHaveBeenCalledWith("BIST");
817
840
  });
818
841
 
819
842
  test("should handle API errors for single state", async () => {
@@ -823,5 +846,29 @@ describe("Stocks Client", () => {
823
846
  .rejects.toThrow("Market state not found");
824
847
  });
825
848
  });
849
+
850
+ describe("getStockChartImage", () => {
851
+ test("should return chart image with mock data", async () => {
852
+ jest.spyOn(client, 'getStockChartImage').mockResolvedValue(mockChartImageBlob);
853
+
854
+ const resp = await client.getStockChartImage({
855
+ symbol: "TUPRS",
856
+ region: Region.Tr,
857
+ });
858
+
859
+ expect(resp).toBeDefined();
860
+ expect(resp).toBeInstanceOf(Blob);
861
+ expect(resp.type).toBe('image/png');
862
+ });
863
+
864
+ test("should handle API errors for chart image", async () => {
865
+ jest.spyOn(client, 'getStockChartImage').mockRejectedValue(new Error("Failed to generate chart"));
866
+
867
+ await expect(client.getStockChartImage({
868
+ symbol: "INVALID",
869
+ region: Region.Tr
870
+ })).rejects.toThrow("Failed to generate chart");
871
+ });
872
+ });
826
873
  });
827
874
  });