laplace-api 5.2.0 → 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 +1 -1
- package/src/client/news.ts +34 -3
- package/src/client/screener.ts +81 -0
- package/src/test/news.test.ts +113 -5
- package/src/test/screener.test.ts +143 -0
package/package.json
CHANGED
package/src/client/news.ts
CHANGED
|
@@ -40,6 +40,8 @@ export interface News {
|
|
|
40
40
|
industries?: NewsIndustry;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export type NewsV2 = Omit<News, "relatedTickers">;
|
|
44
|
+
|
|
43
45
|
export interface NewsPublisher {
|
|
44
46
|
name: string;
|
|
45
47
|
logoUrl: string | null;
|
|
@@ -122,18 +124,47 @@ export class NewsClient extends Client {
|
|
|
122
124
|
});
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
async getNewsV2(
|
|
128
|
+
region: Region,
|
|
129
|
+
locale: Locale,
|
|
130
|
+
newsType?: NewsType,
|
|
131
|
+
page?: number,
|
|
132
|
+
size?: number,
|
|
133
|
+
orderBy?: NewsOrderBy,
|
|
134
|
+
orderByDirection?: SortDirection,
|
|
135
|
+
extraFilters?: string
|
|
136
|
+
): Promise<PaginatedResponse<NewsV2>> {
|
|
137
|
+
const params = {
|
|
138
|
+
region,
|
|
139
|
+
locale,
|
|
140
|
+
...(newsType != null && { newsType }),
|
|
141
|
+
...(page != null && { page }),
|
|
142
|
+
...(size != null && { size }),
|
|
143
|
+
...(orderBy != null && { orderBy }),
|
|
144
|
+
...(orderByDirection != null && { orderByDirection }),
|
|
145
|
+
...(extraFilters != null && { extraFilters }),
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return this.sendRequest<PaginatedResponse<NewsV2>>({
|
|
149
|
+
method: "GET",
|
|
150
|
+
url: "/api/v2/news",
|
|
151
|
+
params,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
125
155
|
streamNews(
|
|
156
|
+
region: Region,
|
|
126
157
|
locale: Locale,
|
|
127
158
|
sectors?: string[],
|
|
128
159
|
tickers?: string[],
|
|
129
160
|
categories?: string[],
|
|
130
161
|
industries?: string[]
|
|
131
|
-
): { events: AsyncIterable<
|
|
132
|
-
let url = `${this["baseUrl"]}/api/v1/news/stream?locale=${locale}`;
|
|
162
|
+
): { events: AsyncIterable<NewsV2[]>, cancel: () => void } {
|
|
163
|
+
let url = `${this["baseUrl"]}/api/v1/news/stream?locale=${locale}®ion=${region}`;
|
|
133
164
|
if (sectors?.length) url += `§ors=${encodeURIComponent(sectors.join(","))}`;
|
|
134
165
|
if (tickers?.length) url += `&tickers=${encodeURIComponent(tickers.join(","))}`;
|
|
135
166
|
if (categories?.length) url += `&categories=${encodeURIComponent(categories.join(","))}`;
|
|
136
167
|
if (industries?.length) url += `&industries=${encodeURIComponent(industries.join(","))}`;
|
|
137
|
-
return this.sendSSERequest<
|
|
168
|
+
return this.sendSSERequest<NewsV2[]>(url);
|
|
138
169
|
}
|
|
139
170
|
}
|
|
@@ -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
|
+
}
|
package/src/test/news.test.ts
CHANGED
|
@@ -67,6 +67,11 @@ const mockNewsResponse = {
|
|
|
67
67
|
recordCount: 352
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
+
const mockNewsV2Response = {
|
|
71
|
+
items: mockNewsResponse.items.map(({ relatedTickers, ...rest }) => rest),
|
|
72
|
+
recordCount: mockNewsResponse.recordCount
|
|
73
|
+
};
|
|
74
|
+
|
|
70
75
|
describe("NewsClient", () => {
|
|
71
76
|
let client: NewsClient;
|
|
72
77
|
|
|
@@ -191,9 +196,42 @@ describe("NewsClient", () => {
|
|
|
191
196
|
}
|
|
192
197
|
});
|
|
193
198
|
|
|
199
|
+
test("getNewsV2 returns valid paginated data", async () => {
|
|
200
|
+
const resp = await client.getNewsV2(
|
|
201
|
+
Region.Us,
|
|
202
|
+
Locale.Tr,
|
|
203
|
+
NewsType.BRIEFS,
|
|
204
|
+
0,
|
|
205
|
+
10,
|
|
206
|
+
NewsOrderBy.TIMESTAMP,
|
|
207
|
+
SortDirection.Desc
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
expect(resp).toBeDefined();
|
|
211
|
+
expect(typeof resp.recordCount).toBe("number");
|
|
212
|
+
expect(resp.recordCount).toBeGreaterThanOrEqual(0);
|
|
213
|
+
expect(Array.isArray(resp.items)).toBe(true);
|
|
214
|
+
|
|
215
|
+
if (resp.items.length > 0) {
|
|
216
|
+
const n = resp.items[0];
|
|
217
|
+
|
|
218
|
+
expect(typeof n.url).toBe("string");
|
|
219
|
+
expect(typeof n.imageUrl).toBe("string");
|
|
220
|
+
expect(typeof n.timestamp).toBe("string");
|
|
221
|
+
expect(typeof n.publisherUrl).toBe("string");
|
|
222
|
+
expect(typeof n.qualityScore).toBe("number");
|
|
223
|
+
expect(typeof n.createdAt).toBe("string");
|
|
224
|
+
|
|
225
|
+
expect((n as any).relatedTickers).toBeUndefined();
|
|
226
|
+
|
|
227
|
+
expect(n.publisher).toBeDefined();
|
|
228
|
+
expect(typeof n.publisher.name).toBe("string");
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
194
232
|
test("streamNews yields item before timeout or throws gracefully if none arrive", async () => {
|
|
195
233
|
let newsItemsReceived = 0;
|
|
196
|
-
const { events, cancel } = client.streamNews(Locale.Tr);
|
|
234
|
+
const { events, cancel } = client.streamNews(Region.Us, Locale.Tr);
|
|
197
235
|
|
|
198
236
|
const receivePromise = (async () => {
|
|
199
237
|
for await (const items of events) {
|
|
@@ -364,6 +402,76 @@ describe("NewsClient", () => {
|
|
|
364
402
|
});
|
|
365
403
|
});
|
|
366
404
|
|
|
405
|
+
describe("getNewsV2", () => {
|
|
406
|
+
test("calls correct endpoint/params and matches raw response", async () => {
|
|
407
|
+
cli.request.mockResolvedValueOnce({ data: mockNewsV2Response });
|
|
408
|
+
|
|
409
|
+
const resp = await client.getNewsV2(
|
|
410
|
+
Region.Tr,
|
|
411
|
+
Locale.Tr,
|
|
412
|
+
NewsType.BRIEFS,
|
|
413
|
+
1,
|
|
414
|
+
10,
|
|
415
|
+
NewsOrderBy.TIMESTAMP,
|
|
416
|
+
SortDirection.Desc,
|
|
417
|
+
undefined
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
expect(cli.request).toHaveBeenCalledTimes(1);
|
|
421
|
+
const call = cli.request.mock.calls[0][0];
|
|
422
|
+
|
|
423
|
+
expect(call.method).toBe("GET");
|
|
424
|
+
expect(call.url).toBe("/api/v2/news");
|
|
425
|
+
expect(call.params).toEqual({
|
|
426
|
+
region: Region.Tr,
|
|
427
|
+
locale: Locale.Tr,
|
|
428
|
+
newsType: NewsType.BRIEFS,
|
|
429
|
+
page: 1,
|
|
430
|
+
size: 10,
|
|
431
|
+
orderBy: NewsOrderBy.TIMESTAMP,
|
|
432
|
+
orderByDirection: SortDirection.Desc
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
expect(resp.recordCount).toBe(352);
|
|
436
|
+
expect(resp.items).toHaveLength(1);
|
|
437
|
+
|
|
438
|
+
const n = resp.items[0];
|
|
439
|
+
|
|
440
|
+
expect((n as any).relatedTickers).toBeUndefined();
|
|
441
|
+
expect(n.url).toBe(mockNewsV2Response.items[0].url);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
test("does not send optional params when undefined", async () => {
|
|
445
|
+
cli.request.mockResolvedValueOnce({ data: mockNewsV2Response });
|
|
446
|
+
|
|
447
|
+
await client.getNewsV2(Region.Tr, Locale.Tr);
|
|
448
|
+
|
|
449
|
+
const call = cli.request.mock.calls[0][0];
|
|
450
|
+
expect(call.params).toEqual({
|
|
451
|
+
region: Region.Tr,
|
|
452
|
+
locale: Locale.Tr
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
test("bubbles up request error", async () => {
|
|
457
|
+
cli.request.mockRejectedValueOnce(new Error("Failed to fetch news v2"));
|
|
458
|
+
|
|
459
|
+
await expect(
|
|
460
|
+
client.getNewsV2(
|
|
461
|
+
Region.Tr,
|
|
462
|
+
Locale.Tr,
|
|
463
|
+
NewsType.REUTERS,
|
|
464
|
+
0,
|
|
465
|
+
10,
|
|
466
|
+
NewsOrderBy.TIMESTAMP,
|
|
467
|
+
SortDirection.Desc
|
|
468
|
+
)
|
|
469
|
+
).rejects.toThrow("Failed to fetch news v2");
|
|
470
|
+
|
|
471
|
+
expect(cli.request).toHaveBeenCalledTimes(1);
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
367
475
|
describe("streamNews", () => {
|
|
368
476
|
test("calls correct endpoint/params and correctly yields stream entities", async () => {
|
|
369
477
|
const eventsList: any[] = [];
|
|
@@ -386,7 +494,7 @@ describe("NewsClient", () => {
|
|
|
386
494
|
data: mockAsyncIterator
|
|
387
495
|
});
|
|
388
496
|
|
|
389
|
-
const { events, cancel } = client.streamNews(Locale.Tr);
|
|
497
|
+
const { events, cancel } = client.streamNews(Region.Us, Locale.Tr);
|
|
390
498
|
|
|
391
499
|
for await (const newsList of events) {
|
|
392
500
|
eventsList.push(newsList);
|
|
@@ -394,7 +502,7 @@ describe("NewsClient", () => {
|
|
|
394
502
|
|
|
395
503
|
expect(axiosGetSpy).toHaveBeenCalledTimes(1);
|
|
396
504
|
const callArgs = axiosGetSpy.mock.calls[0];
|
|
397
|
-
expect(callArgs[0]).toBe(`${client["baseUrl"]}/api/v1/news/stream?locale=tr`);
|
|
505
|
+
expect(callArgs[0]).toBe(`${client["baseUrl"]}/api/v1/news/stream?locale=tr®ion=us`);
|
|
398
506
|
expect(callArgs[1]?.responseType).toBe('stream');
|
|
399
507
|
|
|
400
508
|
expect(eventsList).toHaveLength(2);
|
|
@@ -416,7 +524,7 @@ describe("NewsClient", () => {
|
|
|
416
524
|
data: mockAsyncIterator
|
|
417
525
|
});
|
|
418
526
|
|
|
419
|
-
const { events, cancel } = client.streamNews(Locale.En, ["tech"], ["AAPL"], ["category"], ["software"]);
|
|
527
|
+
const { events, cancel } = client.streamNews(Region.Us, Locale.En, ["tech"], ["AAPL"], ["category"], ["software"]);
|
|
420
528
|
|
|
421
529
|
for await (const _ of events) {
|
|
422
530
|
break;
|
|
@@ -424,7 +532,7 @@ describe("NewsClient", () => {
|
|
|
424
532
|
|
|
425
533
|
expect(axiosGetSpy).toHaveBeenCalledTimes(1);
|
|
426
534
|
const callArgs = axiosGetSpy.mock.calls[0];
|
|
427
|
-
expect(callArgs[0]).toBe(`${client["baseUrl"]}/api/v1/news/stream?locale=en§ors=tech&tickers=AAPL&categories=category&industries=software`);
|
|
535
|
+
expect(callArgs[0]).toBe(`${client["baseUrl"]}/api/v1/news/stream?locale=en®ion=us§ors=tech&tickers=AAPL&categories=category&industries=software`);
|
|
428
536
|
|
|
429
537
|
cancel();
|
|
430
538
|
axiosGetSpy.mockRestore();
|
|
@@ -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
|
+
});
|