Skip to content

Commit f628937

Browse files
refcellampagent
andauthored
test: add unit tests for rpc config and fix missing IndexStats (#19)
Amp-Thread-ID: https://ampcode.com/threads/T-019c1732-abef-7495-a2ca-b51abb5ab3ec Co-authored-by: Amp <amp@ampcode.com>
1 parent 67a4f0a commit f628937

4 files changed

Lines changed: 281 additions & 4 deletions

File tree

crates/node/rpc/src/config.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,166 @@ impl RateLimitConfig {
145145
Self { requests_per_second: u64::MAX, burst_size: u64::MAX }
146146
}
147147
}
148+
149+
#[cfg(test)]
150+
mod tests {
151+
use super::*;
152+
153+
#[test]
154+
fn rpc_server_config_default() {
155+
let config = RpcServerConfig::default();
156+
assert_eq!(config.http_addr, "127.0.0.1:8545".parse().unwrap());
157+
assert_eq!(config.jsonrpc_addr, "127.0.0.1:8545".parse().unwrap());
158+
assert_eq!(config.chain_id, 1);
159+
assert_eq!(config.max_connections, 100);
160+
}
161+
162+
#[test]
163+
fn rpc_server_config_new() {
164+
let http: SocketAddr = "127.0.0.1:8080".parse().unwrap();
165+
let jsonrpc: SocketAddr = "127.0.0.1:8545".parse().unwrap();
166+
let config = RpcServerConfig::new(http, jsonrpc, 42);
167+
168+
assert_eq!(config.http_addr, http);
169+
assert_eq!(config.jsonrpc_addr, jsonrpc);
170+
assert_eq!(config.chain_id, 42);
171+
assert_eq!(config.max_connections, 100);
172+
}
173+
174+
#[test]
175+
fn rpc_server_config_single_addr() {
176+
let addr: SocketAddr = "0.0.0.0:9000".parse().unwrap();
177+
let config = RpcServerConfig::single_addr(addr, 137);
178+
179+
assert_eq!(config.http_addr, addr);
180+
assert_eq!(config.jsonrpc_addr, addr);
181+
assert_eq!(config.chain_id, 137);
182+
}
183+
184+
#[test]
185+
fn rpc_server_config_with_cors_origins() {
186+
let config =
187+
RpcServerConfig::default().with_cors_origins(vec!["https://example.com".to_string()]);
188+
assert_eq!(config.cors.allowed_origins, vec!["https://example.com"]);
189+
}
190+
191+
#[test]
192+
fn rpc_server_config_with_rate_limit() {
193+
let config = RpcServerConfig::default().with_rate_limit(500);
194+
assert_eq!(config.rate_limit.requests_per_second, 500);
195+
}
196+
197+
#[test]
198+
fn rpc_server_config_with_max_connections() {
199+
let config = RpcServerConfig::default().with_max_connections(200);
200+
assert_eq!(config.max_connections, 200);
201+
}
202+
203+
#[test]
204+
fn rpc_server_config_chained_builder() {
205+
let config = RpcServerConfig::default()
206+
.with_cors_origins(vec!["*".to_string()])
207+
.with_rate_limit(1000)
208+
.with_max_connections(50);
209+
210+
assert_eq!(config.cors.allowed_origins, vec!["*"]);
211+
assert_eq!(config.rate_limit.requests_per_second, 1000);
212+
assert_eq!(config.max_connections, 50);
213+
}
214+
215+
#[test]
216+
fn cors_config_default() {
217+
let config = CorsConfig::default();
218+
assert_eq!(config.allowed_origins, vec!["http://localhost:3000"]);
219+
assert_eq!(config.allowed_methods, vec!["GET", "POST", "OPTIONS"]);
220+
assert_eq!(config.allowed_headers, vec!["Content-Type"]);
221+
assert_eq!(config.max_age, 3600);
222+
}
223+
224+
#[test]
225+
fn cors_config_none() {
226+
let config = CorsConfig::none();
227+
assert!(config.allowed_origins.is_empty());
228+
assert!(config.allowed_methods.is_empty());
229+
assert!(config.allowed_headers.is_empty());
230+
assert_eq!(config.max_age, 0);
231+
}
232+
233+
#[test]
234+
fn cors_config_permissive() {
235+
let config = CorsConfig::permissive();
236+
assert_eq!(config.allowed_origins, vec!["*"]);
237+
assert!(config.allowed_methods.contains(&"GET".to_string()));
238+
assert!(config.allowed_methods.contains(&"POST".to_string()));
239+
assert!(config.allowed_methods.contains(&"PUT".to_string()));
240+
assert!(config.allowed_methods.contains(&"DELETE".to_string()));
241+
assert!(config.allowed_methods.contains(&"OPTIONS".to_string()));
242+
assert_eq!(config.allowed_headers, vec!["*"]);
243+
assert_eq!(config.max_age, 86400);
244+
}
245+
246+
#[test]
247+
fn rate_limit_config_default() {
248+
let config = RateLimitConfig::default();
249+
assert_eq!(config.requests_per_second, 100);
250+
assert_eq!(config.burst_size, 200);
251+
}
252+
253+
#[test]
254+
fn rate_limit_config_disabled() {
255+
let config = RateLimitConfig::disabled();
256+
assert_eq!(config.requests_per_second, u64::MAX);
257+
assert_eq!(config.burst_size, u64::MAX);
258+
}
259+
260+
#[test]
261+
fn rpc_server_config_clone() {
262+
let original = RpcServerConfig::default().with_rate_limit(250).with_max_connections(75);
263+
let cloned = original.clone();
264+
265+
assert_eq!(cloned.rate_limit.requests_per_second, 250);
266+
assert_eq!(cloned.max_connections, 75);
267+
}
268+
269+
#[test]
270+
fn cors_config_clone() {
271+
let original = CorsConfig::permissive();
272+
let cloned = original.clone();
273+
274+
assert_eq!(cloned.allowed_origins, vec!["*"]);
275+
assert_eq!(cloned.max_age, 86400);
276+
}
277+
278+
#[test]
279+
fn rate_limit_config_clone() {
280+
let original = RateLimitConfig { requests_per_second: 500, burst_size: 1000 };
281+
let cloned = original.clone();
282+
283+
assert_eq!(cloned.requests_per_second, 500);
284+
assert_eq!(cloned.burst_size, 1000);
285+
}
286+
287+
#[test]
288+
fn rpc_server_config_debug() {
289+
let config = RpcServerConfig::default();
290+
let debug_str = format!("{:?}", config);
291+
assert!(debug_str.contains("RpcServerConfig"));
292+
assert!(debug_str.contains("chain_id"));
293+
}
294+
295+
#[test]
296+
fn cors_config_debug() {
297+
let config = CorsConfig::default();
298+
let debug_str = format!("{:?}", config);
299+
assert!(debug_str.contains("CorsConfig"));
300+
assert!(debug_str.contains("allowed_origins"));
301+
}
302+
303+
#[test]
304+
fn rate_limit_config_debug() {
305+
let config = RateLimitConfig::default();
306+
let debug_str = format!("{:?}", config);
307+
assert!(debug_str.contains("RateLimitConfig"));
308+
assert!(debug_str.contains("requests_per_second"));
309+
}
310+
}

