Skip to content

Commit 4eeb7f3

Browse files
committed
test(integration-tests): add structured test infrastructure
Signed-off-by: Joseph Livesey <[email protected]>
1 parent 6bd2b10 commit 4eeb7f3

File tree

7 files changed

+1206
-5
lines changed

7 files changed

+1206
-5
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

integration-tests/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ clap = { version = "4.0", features = ["derive"] }
2828
base64 = { workspace = true }
2929
prost = { workspace = true }
3030
tap_aggregator = { workspace = true }
31+
thiserror = { workspace = true }

integration-tests/INTEGRATION_TESTING_INSTRUCTIONS.md

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,36 @@ just load-test-v2 1000
145145

146146
This sends 1000 V2 receipts to test system performance.
147147

148-
## Step 10: Observe and Debug
148+
## Step 10: Run Enhanced Integration Tests (New Infrastructure)
149+
150+
**Test the new structured testing infrastructure**:
151+
152+
```bash
153+
cd integration-tests
154+
cargo run -- test-with-context
155+
```
156+
157+
**What this tests**:
158+
- V2 receipt processing with detailed error diagnostics
159+
- Insufficient escrow scenario handling
160+
- Batch processing with real-time monitoring
161+
- Automatic test isolation and cleanup
162+
163+
**Expected behavior**:
164+
- Each test runs with a unique ID for isolation
165+
- Detailed progress logging with structured error messages
166+
- Automatic resource cleanup even if tests fail
167+
- Better diagnostics for debugging issues
168+
169+
**Benefits over existing tests**:
170+
- Structured error types provide specific failure contexts
171+
- Test isolation prevents interference between tests
172+
- Reusable utilities reduce code duplication
173+
- Enhanced observability into test execution
174+
175+
See `TESTING_INFRASTRUCTURE_IMPROVEMENT.md` for detailed documentation on the new testing capabilities.
176+
177+
## Step 11: Observe and Debug
149178

150179
### Check Network Subgraph
151180

