Skip to content

Commit

Permalink
test: add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
0xnigir1 committed Oct 8, 2024
1 parent 0529357 commit 98324ea
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 3 deletions.
3 changes: 3 additions & 0 deletions packages/pricing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,8 @@
"dependencies": {
"@grants-stack-indexer/shared": "workspace:0.0.1",
"axios": "1.7.7"
},
"devDependencies": {
"axios-mock-adapter": "2.0.0"
}
}
2 changes: 1 addition & 1 deletion packages/pricing/src/external.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type { TokenPrice } from "./internal.js";
export type { TokenPrice, IPricingProvider } from "./internal.js";

export { CoingeckoProvider } from "./internal.js";
export {
Expand Down
7 changes: 5 additions & 2 deletions packages/pricing/src/providers/coingecko.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ const nativeTokens: { [key in CoingeckoSupportedChainId]: CoingeckoTokenId } = {
1088: "metis-token" as CoingeckoTokenId,
};

/**
* The Coingecko provider is a pricing provider that uses the Coingecko API to get the price of a token.
*/
export class CoingeckoProvider implements IPricingProvider {
private readonly axios: AxiosInstance;

Expand Down Expand Up @@ -112,13 +115,13 @@ export class CoingeckoProvider implements IPricingProvider {
return undefined;
}

if (error.status! >= 500) {
if (error.status! >= 500 || error.message === "Network Error") {
throw new NetworkException(error.message, error.status!);
}
}
console.error(error);
throw new UnknownPricingException(
JSON.stringify(error),
isNativeError(error) ? error.message : JSON.stringify(error),
isNativeError(error) ? error.stack : undefined,
);
}
Expand Down
144 changes: 144 additions & 0 deletions packages/pricing/test/providers/coingecko.provider.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import MockAdapter from "axios-mock-adapter";
import { afterEach, beforeEach, describe, expect, it } from "vitest";

import { Address, NATIVE_TOKEN_ADDRESS } from "@grants-stack-indexer/shared";

import type { TokenPrice } from "../../src/external.js";
import {
CoingeckoProvider,
NetworkException,
UnsupportedChainException,
} from "../../src/external.js";

describe("CoingeckoProvider", () => {
let provider: CoingeckoProvider;
let mock: MockAdapter;

beforeEach(() => {
provider = new CoingeckoProvider({
apiKey: "test-api-key",
apiType: "demo",
});
mock = new MockAdapter(provider["axios"]);
});

afterEach(() => {
mock.reset();
});

describe("getTokenPrice", () => {
it("return token price for a supported chain and valid token", async () => {
const mockResponse = {
prices: [[1609459200000, 100]],
};
mock.onGet().reply(200, mockResponse);

const result = await provider.getTokenPrice(
1,
"0x1234567890123456789012345678901234567890" as Address,
1609459200000,
1609545600000,
);

const expectedPrice: TokenPrice = {
timestampMs: 1609459200000,
priceUsd: 100,
};

expect(result).toEqual(expectedPrice);
expect(mock.history.get[0].url).toContain(
"/coins/ethereum/contract/0x1234567890123456789012345678901234567890/market_chart/range?vs_currency=usd&from=1609459200&to=1609545600&precision=full",
);
});

it("return token price for a supported chain and native token", async () => {
const mockResponse = {
prices: [[1609459200000, 100]],
};
mock.onGet().reply(200, mockResponse);

const result = await provider.getTokenPrice(
10,
NATIVE_TOKEN_ADDRESS,
1609459200000,
1609545600000,
);

const expectedPrice: TokenPrice = {
timestampMs: 1609459200000,
priceUsd: 100,
};

expect(result).toEqual(expectedPrice);
expect(mock.history.get[0].url).toContain(
"/coins/ethereum/market_chart/range?vs_currency=usd&from=1609459200&to=1609545600&precision=full",
);
});

it("return undefined if no price data is available for timerange", async () => {
const mockResponse = {
prices: [],
};
mock.onGet().reply(200, mockResponse);

const result = await provider.getTokenPrice(
1,
"0x1234567890123456789012345678901234567890" as Address,
1609459200000,
1609545600000,
);

expect(result).toBeUndefined();
});

it("return undefined if 400 family error", async () => {
mock.onGet().replyOnce(400, "Bad Request");

const result = await provider.getTokenPrice(
1,
"0x1234567890123456789012345678901234567890" as Address,
1609459200000,
1609545600000,
);

expect(result).toBeUndefined();
});

it("throw UnsupportedChainException for unsupported chain", async () => {
await expect(() =>
provider.getTokenPrice(
999999, // Unsupported chain ID
"0x1234567890123456789012345678901234567890" as Address,
1609459200000,
1609545600000,
),
).rejects.toThrow(UnsupportedChainException);
});

it("should throw NetworkException for 500 family errors", async () => {
mock.onGet().reply(500, "Internal Server Error");

await expect(
provider.getTokenPrice(
1,
"0x1234567890123456789012345678901234567890" as Address,
1609459200000,
1609545600000,
),
).rejects.toThrow(NetworkException);
});

it("throw NetworkException for network errors", async () => {
mock.onGet().networkErrorOnce();

await expect(
provider.getTokenPrice(
1,
"0x1234567890123456789012345678901234567890" as Address,
1609459200000,
1609545600000,
),
).rejects.toThrow(NetworkException);
});
});
});
27 changes: 27 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 98324ea

Please sign in to comment.