crates/storage/indexer/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ mod store;
1313
pub use store::BlockIndex;
1414

1515
mod types;
16-
pub use types::{IndexedBlock, IndexedLog, IndexedReceipt, IndexedTransaction};
16+
pub use types::{IndexStats, IndexedBlock, IndexedLog, IndexedReceipt, IndexedTransaction};

crates/storage/indexer/src/store.rs

Lines changed: 104 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use tracing::debug;
1111

1212
use crate::{
1313
filter::LogFilter,
14-
types::{IndexedBlock, IndexedLog, IndexedReceipt, IndexedTransaction},
14+
types::{IndexStats, IndexedBlock, IndexedLog, IndexedReceipt, IndexedTransaction},
1515
};
1616

1717
/// In-memory storage for indexed blocks, transactions, receipts, and logs.
@@ -161,6 +161,36 @@ impl BlockIndex {
161161
result
162162
}
163163

164+
/// Returns the total number of indexed blocks.
165+
pub fn block_count(&self) -> usize {
166+
self.blocks_by_hash.read().len()
167+
}
168+
169+
/// Returns the total number of indexed transactions.
170+
pub fn transaction_count(&self) -> usize {
171+
self.transactions.read().len()
172+
}
173+
174+
/// Returns the total number of indexed receipts.
175+
pub fn receipt_count(&self) -> usize {
176+
self.receipts.read().len()
177+
}
178+
179+
/// Returns true if the index is empty (no blocks indexed).
180+
pub fn is_empty(&self) -> bool {
181+
self.blocks_by_hash.read().is_empty()
182+
}
183+
184+
/// Returns statistics about the index.
185+
pub fn stats(&self) -> IndexStats {
186+
IndexStats {
187+
block_count: self.block_count(),
188+
transaction_count: self.transaction_count(),
189+
receipt_count: self.receipt_count(),
190+
head_block_number: self.head_block_number(),
191+
}
192+
}
193+
164194
fn matches_filter(log: &IndexedLog, filter: &LogFilter) -> bool {
165195
if let Some(addresses) = &filter.address
166196
&& !addresses.contains(&log.address)
@@ -239,7 +269,7 @@ mod tests {
239269
let block_hash = B256::repeat_byte(1);
240270
let block = create_test_block(1, block_hash);
241271

242-
index.insert_block(block.clone(), vec![], vec![]);
272+
index.insert_block(block, vec![], vec![]);
243273

244274
let retrieved = index.get_block_by_hash(&block_hash).unwrap();
245275
assert_eq!(retrieved.number, 1);
@@ -258,7 +288,7 @@ mod tests {
258288
let tx = create_test_tx(tx_hash, block_hash, 1);
259289
let receipt = create_test_receipt(tx_hash, block_hash, 1);
260290

261-
index.insert_block(block, vec![tx], vec![receipt.clone()]);
291+
index.insert_block(block, vec![tx], vec![receipt]);
262292

263293
let retrieved_tx = index.get_transaction(&tx_hash).unwrap();
264294
assert_eq!(retrieved_tx.hash, tx_hash);
@@ -325,4 +355,75 @@ mod tests {
325355
let logs = index.get_logs(&filter);
326356
assert!(logs.is_empty());
327357
}
358+
359+
#[test]
360+
fn test_is_empty() {
361+
let index = BlockIndex::new();
362+
assert!(index.is_empty());
363+
364+
index.insert_block(create_test_block(1, B256::repeat_byte(1)), vec![], vec![]);
365+
assert!(!index.is_empty());
366+
}
367+
368+
#[test]
369+
fn test_block_count() {
370+
let index = BlockIndex::new();
371+
assert_eq!(index.block_count(), 0);
372+
373+
index.insert_block(create_test_block(1, B256::repeat_byte(1)), vec![], vec![]);
374+
assert_eq!(index.block_count(), 1);
375+
376+
index.insert_block(create_test_block(2, B256::repeat_byte(2)), vec![], vec![]);
377+
assert_eq!(index.block_count(), 2);
378+
}
379+
380+
#[test]
381+
fn test_transaction_count() {
382+
let index = BlockIndex::new();
383+
assert_eq!(index.transaction_count(), 0);
384+
385+
let block_hash = B256::repeat_byte(1);
386+
let tx1 = create_test_tx(B256::repeat_byte(2), block_hash, 1);
387+
let tx2 = create_test_tx(B256::repeat_byte(3), block_hash, 1);
388+
389+
index.insert_block(create_test_block(1, block_hash), vec![tx1, tx2], vec![]);
390+
assert_eq!(index.transaction_count(), 2);
391+
}
392+
393+
#[test]
394+
fn test_receipt_count() {
395+
let index = BlockIndex::new();
396+
assert_eq!(index.receipt_count(), 0);
397+
398+
let block_hash = B256::repeat_byte(1);
399+
let tx_hash = B256::repeat_byte(2);
400+
let receipt = create_test_receipt(tx_hash, block_hash, 1);
401+
402+
index.insert_block(create_test_block(1, block_hash), vec![], vec![receipt]);
403+
assert_eq!(index.receipt_count(), 1);
404+
}
405+
406+
#[test]
407+
fn test_stats() {
408+
let index = BlockIndex::new();
409+
410+
let stats = index.stats();
411+
assert_eq!(stats.block_count, 0);
412+
assert_eq!(stats.transaction_count, 0);
413+
assert_eq!(stats.receipt_count, 0);
414+
assert_eq!(stats.head_block_number, 0);
415+
416+
let block_hash = B256::repeat_byte(1);
417+
let tx_hash = B256::repeat_byte(2);
418+
let tx = create_test_tx(tx_hash, block_hash, 5);
419+
let receipt = create_test_receipt(tx_hash, block_hash, 5);
420+
421+
index.insert_block(create_test_block(5, block_hash), vec![tx], vec![receipt]);
422+
423+
let stats = index.stats();
424+
assert_eq!(stats.block_count, 1);
425+
assert_eq!(stats.transaction_count, 1);
426+
assert_eq!(stats.receipt_count, 1);
427+
assert_eq!(stats.head_block_number, 5);
428+
}
328429
}

crates/storage/indexer/src/types.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,16 @@ pub struct IndexedLog {
9191
/// Log index within the block.
9292
pub log_index: u64,
9393
}
94+
95+
/// Statistics about the block index.
96+
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
97+
pub struct IndexStats {
98+
/// Total number of indexed blocks.
99+
pub block_count: usize,
100+
/// Total number of indexed transactions.
101+
pub transaction_count: usize,
102+
/// Total number of indexed receipts.
103+
pub receipt_count: usize,
104+
/// Current head block number.
105+
pub head_block_number: u64,
106+
}

0 commit comments

Comments
 (0)