Skip to content
Draft
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
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules/
dist/
.env
__pycache__/
*.log
.DS_Store
coverage/
.vscode/
.idea/
19 changes: 19 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "coin-gekko-api",
"version": "1.0.0",
"description": "Coin Gekko API implementation",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"test": "vitest",
"start": "node dist/index.js"
},
"dependencies": {
"axios": "^0.21.1"
},
"devDependencies": {
"@types/node": "^16.11.6",
"typescript": "^4.4.4",
"vitest": "^0.24.0"
}
}
98 changes: 98 additions & 0 deletions src/coinGekkoApi.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { describe, it, expect, beforeEach } from 'vitest';
import axios from 'axios';
import { CoinGekkoApi } from './coinGekkoApi';

// Mock axios
vi.mock('axios');

describe('CoinGekkoApi', () => {
let coinGekkoApi: CoinGekkoApi;

beforeEach(() => {
coinGekkoApi = new CoinGekkoApi();
});

describe('getCoinById', () => {
it('should fetch coin data successfully', async () => {
const mockCoinData = {
id: 'bitcoin',
symbol: 'btc',
name: 'Bitcoin',
market_data: {
current_price: { usd: 50000 },
market_cap: { usd: 1000000000000 }
}
};

vi.mocked(axios.get).mockResolvedValue({ data: mockCoinData });

const result = await coinGekkoApi.getCoinById('bitcoin');
expect(result).toEqual({
id: 'bitcoin',
symbol: 'btc',
name: 'Bitcoin',
price: 50000,
marketCap: 1000000000000
});
});

it('should throw an error if coin ID is not provided', async () => {
await expect(coinGekkoApi.getCoinById('')).rejects.toThrow('Coin ID is required');
});

it('should throw an error if coin is not found', async () => {
vi.mocked(axios.get).mockRejectedValue({
response: { status: 404 },
isAxiosError: true
});

await expect(coinGekkoApi.getCoinById('nonexistent')).rejects.toThrow('Coin with ID nonexistent not found');
});
});

describe('getTopCryptocurrencies', () => {
it('should fetch top cryptocurrencies successfully', async () => {
const mockTopCoins = [
{
id: 'bitcoin',
symbol: 'btc',
name: 'Bitcoin',
current_price: 50000,
market_cap: 1000000000000
},
{
id: 'ethereum',
symbol: 'eth',
name: 'Ethereum',
current_price: 4000,
market_cap: 500000000000
}
];

vi.mocked(axios.get).mockResolvedValue({ data: mockTopCoins });

const result = await coinGekkoApi.getTopCryptocurrencies(2);
expect(result).toEqual([
{
id: 'bitcoin',
symbol: 'btc',
name: 'Bitcoin',
price: 50000,
marketCap: 1000000000000
},
{
id: 'ethereum',
symbol: 'eth',
name: 'Ethereum',
price: 4000,
marketCap: 500000000000
}
]);
});

it('should throw an error if limit is not positive', async () => {
await expect(coinGekkoApi.getTopCryptocurrencies(0)).rejects.toThrow('Limit must be a positive number');
await expect(coinGekkoApi.getTopCryptocurrencies(-1)).rejects.toThrow('Limit must be a positive number');
});
});
});
79 changes: 79 additions & 0 deletions src/coinGekkoApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import axios from 'axios';

export interface CoinData {
id: string;
symbol: string;
name: string;
price: number;
marketCap: number;
}

export class CoinGekkoApi {
private baseUrl: string;

constructor(baseUrl: string = 'https://api.coingecko.com/api/v3') {
this.baseUrl = baseUrl;
}

/**
* Fetch coin data by ID
* @param coinId - The ID of the cryptocurrency
* @returns Promise with coin data
*/
async getCoinById(coinId: string): Promise<CoinData> {
if (!coinId) {
throw new Error('Coin ID is required');
}

try {
const response = await axios.get(`${this.baseUrl}/coins/${coinId}`);
return {
id: response.data.id,
symbol: response.data.symbol,
name: response.data.name,
price: response.data.market_data.current_price.usd,
marketCap: response.data.market_data.market_cap.usd
};
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response?.status === 404) {
throw new Error(`Coin with ID ${coinId} not found`);
}
throw new Error(`Error fetching coin data: ${error.message}`);
}
throw error;
}
}

/**
* List top cryptocurrencies by market cap
* @param limit - Number of cryptocurrencies to return (default: 10)
* @returns Promise with list of top cryptocurrencies
*/
async getTopCryptocurrencies(limit: number = 10): Promise<CoinData[]> {
if (limit <= 0) {
throw new Error('Limit must be a positive number');
}

try {
const response = await axios.get(`${this.baseUrl}/coins/markets`, {
params: {
vs_currency: 'usd',
order: 'market_cap_desc',
per_page: limit,
page: 1
}
});

return response.data.map((coin: any) => ({
id: coin.id,
symbol: coin.symbol,
name: coin.name,
price: coin.current_price,
marketCap: coin.market_cap
}));
} catch (error) {
throw new Error(`Error fetching top cryptocurrencies: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { CoinGekkoApi, CoinData } from './coinGekkoApi';
14 changes: 14 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}