Skip to content

Commit 74ccb43

Browse files
committed
refactor(agent): rename subgraph-client-abstraction module
1 parent acba567 commit 74ccb43

File tree

4 files changed

+271
-141
lines changed

4 files changed

+271
-141
lines changed

crates/tap-agent/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub mod cli;
3636
pub mod database;
3737
/// Prometheus Metrics server
3838
pub mod metrics;
39-
pub mod subgraph_client_simple;
39+
pub mod subgraph_client_abstraction;
4040
pub mod tap;
4141
pub mod task_lifecycle;
4242

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
// Copyright 2023-, Edge & Node, GraphOps, and Semiotic Labs.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Simple SubgraphClient Abstraction for Testing
5+
//!
6+
//! This provides a minimal abstraction to enable Layer 2 integration testing
7+
//! without the complexity of async trait objects.
8+
9+
use crate::agent::sender_accounts_manager::AllocationId;
10+
use anyhow::Result;
11+
use indexer_monitor::SubgraphClient;
12+
use serde_json;
13+
use std::sync::Arc;
14+
15+
/// Simple enum wrapper for different SubgraphClient implementations
16+
/// This solves the dependency injection problem for testing
17+
#[derive(Clone)]
18+
pub enum SimpleSubgraphClient {
19+
/// Production implementation using real SubgraphClient
20+
Production(Arc<SubgraphClient>),
21+
/// Mock implementation for testing
22+
Mock(SimpleSubgraphMock),
23+
}
24+
25+
impl SimpleSubgraphClient {
26+
/// Create a production client wrapper
27+
pub fn production(client: Arc<SubgraphClient>) -> Self {
28+
Self::Production(client)
29+
}
30+
31+
/// Create a mock client for testing
32+
pub fn mock(mock: SimpleSubgraphMock) -> Self {
33+
Self::Mock(mock)
34+
}
35+
36+
/// Validate allocation status for receipt processing
37+
/// This is the key operation that SenderAllocationTask needs
38+
pub async fn validate_allocation(&self, allocation_id: &AllocationId) -> Result<bool> {
39+
match self {
40+
Self::Production(client) => {
41+
Self::validate_allocation_via_subgraph(client, allocation_id).await
42+
}
43+
Self::Mock(mock) => Ok(mock.should_validate_allocation),
44+
}
45+
}
46+
47+
/// Query the network subgraph to validate allocation status
48+
async fn validate_allocation_via_subgraph(
49+
client: &SubgraphClient,
50+
allocation_id: &AllocationId,
51+
) -> Result<bool> {
52+
// Convert AllocationId to string format for GraphQL query
53+
let allocation_id_str = match allocation_id {
54+
AllocationId::Legacy(id) => id.to_string(),
55+
AllocationId::Horizon(id) => id.to_string(),
56+
};
57+
58+
// Simple GraphQL query to check if allocation exists and is active
59+
let query = format!(
60+
r#"{{
61+
allocation(id: "{}") {{
62+
id
63+
status
64+
indexer {{
65+
id
66+
}}
67+
}}
68+
}}"#,
69+
allocation_id_str.to_lowercase()
70+
);
71+
72+
tracing::debug!(
73+
allocation_id = %allocation_id_str,
74+
"Validating allocation status via network subgraph"
75+
);
76+
77+
// Execute the GraphQL query
78+
let response = client
79+
.query_raw(query.into())
80+
.await
81+
.map_err(|e| anyhow::anyhow!("Failed to query allocation status: {}", e))?;
82+
83+
if !response.status().is_success() {
84+
let status = response.status();
85+
let body = response
86+
.text()
87+
.await
88+
.unwrap_or_else(|_| "Failed to read response body".to_string());
89+
tracing::warn!(
90+
allocation_id = %allocation_id_str,
91+
status = %status,
92+
body = %body,
93+
"Subgraph query failed"
94+
);
95+
return Ok(false);
96+
}
97+
98+
let response_text = response
99+
.text()
100+
.await
101+
.map_err(|e| anyhow::anyhow!("Failed to read response body: {}", e))?;
102+
103+
// Parse the JSON response to check allocation status
104+
let response_json: serde_json::Value = serde_json::from_str(&response_text)
105+
.map_err(|e| anyhow::anyhow!("Failed to parse JSON response: {}", e))?;
106+
107+
// Check if allocation exists and is active
108+
let is_valid = response_json
109+
.get("data")
110+
.and_then(|data| data.get("allocation"))
111+
.map(|allocation| {
112+
// If allocation is null, it doesn't exist
113+
if allocation.is_null() {
114+
tracing::debug!(
115+
allocation_id = %allocation_id_str,
116+
"Allocation not found in network subgraph"
117+
);
118+
false
119+
} else {
120+
// Check if allocation status is "Active"
121+
let status = allocation
122+
.get("status")
123+
.and_then(|s| s.as_str())
124+
.unwrap_or("");
125+
126+
let is_active = status == "Active";
127+
128+
tracing::debug!(
129+
allocation_id = %allocation_id_str,
130+
status = %status,
131+
is_active = %is_active,
132+
"Allocation validation result"
133+
);
134+
135+
is_active
136+
}
137+
})
138+
.unwrap_or(false);
139+
140+
Ok(is_valid)
141+
}
142+
143+
/// Check if the subgraph client is healthy and ready
144+
pub async fn is_healthy(&self) -> bool {
145+
match self {
146+
Self::Production(client) => Self::check_subgraph_health(client).await,
147+
Self::Mock(mock) => mock.is_healthy,
148+
}
149+
}
150+
151+
/// Perform a health check against the subgraph endpoint
152+
async fn check_subgraph_health(client: &SubgraphClient) -> bool {
153+
// Use a simple _meta query to check connectivity and basic functionality
154+
let health_query = r#"{
155+
_meta {
156+
block {
157+
number
158+
hash
159+
}
160+
}
161+
}"#;
162+
163+
tracing::debug!("Performing subgraph health check");
164+
165+
match client.query_raw(health_query.to_string().into()).await {
166+
Ok(response) => {
167+
let is_healthy = response.status().is_success();
168+
169+
if is_healthy {
170+
tracing::debug!("Subgraph health check passed");
171+
} else {
172+
tracing::warn!(
173+
status = %response.status(),
174+
"Subgraph health check failed - HTTP error"
175+
);
176+
}
177+
178+
is_healthy
179+
}
180+
Err(e) => {
181+
tracing::warn!(
182+
error = %e,
183+
"Subgraph health check failed - connection error"
184+
);
185+
false
186+
}
187+
}
188+
}
189+
}
190+
191+
/// Simple mock for testing SubgraphClient behavior
192+
#[derive(Clone)]
193+
pub struct SimpleSubgraphMock {
194+
/// Controls whether allocation validation succeeds
195+
pub should_validate_allocation: bool,
196+
/// Controls whether the client appears healthy
197+
pub is_healthy: bool,
198+
}
199+
200+
impl SimpleSubgraphMock {
201+
/// Create a new mock with default settings
202+
pub fn new() -> Self {
203+
Self {
204+
should_validate_allocation: true,
205+
is_healthy: true,
206+
}
207+
}
208+
209+
/// Configure the mock to simulate allocation validation failures
210+
pub fn with_allocation_validation(mut self, should_validate: bool) -> Self {
211+
self.should_validate_allocation = should_validate;
212+
self
213+
}
214+
215+
/// Configure the mock to simulate health check results
216+
pub fn with_health_status(mut self, is_healthy: bool) -> Self {
217+
self.is_healthy = is_healthy;
218+
self
219+
}
220+
}
221+
222+
impl Default for SimpleSubgraphMock {
223+
fn default() -> Self {
224+
Self::new()
225+
}
226+
}
227+
228+
#[cfg(test)]
229+
mod tests {
230+
use super::*;
231+
use crate::agent::sender_accounts_manager::AllocationId;
232+
use thegraph_core::alloy::primitives::Address;
233+
234+
#[tokio::test]
235+
async fn test_mock_allocation_validation_success() {
236+
let mock = SimpleSubgraphMock::new().with_allocation_validation(true);
237+
let client = SimpleSubgraphClient::mock(mock);
238+
239+
let test_address = Address::from([0x42; 20]);
240+
let allocation_id = AllocationId::Legacy(test_address.into());
241+
let result = client.validate_allocation(&allocation_id).await.unwrap();
242+
243+
assert!(result);
244+
}
245+
246+
#[tokio::test]
247+
async fn test_mock_allocation_validation_failure() {
248+
let mock = SimpleSubgraphMock::new().with_allocation_validation(false);
249+
let client = SimpleSubgraphClient::mock(mock);
250+
251+
let test_address = Address::from([0x42; 20]);
252+
let allocation_id = AllocationId::Legacy(test_address.into());
253+
let result = client.validate_allocation(&allocation_id).await.unwrap();
254+
255+
assert!(!result);
256+
}
257+
258+
#[tokio::test]
259+
async fn test_mock_health_check() {
260+
let healthy_mock = SimpleSubgraphMock::new().with_health_status(true);
261+
let healthy_client = SimpleSubgraphClient::mock(healthy_mock);
262+
263+
let unhealthy_mock = SimpleSubgraphMock::new().with_health_status(false);
264+
let unhealthy_client = SimpleSubgraphClient::mock(unhealthy_mock);
265+
266+
assert!(healthy_client.is_healthy().await);
267+
assert!(!unhealthy_client.is_healthy().await);
268+
}
269+
}

0 commit comments

Comments
 (0)