UNPKG

laplace-api

Version:

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

420 lines (356 loc) 15.5 kB
import { Logger } from "winston"; import { LaplaceConfiguration } from "../utilities/configuration"; import { CustomThemeClient, CollectionStatus, CreateCustomThemeParams, UpdateCustomThemeParams, } from "../client/custom_theme"; import { Stock, StockClient } from "../client/stocks"; import "./client_test_suite"; import { validateCollection } from "./helpers"; import { Locale, Region, SortBy, CollectionType } from "../client/collections"; const mockCustomThemes = [ { "id": "6888e18a6c84bcba9dc69ef7", "title": "Test Custom Theme", "region": [ "tr" ], "imageUrl": "Test Custom Theme Image URL", "avatarUrl": "Test Custom Theme Avatar Image", "numStocks": 2, "assetClass": "equity", "description": "Test Custom Theme Description", "image": "Test Custom Theme Image", "order": 0, "status": "active" } ]; const mockCustomThemeDetail = { "id": "6888e18a6c84bcba9dc69ef7", "title": "Test Custom Theme", "region": [ "tr" ], "imageUrl": "Test Custom Theme Image URL", "avatarUrl": "Test Custom Theme Avatar Image", "numStocks": 2, "assetClass": "equity", "description": "Test Custom Theme Description", "image": "Test Custom Theme Image", "order": 0, "status": "active", "stocks": [ { "id": "648ab66e38daf3102a5a7401", "assetType": "stock", "name": "A1 Capital Yatırım Menkul Değerler", "symbol": "A1CAP", "sectorId": "65533e047844ee7afe9941bc", "industryId": "65533e441fa5c7b58afa0955", "updatedDate": "2025-04-01T00:00:00.533Z", "active": true }, { "id": "61dd0da80ec21141463430cf", "assetType": "stock", "name": "A1 Yenilenebilir Enerji", "symbol": "A1YEN", "sectorId": "65533e047844ee7afe9941c2", "industryId": "65533e441fa5c7b58afa0985", "updatedDate": "2025-07-28T09:06:04.286Z", "active": true } ], "locale": "tr" }; describe("CustomTheme", () => { let client: CustomThemeClient; let stocksClient: 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 CustomThemeClient(config, logger); stocksClient = new StockClient(config, logger); }); describe("Integration Tests", () => { test("GetAllCustomThemes", async () => { const resp = await client.getAllCustomThemes(Locale.Tr); expect(resp).not.toBeEmpty(); const firstTheme = resp[0]; validateCollection(firstTheme); }); test('CreateUpdateDeleteCustomTheme', async () => { const stocks = await stocksClient.getAllStocks(Region.Tr); expect(stocks).not.toBeEmpty(); const createParams: CreateCustomThemeParams = { title: { [Locale.Tr]: 'Test Custom Theme', }, description: { [Locale.Tr]: 'Test Custom Theme Description', }, region: [Region.Tr], image_url: 'Test Custom Theme Image URL', image: 'Test Custom Theme Image', avatar_url: 'Test Custom Theme Avatar Image', stocks: [stocks[0].id, stocks[1].id], status: CollectionStatus.Active, }; const id = await testCreateCustomTheme(client, createParams); await testGetDetails(id, Locale.Tr, client, createParams); let updateParams: UpdateCustomThemeParams = { stockIds: [stocks[0].id, stocks[2].id], }; await testUpdateCustomTheme(id, client, updateParams); applyUpdateParams(updateParams, createParams); await testGetDetails(id, Locale.Tr, client, createParams); updateParams = { title: { [Locale.Tr]: 'Test Custom Theme Title Updated', [Locale.En]: 'Test Custom Theme Title Updated', }, description: { [Locale.Tr]: 'Test Custom Theme Description Updated', [Locale.En]: 'Test Custom Theme Description Updated', }, }; await testUpdateCustomTheme(id, client, updateParams); applyUpdateParams(updateParams, createParams); await testGetDetails(id, Locale.Tr, client, createParams); await testGetDetails(id, Locale.En, client, createParams); updateParams = { status: CollectionStatus.Inactive, }; await testUpdateCustomTheme(id, client, updateParams); applyUpdateParams(updateParams, createParams); await testGetDetails(id, Locale.Tr, client, createParams); await testDeleteCustomTheme(id, client); await expect(client.getCustomThemeDetail(id, Locale.Tr, SortBy.PriceChange)).rejects.toThrow(); }, 15000); }); describe("Mock Tests", () => { const locale = Locale.Tr; let client: CustomThemeClient; 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 CustomThemeClient(config, logger, cli as any); }); describe("getAllCustomThemes", () => { test("should call correct endpoint/params and return list (check all fields)", async () => { cli.request.mockResolvedValueOnce({ data: mockCustomThemes }); const resp = await client.getAllCustomThemes(locale); expect(cli.request).toHaveBeenCalledTimes(1); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe(`/api/v1/${CollectionType.CustomTheme}`); expect(call.params).toEqual({ locale }); expect(Array.isArray(resp)).toBe(true); expect(resp).toHaveLength(1); const theme = resp[0]; expect(theme.id).toBe("6888e18a6c84bcba9dc69ef7"); expect(theme.title).toBe("Test Custom Theme"); expect(theme.region).toEqual(["tr"]); expect(theme.imageUrl).toBe("Test Custom Theme Image URL"); expect(theme.avatarUrl).toBe("Test Custom Theme Avatar Image"); expect(theme.numStocks).toBe(2); expect(theme.assetClass).toBe("equity"); expect(theme.description).toBe("Test Custom Theme Description"); expect(theme.image).toBe("Test Custom Theme Image"); expect(theme.order).toBe(0); expect(theme.status).toBe("active"); }); }); describe("getCustomThemeDetail", () => { test("should call correct endpoint/params and return detail (check all fields)", async () => { cli.request.mockResolvedValueOnce({ data: mockCustomThemeDetail }); const resp = await client.getCustomThemeDetail( "6888e18a6c84bcba9dc69ef7", locale, null ); expect(cli.request).toHaveBeenCalledTimes(1); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe( `/api/v1/${CollectionType.CustomTheme}/6888e18a6c84bcba9dc69ef7` ); expect(call.params).toEqual({ locale }); expect(resp.id).toBe("6888e18a6c84bcba9dc69ef7"); expect(resp.title).toBe("Test Custom Theme"); expect(resp.region).toEqual(["tr"]); expect(resp.imageUrl).toBe("Test Custom Theme Image URL"); expect(resp.avatarUrl).toBe("Test Custom Theme Avatar Image"); expect(resp.numStocks).toBe(2); expect(resp.assetClass).toBe("equity"); expect(resp.description).toBe("Test Custom Theme Description"); expect(resp.image).toBe("Test Custom Theme Image"); expect(resp.order).toBe(0); expect(resp.status).toBe("active"); expect(resp.locale).toBe("tr"); expect(Array.isArray(resp.stocks)).toBe(true); expect(resp.stocks).toHaveLength(2); const s0 = resp.stocks[0]; expect(s0.id).toBe("648ab66e38daf3102a5a7401"); expect(s0.assetType).toBe("stock"); expect(s0.name).toBe("A1 Capital Yatırım Menkul Değerler"); expect(s0.symbol).toBe("A1CAP"); expect(s0.sectorId).toBe("65533e047844ee7afe9941bc"); expect(s0.industryId).toBe("65533e441fa5c7b58afa0955"); expect(s0.updatedDate).toBe("2025-04-01T00:00:00.533Z"); expect(s0.active).toBe(true); const s1 = resp.stocks[1]; expect(s1.id).toBe("61dd0da80ec21141463430cf"); expect(s1.assetType).toBe("stock"); expect(s1.name).toBe("A1 Yenilenebilir Enerji"); expect(s1.symbol).toBe("A1YEN"); expect(s1.sectorId).toBe("65533e047844ee7afe9941c2"); expect(s1.industryId).toBe("65533e441fa5c7b58afa0985"); expect(s1.updatedDate).toBe("2025-07-28T09:06:04.286Z"); expect(s1.active).toBe(true); }); test("should include sortBy when provided", async () => { cli.request.mockResolvedValueOnce({ data: mockCustomThemeDetail }); await client.getCustomThemeDetail( "6888e18a6c84bcba9dc69ef7", locale, SortBy.PriceChange ); expect(cli.request).toHaveBeenCalledTimes(1); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("GET"); expect(call.url).toBe( `/api/v1/${CollectionType.CustomTheme}/6888e18a6c84bcba9dc69ef7` ); expect(call.params).toEqual({ locale, sortBy: SortBy.PriceChange }); }); }); describe("createCustomTheme", () => { test("should call correct endpoint/body and return id", async () => { const createParams: CreateCustomThemeParams = { title: { [Locale.Tr]: "Test Custom Theme" }, description: { [Locale.Tr]: "Test Custom Theme Description" }, region: [Region.Tr], image_url: "Test Custom Theme Image URL", image: "Test Custom Theme Image", avatar_url: "Test Custom Theme Avatar Image", stocks: [ "648ab66e38daf3102a5a7401", "61dd0da80ec21141463430cf", ], order: 0, status: CollectionStatus.Active, meta_data: { foo: "bar" }, }; cli.request.mockResolvedValueOnce({ data: { id: "6888e18a6c84bcba9dc69ef7" } }); const id = await client.createCustomTheme(createParams); expect(cli.request).toHaveBeenCalledTimes(1); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("POST"); expect(call.url).toBe("/api/v1/custom-theme"); expect(call.data).toEqual(createParams); expect(id).toBe("6888e18a6c84bcba9dc69ef7"); }); test("should bubble up request error", async () => { const createParams: CreateCustomThemeParams = { title: { [Locale.Tr]: "Test Custom Theme" }, stocks: ["648ab66e38daf3102a5a7401"], status: CollectionStatus.Active, }; cli.request.mockRejectedValueOnce(new Error("Validation error")); await expect(client.createCustomTheme(createParams)).rejects.toThrow("Validation error"); }); }); describe("updateCustomTheme", () => { test("should call correct endpoint/body (stockIds) and resolve", async () => { const updateParams: UpdateCustomThemeParams = { title: { [Locale.Tr]: "Test Custom Theme Title Updated" }, description: { [Locale.Tr]: "Test Custom Theme Description Updated" }, image_url: "Test Custom Theme Image URL Updated", image: "Test Custom Theme Image Updated", avatar_url: "Test Custom Theme Avatar Image Updated", stockIds: [ "648ab66e38daf3102a5a7401", "61dd0da80ec21141463430cf", ], status: CollectionStatus.Inactive, meta_data: { hello: "world" }, }; cli.request.mockResolvedValueOnce({ data: undefined }); await client.updateCustomTheme("6888e18a6c84bcba9dc69ef7", updateParams); expect(cli.request).toHaveBeenCalledTimes(1); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("PATCH"); expect(call.url).toBe("/api/v1/custom-theme/6888e18a6c84bcba9dc69ef7"); expect(call.data).toEqual(updateParams); }); test("should bubble up request error", async () => { cli.request.mockRejectedValueOnce(new Error("Theme not found")); await expect( client.updateCustomTheme("invalid-theme", { status: CollectionStatus.Active }) ).rejects.toThrow("Theme not found"); }); }); describe("deleteCustomTheme", () => { test("should call correct endpoint and resolve", async () => { cli.request.mockResolvedValueOnce({ data: undefined }); await client.deleteCustomTheme("6888e18a6c84bcba9dc69ef7"); expect(cli.request).toHaveBeenCalledTimes(1); const call = cli.request.mock.calls[0][0]; expect(call.method).toBe("DELETE"); expect(call.url).toBe("/api/v1/custom-theme/6888e18a6c84bcba9dc69ef7"); }); test("should bubble up request error", async () => { cli.request.mockRejectedValueOnce(new Error("Theme not found")); await expect(client.deleteCustomTheme("invalid-theme")).rejects.toThrow("Theme not found"); }); }); }); }); async function testCreateCustomTheme(client: CustomThemeClient, createParams: CreateCustomThemeParams): Promise<string> { const resp = await client.createCustomTheme(createParams); expect(resp).not.toBeEmpty(); return resp; } async function testGetDetails(id: string, locale: Locale, client: CustomThemeClient, createParams: CreateCustomThemeParams) { const resp = await client.getCustomThemeDetail(id, locale, null); expect(resp).not.toBeEmpty(); expect(resp.title).toBe(createParams.title[locale]); expect(resp.description).toBe(createParams.description?.[locale]); expect(resp.region).toEqual(createParams.region); expect(resp.imageUrl).toBe(createParams.image_url); expect(resp.image).toBe(createParams.image); expect(resp.avatarUrl).toBe(createParams.avatar_url); expect(resp.stocks.map((stock: Stock) => stock.id)).toEqual(createParams.stocks); expect(resp.status).toBe(createParams.status); } async function testUpdateCustomTheme(id: string, client: CustomThemeClient, updateParams: UpdateCustomThemeParams) { await client.updateCustomTheme(id, updateParams); } function applyUpdateParams(updateParams: UpdateCustomThemeParams, createParams: CreateCustomThemeParams) { if (updateParams.stockIds) createParams.stocks = updateParams.stockIds; if (updateParams.title) createParams.title = updateParams.title; if (updateParams.description) createParams.description = updateParams.description; if (updateParams.image_url) createParams.image_url = updateParams.image_url; if (updateParams.image) createParams.image = updateParams.image; if (updateParams.avatar_url) createParams.avatar_url = updateParams.avatar_url; if (updateParams.status) createParams.status = updateParams.status; if (updateParams.meta_data) createParams.meta_data = updateParams.meta_data; } async function testDeleteCustomTheme(id: string, client: CustomThemeClient) { await client.deleteCustomTheme(id); }