laplace-api 5.2.1 → 5.2.2

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": "5.2.1",
3
+ "version": "5.2.2",
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": {
@@ -0,0 +1,81 @@
1
+ import { Client } from "./client";
2
+ import { PaginatedResponse } from "./capital_increase";
3
+ import { Region } from "./collections";
4
+
5
+ export enum ScreenerSortBy {
6
+ Price = "price",
7
+ DailyChange = "dailyChange",
8
+ MarketCap = "marketCap",
9
+ PeRatio = "peRatio",
10
+ PbRatio = "pbRatio",
11
+ WeeklyReturn = "weeklyReturn",
12
+ MonthlyReturn = "monthlyReturn",
13
+ ThreeMonthReturn = "threeMonthReturn",
14
+ YearlyReturn = "yearlyReturn",
15
+ ThreeYearReturn = "threeYearReturn",
16
+ FiveYearReturn = "fiveYearReturn",
17
+ YtdReturn = "ytdReturn",
18
+ }
19
+
20
+ export enum ScreenerSortOrder {
21
+ Asc = "asc",
22
+ Desc = "desc",
23
+ }
24
+
25
+ export interface ScreenerRange {
26
+ min?: number;
27
+ max?: number;
28
+ }
29
+
30
+ export interface ScreenerFilters {
31
+ price?: ScreenerRange;
32
+ dailyChange?: ScreenerRange;
33
+ peRatio?: ScreenerRange;
34
+ pbRatio?: ScreenerRange;
35
+ marketCap?: ScreenerRange;
36
+ weeklyReturn?: ScreenerRange;
37
+ monthlyReturn?: ScreenerRange;
38
+ threeMonthReturn?: ScreenerRange;
39
+ yearlyReturn?: ScreenerRange;
40
+ threeYearReturn?: ScreenerRange;
41
+ fiveYearReturn?: ScreenerRange;
42
+ ytdReturn?: ScreenerRange;
43
+ }
44
+
45
+ export interface ScreenerRequest {
46
+ filters?: ScreenerFilters;
47
+ sortBy?: ScreenerSortBy;
48
+ sortOrder?: ScreenerSortOrder;
49
+ page?: number;
50
+ pageSize?: number;
51
+ }
52
+
53
+ export interface ScreenerStock {
54
+ symbol: string;
55
+ price: number | null;
56
+ dailyChange: number | null;
57
+ marketCap: number | null;
58
+ peRatio: number | null;
59
+ pbRatio: number | null;
60
+ weeklyReturn: number | null;
61
+ monthlyReturn: number | null;
62
+ threeMonthReturn: number | null;
63
+ yearlyReturn: number | null;
64
+ threeYearReturn: number | null;
65
+ fiveYearReturn: number | null;
66
+ ytdReturn: number | null;
67
+ }
68
+
69
+ export class ScreenerClient extends Client {
70
+ async getScreener(
71
+ region?: Region,
72
+ request?: ScreenerRequest
73
+ ): Promise<PaginatedResponse<ScreenerStock>> {
74
+ return this.sendRequest<PaginatedResponse<ScreenerStock>>({
75
+ method: "POST",
76
+ url: "/api/v1/screener",
77
+ params: region != null ? { region } : undefined,
78
+ data: request ?? {},
79
+ });
80
+ }
81
+ }
@@ -0,0 +1,143 @@
1
+ import { Logger } from "winston";
2
+ import { LaplaceConfiguration } from "../utilities/configuration";
3
+ import {
4
+ ScreenerClient,
5
+ ScreenerSortBy,
6
+ ScreenerSortOrder,
7
+ } from "../client/screener";
8
+ import "./client_test_suite";
9
+ import { Region } from "../client/collections";
10
+
11
+ const mockScreenerResponse = {
12
+ items: [
13
+ {
14
+ symbol: "AKBNK",
15
+ price: 931.5,
16
+ dailyChange: 27.12,
17
+ marketCap: 4841200000000,
18
+ peRatio: 84.6,
19
+ pbRatio: 15.6,
20
+ weeklyReturn: 0.417,
21
+ monthlyReturn: 0.552,
22
+ threeMonthReturn: 27.04,
23
+ yearlyReturn: 27.47,
24
+ threeYearReturn: 423.26,
25
+ fiveYearReturn: 1589.99,
26
+ ytdReturn: 27.04,
27
+ },
28
+ ],
29
+ recordCount: 511,
30
+ };
31
+
32
+ describe("ScreenerClient", () => {
33
+ let client: ScreenerClient;
34
+
35
+ beforeAll(() => {
36
+ const config = (global as any).testSuite.config as LaplaceConfiguration;
37
+ const logger: Logger = {
38
+ info: jest.fn(),
39
+ error: jest.fn(),
40
+ warn: jest.fn(),
41
+ debug: jest.fn(),
42
+ } as unknown as Logger;
43
+
44
+ client = new ScreenerClient(config, logger);
45
+ });
46
+
47
+ describe("Integration Tests", () => {
48
+ jest.setTimeout(60_000);
49
+
50
+ test("getScreener returns valid paginated data", async () => {
51
+ const resp = await client.getScreener(Region.Tr, {
52
+ sortBy: ScreenerSortBy.MarketCap,
53
+ sortOrder: ScreenerSortOrder.Desc,
54
+ page: 1,
55
+ pageSize: 10,
56
+ });
57
+
58
+ expect(resp).toBeDefined();
59
+ expect(typeof resp.recordCount).toBe("number");
60
+ expect(resp.recordCount).toBeGreaterThanOrEqual(0);
61
+ expect(Array.isArray(resp.items)).toBe(true);
62
+
63
+ if (resp.items.length > 0) {
64
+ const s = resp.items[0];
65
+ expect(typeof s.symbol).toBe("string");
66
+ expect(s.price == null || typeof s.price === "number").toBe(true);
67
+ expect(s.marketCap == null || typeof s.marketCap === "number").toBe(true);
68
+ }
69
+ });
70
+ });
71
+
72
+ describe("Mock Tests", () => {
73
+ let client: ScreenerClient;
74
+ let cli: { request: jest.Mock };
75
+
76
+ beforeEach(() => {
77
+ cli = { request: jest.fn() };
78
+
79
+ const config = (global as any).testSuite.config as LaplaceConfiguration;
80
+ const logger: Logger = {
81
+ info: jest.fn(),
82
+ error: jest.fn(),
83
+ warn: jest.fn(),
84
+ debug: jest.fn(),
85
+ } as unknown as Logger;
86
+
87
+ client = new ScreenerClient(config, logger, cli as any);
88
+ });
89
+
90
+ test("calls correct endpoint, query and body and matches raw response", async () => {
91
+ cli.request.mockResolvedValueOnce({ data: mockScreenerResponse });
92
+
93
+ const body = {
94
+ filters: {
95
+ price: { min: 10.5, max: 500 },
96
+ marketCap: { min: 10000000000 },
97
+ },
98
+ sortBy: ScreenerSortBy.MarketCap,
99
+ sortOrder: ScreenerSortOrder.Desc,
100
+ page: 1,
101
+ pageSize: 20,
102
+ };
103
+
104
+ const resp = await client.getScreener(Region.Tr, body);
105
+
106
+ expect(cli.request).toHaveBeenCalledTimes(1);
107
+ const call = cli.request.mock.calls[0][0];
108
+
109
+ expect(call.method).toBe("POST");
110
+ expect(call.url).toBe("/api/v1/screener");
111
+ expect(call.params).toEqual({ region: Region.Tr });
112
+ expect(call.data).toEqual(body);
113
+
114
+ expect(resp.recordCount).toBe(511);
115
+ expect(resp.items).toHaveLength(1);
116
+ expect(resp.items[0].symbol).toBe("AKBNK");
117
+ expect(resp.items[0].price).toBe(931.5);
118
+ expect(resp.items[0].marketCap).toBe(4841200000000);
119
+ });
120
+
121
+ test("omits region param when not provided and sends empty body when request omitted", async () => {
122
+ cli.request.mockResolvedValueOnce({ data: mockScreenerResponse });
123
+
124
+ await client.getScreener();
125
+
126
+ const call = cli.request.mock.calls[0][0];
127
+ expect(call.method).toBe("POST");
128
+ expect(call.url).toBe("/api/v1/screener");
129
+ expect(call.params).toBeUndefined();
130
+ expect(call.data).toEqual({});
131
+ });
132
+
133
+ test("bubbles up request error", async () => {
134
+ cli.request.mockRejectedValueOnce(new Error("Failed to fetch screener"));
135
+
136
+ await expect(client.getScreener(Region.Tr)).rejects.toThrow(
137
+ "Failed to fetch screener"
138
+ );
139
+
140
+ expect(cli.request).toHaveBeenCalledTimes(1);
141
+ });
142
+ });
143
+ });