mockforge_graphql/
executor.rs1use async_graphql::http::GraphQLPlaygroundConfig;
4use async_graphql_axum::{GraphQLRequest, GraphQLResponse, GraphQLSubscription};
5use axum::{
6 extract::State,
7 response::{Html, IntoResponse},
8 routing::{get, post},
9 Router,
10};
11use mockforge_observability::get_global_registry;
12use std::sync::Arc;
13use std::time::Instant;
14use tokio::net::TcpListener;
15
16use crate::GraphQLSchema;
17
18pub struct GraphQLExecutor {
20 schema: Arc<GraphQLSchema>,
21}
22
23impl GraphQLExecutor {
24 pub fn new(schema: GraphQLSchema) -> Self {
26 Self {
27 schema: Arc::new(schema),
28 }
29 }
30
31 pub async fn execute(&self, request: GraphQLRequest) -> GraphQLResponse {
33 let response = self.schema.schema().execute(request.into_inner()).await;
34 response.into()
35 }
36
37 pub fn schema(&self) -> &GraphQLSchema {
39 &self.schema
40 }
41}
42
43pub async fn start_graphql_server(
45 port: u16,
46 latency_profile: Option<mockforge_foundation::latency::LatencyProfile>,
47) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
48 let addr = mockforge_core::wildcard_socket_addr(port);
49 tracing::info!("GraphQL server listening on {}", addr);
50
51 let app = create_graphql_router(latency_profile).await?;
52
53 let listener = TcpListener::bind(addr).await?;
54 axum::serve(listener, app).await?;
55
56 Ok(())
57}
58
59pub async fn create_graphql_router(
61 latency_profile: Option<mockforge_foundation::latency::LatencyProfile>,
62) -> Result<Router, Box<dyn std::error::Error + Send + Sync>> {
63 let schema = GraphQLSchema::generate_basic_schema();
65 let schema_for_subscriptions = schema.schema().clone();
70 let executor = GraphQLExecutor::new(schema);
71
72 let mut app = Router::new()
73 .route("/graphql", post(graphql_handler))
74 .route("/graphql", get(graphql_playground))
75 .route_service("/graphql/ws", GraphQLSubscription::new(schema_for_subscriptions))
83 .with_state(Arc::new(executor));
84
85 if let Some(profile) = latency_profile {
87 let latency_injector =
88 mockforge_foundation::latency::LatencyInjector::new(profile, Default::default());
89 app = app.layer(axum::middleware::from_fn(
90 move |req: axum::http::Request<axum::body::Body>, next: axum::middleware::Next| {
91 let injector = latency_injector.clone();
92 async move {
93 let _ = injector.inject_latency(&[]).await;
94 next.run(req).await
95 }
96 },
97 ));
98 }
99
100 Ok(app)
101}
102
103async fn graphql_handler(
105 State(executor): State<Arc<GraphQLExecutor>>,
106 req: GraphQLRequest,
107) -> GraphQLResponse {
108 let start = Instant::now();
109 let registry = get_global_registry();
110
111 registry.increment_in_flight("graphql");
113
114 let response = executor.execute(req).await;
116
117 registry.decrement_in_flight("graphql");
119
120 let duration = start.elapsed().as_secs_f64();
121 let status = if response.0.is_ok() { 200 } else { 400 };
122
123 registry.record_graphql_request("query", status, duration);
125
126 if !response.0.is_ok() {
127 registry.record_error("graphql", "graphql_error");
128 }
129
130 response
131}
132
133async fn graphql_playground() -> impl IntoResponse {
135 Html(async_graphql::http::playground_source(
136 GraphQLPlaygroundConfig::new("/graphql")
137 .title("MockForge GraphQL Playground")
138 .subscription_endpoint("/graphql/ws"),
139 ))
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_graphql_executor_new() {
148 let schema = GraphQLSchema::new();
149 let executor = GraphQLExecutor::new(schema);
150
151 assert!(!executor.schema.schema().sdl().is_empty());
153 }
154
155 #[test]
156 fn test_graphql_executor_schema_getter() {
157 let schema = GraphQLSchema::new();
158 let executor = GraphQLExecutor::new(schema);
159
160 let retrieved_schema = executor.schema();
161 assert!(!retrieved_schema.schema().sdl().is_empty());
162 }
163
164 #[tokio::test]
165 async fn test_graphql_executor_can_execute() {
166 let schema = GraphQLSchema::new();
167 let executor = GraphQLExecutor::new(schema);
168
169 assert!(executor.schema().schema().sdl().contains("Query"));
171 }
172
173 #[tokio::test]
174 async fn test_create_graphql_router_no_latency() {
175 let result = create_graphql_router(None).await;
176 assert!(result.is_ok());
177 }
178
179 #[tokio::test]
180 async fn test_create_graphql_router_with_latency() {
181 let latency = mockforge_foundation::latency::LatencyProfile::default();
182 let result = create_graphql_router(Some(latency)).await;
183 assert!(result.is_ok());
184 }
185
186 #[tokio::test]
187 async fn test_create_graphql_router_with_custom_latency() {
188 let latency = mockforge_foundation::latency::LatencyProfile::new(100, 25);
189 let result = create_graphql_router(Some(latency)).await;
190 assert!(result.is_ok());
191 }
192
193 #[tokio::test]
194 async fn test_graphql_playground_returns_html() {
195 let response = graphql_playground().await;
196 let _html_response = response.into_response();
198 }
199
200 #[test]
201 fn test_graphql_handler_setup() {
202 let schema = GraphQLSchema::new();
203 let executor = Arc::new(GraphQLExecutor::new(schema));
204
205 assert_eq!(Arc::strong_count(&executor), 1);
207 }
208
209 #[test]
210 fn test_executor_arc_shared_ownership() {
211 let schema = GraphQLSchema::new();
212 let executor = Arc::new(GraphQLExecutor::new(schema));
213
214 let executor_clone = Arc::clone(&executor);
215 assert_eq!(Arc::strong_count(&executor), 2);
216
217 drop(executor_clone);
218 assert_eq!(Arc::strong_count(&executor), 1);
219 }
220
221 #[test]
222 fn test_executor_schema_contains_query_type() {
223 let schema = GraphQLSchema::new();
224 let executor = GraphQLExecutor::new(schema);
225
226 let sdl = executor.schema().schema().sdl();
227 assert!(sdl.contains("Query"));
228 }
229
230 #[test]
231 fn test_executor_schema_contains_user_type() {
232 let schema = GraphQLSchema::new();
233 let executor = GraphQLExecutor::new(schema);
234
235 let sdl = executor.schema().schema().sdl();
236 assert!(sdl.contains("User"));
237 }
238
239 #[test]
240 fn test_executor_schema_contains_post_type() {
241 let schema = GraphQLSchema::new();
242 let executor = GraphQLExecutor::new(schema);
243
244 let sdl = executor.schema().schema().sdl();
245 assert!(sdl.contains("Post"));
246 }
247}