UNPKG

laplace-api

Version:

Client library for Laplace API for the US stock market and BIST (Istanbul stock market) fundamental financial data.

811 lines (673 loc) 27.3 kB
import { Logger } from "winston"; import { LaplaceConfiguration } from "../utilities/configuration"; import { StockClient, HistoricalPricePeriod, HistoricalPriceInterval, ChartImagePeriod, AssetClass, Stock, AssetType, Market, StockPriceGraph, MarketState, EarningsTranscriptWithSummary, EarningsTranscriptListItem } from "../client/stocks"; import "./client_test_suite"; import { Region, Locale } from "../client/collections"; import { PaginatedResponse } from "../client/capital_increase"; const mockStocksResponse = [ { id: "61dd0d6f0ec2114146342fd0", assetType: AssetType.Stock, name: "Tüpraş", symbol: "TUPRS", sectorId: "sector123", industryId: "industry456", updatedDate: "2024-03-14T10:00:00Z", active: true } ]; const mockStockDetailResponse = { id: "61dd0d6f0ec2114146342fd0", assetType: AssetType.Stock, assetClass: AssetClass.Equity, name: "Tüpraş", symbol: "TUPRS", sectorId: "sector123", industryId: "industry456", updatedDate: "2024-03-14T10:00:00Z", dailyChange: 2.5, active: true, description: "Türkiye'nin en büyük rafineri şirketi", shortDescription: "Rafineri şirketi", region: Region.Tr, localized_description: { tr: "Türkiye'nin en büyük rafineri şirketi", en: "Turkey's largest refinery company" }, localizedShortDescription: { tr: "Rafineri şirketi", en: "Refinery company" }, markets: [Market.Yildiz] }; const mockHistoricalPricesResponse: StockPriceGraph[] = [ { symbol: "TUPRS", "1D": [ { d: 1710374400000, c: 425.5, h: 428.0, l: 422.0, o: 423.0 }, { d: 1710378000000, c: 426.8, h: 427.5, l: 425.0, o: 425.5 } ], "1W": [ { d: 1709856000000, c: 420.5, h: 422.0, l: 419.0, o: 419.5 }, { d: 1709942400000, c: 423.0, h: 424.5, l: 420.0, o: 420.5 } ], "1M": [ { d: 1707436800000, c: 415.0, h: 416.5, l: 414.0, o: 414.5 }, { d: 1707523200000, c: 417.2, h: 418.0, l: 415.0, o: 415.0 } ], "3M": [], "1Y": [], "2Y": [], "3Y": [], "5Y": [] } ]; const mockStockRestrictionsResponse = [ { id: 1, title: "Bedelli Sermaye Artırımı", description: "Şirket bedelli sermaye artırımı yapacaktır", symbol: "TUPRS", startDate: "2024-03-15T00:00:00Z", endDate: "2024-03-20T00:00:00Z", market: Market.Yildiz } ]; const mockTickRulesResponse = { basePrice: 425.5, additionalPrice: 0.1, lowerPriceLimit: 382.95, upperPriceLimit: 468.05, rules: [ { priceFrom: 0, priceTo: 20, tickSize: 0.01 }, { priceFrom: 20, priceTo: 50, tickSize: 0.02 }, { priceFrom: 50, priceTo: 100, tickSize: 0.05 } ] }; const mockEarningsTranscriptList: EarningsTranscriptListItem[] = [ { symbol: "AAPL", year: 2024, quarter: 1, date: "2024-05-15", fiscal_year: 2024, }, { symbol: "AAPL", year: 2023, quarter: 4, date: "2024-02-20", fiscal_year: 2023 } ]; const mockEarningsTranscriptDetail: EarningsTranscriptWithSummary = { symbol: "AAPL", year: 2024, quarter: 1, date: "2024-05-15", content: "Q1 2024 earnings call transcript content...", summary: "Strong Q1 performance with 15% revenue growth", has_summary: true }; const mockMarketStates: MarketState[] = [ { id: 1, marketSymbol: "BIST", state: "OPEN", lastTimestamp: "2024-03-14T10:00:00Z", stockSymbol: "TUPRS" }, { id: 2, marketSymbol: "BIST", state: "CLOSED", lastTimestamp: "2024-03-14T18:00:00Z", stockSymbol: "GARAN" } ]; const mockPaginatedMarketStates: PaginatedResponse<MarketState> = { recordCount: 2, items: mockMarketStates }; const mockSingleMarketState: MarketState = { id: 1, marketSymbol: "BIST", state: "OPEN", lastTimestamp: "2024-03-14T10:00:00Z", stockSymbol: "TUPRS" }; describe("Stocks Client", () => { let client: StockClient; beforeAll(() => { const config = (global as any).testSuite.config as LaplaceConfiguration; const logger: Logger = { info: jest.fn(), error: jest.fn(), warn: jest.fn(), debug: jest.fn(), } as unknown as Logger; client = new StockClient(config, logger); }); describe("Integration Tests", () => { describe("getAllStocks", () => { test("should return stocks list for TR region", async () => { const resp = await client.getAllStocks(Region.Tr); expect(resp).not.toBeEmpty(); const firstStock = resp[0]; expect(typeof firstStock.id).toBe("string"); expect(typeof firstStock.assetType).toBe("string"); expect(typeof firstStock.name).toBe("string"); expect(typeof firstStock.symbol).toBe("string"); expect(typeof firstStock.sectorId).toBe("string"); expect(typeof firstStock.industryId).toBe("string"); expect(typeof firstStock.updatedDate).toBe("string"); expect(typeof firstStock.active).toBe("boolean"); if (firstStock.dailyChange !== undefined) { expect(typeof firstStock.dailyChange).toBe("number"); } }); test("should handle pagination correctly", async () => { const resp = await client.getAllStocks(Region.Tr, 10, 10); expect(resp).not.toBeEmpty(); expect(resp.length).toBeLessThanOrEqual(10); }); }); describe("getStockDetailById", () => { test("should return stock detail by ID", async () => { const resp = await client.getStockDetailById( "61dd0d6f0ec2114146342fd0", Locale.Tr ); expect(resp).toBeDefined(); expect(typeof resp.id).toBe("string"); expect(typeof resp.assetClass).toBe("string"); expect(typeof resp.description).toBe("string"); expect(typeof resp.shortDescription).toBe("string"); expect(typeof resp.region).toBe("string"); expect(typeof resp.localized_description).toBe("object"); expect(typeof resp.localizedShortDescription).toBe("object"); if (resp.markets) { expect(Array.isArray(resp.markets)).toBe(true); } }); }); describe("getStockDetailBySymbol", () => { test("should return stock detail by symbol", async () => { const resp = await client.getStockDetailBySymbol( "TUPRS", AssetClass.Equity, Region.Tr, Locale.Tr ); expect(resp).toBeDefined(); expect(resp.symbol).toBe("TUPRS"); expect(typeof resp.id).toBe("string"); expect(typeof resp.assetClass).toBe("string"); expect(typeof resp.description).toBe("string"); expect(typeof resp.shortDescription).toBe("string"); expect(typeof resp.region).toBe("string"); expect(typeof resp.localized_description).toBe("object"); expect(typeof resp.localizedShortDescription).toBe("object"); }); }); describe("getHistoricalPrices", () => { test("should return historical prices for multiple symbols", async () => { const resp = await client.getHistoricalPrices( ["TUPRS", "SASA"], Region.Tr, [ HistoricalPricePeriod.OneDay, HistoricalPricePeriod.OneWeek, HistoricalPricePeriod.OneMonth, ] ); expect(resp).not.toBeEmpty(); const firstPriceGraph = resp[0]; expect(typeof firstPriceGraph.symbol).toBe("string"); expect(Array.isArray(firstPriceGraph["1D"])).toBe(true); expect(Array.isArray(firstPriceGraph["1W"])).toBe(true); expect(Array.isArray(firstPriceGraph["1M"])).toBe(true); if (firstPriceGraph["1D"].length > 0) { const firstDataPoint = firstPriceGraph["1D"][0]; expect(typeof firstDataPoint.d).toBe("number"); expect(typeof firstDataPoint.c).toBe("number"); expect(typeof firstDataPoint.h).toBe("number"); expect(typeof firstDataPoint.l).toBe("number"); expect(typeof firstDataPoint.o).toBe("number"); } }); }); describe("getCustomHistoricalPrices", () => { test("should return custom historical prices", async () => { const resp = await client.getCustomHistoricalPrices( "TUPRS", Region.Tr, "2024-01-01", "2024-03-01", HistoricalPriceInterval.OneDay, false ); expect(resp).not.toBeEmpty(); const firstDataPoint = resp[0]; expect(typeof firstDataPoint.d).toBe("number"); expect(typeof firstDataPoint.c).toBe("number"); expect(typeof firstDataPoint.h).toBe("number"); expect(typeof firstDataPoint.l).toBe("number"); expect(typeof firstDataPoint.o).toBe("number"); }); test("should handle detailed historical prices", async () => { const resp = await client.getCustomHistoricalPrices( "SASA", Region.Tr, "2024-01-01 10:00:00", "2024-01-05 10:00:00", HistoricalPriceInterval.OneHour, true ); expect(resp).not.toBeEmpty(); const firstDataPoint = resp[0]; expect(typeof firstDataPoint.d).toBe("number"); expect(typeof firstDataPoint.c).toBe("number"); expect(typeof firstDataPoint.h).toBe("number"); expect(typeof firstDataPoint.l).toBe("number"); expect(typeof firstDataPoint.o).toBe("number"); expect(typeof firstDataPoint.uc).toBe("number"); expect(typeof firstDataPoint.uh).toBe("number"); expect(typeof firstDataPoint.ul).toBe("number"); expect(typeof firstDataPoint.uo).toBe("number"); expect(typeof firstDataPoint.uv).toBe("number"); expect(typeof firstDataPoint.v).toBe("number"); }); }); describe("getStockRestrictions", () => { test("should return stock restrictions for specific symbol", async () => { const resp = await client.getStockRestrictions("TUPRS", Region.Tr); if (resp && resp.length > 0) { const firstRestriction = resp[0]; expect(typeof firstRestriction.id).toBe("number"); expect(typeof firstRestriction.title).toBe("string"); expect(typeof firstRestriction.description).toBe("string"); expect(typeof firstRestriction.startDate).toBe("string"); expect(typeof firstRestriction.endDate).toBe("string"); if (firstRestriction.symbol) { expect(typeof firstRestriction.symbol).toBe("string"); } if (firstRestriction.market) { expect(typeof firstRestriction.market).toBe("string"); } } }); }); describe("getAllStockRestrictions", () => { test("should return all stock restrictions for region", async () => { const resp = await client.getAllStockRestrictions(); expect(Array.isArray(resp)).toBe(true); if (resp.length > 0) { const firstRestriction = resp[0]; expect(typeof firstRestriction.id).toBe("number"); expect(typeof firstRestriction.title).toBe("string"); expect(typeof firstRestriction.description).toBe("string"); expect(typeof firstRestriction.startDate).toBe("string"); expect(typeof firstRestriction.endDate).toBe("string"); } }); }); describe("getTickRules", () => { test("should return tick rules for symbol", async () => { const resp = await client.getTickRules("TUPRS", Region.Tr); expect(resp).toBeDefined(); expect(typeof resp.basePrice).toBe("number"); expect(typeof resp.additionalPrice).toBe("number"); expect(typeof resp.lowerPriceLimit).toBe("number"); expect(typeof resp.upperPriceLimit).toBe("number"); if (resp.rules !== null) { expect(Array.isArray(resp.rules)).toBe(true); if (resp.rules.length > 0) { const firstRule = resp.rules[0] expect(firstRule).toBeDefined(); expect(typeof firstRule.priceFrom).toBe("number"); expect(typeof firstRule.priceTo).toBe("number"); expect(typeof firstRule.tickSize).toBe("number"); } } }); }); describe("getEarningsTranscripts", () => { test("should return earnings transcript list", async () => { const resp = await client.getEarningsTranscripts("AAPL", Region.Us); expect(Array.isArray(resp)).toBe(true); if (resp.length > 0) { const firstTranscript = resp[0]; expect(typeof firstTranscript.symbol).toBe("string"); expect(typeof firstTranscript.year).toBe("number"); expect(typeof firstTranscript.quarter).toBe("number"); expect(typeof firstTranscript.date).toBe("string"); expect(typeof firstTranscript.fiscal_year).toBe("number"); } }); }); describe("getEarningsTranscript", () => { test("should return earnings transcript detail", async () => { const resp = await client.getEarningsTranscript("AAPL", 2023, 4); expect(resp).toBeDefined(); expect(typeof resp.symbol).toBe("string"); expect(typeof resp.year).toBe("number"); expect(typeof resp.quarter).toBe("number"); expect(typeof resp.date).toBe("string"); expect(typeof resp.content).toBe("string"); expect(typeof resp.has_summary).toBe("boolean"); if (resp.summary) { expect(typeof resp.summary).toBe("string"); } }); }); describe("getStockStateAll", () => { test("should return paginated stock states", async () => { const resp = await client.getStockStateAll(0, 10, Region.Tr); expect(resp).toBeDefined(); expect(Array.isArray(resp.items)).toBe(true); expect(typeof resp.recordCount).toBe("number"); if (resp.items.length > 0) { const firstState = resp.items[0]; expect(typeof firstState.id).toBe("number"); expect(typeof firstState.state).toBe("string"); expect(typeof firstState.lastTimestamp).toBe("string"); } }); }); describe("getStockState", () => { test("should return single stock state", async () => { const resp = await client.getStockState("TUPRS"); expect(resp).toBeDefined(); expect(typeof resp.id).toBe("number"); expect(typeof resp.state).toBe("string"); expect(typeof resp.lastTimestamp).toBe("string"); if (resp.stockSymbol) { expect(typeof resp.stockSymbol).toBe("string"); } if (resp.marketSymbol) { expect(typeof resp.marketSymbol).toBe("string"); } }); }); describe("getStateAll", () => { test("should return paginated market states", async () => { const resp = await client.getStateAll(0, 10, Region.Tr); expect(resp).toBeDefined(); expect(Array.isArray(resp.items)).toBe(true); expect(typeof resp.recordCount).toBe("number"); if (resp.items.length > 0) { const firstState = resp.items[0]; expect(typeof firstState.id).toBe("number"); expect(typeof firstState.state).toBe("string"); expect(typeof firstState.lastTimestamp).toBe("string"); } }); }); describe("getState", () => { test("should return single market state", async () => { const resp = await client.getState("BIST"); expect(resp).toBeDefined(); expect(typeof resp.id).toBe("number"); expect(typeof resp.state).toBe("string"); expect(typeof resp.lastTimestamp).toBe("string"); if (resp.marketSymbol) { expect(typeof resp.marketSymbol).toBe("string"); } }); }); describe("getStockChartImage", () => { test("should return chart image blob", async () => { const resp = await client.getStockChartImage({ symbol: "TUPRS", region: Region.Tr, }); expect(resp).toBeDefined(); expect(resp).toBeInstanceOf(Blob); expect(resp.size).toBeGreaterThan(0); }, 30000); }); }); describe("Mock Tests (Data Injection)", () => { let client: StockClient; let cli: { request: jest.Mock }; beforeEach(() => { cli = { request: jest.fn() }; const config = (global as any).testSuite.config as LaplaceConfiguration; const logger: Logger = { info: jest.fn(), error: jest.fn(), warn: jest.fn(), debug: jest.fn(), } as unknown as Logger; client = new StockClient(config, logger, cli as any); }); test("getAllStocks: calls correct endpoint/params and matches raw response", async () => { cli.request.mockResolvedValueOnce({ data: mockStocksResponse }); const resp = await client.getAllStocks(Region.Tr); expect(cli.request).toHaveBeenCalledTimes(1); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe("/api/v2/stock/all"); expect(call.params).toEqual({ region: Region.Tr }); // raw match expect(resp).toEqual(mockStocksResponse); }); test("getAllStocks: includes page/pageSize when provided (including 0)", async () => { cli.request.mockResolvedValueOnce({ data: mockStocksResponse }); await client.getAllStocks(Region.Tr, 0, 0); const call = cli.request.mock.calls[0][0]; expect(call.params).toEqual({ region: Region.Tr, page: 0, pageSize: 0 }); }); test("getStockDetailById: calls correct endpoint/params and matches raw response", async () => { cli.request.mockResolvedValueOnce({ data: mockStockDetailResponse }); const resp = await client.getStockDetailById("61dd0d6f0ec2114146342fd0", Locale.Tr); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe("/api/v1/stock/61dd0d6f0ec2114146342fd0"); expect(call.params).toEqual({ locale: Locale.Tr }); expect(resp).toEqual(mockStockDetailResponse); }); test("getStockDetailBySymbol: calls correct endpoint/params and matches raw response", async () => { cli.request.mockResolvedValueOnce({ data: mockStockDetailResponse }); const resp = await client.getStockDetailBySymbol("TUPRS", AssetClass.Equity, Region.Tr, Locale.Tr); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe("/api/v1/stock/detail"); expect(call.params).toEqual({ symbol: "TUPRS", asset_class: AssetClass.Equity, region: Region.Tr, locale: Locale.Tr, }); expect(resp).toEqual(mockStockDetailResponse); }); test("getHistoricalPrices: calls correct endpoint/params and matches raw response", async () => { cli.request.mockResolvedValueOnce({ data: mockHistoricalPricesResponse }); const resp = await client.getHistoricalPrices( ["TUPRS"], Region.Tr, [HistoricalPricePeriod.OneDay, HistoricalPricePeriod.OneWeek, HistoricalPricePeriod.OneMonth] ); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe("/api/v1/stock/price"); expect(call.params).toEqual({ symbols: "TUPRS", region: Region.Tr, keys: "1D,1W,1M", }); expect(resp).toEqual(mockHistoricalPricesResponse); }); test("getCustomHistoricalPrices: calls correct endpoint/params and matches raw response", async () => { const mockCustom = [{ d: 1, o: 1, h: 1, l: 1, c: 1 }]; cli.request.mockResolvedValueOnce({ data: mockCustom }); const resp = await client.getCustomHistoricalPrices( "TUPRS", Region.Tr, "2024-01-01", "2024-03-01", HistoricalPriceInterval.OneDay, false, 10 ); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe("/api/v1/stock/price/interval"); expect(call.params).toEqual({ stock: "TUPRS", region: Region.Tr, fromDate: "2024-01-01", toDate: "2024-03-01", interval: HistoricalPriceInterval.OneDay, detail: false, numIntervals: 10, }); expect(resp).toEqual(mockCustom); }); test("getStockRestrictions: calls correct endpoint/params and matches raw response", async () => { cli.request.mockResolvedValueOnce({ data: mockStockRestrictionsResponse }); const resp = await client.getStockRestrictions("TUPRS", Region.Tr); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe("/api/v1/stock/restrictions"); expect(call.params).toEqual({ symbol: "TUPRS", region: Region.Tr }); expect(resp).toEqual(mockStockRestrictionsResponse); }); test("getAllStockRestrictions: calls correct endpoint and matches raw response", async () => { cli.request.mockResolvedValueOnce({ data: mockStockRestrictionsResponse }); const resp = await client.getAllStockRestrictions(); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe("/api/v1/stock/restrictions/all"); expect(call.params).toBeUndefined(); expect(resp).toEqual(mockStockRestrictionsResponse); }); test("getTickRules: calls correct endpoint/params and matches raw response", async () => { cli.request.mockResolvedValueOnce({ data: mockTickRulesResponse }); const resp = await client.getTickRules("TUPRS", Region.Tr); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe("/api/v1/stock/rules"); expect(call.params).toEqual({ symbol: "TUPRS", region: Region.Tr }); expect(resp).toEqual(mockTickRulesResponse); }); test("getEarningsTranscripts: calls correct endpoint/params and matches raw response", async () => { cli.request.mockResolvedValueOnce({ data: mockEarningsTranscriptList }); const resp = await client.getEarningsTranscripts("AAPL", Region.Us); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe("/api/v1/earnings/transcripts"); expect(call.params).toEqual({ symbol: "AAPL", region: Region.Us }); expect(resp).toEqual(mockEarningsTranscriptList); }); test("getEarningsTranscript: calls correct endpoint/params and matches raw response", async () => { cli.request.mockResolvedValueOnce({ data: mockEarningsTranscriptDetail }); const resp = await client.getEarningsTranscript("AAPL", 2024, 1); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe("/api/v1/earnings/transcript"); expect(call.params).toEqual({ symbol: "AAPL", year: 2024, quarter: 1 }); expect(resp).toEqual(mockEarningsTranscriptDetail); }); test("getStockStateAll: calls correct endpoint/params and matches raw response", async () => { cli.request.mockResolvedValueOnce({ data: mockPaginatedMarketStates }); const resp = await client.getStockStateAll(0, 10, Region.Tr); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe("/api/v1/state/stock/all"); expect(call.params).toEqual({ page: 0, size: 10, region: Region.Tr }); expect(resp).toEqual(mockPaginatedMarketStates); }); test("getStockState: calls correct endpoint and matches raw response", async () => { cli.request.mockResolvedValueOnce({ data: mockSingleMarketState }); const resp = await client.getStockState("TUPRS"); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe("/api/v1/state/stock/TUPRS"); expect(call.params).toBeUndefined(); expect(resp).toEqual(mockSingleMarketState); }); test("getStateAll: calls correct endpoint/params and matches raw response", async () => { cli.request.mockResolvedValueOnce({ data: mockPaginatedMarketStates }); const resp = await client.getStateAll(0, 10, Region.Tr); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe("/api/v1/state/all"); expect(call.params).toEqual({ page: 0, size: 10, region: Region.Tr }); expect(resp).toEqual(mockPaginatedMarketStates); }); test("getState: calls correct endpoint and matches raw response", async () => { cli.request.mockResolvedValueOnce({ data: mockSingleMarketState }); const resp = await client.getState("BIST"); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe("/api/v1/state/BIST"); expect(call.params).toBeUndefined(); expect(resp).toEqual(mockSingleMarketState); }); test("bubbles up request error", async () => { cli.request.mockRejectedValueOnce(new Error("API Error")); await expect(client.getAllStocks(Region.Tr)).rejects.toThrow("API Error"); expect(cli.request).toHaveBeenCalledTimes(1); }); test("getStockChartImage: calls correct endpoint/params and returns Blob", async () => { const mockArrayBuffer = new ArrayBuffer(8); cli.request.mockResolvedValueOnce({ data: mockArrayBuffer }); const resp = await client.getStockChartImage({ symbol: "TUPRS", region: Region.Tr, }); expect(cli.request).toHaveBeenCalledTimes(1); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe("/api/v1/stock/chart"); expect(call.params).toEqual({ symbol: "TUPRS", region: Region.Tr }); expect(call.responseType).toBe("arraybuffer"); expect(resp).toBeInstanceOf(Blob); expect(resp.type).toBe("image/png"); }); test("getStockChartImage: includes optional params when provided", async () => { const mockArrayBuffer = new ArrayBuffer(8); cli.request.mockResolvedValueOnce({ data: mockArrayBuffer }); await client.getStockChartImage({ symbol: "TUPRS", region: Region.Tr, period: ChartImagePeriod.OneMonth, resolution: HistoricalPriceInterval.OneDay, indicators: ["RSI", "MACD"], chartType: 1, }); const call = cli.request.mock.calls[0][0]; expect(call.params).toEqual({ symbol: "TUPRS", region: Region.Tr, period: ChartImagePeriod.OneMonth, resolution: HistoricalPriceInterval.OneDay, indicators: ["RSI", "MACD"], chartType: 1, }); }); test("getStockChartImage: bubbles up request error", async () => { cli.request.mockRejectedValueOnce(new Error("Failed to generate chart")); await expect(client.getStockChartImage({ symbol: "INVALID", region: Region.Tr, })).rejects.toThrow("Failed to generate chart"); expect(cli.request).toHaveBeenCalledTimes(1); }); }); });