1use crate::result::{ProbarError, ProbarResult};
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::sync::{Arc, Mutex};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
24pub enum AbortReason {
25 Failed,
27 Aborted,
29 TimedOut,
31 AccessDenied,
33 ConnectionClosed,
35 ConnectionFailed,
37 ConnectionRefused,
39 ConnectionReset,
41 InternetDisconnected,
43 NameNotResolved,
45 BlockedByClient,
47}
48
49impl AbortReason {
50 #[must_use]
52 pub const fn message(&self) -> &'static str {
53 match self {
54 Self::Failed => "net::ERR_FAILED",
55 Self::Aborted => "net::ERR_ABORTED",
56 Self::TimedOut => "net::ERR_TIMED_OUT",
57 Self::AccessDenied => "net::ERR_ACCESS_DENIED",
58 Self::ConnectionClosed => "net::ERR_CONNECTION_CLOSED",
59 Self::ConnectionFailed => "net::ERR_CONNECTION_FAILED",
60 Self::ConnectionRefused => "net::ERR_CONNECTION_REFUSED",
61 Self::ConnectionReset => "net::ERR_CONNECTION_RESET",
62 Self::InternetDisconnected => "net::ERR_INTERNET_DISCONNECTED",
63 Self::NameNotResolved => "net::ERR_NAME_NOT_RESOLVED",
64 Self::BlockedByClient => "net::ERR_BLOCKED_BY_CLIENT",
65 }
66 }
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub enum RouteAction {
72 Respond(MockResponse),
74 Abort(AbortReason),
76 Continue,
78}
79
80impl Default for RouteAction {
81 fn default() -> Self {
82 Self::Continue
83 }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
88pub enum HttpMethod {
89 Get,
91 Post,
93 Put,
95 Delete,
97 Patch,
99 Head,
101 Options,
103 Any,
105}
106
107impl HttpMethod {
108 #[must_use]
110 pub fn from_str(s: &str) -> Self {
111 match s.to_uppercase().as_str() {
112 "GET" => Self::Get,
113 "POST" => Self::Post,
114 "PUT" => Self::Put,
115 "DELETE" => Self::Delete,
116 "PATCH" => Self::Patch,
117 "HEAD" => Self::Head,
118 "OPTIONS" => Self::Options,
119 _ => Self::Any,
120 }
121 }
122
123 #[must_use]
125 pub const fn as_str(&self) -> &'static str {
126 match self {
127 Self::Get => "GET",
128 Self::Post => "POST",
129 Self::Put => "PUT",
130 Self::Delete => "DELETE",
131 Self::Patch => "PATCH",
132 Self::Head => "HEAD",
133 Self::Options => "OPTIONS",
134 Self::Any => "*",
135 }
136 }
137
138 #[must_use]
140 pub fn matches(&self, other: &Self) -> bool {
141 *self == Self::Any || *other == Self::Any || *self == *other
142 }
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct MockResponse {
148 pub status: u16,
150 pub headers: HashMap<String, String>,
152 pub body: Vec<u8>,
154 pub content_type: String,
156 pub delay_ms: u64,
158}
159
160impl Default for MockResponse {
161 fn default() -> Self {
162 Self {
163 status: 200,
164 headers: HashMap::new(),
165 body: Vec::new(),
166 content_type: "application/json".to_string(),
167 delay_ms: 0,
168 }
169 }
170}
171
172impl MockResponse {
173 #[must_use]
175 pub fn new() -> Self {
176 Self::default()
177 }
178
179 #[must_use]
181 pub fn json<T: Serialize>(data: &T) -> ProbarResult<Self> {
182 let body = serde_json::to_vec(data)?;
183 Ok(Self {
184 status: 200,
185 headers: HashMap::new(),
186 body,
187 content_type: "application/json".to_string(),
188 delay_ms: 0,
189 })
190 }
191
192 #[must_use]
194 pub fn text(content: &str) -> Self {
195 Self {
196 status: 200,
197 headers: HashMap::new(),
198 body: content.as_bytes().to_vec(),
199 content_type: "text/plain".to_string(),
200 delay_ms: 0,
201 }
202 }
203
204 #[must_use]
206 pub fn error(status: u16, message: &str) -> Self {
207 let body = serde_json::json!({ "error": message }).to_string();
208 Self {
209 status,
210 headers: HashMap::new(),
211 body: body.into_bytes(),
212 content_type: "application/json".to_string(),
213 delay_ms: 0,
214 }
215 }
216
217 #[must_use]
219 pub const fn with_status(mut self, status: u16) -> Self {
220 self.status = status;
221 self
222 }
223
224 #[must_use]
226 pub fn with_body(mut self, body: Vec<u8>) -> Self {
227 self.body = body;
228 self
229 }
230
231 pub fn with_json<T: Serialize>(mut self, data: &T) -> ProbarResult<Self> {
233 self.body = serde_json::to_vec(data)?;
234 self.content_type = "application/json".to_string();
235 Ok(self)
236 }
237
238 #[must_use]
240 pub fn with_header(mut self, key: &str, value: &str) -> Self {
241 self.headers.insert(key.to_string(), value.to_string());
242 self
243 }
244
245 #[must_use]
247 pub fn with_content_type(mut self, content_type: &str) -> Self {
248 self.content_type = content_type.to_string();
249 self
250 }
251
252 #[must_use]
254 pub const fn with_delay(mut self, delay_ms: u64) -> Self {
255 self.delay_ms = delay_ms;
256 self
257 }
258
259 #[must_use]
261 pub fn body_string(&self) -> String {
262 String::from_utf8_lossy(&self.body).to_string()
263 }
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
268pub enum UrlPattern {
269 Exact(String),
271 Prefix(String),
273 Contains(String),
275 Regex(String),
277 Glob(String),
279 Any,
281}
282
283impl UrlPattern {
284 #[must_use]
286 pub fn matches(&self, url: &str) -> bool {
287 match self {
288 Self::Exact(pattern) => url == pattern,
289 Self::Prefix(pattern) => url.starts_with(pattern),
290 Self::Contains(pattern) => url.contains(pattern),
291 Self::Regex(pattern) => regex::Regex::new(pattern)
292 .map(|re| re.is_match(url))
293 .unwrap_or(false),
294 Self::Glob(pattern) => Self::glob_matches(pattern, url),
295 Self::Any => true,
296 }
297 }
298
299 fn glob_matches(pattern: &str, url: &str) -> bool {
301 let parts: Vec<&str> = pattern.split('*').collect();
302 if parts.is_empty() {
303 return url.is_empty();
304 }
305
306 let mut pos = 0;
307 for (i, part) in parts.iter().enumerate() {
308 if part.is_empty() {
309 continue;
310 }
311 if let Some(found) = url[pos..].find(part) {
312 if i == 0 && found != 0 {
313 return false;
314 }
315 pos += found + part.len();
316 } else {
317 return false;
318 }
319 }
320
321 pattern.ends_with('*') || pos == url.len()
324 }
325}
326
327#[derive(Debug, Clone, Serialize, Deserialize)]
329pub struct CapturedRequest {
330 pub url: String,
332 pub method: HttpMethod,
334 pub headers: HashMap<String, String>,
336 pub body: Option<Vec<u8>>,
338 pub timestamp_ms: u64,
340}
341
342impl CapturedRequest {
343 #[must_use]
345 pub fn new(url: &str, method: HttpMethod, timestamp_ms: u64) -> Self {
346 Self {
347 url: url.to_string(),
348 method,
349 headers: HashMap::new(),
350 body: None,
351 timestamp_ms,
352 }
353 }
354
355 #[must_use]
357 pub fn body_string(&self) -> Option<String> {
358 self.body
359 .as_ref()
360 .map(|b| String::from_utf8_lossy(b).to_string())
361 }
362
363 pub fn body_json<T: for<'de> Deserialize<'de>>(&self) -> ProbarResult<T> {
365 let body = self
366 .body
367 .as_ref()
368 .ok_or_else(|| ProbarError::AssertionFailed {
369 message: "No request body".to_string(),
370 })?;
371 let data = serde_json::from_slice(body)?;
372 Ok(data)
373 }
374}
375
376#[derive(Debug, Clone)]
378pub struct Route {
379 pub pattern: UrlPattern,
381 pub method: HttpMethod,
383 pub response: MockResponse,
385 pub times: Option<usize>,
387 pub match_count: usize,
389}
390
391impl Route {
392 #[must_use]
394 pub fn new(pattern: UrlPattern, method: HttpMethod, response: MockResponse) -> Self {
395 Self {
396 pattern,
397 method,
398 response,
399 times: None,
400 match_count: 0,
401 }
402 }
403
404 #[must_use]
406 pub const fn times(mut self, n: usize) -> Self {
407 self.times = Some(n);
408 self
409 }
410
411 #[must_use]
413 pub fn matches(&self, url: &str, method: &HttpMethod) -> bool {
414 if let Some(max) = self.times {
416 if self.match_count >= max {
417 return false;
418 }
419 }
420 self.pattern.matches(url) && self.method.matches(method)
421 }
422
423 pub fn record_match(&mut self) {
425 self.match_count += 1;
426 }
427
428 #[must_use]
430 pub fn is_exhausted(&self) -> bool {
431 self.times.is_some_and(|max| self.match_count >= max)
432 }
433}
434
435#[derive(Debug)]
437pub struct NetworkInterception {
438 routes: Vec<Route>,
440 captured: Arc<Mutex<Vec<CapturedRequest>>>,
442 capture_all: bool,
444 active: bool,
446 start_time: std::time::Instant,
448 block_unmatched: bool,
450}
451
452impl Default for NetworkInterception {
453 fn default() -> Self {
454 Self::new()
455 }
456}
457
458impl NetworkInterception {
459 #[must_use]
461 pub fn new() -> Self {
462 Self {
463 routes: Vec::new(),
464 captured: Arc::new(Mutex::new(Vec::new())),
465 capture_all: false,
466 active: false,
467 start_time: std::time::Instant::now(),
468 block_unmatched: false,
469 }
470 }
471
472 #[must_use]
474 pub const fn capture_all(mut self) -> Self {
475 self.capture_all = true;
476 self
477 }
478
479 #[must_use]
481 pub const fn block_unmatched(mut self) -> Self {
482 self.block_unmatched = true;
483 self
484 }
485
486 pub fn start(&mut self) {
488 self.active = true;
489 self.start_time = std::time::Instant::now();
490 }
491
492 pub fn stop(&mut self) {
494 self.active = false;
495 }
496
497 #[must_use]
499 pub const fn is_active(&self) -> bool {
500 self.active
501 }
502
503 pub fn route(&mut self, route: Route) {
505 self.routes.push(route);
506 }
507
508 pub fn get(&mut self, pattern: &str, response: MockResponse) {
510 self.routes.push(Route::new(
511 UrlPattern::Contains(pattern.to_string()),
512 HttpMethod::Get,
513 response,
514 ));
515 }
516
517 pub fn post(&mut self, pattern: &str, response: MockResponse) {
519 self.routes.push(Route::new(
520 UrlPattern::Contains(pattern.to_string()),
521 HttpMethod::Post,
522 response,
523 ));
524 }
525
526 pub fn put(&mut self, pattern: &str, response: MockResponse) {
528 self.routes.push(Route::new(
529 UrlPattern::Contains(pattern.to_string()),
530 HttpMethod::Put,
531 response,
532 ));
533 }
534
535 pub fn delete(&mut self, pattern: &str, response: MockResponse) {
537 self.routes.push(Route::new(
538 UrlPattern::Contains(pattern.to_string()),
539 HttpMethod::Delete,
540 response,
541 ));
542 }
543
544 pub fn handle_request(
546 &mut self,
547 url: &str,
548 method: HttpMethod,
549 headers: HashMap<String, String>,
550 body: Option<Vec<u8>>,
551 ) -> Option<MockResponse> {
552 if !self.active {
553 return None;
554 }
555
556 let timestamp_ms = self.start_time.elapsed().as_millis() as u64;
557
558 if self.capture_all {
560 let mut request = CapturedRequest::new(url, method, timestamp_ms);
561 request.headers = headers.clone();
562 request.body = body.clone();
563 if let Ok(mut captured) = self.captured.lock() {
564 captured.push(request);
565 }
566 }
567
568 for route in &mut self.routes {
570 if route.matches(url, &method) {
571 route.record_match();
572
573 if !self.capture_all {
575 let mut request = CapturedRequest::new(url, method, timestamp_ms);
576 request.headers = headers;
577 request.body = body;
578 if let Ok(mut captured) = self.captured.lock() {
579 captured.push(request);
580 }
581 }
582
583 return Some(route.response.clone());
584 }
585 }
586
587 if self.block_unmatched {
589 Some(MockResponse::error(404, "No route matched"))
590 } else {
591 None
592 }
593 }
594
595 #[must_use]
597 pub fn captured_requests(&self) -> Vec<CapturedRequest> {
598 self.captured.lock().map(|c| c.clone()).unwrap_or_default()
599 }
600
601 #[must_use]
603 pub fn requests_matching(&self, pattern: &UrlPattern) -> Vec<CapturedRequest> {
604 self.captured_requests()
605 .into_iter()
606 .filter(|r| pattern.matches(&r.url))
607 .collect()
608 }
609
610 #[must_use]
612 pub fn requests_by_method(&self, method: HttpMethod) -> Vec<CapturedRequest> {
613 self.captured_requests()
614 .into_iter()
615 .filter(|r| r.method == method)
616 .collect()
617 }
618
619 pub fn assert_requested(&self, pattern: &UrlPattern) -> ProbarResult<()> {
621 let requests = self.requests_matching(pattern);
622 if requests.is_empty() {
623 return Err(ProbarError::AssertionFailed {
624 message: format!("Expected request matching {:?}, but none found", pattern),
625 });
626 }
627 Ok(())
628 }
629
630 pub fn assert_requested_times(&self, pattern: &UrlPattern, times: usize) -> ProbarResult<()> {
632 let requests = self.requests_matching(pattern);
633 if requests.len() != times {
634 return Err(ProbarError::AssertionFailed {
635 message: format!(
636 "Expected {} requests matching {:?}, but found {}",
637 times,
638 pattern,
639 requests.len()
640 ),
641 });
642 }
643 Ok(())
644 }
645
646 pub fn assert_not_requested(&self, pattern: &UrlPattern) -> ProbarResult<()> {
648 let requests = self.requests_matching(pattern);
649 if !requests.is_empty() {
650 return Err(ProbarError::AssertionFailed {
651 message: format!(
652 "Expected no requests matching {:?}, but found {}",
653 pattern,
654 requests.len()
655 ),
656 });
657 }
658 Ok(())
659 }
660
661 pub fn clear_captured(&self) {
663 if let Ok(mut captured) = self.captured.lock() {
664 captured.clear();
665 }
666 }
667
668 #[must_use]
670 pub fn route_count(&self) -> usize {
671 self.routes.len()
672 }
673
674 pub fn clear_routes(&mut self) {
676 self.routes.clear();
677 }
678
679 pub fn abort(&mut self, pattern: &str, reason: AbortReason) {
687 let abort_response = MockResponse::new()
688 .with_status(0)
689 .with_body(reason.message().as_bytes().to_vec());
690 self.routes.push(Route::new(
691 UrlPattern::Contains(pattern.to_string()),
692 HttpMethod::Any,
693 abort_response,
694 ));
695 }
696
697 pub fn abort_pattern(&mut self, pattern: UrlPattern, reason: AbortReason) {
699 let abort_response = MockResponse::new()
700 .with_status(0)
701 .with_body(reason.message().as_bytes().to_vec());
702 self.routes
703 .push(Route::new(pattern, HttpMethod::Any, abort_response));
704 }
705
706 #[must_use]
710 pub fn find_request(&self, pattern: &UrlPattern) -> Option<CapturedRequest> {
711 self.requests_matching(pattern).into_iter().next()
712 }
713
714 #[must_use]
718 pub fn find_response_for(&self, pattern: &UrlPattern) -> Option<MockResponse> {
719 for route in &self.routes {
720 if route.pattern.matches(&pattern.to_string()) || route.match_count > 0 {
721 return Some(route.response.clone());
722 }
723 }
724 None
725 }
726
727 #[must_use]
729 pub fn was_aborted(&self, pattern: &UrlPattern) -> bool {
730 for route in &self.routes {
731 if route.match_count > 0 && route.response.status == 0 {
732 if let UrlPattern::Contains(p) = &route.pattern {
733 if pattern.matches(p) {
734 return true;
735 }
736 }
737 }
738 }
739 false
740 }
741
742 #[must_use]
744 pub fn captured_responses(&self) -> Vec<MockResponse> {
745 self.routes
746 .iter()
747 .filter(|r| r.match_count > 0)
748 .map(|r| r.response.clone())
749 .collect()
750 }
751}
752
753impl std::fmt::Display for UrlPattern {
754 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
755 match self {
756 Self::Exact(s)
757 | Self::Prefix(s)
758 | Self::Contains(s)
759 | Self::Regex(s)
760 | Self::Glob(s) => write!(f, "{}", s),
761 Self::Any => write!(f, "*"),
762 }
763 }
764}
765
766#[derive(Debug, Default)]
768pub struct NetworkInterceptionBuilder {
769 interception: NetworkInterception,
770}
771
772impl NetworkInterceptionBuilder {
773 #[must_use]
775 pub fn new() -> Self {
776 Self::default()
777 }
778
779 #[must_use]
781 pub fn capture_all(mut self) -> Self {
782 self.interception.capture_all = true;
783 self
784 }
785
786 #[must_use]
788 pub fn block_unmatched(mut self) -> Self {
789 self.interception.block_unmatched = true;
790 self
791 }
792
793 #[must_use]
795 pub fn get(mut self, pattern: &str, response: MockResponse) -> Self {
796 self.interception.get(pattern, response);
797 self
798 }
799
800 #[must_use]
802 pub fn post(mut self, pattern: &str, response: MockResponse) -> Self {
803 self.interception.post(pattern, response);
804 self
805 }
806
807 #[must_use]
809 pub fn route(mut self, route: Route) -> Self {
810 self.interception.route(route);
811 self
812 }
813
814 #[must_use]
816 pub fn build(self) -> NetworkInterception {
817 self.interception
818 }
819}
820
821#[cfg(test)]
822#[allow(
823 clippy::unwrap_used,
824 clippy::expect_used,
825 clippy::panic,
826 clippy::default_trait_access
827)]
828mod tests {
829 use super::*;
830
831 mod http_method_tests {
832 use super::*;
833
834 #[test]
835 fn test_from_str() {
836 assert_eq!(HttpMethod::from_str("GET"), HttpMethod::Get);
837 assert_eq!(HttpMethod::from_str("post"), HttpMethod::Post);
838 assert_eq!(HttpMethod::from_str("PUT"), HttpMethod::Put);
839 assert_eq!(HttpMethod::from_str("DELETE"), HttpMethod::Delete);
840 assert_eq!(HttpMethod::from_str("unknown"), HttpMethod::Any);
841 }
842
843 #[test]
844 fn test_as_str() {
845 assert_eq!(HttpMethod::Get.as_str(), "GET");
846 assert_eq!(HttpMethod::Post.as_str(), "POST");
847 assert_eq!(HttpMethod::Any.as_str(), "*");
848 }
849
850 #[test]
851 fn test_matches() {
852 assert!(HttpMethod::Get.matches(&HttpMethod::Get));
853 assert!(HttpMethod::Any.matches(&HttpMethod::Get));
854 assert!(HttpMethod::Get.matches(&HttpMethod::Any));
855 assert!(!HttpMethod::Get.matches(&HttpMethod::Post));
856 }
857 }
858
859 mod mock_response_tests {
860 use super::*;
861
862 #[test]
863 fn test_default() {
864 let response = MockResponse::default();
865 assert_eq!(response.status, 200);
866 assert_eq!(response.content_type, "application/json");
867 }
868
869 #[test]
870 fn test_json() {
871 let data = serde_json::json!({"name": "test"});
872 let response = MockResponse::json(&data).unwrap();
873 assert_eq!(response.status, 200);
874 assert!(response.body_string().contains("test"));
875 }
876
877 #[test]
878 fn test_text() {
879 let response = MockResponse::text("Hello World");
880 assert_eq!(response.body_string(), "Hello World");
881 assert_eq!(response.content_type, "text/plain");
882 }
883
884 #[test]
885 fn test_error() {
886 let response = MockResponse::error(404, "Not Found");
887 assert_eq!(response.status, 404);
888 assert!(response.body_string().contains("Not Found"));
889 }
890
891 #[test]
892 fn test_with_status() {
893 let response = MockResponse::new().with_status(201);
894 assert_eq!(response.status, 201);
895 }
896
897 #[test]
898 fn test_with_header() {
899 let response = MockResponse::new().with_header("X-Custom", "value");
900 assert_eq!(response.headers.get("X-Custom"), Some(&"value".to_string()));
901 }
902
903 #[test]
904 fn test_with_delay() {
905 let response = MockResponse::new().with_delay(100);
906 assert_eq!(response.delay_ms, 100);
907 }
908 }
909
910 mod url_pattern_tests {
911 use super::*;
912
913 #[test]
914 fn test_exact() {
915 let pattern = UrlPattern::Exact("https://api.example.com/users".to_string());
916 assert!(pattern.matches("https://api.example.com/users"));
917 assert!(!pattern.matches("https://api.example.com/users/1"));
918 }
919
920 #[test]
921 fn test_prefix() {
922 let pattern = UrlPattern::Prefix("https://api.example.com".to_string());
923 assert!(pattern.matches("https://api.example.com/users"));
924 assert!(pattern.matches("https://api.example.com/posts"));
925 assert!(!pattern.matches("https://other.com"));
926 }
927
928 #[test]
929 fn test_contains() {
930 let pattern = UrlPattern::Contains("/api/".to_string());
931 assert!(pattern.matches("https://example.com/api/users"));
932 assert!(!pattern.matches("https://example.com/users"));
933 }
934
935 #[test]
936 fn test_regex() {
937 let pattern = UrlPattern::Regex(r"/users/\d+".to_string());
938 assert!(pattern.matches("https://api.example.com/users/123"));
939 assert!(!pattern.matches("https://api.example.com/users/abc"));
940 }
941
942 #[test]
943 fn test_glob() {
944 let pattern = UrlPattern::Glob("*/api/users/*".to_string());
945 assert!(pattern.matches("https://example.com/api/users/123"));
946 assert!(!pattern.matches("https://example.com/api/posts/123"));
947 }
948
949 #[test]
950 fn test_any() {
951 let pattern = UrlPattern::Any;
952 assert!(pattern.matches("anything"));
953 assert!(pattern.matches(""));
954 }
955 }
956
957 mod captured_request_tests {
958 use super::*;
959
960 #[test]
961 fn test_new() {
962 let request = CapturedRequest::new("https://api.example.com", HttpMethod::Get, 1000);
963 assert_eq!(request.url, "https://api.example.com");
964 assert_eq!(request.method, HttpMethod::Get);
965 assert_eq!(request.timestamp_ms, 1000);
966 }
967
968 #[test]
969 fn test_body_string() {
970 let mut request = CapturedRequest::new("url", HttpMethod::Post, 0);
971 request.body = Some(b"test body".to_vec());
972 assert_eq!(request.body_string(), Some("test body".to_string()));
973 }
974
975 #[test]
976 fn test_body_json() {
977 let mut request = CapturedRequest::new("url", HttpMethod::Post, 0);
978 request.body = Some(b"{\"name\":\"test\"}".to_vec());
979 let data: serde_json::Value = request.body_json().unwrap();
980 assert_eq!(data["name"], "test");
981 }
982 }
983
984 mod route_tests {
985 use super::*;
986
987 #[test]
988 fn test_new() {
989 let route = Route::new(
990 UrlPattern::Contains("/api".to_string()),
991 HttpMethod::Get,
992 MockResponse::new(),
993 );
994 assert_eq!(route.match_count, 0);
995 assert!(route.times.is_none());
996 }
997
998 #[test]
999 fn test_times() {
1000 let route = Route::new(UrlPattern::Any, HttpMethod::Get, MockResponse::new()).times(3);
1001 assert_eq!(route.times, Some(3));
1002 }
1003
1004 #[test]
1005 fn test_matches() {
1006 let route = Route::new(
1007 UrlPattern::Contains("/users".to_string()),
1008 HttpMethod::Get,
1009 MockResponse::new(),
1010 );
1011 assert!(route.matches("https://api.example.com/users", &HttpMethod::Get));
1012 assert!(!route.matches("https://api.example.com/users", &HttpMethod::Post));
1013 assert!(!route.matches("https://api.example.com/posts", &HttpMethod::Get));
1014 }
1015
1016 #[test]
1017 fn test_record_match() {
1018 let mut route = Route::new(UrlPattern::Any, HttpMethod::Any, MockResponse::new());
1019 route.record_match();
1020 assert_eq!(route.match_count, 1);
1021 }
1022
1023 #[test]
1024 fn test_is_exhausted() {
1025 let mut route =
1026 Route::new(UrlPattern::Any, HttpMethod::Any, MockResponse::new()).times(2);
1027
1028 assert!(!route.is_exhausted());
1029 route.record_match();
1030 assert!(!route.is_exhausted());
1031 route.record_match();
1032 assert!(route.is_exhausted());
1033 }
1034
1035 #[test]
1036 fn test_exhausted_route_no_longer_matches() {
1037 let mut route =
1038 Route::new(UrlPattern::Any, HttpMethod::Any, MockResponse::new()).times(1);
1039
1040 assert!(route.matches("url", &HttpMethod::Get));
1041 route.record_match();
1042 assert!(!route.matches("url", &HttpMethod::Get));
1043 }
1044 }
1045
1046 mod network_interception_tests {
1047 use super::*;
1048
1049 #[test]
1050 fn test_new() {
1051 let interception = NetworkInterception::new();
1052 assert!(!interception.is_active());
1053 assert_eq!(interception.route_count(), 0);
1054 }
1055
1056 #[test]
1057 fn test_start_stop() {
1058 let mut interception = NetworkInterception::new();
1059 interception.start();
1060 assert!(interception.is_active());
1061 interception.stop();
1062 assert!(!interception.is_active());
1063 }
1064
1065 #[test]
1066 fn test_add_routes() {
1067 let mut interception = NetworkInterception::new();
1068 interception.get("/api/users", MockResponse::text("users"));
1069 interception.post("/api/users", MockResponse::new().with_status(201));
1070 interception.put("/api/users/1", MockResponse::new());
1071 interception.delete("/api/users/1", MockResponse::new().with_status(204));
1072
1073 assert_eq!(interception.route_count(), 4);
1074 }
1075
1076 #[test]
1077 fn test_handle_request() {
1078 let mut interception = NetworkInterception::new();
1079 interception.get("/api/users", MockResponse::text("users list"));
1080 interception.start();
1081
1082 let response = interception.handle_request(
1083 "https://api.example.com/api/users",
1084 HttpMethod::Get,
1085 HashMap::new(),
1086 None,
1087 );
1088
1089 assert!(response.is_some());
1090 let response = response.unwrap();
1091 assert_eq!(response.body_string(), "users list");
1092 }
1093
1094 #[test]
1095 fn test_handle_request_no_match() {
1096 let mut interception = NetworkInterception::new();
1097 interception.get("/api/users", MockResponse::text("users"));
1098 interception.start();
1099
1100 let response = interception.handle_request(
1101 "https://api.example.com/api/posts",
1102 HttpMethod::Get,
1103 HashMap::new(),
1104 None,
1105 );
1106
1107 assert!(response.is_none());
1108 }
1109
1110 #[test]
1111 fn test_block_unmatched() {
1112 let mut interception = NetworkInterception::new().block_unmatched();
1113 interception.start();
1114
1115 let response = interception.handle_request(
1116 "https://api.example.com/unknown",
1117 HttpMethod::Get,
1118 HashMap::new(),
1119 None,
1120 );
1121
1122 assert!(response.is_some());
1123 assert_eq!(response.unwrap().status, 404);
1124 }
1125
1126 #[test]
1127 fn test_capture_requests() {
1128 let mut interception = NetworkInterception::new().capture_all();
1129 interception.get("/api/users", MockResponse::new());
1130 interception.start();
1131
1132 interception.handle_request(
1133 "https://api.example.com/api/users",
1134 HttpMethod::Get,
1135 HashMap::new(),
1136 None,
1137 );
1138
1139 let captured = interception.captured_requests();
1140 assert_eq!(captured.len(), 1);
1141 assert_eq!(captured[0].url, "https://api.example.com/api/users");
1142 }
1143
1144 #[test]
1145 fn test_requests_matching() {
1146 let mut interception = NetworkInterception::new().capture_all();
1147 interception.start();
1148
1149 interception.handle_request(
1150 "https://api.example.com/api/users",
1151 HttpMethod::Get,
1152 HashMap::new(),
1153 None,
1154 );
1155 interception.handle_request(
1156 "https://api.example.com/api/posts",
1157 HttpMethod::Get,
1158 HashMap::new(),
1159 None,
1160 );
1161
1162 let users = interception.requests_matching(&UrlPattern::Contains("/users".to_string()));
1163 assert_eq!(users.len(), 1);
1164 }
1165
1166 #[test]
1167 fn test_requests_by_method() {
1168 let mut interception = NetworkInterception::new().capture_all();
1169 interception.start();
1170
1171 interception.handle_request("url1", HttpMethod::Get, HashMap::new(), None);
1172 interception.handle_request("url2", HttpMethod::Post, HashMap::new(), None);
1173
1174 let gets = interception.requests_by_method(HttpMethod::Get);
1175 assert_eq!(gets.len(), 1);
1176 }
1177
1178 #[test]
1179 fn test_assert_requested() {
1180 let mut interception = NetworkInterception::new().capture_all();
1181 interception.start();
1182
1183 interception.handle_request(
1184 "https://api.example.com/api/users",
1185 HttpMethod::Get,
1186 HashMap::new(),
1187 None,
1188 );
1189
1190 assert!(interception
1191 .assert_requested(&UrlPattern::Contains("/users".to_string()))
1192 .is_ok());
1193
1194 assert!(interception
1195 .assert_requested(&UrlPattern::Contains("/posts".to_string()))
1196 .is_err());
1197 }
1198
1199 #[test]
1200 fn test_assert_requested_times() {
1201 let mut interception = NetworkInterception::new().capture_all();
1202 interception.start();
1203
1204 interception.handle_request("url", HttpMethod::Get, HashMap::new(), None);
1205 interception.handle_request("url", HttpMethod::Get, HashMap::new(), None);
1206
1207 assert!(interception
1208 .assert_requested_times(&UrlPattern::Any, 2)
1209 .is_ok());
1210
1211 assert!(interception
1212 .assert_requested_times(&UrlPattern::Any, 3)
1213 .is_err());
1214 }
1215
1216 #[test]
1217 fn test_assert_not_requested() {
1218 let interception = NetworkInterception::new().capture_all();
1219
1220 assert!(interception.assert_not_requested(&UrlPattern::Any).is_ok());
1221 }
1222
1223 #[test]
1224 fn test_clear_captured() {
1225 let mut interception = NetworkInterception::new().capture_all();
1226 interception.start();
1227
1228 interception.handle_request("url", HttpMethod::Get, HashMap::new(), None);
1229 assert_eq!(interception.captured_requests().len(), 1);
1230
1231 interception.clear_captured();
1232 assert_eq!(interception.captured_requests().len(), 0);
1233 }
1234
1235 #[test]
1236 fn test_clear_routes() {
1237 let mut interception = NetworkInterception::new();
1238 interception.get("/api", MockResponse::new());
1239 assert_eq!(interception.route_count(), 1);
1240
1241 interception.clear_routes();
1242 assert_eq!(interception.route_count(), 0);
1243 }
1244 }
1245
1246 mod network_interception_builder_tests {
1247 use super::*;
1248
1249 #[test]
1250 fn test_builder() {
1251 let interception = NetworkInterceptionBuilder::new()
1252 .capture_all()
1253 .block_unmatched()
1254 .get("/api/users", MockResponse::text("users"))
1255 .post("/api/users", MockResponse::new().with_status(201))
1256 .build();
1257
1258 assert!(interception.capture_all);
1259 assert!(interception.block_unmatched);
1260 assert_eq!(interception.route_count(), 2);
1261 }
1262
1263 #[test]
1264 fn test_builder_with_route() {
1265 let route = Route::new(
1266 UrlPattern::Regex(r"/users/\d+".to_string()),
1267 HttpMethod::Get,
1268 MockResponse::new(),
1269 );
1270
1271 let interception = NetworkInterceptionBuilder::new().route(route).build();
1272
1273 assert_eq!(interception.route_count(), 1);
1274 }
1275 }
1276
1277 mod abort_tests {
1282 use super::*;
1283
1284 #[test]
1285 fn test_abort_reason_messages() {
1286 assert_eq!(AbortReason::Failed.message(), "net::ERR_FAILED");
1287 assert_eq!(AbortReason::Aborted.message(), "net::ERR_ABORTED");
1288 assert_eq!(AbortReason::TimedOut.message(), "net::ERR_TIMED_OUT");
1289 assert_eq!(
1290 AbortReason::AccessDenied.message(),
1291 "net::ERR_ACCESS_DENIED"
1292 );
1293 assert_eq!(
1294 AbortReason::ConnectionClosed.message(),
1295 "net::ERR_CONNECTION_CLOSED"
1296 );
1297 assert_eq!(
1298 AbortReason::ConnectionFailed.message(),
1299 "net::ERR_CONNECTION_FAILED"
1300 );
1301 assert_eq!(
1302 AbortReason::ConnectionRefused.message(),
1303 "net::ERR_CONNECTION_REFUSED"
1304 );
1305 assert_eq!(
1306 AbortReason::ConnectionReset.message(),
1307 "net::ERR_CONNECTION_RESET"
1308 );
1309 assert_eq!(
1310 AbortReason::InternetDisconnected.message(),
1311 "net::ERR_INTERNET_DISCONNECTED"
1312 );
1313 assert_eq!(
1314 AbortReason::NameNotResolved.message(),
1315 "net::ERR_NAME_NOT_RESOLVED"
1316 );
1317 assert_eq!(
1318 AbortReason::BlockedByClient.message(),
1319 "net::ERR_BLOCKED_BY_CLIENT"
1320 );
1321 }
1322
1323 #[test]
1324 fn test_abort_request() {
1325 let mut interception = NetworkInterception::new();
1326 interception.abort("/api/blocked", AbortReason::BlockedByClient);
1327 interception.start();
1328
1329 let response = interception.handle_request(
1330 "https://example.com/api/blocked/resource",
1331 HttpMethod::Get,
1332 HashMap::new(),
1333 None,
1334 );
1335
1336 assert!(response.is_some());
1337 let resp = response.unwrap();
1338 assert_eq!(resp.status, 0); assert!(String::from_utf8_lossy(&resp.body).contains("ERR_BLOCKED_BY_CLIENT"));
1340 }
1341
1342 #[test]
1343 fn test_abort_pattern() {
1344 let mut interception = NetworkInterception::new();
1345 interception.abort_pattern(
1346 UrlPattern::Prefix("https://blocked.com".to_string()),
1347 AbortReason::AccessDenied,
1348 );
1349 interception.start();
1350
1351 let response = interception.handle_request(
1352 "https://blocked.com/any/path",
1353 HttpMethod::Get,
1354 HashMap::new(),
1355 None,
1356 );
1357
1358 assert!(response.is_some());
1359 assert_eq!(response.unwrap().status, 0);
1360 }
1361
1362 #[test]
1363 fn test_route_action_default() {
1364 let action: RouteAction = Default::default();
1365 assert!(matches!(action, RouteAction::Continue));
1366 }
1367
1368 #[test]
1369 fn test_route_action_respond() {
1370 let action = RouteAction::Respond(MockResponse::text("test"));
1371 if let RouteAction::Respond(resp) = action {
1372 assert_eq!(resp.body_string(), "test");
1373 } else {
1374 panic!("Expected Respond action");
1375 }
1376 }
1377
1378 #[test]
1379 fn test_route_action_abort() {
1380 let action = RouteAction::Abort(AbortReason::TimedOut);
1381 if let RouteAction::Abort(reason) = action {
1382 assert_eq!(reason, AbortReason::TimedOut);
1383 } else {
1384 panic!("Expected Abort action");
1385 }
1386 }
1387 }
1388
1389 mod wait_tests {
1390 use super::*;
1391
1392 #[test]
1393 fn test_find_request() {
1394 let mut interception = NetworkInterception::new().capture_all();
1395 interception.start();
1396
1397 interception.handle_request(
1398 "https://api.example.com/users/123",
1399 HttpMethod::Get,
1400 HashMap::new(),
1401 None,
1402 );
1403
1404 let request = interception.find_request(&UrlPattern::Contains("users".to_string()));
1405 assert!(request.is_some());
1406 assert!(request.unwrap().url.contains("users"));
1407
1408 let not_found = interception.find_request(&UrlPattern::Contains("posts".to_string()));
1409 assert!(not_found.is_none());
1410 }
1411
1412 #[test]
1413 fn test_find_response_for() {
1414 let mut interception = NetworkInterception::new();
1415 interception.get("/api/users", MockResponse::text("user data"));
1416 interception.start();
1417
1418 interception.handle_request(
1420 "https://example.com/api/users",
1421 HttpMethod::Get,
1422 HashMap::new(),
1423 None,
1424 );
1425
1426 let resp = interception.find_response_for(&UrlPattern::Contains("users".to_string()));
1428 assert!(resp.is_some());
1429 }
1430
1431 #[test]
1432 fn test_captured_responses() {
1433 let mut interception = NetworkInterception::new();
1434 interception.get("/api/users", MockResponse::text("users"));
1435 interception.post("/api/posts", MockResponse::text("posts"));
1436 interception.start();
1437
1438 interception.handle_request(
1440 "https://example.com/api/users",
1441 HttpMethod::Get,
1442 HashMap::new(),
1443 None,
1444 );
1445
1446 let responses = interception.captured_responses();
1447 assert_eq!(responses.len(), 1);
1448 assert_eq!(responses[0].body_string(), "users");
1449 }
1450
1451 #[test]
1452 fn test_url_pattern_to_string() {
1453 let exact = UrlPattern::Exact("https://example.com".to_string());
1454 let prefix = UrlPattern::Prefix("https://".to_string());
1455 let contains = UrlPattern::Contains("api".to_string());
1456 let regex = UrlPattern::Regex(r"\d+".to_string());
1457 let glob = UrlPattern::Glob("**/api/*".to_string());
1458 let any = UrlPattern::Any;
1459
1460 assert_eq!(exact.to_string(), "https://example.com");
1461 assert_eq!(prefix.to_string(), "https://");
1462 assert_eq!(contains.to_string(), "api");
1463 assert_eq!(regex.to_string(), r"\d+");
1464 assert_eq!(glob.to_string(), "**/api/*");
1465 assert_eq!(any.to_string(), "*");
1466 }
1467 }
1468
1469 mod h0_network_tests {
1474 use super::*;
1475
1476 #[test]
1477 fn h0_network_01_abort_reason_failed_message() {
1478 assert_eq!(AbortReason::Failed.message(), "net::ERR_FAILED");
1479 }
1480
1481 #[test]
1482 fn h0_network_02_abort_reason_timed_out() {
1483 assert_eq!(AbortReason::TimedOut.message(), "net::ERR_TIMED_OUT");
1484 }
1485
1486 #[test]
1487 fn h0_network_03_abort_reason_access_denied() {
1488 assert_eq!(
1489 AbortReason::AccessDenied.message(),
1490 "net::ERR_ACCESS_DENIED"
1491 );
1492 }
1493
1494 #[test]
1495 fn h0_network_04_abort_reason_connection_refused() {
1496 assert_eq!(
1497 AbortReason::ConnectionRefused.message(),
1498 "net::ERR_CONNECTION_REFUSED"
1499 );
1500 }
1501
1502 #[test]
1503 fn h0_network_05_abort_reason_internet_disconnected() {
1504 assert_eq!(
1505 AbortReason::InternetDisconnected.message(),
1506 "net::ERR_INTERNET_DISCONNECTED"
1507 );
1508 }
1509
1510 #[test]
1511 fn h0_network_06_route_action_default_continue() {
1512 let action: RouteAction = Default::default();
1513 assert!(matches!(action, RouteAction::Continue));
1514 }
1515
1516 #[test]
1517 fn h0_network_07_http_method_from_str_get() {
1518 let method = HttpMethod::from_str("GET");
1519 assert_eq!(method, HttpMethod::Get);
1520 }
1521
1522 #[test]
1523 fn h0_network_08_http_method_from_str_post() {
1524 let method = HttpMethod::from_str("POST");
1525 assert_eq!(method, HttpMethod::Post);
1526 }
1527
1528 #[test]
1529 fn h0_network_09_http_method_from_str_put() {
1530 let method = HttpMethod::from_str("PUT");
1531 assert_eq!(method, HttpMethod::Put);
1532 }
1533
1534 #[test]
1535 fn h0_network_10_http_method_from_str_delete() {
1536 let method = HttpMethod::from_str("DELETE");
1537 assert_eq!(method, HttpMethod::Delete);
1538 }
1539 }
1540
1541 mod h0_mock_response_tests {
1542 use super::*;
1543
1544 #[test]
1545 fn h0_network_11_mock_response_text() {
1546 let resp = MockResponse::text("hello");
1547 assert_eq!(resp.body_string(), "hello");
1548 }
1549
1550 #[test]
1551 fn h0_network_12_mock_response_json() {
1552 let resp = MockResponse::json(&serde_json::json!({"key": "value"})).unwrap();
1553 assert_eq!(resp.content_type, "application/json");
1554 }
1555
1556 #[test]
1557 fn h0_network_13_mock_response_error() {
1558 let resp = MockResponse::error(404, "Not Found");
1559 assert_eq!(resp.status, 404);
1560 }
1561
1562 #[test]
1563 fn h0_network_14_mock_response_status_200() {
1564 let resp = MockResponse::text("ok");
1565 assert_eq!(resp.status, 200);
1566 }
1567
1568 #[test]
1569 fn h0_network_15_mock_response_with_body() {
1570 let resp = MockResponse::new().with_body(vec![1, 2, 3]);
1571 assert_eq!(resp.body, vec![1, 2, 3]);
1572 }
1573
1574 #[test]
1575 fn h0_network_16_mock_response_with_header() {
1576 let resp = MockResponse::text("body").with_header("X-Custom", "value");
1577 assert_eq!(resp.headers.get("X-Custom"), Some(&"value".to_string()));
1578 }
1579
1580 #[test]
1581 fn h0_network_17_mock_response_with_content_type() {
1582 let resp = MockResponse::new().with_content_type("text/plain");
1583 assert_eq!(resp.content_type, "text/plain");
1584 }
1585
1586 #[test]
1587 fn h0_network_18_mock_response_default() {
1588 let resp = MockResponse::default();
1589 assert_eq!(resp.status, 200);
1590 }
1591
1592 #[test]
1593 fn h0_network_19_mock_response_clone() {
1594 let resp1 = MockResponse::text("cloned");
1595 let resp2 = resp1.clone();
1596 assert_eq!(resp1.body, resp2.body);
1597 }
1598
1599 #[test]
1600 fn h0_network_20_mock_response_debug() {
1601 let resp = MockResponse::text("test");
1602 let debug = format!("{:?}", resp);
1603 assert!(debug.contains("MockResponse"));
1604 }
1605 }
1606
1607 mod h0_url_pattern_tests {
1608 use super::*;
1609
1610 #[test]
1611 fn h0_network_21_url_pattern_exact_match() {
1612 let pattern = UrlPattern::Exact("https://example.com".to_string());
1613 assert!(pattern.matches("https://example.com"));
1614 }
1615
1616 #[test]
1617 fn h0_network_22_url_pattern_exact_no_match() {
1618 let pattern = UrlPattern::Exact("https://example.com".to_string());
1619 assert!(!pattern.matches("https://example.com/path"));
1620 }
1621
1622 #[test]
1623 fn h0_network_23_url_pattern_prefix_match() {
1624 let pattern = UrlPattern::Prefix("https://api.".to_string());
1625 assert!(pattern.matches("https://api.example.com/v1"));
1626 }
1627
1628 #[test]
1629 fn h0_network_24_url_pattern_contains_match() {
1630 let pattern = UrlPattern::Contains("/api/".to_string());
1631 assert!(pattern.matches("https://example.com/api/users"));
1632 }
1633
1634 #[test]
1635 fn h0_network_25_url_pattern_any_matches_all() {
1636 let pattern = UrlPattern::Any;
1637 assert!(pattern.matches("https://any-url.com/any/path"));
1638 }
1639
1640 #[test]
1641 fn h0_network_26_url_pattern_glob_single_star() {
1642 let pattern = UrlPattern::Glob("**/api/*".to_string());
1643 assert!(pattern.matches("https://example.com/api/users"));
1644 }
1645
1646 #[test]
1647 fn h0_network_27_url_pattern_regex() {
1648 let pattern = UrlPattern::Regex(r"/users/\d+".to_string());
1649 assert!(pattern.matches("https://api.com/users/123"));
1650 }
1651
1652 #[test]
1653 fn h0_network_28_url_pattern_to_string_exact() {
1654 let pattern = UrlPattern::Exact("test".to_string());
1655 assert_eq!(pattern.to_string(), "test");
1656 }
1657
1658 #[test]
1659 fn h0_network_29_url_pattern_to_string_any() {
1660 let pattern = UrlPattern::Any;
1661 assert_eq!(pattern.to_string(), "*");
1662 }
1663
1664 #[test]
1665 fn h0_network_30_url_pattern_clone() {
1666 let pattern1 = UrlPattern::Contains("api".to_string());
1667 let pattern2 = pattern1;
1668 assert!(pattern2.matches("https://api.com"));
1669 }
1670 }
1671
1672 mod h0_network_interception_tests {
1673 use super::*;
1674
1675 #[test]
1676 fn h0_network_31_interception_new() {
1677 let interception = NetworkInterception::new();
1678 assert!(!interception.is_active());
1679 }
1680
1681 #[test]
1682 fn h0_network_32_interception_start_stop() {
1683 let mut interception = NetworkInterception::new();
1684 interception.start();
1685 assert!(interception.is_active());
1686 interception.stop();
1687 assert!(!interception.is_active());
1688 }
1689
1690 #[test]
1691 fn h0_network_33_interception_get_route() {
1692 let mut interception = NetworkInterception::new();
1693 interception.get("/api/users", MockResponse::text("users"));
1694 assert_eq!(interception.route_count(), 1);
1695 }
1696
1697 #[test]
1698 fn h0_network_34_interception_post_route() {
1699 let mut interception = NetworkInterception::new();
1700 interception.post("/api/create", MockResponse::text("data"));
1701 assert_eq!(interception.route_count(), 1);
1702 }
1703
1704 #[test]
1705 fn h0_network_35_interception_put_route() {
1706 let mut interception = NetworkInterception::new();
1707 interception.put("/api/update", MockResponse::text("updated"));
1708 assert_eq!(interception.route_count(), 1);
1709 }
1710
1711 #[test]
1712 fn h0_network_36_interception_delete_route() {
1713 let mut interception = NetworkInterception::new();
1714 interception.delete("/api/remove", MockResponse::text("deleted"));
1715 assert_eq!(interception.route_count(), 1);
1716 }
1717
1718 #[test]
1719 fn h0_network_37_interception_abort() {
1720 let mut interception = NetworkInterception::new();
1721 interception.abort("/blocked", AbortReason::BlockedByClient);
1722 assert_eq!(interception.route_count(), 1);
1723 }
1724
1725 #[test]
1726 fn h0_network_38_interception_clear() {
1727 let mut interception = NetworkInterception::new();
1728 interception.get("/api", MockResponse::text("data"));
1729 interception.clear_routes();
1730 assert_eq!(interception.route_count(), 0);
1731 }
1732
1733 #[test]
1734 fn h0_network_39_interception_captured_requests() {
1735 let interception = NetworkInterception::new();
1736 assert!(interception.captured_requests().is_empty());
1737 }
1738
1739 #[test]
1740 fn h0_network_40_interception_captured_responses() {
1741 let interception = NetworkInterception::new();
1742 assert!(interception.captured_responses().is_empty());
1743 }
1744 }
1745
1746 mod h0_route_tests {
1747 use super::*;
1748
1749 #[test]
1750 fn h0_network_41_route_new() {
1751 let route = Route::new(
1752 UrlPattern::Contains("/api".to_string()),
1753 HttpMethod::Get,
1754 MockResponse::text("response"),
1755 );
1756 assert!(route.pattern.matches("https://example.com/api"));
1757 }
1758
1759 #[test]
1760 fn h0_network_42_route_matches_url_and_method() {
1761 let route = Route::new(
1762 UrlPattern::Contains("/api".to_string()),
1763 HttpMethod::Post,
1764 MockResponse::text("data"),
1765 );
1766 assert!(route.matches("https://example.com/api", &HttpMethod::Post));
1767 }
1768
1769 #[test]
1770 fn h0_network_43_route_no_match_wrong_method() {
1771 let route = Route::new(
1772 UrlPattern::Contains("/api".to_string()),
1773 HttpMethod::Get,
1774 MockResponse::text("data"),
1775 );
1776 assert!(!route.matches("https://example.com/api", &HttpMethod::Post));
1777 }
1778
1779 #[test]
1780 fn h0_network_44_route_record_match() {
1781 let mut route =
1782 Route::new(UrlPattern::Any, HttpMethod::Get, MockResponse::text("data"));
1783 route.record_match();
1784 assert_eq!(route.match_count, 1);
1785 }
1786
1787 #[test]
1788 fn h0_network_45_captured_request_new() {
1789 let request = CapturedRequest::new("https://api.com/users", HttpMethod::Get, 1000);
1790 assert_eq!(request.url, "https://api.com/users");
1791 assert_eq!(request.method, HttpMethod::Get);
1792 }
1793
1794 #[test]
1795 fn h0_network_46_captured_request_body_string() {
1796 let mut request = CapturedRequest::new("https://api.com", HttpMethod::Post, 0);
1797 request.body = Some(b"hello".to_vec());
1798 assert_eq!(request.body_string(), Some("hello".to_string()));
1799 }
1800
1801 #[test]
1802 fn h0_network_47_http_method_matches_same() {
1803 assert!(HttpMethod::Get.matches(&HttpMethod::Get));
1804 }
1805
1806 #[test]
1807 fn h0_network_48_http_method_matches_any() {
1808 assert!(HttpMethod::Any.matches(&HttpMethod::Get));
1809 assert!(HttpMethod::Any.matches(&HttpMethod::Post));
1810 }
1811
1812 #[test]
1813 fn h0_network_49_http_method_no_match_different() {
1814 assert!(!HttpMethod::Get.matches(&HttpMethod::Post));
1815 }
1816
1817 #[test]
1818 fn h0_network_50_abort_reason_all_variants() {
1819 let reasons = vec![
1820 AbortReason::Failed,
1821 AbortReason::Aborted,
1822 AbortReason::TimedOut,
1823 AbortReason::AccessDenied,
1824 AbortReason::ConnectionClosed,
1825 AbortReason::ConnectionFailed,
1826 AbortReason::ConnectionRefused,
1827 AbortReason::ConnectionReset,
1828 AbortReason::InternetDisconnected,
1829 AbortReason::NameNotResolved,
1830 AbortReason::BlockedByClient,
1831 ];
1832 for reason in reasons {
1833 assert!(!reason.message().is_empty());
1834 }
1835 }
1836 }
1837}