mockforge_core/
lifecycle.rs

1//! Lifecycle hooks for extensibility
2//!
3//! This module provides a comprehensive lifecycle hook system that allows
4//! extensions to hook into various lifecycle events in MockForge:
5//!
6//! - Request/Response lifecycle: before_request, after_response
7//! - Server lifecycle: on_startup, on_shutdown
8//! - Mock lifecycle: on_mock_created, on_mock_updated, on_mock_deleted
9//!
10//! # Examples
11//!
12//! ```rust
13//! use mockforge_core::lifecycle::{LifecycleHook, RequestContext, ResponseContext};
14//! use async_trait::async_trait;
15//!
16//! struct LoggingHook;
17//!
18//! #[async_trait]
19//! impl LifecycleHook for LoggingHook {
20//!     async fn before_request(&self, ctx: &RequestContext) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
21//!         println!("Request: {} {}", ctx.method, ctx.path);
22//!         Ok(())
23//!     }
24//!
25//!     async fn after_response(&self, ctx: &ResponseContext) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
26//!         println!("Response: {} in {}ms", ctx.status_code, ctx.response_time_ms);
27//!         Ok(())
28//!     }
29//! }
30//! ```
31
32use async_trait::async_trait;
33use serde::{Deserialize, Serialize};
34use std::collections::HashMap;
35use std::sync::Arc;
36use tokio::sync::RwLock;
37
38/// Request context for lifecycle hooks
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct RequestContext {
41    /// HTTP method
42    pub method: String,
43    /// Request path
44    pub path: String,
45    /// Request headers
46    pub headers: HashMap<String, String>,
47    /// Query parameters
48    pub query_params: HashMap<String, String>,
49    /// Request body (if available)
50    pub body: Option<Vec<u8>>,
51    /// Request ID for tracking
52    pub request_id: String,
53    /// Timestamp when request was received
54    pub timestamp: chrono::DateTime<chrono::Utc>,
55}
56
57/// Response context for lifecycle hooks
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ResponseContext {
60    /// Request context
61    pub request: RequestContext,
62    /// HTTP status code
63    pub status_code: u16,
64    /// Response headers
65    pub headers: HashMap<String, String>,
66    /// Response body (if available)
67    pub body: Option<Vec<u8>>,
68    /// Response time in milliseconds
69    pub response_time_ms: u64,
70    /// Timestamp when response was sent
71    pub timestamp: chrono::DateTime<chrono::Utc>,
72}
73
74/// Mock lifecycle event
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub enum MockLifecycleEvent {
77    /// Mock was created
78    Created {
79        /// Mock ID
80        id: String,
81        /// Mock name
82        name: String,
83        /// Mock configuration (serialized)
84        config: serde_json::Value,
85    },
86    /// Mock was updated
87    Updated {
88        /// Mock ID
89        id: String,
90        /// Mock name
91        name: String,
92        /// Updated mock configuration (serialized)
93        config: serde_json::Value,
94    },
95    /// Mock was deleted
96    Deleted {
97        /// Mock ID
98        id: String,
99        /// Mock name
100        name: String,
101    },
102    /// Mock was enabled
103    Enabled {
104        /// Mock ID
105        id: String,
106    },
107    /// Mock was disabled
108    Disabled {
109        /// Mock ID
110        id: String,
111    },
112}
113
114/// Server lifecycle event
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub enum ServerLifecycleEvent {
117    /// Server is starting up
118    Startup {
119        /// Server configuration
120        config: serde_json::Value,
121    },
122    /// Server is shutting down
123    Shutdown {
124        /// Shutdown reason
125        reason: String,
126    },
127}
128
129/// Comprehensive lifecycle hook trait
130///
131/// Implement this trait to hook into various lifecycle events in MockForge.
132/// All methods have default no-op implementations, so you only need to
133/// implement the hooks you care about.
134#[async_trait]
135pub trait LifecycleHook: Send + Sync {
136    /// Called before a request is processed
137    ///
138    /// This hook is called after the request is received but before it's
139    /// matched against mocks or processed. You can use this to:
140    /// - Log requests
141    /// - Modify request headers
142    /// - Add request metadata
143    /// - Perform authentication checks
144    async fn before_request(
145        &self,
146        _ctx: &RequestContext,
147    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
148        Ok(())
149    }
150
151    /// Called after a response is generated
152    ///
153    /// This hook is called after the response is generated but before it's
154    /// sent to the client. You can use this to:
155    /// - Log responses
156    /// - Modify response headers
157    /// - Add response metadata
158    /// - Perform response validation
159    async fn after_response(
160        &self,
161        _ctx: &ResponseContext,
162    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
163        Ok(())
164    }
165
166    /// Called when a mock is created
167    async fn on_mock_created(
168        &self,
169        _event: &MockLifecycleEvent,
170    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
171        Ok(())
172    }
173
174    /// Called when a mock is updated
175    async fn on_mock_updated(
176        &self,
177        _event: &MockLifecycleEvent,
178    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
179        Ok(())
180    }
181
182    /// Called when a mock is deleted
183    async fn on_mock_deleted(
184        &self,
185        _event: &MockLifecycleEvent,
186    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
187        Ok(())
188    }
189
190    /// Called when a mock is enabled or disabled
191    async fn on_mock_state_changed(
192        &self,
193        _event: &MockLifecycleEvent,
194    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
195        Ok(())
196    }
197
198    /// Called when the server starts up
199    async fn on_startup(
200        &self,
201        _event: &ServerLifecycleEvent,
202    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
203        Ok(())
204    }
205
206    /// Called when the server shuts down
207    async fn on_shutdown(
208        &self,
209        _event: &ServerLifecycleEvent,
210    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
211        Ok(())
212    }
213}
214
215/// Lifecycle hook registry
216///
217/// Manages all registered lifecycle hooks and provides methods to invoke them.
218pub struct LifecycleHookRegistry {
219    hooks: Arc<RwLock<Vec<Arc<dyn LifecycleHook>>>>,
220}
221
222impl LifecycleHookRegistry {
223    /// Create a new lifecycle hook registry
224    pub fn new() -> Self {
225        Self {
226            hooks: Arc::new(RwLock::new(Vec::new())),
227        }
228    }
229
230    /// Register a lifecycle hook
231    pub async fn register_hook(&self, hook: Arc<dyn LifecycleHook>) {
232        let mut hooks = self.hooks.write().await;
233        hooks.push(hook);
234    }
235
236    /// Invoke all registered before_request hooks
237    pub async fn invoke_before_request(&self, ctx: &RequestContext) {
238        let hooks = self.hooks.read().await;
239        for hook in hooks.iter() {
240            if let Err(e) = hook.before_request(ctx).await {
241                tracing::error!("Error in before_request hook: {}", e);
242            }
243        }
244    }
245
246    /// Invoke all registered after_response hooks
247    pub async fn invoke_after_response(&self, ctx: &ResponseContext) {
248        let hooks = self.hooks.read().await;
249        for hook in hooks.iter() {
250            if let Err(e) = hook.after_response(ctx).await {
251                tracing::error!("Error in after_response hook: {}", e);
252            }
253        }
254    }
255
256    /// Invoke all registered on_mock_created hooks
257    pub async fn invoke_mock_created(&self, event: &MockLifecycleEvent) {
258        let hooks = self.hooks.read().await;
259        for hook in hooks.iter() {
260            if let Err(e) = hook.on_mock_created(event).await {
261                tracing::error!("Error in on_mock_created hook: {}", e);
262            }
263        }
264    }
265
266    /// Invoke all registered on_mock_updated hooks
267    pub async fn invoke_mock_updated(&self, event: &MockLifecycleEvent) {
268        let hooks = self.hooks.read().await;
269        for hook in hooks.iter() {
270            if let Err(e) = hook.on_mock_updated(event).await {
271                tracing::error!("Error in on_mock_updated hook: {}", e);
272            }
273        }
274    }
275
276    /// Invoke all registered on_mock_deleted hooks
277    pub async fn invoke_mock_deleted(&self, event: &MockLifecycleEvent) {
278        let hooks = self.hooks.read().await;
279        for hook in hooks.iter() {
280            if let Err(e) = hook.on_mock_deleted(event).await {
281                tracing::error!("Error in on_mock_deleted hook: {}", e);
282            }
283        }
284    }
285
286    /// Invoke all registered on_mock_state_changed hooks
287    pub async fn invoke_mock_state_changed(&self, event: &MockLifecycleEvent) {
288        let hooks = self.hooks.read().await;
289        for hook in hooks.iter() {
290            if let Err(e) = hook.on_mock_state_changed(event).await {
291                tracing::error!("Error in on_mock_state_changed hook: {}", e);
292            }
293        }
294    }
295
296    /// Invoke all registered on_startup hooks
297    pub async fn invoke_startup(&self, event: &ServerLifecycleEvent) {
298        let hooks = self.hooks.read().await;
299        for hook in hooks.iter() {
300            if let Err(e) = hook.on_startup(event).await {
301                tracing::error!("Error in on_startup hook: {}", e);
302            }
303        }
304    }
305
306    /// Invoke all registered on_shutdown hooks
307    pub async fn invoke_shutdown(&self, event: &ServerLifecycleEvent) {
308        let hooks = self.hooks.read().await;
309        for hook in hooks.iter() {
310            if let Err(e) = hook.on_shutdown(event).await {
311                tracing::error!("Error in on_shutdown hook: {}", e);
312            }
313        }
314    }
315}
316
317impl Default for LifecycleHookRegistry {
318    fn default() -> Self {
319        Self::new()
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326    use serde_json::json;
327    use std::sync::atomic::{AtomicUsize, Ordering};
328
329    fn create_request_context() -> RequestContext {
330        RequestContext {
331            method: "GET".to_string(),
332            path: "/test".to_string(),
333            headers: {
334                let mut h = HashMap::new();
335                h.insert("Content-Type".to_string(), "application/json".to_string());
336                h
337            },
338            query_params: {
339                let mut q = HashMap::new();
340                q.insert("page".to_string(), "1".to_string());
341                q
342            },
343            body: Some(b"test body".to_vec()),
344            request_id: "test-123".to_string(),
345            timestamp: chrono::Utc::now(),
346        }
347    }
348
349    fn create_response_context() -> ResponseContext {
350        ResponseContext {
351            request: create_request_context(),
352            status_code: 200,
353            headers: {
354                let mut h = HashMap::new();
355                h.insert("Content-Type".to_string(), "application/json".to_string());
356                h
357            },
358            body: Some(b"response body".to_vec()),
359            response_time_ms: 50,
360            timestamp: chrono::Utc::now(),
361        }
362    }
363
364    // RequestContext tests
365    #[test]
366    fn test_request_context_debug() {
367        let ctx = create_request_context();
368        let debug = format!("{:?}", ctx);
369        assert!(debug.contains("RequestContext"));
370        assert!(debug.contains("GET"));
371        assert!(debug.contains("/test"));
372    }
373
374    #[test]
375    fn test_request_context_clone() {
376        let ctx = create_request_context();
377        let cloned = ctx.clone();
378        assert_eq!(cloned.method, ctx.method);
379        assert_eq!(cloned.path, ctx.path);
380        assert_eq!(cloned.request_id, ctx.request_id);
381    }
382
383    #[test]
384    fn test_request_context_serialize_deserialize() {
385        let ctx = create_request_context();
386        let json = serde_json::to_string(&ctx).unwrap();
387        assert!(json.contains("GET"));
388        assert!(json.contains("/test"));
389
390        let deserialized: RequestContext = serde_json::from_str(&json).unwrap();
391        assert_eq!(deserialized.method, ctx.method);
392        assert_eq!(deserialized.path, ctx.path);
393    }
394
395    // ResponseContext tests
396    #[test]
397    fn test_response_context_debug() {
398        let ctx = create_response_context();
399        let debug = format!("{:?}", ctx);
400        assert!(debug.contains("ResponseContext"));
401        assert!(debug.contains("200"));
402    }
403
404    #[test]
405    fn test_response_context_clone() {
406        let ctx = create_response_context();
407        let cloned = ctx.clone();
408        assert_eq!(cloned.status_code, ctx.status_code);
409        assert_eq!(cloned.response_time_ms, ctx.response_time_ms);
410    }
411
412    #[test]
413    fn test_response_context_serialize_deserialize() {
414        let ctx = create_response_context();
415        let json = serde_json::to_string(&ctx).unwrap();
416        assert!(json.contains("200"));
417
418        let deserialized: ResponseContext = serde_json::from_str(&json).unwrap();
419        assert_eq!(deserialized.status_code, ctx.status_code);
420    }
421
422    // MockLifecycleEvent tests
423    #[test]
424    fn test_mock_lifecycle_event_created() {
425        let event = MockLifecycleEvent::Created {
426            id: "mock-1".to_string(),
427            name: "Test Mock".to_string(),
428            config: json!({"endpoint": "/api/test"}),
429        };
430
431        let debug = format!("{:?}", event);
432        assert!(debug.contains("Created"));
433        assert!(debug.contains("mock-1"));
434    }
435
436    #[test]
437    fn test_mock_lifecycle_event_updated() {
438        let event = MockLifecycleEvent::Updated {
439            id: "mock-1".to_string(),
440            name: "Updated Mock".to_string(),
441            config: json!({"endpoint": "/api/updated"}),
442        };
443
444        let debug = format!("{:?}", event);
445        assert!(debug.contains("Updated"));
446    }
447
448    #[test]
449    fn test_mock_lifecycle_event_deleted() {
450        let event = MockLifecycleEvent::Deleted {
451            id: "mock-1".to_string(),
452            name: "Deleted Mock".to_string(),
453        };
454
455        let debug = format!("{:?}", event);
456        assert!(debug.contains("Deleted"));
457    }
458
459    #[test]
460    fn test_mock_lifecycle_event_enabled() {
461        let event = MockLifecycleEvent::Enabled {
462            id: "mock-1".to_string(),
463        };
464
465        let debug = format!("{:?}", event);
466        assert!(debug.contains("Enabled"));
467    }
468
469    #[test]
470    fn test_mock_lifecycle_event_disabled() {
471        let event = MockLifecycleEvent::Disabled {
472            id: "mock-1".to_string(),
473        };
474
475        let debug = format!("{:?}", event);
476        assert!(debug.contains("Disabled"));
477    }
478
479    #[test]
480    fn test_mock_lifecycle_event_clone() {
481        let event = MockLifecycleEvent::Created {
482            id: "mock-1".to_string(),
483            name: "Test Mock".to_string(),
484            config: json!({}),
485        };
486        let cloned = event.clone();
487        if let MockLifecycleEvent::Created { id, .. } = cloned {
488            assert_eq!(id, "mock-1");
489        }
490    }
491
492    #[test]
493    fn test_mock_lifecycle_event_serialize_deserialize() {
494        let event = MockLifecycleEvent::Created {
495            id: "mock-1".to_string(),
496            name: "Test Mock".to_string(),
497            config: json!({"key": "value"}),
498        };
499        let json = serde_json::to_string(&event).unwrap();
500        let deserialized: MockLifecycleEvent = serde_json::from_str(&json).unwrap();
501        if let MockLifecycleEvent::Created { id, name, .. } = deserialized {
502            assert_eq!(id, "mock-1");
503            assert_eq!(name, "Test Mock");
504        }
505    }
506
507    // ServerLifecycleEvent tests
508    #[test]
509    fn test_server_lifecycle_event_startup() {
510        let event = ServerLifecycleEvent::Startup {
511            config: json!({"port": 8080}),
512        };
513        let debug = format!("{:?}", event);
514        assert!(debug.contains("Startup"));
515    }
516
517    #[test]
518    fn test_server_lifecycle_event_shutdown() {
519        let event = ServerLifecycleEvent::Shutdown {
520            reason: "User requested".to_string(),
521        };
522        let debug = format!("{:?}", event);
523        assert!(debug.contains("Shutdown"));
524        assert!(debug.contains("User requested"));
525    }
526
527    #[test]
528    fn test_server_lifecycle_event_clone() {
529        let event = ServerLifecycleEvent::Startup { config: json!({}) };
530        let cloned = event.clone();
531        if let ServerLifecycleEvent::Startup { .. } = cloned {
532            // Clone successful
533        } else {
534            panic!("Clone failed");
535        }
536    }
537
538    #[test]
539    fn test_server_lifecycle_event_serialize_deserialize() {
540        let event = ServerLifecycleEvent::Shutdown {
541            reason: "Test shutdown".to_string(),
542        };
543        let json = serde_json::to_string(&event).unwrap();
544        let deserialized: ServerLifecycleEvent = serde_json::from_str(&json).unwrap();
545        if let ServerLifecycleEvent::Shutdown { reason } = deserialized {
546            assert_eq!(reason, "Test shutdown");
547        }
548    }
549
550    // LifecycleHookRegistry tests
551    #[tokio::test]
552    async fn test_registry_new() {
553        let registry = LifecycleHookRegistry::new();
554        // Registry should be created successfully
555        let _ = registry;
556    }
557
558    #[tokio::test]
559    async fn test_registry_default() {
560        let registry = LifecycleHookRegistry::default();
561        // Default registry should work the same as new
562        let _ = registry;
563    }
564
565    struct TestHook {
566        before_request_called: Arc<RwLock<bool>>,
567        after_response_called: Arc<RwLock<bool>>,
568    }
569
570    #[async_trait]
571    impl LifecycleHook for TestHook {
572        async fn before_request(
573            &self,
574            _ctx: &RequestContext,
575        ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
576            *self.before_request_called.write().await = true;
577            Ok(())
578        }
579
580        async fn after_response(
581            &self,
582            _ctx: &ResponseContext,
583        ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
584            *self.after_response_called.write().await = true;
585            Ok(())
586        }
587    }
588
589    #[tokio::test]
590    async fn test_lifecycle_hooks() {
591        let registry = LifecycleHookRegistry::new();
592        let before_called = Arc::new(RwLock::new(false));
593        let after_called = Arc::new(RwLock::new(false));
594
595        let hook = Arc::new(TestHook {
596            before_request_called: before_called.clone(),
597            after_response_called: after_called.clone(),
598        });
599
600        registry.register_hook(hook).await;
601
602        let request_ctx = RequestContext {
603            method: "GET".to_string(),
604            path: "/test".to_string(),
605            headers: HashMap::new(),
606            query_params: HashMap::new(),
607            body: None,
608            request_id: "test-1".to_string(),
609            timestamp: chrono::Utc::now(),
610        };
611
612        registry.invoke_before_request(&request_ctx).await;
613        assert!(*before_called.read().await);
614
615        let response_ctx = ResponseContext {
616            request: request_ctx,
617            status_code: 200,
618            headers: HashMap::new(),
619            body: None,
620            response_time_ms: 10,
621            timestamp: chrono::Utc::now(),
622        };
623
624        registry.invoke_after_response(&response_ctx).await;
625        assert!(*after_called.read().await);
626    }
627
628    // Test multiple hooks
629    struct CountingHook {
630        call_count: Arc<AtomicUsize>,
631    }
632
633    #[async_trait]
634    impl LifecycleHook for CountingHook {
635        async fn before_request(
636            &self,
637            _ctx: &RequestContext,
638        ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
639            self.call_count.fetch_add(1, Ordering::SeqCst);
640            Ok(())
641        }
642    }
643
644    #[tokio::test]
645    async fn test_multiple_hooks() {
646        let registry = LifecycleHookRegistry::new();
647        let count1 = Arc::new(AtomicUsize::new(0));
648        let count2 = Arc::new(AtomicUsize::new(0));
649
650        let hook1 = Arc::new(CountingHook {
651            call_count: count1.clone(),
652        });
653        let hook2 = Arc::new(CountingHook {
654            call_count: count2.clone(),
655        });
656
657        registry.register_hook(hook1).await;
658        registry.register_hook(hook2).await;
659
660        let request_ctx = create_request_context();
661        registry.invoke_before_request(&request_ctx).await;
662
663        assert_eq!(count1.load(Ordering::SeqCst), 1);
664        assert_eq!(count2.load(Ordering::SeqCst), 1);
665    }
666
667    // Test error handling in hooks
668    struct ErrorHook;
669
670    #[async_trait]
671    impl LifecycleHook for ErrorHook {
672        async fn before_request(
673            &self,
674            _ctx: &RequestContext,
675        ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
676            Err("Test error".into())
677        }
678
679        async fn on_mock_created(
680            &self,
681            _event: &MockLifecycleEvent,
682        ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
683            Err("Mock creation error".into())
684        }
685
686        async fn on_startup(
687            &self,
688            _event: &ServerLifecycleEvent,
689        ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
690            Err("Startup error".into())
691        }
692    }
693
694    #[tokio::test]
695    async fn test_error_handling_in_hooks() {
696        let registry = LifecycleHookRegistry::new();
697        registry.register_hook(Arc::new(ErrorHook)).await;
698
699        // Should not panic, just log the error
700        let request_ctx = create_request_context();
701        registry.invoke_before_request(&request_ctx).await;
702    }
703
704    // Test mock lifecycle hooks
705    struct MockLifecycleTestHook {
706        created_called: Arc<RwLock<bool>>,
707        updated_called: Arc<RwLock<bool>>,
708        deleted_called: Arc<RwLock<bool>>,
709        state_changed_called: Arc<RwLock<bool>>,
710    }
711
712    #[async_trait]
713    impl LifecycleHook for MockLifecycleTestHook {
714        async fn on_mock_created(
715            &self,
716            _event: &MockLifecycleEvent,
717        ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
718            *self.created_called.write().await = true;
719            Ok(())
720        }
721
722        async fn on_mock_updated(
723            &self,
724            _event: &MockLifecycleEvent,
725        ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
726            *self.updated_called.write().await = true;
727            Ok(())
728        }
729
730        async fn on_mock_deleted(
731            &self,
732            _event: &MockLifecycleEvent,
733        ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
734            *self.deleted_called.write().await = true;
735            Ok(())
736        }
737
738        async fn on_mock_state_changed(
739            &self,
740            _event: &MockLifecycleEvent,
741        ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
742            *self.state_changed_called.write().await = true;
743            Ok(())
744        }
745    }
746
747    #[tokio::test]
748    async fn test_mock_lifecycle_hooks() {
749        let registry = LifecycleHookRegistry::new();
750        let hook = Arc::new(MockLifecycleTestHook {
751            created_called: Arc::new(RwLock::new(false)),
752            updated_called: Arc::new(RwLock::new(false)),
753            deleted_called: Arc::new(RwLock::new(false)),
754            state_changed_called: Arc::new(RwLock::new(false)),
755        });
756        let hook_clone = hook.clone();
757
758        registry.register_hook(hook).await;
759
760        // Test mock created
761        let created_event = MockLifecycleEvent::Created {
762            id: "mock-1".to_string(),
763            name: "Test".to_string(),
764            config: json!({}),
765        };
766        registry.invoke_mock_created(&created_event).await;
767        assert!(*hook_clone.created_called.read().await);
768
769        // Test mock updated
770        let updated_event = MockLifecycleEvent::Updated {
771            id: "mock-1".to_string(),
772            name: "Test".to_string(),
773            config: json!({}),
774        };
775        registry.invoke_mock_updated(&updated_event).await;
776        assert!(*hook_clone.updated_called.read().await);
777
778        // Test mock deleted
779        let deleted_event = MockLifecycleEvent::Deleted {
780            id: "mock-1".to_string(),
781            name: "Test".to_string(),
782        };
783        registry.invoke_mock_deleted(&deleted_event).await;
784        assert!(*hook_clone.deleted_called.read().await);
785
786        // Test mock state changed
787        let state_event = MockLifecycleEvent::Enabled {
788            id: "mock-1".to_string(),
789        };
790        registry.invoke_mock_state_changed(&state_event).await;
791        assert!(*hook_clone.state_changed_called.read().await);
792    }
793
794    // Test server lifecycle hooks
795    struct ServerLifecycleTestHook {
796        startup_called: Arc<RwLock<bool>>,
797        shutdown_called: Arc<RwLock<bool>>,
798    }
799
800    #[async_trait]
801    impl LifecycleHook for ServerLifecycleTestHook {
802        async fn on_startup(
803            &self,
804            _event: &ServerLifecycleEvent,
805        ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
806            *self.startup_called.write().await = true;
807            Ok(())
808        }
809
810        async fn on_shutdown(
811            &self,
812            _event: &ServerLifecycleEvent,
813        ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
814            *self.shutdown_called.write().await = true;
815            Ok(())
816        }
817    }
818
819    #[tokio::test]
820    async fn test_server_lifecycle_hooks() {
821        let registry = LifecycleHookRegistry::new();
822        let hook = Arc::new(ServerLifecycleTestHook {
823            startup_called: Arc::new(RwLock::new(false)),
824            shutdown_called: Arc::new(RwLock::new(false)),
825        });
826        let hook_clone = hook.clone();
827
828        registry.register_hook(hook).await;
829
830        // Test startup
831        let startup_event = ServerLifecycleEvent::Startup {
832            config: json!({"port": 8080}),
833        };
834        registry.invoke_startup(&startup_event).await;
835        assert!(*hook_clone.startup_called.read().await);
836
837        // Test shutdown
838        let shutdown_event = ServerLifecycleEvent::Shutdown {
839            reason: "Test shutdown".to_string(),
840        };
841        registry.invoke_shutdown(&shutdown_event).await;
842        assert!(*hook_clone.shutdown_called.read().await);
843    }
844
845    // Test default trait implementations
846    struct EmptyHook;
847
848    #[async_trait]
849    impl LifecycleHook for EmptyHook {}
850
851    #[tokio::test]
852    async fn test_default_hook_implementations() {
853        let hook = EmptyHook;
854
855        // All these should succeed with the default no-op implementations
856        let request_ctx = create_request_context();
857        assert!(hook.before_request(&request_ctx).await.is_ok());
858
859        let response_ctx = create_response_context();
860        assert!(hook.after_response(&response_ctx).await.is_ok());
861
862        let mock_event = MockLifecycleEvent::Created {
863            id: "test".to_string(),
864            name: "Test".to_string(),
865            config: json!({}),
866        };
867        assert!(hook.on_mock_created(&mock_event).await.is_ok());
868        assert!(hook.on_mock_updated(&mock_event).await.is_ok());
869        assert!(hook.on_mock_deleted(&mock_event).await.is_ok());
870        assert!(hook.on_mock_state_changed(&mock_event).await.is_ok());
871
872        let server_event = ServerLifecycleEvent::Startup { config: json!({}) };
873        assert!(hook.on_startup(&server_event).await.is_ok());
874        assert!(hook.on_shutdown(&server_event).await.is_ok());
875    }
876
877    // Test empty registry
878    #[tokio::test]
879    async fn test_empty_registry() {
880        let registry = LifecycleHookRegistry::new();
881        let request_ctx = create_request_context();
882        let response_ctx = create_response_context();
883
884        // These should not panic on empty registry
885        registry.invoke_before_request(&request_ctx).await;
886        registry.invoke_after_response(&response_ctx).await;
887
888        let mock_event = MockLifecycleEvent::Created {
889            id: "test".to_string(),
890            name: "Test".to_string(),
891            config: json!({}),
892        };
893        registry.invoke_mock_created(&mock_event).await;
894
895        let server_event = ServerLifecycleEvent::Startup { config: json!({}) };
896        registry.invoke_startup(&server_event).await;
897    }
898
899    // Test hook error doesn't stop other hooks
900    #[tokio::test]
901    async fn test_error_doesnt_stop_other_hooks() {
902        let registry = LifecycleHookRegistry::new();
903        let count = Arc::new(AtomicUsize::new(0));
904
905        // Register error hook first
906        registry.register_hook(Arc::new(ErrorHook)).await;
907
908        // Register counting hook second
909        let counting_hook = Arc::new(CountingHook {
910            call_count: count.clone(),
911        });
912        registry.register_hook(counting_hook).await;
913
914        let request_ctx = create_request_context();
915        registry.invoke_before_request(&request_ctx).await;
916
917        // Counting hook should still have been called despite error in first hook
918        assert_eq!(count.load(Ordering::SeqCst), 1);
919    }
920
921    // Test request context with all fields populated
922    #[test]
923    fn test_request_context_full() {
924        let ctx = RequestContext {
925            method: "POST".to_string(),
926            path: "/api/users".to_string(),
927            headers: {
928                let mut h = HashMap::new();
929                h.insert("Content-Type".to_string(), "application/json".to_string());
930                h.insert("Authorization".to_string(), "Bearer token".to_string());
931                h
932            },
933            query_params: {
934                let mut q = HashMap::new();
935                q.insert("page".to_string(), "1".to_string());
936                q.insert("limit".to_string(), "10".to_string());
937                q
938            },
939            body: Some(b"{\"name\": \"test\"}".to_vec()),
940            request_id: "req-12345".to_string(),
941            timestamp: chrono::Utc::now(),
942        };
943
944        assert_eq!(ctx.method, "POST");
945        assert_eq!(ctx.path, "/api/users");
946        assert_eq!(ctx.headers.len(), 2);
947        assert_eq!(ctx.query_params.len(), 2);
948        assert!(ctx.body.is_some());
949        assert_eq!(ctx.request_id, "req-12345");
950    }
951
952    // Test response context with error response
953    #[test]
954    fn test_response_context_error() {
955        let ctx = ResponseContext {
956            request: create_request_context(),
957            status_code: 500,
958            headers: HashMap::new(),
959            body: Some(b"{\"error\": \"Internal Server Error\"}".to_vec()),
960            response_time_ms: 100,
961            timestamp: chrono::Utc::now(),
962        };
963
964        assert_eq!(ctx.status_code, 500);
965        assert!(ctx.body.is_some());
966    }
967}