laplace-api 1.1.2 → 1.1.5

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": "1.1.2",
3
+ "version": "1.1.5",
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": {
@@ -42,7 +42,8 @@
42
42
  "event-source-polyfill": "^1.0.31",
43
43
  "mongodb": "^6.8.0",
44
44
  "uuid": "^10.0.0",
45
- "winston": "^3.14.2"
45
+ "winston": "^3.14.2",
46
+ "ws": "^8.18.0"
46
47
  },
47
48
  "devDependencies": {
48
49
  "@types/axios": "^0.14.0",
@@ -51,6 +52,7 @@
51
52
  "@types/mongodb": "^4.0.7",
52
53
  "@types/node": "^22.5.4",
53
54
  "@types/winston": "^2.4.4",
55
+ "@types/ws": "^8.5.13",
54
56
  "jest": "^29.7.0",
55
57
  "ts-jest": "^29.2.5",
56
58
  "typescript": "^5.5.4"
@@ -0,0 +1,398 @@
1
+ export interface BISTStockLiveData {
2
+ symbol: string;
3
+ cl: number; // Close
4
+ c: number; // PercentChange
5
+ }
6
+
7
+ interface WebSocketOptions {
8
+ enableLogging?: boolean;
9
+ reconnectAttempts?: number;
10
+ reconnectDelay?: number;
11
+ maxReconnectDelay?: number;
12
+ }
13
+
14
+ type WebSocketMessageType = "heartbeat" | "error" | "warning" | "price_update";
15
+
16
+ interface WebSocketMessage<T> {
17
+ type: WebSocketMessageType;
18
+ message?: T;
19
+ }
20
+
21
+ export enum WebSocketErrorType {
22
+ MAX_RECONNECT_EXCEEDED = "MAX_RECONNECT_EXCEEDED",
23
+ CONNECTION_ERROR = "CONNECTION_ERROR",
24
+ CLOSE_ERROR = "CLOSE_ERROR",
25
+ WEBSOCKET_NOT_INITIALIZED = "WEBSOCKET_NOT_INITIALIZED",
26
+ MESSAGE_PARSE_ERROR = "MESSAGE_PARSE_ERROR",
27
+ WEBSOCKET_NOT_CONNECTED = "WEBSOCKET_NOT_CONNECTED",
28
+ WEBSOCKET_ERROR = "WEBSOCKET_ERROR",
29
+ UNKNOWN_ERROR = "UNKNOWN_ERROR",
30
+ }
31
+
32
+ export enum WebSocketCloseReason {
33
+ NORMAL_CLOSURE = "NORMAL_CLOSURE",
34
+ CONNECTION_ERROR = "CONNECTION_ERROR",
35
+ MAX_RECONNECT_EXCEEDED = "MAX_RECONNECT_EXCEEDED",
36
+ UNKNOWN = "UNKNOWN",
37
+ }
38
+
39
+ export class WebSocketError extends Error {
40
+ constructor(
41
+ message: string,
42
+ public readonly code: WebSocketErrorType = WebSocketErrorType.UNKNOWN_ERROR
43
+ ) {
44
+ super(message);
45
+ this.name = "WebSocketError";
46
+ }
47
+ }
48
+
49
+ export class LivePriceWebSocketClient {
50
+ private ws: WebSocket | null = null;
51
+ private subscriptionCounter = 0;
52
+ private subscriptions = new Map<
53
+ number,
54
+ {
55
+ symbols: string[];
56
+ handler: (data: BISTStockLiveData) => void;
57
+ }
58
+ >();
59
+ private reconnectAttempts = 0;
60
+ private reconnectTimeout: NodeJS.Timeout | null = null;
61
+ private isClosed: boolean = false;
62
+ private closedReason: WebSocketCloseReason | null = null;
63
+ private wsUrl: string | null = null;
64
+ private readonly options: Required<WebSocketOptions>;
65
+
66
+ constructor(options: WebSocketOptions = {}) {
67
+ this.options = {
68
+ enableLogging: true,
69
+ reconnectAttempts: 5,
70
+ reconnectDelay: 1000,
71
+ maxReconnectDelay: 30000,
72
+ ...options,
73
+ };
74
+ }
75
+
76
+ private log(message: string, level: "info" | "error" | "warn" = "info") {
77
+ if (!this.options.enableLogging) return;
78
+
79
+ const prefix = `[LivePriceWebSocket][${level.toUpperCase()}]`;
80
+
81
+ switch (level) {
82
+ case "error":
83
+ console.error(`${prefix} ${message}`);
84
+ break;
85
+ case "warn":
86
+ console.warn(`${prefix} ${message}`);
87
+ break;
88
+ default:
89
+ console.info(`${prefix} ${message}`);
90
+ break;
91
+ }
92
+ }
93
+
94
+ async connect(url: string): Promise<WebSocket> {
95
+ this.log("Connecting to WebSocket...");
96
+ this.wsUrl = url;
97
+
98
+ if (!this.ws || this.ws.readyState === WebSocket.CLOSED) {
99
+ this.ws = new WebSocket(url);
100
+ await this.setupWebSocket();
101
+ }
102
+
103
+ return this.ws;
104
+ }
105
+
106
+ private async setupWebSocket(): Promise<void> {
107
+ if (!this.ws) {
108
+ throw new WebSocketError(
109
+ "WebSocket not initialized",
110
+ WebSocketErrorType.WEBSOCKET_NOT_INITIALIZED
111
+ );
112
+ }
113
+
114
+ return new Promise((resolve, reject) => {
115
+ if (!this.ws) {
116
+ return reject(
117
+ new WebSocketError(
118
+ "WebSocket not initialized",
119
+ WebSocketErrorType.WEBSOCKET_NOT_INITIALIZED
120
+ )
121
+ );
122
+ }
123
+
124
+ this.ws.onopen = () => {
125
+ this.reconnectAttempts = 0;
126
+ this.log("WebSocket connected");
127
+ resolve();
128
+ };
129
+
130
+ this.ws.onerror = (error) => {
131
+ reject(
132
+ new WebSocketError(
133
+ `WebSocket connection error: ${error}`,
134
+ WebSocketErrorType.CONNECTION_ERROR
135
+ )
136
+ );
137
+ };
138
+
139
+ this.ws.onclose = () => {
140
+ this.isClosed = true;
141
+ this.log("WebSocket closed");
142
+ if (this.closedReason !== WebSocketCloseReason.NORMAL_CLOSURE) {
143
+ try {
144
+ this.attemptReconnect();
145
+ } catch (error) {
146
+ if (error instanceof WebSocketError) {
147
+ switch (error.code) {
148
+ case WebSocketErrorType.MAX_RECONNECT_EXCEEDED:
149
+ this.closedReason =
150
+ WebSocketCloseReason.MAX_RECONNECT_EXCEEDED;
151
+ break;
152
+ case WebSocketErrorType.CONNECTION_ERROR:
153
+ case WebSocketErrorType.WEBSOCKET_NOT_INITIALIZED:
154
+ this.closedReason = WebSocketCloseReason.CONNECTION_ERROR;
155
+ break;
156
+ default:
157
+ this.closedReason = WebSocketCloseReason.UNKNOWN;
158
+ break;
159
+ }
160
+ } else {
161
+ this.closedReason = WebSocketCloseReason.UNKNOWN;
162
+ }
163
+ this.log(`Failed to reconnect: ${error}`, "error");
164
+ }
165
+ }
166
+ };
167
+
168
+ this.ws.onmessage = (event) => {
169
+ try {
170
+ const rawData = JSON.parse(event.data.toString());
171
+ switch (rawData.type) {
172
+ case "price_update":
173
+ const priceData = rawData as WebSocketMessage<BISTStockLiveData>;
174
+ const data = priceData.message;
175
+ if (!data) {
176
+ throw new WebSocketError(
177
+ "Price update message is empty",
178
+ WebSocketErrorType.MESSAGE_PARSE_ERROR
179
+ );
180
+ }
181
+ if (data.symbol) {
182
+ const handlers = this.getHandlersForSymbol(data.symbol);
183
+ handlers.forEach((handler) => handler(data));
184
+ }
185
+ break;
186
+ case "heartbeat":
187
+ this.log("Received heartbeat");
188
+ return;
189
+ case "error":
190
+ this.log(`Received error: ${rawData.message}`, "error");
191
+ return;
192
+ case "warning":
193
+ this.log(`Received warning: ${rawData.message}`, "warn");
194
+ return;
195
+ default:
196
+ this.log(`Unknown message type: ${rawData.type}`, "error");
197
+ return;
198
+ }
199
+ } catch (error) {
200
+ this.log(`Failed to parse WebSocket message: ${error}`, "error");
201
+ }
202
+ };
203
+ });
204
+ }
205
+
206
+ private getActiveSymbols(): string[] {
207
+ const allSymbols = Array.from(this.subscriptions.values()).flatMap(
208
+ (sub) => sub.symbols
209
+ );
210
+
211
+ return [...new Set(allSymbols)];
212
+ }
213
+
214
+ private async attemptReconnect() {
215
+ const url = this.wsUrl;
216
+ if (!url) {
217
+ throw new WebSocketError(
218
+ "WebSocket URL is not set",
219
+ WebSocketErrorType.WEBSOCKET_NOT_INITIALIZED
220
+ );
221
+ }
222
+ if (this.reconnectAttempts >= this.options.reconnectAttempts) {
223
+ throw new WebSocketError(
224
+ `Maximum reconnection attempts (${this.options.reconnectAttempts}) reached`,
225
+ WebSocketErrorType.MAX_RECONNECT_EXCEEDED
226
+ );
227
+ }
228
+
229
+ this.reconnectAttempts++;
230
+ const delay = Math.min(
231
+ this.options.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1),
232
+ this.options.maxReconnectDelay
233
+ );
234
+
235
+ this.log(
236
+ `Attempting to reconnect (${this.reconnectAttempts}/${this.options.reconnectAttempts}) in ${delay}ms...`
237
+ );
238
+
239
+ if (this.reconnectTimeout) {
240
+ clearTimeout(this.reconnectTimeout);
241
+ this.reconnectTimeout = null;
242
+ }
243
+
244
+ this.reconnectTimeout = setTimeout(async () => {
245
+ try {
246
+ await this.connect(url);
247
+ const activeSymbols = this.getActiveSymbols();
248
+ if (activeSymbols.length > 0) {
249
+ this.addSymbols(activeSymbols);
250
+ }
251
+ if (this.reconnectTimeout) {
252
+ clearTimeout(this.reconnectTimeout);
253
+ this.reconnectTimeout = null;
254
+ }
255
+ this.reconnectAttempts = 0;
256
+ } catch (error) {
257
+ this.attemptReconnect();
258
+ }
259
+ }, delay).unref();
260
+ }
261
+
262
+ subscribe(
263
+ symbols: string[],
264
+ handler: (data: BISTStockLiveData) => void
265
+ ): () => void {
266
+ const subscriptionId = this.subscriptionCounter++;
267
+ let symbolsToAdd: string[] = [];
268
+
269
+ this.subscriptions.set(subscriptionId, { symbols, handler });
270
+
271
+ for (const symbol of symbols) {
272
+ if (this.getHandlersForSymbol(symbol).length === 1) {
273
+ symbolsToAdd.push(symbol);
274
+ }
275
+ }
276
+ if (symbolsToAdd.length > 0) {
277
+ this.addSymbols(symbolsToAdd);
278
+ }
279
+
280
+ return () => {
281
+ this.subscriptions.delete(subscriptionId);
282
+ const symbolsForRemove = symbols.filter(
283
+ (s) => this.getHandlersForSymbol(s).length === 0
284
+ );
285
+ this.removeSymbols(symbolsForRemove);
286
+ };
287
+ }
288
+
289
+ private getHandlersForSymbol(
290
+ symbol: string
291
+ ): ((data: BISTStockLiveData) => void)[] {
292
+ return Array.from(this.subscriptions.values())
293
+ .filter((s) => s.symbols.includes(symbol))
294
+ .map((s) => s.handler);
295
+ }
296
+
297
+ private async removeSymbols(symbols: string[]) {
298
+ if (!this.ws) {
299
+ throw new WebSocketError(
300
+ "WebSocket is not initialized",
301
+ WebSocketErrorType.WEBSOCKET_NOT_INITIALIZED
302
+ );
303
+ }
304
+
305
+ if (this.ws.readyState !== WebSocket.OPEN) {
306
+ throw new WebSocketError(
307
+ "WebSocket is not connected",
308
+ WebSocketErrorType.WEBSOCKET_NOT_CONNECTED
309
+ );
310
+ }
311
+ this.ws.send(
312
+ JSON.stringify({
313
+ type: "unsubscribe",
314
+ symbols: symbols,
315
+ })
316
+ );
317
+ }
318
+
319
+ private async addSymbols(symbols: string[]) {
320
+ if (!this.ws) {
321
+ throw new WebSocketError(
322
+ "WebSocket is not initialized",
323
+ WebSocketErrorType.WEBSOCKET_NOT_INITIALIZED
324
+ );
325
+ }
326
+
327
+ if (this.ws.readyState !== WebSocket.OPEN) {
328
+ throw new WebSocketError(
329
+ "WebSocket is not connected",
330
+ WebSocketErrorType.WEBSOCKET_NOT_CONNECTED
331
+ );
332
+ }
333
+
334
+ this.ws.send(
335
+ JSON.stringify({
336
+ type: "subscribe",
337
+ symbols: symbols,
338
+ })
339
+ );
340
+ }
341
+
342
+ async close(): Promise<void> {
343
+ try {
344
+ this.subscriptions.clear();
345
+ this.closedReason = WebSocketCloseReason.NORMAL_CLOSURE;
346
+ if (this.ws?.readyState === WebSocket.OPEN) {
347
+ await new Promise<void>((resolve, reject) => {
348
+ if (!this.ws) {
349
+ resolve();
350
+ return;
351
+ }
352
+
353
+ this.ws.onclose = () => {
354
+ this.isClosed = true;
355
+ resolve();
356
+ };
357
+
358
+ try {
359
+ this.ws.close();
360
+ } catch (closeError) {
361
+ this.closedReason = null;
362
+ reject(
363
+ new WebSocketError(
364
+ `Failed to initiate close: ${closeError}`,
365
+ WebSocketErrorType.CLOSE_ERROR
366
+ )
367
+ );
368
+ }
369
+ });
370
+ }
371
+ } catch (error) {
372
+ const errorMessage =
373
+ error instanceof WebSocketError
374
+ ? error.message
375
+ : `Unexpected error during close: ${error}`;
376
+
377
+ this.log(errorMessage, "error");
378
+ throw error instanceof WebSocketError
379
+ ? error
380
+ : new WebSocketError(errorMessage, WebSocketErrorType.CLOSE_ERROR);
381
+ } finally {
382
+ if (this.reconnectTimeout) {
383
+ clearTimeout(this.reconnectTimeout);
384
+ this.reconnectTimeout = null;
385
+ }
386
+ this.ws = null;
387
+ this.subscriptions.clear();
388
+ }
389
+ }
390
+
391
+ isConnectionClosed(): boolean {
392
+ return this.isClosed;
393
+ }
394
+
395
+ getCloseReason(): WebSocketCloseReason | null {
396
+ return this.closedReason;
397
+ }
398
+ }
@@ -1,8 +1,20 @@
1
- import { Client } from './client';
2
- import { Region } from './collections';
1
+ import { Client } from "./client";
2
+ import { Region } from "./collections";
3
3
  import { v4 as uuidv4 } from 'uuid';
