Skip to content

Commit 3b8293b

Browse files
committed
feat: add TokenPriceResolver with price history and market cap queries
1 parent b4fecbf commit 3b8293b

File tree

2 files changed

+247
-0
lines changed

2 files changed

+247
-0
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { assert } from 'chai';
2+
import axios from 'axios';
3+
import {
4+
graphqlUrl,
5+
generateRandomEtheriumAddress,
6+
} from '../../test/testUtils';
7+
import { TokenPriceHistory } from '../entities/tokenPriceHistory';
8+
import { AppDataSource } from '../orm';
9+
import {
10+
getTokenPriceHistoryQuery,
11+
getTokenMarketCapChanges24hQuery,
12+
} from '../../test/graphqlQueries';
13+
14+
describe('TokenPriceResolver test cases', () => {
15+
describe('getTokenPriceHistory() test cases', getTokenPriceHistoryTestCases);
16+
describe(
17+
'getTokenMarketCapChanges24h() test cases',
18+
getTokenMarketCapChanges24hTestCases,
19+
);
20+
});
21+
22+
function getTokenPriceHistoryTestCases() {
23+
let tokenAddress: string;
24+
let repository;
25+
26+
beforeEach(async () => {
27+
// Get repository
28+
repository = AppDataSource.getDataSource().getRepository(TokenPriceHistory);
29+
30+
// Create test data
31+
tokenAddress = generateRandomEtheriumAddress();
32+
await Promise.all([
33+
repository.save({
34+
token: 'TEST',
35+
tokenAddress: tokenAddress.toLowerCase(),
36+
price: 1.5,
37+
priceUSD: 2.0,
38+
marketCap: 1000000,
39+
timestamp: new Date(Date.now() - 48 * 60 * 60 * 1000), // 48 hours ago
40+
}),
41+
repository.save({
42+
token: 'TEST',
43+
tokenAddress: tokenAddress.toLowerCase(),
44+
price: 1.6,
45+
priceUSD: 2.1,
46+
marketCap: 1100000,
47+
timestamp: new Date(Date.now() - 24 * 60 * 60 * 1000), // 24 hours ago
48+
}),
49+
repository.save({
50+
token: 'TEST',
51+
tokenAddress: tokenAddress.toLowerCase(),
52+
price: 1.7,
53+
priceUSD: 2.2,
54+
marketCap: 1200000,
55+
timestamp: new Date(), // now
56+
}),
57+
]);
58+
});
59+
60+
afterEach(async () => {
61+
// Clean up test data
62+
await repository.delete({
63+
tokenAddress: tokenAddress.toLowerCase(),
64+
});
65+
});
66+
67+
it('should return all price history entries for a token', async () => {
68+
const result = await axios.post(graphqlUrl, {
69+
query: getTokenPriceHistoryQuery,
70+
variables: {
71+
tokenAddress,
72+
},
73+
});
74+
75+
const entries = result.data.data.getTokenPriceHistory;
76+
assert.isArray(entries);
77+
assert.equal(entries.length, 3);
78+
assert.equal(entries[0].tokenAddress, tokenAddress.toLowerCase());
79+
assert.equal(entries[0].price, 1.7); // Most recent first
80+
assert.equal(entries[0].marketCap, 1200000);
81+
});
82+
83+
it('should return price history within a time range', async () => {
84+
const startTime = new Date(Date.now() - 36 * 60 * 60 * 1000); // 36 hours ago
85+
const endTime = new Date(Date.now() - 12 * 60 * 60 * 1000); // 12 hours ago
86+
87+
const result = await axios.post(graphqlUrl, {
88+
query: getTokenPriceHistoryQuery,
89+
variables: {
90+
tokenAddress,
91+
startTime,
92+
endTime,
93+
},
94+
});
95+
96+
const entries = result.data.data.getTokenPriceHistory;
97+
assert.isArray(entries);
98+
assert.equal(entries.length, 1); // Only the 24-hour old entry should be included
99+
assert.equal(entries[0].price, 1.6);
100+
assert.equal(entries[0].marketCap, 1100000);
101+
});
102+
}
103+
104+
function getTokenMarketCapChanges24hTestCases() {
105+
let tokenAddress: string;
106+
let repository;
107+
108+
beforeEach(async () => {
109+
// Get repository
110+
repository = AppDataSource.getDataSource().getRepository(TokenPriceHistory);
111+
112+
// Create test data
113+
tokenAddress = generateRandomEtheriumAddress();
114+
await Promise.all([
115+
repository.save({
116+
token: 'TEST',
117+
tokenAddress: tokenAddress.toLowerCase(),
118+
price: 1.5,
119+
priceUSD: 2.0,
120+
marketCap: 1000000,
121+
timestamp: new Date(Date.now() - 48 * 60 * 60 * 1000), // 48 hours ago
122+
}),
123+
repository.save({
124+
token: 'TEST',
125+
tokenAddress: tokenAddress.toLowerCase(),
126+
price: 1.6,
127+
priceUSD: 2.1,
128+
marketCap: 1100000,
129+
timestamp: new Date(Date.now() - 23 * 60 * 60 * 1000), // 23 hours ago
130+
}),
131+
repository.save({
132+
token: 'TEST',
133+
tokenAddress: tokenAddress.toLowerCase(),
134+
price: 1.7,
135+
priceUSD: 2.2,
136+
marketCap: 1200000,
137+
timestamp: new Date(), // now
138+
}),
139+
]);
140+
});
141+
142+
afterEach(async () => {
143+
// Clean up test data
144+
await repository.delete({
145+
tokenAddress: tokenAddress.toLowerCase(),
146+
});
147+
});
148+
149+
it('should return market cap changes in the last 24 hours', async () => {
150+
const result = await axios.post(graphqlUrl, {
151+
query: getTokenMarketCapChanges24hQuery,
152+
variables: {
153+
tokenAddress,
154+
},
155+
});
156+
157+
const entries = result.data.data.getTokenMarketCapChanges24h;
158+
assert.isArray(entries);
159+
assert.equal(entries.length, 2); // Only entries from last 24 hours
160+
assert.equal(entries[0].tokenAddress, tokenAddress.toLowerCase());
161+
assert.equal(entries[0].price, 1.7); // Most recent first
162+
assert.equal(entries[0].marketCap, 1200000);
163+
assert.equal(entries[1].price, 1.6);
164+
assert.equal(entries[1].marketCap, 1100000);
165+
});
166+
167+
it('should return empty array for non-existent token', async () => {
168+
const nonExistentAddress = generateRandomEtheriumAddress();
169+
const result = await axios.post(graphqlUrl, {
170+
query: getTokenMarketCapChanges24hQuery,
171+
variables: {
172+
tokenAddress: nonExistentAddress,
173+
},
174+
});
175+
176+
const entries = result.data.data.getTokenMarketCapChanges24h;
177+
assert.isArray(entries);
178+
assert.equal(entries.length, 0);
179+
});
180+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { Resolver, Query, Arg } from 'type-graphql';
2+
import { Between, LessThanOrEqual, MoreThanOrEqual } from 'typeorm';
3+
import { TokenPriceHistory } from '../entities/tokenPriceHistory';
4+
import { logger } from '../utils/logger';
5+
6+
@Resolver(_of => TokenPriceHistory)
7+
export class TokenPriceResolver {
8+
@Query(_returns => [TokenPriceHistory])
9+
async getTokenPriceHistory(
10+
@Arg('tokenAddress') tokenAddress: string,
11+
@Arg('startTime', { nullable: true })
12+
startTime?: Date,
13+
@Arg('endTime', { nullable: true })
14+
endTime?: Date,
15+
): Promise<TokenPriceHistory[]> {
16+
try {
17+
const where: any = {
18+
tokenAddress: tokenAddress.toLowerCase(),
19+
};
20+
21+
if (startTime && endTime) {
22+
where.timestamp = Between(startTime, endTime);
23+
} else if (startTime) {
24+
where.timestamp = MoreThanOrEqual(startTime);
25+
} else if (endTime) {
26+
where.timestamp = LessThanOrEqual(endTime);
27+
}
28+
29+
return await TokenPriceHistory.find({
30+
where,
31+
order: { timestamp: 'DESC' },
32+
});
33+
} catch (error) {
34+
logger.error('Error fetching token price history', {
35+
tokenAddress,
36+
error: error.message,
37+
});
38+
throw error;
39+
}
40+
}
41+
42+
@Query(_returns => [TokenPriceHistory])
43+
async getTokenMarketCapChanges24h(
44+
@Arg('tokenAddress') tokenAddress: string,
45+
): Promise<TokenPriceHistory[]> {
46+
try {
47+
const now = new Date();
48+
const twentyFourHoursAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
49+
50+
const priceHistory = await TokenPriceHistory.find({
51+
where: {
52+
tokenAddress: tokenAddress.toLowerCase(),
53+
timestamp: Between(twentyFourHoursAgo, now),
54+
},
55+
order: { timestamp: 'DESC' },
56+
});
57+
58+
return priceHistory;
59+
} catch (error) {
60+
logger.error('Error fetching 24h market cap changes', {
61+
tokenAddress,
62+
error: error.message,
63+
});
64+
throw error;
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)