Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,16 @@ k8s_resource(
labels = ["solana"],
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"]
)
17 changes: 17 additions & 0 deletions price_service/client/js/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
3 changes: 2 additions & 1 deletion price_service/client/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
],
"repository": "https://github.com/pyth-network/pyth-crosschain",
"scripts": {
"test": "jest --passWithNoTests",
"test": "jest --testPathIgnorePatterns=.*.e2e.test.ts --passWithNoTests",
"test:e2e": "jest --testPathPattern=.*.e2e.test.ts",
"build": "tsc",
"example": "npm run build && node lib/examples/PriceServiceClient.js",
"format": "prettier --write \"src/**/*.ts\"",
Expand Down
196 changes: 196 additions & 0 deletions price_service/client/js/src/__tests__/connection.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -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);
}
});
});
1 change: 1 addition & 0 deletions price_service/client/js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export {

export {
HexString,
PriceFeedMetadata,
PriceFeed,
Price,
UnixTimestamp,
Expand Down
2 changes: 1 addition & 1 deletion third_party/pyth/p2w_autoattest.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
min_rpc_interval_ms: 0 # RIP RPC
max_batch_jobs: 1000 # Where we're going there's no oomkiller
default_attestation_conditions:
min_interval_secs: 60
min_interval_secs: 10
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10 seconds is still slow compared to the rest of groups but will help to make e2e tests run faster.

symbol_groups:
- group_name: fast_interval_only
conditions:
Expand Down
32 changes: 32 additions & 0 deletions tilt_devnet/k8s/pyth-price-client-js.yaml
Original file line number Diff line number Diff line change
@@ -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