4
4
 
5
- function getLivePrice<T>(
5
+ export interface BISTStockLiveData {
6
+ s: string; // Symbol
7
+ ch: number; // DailyPercentChange
8
+ p: number; // ClosePrice
9
+ }
10
+
11
+ export interface USStockLiveData {
12
+ s: string; // Symbol
13
+ bp: number; // BidPrice
14
+ ap: number; // AskPrice
15
+ }
16
+
17
+ function getSSELivePrice<T>(
6
18
  client: Client,
7
19
  symbols: string[],
8
20
  region: Region,
@@ -16,19 +28,26 @@ function getLivePrice<T>(
16
28
  return client.sendSSERequest<T>(url);
17
29
  }
18
30
 
19
- export interface BISTStockLiveData {
20
- s: string; // Symbol
21
- ch: number; // DailyPercentChange
22
- p: number; // ClosePrice
23
- }
31
+ export class LivePriceWebSocketUrlClient extends Client {
32
+ async getWebSocketUrl(
33
+ externalUserId: string,
34
+ region: Region
35
+ ): Promise<string> {
36
+ const url = new URL(`${this["baseUrl"]}/api/v1/ws/url`);
37
+ url.searchParams.append("region", region);
38
+ url.searchParams.append("accessLevel", "KRMD1");
24
39
 
25
- export interface USStockLiveData {
26
- s: string; // Symbol
27
- bp: number; // BidPrice
28
- ap: number; // AskPrice
29
- }
40
+ const response = await this.sendRequest<string>({
41
+ method: "POST",
42
+ url: url.toString(),
43
+ data: {
44
+ externalUserId: externalUserId,
45
+ },
46
+ });
47
+
48
+ return response;
49
+ }
30
50
 
31
- export class LivePriceClient extends Client {
32
51
  getLivePriceForBIST(
33
52
  symbols: string[],
34
53
  region: Region,
@@ -37,7 +56,7 @@ export class LivePriceClient extends Client {
37
56
  events: AsyncIterable<BISTStockLiveData>,
38
57
  cancel: () => void
39
58
  } {
40
- return getLivePrice<BISTStockLiveData>(this, symbols, region, streamId);
59
+ return getSSELivePrice<BISTStockLiveData>(this, symbols, region, streamId);
41
60
  }
42
61
 
43
62
  getLivePriceForUS(
@@ -48,6 +67,6 @@ export class LivePriceClient extends Client {
48
67
  events: AsyncIterable<USStockLiveData>,
49
68
  cancel: () => void
50
69
  } {
51
- return getLivePrice<USStockLiveData>(this, symbols, region, streamId);
70
+ return getSSELivePrice<USStockLiveData>(this, symbols, region, streamId);
52
71
  }
53
- }
72
+ }
@@ -55,21 +55,13 @@ export interface LocaleString {
55
55
  [key: string]: string;
56
56
  }
57
57
 
58
- export interface StockDetail {
59
- id: string;
60
- assetType: AssetType;
58
+ export interface StockDetail extends Stock {
61
59
  assetClass: AssetClass;
62
- name: string;
63
- symbol: string;
64
60
  description: string;
65
61
  localized_description: LocaleString;
66
62
  shortDescription: string;
67
63
  localizedShortDescription: LocaleString;
68
64
  region: string;
69
- sectorId: string;
70
- industryId: string;
71
- updatedDate: string;
72
- active: boolean;
73
65
  markets: Market[];
74
66
  }
75
67
 
@@ -0,0 +1,183 @@
1
+ import { Logger } from "winston";
2
+ import { LaplaceConfiguration } from "../utilities/configuration";
3
+ import { Region } from "../client/collections";
4
+ import "./client_test_suite";
5
+ import { LivePriceWebSocketUrlClient } from "../client/live-price";
6
+ import {
7
+ BISTStockLiveData,
8
+ LivePriceWebSocketClient,
9
+ } from "../client/live-price-web-socket";
10
+
11
+ describe("LivePrice", () => {
12
+ let livePriceUrlClient: LivePriceWebSocketUrlClient;
13
+ let url: string;
14
+ let ws: LivePriceWebSocketClient;
15
+
16
+ const TEST_CONSTANTS = {
17
+ JEST_TIMEOUT: 30000,
18
+ MAIN_TIMEOUT: 25000,
19
+ };
20
+
21
+ beforeAll(async () => {
22
+ const config = (global as any).testSuite.config as LaplaceConfiguration;
23
+ const logger: Logger = {
24
+ info: jest.fn(),
25
+ error: jest.fn(),
26
+ warn: jest.fn(),
27
+ debug: jest.fn(),
28
+ } as unknown as Logger;
29
+
30
+ livePriceUrlClient = new LivePriceWebSocketUrlClient(config, logger);
31
+ url = await livePriceUrlClient.getWebSocketUrl("2459", Region.Tr);
32
+
33
+ ws = new LivePriceWebSocketClient({
34
+ enableLogging: true,
35
+ });
36
+
37
+ await ws.connect(url);
38
+ });
39
+
40
+ afterAll(async () => {
41
+ try {
42
+ await ws.close();
43
+ } catch (error) {
44
+ console.error("Error closing websocket connection", error);
45
+ }
46
+ });
47
+
48
+ describe("BIST Live Price Tests", () => {
49
+ const symbols = ["TUPRS", "SASA", "THYAO", "GARAN", "YKBNK"];
50
+ const newSymbols = ["AKBNK", "KCHOL"];
51
+
52
+ it(
53
+ "should receive data for initial and updated symbols",
54
+ async () => {
55
+ const receivedData: BISTStockLiveData[] = [];
56
+
57
+ await new Promise<void>((resolve, reject) => {
58
+ const timeoutId = setTimeout(() => {
59
+ reject(new Error("Test timeout: No data received"));
60
+ }, TEST_CONSTANTS.MAIN_TIMEOUT).unref();
61
+
62
+ let unsubscribeNewSymbols: (() => void) | null = null;
63
+
64
+ const initialHandler = (data: BISTStockLiveData) => {
65
+ receivedData.push(data);
66
+
67
+ if (symbols.includes(data.symbol)) {
68
+ const unsubscribeInitial = ws.subscribe(symbols, initialHandler);
69
+ unsubscribeInitial();
70
+
71
+ unsubscribeNewSymbols = ws.subscribe(newSymbols, (data) => {
72
+ receivedData.push(data);
73
+
74
+ if (newSymbols.includes(data.symbol)) {
75
+ clearTimeout(timeoutId);
76
+ if (unsubscribeNewSymbols) unsubscribeNewSymbols();
77
+ resolve();
78
+ }
79
+ });
80
+ }
81
+ };
82
+
83
+ ws.subscribe(symbols, initialHandler);
84
+ });
85
+
86
+ const newSymbolData = receivedData.filter((data) =>
87
+ newSymbols.includes(data.symbol)
88
+ );
89
+ const oldSymbolData = receivedData.filter((data) =>
90
+ symbols.includes(data.symbol)
91
+ );
92
+
93
+ expect(oldSymbolData.length).toBeGreaterThan(0);
94
+ expect(newSymbolData.length).toBeGreaterThan(0);
95
+
96
+ newSymbolData.forEach((data) => {
97
+ expect(newSymbols).toContain(data.symbol);
98
+ expect(typeof data.symbol).toBe("string");
99
+ expect(typeof data.c).toBe("number");
100
+ expect(typeof data.cl).toBe("number");
101
+ });
102
+
103
+ const lastNewSymbolIndex = receivedData.findIndex((data) =>
104
+ newSymbols.includes(data.symbol)
105
+ );
106
+ const dataAfterUpdate = receivedData.slice(lastNewSymbolIndex);
107
+ const oldSymbolDataAfterUpdate = dataAfterUpdate.filter((data) =>
108
+ symbols.includes(data.symbol)
109
+ );
110
+
111
+ expect(oldSymbolDataAfterUpdate.length).toBe(0);
112
+ },
113
+ TEST_CONSTANTS.JEST_TIMEOUT
114
+ );
115
+
116
+ it(
117
+ "should handle multiple subscriptions for the same symbol",
118
+ async () => {
119
+ const symbol = "GARAN";
120
+ const receivedData1: BISTStockLiveData[] = [];
121
+ const receivedData2: BISTStockLiveData[] = [];
122
+
123
+ await new Promise<void>((resolve, reject) => {
124
+ const timeoutId = setTimeout(() => {
125
+ reject(new Error("Test timeout: No data received"));
126
+ }, TEST_CONSTANTS.MAIN_TIMEOUT).unref();
127
+
128
+ const unsubscribe1 = ws.subscribe([symbol], (data) => {
129
+ receivedData1.push(data);
130
+ });
131
+
132
+ const unsubscribe2 = ws.subscribe([symbol], (data) => {
133
+ receivedData2.push(data);
134
+ if (receivedData2.length >= 2) {
135
+ clearTimeout(timeoutId);
136
+ unsubscribe1();
137
+ unsubscribe2();
138
+ resolve();
139
+ }
140
+ });
141
+ });
142
+
143
+ expect(receivedData1.length).toBeGreaterThan(0);
144
+ expect(receivedData2.length).toBeGreaterThan(0);
145
+ expect(receivedData1).toEqual(receivedData2);
146
+ },
147
+ TEST_CONSTANTS.JEST_TIMEOUT
148
+ );
149
+ });
150
+
151
+ // const testLivePrice = async (
152
+ // symbols: string[],
153
+ // region: Region,
154
+ // getLivePriceFunc: (symbols: string[], region: Region) => AsyncGenerator<BISTStockLiveData | USStockLiveData, void, unknown>
155
+ // ) => {
156
+ // const livePriceGenerator = getLivePriceFunc.call(livePriceClient, symbols, region);
157
+ // let livePriceCount = 0;
158
+
159
+ // try {
160
+ // for await (const livePrice of livePriceGenerator) {
161
+ // expect(livePrice).not.toBeEmpty();
162
+ // livePriceCount++;
163
+ // if (livePriceCount > 3) {
164
+ // break;
165
+ // }
166
+ // }
167
+ // } catch (error) {
168
+ // throw new Error(`Error occurred during live price retrieval: ${error}`);
169
+ // }
170
+
171
+ // expect(livePriceCount).toBeGreaterThan(0);
172
+ // };
173
+
174
+ // test('BISTLivePrice', async () => {
175
+ // const symbols = ['TUPRS', 'SASA', 'THYAO', 'GARAN', 'YKBN'];
176
+ // await testLivePrice(symbols, Region.Tr, livePriceClient.getLivePriceForBIST);
177
+ // }, 10000);
178
+
179
+ // test('USLivePrice', async () => {
180
+ // const symbols = ['AAPL', 'GOOGL', 'MSFT', 'AMZN', 'META'];
181
+ // await testLivePrice(symbols, Region.Us, livePriceClient.getLivePriceForUS);
182
+ // }, 10000);
183
+ });
@@ -1,2 +1,2 @@
1
- BASE_URL=https://api.finfree.app
2
- API_KEY=api-6fa6cefb-16df-4a19-8351-54f83c6bbe2f
1
+ BASE_URL=
2
+ API_KEY=
@@ -1,57 +0,0 @@
1
- // import { Logger } from 'winston';
2
- // import { LaplaceConfiguration } from '../utilities/configuration';
3
- // import { Client, createClient } from '../client/client';
4
- // import { LivePriceClient, BISTStockLiveData, USStockLiveData } from '../client/live_price';
5
- // import { Region } from '../client/collections';
6
- // import './client_test_suite';
7
-
8
- // describe('LivePrice', () => {
9
- // let client: Client;
10
- // let livePriceClient: LivePriceClient;
11
-
12
- // beforeAll(() => {
13
- // const config = (global as any).testSuite.config as LaplaceConfiguration;
14
- // const logger: Logger = {
15
- // info: jest.fn(),
16
- // error: jest.fn(),
17
- // warn: jest.fn(),
18
- // debug: jest.fn(),
19
- // } as unknown as Logger;
20
-
21
- // client = createClient(config, logger);
22
- // livePriceClient = new LivePriceClient(client);
23
- // });
24
-
25
- // const testLivePrice = async (
26
- // symbols: string[],
27
- // region: Region,
28
- // getLivePriceFunc: (symbols: string[], region: Region) => AsyncGenerator<BISTStockLiveData | USStockLiveData, void, unknown>
29
- // ) => {
30
- // const livePriceGenerator = getLivePriceFunc.call(livePriceClient, symbols, region);
31
- // let livePriceCount = 0;
32
-
33
- // try {
34
- // for await (const livePrice of livePriceGenerator) {
35
- // expect(livePrice).not.toBeEmpty();
36
- // livePriceCount++;
37
- // if (livePriceCount > 3) {
38
- // break;
39
- // }
40
- // }
41
- // } catch (error) {
42
- // throw new Error(`Error occurred during live price retrieval: ${error}`);
43
- // }
44
-
45
- // expect(livePriceCount).toBeGreaterThan(0);
46
- // };
47
-
48
- // test('BISTLivePrice', async () => {
49
- // const symbols = ['TUPRS', 'SASA', 'THYAO', 'GARAN', 'YKBN'];
50
- // await testLivePrice(symbols, Region.Tr, livePriceClient.getLivePriceForBIST);
51
- // }, 10000);
52
-
53
- // test('USLivePrice', async () => {
54
- // const symbols = ['AAPL', 'GOOGL', 'MSFT', 'AMZN', 'META'];
55
- // await testLivePrice(symbols, Region.Us, livePriceClient.getLivePriceForUS);
56
- // }, 10000);
57
- // });