1use async_trait::async_trait;
33use serde::{Deserialize, Serialize};
34use std::collections::HashMap;
35use std::sync::Arc;
36use tokio::sync::RwLock;
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct RequestContext {
41 pub method: String,
43 pub path: String,
45 pub headers: HashMap<String, String>,
47 pub query_params: HashMap<String, String>,
49 pub body: Option<Vec<u8>>,
51 pub request_id: String,
53 pub timestamp: chrono::DateTime<chrono::Utc>,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ResponseContext {
60 pub request: RequestContext,
62 pub status_code: u16,
64 pub headers: HashMap<String, String>,
66 pub body: Option<Vec<u8>>,
68 pub response_time_ms: u64,
70 pub timestamp: chrono::DateTime<chrono::Utc>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
76pub enum MockLifecycleEvent {
77 Created {
79 id: String,
81 name: String,
83 config: serde_json::Value,
85 },
86 Updated {
88 id: String,
90 name: String,
92 config: serde_json::Value,
94 },
95 Deleted {
97 id: String,
99 name: String,
101 },
102 Enabled {
104 id: String,
106 },
107 Disabled {
109 id: String,
111 },
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116pub enum ServerLifecycleEvent {
117 Startup {
119 config: serde_json::Value,
121 },
122 Shutdown {
124 reason: String,
126 },
127}
128
129#[async_trait]
135pub trait LifecycleHook: Send + Sync {
136 async fn before_request(
145 &self,
146 _ctx: &RequestContext,
147 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
148 Ok(())
149 }
150
151 async fn after_response(
160 &self,
161 _ctx: &ResponseContext,
162 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
163 Ok(())
164 }
165
166 async fn on_mock_created(
168 &self,
169 _event: &MockLifecycleEvent,
170 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
171 Ok(())
172 }
173
174 async fn on_mock_updated(
176 &self,
177 _event: &MockLifecycleEvent,
178 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
179 Ok(())
180 }
181
182 async fn on_mock_deleted(
184 &self,
185 _event: &MockLifecycleEvent,
186 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
187 Ok(())
188 }
189
190 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 async fn on_startup(
200 &self,
201 _event: &ServerLifecycleEvent,
202 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
203 Ok(())
204 }
205
206 async fn on_shutdown(
208 &self,
209 _event: &ServerLifecycleEvent,
210 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
211 Ok(())
212 }
213}
214
215pub struct LifecycleHookRegistry {
219 hooks: Arc<RwLock<Vec<Arc<dyn LifecycleHook>>>>,
220}
221
222impl LifecycleHookRegistry {
223 pub fn new() -> Self {
225 Self {
226 hooks: Arc::new(RwLock::new(Vec::new())),
227 }
228 }
229
230 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 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 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 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 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 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 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 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 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 #[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 #[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 #[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 #[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 } 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 #[tokio::test]
552 async fn test_registry_new() {
553 let registry = LifecycleHookRegistry::new();
554 let _ = registry;
556 }
557
558 #[tokio::test]
559 async fn test_registry_default() {
560 let registry = LifecycleHookRegistry::default();
561 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 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 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 let request_ctx = create_request_context();
701 registry.invoke_before_request(&request_ctx).await;
702 }
703
704 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 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 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 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 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 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 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 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 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 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 #[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 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 #[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 registry.register_hook(Arc::new(ErrorHook)).await;
907
908 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 assert_eq!(count.load(Ordering::SeqCst), 1);
919 }
920
921 #[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]
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}