Skip to main content

mockforge_import/import/
har_import.rs

1//! HAR (HTTP Archive) import functionality
2//!
3//! This module handles parsing HAR files and converting them
4//! to MockForge routes and configurations.
5
6use serde::{Deserialize, Serialize};
7use serde_json::{json, Value};
8use std::collections::HashMap;
9
10/// HAR log structure (root object)
11#[derive(Debug, Deserialize)]
12pub struct HarLog {
13    /// HAR format version
14    pub version: String,
15    /// Tool that created the HAR file
16    pub creator: HarCreator,
17    /// Optional browser information
18    #[serde(default)]
19    pub browser: Option<HarBrowser>,
20    /// Optional page information
21    #[serde(default)]
22    pub pages: Vec<HarPage>,
23    /// Array of HTTP request/response entries
24    pub entries: Vec<HarEntry>,
25}
26
27/// HAR creator information
28#[derive(Debug, Deserialize)]
29pub struct HarCreator {
30    /// Creator name (e.g., "Chrome DevTools")
31    pub name: String,
32    /// Creator version
33    pub version: String,
34}
35
36/// HAR browser information (optional)
37#[derive(Debug, Deserialize)]
38pub struct HarBrowser {
39    /// Browser name
40    pub name: String,
41    /// Browser version
42    pub version: String,
43}
44
45/// HAR page information (optional)
46#[derive(Debug, Deserialize)]
47pub struct HarPage {
48    /// Page load start timestamp
49    pub started_date_time: String,
50    /// Unique page identifier
51    pub id: String,
52    /// Page title
53    pub title: String,
54    /// Page load timing information
55    pub page_timings: HarPageTimings,
56}
57
58/// HAR page timings
59#[derive(Debug, Deserialize)]
60pub struct HarPageTimings {
61    /// Time to load page content (ms)
62    pub on_content_load: Option<f64>,
63    /// Time to complete page load (ms)
64    pub on_load: Option<f64>,
65}
66
67/// HAR entry (request/response pair)
68#[derive(Debug, Deserialize)]
69pub struct HarEntry {
70    /// Page reference ID (if applicable)
71    pub pageref: Option<String>,
72    /// Request start timestamp
73    #[serde(rename = "startedDateTime")]
74    pub started_date_time: String,
75    /// Total request/response time (ms)
76    pub time: f64,
77    /// HTTP request details
78    pub request: HarRequest,
79    /// HTTP response details
80    pub response: HarResponse,
81    /// Cache information
82    pub cache: HarCache,
83    /// Detailed timing breakdown
84    pub timings: HarTimings,
85}
86
87/// HAR request structure
88#[derive(Debug, Deserialize)]
89pub struct HarRequest {
90    /// HTTP method
91    pub method: String,
92    /// Request URL
93    pub url: String,
94    /// HTTP version (e.g., "HTTP/1.1")
95    #[serde(rename = "httpVersion")]
96    pub http_version: String,
97    /// Request cookies
98    pub cookies: Vec<HarCookie>,
99    /// Request headers
100    pub headers: Vec<HarHeader>,
101    /// Query parameters
102    #[serde(default, rename = "queryString")]
103    pub query_string: Vec<HarQueryParam>,
104    /// POST data (if any)
105    #[serde(rename = "postData")]
106    pub post_data: Option<HarPostData>,
107    /// Size of request headers (bytes)
108    #[serde(rename = "headersSize")]
109    pub headers_size: i64,
110    /// Size of request body (bytes)
111    #[serde(rename = "bodySize")]
112    pub body_size: i64,
113}
114
115/// HAR response structure
116#[derive(Debug, Deserialize)]
117pub struct HarResponse {
118    /// HTTP status code
119    pub status: u16,
120    /// HTTP status text
121    #[serde(rename = "statusText")]
122    pub status_text: String,
123    /// HTTP version (e.g., "HTTP/1.1")
124    #[serde(rename = "httpVersion")]
125    pub http_version: String,
126    /// Response cookies
127    pub cookies: Vec<HarCookie>,
128    /// Response headers
129    pub headers: Vec<HarHeader>,
130    /// Response content
131    pub content: HarContent,
132    /// Redirect URL (if applicable)
133    #[serde(rename = "redirectURL")]
134    pub redirect_url: String,
135    /// Size of response headers (bytes)
136    #[serde(rename = "headersSize")]
137    pub headers_size: i64,
138    /// Size of response body (bytes)
139    #[serde(rename = "bodySize")]
140    pub body_size: i64,
141}
142
143/// HAR cookie entry
144#[derive(Debug, Deserialize)]
145pub struct HarCookie {
146    /// Cookie name
147    pub name: String,
148    /// Cookie value
149    pub value: String,
150    /// Cookie path
151    #[serde(default)]
152    pub path: Option<String>,
153    /// Cookie domain
154    #[serde(default)]
155    pub domain: Option<String>,
156    /// Cookie expiration timestamp
157    #[serde(default)]
158    pub expires: Option<String>,
159    /// Whether cookie is HTTP-only
160    #[serde(default)]
161    pub http_only: Option<bool>,
162    /// Whether cookie requires HTTPS
163    #[serde(default)]
164    pub secure: Option<bool>,
165}
166
167/// HAR header entry
168#[derive(Debug, Deserialize)]
169pub struct HarHeader {
170    /// Header name
171    pub name: String,
172    /// Header value
173    pub value: String,
174}
175
176/// HAR query parameter entry
177#[derive(Debug, Deserialize)]
178pub struct HarQueryParam {
179    /// Parameter name
180    pub name: String,
181    /// Parameter value
182    pub value: String,
183}
184
185/// HAR POST request body data
186#[derive(Debug, Deserialize)]
187pub struct HarPostData {
188    /// MIME type of the POST data
189    #[serde(rename = "mimeType")]
190    pub mime_type: String,
191    /// Form parameters (for form-urlencoded or multipart/form-data)
192    #[serde(default)]
193    pub params: Vec<HarParam>,
194    /// Raw text content (for raw or JSON bodies)
195    #[serde(default)]
196    pub text: Option<String>,
197}
198
199/// HAR POST parameter (form field or file)
200#[derive(Debug, Deserialize)]
201pub struct HarParam {
202    /// Parameter name
203    pub name: String,
204    /// Parameter value (None for file uploads)
205    pub value: Option<String>,
206    /// Filename (for file uploads)
207    #[serde(default, rename = "fileName")]
208    pub file_name: Option<String>,
209    /// Content type (for file uploads)
210    #[serde(default, rename = "contentType")]
211    pub content_type: Option<String>,
212}
213
214/// HAR response content
215#[derive(Debug, Deserialize)]
216pub struct HarContent {
217    /// Content size in bytes
218    pub size: i64,
219    /// Compressed size (bytes, if compression was used)
220    #[serde(default)]
221    pub compression: Option<i64>,
222    /// MIME type of the content
223    #[serde(rename = "mimeType")]
224    pub mime_type: String,
225    /// Response body text (base64 encoded if encoding is specified)
226    #[serde(default)]
227    pub text: Option<String>,
228    /// Content encoding (base64, gzip, etc.)
229    #[serde(default)]
230    pub encoding: Option<String>,
231}
232
233/// HAR cache information (currently empty struct per HAR spec)
234#[derive(Debug, Deserialize)]
235pub struct HarCache {}
236
237/// HAR timing breakdown for performance analysis
238#[derive(Debug, Deserialize)]
239pub struct HarTimings {
240    /// Time to send request (ms)
241    #[serde(default)]
242    pub send: Option<f64>,
243    /// Time waiting for response (ms)
244    #[serde(default)]
245    pub wait: Option<f64>,
246    /// Time to receive response (ms)
247    #[serde(default)]
248    pub receive: Option<f64>,
249}
250
251/// HAR archive root structure
252#[derive(Debug, Deserialize)]
253pub struct HarArchive {
254    /// HAR log containing all entries
255    pub log: HarLog,
256}
257
258/// MockForge route structure for HAR import
259#[derive(Debug, Serialize)]
260pub struct MockForgeRoute {
261    /// HTTP method
262    pub method: String,
263    /// Request path
264    pub path: String,
265    /// Request headers
266    pub headers: HashMap<String, String>,
267    /// Optional request body
268    pub body: Option<String>,
269    /// Mock response for this route
270    pub response: MockForgeResponse,
271}
272
273/// MockForge response structure
274#[derive(Debug, Serialize)]
275pub struct MockForgeResponse {
276    /// HTTP status code
277    pub status: u16,
278    /// Response headers
279    pub headers: HashMap<String, String>,
280    /// Response body
281    pub body: Value,
282}
283
284/// Result of importing a HAR archive
285pub struct HarImportResult {
286    /// Converted routes from HAR entries
287    pub routes: Vec<MockForgeRoute>,
288    /// Warnings encountered during import
289    pub warnings: Vec<String>,
290}
291
292/// Import a HAR archive
293pub fn import_har_archive(
294    content: &str,
295    base_url: Option<&str>,
296) -> Result<HarImportResult, String> {
297    let archive: HarArchive =
298        serde_json::from_str(content).map_err(|e| format!("Failed to parse HAR archive: {}", e))?;
299
300    let mut routes = Vec::new();
301    let mut warnings = Vec::new();
302
303    // Process each entry in the HAR log
304    for entry in &archive.log.entries {
305        match convert_entry_to_route(entry, base_url) {
306            Ok(route) => routes.push(route),
307            Err(e) => warnings.push(format!("Failed to convert HAR entry: {}", e)),
308        }
309    }
310
311    Ok(HarImportResult { routes, warnings })
312}
313
314/// Convert a HAR entry to a MockForge route
315fn convert_entry_to_route(
316    entry: &HarEntry,
317    base_url: Option<&str>,
318) -> Result<MockForgeRoute, String> {
319    let request = &entry.request;
320    let response = &entry.response;
321
322    // Extract path from URL
323    let path = extract_path_from_url(&request.url, base_url)?;
324
325    // Extract request headers
326    let mut request_headers = HashMap::new();
327    for header in &request.headers {
328        if !header.name.is_empty() {
329            request_headers.insert(header.name.clone(), header.value.clone());
330        }
331    }
332
333    // Extract request body
334    let body = extract_request_body(request);
335
336    // Extract response headers
337    let mut response_headers = HashMap::new();
338    for header in &response.headers {
339        if !header.name.is_empty() {
340            response_headers.insert(header.name.clone(), header.value.clone());
341        }
342    }
343
344    // Extract response body
345    let response_body = extract_response_body(response);
346
347    let mock_response = MockForgeResponse {
348        status: response.status,
349        headers: response_headers,
350        body: response_body,
351    };
352
353    Ok(MockForgeRoute {
354        method: request.method.clone(),
355        path,
356        headers: request_headers,
357        body,
358        response: mock_response,
359    })
360}
361
362/// Extract path from URL, optionally making it relative to base_url
363fn extract_path_from_url(url: &str, base_url: Option<&str>) -> Result<String, String> {
364    if let Ok(parsed_url) = url::Url::parse(url) {
365        let path = parsed_url.path();
366        let query = parsed_url.query();
367
368        let full_path = if let Some(q) = query {
369            format!("{}?{}", path, q)
370        } else {
371            path.to_string()
372        };
373
374        // If base_url is provided, make path relative
375        if let Some(base) = base_url {
376            if let Ok(base_parsed) = url::Url::parse(base) {
377                if parsed_url.host() == base_parsed.host() {
378                    return Ok(full_path);
379                }
380            }
381        }
382
383        // Extract just the path part if it's an absolute URL
384        Ok(full_path)
385    } else {
386        // Assume it's already a path
387        Ok(url.to_string())
388    }
389}
390
391/// Extract request body from HAR request
392fn extract_request_body(request: &HarRequest) -> Option<String> {
393    if let Some(post_data) = &request.post_data {
394        if let Some(text) = &post_data.text {
395            if !text.is_empty() {
396                return Some(text.clone());
397            }
398        }
399
400        // Handle form parameters
401        if !post_data.params.is_empty() {
402            let mut form_data = Vec::new();
403            for param in &post_data.params {
404                if let Some(value) = &param.value {
405                    form_data.push(format!("{}={}", param.name, value));
406                }
407            }
408            if !form_data.is_empty() {
409                return Some(form_data.join("&"));
410            }
411        }
412    }
413    None
414}
415
416/// Extract response body from HAR response
417fn extract_response_body(response: &HarResponse) -> Value {
418    if let Some(text) = &response.content.text {
419        if !text.is_empty() {
420            // Try to parse as JSON first
421            if let Ok(json_value) = serde_json::from_str::<Value>(text) {
422                return json_value;
423            }
424
425            // If not JSON, return as string
426            return Value::String(text.clone());
427        }
428    }
429
430    // Default empty response
431    json!({"message": "Mock response from HAR import"})
432}
433
434#[cfg(test)]
435mod tests {
436    use super::*;
437
438    #[test]
439    fn test_parse_har_archive() {
440        let har_json = r#"{
441            "log": {
442                "version": "1.2",
443                "creator": {
444                    "name": "Test Creator",
445                    "version": "1.0"
446                },
447                "entries": [
448                    {
449                        "startedDateTime": "2024-01-15T10:30:00Z",
450                        "time": 123.45,
451                        "request": {
452                            "method": "GET",
453                            "url": "https://api.example.com/users",
454                            "httpVersion": "HTTP/1.1",
455                            "cookies": [],
456                            "headers": [
457                                {"name": "Authorization", "value": "Bearer token123"},
458                                {"name": "Content-Type", "value": "application/json"}
459                            ],
460                            "queryString": [],
461                            "headersSize": 150,
462                            "bodySize": 0
463                        },
464                        "response": {
465                            "status": 200,
466                            "statusText": "OK",
467                            "httpVersion": "HTTP/1.1",
468                            "cookies": [],
469                            "headers": [
470                                {"name": "Content-Type", "value": "application/json"}
471                            ],
472                            "content": {
473                                "size": 45,
474                                "mimeType": "application/json",
475                                "text": "{\"users\": [{\"id\": 1, \"name\": \"John\"}]}"
476                            },
477                            "redirectURL": "",
478                            "headersSize": 100,
479                            "bodySize": 45
480                        },
481                        "cache": {},
482                        "timings": {
483                            "send": 10.0,
484                            "wait": 100.0,
485                            "receive": 13.45
486                        }
487                    }
488                ]
489            }
490        }"#;
491
492        let result = import_har_archive(har_json, Some("https://api.example.com")).unwrap();
493
494        assert_eq!(result.routes.len(), 1);
495        assert_eq!(result.routes[0].method, "GET");
496        assert_eq!(result.routes[0].path, "/users");
497        assert!(result.routes[0].headers.contains_key("Authorization"));
498        assert_eq!(result.routes[0].response.status, 200);
499    }
500
501    #[test]
502    fn test_extract_path_from_url() {
503        // Test with base URL
504        assert_eq!(
505            extract_path_from_url(
506                "https://api.example.com/users/123",
507                Some("https://api.example.com")
508            )
509            .unwrap(),
510            "/users/123"
511        );
512
513        // Test with query parameters
514        assert_eq!(
515            extract_path_from_url(
516                "https://api.example.com/search?q=test&page=1",
517                Some("https://api.example.com")
518            )
519            .unwrap(),
520            "/search?q=test&page=1"
521        );
522
523        // Test without base URL
524        assert_eq!(extract_path_from_url("https://api.example.com/users", None).unwrap(), "/users");
525    }
526
527    #[test]
528    fn test_extract_request_body() {
529        let request_with_text = HarRequest {
530            method: "POST".to_string(),
531            url: "https://api.example.com/users".to_string(),
532            http_version: "HTTP/1.1".to_string(),
533            cookies: vec![],
534            headers: vec![],
535            query_string: vec![],
536            post_data: Some(HarPostData {
537                mime_type: "application/json".to_string(),
538                params: vec![],
539                text: Some(r#"{"name": "John"}"#.to_string()),
540            }),
541            headers_size: 100,
542            body_size: 16,
543        };
544
545        assert_eq!(
546            extract_request_body(&request_with_text),
547            Some(r#"{"name": "John"}"#.to_string())
548        );
549    }
550
551    #[test]
552    fn test_extract_response_body() {
553        let response_with_json = HarResponse {
554            status: 200,
555            status_text: "OK".to_string(),
556            http_version: "HTTP/1.1".to_string(),
557            cookies: vec![],
558            headers: vec![],
559            content: HarContent {
560                size: 25,
561                compression: None,
562                mime_type: "application/json".to_string(),
563                text: Some(r#"{"message": "success"}"#.to_string()),
564                encoding: None,
565            },
566            redirect_url: "".to_string(),
567            headers_size: 100,
568            body_size: 25,
569        };
570
571        let body = extract_response_body(&response_with_json);
572        assert_eq!(body, json!({"message": "success"}));
573    }
574
575    #[test]
576    fn test_parse_har_with_multiple_entries() {
577        let har_json = r#"{
578            "log": {
579                "version": "1.2",
580                "creator": {
581                    "name": "Test Creator",
582                    "version": "1.0"
583                },
584                "entries": [
585                    {
586                        "startedDateTime": "2024-01-15T10:30:00Z",
587                        "time": 123.45,
588                        "request": {
589                            "method": "GET",
590                            "url": "https://api.example.com/users",
591                            "httpVersion": "HTTP/1.1",
592                            "cookies": [],
593                            "headers": [],
594                            "queryString": [],
595                            "headersSize": 100,
596                            "bodySize": 0
597                        },
598                        "response": {
599                            "status": 200,
600                            "statusText": "OK",
601                            "httpVersion": "HTTP/1.1",
602                            "cookies": [],
603                            "headers": [],
604                            "content": {
605                                "size": 25,
606                                "mimeType": "application/json",
607                                "text": "{\"users\": []}"
608                            },
609                            "redirectURL": "",
610                            "headersSize": 100,
611                            "bodySize": 25
612                        },
613                        "cache": {},
614                        "timings": {}
615                    },
616                    {
617                        "startedDateTime": "2024-01-15T10:31:00Z",
618                        "time": 234.56,
619                        "request": {
620                            "method": "POST",
621                            "url": "https://api.example.com/users",
622                            "httpVersion": "HTTP/1.1",
623                            "cookies": [],
624                            "headers": [],
625                            "queryString": [],
626                            "headersSize": 100,
627                            "bodySize": 16
628                        },
629                        "response": {
630                            "status": 201,
631                            "statusText": "Created",
632                            "httpVersion": "HTTP/1.1",
633                            "cookies": [],
634                            "headers": [],
635                            "content": {
636                                "size": 25,
637                                "mimeType": "application/json",
638                                "text": "{\"id\": 123}"
639                            },
640                            "redirectURL": "",
641                            "headersSize": 100,
642                            "bodySize": 25
643                        },
644                        "cache": {},
645                        "timings": {}
646                    }
647                ]
648            }
649        }"#;
650
651        let result = import_har_archive(har_json, Some("https://api.example.com")).unwrap();
652
653        assert_eq!(result.routes.len(), 2);
654
655        // Check first route (GET)
656        assert_eq!(result.routes[0].method, "GET");
657        assert_eq!(result.routes[0].path, "/users");
658        assert_eq!(result.routes[0].response.status, 200);
659
660        // Check second route (POST)
661        assert_eq!(result.routes[1].method, "POST");
662        assert_eq!(result.routes[1].path, "/users");
663        assert_eq!(result.routes[1].response.status, 201);
664    }
665
666    #[test]
667    fn test_parse_har_with_query_parameters() {
668        let har_json = r#"{
669            "log": {
670                "version": "1.2",
671                "creator": {"name": "Test", "version": "1.0"},
672                "entries": [{
673                    "startedDateTime": "2024-01-15T10:30:00Z",
674                    "time": 123.45,
675                    "request": {
676                        "method": "GET",
677                        "url": "https://api.example.com/search?q=test&page=1&limit=10",
678                        "httpVersion": "HTTP/1.1",
679                        "cookies": [],
680                        "headers": [],
681                        "queryString": [
682                            {"name": "q", "value": "test"},
683                            {"name": "page", "value": "1"},
684                            {"name": "limit", "value": "10"}
685                        ],
686                        "headersSize": 100,
687                        "bodySize": 0
688                    },
689                    "response": {
690                        "status": 200,
691                        "statusText": "OK",
692                        "httpVersion": "HTTP/1.1",
693                        "cookies": [],
694                        "headers": [],
695                        "content": {"size": 25, "mimeType": "application/json", "text": "{}"},
696                        "redirectURL": "",
697                        "headersSize": 100,
698                        "bodySize": 25
699                    },
700                    "cache": {},
701                    "timings": {}
702                }]
703            }
704        }"#;
705
706        let result = import_har_archive(har_json, Some("https://api.example.com")).unwrap();
707
708        assert_eq!(result.routes.len(), 1);
709        assert_eq!(result.routes[0].method, "GET");
710        assert_eq!(result.routes[0].path, "/search?q=test&page=1&limit=10");
711    }
712
713    #[test]
714    fn test_parse_har_with_post_data() {
715        let har_json = r#"{
716            "log": {
717                "version": "1.2",
718                "creator": {"name": "Test", "version": "1.0"},
719                "entries": [{
720                    "startedDateTime": "2024-01-15T10:30:00Z",
721                    "time": 123.45,
722                    "request": {
723                        "method": "POST",
724                        "url": "https://api.example.com/users",
725                        "httpVersion": "HTTP/1.1",
726                        "cookies": [],
727                        "headers": [],
728                        "queryString": [],
729                        "postData": {
730                            "mimeType": "application/json",
731                            "params": [],
732                            "text": "{\"name\": \"John\", \"age\": 30}"
733                        },
734                        "headersSize": 100,
735                        "bodySize": 30
736                    },
737                    "response": {
738                        "status": 201,
739                        "statusText": "Created",
740                        "httpVersion": "HTTP/1.1",
741                        "cookies": [],
742                        "headers": [],
743                        "content": {"size": 25, "mimeType": "application/json", "text": "{}"},
744                        "redirectURL": "",
745                        "headersSize": 100,
746                        "bodySize": 25
747                    },
748                    "cache": {},
749                    "timings": {}
750                }]
751            }
752        }"#;
753
754        let result = import_har_archive(har_json, Some("https://api.example.com")).unwrap();
755
756        assert_eq!(result.routes.len(), 1);
757        assert_eq!(result.routes[0].method, "POST");
758        assert_eq!(result.routes[0].path, "/users");
759        assert_eq!(result.routes[0].body, Some("{\"name\": \"John\", \"age\": 30}".to_string()));
760    }
761
762    #[test]
763    fn test_parse_har_with_form_data() {
764        let har_json = r#"{
765            "log": {
766                "version": "1.2",
767                "creator": {"name": "Test", "version": "1.0"},
768                "entries": [{
769                    "startedDateTime": "2024-01-15T10:30:00Z",
770                    "time": 123.45,
771                    "request": {
772                        "method": "POST",
773                        "url": "https://api.example.com/form",
774                        "httpVersion": "HTTP/1.1",
775                        "cookies": [],
776                        "headers": [],
777                        "queryString": [],
778                        "postData": {
779                            "mimeType": "application/x-www-form-urlencoded",
780                            "params": [
781                                {"name": "username", "value": "john_doe"},
782                                {"name": "password", "value": "secret123"},
783                                {"name": "remember", "value": "true"}
784                            ],
785                            "text": null
786                        },
787                        "headersSize": 100,
788                        "bodySize": 50
789                    },
790                    "response": {
791                        "status": 200,
792                        "statusText": "OK",
793                        "httpVersion": "HTTP/1.1",
794                        "cookies": [],
795                        "headers": [],
796                        "content": {"size": 25, "mimeType": "application/json", "text": "{}"},
797                        "redirectURL": "",
798                        "headersSize": 100,
799                        "bodySize": 25
800                    },
801                    "cache": {},
802                    "timings": {}
803                }]
804            }
805        }"#;
806
807        let result = import_har_archive(har_json, Some("https://api.example.com")).unwrap();
808
809        assert_eq!(result.routes.len(), 1);
810        assert_eq!(result.routes[0].method, "POST");
811        assert_eq!(result.routes[0].path, "/form");
812        assert_eq!(
813            result.routes[0].body,
814            Some("username=john_doe&password=secret123&remember=true".to_string())
815        );
816    }
817
818    #[test]
819    fn test_extract_path_from_url_edge_cases() {
820        // Test with various URL formats
821        let test_cases = vec![
822            (
823                "https://api.example.com/users/123",
824                Some("https://api.example.com"),
825                "/users/123",
826            ),
827            (
828                "https://api.example.com/users/123/details",
829                Some("https://api.example.com"),
830                "/users/123/details",
831            ),
832            (
833                "https://api.example.com/search?q=test",
834                Some("https://api.example.com"),
835                "/search?q=test",
836            ),
837            (
838                "https://api.example.com/search?q=test&page=1",
839                Some("https://api.example.com"),
840                "/search?q=test&page=1",
841            ),
842            ("https://api.example.com/", Some("https://api.example.com"), "/"),
843            ("https://api.example.com", Some("https://api.example.com"), "/"),
844            ("https://api.example.com/users", None, "/users"),
845            ("https://api.example.com/users/", None, "/users/"),
846            ("http://localhost:9080/api/test", Some("http://localhost:9080"), "/api/test"),
847            (
848                "https://subdomain.example.com/api/v1/test",
849                Some("https://subdomain.example.com"),
850                "/api/v1/test",
851            ),
852        ];
853
854        for (url, base_url, expected) in test_cases {
855            let result = extract_path_from_url(url, base_url);
856            assert_eq!(result.unwrap(), expected, "Failed for URL: {}, base: {:?}", url, base_url);
857        }
858    }
859
860    #[test]
861    fn test_extract_request_body_comprehensive() {
862        // Test with JSON data
863        let request_with_json = HarRequest {
864            method: "POST".to_string(),
865            url: "https://api.example.com/users".to_string(),
866            http_version: "HTTP/1.1".to_string(),
867            cookies: vec![],
868            headers: vec![],
869            query_string: vec![],
870            post_data: Some(HarPostData {
871                mime_type: "application/json".to_string(),
872                params: vec![],
873                text: Some(r#"{"name": "John", "age": 30}"#.to_string()),
874            }),
875            headers_size: 100,
876            body_size: 30,
877        };
878        assert_eq!(
879            extract_request_body(&request_with_json),
880            Some(r#"{"name": "John", "age": 30}"#.to_string())
881        );
882
883        // Test with form parameters
884        let request_with_form = HarRequest {
885            method: "POST".to_string(),
886            url: "https://api.example.com/form".to_string(),
887            http_version: "HTTP/1.1".to_string(),
888            cookies: vec![],
889            headers: vec![],
890            query_string: vec![],
891            post_data: Some(HarPostData {
892                mime_type: "application/x-www-form-urlencoded".to_string(),
893                params: vec![
894                    HarParam {
895                        name: "username".to_string(),
896                        value: Some("john_doe".to_string()),
897                        file_name: None,
898                        content_type: None,
899                    },
900                    HarParam {
901                        name: "password".to_string(),
902                        value: Some("secret123".to_string()),
903                        file_name: None,
904                        content_type: None,
905                    },
906                    HarParam {
907                        name: "remember".to_string(),
908                        value: Some("true".to_string()),
909                        file_name: None,
910                        content_type: None,
911                    },
912                ],
913                text: None,
914            }),
915            headers_size: 100,
916            body_size: 50,
917        };
918        assert_eq!(
919            extract_request_body(&request_with_form),
920            Some("username=john_doe&password=secret123&remember=true".to_string())
921        );
922
923        // Test with empty params
924        let request_with_empty_params = HarRequest {
925            method: "POST".to_string(),
926            url: "https://api.example.com/form".to_string(),
927            http_version: "HTTP/1.1".to_string(),
928            cookies: vec![],
929            headers: vec![],
930            query_string: vec![],
931            post_data: Some(HarPostData {
932                mime_type: "application/x-www-form-urlencoded".to_string(),
933                params: vec![],
934                text: Some("".to_string()),
935            }),
936            headers_size: 100,
937            body_size: 0,
938        };
939        assert_eq!(extract_request_body(&request_with_empty_params), None);
940
941        // Test with no post data
942        let request_no_body = HarRequest {
943            method: "GET".to_string(),
944            url: "https://api.example.com/users".to_string(),
945            http_version: "HTTP/1.1".to_string(),
946            cookies: vec![],
947            headers: vec![],
948            query_string: vec![],
949            post_data: None,
950            headers_size: 100,
951            body_size: 0,
952        };
953        assert_eq!(extract_request_body(&request_no_body), None);
954    }
955
956    #[test]
957    fn test_extract_response_body_comprehensive() {
958        // Test with valid JSON
959        let response_with_json = HarResponse {
960            status: 200,
961            status_text: "OK".to_string(),
962            http_version: "HTTP/1.1".to_string(),
963            cookies: vec![],
964            headers: vec![],
965            content: HarContent {
966                size: 25,
967                compression: None,
968                mime_type: "application/json".to_string(),
969                text: Some(r#"{"message": "success"}"#.to_string()),
970                encoding: None,
971            },
972            redirect_url: "".to_string(),
973            headers_size: 100,
974            body_size: 25,
975        };
976        assert_eq!(extract_response_body(&response_with_json), json!({"message": "success"}));
977
978        // Test with invalid JSON (should return as string)
979        let response_with_invalid_json = HarResponse {
980            status: 200,
981            status_text: "OK".to_string(),
982            http_version: "HTTP/1.1".to_string(),
983            cookies: vec![],
984            headers: vec![],
985            content: HarContent {
986                size: 15,
987                compression: None,
988                mime_type: "text/plain".to_string(),
989                text: Some("not json".to_string()),
990                encoding: None,
991            },
992            redirect_url: "".to_string(),
993            headers_size: 100,
994            body_size: 15,
995        };
996        assert_eq!(extract_response_body(&response_with_invalid_json), json!("not json"));
997
998        // Test with empty text
999        let response_with_empty_text = HarResponse {
1000            status: 204,
1001            status_text: "No Content".to_string(),
1002            http_version: "HTTP/1.1".to_string(),
1003            cookies: vec![],
1004            headers: vec![],
1005            content: HarContent {
1006                size: 0,
1007                compression: None,
1008                mime_type: "application/json".to_string(),
1009                text: Some("".to_string()),
1010                encoding: None,
1011            },
1012            redirect_url: "".to_string(),
1013            headers_size: 100,
1014            body_size: 0,
1015        };
1016        assert_eq!(
1017            extract_response_body(&response_with_empty_text),
1018            json!({"message": "Mock response from HAR import"})
1019        );
1020
1021        // Test with no text
1022        let response_with_no_text = HarResponse {
1023            status: 500,
1024            status_text: "Internal Server Error".to_string(),
1025            http_version: "HTTP/1.1".to_string(),
1026            cookies: vec![],
1027            headers: vec![],
1028            content: HarContent {
1029                size: 0,
1030                compression: None,
1031                mime_type: "application/json".to_string(),
1032                text: None,
1033                encoding: None,
1034            },
1035            redirect_url: "".to_string(),
1036            headers_size: 100,
1037            body_size: 0,
1038        };
1039        assert_eq!(
1040            extract_response_body(&response_with_no_text),
1041            json!({"message": "Mock response from HAR import"})
1042        );
1043
1044        // Test with complex JSON
1045        let response_with_complex_json = HarResponse {
1046            status: 200,
1047            status_text: "OK".to_string(),
1048            http_version: "HTTP/1.1".to_string(),
1049            cookies: vec![],
1050            headers: vec![],
1051            content: HarContent {
1052                size: 100,
1053                compression: None,
1054                mime_type: "application/json".to_string(),
1055                text: Some(r#"{"users": [{"id": 1, "name": "John"}, {"id": 2, "name": "Jane"}], "total": 2}"#.to_string()),
1056                encoding: None,
1057            },
1058            redirect_url: "".to_string(),
1059            headers_size: 100,
1060            body_size: 100,
1061        };
1062        let expected =
1063            json!({"users": [{"id": 1, "name": "John"}, {"id": 2, "name": "Jane"}], "total": 2});
1064        assert_eq!(extract_response_body(&response_with_complex_json), expected);
1065    }
1066
1067    #[test]
1068    fn test_parse_har_with_different_status_codes() {
1069        let har_json = r#"{
1070            "log": {
1071                "version": "1.2",
1072                "creator": {"name": "Test", "version": "1.0"},
1073                "entries": [
1074                    {
1075                        "startedDateTime": "2024-01-15T10:30:00Z",
1076                        "time": 123.45,
1077                        "request": {"method": "GET", "url": "https://api.example.com/test", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [], "queryString": [], "headersSize": 100, "bodySize": 0},
1078                        "response": {"status": 404, "statusText": "Not Found", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [], "content": {"size": 0, "mimeType": "application/json", "text": null}, "redirectURL": "", "headersSize": 100, "bodySize": 0},
1079                        "cache": {}, "timings": {}
1080                    },
1081                    {
1082                        "startedDateTime": "2024-01-15T10:31:00Z",
1083                        "time": 234.56,
1084                        "request": {"method": "POST", "url": "https://api.example.com/test", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [], "queryString": [], "headersSize": 100, "bodySize": 0},
1085                        "response": {"status": 500, "statusText": "Internal Server Error", "httpVersion": "HTTP/1.1", "cookies": [], "headers": [], "content": {"size": 0, "mimeType": "application/json", "text": null}, "redirectURL": "", "headersSize": 100, "bodySize": 0},
1086                        "cache": {}, "timings": {}
1087                    }
1088                ]
1089            }
1090        }"#;
1091
1092        let result = import_har_archive(har_json, Some("https://api.example.com")).unwrap();
1093
1094        assert_eq!(result.routes.len(), 2);
1095        assert_eq!(result.routes[0].response.status, 404);
1096        assert_eq!(result.routes[1].response.status, 500);
1097    }
1098
1099    #[test]
1100    fn test_parse_har_with_invalid_json() {
1101        let invalid_har_json = r#"{
1102            "log": {
1103                "version": "1.2",
1104                "creator": {"name": "Test", "version": "1.0"},
1105                "entries": [
1106                    {
1107                        "startedDateTime": "2024-01-15T10:30:00Z",
1108                        "time": "invalid_number",
1109                        "request": {"method": "GET", "url": "https://api.example.com/test", "httpVersion": "HTTP/1.1"},
1110                        "response": {"status": 200, "statusText": "OK", "httpVersion": "HTTP/1.1"},
1111                        "cache": {},
1112                        "timings": {}
1113                    }
1114                ]
1115            }
1116        }"#;
1117
1118        let result = import_har_archive(invalid_har_json, Some("https://api.example.com"));
1119        assert!(result.is_err());
1120    }
1121}