1use crate::ast::{Document, Value};
7use crate::execution::FieldResolver;
8use crate::execution::{ExecutionContext, QueryExecutor};
9use crate::types::Schema;
10use anyhow::{anyhow, Result};
11use futures_util::{SinkExt, StreamExt};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::sync::Arc;
15use std::time::{Duration, Instant};
16use tokio::net::{TcpListener, TcpStream};
17use tokio::sync::{broadcast, RwLock};
18use tokio::time::interval;
19use tokio_tungstenite::{accept_async, tungstenite::Message, WebSocketStream};
20use tracing::{debug, error, info, warn};
21use uuid::Uuid;
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25#[serde(tag = "type")]
26pub enum SubscriptionMessage {
27 #[serde(rename = "connection_init")]
28 ConnectionInit { payload: Option<serde_json::Value> },
29 #[serde(rename = "connection_ack")]
30 ConnectionAck,
31 #[serde(rename = "connection_error")]
32 ConnectionError { payload: Option<serde_json::Value> },
33 #[serde(rename = "connection_terminate")]
34 ConnectionTerminate,
35 #[serde(rename = "start")]
36 Start {
37 id: String,
38 payload: SubscriptionPayload,
39 },
40 #[serde(rename = "data")]
41 Data {
42 id: String,
43 payload: serde_json::Value,
44 },
45 #[serde(rename = "error")]
46 Error {
47 id: String,
48 payload: serde_json::Value,
49 },
50 #[serde(rename = "complete")]
51 Complete { id: String },
52 #[serde(rename = "stop")]
53 Stop { id: String },
54 #[serde(rename = "ka")]
55 KeepAlive,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct SubscriptionPayload {
61 pub query: String,
62 pub variables: Option<HashMap<String, Value>>,
63 pub operation_name: Option<String>,
64}
65
66#[derive(Debug, Clone)]
68pub struct ActiveSubscription {
69 pub id: String,
70 pub connection_id: String,
71 pub document: Document,
72 pub context: ExecutionContext,
73 pub created_at: Instant,
74 pub last_execution: Option<Instant>,
75 pub execution_count: u64,
76}
77
78#[derive(Debug, Clone)]
80pub enum SubscriptionEvent {
81 TripleAdded {
82 subject: String,
83 predicate: String,
84 object: String,
85 },
86 TripleRemoved {
87 subject: String,
88 predicate: String,
89 object: String,
90 },
91 SubjectChanged {
92 subject: String,
93 },
94 PredicateChanged {
95 predicate: String,
96 },
97 BulkChange,
98}
99
100#[derive(Debug)]
102pub struct WebSocketConnection {
103 pub id: String,
104 pub socket: Arc<RwLock<WebSocketStream<TcpStream>>>,
105 pub subscriptions: Arc<RwLock<HashMap<String, ActiveSubscription>>>,
106 pub last_ping: Arc<RwLock<Instant>>,
107 pub authenticated: Arc<RwLock<bool>>,
108}
109
110#[derive(Debug, Clone)]
112pub enum AuthenticationMethod {
113 None,
115 BearerToken { valid_tokens: Vec<String> },
117 ApiKey { valid_keys: Vec<String> },
119 JWT { secret: String },
121}
122
123#[derive(Debug, Clone)]
125pub struct SubscriptionConfig {
126 pub max_subscriptions_per_connection: usize,
127 pub max_total_subscriptions: usize,
128 pub keepalive_interval: Duration,
129 pub subscription_timeout: Duration,
130 pub enable_authentication: bool,
131 pub max_execution_frequency: Duration,
132 pub auth_method: AuthenticationMethod,
133}
134
135impl Default for SubscriptionConfig {
136 fn default() -> Self {
137 Self {
138 max_subscriptions_per_connection: 10,
139 max_total_subscriptions: 1000,
140 keepalive_interval: Duration::from_secs(30),
141 subscription_timeout: Duration::from_secs(300),
142 enable_authentication: false,
143 max_execution_frequency: Duration::from_millis(100),
144 auth_method: AuthenticationMethod::None,
145 }
146 }
147}
148
149pub struct SubscriptionManager {
151 config: SubscriptionConfig,
152 connections: Arc<RwLock<HashMap<String, Arc<WebSocketConnection>>>>,
153 active_subscriptions: Arc<RwLock<HashMap<String, ActiveSubscription>>>,
154 event_sender: broadcast::Sender<SubscriptionEvent>,
155 schema: Arc<RwLock<Schema>>,
156 executor: Arc<QueryExecutor>,
157 resolvers: Arc<RwLock<HashMap<String, Arc<dyn FieldResolver>>>>,
158}
159
160impl SubscriptionManager {
161 pub fn new(config: SubscriptionConfig, schema: Schema, executor: QueryExecutor) -> Self {
162 let (event_sender, _) = broadcast::channel(1000);
163
164 Self {
165 config,
166 connections: Arc::new(RwLock::new(HashMap::new())),
167 active_subscriptions: Arc::new(RwLock::new(HashMap::new())),
168 event_sender,
169 schema: Arc::new(RwLock::new(schema)),
170 executor: Arc::new(executor),
171 resolvers: Arc::new(RwLock::new(HashMap::new())),
172 }
173 }
174
175 pub async fn start_server(&self, addr: &str) -> Result<()> {
177 let listener = TcpListener::bind(addr).await?;
178 info!("GraphQL subscription server listening on {}", addr);
179
180 self.start_keepalive_task().await;
182 self.start_cleanup_task().await;
183
184 while let Ok((stream, addr)) = listener.accept().await {
186 info!("New WebSocket connection from {}", addr);
187
188 let manager = self.clone();
189 tokio::spawn(async move {
190 if let Err(e) = manager.handle_connection(stream).await {
191 error!("Error handling WebSocket connection: {}", e);
192 }
193 });
194 }
195
196 Ok(())
197 }
198
199 async fn handle_connection(&self, stream: TcpStream) -> Result<()> {
201 let ws_stream = accept_async(stream).await?;
202 let connection_id = Uuid::new_v4().to_string();
203
204 let connection = Arc::new(WebSocketConnection {
205 id: connection_id.clone(),
206 socket: Arc::new(RwLock::new(ws_stream)),
207 subscriptions: Arc::new(RwLock::new(HashMap::new())),
208 last_ping: Arc::new(RwLock::new(Instant::now())),
209 authenticated: Arc::new(RwLock::new(!self.config.enable_authentication)),
210 });
211
212 {
214 let mut connections = self.connections.write().await;
215 connections.insert(connection_id.clone(), connection.clone());
216 }
217
218 let result = self.handle_connection_messages(connection.clone()).await;
220
221 self.cleanup_connection(&connection_id).await;
223
224 result
225 }
226
227 async fn handle_connection_messages(&self, connection: Arc<WebSocketConnection>) -> Result<()> {
229 let mut event_receiver = self.event_sender.subscribe();
230
231 loop {
232 tokio::select! {
233 message_result = async {
235 let mut socket = connection.socket.write().await;
236 socket.next().await
237 } => {
238 match message_result {
239 Some(Ok(msg)) => {
240 if let Err(e) = self.handle_websocket_message(&connection, msg).await {
241 error!("Error handling WebSocket message: {}", e);
242 break;
243 }
244 }
245 Some(Err(e)) => {
246 error!("WebSocket error: {}", e);
247 break;
248 }
249 None => {
250 debug!("WebSocket connection closed");
251 break;
252 }
253 }
254 }
255
256 event_result = event_receiver.recv() => {
258 match event_result {
259 Ok(event) => {
260 if let Err(e) = self.handle_subscription_event(&connection, &event).await {
261 error!("Error handling subscription event: {}", e);
262 }
263 }
264 Err(broadcast::error::RecvError::Lagged(_)) => {
265 warn!("Subscription event receiver lagged, continuing...");
266 }
267 Err(broadcast::error::RecvError::Closed) => {
268 info!("Event channel closed");
269 break;
270 }
271 }
272 }
273 }
274 }
275
276 Ok(())
277 }
278
279 async fn handle_websocket_message(
281 &self,
282 connection: &Arc<WebSocketConnection>,
283 message: Message,
284 ) -> Result<()> {
285 match message {
286 Message::Text(text) => {
287 let msg: SubscriptionMessage = serde_json::from_str(&text)?;
288 self.handle_subscription_message(connection, msg).await
289 }
290 Message::Close(_) => {
291 info!("WebSocket connection closed by client");
292 Ok(())
293 }
294 Message::Ping(data) => {
295 let mut socket = connection.socket.write().await;
296 socket.send(Message::Pong(data)).await?;
297 Ok(())
298 }
299 Message::Pong(_) => {
300 *connection.last_ping.write().await = Instant::now();
302 Ok(())
303 }
304 _ => Ok(()), }
306 }
307
308 async fn handle_subscription_message(
310 &self,
311 connection: &Arc<WebSocketConnection>,
312 message: SubscriptionMessage,
313 ) -> Result<()> {
314 match message {
315 SubscriptionMessage::ConnectionInit { payload } => {
316 let is_authenticated = if self.config.enable_authentication {
318 let auth_result = self.authenticate_connection(payload).await?;
319
320 if !auth_result {
321 let error = SubscriptionMessage::ConnectionError {
322 payload: Some(serde_json::json!({
323 "message": "Authentication failed",
324 "code": "AUTH_FAILED"
325 })),
326 };
327 self.send_message(connection, &error).await?;
328 return Ok(());
329 }
330 auth_result
331 } else {
332 true
334 };
335
336 *connection.authenticated.write().await = is_authenticated;
338
339 let ack = SubscriptionMessage::ConnectionAck;
341 self.send_message(connection, &ack).await?;
342 }
343
344 SubscriptionMessage::Start { id, payload } => {
345 if !*connection.authenticated.read().await {
346 let error = SubscriptionMessage::ConnectionError {
347 payload: Some(serde_json::json!({"message": "Not authenticated"})),
348 };
349 self.send_message(connection, &error).await?;
350 return Ok(());
351 }
352
353 self.start_subscription(connection, &id, payload).await?;
354 }
355
356 SubscriptionMessage::Stop { id } => {
357 self.stop_subscription(connection, &id).await?;
358 }
359
360 SubscriptionMessage::ConnectionTerminate => {
361 info!("Connection terminated by client");
362 return Err(anyhow!("Connection terminated"));
363 }
364
365 _ => {
366 warn!("Unexpected message type received");
367 }
368 }
369
370 Ok(())
371 }
372
373 async fn start_subscription(
375 &self,
376 connection: &Arc<WebSocketConnection>,
377 subscription_id: &str,
378 payload: SubscriptionPayload,
379 ) -> Result<()> {
380 let connection_sub_count = {
382 let connection_subscriptions = connection.subscriptions.read().await;
383 connection_subscriptions.len()
384 };
385
386 if connection_sub_count >= self.config.max_subscriptions_per_connection {
387 let error = SubscriptionMessage::Error {
388 id: subscription_id.to_string(),
389 payload: serde_json::json!({"message": "Too many subscriptions for this connection"}),
390 };
391 self.send_message(connection, &error).await?;
392 return Ok(());
393 }
394
395 let total_sub_count = {
396 let active_subscriptions = self.active_subscriptions.read().await;
397 active_subscriptions.len()
398 };
399
400 if total_sub_count >= self.config.max_total_subscriptions {
401 let error = SubscriptionMessage::Error {
402 id: subscription_id.to_string(),
403 payload: serde_json::json!({"message": "Server subscription limit reached"}),
404 };
405 self.send_message(connection, &error).await?;
406 return Ok(());
407 }
408
409 let document = match crate::parser::parse_document(&payload.query) {
411 Ok(doc) => doc,
412 Err(e) => {
413 let error = SubscriptionMessage::Error {
414 id: subscription_id.to_string(),
415 payload: serde_json::json!({"message": format!("Parse error: {}", e)}),
416 };
417 self.send_message(connection, &error).await?;
418 return Ok(());
419 }
420 };
421
422 let mut context = ExecutionContext::new();
424 if let Some(variables) = payload.variables {
425 context = context.with_variables(variables);
426 }
427 if let Some(operation_name) = payload.operation_name {
428 context = context.with_operation_name(operation_name);
429 }
430
431 let subscription = ActiveSubscription {
433 id: subscription_id.to_string(),
434 connection_id: connection.id.clone(),
435 document,
436 context,
437 created_at: Instant::now(),
438 last_execution: None,
439 execution_count: 0,
440 };
441
442 {
444 let mut connection_subscriptions = connection.subscriptions.write().await;
445 connection_subscriptions.insert(subscription_id.to_string(), subscription.clone());
446 }
447
448 {
449 let mut active_subscriptions = self.active_subscriptions.write().await;
450 active_subscriptions.insert(subscription_id.to_string(), subscription.clone());
451 }
452
453 self.execute_subscription(&subscription).await?;
455
456 info!(
457 "Started subscription {} for connection {}",
458 subscription_id, connection.id
459 );
460 Ok(())
461 }
462
463 async fn stop_subscription(
465 &self,
466 connection: &Arc<WebSocketConnection>,
467 subscription_id: &str,
468 ) -> Result<()> {
469 {
471 let mut connection_subscriptions = connection.subscriptions.write().await;
472 connection_subscriptions.remove(subscription_id);
473 }
474
475 {
477 let mut active_subscriptions = self.active_subscriptions.write().await;
478 active_subscriptions.remove(subscription_id);
479 }
480
481 let complete = SubscriptionMessage::Complete {
483 id: subscription_id.to_string(),
484 };
485 self.send_message(connection, &complete).await?;
486
487 info!(
488 "Stopped subscription {} for connection {}",
489 subscription_id, connection.id
490 );
491 Ok(())
492 }
493
494 async fn execute_subscription(&self, subscription: &ActiveSubscription) -> Result<()> {
496 if let Some(last_execution) = subscription.last_execution {
498 let elapsed = Instant::now().duration_since(last_execution);
499 if elapsed < self.config.max_execution_frequency {
500 return Ok(()); }
502 }
503
504 let result = self
505 .executor
506 .execute(&subscription.document, &subscription.context)
507 .await?;
508
509 let connection = {
511 let connections = self.connections.read().await;
512 connections.get(&subscription.connection_id).cloned()
513 };
514
515 if let Some(connection) = connection {
516 if result.has_errors() {
517 let error = SubscriptionMessage::Error {
518 id: subscription.id.clone(),
519 payload: serde_json::to_value(&result.errors)?,
520 };
521 self.send_message(&connection, &error).await?;
522 } else if let Some(data) = result.data {
523 let data_msg = SubscriptionMessage::Data {
524 id: subscription.id.clone(),
525 payload: data,
526 };
527 self.send_message(&connection, &data_msg).await?;
528 }
529
530 {
532 let mut active_subscriptions = self.active_subscriptions.write().await;
533 if let Some(sub) = active_subscriptions.get_mut(&subscription.id) {
534 sub.last_execution = Some(Instant::now());
535 sub.execution_count += 1;
536 }
537 }
538 }
539
540 Ok(())
541 }
542
543 async fn handle_subscription_event(
545 &self,
546 connection: &Arc<WebSocketConnection>,
547 event: &SubscriptionEvent,
548 ) -> Result<()> {
549 let subscriptions: Vec<ActiveSubscription> = {
550 let connection_subscriptions = connection.subscriptions.read().await;
551 connection_subscriptions.values().cloned().collect()
552 };
553
554 for subscription in subscriptions {
555 if self.should_execute_for_event(&subscription, event) {
557 if let Err(e) = self.execute_subscription(&subscription).await {
558 error!("Error executing subscription {}: {}", subscription.id, e);
559 }
560 }
561 }
562
563 Ok(())
564 }
565
566 fn should_execute_for_event(
568 &self,
569 subscription: &ActiveSubscription,
570 event: &SubscriptionEvent,
571 ) -> bool {
572 match event {
573 SubscriptionEvent::BulkChange => true, SubscriptionEvent::TripleAdded {
575 subject,
576 predicate,
577 object: _,
578 }
579 | SubscriptionEvent::TripleRemoved {
580 subject,
581 predicate,
582 object: _,
583 } => {
584 self.subscription_involves_resource(subscription, subject)
586 || self.subscription_involves_resource(subscription, predicate)
587 }
588 SubscriptionEvent::SubjectChanged { subject } => {
589 self.subscription_involves_resource(subscription, subject)
590 }
591 SubscriptionEvent::PredicateChanged { predicate } => {
592 self.subscription_involves_resource(subscription, predicate)
593 }
594 }
595 }
596
597 fn subscription_involves_resource(
599 &self,
600 _subscription: &ActiveSubscription,
601 _resource: &str,
602 ) -> bool {
603 true
609 }
610
611 async fn send_message(
613 &self,
614 connection: &Arc<WebSocketConnection>,
615 message: &SubscriptionMessage,
616 ) -> Result<()> {
617 let text = serde_json::to_string(message)?;
618 let mut socket = connection.socket.write().await;
619 socket.send(Message::Text(text.into())).await?;
620 Ok(())
621 }
622
623 pub fn trigger_event(&self, event: SubscriptionEvent) {
625 if let Err(e) = self.event_sender.send(event) {
626 debug!("No active subscription listeners: {}", e);
627 }
628 }
629
630 async fn start_keepalive_task(&self) {
632 let connections = Arc::clone(&self.connections);
633 let keepalive_interval = self.config.keepalive_interval;
634
635 tokio::spawn(async move {
636 let mut interval = interval(keepalive_interval);
637
638 loop {
639 interval.tick().await;
640
641 let connections_to_ping: Vec<Arc<WebSocketConnection>> = {
642 let connections = connections.read().await;
643 connections.values().cloned().collect()
644 };
645
646 for connection in connections_to_ping {
647 let keepalive = SubscriptionMessage::KeepAlive;
648 if let Ok(text) = serde_json::to_string(&keepalive) {
649 let mut socket = connection.socket.write().await;
650 if socket.send(Message::Text(text.into())).await.is_err() {
651 debug!("Failed to send keepalive to connection {}", connection.id);
652 }
653 }
654 }
655 }
656 });
657 }
658
659 async fn start_cleanup_task(&self) {
661 let active_subscriptions = Arc::clone(&self.active_subscriptions);
662 let connections = Arc::clone(&self.connections);
663 let timeout = self.config.subscription_timeout;
664
665 tokio::spawn(async move {
666 let mut interval = interval(Duration::from_secs(60)); loop {
669 interval.tick().await;
670
671 let now = Instant::now();
672 let mut expired_subscriptions = Vec::new();
673
674 {
675 let subscriptions = active_subscriptions.read().await;
676 for (id, subscription) in subscriptions.iter() {
677 if now.duration_since(subscription.created_at) > timeout {
678 expired_subscriptions.push(id.clone());
679 }
680 }
681 }
682
683 for subscription_id in expired_subscriptions {
684 info!("Cleaning up expired subscription: {}", subscription_id);
685
686 let connection_id = {
688 let mut subscriptions = active_subscriptions.write().await;
689 subscriptions
690 .remove(&subscription_id)
691 .map(|sub| sub.connection_id)
692 };
693
694 if let Some(connection_id) = connection_id {
696 let connection_opt = {
697 let connections = connections.read().await;
698 connections.get(&connection_id).cloned()
699 };
700
701 if let Some(connection) = connection_opt {
702 {
703 let mut connection_subscriptions =
704 connection.subscriptions.write().await;
705 connection_subscriptions.remove(&subscription_id);
706 }
707
708 let complete = SubscriptionMessage::Complete {
709 id: subscription_id.clone(),
710 };
711
712 if let Ok(text) = serde_json::to_string(&complete) {
713 let mut socket = connection.socket.write().await;
714 let _ = socket.send(Message::Text(text.into())).await;
715 }
716 }
717 }
718 }
719 }
720 });
721 }
722
723 async fn cleanup_connection(&self, connection_id: &str) {
725 info!("Cleaning up connection: {}", connection_id);
726
727 {
729 let mut connections = self.connections.write().await;
730 connections.remove(connection_id);
731 }
732
733 let subscription_ids: Vec<String> = {
735 let active_subscriptions = self.active_subscriptions.read().await;
736 active_subscriptions
737 .iter()
738 .filter(|(_, sub)| sub.connection_id == connection_id)
739 .map(|(id, _)| id.clone())
740 .collect()
741 };
742
743 {
744 let mut active_subscriptions = self.active_subscriptions.write().await;
745 for subscription_id in subscription_ids {
746 active_subscriptions.remove(&subscription_id);
747 info!(
748 "Removed subscription {} for connection {}",
749 subscription_id, connection_id
750 );
751 }
752 }
753 }
754
755 pub async fn get_stats(&self) -> SubscriptionStats {
757 let connections = self.connections.read().await;
758 let active_subscriptions = self.active_subscriptions.read().await;
759
760 SubscriptionStats {
761 total_connections: connections.len(),
762 total_subscriptions: active_subscriptions.len(),
763 avg_subscriptions_per_connection: if connections.is_empty() {
764 0.0
765 } else {
766 active_subscriptions.len() as f64 / connections.len() as f64
767 },
768 }
769 }
770
771 async fn authenticate_connection(&self, payload: Option<serde_json::Value>) -> Result<bool> {
773 if !self.config.enable_authentication {
774 return Ok(true);
775 }
776
777 let payload = match payload {
778 Some(p) => p,
779 None => {
780 debug!("No authentication payload provided");
781 return Ok(false);
782 }
783 };
784
785 match &self.config.auth_method {
786 AuthenticationMethod::None => Ok(true),
787
788 AuthenticationMethod::BearerToken { valid_tokens } => {
789 if let Some(token) = payload
790 .get("authorization")
791 .or_else(|| payload.get("Authorization"))
792 .and_then(|v| v.as_str())
793 {
794 let token = token.strip_prefix("Bearer ").unwrap_or(token);
796
797 if valid_tokens.contains(&token.to_string()) {
798 info!("WebSocket connection authenticated with Bearer token");
799 Ok(true)
800 } else {
801 warn!("Invalid Bearer token provided");
802 Ok(false)
803 }
804 } else {
805 warn!("No authorization token found in payload");
806 Ok(false)
807 }
808 }
809
810 AuthenticationMethod::ApiKey { valid_keys } => {
811 if let Some(api_key) = payload
812 .get("apiKey")
813 .or_else(|| payload.get("api_key"))
814 .and_then(|v| v.as_str())
815 {
816 if valid_keys.contains(&api_key.to_string()) {
817 info!("WebSocket connection authenticated with API key");
818 Ok(true)
819 } else {
820 warn!("Invalid API key provided");
821 Ok(false)
822 }
823 } else {
824 warn!("No API key found in payload");
825 Ok(false)
826 }
827 }
828
829 AuthenticationMethod::JWT { secret: _ } => {
830 if let Some(jwt) = payload
832 .get("jwt")
833 .or_else(|| payload.get("token"))
834 .and_then(|v| v.as_str())
835 {
836 let parts: Vec<&str> = jwt.split('.').collect();
838 if parts.len() == 3 {
839 info!("WebSocket connection authenticated with JWT (basic validation)");
844 Ok(true)
845 } else {
846 warn!("Invalid JWT format");
847 Ok(false)
848 }
849 } else {
850 warn!("No JWT token found in payload");
851 Ok(false)
852 }
853 }
854 }
855 }
856}
857
858#[derive(Debug, Clone, Serialize)]
860pub struct SubscriptionStats {
861 pub total_connections: usize,
862 pub total_subscriptions: usize,
863 pub avg_subscriptions_per_connection: f64,
864}
865
866impl Clone for SubscriptionManager {
867 fn clone(&self) -> Self {
868 Self {
869 config: self.config.clone(),
870 connections: Arc::clone(&self.connections),
871 active_subscriptions: Arc::clone(&self.active_subscriptions),
872 event_sender: self.event_sender.clone(),
873 schema: Arc::clone(&self.schema),
874 executor: Arc::clone(&self.executor),
875 resolvers: Arc::clone(&self.resolvers),
876 }
877 }
878}
879
880#[cfg(test)]
881mod tests {
882 use super::*;
883 use crate::types::{BuiltinScalars, FieldType, ObjectType, Schema};
884
885 fn create_test_schema() -> Schema {
886 let mut schema = Schema::new();
887
888 let query_type = ObjectType::new("Query".to_string()).with_field(
889 "hello".to_string(),
890 FieldType::new(
891 "hello".to_string(),
892 crate::types::GraphQLType::Scalar(BuiltinScalars::string()),
893 ),
894 );
895
896 schema.add_type(crate::types::GraphQLType::Object(query_type));
897 schema.set_query_type("Query".to_string());
898
899 schema
900 }
901
902 #[test]
903 fn test_subscription_config() {
904 let config = SubscriptionConfig::default();
905 assert_eq!(config.max_subscriptions_per_connection, 10);
906 assert_eq!(config.max_total_subscriptions, 1000);
907 assert!(!config.enable_authentication);
908 }
909
910 #[test]
911 fn test_subscription_message_serialization() {
912 let msg = SubscriptionMessage::ConnectionInit {
913 payload: Some(serde_json::json!({"auth": "token"})),
914 };
915
916 let serialized = serde_json::to_string(&msg).expect("should succeed");
917 let deserialized: SubscriptionMessage =
918 serde_json::from_str(&serialized).expect("should succeed");
919
920 matches!(deserialized, SubscriptionMessage::ConnectionInit { .. });
921 }
922
923 #[test]
924 fn test_subscription_payload() {
925 let payload = SubscriptionPayload {
926 query: "subscription { hello }".to_string(),
927 variables: Some(HashMap::new()),
928 operation_name: None,
929 };
930
931 assert_eq!(payload.query, "subscription { hello }");
932 assert!(payload.variables.is_some());
933 assert!(payload.operation_name.is_none());
934 }
935
936 #[tokio::test]
937 async fn test_subscription_manager_creation() {
938 let config = SubscriptionConfig::default();
939 let schema = create_test_schema();
940 let executor = QueryExecutor::new(schema.clone());
941
942 let manager = SubscriptionManager::new(config, schema, executor);
943 let stats = manager.get_stats().await;
944
945 assert_eq!(stats.total_connections, 0);
946 assert_eq!(stats.total_subscriptions, 0);
947 assert_eq!(stats.avg_subscriptions_per_connection, 0.0);
948 }
949
950 #[test]
951 fn test_subscription_event_types() {
952 let event = SubscriptionEvent::TripleAdded {
953 subject: "http://example.org/person1".to_string(),
954 predicate: "http://xmlns.com/foaf/0.1/name".to_string(),
955 object: "John Doe".to_string(),
956 };
957
958 matches!(event, SubscriptionEvent::TripleAdded { .. });
959 }
960
961 #[test]
962 fn test_active_subscription() {
963 let subscription = ActiveSubscription {
964 id: "sub1".to_string(),
965 connection_id: "conn1".to_string(),
966 document: crate::ast::Document {
967 definitions: vec![],
968 },
969 context: ExecutionContext::new(),
970 created_at: Instant::now(),
971 last_execution: None,
972 execution_count: 0,
973 };
974
975 assert_eq!(subscription.id, "sub1");
976 assert_eq!(subscription.connection_id, "conn1");
977 assert_eq!(subscription.execution_count, 0);
978 }
979
980 #[tokio::test]
987 async fn test_lock_not_poisoned_after_writer_panic() {
988 let config = SubscriptionConfig::default();
989 let schema = create_test_schema();
990 let executor = QueryExecutor::new(schema.clone());
991 let manager = Arc::new(SubscriptionManager::new(config, schema, executor));
992
993 let manager_clone = Arc::clone(&manager);
995 let handle = tokio::spawn(async move {
996 let _guard = manager_clone.active_subscriptions.write().await;
997 panic!("simulated writer panic while holding the lock");
998 });
999 assert!(handle.await.is_err());
1001
1002 let stats = manager.get_stats().await;
1004 assert_eq!(stats.total_subscriptions, 0);
1005
1006 {
1007 let mut active = manager.active_subscriptions.write().await;
1008 active.insert(
1009 "sub-after-panic".to_string(),
1010 ActiveSubscription {
1011 id: "sub-after-panic".to_string(),
1012 connection_id: "conn-after-panic".to_string(),
1013 document: crate::ast::Document {
1014 definitions: vec![],
1015 },
1016 context: ExecutionContext::new(),
1017 created_at: Instant::now(),
1018 last_execution: None,
1019 execution_count: 0,
1020 },
1021 );
1022 }
1023
1024 let stats = manager.get_stats().await;
1025 assert_eq!(stats.total_subscriptions, 1);
1026 }
1027}