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