laplace-api 4.4.0 → 4.6.0
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 +1 -1
- package/src/client/live-price-web-socket.ts +38 -14
- package/src/client/news.ts +137 -0
- package/src/client/stocks.ts +7 -5
- package/src/test/news.test.ts +204 -0
- package/src/test/stocks.test.ts +35 -27
package/package.json
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { OrderbookLiveData } from "./live-price";
|
|
2
|
+
|
|
1
3
|
interface RawBISTStockLiveData {
|
|
2
4
|
_id: number;
|
|
3
5
|
symbol: string;
|
|
@@ -33,16 +35,19 @@ export enum LivePriceFeed {
|
|
|
33
35
|
LiveUs = "live_price_us",
|
|
34
36
|
DelayedBist = "delayed_price_tr",
|
|
35
37
|
DelayedUs = "delayed_price_us",
|
|
36
|
-
|
|
38
|
+
DepthBist = "depth_tr",
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
type StockLiveDataType<T extends LivePriceFeed> = T extends
|
|
40
42
|
| LivePriceFeed.LiveBist
|
|
41
43
|
| LivePriceFeed.DelayedBist
|
|
42
|
-
?
|
|
43
|
-
|
|
44
|
+
? BISTStockLiveData
|
|
45
|
+
: T extends LivePriceFeed.DepthBist
|
|
46
|
+
? OrderbookLiveData
|
|
44
47
|
: USStockLiveData;
|
|
45
48
|
|
|
49
|
+
type LastDataKey = `${string}:${LivePriceFeed}`;
|
|
50
|
+
|
|
46
51
|
export enum LogLevel {
|
|
47
52
|
Info = "info",
|
|
48
53
|
Warn = "warn",
|
|
@@ -94,14 +99,17 @@ export class LivePriceWebSocketClient {
|
|
|
94
99
|
number,
|
|
95
100
|
{
|
|
96
101
|
symbols: string[];
|
|
97
|
-
handler: (data: BISTStockLiveData | USStockLiveData) => void;
|
|
102
|
+
handler: (data: BISTStockLiveData | USStockLiveData | OrderbookLiveData) => void;
|
|
98
103
|
feed: LivePriceFeed;
|
|
99
104
|
}
|
|
100
105
|
>();
|
|
101
106
|
private symbolLastData = new Map<
|
|
102
|
-
|
|
103
|
-
BISTStockLiveData | USStockLiveData
|
|
107
|
+
LastDataKey,
|
|
108
|
+
BISTStockLiveData | USStockLiveData | OrderbookLiveData
|
|
104
109
|
>();
|
|
110
|
+
private getLastDataKey(symbol: string, feed: LivePriceFeed): LastDataKey {
|
|
111
|
+
return `${symbol}:${feed}`;
|
|
112
|
+
}
|
|
105
113
|
private reconnectAttempts = 0;
|
|
106
114
|
private reconnectTimeout: NodeJS.Timeout | null = null;
|
|
107
115
|
private isClosed: boolean = false;
|
|
@@ -276,13 +284,11 @@ export class LivePriceWebSocketClient {
|
|
|
276
284
|
WebSocketErrorType.MESSAGE_PARSE_ERROR
|
|
277
285
|
);
|
|
278
286
|
}
|
|
279
|
-
let priceData: BISTStockLiveData | USStockLiveData;
|
|
287
|
+
let priceData: BISTStockLiveData | USStockLiveData | OrderbookLiveData;
|
|
280
288
|
|
|
281
289
|
if (
|
|
282
290
|
feed === LivePriceFeed.DelayedBist ||
|
|
283
291
|
feed === LivePriceFeed.LiveBist
|
|
284
|
-
// ||
|
|
285
|
-
// feed === LivePriceFeed.DepthBist
|
|
286
292
|
) {
|
|
287
293
|
const message = messageData as RawBISTStockLiveData;
|
|
288
294
|
priceData = {
|
|
@@ -293,6 +299,13 @@ export class LivePriceWebSocketClient {
|
|
|
293
299
|
timestamp: message?.d,
|
|
294
300
|
percentChange: message?.c,
|
|
295
301
|
} as BISTStockLiveData;
|
|
302
|
+
} else if (feed === LivePriceFeed.DepthBist) {
|
|
303
|
+
const message = messageData as OrderbookLiveData;
|
|
304
|
+
priceData = {
|
|
305
|
+
updated: message.updated,
|
|
306
|
+
deleted: message.deleted,
|
|
307
|
+
symbol: message?.symbol,
|
|
308
|
+
} as OrderbookLiveData;
|
|
296
309
|
} else {
|
|
297
310
|
const message = messageData as RawUSStockLiveData;
|
|
298
311
|
priceData = {
|
|
@@ -302,7 +315,8 @@ export class LivePriceWebSocketClient {
|
|
|
302
315
|
} as USStockLiveData;
|
|
303
316
|
}
|
|
304
317
|
if (priceData.symbol) {
|
|
305
|
-
this.
|
|
318
|
+
const lastDataKey = this.getLastDataKey(priceData.symbol, feed);
|
|
319
|
+
this.symbolLastData.set(lastDataKey, priceData);
|
|
306
320
|
const handlers = this.getHandlersForSymbol(
|
|
307
321
|
priceData.symbol,
|
|
308
322
|
feed
|
|
@@ -409,7 +423,7 @@ export class LivePriceWebSocketClient {
|
|
|
409
423
|
const subscriptionId = this.subscriptionCounter++;
|
|
410
424
|
let symbolsToAdd: string[] = [];
|
|
411
425
|
|
|
412
|
-
const typedHandler = (data: BISTStockLiveData | USStockLiveData) => {
|
|
426
|
+
const typedHandler = (data: BISTStockLiveData | USStockLiveData | OrderbookLiveData) => {
|
|
413
427
|
handler(data as StockLiveDataType<F>);
|
|
414
428
|
};
|
|
415
429
|
|
|
@@ -424,8 +438,13 @@ export class LivePriceWebSocketClient {
|
|
|
424
438
|
if (symbolHandlers.length === 1) {
|
|
425
439
|
symbolsToAdd.push(symbol);
|
|
426
440
|
} else if (symbolHandlers.length > 1) {
|
|
427
|
-
const
|
|
428
|
-
|
|
441
|
+
const lastDataKey = this.getLastDataKey(symbol, feed);
|
|
442
|
+
const lastData:
|
|
443
|
+
| BISTStockLiveData
|
|
444
|
+
| USStockLiveData
|
|
445
|
+
| OrderbookLiveData
|
|
446
|
+
| undefined = this.symbolLastData.get(lastDataKey);
|
|
447
|
+
|
|
429
448
|
if (lastData) {
|
|
430
449
|
typedHandler(lastData);
|
|
431
450
|
}
|
|
@@ -445,7 +464,7 @@ export class LivePriceWebSocketClient {
|
|
|
445
464
|
private getHandlersForSymbol(
|
|
446
465
|
symbol: string,
|
|
447
466
|
feed: LivePriceFeed
|
|
448
|
-
): ((data: BISTStockLiveData | USStockLiveData) => void)[] {
|
|
467
|
+
): ((data: BISTStockLiveData | USStockLiveData | OrderbookLiveData) => void)[] {
|
|
449
468
|
return Array.from(this.subscriptions.values())
|
|
450
469
|
.filter((s) => s.symbols.includes(symbol) && s.feed === feed)
|
|
451
470
|
.map((s) => s.handler);
|
|
@@ -479,6 +498,11 @@ export class LivePriceWebSocketClient {
|
|
|
479
498
|
feed: feed,
|
|
480
499
|
})
|
|
481
500
|
);
|
|
501
|
+
|
|
502
|
+
for (const symbol of symbols) {
|
|
503
|
+
const key = this.getLastDataKey(symbol, feed);
|
|
504
|
+
this.symbolLastData.delete(key);
|
|
505
|
+
}
|
|
482
506
|
}
|
|
483
507
|
|
|
484
508
|
private async addSymbols(symbols: string[], feed: LivePriceFeed) {
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {Client} from "./client";
|
|
2
|
+
import {Locale, Region} from "./collections";
|
|
3
|
+
import {PaginatedResponse} from "./capital_increase";
|
|
4
|
+
import {SortDirection} from "./broker";
|
|
5
|
+
|
|
6
|
+
export interface NewsHighlights {
|
|
7
|
+
consumer: string[];
|
|
8
|
+
energyAndUtilities: string[];
|
|
9
|
+
finance: string[];
|
|
10
|
+
healthcare: string[];
|
|
11
|
+
industrialsAndMaterials: string[];
|
|
12
|
+
tech: string[];
|
|
13
|
+
other: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export enum NewsType {
|
|
17
|
+
BRIEFS = "briefs",
|
|
18
|
+
BLOOMBERG = "bloomberg",
|
|
19
|
+
FDA = "fda",
|
|
20
|
+
REUTERS = "reuters",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export enum NewsOrderBy {
|
|
24
|
+
TIMESTAMP = "timestamp",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface News {
|
|
28
|
+
url: string;
|
|
29
|
+
imageUrl: string;
|
|
30
|
+
timestamp: string;
|
|
31
|
+
publisherUrl: string;
|
|
32
|
+
publisher: NewsPublisher;
|
|
33
|
+
relatedTickers: NewsTicker[];
|
|
34
|
+
qualityScore: number;
|
|
35
|
+
createdAt: string;
|
|
36
|
+
tickers?: NewsTicker[];
|
|
37
|
+
categories?: NewsCategories;
|
|
38
|
+
sectors?: NewsSector;
|
|
39
|
+
content?: NewsContent;
|
|
40
|
+
industries?: NewsIndustry;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface NewsPublisher {
|
|
44
|
+
name: string;
|
|
45
|
+
logoUrl?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface NewsTicker {
|
|
49
|
+
id: string;
|
|
50
|
+
name: string;
|
|
51
|
+
symbol?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface NewsCategories {
|
|
55
|
+
name: string;
|
|
56
|
+
newsCount: number;
|
|
57
|
+
categoryType?: string;
|
|
58
|
+
meanType?: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface NewsSector {
|
|
62
|
+
name: string;
|
|
63
|
+
newsCount: number;
|
|
64
|
+
categoryType?: string;
|
|
65
|
+
meanType?: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface NewsContent {
|
|
69
|
+
title: string;
|
|
70
|
+
description: string;
|
|
71
|
+
content: string[];
|
|
72
|
+
summary: string[];
|
|
73
|
+
investorInsight: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface NewsIndustry {
|
|
77
|
+
name: string;
|
|
78
|
+
meanType: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export class NewsClient extends Client {
|
|
82
|
+
async getHighlights(region: Region, locale: Locale): Promise<NewsHighlights> {
|
|
83
|
+
const url = new URL(
|
|
84
|
+
`${this["baseUrl"]}/api/v1/news/highlights`,
|
|
85
|
+
);
|
|
86
|
+
url.searchParams.append("region", region);
|
|
87
|
+
url.searchParams.append("locale", locale);
|
|
88
|
+
|
|
89
|
+
return this.sendRequest<NewsHighlights>({
|
|
90
|
+
method: "GET",
|
|
91
|
+
url: url.toString(),
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async getNews(
|
|
96
|
+
region: Region,
|
|
97
|
+
locale: Locale,
|
|
98
|
+
newsType: NewsType,
|
|
99
|
+
page: number | null,
|
|
100
|
+
size: number | null,
|
|
101
|
+
orderBy: NewsOrderBy | null,
|
|
102
|
+
orderByDirection: SortDirection | null,
|
|
103
|
+
extraFilters: string | null,
|
|
104
|
+
): Promise<PaginatedResponse<News>> {
|
|
105
|
+
const url = new URL(
|
|
106
|
+
`${this["baseUrl"]}/api/v1/news`,
|
|
107
|
+
);
|
|
108
|
+
url.searchParams.append("region", region);
|
|
109
|
+
url.searchParams.append("locale", locale);
|
|
110
|
+
url.searchParams.append("newsType", newsType);
|
|
111
|
+
|
|
112
|
+
if (page) {
|
|
113
|
+
url.searchParams.append("page", page.toString());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (size) {
|
|
117
|
+
url.searchParams.append("size", size.toString());
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (orderBy) {
|
|
121
|
+
url.searchParams.append("orderBy", orderBy);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (orderByDirection) {
|
|
125
|
+
url.searchParams.append("orderByDirection", orderByDirection);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (extraFilters) {
|
|
129
|
+
url.searchParams.append("extraFilters", extraFilters);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return this.sendRequest<PaginatedResponse<News>>({
|
|
133
|
+
method: "GET",
|
|
134
|
+
url: url.toString(),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
package/src/client/stocks.ts
CHANGED
|
@@ -298,12 +298,14 @@ export class StockClient extends Client {
|
|
|
298
298
|
if (request.resolution) params.resolution = request.resolution;
|
|
299
299
|
if (request.indicators) params.indicators = request.indicators;
|
|
300
300
|
if (request.chartType != null) params.chartType = request.chartType;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
method:
|
|
304
|
-
url:
|
|
301
|
+
|
|
302
|
+
const data = await this.sendRequest<ArrayBuffer>({
|
|
303
|
+
method: "GET",
|
|
304
|
+
url: "/api/v1/stock/chart",
|
|
305
305
|
params,
|
|
306
|
-
responseType:
|
|
306
|
+
responseType: "arraybuffer",
|
|
307
307
|
});
|
|
308
|
+
|
|
309
|
+
return new Blob([data], { type: "image/png" });
|
|
308
310
|
}
|
|
309
311
|
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { Logger } from "winston";
|
|
2
|
+
import { LaplaceConfiguration } from "../utilities/configuration";
|
|
3
|
+
import {
|
|
4
|
+
NewsClient,
|
|
5
|
+
NewsHighlights,
|
|
6
|
+
News,
|
|
7
|
+
NewsType,
|
|
8
|
+
NewsOrderBy,
|
|
9
|
+
} from "../client/news";
|
|
10
|
+
import "./client_test_suite";
|
|
11
|
+
import { Region, Locale } from "../client/collections";
|
|
12
|
+
import { SortDirection } from "../client/broker";
|
|
13
|
+
import { PaginatedResponse } from "../client/capital_increase";
|
|
14
|
+
|
|
15
|
+
const mockNewsHighlightsResponse: NewsHighlights = {
|
|
16
|
+
consumer: ["news1", "news2"],
|
|
17
|
+
energyAndUtilities: ["news3"],
|
|
18
|
+
finance: ["news4", "news5"],
|
|
19
|
+
healthcare: ["news6"],
|
|
20
|
+
industrialsAndMaterials: ["news7"],
|
|
21
|
+
tech: ["news8"],
|
|
22
|
+
other: ["news9"]
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const mockNewsResponse: News[] = [
|
|
26
|
+
{
|
|
27
|
+
url: "https://example.com/news1",
|
|
28
|
+
imageUrl: "https://example.com/image1.jpg",
|
|
29
|
+
timestamp: "2024-03-14T10:00:00Z",
|
|
30
|
+
publisherUrl: "https://example.com",
|
|
31
|
+
publisher: {
|
|
32
|
+
name: "Example Publisher",
|
|
33
|
+
logoUrl: "https://example.com/logo.png"
|
|
34
|
+
},
|
|
35
|
+
relatedTickers: [
|
|
36
|
+
{
|
|
37
|
+
id: "1",
|
|
38
|
+
name: "Ticker 1",
|
|
39
|
+
symbol: "TCK1"
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
qualityScore: 85,
|
|
43
|
+
createdAt: "2024-03-14T09:00:00Z"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
url: "https://example.com/news2",
|
|
47
|
+
imageUrl: "https://example.com/image2.jpg",
|
|
48
|
+
timestamp: "2024-03-14T11:00:00Z",
|
|
49
|
+
publisherUrl: "https://example.com",
|
|
50
|
+
publisher: {
|
|
51
|
+
name: "Example Publisher 2"
|
|
52
|
+
},
|
|
53
|
+
relatedTickers: [],
|
|
54
|
+
qualityScore: 90,
|
|
55
|
+
createdAt: "2024-03-14T10:00:00Z"
|
|
56
|
+
}
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const mockPaginatedNewsResponse: PaginatedResponse<News> = {
|
|
60
|
+
recordCount: 2,
|
|
61
|
+
items: mockNewsResponse
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
describe("News Client", () => {
|
|
65
|
+
let client: NewsClient;
|
|
66
|
+
|
|
67
|
+
beforeAll(() => {
|
|
68
|
+
const config = (global as any).testSuite.config as LaplaceConfiguration;
|
|
69
|
+
const logger: Logger = {
|
|
70
|
+
info: jest.fn(),
|
|
71
|
+
error: jest.fn(),
|
|
72
|
+
warn: jest.fn(),
|
|
73
|
+
debug: jest.fn(),
|
|
74
|
+
} as unknown as Logger;
|
|
75
|
+
|
|
76
|
+
client = new NewsClient(config, logger);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("Integration Tests", () => {
|
|
80
|
+
describe("getHighlights", () => {
|
|
81
|
+
test("should return news highlights for region and locale", async () => {
|
|
82
|
+
const resp = await client.getHighlights(Region.Tr, Locale.Tr);
|
|
83
|
+
|
|
84
|
+
expect(resp).toBeDefined();
|
|
85
|
+
expect(Array.isArray(resp.consumer)).toBe(true);
|
|
86
|
+
expect(Array.isArray(resp.energyAndUtilities)).toBe(true);
|
|
87
|
+
expect(Array.isArray(resp.finance)).toBe(true);
|
|
88
|
+
expect(Array.isArray(resp.healthcare)).toBe(true);
|
|
89
|
+
expect(Array.isArray(resp.industrialsAndMaterials)).toBe(true);
|
|
90
|
+
expect(Array.isArray(resp.tech)).toBe(true);
|
|
91
|
+
expect(Array.isArray(resp.other)).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("getNews", () => {
|
|
96
|
+
test("should return paginated news list", async () => {
|
|
97
|
+
const resp = await client.getNews(
|
|
98
|
+
Region.Tr,
|
|
99
|
+
Locale.Tr,
|
|
100
|
+
NewsType.BRIEFS,
|
|
101
|
+
1,
|
|
102
|
+
10,
|
|
103
|
+
NewsOrderBy.TIMESTAMP,
|
|
104
|
+
SortDirection.Desc,
|
|
105
|
+
null
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(resp).toBeDefined();
|
|
109
|
+
expect(typeof resp.recordCount).toBe("number");
|
|
110
|
+
expect(Array.isArray(resp.items)).toBe(true);
|
|
111
|
+
|
|
112
|
+
if (resp.items.length > 0) {
|
|
113
|
+
const firstNews = resp.items[0];
|
|
114
|
+
expect(typeof firstNews.url).toBe("string");
|
|
115
|
+
expect(typeof firstNews.timestamp).toBe("string");
|
|
116
|
+
expect(typeof firstNews.publisher).toBe("object");
|
|
117
|
+
expect(Array.isArray(firstNews.relatedTickers)).toBe(true);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("Mock Tests", () => {
|
|
124
|
+
beforeEach(() => {
|
|
125
|
+
jest.clearAllMocks();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("getHighlights", () => {
|
|
129
|
+
test("should return news highlights with mock data", async () => {
|
|
130
|
+
jest.spyOn(client, 'getHighlights').mockResolvedValue(mockNewsHighlightsResponse);
|
|
131
|
+
|
|
132
|
+
const resp = await client.getHighlights(Region.Tr, Locale.Tr);
|
|
133
|
+
|
|
134
|
+
expect(resp).toBeDefined();
|
|
135
|
+
expect(resp.consumer).toHaveLength(2);
|
|
136
|
+
expect(resp.energyAndUtilities).toHaveLength(1);
|
|
137
|
+
expect(resp.finance).toHaveLength(2);
|
|
138
|
+
expect(resp.tech).toHaveLength(1);
|
|
139
|
+
|
|
140
|
+
expect(client.getHighlights).toHaveBeenCalledWith(Region.Tr, Locale.Tr);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("should handle API errors for highlights", async () => {
|
|
144
|
+
jest.spyOn(client, 'getHighlights').mockRejectedValue(new Error("Failed to fetch highlights"));
|
|
145
|
+
|
|
146
|
+
await expect(client.getHighlights(Region.Tr, Locale.Tr))
|
|
147
|
+
.rejects.toThrow("Failed to fetch highlights");
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("getNews", () => {
|
|
152
|
+
test("should return paginated news with mock data", async () => {
|
|
153
|
+
jest.spyOn(client, 'getNews').mockResolvedValue(mockPaginatedNewsResponse);
|
|
154
|
+
|
|
155
|
+
const resp = await client.getNews(
|
|
156
|
+
Region.Tr,
|
|
157
|
+
Locale.Tr,
|
|
158
|
+
NewsType.BRIEFS,
|
|
159
|
+
1,
|
|
160
|
+
10,
|
|
161
|
+
NewsOrderBy.TIMESTAMP,
|
|
162
|
+
SortDirection.Desc,
|
|
163
|
+
null
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
expect(resp.items).toHaveLength(2);
|
|
167
|
+
expect(resp.recordCount).toBe(2);
|
|
168
|
+
|
|
169
|
+
const firstNews = resp.items[0];
|
|
170
|
+
expect(firstNews.url).toBe("https://example.com/news1");
|
|
171
|
+
expect(firstNews.publisher.name).toBe("Example Publisher");
|
|
172
|
+
expect(firstNews.relatedTickers).toHaveLength(1);
|
|
173
|
+
expect(firstNews.qualityScore).toBe(85);
|
|
174
|
+
|
|
175
|
+
expect(client.getNews).toHaveBeenCalledWith(
|
|
176
|
+
Region.Tr,
|
|
177
|
+
Locale.Tr,
|
|
178
|
+
NewsType.BRIEFS,
|
|
179
|
+
1,
|
|
180
|
+
10,
|
|
181
|
+
NewsOrderBy.TIMESTAMP,
|
|
182
|
+
SortDirection.Desc,
|
|
183
|
+
null
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("should handle API errors for news", async () => {
|
|
188
|
+
jest.spyOn(client, 'getNews').mockRejectedValue(new Error("Failed to fetch news"));
|
|
189
|
+
|
|
190
|
+
await expect(client.getNews(
|
|
191
|
+
Region.Tr,
|
|
192
|
+
Locale.Tr,
|
|
193
|
+
NewsType.BRIEFS,
|
|
194
|
+
1,
|
|
195
|
+
10,
|
|
196
|
+
NewsOrderBy.TIMESTAMP,
|
|
197
|
+
SortDirection.Desc,
|
|
198
|
+
null
|
|
199
|
+
)).rejects.toThrow("Failed to fetch news");
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
package/src/test/stocks.test.ts
CHANGED
|
@@ -125,14 +125,14 @@ const mockTickRulesResponse = {
|
|
|
125
125
|
|
|
126
126
|
const mockEarningsTranscriptList: EarningsTranscriptListItem[] = [
|
|
127
127
|
{
|
|
128
|
-
symbol: "
|
|
128
|
+
symbol: "AAPL",
|
|
129
129
|
year: 2024,
|
|
130
130
|
quarter: 1,
|
|
131
131
|
date: "2024-05-15",
|
|
132
|
-
fiscal_year: 2024
|
|
132
|
+
fiscal_year: 2024,
|
|
133
133
|
},
|
|
134
134
|
{
|
|
135
|
-
symbol: "
|
|
135
|
+
symbol: "AAPL",
|
|
136
136
|
year: 2023,
|
|
137
137
|
quarter: 4,
|
|
138
138
|
date: "2024-02-20",
|
|
@@ -141,7 +141,7 @@ const mockEarningsTranscriptList: EarningsTranscriptListItem[] = [
|
|
|
141
141
|
];
|
|
142
142
|
|
|
143
143
|
const mockEarningsTranscriptDetail: EarningsTranscriptWithSummary = {
|
|
144
|
-
symbol: "
|
|
144
|
+
symbol: "AAPL",
|
|
145
145
|
year: 2024,
|
|
146
146
|
quarter: 1,
|
|
147
147
|
date: "2024-05-15",
|
|
@@ -153,14 +153,14 @@ const mockEarningsTranscriptDetail: EarningsTranscriptWithSummary = {
|
|
|
153
153
|
const mockMarketStates: MarketState[] = [
|
|
154
154
|
{
|
|
155
155
|
id: 1,
|
|
156
|
-
marketSymbol: "
|
|
156
|
+
marketSymbol: "BIST",
|
|
157
157
|
state: "OPEN",
|
|
158
158
|
lastTimestamp: "2024-03-14T10:00:00Z",
|
|
159
159
|
stockSymbol: "TUPRS"
|
|
160
160
|
},
|
|
161
161
|
{
|
|
162
162
|
id: 2,
|
|
163
|
-
marketSymbol: "
|
|
163
|
+
marketSymbol: "BIST",
|
|
164
164
|
state: "CLOSED",
|
|
165
165
|
lastTimestamp: "2024-03-14T18:00:00Z",
|
|
166
166
|
stockSymbol: "GARAN"
|
|
@@ -174,7 +174,7 @@ const mockPaginatedMarketStates: PaginatedResponse<MarketState> = {
|
|
|
174
174
|
|
|
175
175
|
const mockSingleMarketState: MarketState = {
|
|
176
176
|
id: 1,
|
|
177
|
-
marketSymbol: "
|
|
177
|
+
marketSymbol: "BIST",
|
|
178
178
|
state: "OPEN",
|
|
179
179
|
lastTimestamp: "2024-03-14T10:00:00Z",
|
|
180
180
|
stockSymbol: "TUPRS"
|
|
@@ -389,7 +389,7 @@ describe("Stocks Client", () => {
|
|
|
389
389
|
|
|
390
390
|
describe("getEarningsTranscripts", () => {
|
|
391
391
|
test("should return earnings transcript list", async () => {
|
|
392
|
-
const resp = await client.getEarningsTranscripts("
|
|
392
|
+
const resp = await client.getEarningsTranscripts("AAPL", Region.Us);
|
|
393
393
|
|
|
394
394
|
expect(Array.isArray(resp)).toBe(true);
|
|
395
395
|
|
|
@@ -406,7 +406,7 @@ describe("Stocks Client", () => {
|
|
|
406
406
|
|
|
407
407
|
describe("getEarningsTranscript", () => {
|
|
408
408
|
test("should return earnings transcript detail", async () => {
|
|
409
|
-
const resp = await client.getEarningsTranscript("
|
|
409
|
+
const resp = await client.getEarningsTranscript("AAPL", 2023, 4);
|
|
410
410
|
|
|
411
411
|
expect(resp).toBeDefined();
|
|
412
412
|
expect(typeof resp.symbol).toBe("string");
|
|
@@ -476,7 +476,7 @@ describe("Stocks Client", () => {
|
|
|
476
476
|
|
|
477
477
|
describe("getState", () => {
|
|
478
478
|
test("should return single market state", async () => {
|
|
479
|
-
const resp = await client.getState("
|
|
479
|
+
const resp = await client.getState("BIST");
|
|
480
480
|
|
|
481
481
|
expect(resp).toBeDefined();
|
|
482
482
|
expect(typeof resp.id).toBe("number");
|
|
@@ -495,11 +495,10 @@ describe("Stocks Client", () => {
|
|
|
495
495
|
symbol: "TUPRS",
|
|
496
496
|
region: Region.Tr,
|
|
497
497
|
});
|
|
498
|
-
|
|
499
498
|
expect(resp).toBeDefined();
|
|
500
499
|
expect(resp).toBeInstanceOf(Blob);
|
|
501
500
|
expect(resp.size).toBeGreaterThan(0);
|
|
502
|
-
});
|
|
501
|
+
}, 10000);
|
|
503
502
|
});
|
|
504
503
|
});
|
|
505
504
|
|
|
@@ -693,12 +692,12 @@ describe("Stocks Client", () => {
|
|
|
693
692
|
test("should return earnings transcript list with mock data", async () => {
|
|
694
693
|
jest.spyOn(client, 'getEarningsTranscripts').mockResolvedValue(mockEarningsTranscriptList);
|
|
695
694
|
|
|
696
|
-
const resp = await client.getEarningsTranscripts("
|
|
695
|
+
const resp = await client.getEarningsTranscripts("AAPL", Region.Us);
|
|
697
696
|
|
|
698
697
|
expect(resp).toHaveLength(2);
|
|
699
698
|
|
|
700
699
|
const firstTranscript = resp[0];
|
|
701
|
-
expect(firstTranscript.symbol).toBe("
|
|
700
|
+
expect(firstTranscript.symbol).toBe("AAPL");
|
|
702
701
|
expect(firstTranscript.year).toBe(2024);
|
|
703
702
|
expect(firstTranscript.quarter).toBe(1);
|
|
704
703
|
expect(firstTranscript.date).toBe("2024-05-15");
|
|
@@ -708,14 +707,18 @@ describe("Stocks Client", () => {
|
|
|
708
707
|
expect(secondTranscript.year).toBe(2023);
|
|
709
708
|
expect(secondTranscript.quarter).toBe(4);
|
|
710
709
|
|
|
711
|
-
expect(client.getEarningsTranscripts).toHaveBeenCalledWith(
|
|
710
|
+
expect(client.getEarningsTranscripts).toHaveBeenCalledWith(
|
|
711
|
+
"AAPL",
|
|
712
|
+
Region.Us
|
|
713
|
+
);
|
|
712
714
|
});
|
|
713
715
|
|
|
714
716
|
test("should handle API errors for earnings transcripts", async () => {
|
|
715
717
|
jest.spyOn(client, 'getEarningsTranscripts').mockRejectedValue(new Error("Transcripts not found"));
|
|
716
718
|
|
|
717
|
-
await expect(
|
|
718
|
-
.
|
|
719
|
+
await expect(
|
|
720
|
+
client.getEarningsTranscripts("INVALID", Region.Us)
|
|
721
|
+
).rejects.toThrow("Transcripts not found");
|
|
719
722
|
});
|
|
720
723
|
});
|
|
721
724
|
|
|
@@ -723,9 +726,9 @@ describe("Stocks Client", () => {
|
|
|
723
726
|
test("should return earnings transcript detail with mock data", async () => {
|
|
724
727
|
jest.spyOn(client, 'getEarningsTranscript').mockResolvedValue(mockEarningsTranscriptDetail);
|
|
725
728
|
|
|
726
|
-
const resp = await client.getEarningsTranscript("
|
|
729
|
+
const resp = await client.getEarningsTranscript("AAPL", 2024, 1);
|
|
727
730
|
|
|
728
|
-
expect(resp.symbol).toBe("
|
|
731
|
+
expect(resp.symbol).toBe("AAPL");
|
|
729
732
|
expect(resp.year).toBe(2024);
|
|
730
733
|
expect(resp.quarter).toBe(1);
|
|
731
734
|
expect(resp.date).toBe("2024-05-15");
|
|
@@ -733,14 +736,19 @@ describe("Stocks Client", () => {
|
|
|
733
736
|
expect(resp.summary).toBe("Strong Q1 performance with 15% revenue growth");
|
|
734
737
|
expect(resp.has_summary).toBe(true);
|
|
735
738
|
|
|
736
|
-
expect(client.getEarningsTranscript).toHaveBeenCalledWith(
|
|
739
|
+
expect(client.getEarningsTranscript).toHaveBeenCalledWith(
|
|
740
|
+
"AAPL",
|
|
741
|
+
2024,
|
|
742
|
+
1
|
|
743
|
+
);
|
|
737
744
|
});
|
|
738
745
|
|
|
739
746
|
test("should handle API errors for earnings transcript detail", async () => {
|
|
740
747
|
jest.spyOn(client, 'getEarningsTranscript').mockRejectedValue(new Error("Transcript not found"));
|
|
741
748
|
|
|
742
|
-
await expect(
|
|
743
|
-
.
|
|
749
|
+
await expect(
|
|
750
|
+
client.getEarningsTranscript("AAPL", 2020, 1)
|
|
751
|
+
).rejects.toThrow("Transcript not found");
|
|
744
752
|
});
|
|
745
753
|
});
|
|
746
754
|
|
|
@@ -755,7 +763,7 @@ describe("Stocks Client", () => {
|
|
|
755
763
|
|
|
756
764
|
const firstState = resp.items[0];
|
|
757
765
|
expect(firstState.id).toBe(1);
|
|
758
|
-
expect(firstState.marketSymbol).toBe("
|
|
766
|
+
expect(firstState.marketSymbol).toBe("BIST");
|
|
759
767
|
expect(firstState.state).toBe("OPEN");
|
|
760
768
|
expect(firstState.stockSymbol).toBe("TUPRS");
|
|
761
769
|
|
|
@@ -777,7 +785,7 @@ describe("Stocks Client", () => {
|
|
|
777
785
|
const resp = await client.getStockState("TUPRS");
|
|
778
786
|
|
|
779
787
|
expect(resp.id).toBe(1);
|
|
780
|
-
expect(resp.marketSymbol).toBe("
|
|
788
|
+
expect(resp.marketSymbol).toBe("BIST");
|
|
781
789
|
expect(resp.state).toBe("OPEN");
|
|
782
790
|
expect(resp.lastTimestamp).toBe("2024-03-14T10:00:00Z");
|
|
783
791
|
expect(resp.stockSymbol).toBe("TUPRS");
|
|
@@ -821,14 +829,14 @@ describe("Stocks Client", () => {
|
|
|
821
829
|
test("should return single market state with mock data", async () => {
|
|
822
830
|
jest.spyOn(client, 'getState').mockResolvedValue(mockSingleMarketState);
|
|
823
831
|
|
|
824
|
-
const resp = await client.getState("
|
|
832
|
+
const resp = await client.getState("BIST");
|
|
825
833
|
|
|
826
834
|
expect(resp.id).toBe(1);
|
|
827
|
-
expect(resp.marketSymbol).toBe("
|
|
835
|
+
expect(resp.marketSymbol).toBe("BIST");
|
|
828
836
|
expect(resp.state).toBe("OPEN");
|
|
829
837
|
expect(resp.lastTimestamp).toBe("2024-03-14T10:00:00Z");
|
|
830
838
|
|
|
831
|
-
expect(client.getState).toHaveBeenCalledWith("
|
|
839
|
+
expect(client.getState).toHaveBeenCalledWith("BIST");
|
|
832
840
|
});
|
|
833
841
|
|
|
834
842
|
test("should handle API errors for single state", async () => {
|