laplace-api 1.0.2 → 1.0.3
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/client.ts +6 -2
- package/src/client/custom_theme.ts +1 -1
- package/src/client/errors.ts +75 -0
- package/src/client/financial_fundamentals.ts +2 -2
- package/src/test/client.test.ts +35 -20
- package/src/test/custom_theme.test.ts +2 -2
- package/src/test/financial_fundamentals.test.ts +16 -2
- package/src/test/live_price +57 -0
- package/src/test/live_price.test.ts +0 -57
package/package.json
CHANGED
package/src/client/client.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
|
|
2
2
|
import { Logger } from 'winston';
|
|
3
3
|
import { LaplaceConfiguration } from '../utilities/configuration';
|
|
4
|
-
import {
|
|
4
|
+
import { LaplaceHTTPError, WrapError } from './errors';
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
export class Client {
|
|
@@ -31,7 +31,11 @@ export class Client {
|
|
|
31
31
|
return response.data;
|
|
32
32
|
} catch (error) {
|
|
33
33
|
if (axios.isAxiosError(error) && error.response) {
|
|
34
|
-
|
|
34
|
+
const httpError = new LaplaceHTTPError(
|
|
35
|
+
error.response.status,
|
|
36
|
+
JSON.stringify(error.response.data)
|
|
37
|
+
);
|
|
38
|
+
throw WrapError(httpError);
|
|
35
39
|
}
|
|
36
40
|
throw error;
|
|
37
41
|
}
|
|
@@ -62,7 +62,7 @@ export class CustomThemeClient extends Client {
|
|
|
62
62
|
return this.getAllCollectionsPrivate(CollectionType.CustomTheme, locale);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
async getCustomThemeDetail(id: string, locale: Locale, sortBy: SortBy): Promise<CollectionDetail> {
|
|
65
|
+
async getCustomThemeDetail(id: string, locale: Locale, sortBy: SortBy | null): Promise<CollectionDetail> {
|
|
66
66
|
return this.getCollectionDetailPrivate(id, CollectionType.CustomTheme, locale, sortBy);
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export class LaplaceError extends Error {
|
|
2
|
+
constructor(message: string) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'LaplaceError';
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const ErrYouDoNotHaveAccessToEndpoint = new LaplaceError('you do not have access to this endpoint');
|
|
9
|
+
export const ErrLimitExceeded = new LaplaceError('limit exceeded');
|
|
10
|
+
export const ErrEndpointIsNotActive = new LaplaceError('endpoint is not active');
|
|
11
|
+
export const ErrInvalidToken = new LaplaceError('invalid token');
|
|
12
|
+
export const ErrInvalidID = new LaplaceError('invalid object id');
|
|
13
|
+
|
|
14
|
+
export class LaplaceHTTPError extends Error {
|
|
15
|
+
httpStatus: number;
|
|
16
|
+
message: string;
|
|
17
|
+
internalError?: Error;
|
|
18
|
+
|
|
19
|
+
constructor(httpStatus: number, message: string, internalError?: Error) {
|
|
20
|
+
super(`${httpStatus}: ${message}${internalError ? ` (${internalError.message})` : ''}`);
|
|
21
|
+
this.name = 'LaplaceHTTPError';
|
|
22
|
+
this.httpStatus = httpStatus;
|
|
23
|
+
this.message = message;
|
|
24
|
+
this.internalError = internalError;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
withInternalError(err: Error): LaplaceHTTPError {
|
|
28
|
+
this.internalError = err;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
cause(): Error {
|
|
33
|
+
return this.internalError || this;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function WrapError(err: Error): Error {
|
|
38
|
+
if (err instanceof LaplaceHTTPError) {
|
|
39
|
+
getLaplaceError(err);
|
|
40
|
+
return err;
|
|
41
|
+
}
|
|
42
|
+
return err;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getLaplaceError(httpErr: LaplaceHTTPError): void {
|
|
46
|
+
switch (httpErr.httpStatus) {
|
|
47
|
+
case 403:
|
|
48
|
+
switch (httpErr.message) {
|
|
49
|
+
case '{"message":"you don\'t have access to this endpoint"}\n':
|
|
50
|
+
httpErr.withInternalError(ErrYouDoNotHaveAccessToEndpoint);
|
|
51
|
+
break;
|
|
52
|
+
case '{"message":"endpoint is not active"}\n':
|
|
53
|
+
httpErr.withInternalError(ErrEndpointIsNotActive);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
if (httpErr.message.includes('limit exceeded')) {
|
|
57
|
+
httpErr.withInternalError(ErrLimitExceeded);
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
case 400:
|
|
61
|
+
if (httpErr.message === '{"message":"invalid id"}\n') {
|
|
62
|
+
httpErr.withInternalError(ErrInvalidID);
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
case 401:
|
|
66
|
+
if (httpErr.message === '{"message":"this token is not valid"}\n') {
|
|
67
|
+
httpErr.withInternalError(ErrInvalidToken);
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function HttpError(httpStatus: number, message: string, ...args: any[]): LaplaceHTTPError {
|
|
74
|
+
return new LaplaceHTTPError(httpStatus, message.replace(/%s/g, () => args.shift()));
|
|
75
|
+
}
|
package/src/test/client.test.ts
CHANGED
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
import { Logger } from 'winston';
|
|
2
2
|
import { LaplaceConfiguration } from '../utilities/configuration';
|
|
3
|
-
import {
|
|
3
|
+
import { createClient } from '../client/client';
|
|
4
4
|
import './client_test_suite';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
import { Region, Locale } from '../client/collections';
|
|
6
|
+
import { CollectionClient } from '../client/collections';
|
|
7
|
+
import { LaplaceHTTPError } from '../client/errors';
|
|
8
|
+
import { suite } from 'node:test';
|
|
9
9
|
|
|
10
10
|
class LaplaceClientTestSuite {
|
|
11
|
-
|
|
11
|
+
public config: LaplaceConfiguration;
|
|
12
12
|
|
|
13
13
|
constructor(config: LaplaceConfiguration) {
|
|
14
14
|
this.config = config;
|
|
15
15
|
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('LaplaceClient', () => {
|
|
19
|
+
let testSuite: LaplaceClientTestSuite;
|
|
20
|
+
|
|
21
|
+
beforeAll(() => {
|
|
22
|
+
// Assuming global.testSuite is set up as in the previous example
|
|
23
|
+
const config = (global as any).testSuite.config;
|
|
24
|
+
testSuite = new LaplaceClientTestSuite(config);
|
|
25
|
+
});
|
|
16
26
|
|
|
17
|
-
async
|
|
27
|
+
it('should make a successful request', async () => {
|
|
28
|
+
|
|
18
29
|
const logger: Logger = {
|
|
19
30
|
info: jest.fn(),
|
|
20
31
|
error: jest.fn(),
|
|
@@ -22,11 +33,11 @@ class LaplaceClientTestSuite {
|
|
|
22
33
|
debug: jest.fn(),
|
|
23
34
|
} as unknown as Logger;
|
|
24
35
|
|
|
25
|
-
const client = createClient(
|
|
36
|
+
const client = createClient(testSuite.config, logger);
|
|
26
37
|
|
|
27
38
|
const res = await client.sendRequest({
|
|
28
39
|
method: 'GET',
|
|
29
|
-
url: '/api/v1/
|
|
40
|
+
url: '/api/v1/sector',
|
|
30
41
|
params: {
|
|
31
42
|
region: Region.Tr,
|
|
32
43
|
locale: Locale.Tr,
|
|
@@ -35,19 +46,23 @@ class LaplaceClientTestSuite {
|
|
|
35
46
|
|
|
36
47
|
expect(res).toBeTruthy();
|
|
37
48
|
expect(res).not.toEqual({});
|
|
38
|
-
}
|
|
39
|
-
|
|
49
|
+
});
|
|
50
|
+
it('should fail if invalid token', async () => {
|
|
51
|
+
const logger: Logger = {
|
|
52
|
+
info: jest.fn(),
|
|
53
|
+
error: jest.fn(),
|
|
54
|
+
warn: jest.fn(),
|
|
55
|
+
debug: jest.fn(),
|
|
56
|
+
} as unknown as Logger;
|
|
40
57
|
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
var invalidConfig : LaplaceConfiguration = testSuite.config;
|
|
59
|
+
invalidConfig.apiKey = 'invalid';
|
|
43
60
|
|
|
44
|
-
|
|
45
|
-
// Assuming global.testSuite is set up as in the previous example
|
|
46
|
-
const config = (global as any).testSuite.config;
|
|
47
|
-
testSuite = new LaplaceClientTestSuite(config);
|
|
48
|
-
});
|
|
61
|
+
const client = new CollectionClient(invalidConfig, logger);
|
|
49
62
|
|
|
50
|
-
|
|
51
|
-
|
|
63
|
+
const f = async () => {await client.getAllIndustries(Region.Tr, Locale.Tr)};
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
await expect(f).rejects.toThrow(LaplaceHTTPError);
|
|
52
67
|
});
|
|
53
68
|
});
|
|
@@ -80,7 +80,7 @@ describe('CustomTheme', () => {
|
|
|
80
80
|
|
|
81
81
|
await testDeleteCustomTheme(id, client);
|
|
82
82
|
await expect(client.getCustomThemeDetail(id, Locale.Tr, SortBy.PriceChange)).rejects.toThrow();
|
|
83
|
-
},
|
|
83
|
+
}, 15000);
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
async function testCreateCustomTheme(client: CustomThemeClient, createParams: CreateCustomThemeParams): Promise<string> {
|
|
@@ -90,7 +90,7 @@ async function testCreateCustomTheme(client: CustomThemeClient, createParams: Cr
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
async function testGetDetails(id: string, locale: Locale, client: CustomThemeClient, createParams: CreateCustomThemeParams) {
|
|
93
|
-
const resp = await client.getCustomThemeDetail(id, locale,
|
|
93
|
+
const resp = await client.getCustomThemeDetail(id, locale, null);
|
|
94
94
|
expect(resp).not.toBeEmpty();
|
|
95
95
|
|
|
96
96
|
expect(resp.title).toBe(createParams.title[locale]);
|
|
@@ -28,7 +28,7 @@ describe('FinancialFundamentals', () => {
|
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
test('GetStockStats', async () => {
|
|
31
|
-
|
|
31
|
+
var statKeys = [
|
|
32
32
|
StockStatsKey.PreviousClose,
|
|
33
33
|
StockStatsKey.YtdReturn,
|
|
34
34
|
StockStatsKey.YearlyReturn,
|
|
@@ -40,8 +40,22 @@ describe('FinancialFundamentals', () => {
|
|
|
40
40
|
StockStatsKey.ThreeYearReturn,
|
|
41
41
|
StockStatsKey.FiveYearReturn,
|
|
42
42
|
StockStatsKey.LatestPrice,
|
|
43
|
-
]
|
|
43
|
+
]
|
|
44
|
+
const resp = await stockClient.getStockStats(['TUPRS'], statKeys, Region.Tr);
|
|
44
45
|
expect(resp).not.toBeEmpty();
|
|
46
|
+
expect(resp.length).toBe(1);
|
|
47
|
+
expect(resp[0].symbol).toBe('TUPRS');
|
|
48
|
+
expect(resp[0].previousClose).toBeGreaterThan(0);
|
|
49
|
+
expect(resp[0].ytdReturn).not.toEqual(0);
|
|
50
|
+
expect(resp[0].yearlyReturn).not.toEqual(0);
|
|
51
|
+
expect(resp[0].marketCap).toBeGreaterThan(0);
|
|
52
|
+
expect(resp[0].peRatio).not.toEqual(0);
|
|
53
|
+
expect(resp[0].pbRatio).not.toEqual(0);
|
|
54
|
+
expect(resp[0].yearLow).toBeGreaterThan(0);
|
|
55
|
+
expect(resp[0].yearHigh).toBeGreaterThan(0);
|
|
56
|
+
// TODO fix these once the endpoint is fixed
|
|
57
|
+
// expect(resp[0]['3Year']).not.toEqual(0);
|
|
58
|
+
// expect(resp[0]['5Year']).not.toEqual(0);
|
|
45
59
|
});
|
|
46
60
|
|
|
47
61
|
test('GetTopMovers', async () => {
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
// });
|
|
@@ -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
|
-
});
|