UNPKG

laplace-api

Version:

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

463 lines (393 loc) 15.7 kB
import { Logger } from "winston"; import { LaplaceConfiguration } from "../utilities/configuration"; import { FinancialClient, HistoricalRatiosKey, FinancialSheetType, FinancialSheetPeriod, Currency, HistoricalFinancialSheetRow, RatioComparisonPeerType, HistoricalRatiosFormat, } from "../client/financial_ratios"; import "./client_test_suite"; import { equal } from "assert"; import { Locale, Region } from "../client/collections"; const mockRatioComparisonResponse = [ { "metricName": "pricing", "normalizedValue": 57.1272139065688, "data": [ { "slug": "F/K", "value": 14.9584698641742, "average": -26.1316853855662 } ] } ]; const mockHistoricalRatiosResponse = [ { "items": [ { "period": "2025-4", "value": 0.080499759559051, "sectorMean": 0.080499759559051 } ], "finalValue": 0.080499759559051, "threeYearGrowth": -0.922611834325054, "yearGrowth": -0.527234781985081, "finalSectorValue": 0.080499759559051, "slug": "roe", "currency": "TRY", "format": "percentage", "name": "ROE" } ]; const mockRatiosDescriptionsResponse = [ { "id": 112, "format": "percentage", "currency": "TRY", "slug": "gross-margin", "createdAt": "2025-04-16T07:50:52.245181Z", "updatedAt": "2025-04-16T07:50:52.245181Z", "name": "Brüt Kâr Marjı", "description": "Satışların maliyetinin gelirden çıkarılmasıyla brüt kâr bulunur. Brüt kârın satışlara oranı brüt kâr marjı olarak isimlendirilir.", "locale": "tr", "isRealtime": false } ]; const mockFinancialSheetsResponse = { sheets: [ { period: "2024-1", items: [ { description: "Satış Gelirleri", value: 50000000000, lineCodeId: 1, indentLevel: 0 }, { description: "Satışların Maliyeti", value: -40000000000, lineCodeId: 2, indentLevel: 0 } ] } ] }; describe("FinancialRatios", () => { let financialClient: FinancialClient; 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; financialClient = new FinancialClient(config, logger); }); describe("Integration Tests", () => { test("GetFinancialRatioComparison", async () => { const resp = await financialClient.getFinancialRatioComparison( "TUPRS", Region.Tr, RatioComparisonPeerType.Sector ); expect(resp).not.toBeEmpty(); const comparison = resp[0]; expect(typeof comparison.metricName).toBe("string"); expect(typeof comparison.normalizedValue).toBe("number"); const comparisonData = comparison.data; expect(Array.isArray(comparisonData)).toBe(true); expect(comparisonData).not.toBeEmpty(); expect(typeof comparisonData[0].slug).toBe("string"); expect(typeof comparisonData[0].value).toBe("number"); expect(typeof comparisonData[0].average).toBe("number"); }); describe("GetHistoricalRatios", () => { test("GetHistoricalRatios", async () => { const resp = await financialClient.getHistoricalRatios( "TUPRS", Object.values(HistoricalRatiosKey).flat(), Region.Tr, Locale.Tr ); expect(resp).not.toBeEmpty(); const firstRatio = resp[0]; expect(typeof firstRatio.finalValue).toBe("number"); expect(typeof firstRatio.threeYearGrowth).toBe("number"); expect(typeof firstRatio.yearGrowth).toBe("number"); expect(typeof firstRatio.finalSectorValue).toBe("number"); expect(equal(firstRatio.currency, Currency.TRY)); expect(typeof firstRatio.format).toBe("string"); expect(typeof firstRatio.name).toBe("string"); expect(typeof firstRatio.slug).toBe("string"); expect(firstRatio.items).not.toBeEmpty(); const firstItem = firstRatio.items[0]; expect(typeof firstItem.period).toBe("string"); expect(typeof firstItem.sectorMean).toBe("number"); expect(typeof firstItem.value).toBe("number"); }); }); describe("GetHistoricalRatiosDescriptions", () => { test("GetHistoricalRatiosDescriptions", async () => { const resp = await financialClient.getHistoricalRatiosDescriptions( Locale.Tr, Region.Tr ); expect(resp).not.toBeEmpty(); const firstDescription = resp[0]; expect(typeof firstDescription.id).toBe("number"); expect(typeof firstDescription.format).toBe("string"); expect(typeof firstDescription.currency).toBe("string"); expect(typeof firstDescription.slug).toBe("string"); expect(typeof firstDescription.createdAt).toBe("string"); expect(typeof firstDescription.updatedAt).toBe("string"); expect(typeof firstDescription.name).toBe("string"); expect(typeof firstDescription.description).toBe("string"); expect(typeof firstDescription.locale).toBe("string"); expect(typeof firstDescription.isRealtime).toBe("boolean"); }); }); test("GetHistoricalFinancialSheets", async () => { try { await financialClient.getHistoricalFinancialSheets( "TUPRS", { year: 2022, month: 1, day: 1 }, { year: 2025, month: 1, day: 1 }, FinancialSheetType.BalanceSheet, FinancialSheetPeriod.Annual, Currency.TRY, Region.Tr ); } catch (error) { expect((error as Error).message).toContain( "balance sheet is only available for cumulative period" ); } const resp = await financialClient.getHistoricalFinancialSheets( "TUPRS", { year: 2022, month: 1, day: 1 }, { year: 2025, month: 1, day: 1 }, FinancialSheetType.CashFlow, FinancialSheetPeriod.Quarterly, Currency.TRY, Region.Tr ); expect(resp).toBeDefined(); expect(resp).not.toBeNull(); expect(resp).not.toBeEmpty(); expect(resp.sheets).toBeDefined(); expect(Array.isArray(resp.sheets)).toBe(true); expect(resp.sheets.length).toBeGreaterThan(0); const firstSheet = resp.sheets[0]; expect(firstSheet).toBeDefined(); expect(firstSheet.period).toBeDefined(); expect(typeof firstSheet.period).toBe("string"); expect(firstSheet.items).toBeDefined(); expect(Array.isArray(firstSheet.items)).toBe(true); expect(firstSheet.items.length).toBeGreaterThan(0); const firstRow = firstSheet.items[0]; expect(firstRow).toBeDefined(); expect(firstRow).toMatchObject<HistoricalFinancialSheetRow>({ description: expect.any(String), value: expect.any(Number), lineCodeId: expect.any(Number), indentLevel: expect.any(Number), }); }); }); describe("Mock Tests (Broker-style)", () => { let client: FinancialClient; 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 FinancialClient(config, logger, cli as any); }); describe("getFinancialRatioComparison", () => { test("calls correct endpoint/params and matches raw response", async () => { cli.request.mockResolvedValueOnce({ data: mockRatioComparisonResponse }); const resp = await client.getFinancialRatioComparison( "TUPRS", Region.Tr, RatioComparisonPeerType.Sector ); 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/financial-ratio-comparison"); expect(call.params).toEqual({ symbol: "TUPRS", region: Region.Tr, peerType: RatioComparisonPeerType.Sector }); expect(resp).toHaveLength(1); const item = resp[0]; expect(item.metricName).toBe("pricing"); expect(item.normalizedValue).toBe(57.1272139065688); expect(item.data).toHaveLength(1); expect(item.data[0].slug).toBe("F/K"); expect(item.data[0].value).toBe(14.9584698641742); expect(item.data[0].average).toBe(-26.1316853855662); }); test("bubbles up request error", async () => { cli.request.mockRejectedValueOnce(new Error("Bad request")); await expect( client.getFinancialRatioComparison("TUPRS", Region.Tr, RatioComparisonPeerType.Sector) ).rejects.toThrow("Bad request"); }); }); describe("getHistoricalRatios", () => { test("calls correct endpoint/params and matches raw response", async () => { cli.request.mockResolvedValueOnce({ data: mockHistoricalRatiosResponse }); const resp = await client.getHistoricalRatios( "TUPRS", [HistoricalRatiosKey.ReturnOnEquity], Region.Tr, Locale.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/historical-ratios"); expect(call.params).toEqual({ symbol: "TUPRS", region: Region.Tr, slugs: "roe", locale: Locale.Tr }); expect(resp).toHaveLength(1); const r = resp[0]; expect(r.slug).toBe("roe"); expect(r.finalValue).toBe(0.080499759559051); expect(r.threeYearGrowth).toBe(-0.922611834325054); expect(r.yearGrowth).toBe(-0.527234781985081); expect(r.finalSectorValue).toBe(0.080499759559051); expect(r.currency).toBe("TRY"); expect(r.format).toBe("percentage"); expect(r.name).toBe("ROE"); expect(r.items).toHaveLength(1); expect(r.items[0].period).toBe("2025-4"); expect(r.items[0].value).toBe(0.080499759559051); expect(r.items[0].sectorMean).toBe(0.080499759559051); }); test("omits locale param when locale is not provided", async () => { cli.request.mockResolvedValueOnce({ data: mockHistoricalRatiosResponse }); await client.getHistoricalRatios("TUPRS", [HistoricalRatiosKey.ReturnOnEquity], Region.Tr); const call = cli.request.mock.calls[0][0]; expect(call.url).toBe("/api/v2/stock/historical-ratios"); expect(call.params).toEqual({ symbol: "TUPRS", region: Region.Tr, slugs: "roe" }); expect(call.params.locale).toBeUndefined(); }); test("bubbles up request error", async () => { cli.request.mockRejectedValueOnce(new Error("Invalid slugs")); await expect( client.getHistoricalRatios("TUPRS", [HistoricalRatiosKey.ReturnOnEquity], Region.Tr, Locale.Tr) ).rejects.toThrow("Invalid slugs"); }); }); describe("getHistoricalRatiosDescriptions", () => { test("calls correct endpoint/params and matches raw response", async () => { cli.request.mockResolvedValueOnce({ data: mockRatiosDescriptionsResponse }); const resp = await client.getHistoricalRatiosDescriptions(Locale.Tr, 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/historical-ratios/descriptions"); expect(call.params).toEqual({ locale: Locale.Tr, region: Region.Tr }); expect(resp).toHaveLength(1); const d = resp[0]; expect(d.id).toBe(112); expect(d.format).toBe("percentage"); expect(d.currency).toBe("TRY"); expect(d.slug).toBe("gross-margin"); expect(d.createdAt).toBe("2025-04-16T07:50:52.245181Z"); expect(d.updatedAt).toBe("2025-04-16T07:50:52.245181Z"); expect(d.name).toBe("Brüt Kâr Marjı"); expect(d.description).toBe( "Satışların maliyetinin gelirden çıkarılmasıyla brüt kâr bulunur. Brüt kârın satışlara oranı brüt kâr marjı olarak isimlendirilir." ); expect(d.locale).toBe("tr"); expect(d.isRealtime).toBe(false); }); test("bubbles up request error", async () => { cli.request.mockRejectedValueOnce(new Error("Not found")); await expect( client.getHistoricalRatiosDescriptions(Locale.Tr, Region.Tr) ).rejects.toThrow("Not found"); }); }); describe("getHistoricalFinancialSheets", () => { test("throws error locally for balance sheet when period != cumulative (no request)", async () => { await expect( client.getHistoricalFinancialSheets( "TUPRS", { year: 2022, month: 1, day: 1 }, { year: 2025, month: 1, day: 1 }, FinancialSheetType.BalanceSheet, FinancialSheetPeriod.Annual, Currency.TRY, Region.Tr ) ).rejects.toThrow("balance sheet is only available for cumulative period"); expect(cli.request).not.toHaveBeenCalled(); }); test("calls correct endpoint/params and matches raw response", async () => { cli.request.mockResolvedValueOnce({ data: mockFinancialSheetsResponse }); const resp = await client.getHistoricalFinancialSheets( "TUPRS", { year: 2024, month: 1, day: 1 }, { year: 2024, month: 3, day: 31 }, FinancialSheetType.CashFlow, FinancialSheetPeriod.Quarterly, Currency.TRY, 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/v3/stock/historical-financial-sheets"); expect(call.params).toEqual({ symbol: "TUPRS", from: "2024-01-01", to: "2024-03-31", sheetType: FinancialSheetType.CashFlow, periodType: FinancialSheetPeriod.Quarterly, currency: Currency.TRY, region: Region.Tr, }); expect(resp.sheets).toHaveLength(1); expect(resp.sheets[0].period).toBe("2024-1"); expect(resp.sheets[0].items).toHaveLength(2); const row0 = resp.sheets[0].items[0]; expect(row0.description).toBe("Satış Gelirleri"); expect(row0.value).toBe(50000000000); expect(row0.lineCodeId).toBe(1); expect(row0.indentLevel).toBe(0); const row1 = resp.sheets[0].items[1]; expect(row1.description).toBe("Satışların Maliyeti"); expect(row1.value).toBe(-40000000000); expect(row1.lineCodeId).toBe(2); expect(row1.indentLevel).toBe(0); }); test("bubbles up request error", async () => { cli.request.mockRejectedValueOnce(new Error("Invalid parameters")); await expect( client.getHistoricalFinancialSheets( "TUPRS", { year: 2024, month: 1, day: 1 }, { year: 2024, month: 3, day: 31 }, FinancialSheetType.CashFlow, FinancialSheetPeriod.Quarterly, Currency.TRY, Region.Tr ) ).rejects.toThrow("Invalid parameters"); }); }); }); });