@@ -196,7 +225,7 @@ docker logs tap-agent -f
196225
curl -s http://localhost:7300/metrics | grep -E "(tap_ravs_created|tap_unaggregated_fees)"
197226
```
198227

199-
## Step 11: Development Workflow
228+
## Step 12: Development Workflow
200229

201230
### Hot Reloading
202231

@@ -301,7 +330,8 @@ docker exec postgres psql -U postgres -d indexer_components_1 -c "SELECT COUNT(*
301330
After successful testing, consider:
302331

303332
1. **Run comprehensive test suite**: `just ci` (includes format, lint, test, sqlx-prepare)
304-
2. **Explore refactoring opportunities**: Review `INTEGRATION_TESTING_IMPROVEMENTS.md`
305-
3. **Contribute improvements**: Follow the refactoring roadmap for better testing infrastructure
333+
2. **Use the new testing infrastructure**: Try `cargo run -- test-with-context` for enhanced testing capabilities
334+
3. **Explore refactoring opportunities**: Review `TESTING_INFRASTRUCTURE_IMPROVEMENT.md`
335+
4. **Contribute improvements**: Follow the refactoring roadmap for better testing infrastructure
306336

307-
This testing infrastructure provides a solid foundation for developing and testing both V1 and V2 TAP functionality in indexer-rs.
337+
This testing infrastructure provides a solid foundation for developing and testing both V1 and V2 TAP functionality in indexer-rs. The new enhanced testing infrastructure adds structured error handling, test isolation, and better observability for more reliable and debuggable integration tests.
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
// Copyright 2023-, Edge & Node, GraphOps, and Semiotic Labs.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Enhanced Integration Tests
5+
//!
6+
//! This module demonstrates the new testing infrastructure with improved
7+
//! test structure, error handling, and observability.
8+
9+
use anyhow::Result;
10+
use std::time::Duration;
11+
12+
use crate::{
13+
constants::*,
14+
test_context::{TestContext, TestError},
15+
test_utils::{MetricsUtils, ReceiptUtils, TestAssertions},
16+
};
17+
18+
/// Example test demonstrating the new infrastructure
19+
pub async fn test_v2_receipt_processing_enhanced() -> Result<()> {
20+
let mut ctx = TestContext::new().await?;
21+
22+
println!(
23+
"🧪 Starting enhanced V2 receipt processing test (ID: {})",
24+
ctx.test_id
25+
);
26+
27+
// Step 1: Verify system is ready
28+
let horizon_enabled = ctx.verify_horizon_detection().await?;
29+
TestAssertions::assert_horizon_mode_enabled(horizon_enabled)?;
30+
println!("✅ Horizon mode detected");
31+
32+
// Step 2: Find active allocation
33+
let allocation = ctx.find_active_allocation().await?;
34+
println!("✅ Found active allocation: {}", allocation.id);
35+
ctx.allocations.push(allocation.clone());
36+
37+
// Step 3: Get initial metrics
38+
let initial_metrics = ctx.metrics_checker.get_current_metrics().await?;
39+
let initial_ravs = initial_metrics.ravs_created_by_allocation(&allocation.id.to_string());
40+
let initial_fees = initial_metrics.unaggregated_fees_by_allocation(&allocation.id.to_string());
41+
42+
println!("📊 Initial metrics - RAVs: {initial_ravs}, Unaggregated fees: {initial_fees}");
43+
44+
// Step 4: Send batch of V2 receipts
45+
let batch_size = 5;
46+
let receipt_value = MAX_RECEIPT_VALUE / 10;
47+
let payer = ctx.wallet.address();
48+
let service_provider = allocation.id; // Using allocation_id as service provider
49+
50+
println!("📨 Sending {batch_size} V2 receipts...");
51+
let successful_receipts = ReceiptUtils::send_v2_receipt_batch(
52+
&ctx,
53+
&allocation.id,
54+
batch_size,
55+
receipt_value,
56+
&payer,
57+
&service_provider,
58+
)
59+
.await?;
60+
61+
TestAssertions::assert_receipts_accepted(successful_receipts, batch_size)?;
62+
println!("✅ All {successful_receipts} receipts accepted");
63+
64+
// Step 5: Wait for processing (with timeout)
65+
println!("⏳ Waiting for receipt processing...");
66+
tokio::time::sleep(Duration::from_secs(5)).await;
67+
68+
// Step 6: Check if RAV generation occurred
69+
let rav_result = MetricsUtils::wait_for_rav_generation(
70+
&ctx,
71+
&allocation.id,
72+
initial_ravs as u32,
73+
Duration::from_secs(30),
74+
)
75+
.await;
76+
77+
let fee_result = MetricsUtils::wait_for_fee_aggregation(
78+
&ctx,
79+
&allocation.id,
80+
initial_fees as f64,
81+
Duration::from_secs(30),
82+
)
83+
.await;
84+
85+
// Test passes if either RAV generation or fee aggregation occurs
86+
match (rav_result, fee_result) {
87+
(Ok(new_ravs), _) => {
88+
println!("✅ Test passed: RAV generation detected ({initial_ravs} -> {new_ravs})");
89+
}
90+
(_, Ok(new_fees)) => {
91+
println!("✅ Test passed: Fee aggregation detected ({initial_fees} -> {new_fees})");
92+
}
93+
(Err(rav_err), Err(fee_err)) => {
94+
println!("❌ Test failed: Neither RAV generation nor fee aggregation occurred");
95+
println!(" RAV error: {rav_err}");
96+
println!(" Fee error: {fee_err}");
97+
return Err(anyhow::anyhow!(TestError::Timeout {
98+
condition: "RAV generation or fee aggregation".to_string(),
99+
}));
100+
}
101+
}
102+
103+
// Step 7: Cleanup
104+
ctx.cleanup().await?;
105+
106+
println!("🎉 Enhanced V2 receipt processing test completed successfully!");
107+
Ok(())
108+
}
109+
110+
/// Example test demonstrating error scenario handling
111+
pub async fn test_insufficient_escrow_scenario() -> Result<()> {
112+
let mut ctx = TestContext::new().await?;
113+
114+
println!(
115+
"🧪 Starting insufficient escrow scenario test (ID: {})",
116+
ctx.test_id
117+
);
118+
119+
// Find allocation
120+
let allocation = ctx.find_active_allocation().await?;
121+
println!("✅ Found active allocation: {}", allocation.id);
122+
123+
// Try to send a receipt with excessive value
124+
let excessive_value = MAX_RECEIPT_VALUE * 1000; // Much larger than typical escrow
125+
let payer = ctx.wallet.address();
126+
let service_provider = allocation.id;
127+
128+
println!("📨 Attempting to send receipt with excessive value: {excessive_value}");
129+
130+
let result = ReceiptUtils::send_v2_receipt(
131+
&ctx,
132+
&allocation.id,
133+
excessive_value,
134+
&payer,
135+
&service_provider,
136+
)
137+
.await;
138+
139+
match result {
140+
Err(e) => {
141+
println!("✅ Receipt correctly rejected: {e}");
142+
// Check if it's the expected error type
143+
if e.to_string().contains("Payment Required") || e.to_string().contains("402") {
144+
println!("✅ Correct error type detected");
145+
} else {
146+
println!("⚠️ Unexpected error type, but still acceptable");
147+
}
148+
}
149+
Ok(_) => {
150+
println!("❌ Receipt was unexpectedly accepted");
151+
return Err(anyhow::anyhow!(TestError::ReceiptValidationFailed {
152+
reason: "Receipt with excessive value should have been rejected".to_string(),
153+
}));
154+
}
155+
}
156+
157+
// Cleanup
158+
ctx.cleanup().await?;
159+
160+
println!("🎉 Insufficient escrow scenario test completed successfully!");
161+
Ok(())
162+
}
163+
164+
/// Example test demonstrating batch processing with monitoring
165+
pub async fn test_batch_processing_with_monitoring() -> Result<()> {
166+
let mut ctx = TestContext::new().await?;
167+
168+
println!(
169+
"🧪 Starting batch processing monitoring test (ID: {})",
170+
ctx.test_id
171+
);
172+
173+
// Setup
174+
let allocation = ctx.find_active_allocation().await?;
175+
let payer = ctx.wallet.address();
176+
let service_provider = allocation.id;
177+
178+
// Send multiple small batches and monitor progress
179+
let batches = 3;
180+
let batch_size = 3;
181+
let receipt_value = MAX_RECEIPT_VALUE / 20;
182+
183+
println!("📨 Sending {batches} batches of {batch_size} receipts each...");
184+
185+
for batch_num in 0..batches {
186+
println!(" 📦 Batch {}/{}", batch_num + 1, batches);
187+
188+
let successful = ReceiptUtils::send_v2_receipt_batch(
189+
&ctx,
190+
&allocation.id,
191+
batch_size,
192+
receipt_value,
193+
&payer,
194+
&service_provider,
195+
)
196+
.await?;
197+
198+
TestAssertions::assert_receipts_accepted(successful, batch_size)?;
199+
200+
// Check metrics after each batch
201+
let metrics = ctx.metrics_checker.get_current_metrics().await?;
202+
let current_fees = metrics.unaggregated_fees_by_allocation(&allocation.id.to_string());
203+
204+
println!(
205+
" 📊 Batch {} complete - Current unaggregated fees: {}",
206+
batch_num + 1,
207+
current_fees
208+
);
209+
210+
// Small delay between batches to allow processing
211+
if batch_num < batches - 1 {
212+
tokio::time::sleep(Duration::from_secs(2)).await;
213+
}
214+
}
215+
216+
println!("✅ All batches processed successfully");
217+
218+
// Wait for final processing
219+
println!("⏳ Waiting for final processing...");
220+
tokio::time::sleep(Duration::from_secs(10)).await;
221+
222+
let final_metrics = ctx.metrics_checker.get_current_metrics().await?;
223+
let final_ravs = final_metrics.ravs_created_by_allocation(&allocation.id.to_string());
224+
let final_fees = final_metrics.unaggregated_fees_by_allocation(&allocation.id.to_string());
225+
226+
println!("📊 Final metrics - RAVs: {final_ravs}, Unaggregated fees: {final_fees}");
227+
228+
// Cleanup
229+
ctx.cleanup().await?;
230+
231+
println!("🎉 Batch processing monitoring test completed successfully!");
232+
Ok(())
233+
}
234+
235+
// Test runner function for the enhanced tests
236+
pub async fn run_enhanced_tests() -> Result<()> {
237+
println!("🚀 Running enhanced integration tests...");
238+
239+
// Test 1: Basic V2 receipt processing
240+
if let Err(e) = test_v2_receipt_processing_enhanced().await {
241+
println!("❌ Enhanced V2 receipt processing test failed: {e}");
242+
return Err(e);
243+
}
244+
245+
// Test 2: Error scenario handling
246+
if let Err(e) = test_insufficient_escrow_scenario().await {
247+
println!("❌ Insufficient escrow scenario test failed: {e}");
248+
return Err(e);
249+
}
250+
251+
// Test 3: Batch processing with monitoring
252+
if let Err(e) = test_batch_processing_with_monitoring().await {
253+
println!("❌ Batch processing monitoring test failed: {e}");
254+
return Err(e);
255+
}
256+
257+
println!("🎉 All enhanced integration tests passed!");
258+
Ok(())
259+
}
260+
261+
#[cfg(test)]
262+
mod tests {
263+
// use super::*;
264+
// use crate::test_with_context;
265+
266+
// Example of using the test macro for unit-style testing
267+
// TODO: Fix macro to handle async blocks properly
268+
/*
269+
test_with_context!(test_context_creation, |ctx: &mut TestContext| async {
270+
// Simple test to verify context creation works
271+
assert!(!ctx.test_id.is_empty());
272+
assert!(ctx.allocations.is_empty());
273+
assert!(ctx.cleanup_tasks.is_empty());
274+
Ok(())
275+
});
276+
*/
277+
}

integration-tests/src/main.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
mod constants;
5+
mod enhanced_tests;
56
mod load_test;
67
mod metrics;
78
mod rav_tests;
9+
mod test_context;
10+
mod test_utils;
811
mod utils;
912

1013
use anyhow::Result;
1114
use clap::Parser;
15+
use enhanced_tests::run_enhanced_tests;
1216
use load_test::{receipt_handler_load_test, receipt_handler_load_test_v2};
1317
use metrics::MetricsChecker;
1418
pub(crate) use rav_tests::{test_invalid_chain_id, test_tap_rav_v1, test_tap_rav_v2};
@@ -39,6 +43,9 @@ enum Commands {
3943
#[clap(long, short, value_parser)]
4044
num_receipts: usize,
4145
},
46+
47+
#[clap(name = "test-with-context")]
48+
TestWithContext,
4249
}
4350

4451
#[tokio::main]
@@ -65,6 +72,10 @@ async fn main() -> Result<()> {
6572
let concurrency = num_cpus::get();
6673
receipt_handler_load_test_v2(num_receipts, concurrency).await?;
6774
}
75+
// cargo run -- test-with-context
76+
Commands::TestWithContext => {
77+
run_enhanced_tests().await?;
78+
}
6879
}
6980

7081
Ok(())

0 commit comments

Comments
 (0)