[price-service/client] Add e2 test (#559)
This commit is contained in:
parent
fc08ec277e
commit
3f7cffd5b2
13
Tiltfile
13
Tiltfile
|
@ -338,3 +338,16 @@ k8s_resource(
|
||||||
labels = ["solana"],
|
labels = ["solana"],
|
||||||
trigger_mode = trigger_mode,
|
trigger_mode = trigger_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Pyth Price Client JS e2e test
|
||||||
|
docker_build(
|
||||||
|
ref = "pyth-price-client-js",
|
||||||
|
context = ".",
|
||||||
|
dockerfile = "price_service/client/js/Dockerfile",
|
||||||
|
)
|
||||||
|
k8s_yaml_with_ns("tilt_devnet/k8s/pyth-price-client-js.yaml")
|
||||||
|
k8s_resource(
|
||||||
|
"pyth-price-client-js",
|
||||||
|
resource_deps = ["pyth-price-server"],
|
||||||
|
labels = ["pyth"]
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Defined in tilt_devnet/docker_images/Dockerfile.lerna
|
||||||
|
FROM lerna
|
||||||
|
|
||||||
|
USER root
|
||||||
|
RUN apt-get update && apt-get install -y ncat
|
||||||
|
|
||||||
|
WORKDIR /home/node/
|
||||||
|
USER 1000
|
||||||
|
|
||||||
|
COPY --chown=1000:1000 price_service/client/js price_service/client/js
|
||||||
|
COPY --chown=1000:1000 price_service/sdk/js price_service/sdk/js
|
||||||
|
|
||||||
|
RUN npx lerna run build --scope="@pythnetwork/price-service-client" --include-dependencies
|
||||||
|
|
||||||
|
WORKDIR /home/node/price_service/client/js
|
||||||
|
|
||||||
|
ENTRYPOINT ["npm"]
|
|
@ -13,7 +13,8 @@
|
||||||
],
|
],
|
||||||
"repository": "https://github.com/pyth-network/pyth-crosschain",
|
"repository": "https://github.com/pyth-network/pyth-crosschain",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest --passWithNoTests",
|
"test": "jest --testPathIgnorePatterns=.*.e2e.test.ts --passWithNoTests",
|
||||||
|
"test:e2e": "jest --testPathPattern=.*.e2e.test.ts",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"example": "npm run build && node lib/examples/PriceServiceClient.js",
|
"example": "npm run build && node lib/examples/PriceServiceClient.js",
|
||||||
"format": "prettier --write \"src/**/*.ts\"",
|
"format": "prettier --write \"src/**/*.ts\"",
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
import {
|
||||||
|
DurationInMs,
|
||||||
|
Price,
|
||||||
|
PriceFeed,
|
||||||
|
PriceFeedMetadata,
|
||||||
|
PriceServiceConnection,
|
||||||
|
} from "../index";
|
||||||
|
|
||||||
|
async function sleep(duration: DurationInMs): Promise<void> {
|
||||||
|
return new Promise((res) => setTimeout(res, duration));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The endpoint is set to the price service endpoint in Tilt.
|
||||||
|
// Please note that if you change it to a mainnet/testnet endpoint
|
||||||
|
// some tests might fail due to the huge response size of a request
|
||||||
|
// , i.e. requesting latest price feeds or vaas of all price ids.
|
||||||
|
const PRICE_SERVICE_ENDPOINT = "http://pyth-price-server:4200";
|
||||||
|
|
||||||
|
describe("Test http endpoints", () => {
|
||||||
|
test("Get price feed (without verbose/binary) works", async () => {
|
||||||
|
const connection = new PriceServiceConnection(PRICE_SERVICE_ENDPOINT);
|
||||||
|
const ids = await connection.getPriceFeedIds();
|
||||||
|
expect(ids.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
const priceFeeds = await connection.getLatestPriceFeeds(ids);
|
||||||
|
expect(priceFeeds).toBeDefined();
|
||||||
|
expect(priceFeeds!.length).toEqual(ids.length);
|
||||||
|
|
||||||
|
for (const priceFeed of priceFeeds!) {
|
||||||
|
expect(priceFeed.id.length).toBe(64); // 32 byte address has size 64 in hex
|
||||||
|
expect(priceFeed).toBeInstanceOf(PriceFeed);
|
||||||
|
expect(priceFeed.getPriceUnchecked()).toBeInstanceOf(Price);
|
||||||
|
expect(priceFeed.getEmaPriceUnchecked()).toBeInstanceOf(Price);
|
||||||
|
expect(priceFeed.getMetadata()).toBeUndefined();
|
||||||
|
expect(priceFeed.getVAA()).toBeUndefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Get price feed with verbose flag works", async () => {
|
||||||
|
const connection = new PriceServiceConnection(PRICE_SERVICE_ENDPOINT, {
|
||||||
|
priceFeedRequestConfig: { verbose: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const ids = await connection.getPriceFeedIds();
|
||||||
|
expect(ids.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
const priceFeeds = await connection.getLatestPriceFeeds(ids);
|
||||||
|
expect(priceFeeds).toBeDefined();
|
||||||
|
expect(priceFeeds!.length).toEqual(ids.length);
|
||||||
|
|
||||||
|
for (const priceFeed of priceFeeds!) {
|
||||||
|
expect(priceFeed.getMetadata()).toBeInstanceOf(PriceFeedMetadata);
|
||||||
|
expect(priceFeed.getVAA()).toBeUndefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Get price feed with binary flag works", async () => {
|
||||||
|
const connection = new PriceServiceConnection(PRICE_SERVICE_ENDPOINT, {
|
||||||
|
priceFeedRequestConfig: { binary: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const ids = await connection.getPriceFeedIds();
|
||||||
|
expect(ids.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
const priceFeeds = await connection.getLatestPriceFeeds(ids);
|
||||||
|
expect(priceFeeds).toBeDefined();
|
||||||
|
expect(priceFeeds!.length).toEqual(ids.length);
|
||||||
|
|
||||||
|
for (const priceFeed of priceFeeds!) {
|
||||||
|
expect(priceFeed.getMetadata()).toBeUndefined();
|
||||||
|
expect(priceFeed.getVAA()?.length).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Get latest vaa works", async () => {
|
||||||
|
const connection = new PriceServiceConnection(PRICE_SERVICE_ENDPOINT, {
|
||||||
|
priceFeedRequestConfig: { binary: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const ids = await connection.getPriceFeedIds();
|
||||||
|
expect(ids.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
const vaas = await connection.getLatestVaas(ids);
|
||||||
|
expect(vaas.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
for (const vaa of vaas) {
|
||||||
|
expect(vaa.length).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Get vaa works", async () => {
|
||||||
|
const connection = new PriceServiceConnection(PRICE_SERVICE_ENDPOINT, {
|
||||||
|
priceFeedRequestConfig: { binary: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const ids = await connection.getPriceFeedIds();
|
||||||
|
expect(ids.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
const publishTime10SecAgo = Math.floor(new Date().getTime() / 1000) - 10;
|
||||||
|
const [vaa, vaaPublishTime] = await connection.getVaa(
|
||||||
|
ids[0],
|
||||||
|
publishTime10SecAgo
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(vaa.length).toBeGreaterThan(0);
|
||||||
|
expect(vaaPublishTime).toBeGreaterThanOrEqual(publishTime10SecAgo);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Test websocket endpoints", () => {
|
||||||
|
jest.setTimeout(60 * 1000);
|
||||||
|
|
||||||
|
test.concurrent(
|
||||||
|
"websocket subscription works without verbose and binary",
|
||||||
|
async () => {
|
||||||
|
const connection = new PriceServiceConnection(PRICE_SERVICE_ENDPOINT);
|
||||||
|
|
||||||
|
const ids = await connection.getPriceFeedIds();
|
||||||
|
expect(ids.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
const counter: Map<string, number> = new Map();
|
||||||
|
let totalCounter = 0;
|
||||||
|
|
||||||
|
await connection.subscribePriceFeedUpdates(ids, (priceFeed) => {
|
||||||
|
expect(priceFeed.id.length).toBe(64); // 32 byte address has size 64 in hex
|
||||||
|
expect(priceFeed.getMetadata()).toBeUndefined();
|
||||||
|
expect(priceFeed.getVAA()).toBeUndefined();
|
||||||
|
|
||||||
|
counter.set(priceFeed.id, (counter.get(priceFeed.id) ?? 0) + 1);
|
||||||
|
totalCounter += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for 30 seconds
|
||||||
|
await sleep(30000);
|
||||||
|
connection.closeWebSocket();
|
||||||
|
|
||||||
|
expect(totalCounter).toBeGreaterThan(30);
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
expect(counter.get(id)).toBeDefined();
|
||||||
|
// Make sure it receives more than 1 update
|
||||||
|
expect(counter.get(id)).toBeGreaterThan(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test.concurrent("websocket subscription works with verbose", async () => {
|
||||||
|
const connection = new PriceServiceConnection(PRICE_SERVICE_ENDPOINT, {
|
||||||
|
priceFeedRequestConfig: { verbose: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const ids = await connection.getPriceFeedIds();
|
||||||
|
expect(ids.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
const observedFeeds: Set<string> = new Set();
|
||||||
|
|
||||||
|
await connection.subscribePriceFeedUpdates(ids, (priceFeed) => {
|
||||||
|
expect(priceFeed.getMetadata()).toBeInstanceOf(PriceFeedMetadata);
|
||||||
|
expect(priceFeed.getVAA()).toBeUndefined();
|
||||||
|
observedFeeds.add(priceFeed.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for 20 seconds
|
||||||
|
await sleep(20000);
|
||||||
|
await connection.unsubscribePriceFeedUpdates(ids);
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
expect(observedFeeds.has(id)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test.concurrent("websocket subscription works with binary", async () => {
|
||||||
|
const connection = new PriceServiceConnection(PRICE_SERVICE_ENDPOINT, {
|
||||||
|
priceFeedRequestConfig: { binary: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const ids = await connection.getPriceFeedIds();
|
||||||
|
expect(ids.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
const observedFeeds: Set<string> = new Set();
|
||||||
|
|
||||||
|
await connection.subscribePriceFeedUpdates(ids, (priceFeed) => {
|
||||||
|
expect(priceFeed.getMetadata()).toBeUndefined();
|
||||||
|
expect(priceFeed.getVAA()?.length).toBeGreaterThan(0);
|
||||||
|
observedFeeds.add(priceFeed.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for 20 seconds
|
||||||
|
await sleep(20000);
|
||||||
|
connection.closeWebSocket();
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
expect(observedFeeds.has(id)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -6,6 +6,7 @@ export {
|
||||||
|
|
||||||
export {
|
export {
|
||||||
HexString,
|
HexString,
|
||||||
|
PriceFeedMetadata,
|
||||||
PriceFeed,
|
PriceFeed,
|
||||||
Price,
|
Price,
|
||||||
UnixTimestamp,
|
UnixTimestamp,
|
||||||
|
|
|
@ -114,7 +114,7 @@ mapping_reload_interval_mins: 1 # Very fast for testing purposes
|
||||||
min_rpc_interval_ms: 0 # RIP RPC
|
min_rpc_interval_ms: 0 # RIP RPC
|
||||||
max_batch_jobs: 1000 # Where we're going there's no oomkiller
|
max_batch_jobs: 1000 # Where we're going there's no oomkiller
|
||||||
default_attestation_conditions:
|
default_attestation_conditions:
|
||||||
min_interval_secs: 60
|
min_interval_secs: 10
|
||||||
symbol_groups:
|
symbol_groups:
|
||||||
- group_name: fast_interval_only
|
- group_name: fast_interval_only
|
||||||
conditions:
|
conditions:
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: pyth-price-client-js
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: pyth-price-client-js
|
||||||
|
serviceName: pyth-price-client-js
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: pyth-price-client-js
|
||||||
|
spec:
|
||||||
|
terminationGracePeriodSeconds: 0
|
||||||
|
containers:
|
||||||
|
- name: tests
|
||||||
|
image: pyth-price-client-js
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- "npm run test:e2e && nc -lk 0.0.0.0 2000"
|
||||||
|
readinessProbe:
|
||||||
|
periodSeconds: 5
|
||||||
|
failureThreshold: 300
|
||||||
|
tcpSocket:
|
||||||
|
port: 2000
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: "2"
|
||||||
|
memory: 1Gi
|
Loading…
Reference in New Issue