Skip to content

Commit 3f7cffd

Browse files
authored
[price-service/client] Add e2 test (#559)
1 parent fc08ec2 commit 3f7cffd

File tree

7 files changed

+262
-2
lines changed

7 files changed

+262
-2
lines changed

Tiltfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,16 @@ k8s_resource(
338338
labels = ["solana"],
339339
trigger_mode = trigger_mode,
340340
)
341+
342+
# Pyth Price Client JS e2e test
343+
docker_build(
344+
ref = "pyth-price-client-js",
345+
context = ".",
346+
dockerfile = "price_service/client/js/Dockerfile",
347+
)
348+
k8s_yaml_with_ns("tilt_devnet/k8s/pyth-price-client-js.yaml")
349+
k8s_resource(
350+
"pyth-price-client-js",
351+
resource_deps = ["pyth-price-server"],
352+
labels = ["pyth"]
353+
)

price_service/client/js/Dockerfile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Defined in tilt_devnet/docker_images/Dockerfile.lerna
2+
FROM lerna
3+
4+
USER root
5+
RUN apt-get update && apt-get install -y ncat
6+
7+
WORKDIR /home/node/
8+
USER 1000
9+
10+
COPY --chown=1000:1000 price_service/client/js price_service/client/js
11+
COPY --chown=1000:1000 price_service/sdk/js price_service/sdk/js
12+
13+
RUN npx lerna run build --scope="@pythnetwork/price-service-client" --include-dependencies
14+
15+
WORKDIR /home/node/price_service/client/js
16+
17+
ENTRYPOINT ["npm"]

price_service/client/js/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
],
1414
"repository": "https://github.com/pyth-network/pyth-crosschain",
1515
"scripts": {
16-
"test": "jest --passWithNoTests",
16+
"test": "jest --testPathIgnorePatterns=.*.e2e.test.ts --passWithNoTests",
17+
"test:e2e": "jest --testPathPattern=.*.e2e.test.ts",
1718
"build": "tsc",
1819
"example": "npm run build && node lib/examples/PriceServiceClient.js",
1920
"format": "prettier --write \"src/**/*.ts\"",
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import {
2+
DurationInMs,
3+
Price,
4+
PriceFeed,
5+
PriceFeedMetadata,
6+
PriceServiceConnection,
7+
} from "../index";
8+
9+
async function sleep(duration: DurationInMs): Promise<void> {
10+
return new Promise((res) => setTimeout(res, duration));
11+
}
12+
13+
// The endpoint is set to the price service endpoint in Tilt.
14+
// Please note that if you change it to a mainnet/testnet endpoint
15+
// some tests might fail due to the huge response size of a request
16+
// , i.e. requesting latest price feeds or vaas of all price ids.
17+
const PRICE_SERVICE_ENDPOINT = "http://pyth-price-server:4200";
18+
19+
describe("Test http endpoints", () => {
20+
test("Get price feed (without verbose/binary) works", async () => {
21+
const connection = new PriceServiceConnection(PRICE_SERVICE_ENDPOINT);
22+
const ids = await connection.getPriceFeedIds();
23+
expect(ids.length).toBeGreaterThan(0);
24+
25+
const priceFeeds = await connection.getLatestPriceFeeds(ids);
26+
expect(priceFeeds).toBeDefined();
27+
expect(priceFeeds!.length).toEqual(ids.length);
28+
29+
for (const priceFeed of priceFeeds!) {
30+
expect(priceFeed.id.length).toBe(64); // 32 byte address has size 64 in hex
31+
expect(priceFeed).toBeInstanceOf(PriceFeed);
32+
expect(priceFeed.getPriceUnchecked()).toBeInstanceOf(Price);
33+
expect(priceFeed.getEmaPriceUnchecked()).toBeInstanceOf(Price);
34+
expect(priceFeed.getMetadata()).toBeUndefined();
35+
expect(priceFeed.getVAA()).toBeUndefined();
36+
}
37+
});
38+
39+
test("Get price feed with verbose flag works", async () => {
40+
const connection = new PriceServiceConnection(PRICE_SERVICE_ENDPOINT, {
41+
priceFeedRequestConfig: { verbose: true },
42+
});
43+
44+
const ids = await connection.getPriceFeedIds();
45+
expect(ids.length).toBeGreaterThan(0);
46+
47+
const priceFeeds = await connection.getLatestPriceFeeds(ids);
48+
expect(priceFeeds).toBeDefined();
49+
expect(priceFeeds!.length).toEqual(ids.length);
50+
51+
for (const priceFeed of priceFeeds!) {
52+
expect(priceFeed.getMetadata()).toBeInstanceOf(PriceFeedMetadata);
53+
expect(priceFeed.getVAA()).toBeUndefined();
54+
}
55+
});
56+
57+
test("Get price feed with binary flag works", async () => {
58+
const connection = new PriceServiceConnection(PRICE_SERVICE_ENDPOINT, {
59+
priceFeedRequestConfig: { binary: true },
60+
});
61+
62+
const ids = await connection.getPriceFeedIds();
63+
expect(ids.length).toBeGreaterThan(0);
64+
65+
const priceFeeds = await connection.getLatestPriceFeeds(ids);
66+
expect(priceFeeds).toBeDefined();
67+
expect(priceFeeds!.length).toEqual(ids.length);
68+
69+
for (const priceFeed of priceFeeds!) {
70+
expect(priceFeed.getMetadata()).toBeUndefined();
71+
expect(priceFeed.getVAA()?.length).toBeGreaterThan(0);
72+
}
73+
});
74+
75+
test("Get latest vaa works", async () => {
76+
const connection = new PriceServiceConnection(PRICE_SERVICE_ENDPOINT, {
77+
priceFeedRequestConfig: { binary: true },
78+
});
79+
80+
const ids = await connection.getPriceFeedIds();
81+
expect(ids.length).toBeGreaterThan(0);
82+
83+
const vaas = await connection.getLatestVaas(ids);
84+
expect(vaas.length).toBeGreaterThan(0);
85+
86+
for (const vaa of vaas) {
87+
expect(vaa.length).toBeGreaterThan(0);
88+
}
89+
});
90+
91+
test("Get vaa works", async () => {
92+
const connection = new PriceServiceConnection(PRICE_SERVICE_ENDPOINT, {
93+
priceFeedRequestConfig: { binary: true },
94+
});
95+
96+
const ids = await connection.getPriceFeedIds();
97+
expect(ids.length).toBeGreaterThan(0);
98+
99+
const publishTime10SecAgo = Math.floor(new Date().getTime() / 1000) - 10;
100+
const [vaa, vaaPublishTime] = await connection.getVaa(
101+
ids[0],
102+
publishTime10SecAgo
103+
);
104+
105+
expect(vaa.length).toBeGreaterThan(0);
106+
expect(vaaPublishTime).toBeGreaterThanOrEqual(publishTime10SecAgo);
107+
});
108+
});
109+
110+
describe("Test websocket endpoints", () => {
111+
jest.setTimeout(60 * 1000);
112+
113+
test.concurrent(
114+
"websocket subscription works without verbose and binary",
115+
async () => {
116+
const connection = new PriceServiceConnection(PRICE_SERVICE_ENDPOINT);
117+
118+
const ids = await connection.getPriceFeedIds();
119+
expect(ids.length).toBeGreaterThan(0);
120+
121+
const counter: Map<string, number> = new Map();
122+
let totalCounter = 0;
123+
124+
await connection.subscribePriceFeedUpdates(ids, (priceFeed) => {
125+
expect(priceFeed.id.length).toBe(64); // 32 byte address has size 64 in hex
126+
expect(priceFeed.getMetadata()).toBeUndefined();
127+
expect(priceFeed.getVAA()).toBeUndefined();
128+
129+
counter.set(priceFeed.id, (counter.get(priceFeed.id) ?? 0) + 1);
130+
totalCounter += 1;
131+
});
132+
133+
// Wait for 30 seconds
134+
await sleep(30000);
135+
connection.closeWebSocket();
136+
137+
expect(totalCounter).toBeGreaterThan(30);
138+
139+
for (const id of ids) {
140+
expect(counter.get(id)).toBeDefined();
141+
// Make sure it receives more than 1 update
142+
expect(counter.get(id)).toBeGreaterThan(1);
143+
}
144+
}
145+
);
146+
147+
test.concurrent("websocket subscription works with verbose", async () => {
148+
const connection = new PriceServiceConnection(PRICE_SERVICE_ENDPOINT, {
149+
priceFeedRequestConfig: { verbose: true },
150+
});
151+
152+
const ids = await connection.getPriceFeedIds();
153+
expect(ids.length).toBeGreaterThan(0);
154+
155+
const observedFeeds: Set<string> = new Set();
156+
157+
await connection.subscribePriceFeedUpdates(ids, (priceFeed) => {
158+
expect(priceFeed.getMetadata()).toBeInstanceOf(PriceFeedMetadata);
159+
expect(priceFeed.getVAA()).toBeUndefined();
160+
observedFeeds.add(priceFeed.id);
161+
});
162+
163+
// Wait for 20 seconds
164+
await sleep(20000);
165+
await connection.unsubscribePriceFeedUpdates(ids);
166+
167+
for (const id of ids) {
168+
expect(observedFeeds.has(id)).toBe(true);
169+
}
170+
});
171+
172+
test.concurrent("websocket subscription works with binary", async () => {
173+
const connection = new PriceServiceConnection(PRICE_SERVICE_ENDPOINT, {
174+
priceFeedRequestConfig: { binary: true },
175+
});
176+
177+
const ids = await connection.getPriceFeedIds();
178+
expect(ids.length).toBeGreaterThan(0);
179+
180+
const observedFeeds: Set<string> = new Set();
181+
182+
await connection.subscribePriceFeedUpdates(ids, (priceFeed) => {
183+
expect(priceFeed.getMetadata()).toBeUndefined();
184+
expect(priceFeed.getVAA()?.length).toBeGreaterThan(0);
185+
observedFeeds.add(priceFeed.id);
186+
});
187+
188+
// Wait for 20 seconds
189+
await sleep(20000);
190+
connection.closeWebSocket();
191+
192+
for (const id of ids) {
193+
expect(observedFeeds.has(id)).toBe(true);
194+
}
195+
});
196+
});

price_service/client/js/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export {
66

77
export {
88
HexString,
9+
PriceFeedMetadata,
910
PriceFeed,
1011
Price,
1112
UnixTimestamp,

third_party/pyth/p2w_autoattest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114
min_rpc_interval_ms: 0 # RIP RPC
115115
max_batch_jobs: 1000 # Where we're going there's no oomkiller
116116
default_attestation_conditions:
117-
min_interval_secs: 60
117+
min_interval_secs: 10
118118
symbol_groups:
119119
- group_name: fast_interval_only
120120
conditions:
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: pyth-price-client-js
5+
spec:
6+
selector:
7+
matchLabels:
8+
app: pyth-price-client-js
9+
serviceName: pyth-price-client-js
10+
replicas: 1
11+
template:
12+
metadata:
13+
labels:
14+
app: pyth-price-client-js
15+
spec:
16+
terminationGracePeriodSeconds: 0
17+
containers:
18+
- name: tests
19+
image: pyth-price-client-js
20+
command:
21+
- /bin/sh
22+
- -c
23+
- "npm run test:e2e && nc -lk 0.0.0.0 2000"
24+
readinessProbe:
25+
periodSeconds: 5
26+
failureThreshold: 300
27+
tcpSocket:
28+
port: 2000
29+
resources:
30+
limits:
31+
cpu: "2"
32+
memory: 1Gi

0 commit comments

Comments
 (0)