1use serde::{Deserialize, Serialize};
7use serde_json::{json, Value};
8use std::collections::HashMap;
9
10#[derive(Debug, Deserialize)]
12pub struct PostmanCollection {
13 pub info: CollectionInfo,
15 pub item: Vec<CollectionItem>,
17 #[serde(default)]
19 pub variable: Vec<Variable>,
20}
21
22#[derive(Debug, Deserialize)]
24pub struct CollectionInfo {
25 #[serde(rename = "_postman_id")]
27 pub postman_id: Option<String>,
28 pub name: String,
30 pub description: Option<String>,
32 pub schema: Option<String>,
34}
35
36#[derive(Debug, Deserialize)]
38pub struct CollectionItem {
39 pub name: String,
41 #[serde(default)]
43 pub item: Vec<CollectionItem>, pub request: Option<PostmanRequest>,
46}
47
48#[derive(Debug, Deserialize)]
50pub struct PostmanRequest {
51 pub method: String,
53 #[serde(default)]
55 pub header: Vec<Header>,
56 pub url: UrlOrString,
58 #[serde(default)]
60 pub body: Option<RequestBody>,
61 pub auth: Option<Auth>,
63}
64
65#[derive(Debug, Deserialize)]
67#[serde(untagged)]
68pub enum UrlOrString {
69 String(String),
71 Structured(StructuredUrl),
73}
74
75#[derive(Debug, Deserialize)]
77pub struct StructuredUrl {
78 pub raw: Option<String>,
80 pub protocol: Option<String>,
82 pub host: Option<Vec<String>>,
84 pub path: Option<Vec<StringOrVariable>>,
86 #[serde(default)]
88 pub query: Vec<QueryParam>,
89 #[serde(default)]
91 pub variable: Vec<Variable>,
92}
93
94#[derive(Debug, Deserialize)]
96#[serde(untagged)]
97pub enum StringOrVariable {
98 String(String),
100 Variable(Variable),
102}
103
104#[derive(Debug, Deserialize)]
106pub struct QueryParam {
107 pub key: Option<String>,
109 pub value: Option<String>,
111 pub description: Option<String>,
113 #[serde(default)]
115 pub disabled: bool,
116}
117
118#[derive(Debug, Deserialize)]
120pub struct Header {
121 pub key: String,
123 pub value: String,
125 #[serde(default)]
127 pub disabled: bool,
128}
129
130#[derive(Debug, Deserialize)]
132pub struct RequestBody {
133 pub mode: String,
135 pub raw: Option<String>,
137 pub urlencoded: Option<Vec<FormParam>>,
139 pub formdata: Option<Vec<FormParam>>,
141}
142
143#[derive(Debug, Deserialize)]
145pub struct FormParam {
146 pub key: String,
148 pub value: String,
150 #[serde(rename = "type")]
152 pub param_type: Option<String>,
153}
154
155#[derive(Debug, Deserialize)]
157pub struct Auth {
158 #[serde(rename = "type")]
160 pub auth_type: String,
161 #[serde(flatten)]
163 pub config: Value,
164}
165
166#[derive(Debug, Deserialize)]
168pub struct Variable {
169 pub key: String,
171 pub value: Option<String>,
173 #[serde(rename = "type")]
175 pub var_type: Option<String>,
176}
177
178#[derive(Debug, Serialize)]
180pub struct MockForgeRoute {
181 pub method: String,
183 pub path: String,
185 pub headers: HashMap<String, String>,
187 pub body: Option<String>,
189 pub response: MockForgeResponse,
191}
192
193#[derive(Debug, Serialize)]
195pub struct MockForgeResponse {
196 pub status: u16,
198 pub headers: HashMap<String, String>,
200 pub body: Value,
202}
203
204pub struct ImportResult {
206 pub routes: Vec<MockForgeRoute>,
208 pub variables: HashMap<String, String>,
210 pub warnings: Vec<String>,
212}
213
214pub fn import_postman_collection(
216 content: &str,
217 base_url: Option<&str>,
218) -> Result<ImportResult, String> {
219 let collection: PostmanCollection = serde_json::from_str(content)
220 .map_err(|e| format!("Failed to parse Postman collection: {}", e))?;
221
222 let mut routes = Vec::new();
223 let mut variables = HashMap::new();
224 let mut warnings = Vec::new();
225
226 for var in &collection.variable {
228 if let Some(value) = &var.value {
229 variables.insert(var.key.clone(), value.clone());
230 }
231 }
232
233 process_items(&collection.item, &mut routes, &variables, base_url, &mut warnings);
235
236 Ok(ImportResult {
237 routes,
238 variables,
239 warnings,
240 })
241}
242
243fn process_items(
245 items: &[CollectionItem],
246 routes: &mut Vec<MockForgeRoute>,
247 variables: &HashMap<String, String>,
248 base_url: Option<&str>,
249 warnings: &mut Vec<String>,
250) {
251 for item in items {
252 if item.request.is_some() {
254 if let Some(request) = &item.request {
255 match convert_request_to_route(request, &item.name, variables, base_url) {
257 Ok(route) => routes.push(route),
258 Err(e) => {
259 warnings.push(format!("Failed to convert request '{}': {}", item.name, e))
260 }
261 }
262 }
263 } else if !item.item.is_empty() {
264 process_items(&item.item, routes, variables, base_url, warnings);
266 }
267 }
268}
269
270fn convert_request_to_route(
272 request: &PostmanRequest,
273 _name: &str,
274 variables: &HashMap<String, String>,
275 base_url: Option<&str>,
276) -> Result<MockForgeRoute, String> {
277 let url = build_url(&request.url, variables, base_url)?;
279
280 let mut headers = HashMap::new();
282 for header in &request.header {
283 if !header.disabled && !header.key.is_empty() {
284 headers.insert(header.key.clone(), resolve_variables(&header.value, variables));
285 }
286 }
287
288 let body = match &request.body {
290 Some(body) if body.mode == "raw" => {
291 body.raw.as_ref().map(|raw| resolve_variables(raw, variables))
292 }
293 Some(body) if body.mode == "urlencoded" => {
294 if let Some(form_params) = &body.urlencoded {
295 let encoded_params: Vec<String> = form_params
296 .iter()
297 .map(|param| {
298 let key = resolve_variables(¶m.key, variables);
299 let value = resolve_variables(¶m.value, variables);
300 format!("{}={}", key, value)
301 })
302 .collect();
303 if encoded_params.is_empty() {
304 None
305 } else {
306 Some(encoded_params.join("&"))
307 }
308 } else {
309 None
310 }
311 }
312 Some(body) if body.mode == "formdata" => {
313 if let Some(form_params) = &body.formdata {
314 let encoded_params: Vec<String> = form_params
315 .iter()
316 .map(|param| {
317 let key = resolve_variables(¶m.key, variables);
318 let value = resolve_variables(¶m.value, variables);
319 format!("{}={}", key, value)
320 })
321 .collect();
322 if encoded_params.is_empty() {
323 None
324 } else {
325 Some(encoded_params.join("&"))
326 }
327 } else {
328 None
329 }
330 }
331 _ => None,
332 };
333
334 let response = generate_mock_response(request, variables);
336
337 Ok(MockForgeRoute {
338 method: request.method.clone(),
339 path: url,
340 headers,
341 body,
342 response,
343 })
344}
345
346fn build_url(
348 url: &UrlOrString,
349 variables: &HashMap<String, String>,
350 base_url: Option<&str>,
351) -> Result<String, String> {
352 let raw_url = match url {
353 UrlOrString::String(s) => resolve_variables(s, variables),
354 UrlOrString::Structured(structured) => {
355 if let Some(raw) = &structured.raw {
356 resolve_variables(raw, variables)
357 } else {
358 let mut url_parts = Vec::new();
360
361 if let Some(protocol) = &structured.protocol {
363 url_parts.push(format!("{}://", protocol));
364 }
365
366 if let Some(host_parts) = &structured.host {
368 let host = host_parts.join(".");
369 url_parts.push(resolve_variables(&host, variables));
370 }
371
372 if let Some(path_parts) = &structured.path {
374 let path: Vec<String> = path_parts
375 .iter()
376 .map(|part| match part {
377 StringOrVariable::String(s) => resolve_variables(s, variables),
378 StringOrVariable::Variable(var) => {
379 if let Some(value) = variables.get(&var.key) {
380 value.clone()
381 } else {
382 var.key.clone()
383 }
384 }
385 })
386 .collect();
387 url_parts.push(path.join("/"));
388 }
389
390 let query_parts: Vec<String> = structured
392 .query
393 .iter()
394 .filter(|q| !q.disabled && q.key.is_some())
395 .map(|q| {
396 let key = resolve_variables(q.key.as_ref().unwrap(), variables);
397 let value = q
398 .value
399 .as_ref()
400 .map(|v| resolve_variables(v, variables))
401 .unwrap_or_default();
402 format!("{}={}", key, value)
403 })
404 .collect();
405
406 if !query_parts.is_empty() {
407 url_parts.push(format!("?{}", query_parts.join("&")));
408 }
409
410 url_parts.join("")
411 }
412 }
413 };
414
415 if let Some(base) = base_url {
417 if raw_url.starts_with(base) {
418 let relative_path = raw_url.trim_start_matches(base).trim_start_matches('/');
419 return Ok(if relative_path.is_empty() {
420 "/".to_string()
421 } else {
422 format!("/{}", relative_path)
423 });
424 }
425 }
426
427 if let Ok(url) = url::Url::parse(&raw_url) {
429 Ok(url.path().to_string())
430 } else {
431 Ok(raw_url)
433 }
434}
435
436fn resolve_variables(input: &str, variables: &HashMap<String, String>) -> String {
438 let mut result = input.to_string();
439 for (key, value) in variables {
440 let pattern = format!("{{{{{}}}}}", key);
441 result = result.replace(&pattern, value);
442 }
443 result
444}
445
446fn generate_mock_response(
448 request: &PostmanRequest,
449 _variables: &HashMap<String, String>,
450) -> MockForgeResponse {
451 let mut headers = HashMap::new();
452 headers.insert("Content-Type".to_string(), "application/json".to_string());
453
454 let body = match request.method.as_str() {
455 "GET" => json!({"message": "Mock GET response", "method": "GET"}),
456 "POST" => json!({"message": "Mock POST response", "method": "POST", "created": true}),
457 "PUT" => json!({"message": "Mock PUT response", "method": "PUT", "updated": true}),
458 "DELETE" => json!({"message": "Mock DELETE response", "method": "DELETE", "deleted": true}),
459 "PATCH" => json!({"message": "Mock PATCH response", "method": "PATCH", "patched": true}),
460 _ => json!({"message": "Mock response", "method": &request.method}),
461 };
462
463 MockForgeResponse {
464 status: 200,
465 headers,
466 body,
467 }
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473
474 #[test]
475 fn test_parse_postman_collection() {
476 let collection_json = r#"{
477 "info": {
478 "_postman_id": "test-id",
479 "name": "Test Collection",
480 "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
481 },
482 "item": [
483 {
484 "name": "Get Users",
485 "request": {
486 "method": "GET",
487 "header": [{"key": "Authorization", "value": "Bearer {{token}}"}],
488 "url": {"raw": "{{baseUrl}}/users"}
489 }
490 }
491 ],
492 "variable": [
493 {"key": "baseUrl", "value": "https://api.example.com"},
494 {"key": "token", "value": "test-token"}
495 ]
496 }"#;
497
498 let result =
499 import_postman_collection(collection_json, Some("https://api.example.com")).unwrap();
500
501 assert_eq!(result.routes.len(), 1);
502 assert_eq!(result.routes[0].method, "GET");
503 assert_eq!(result.routes[0].path, "/users");
504 assert!(result.routes[0].headers.contains_key("Authorization"));
505 }
506
507 #[test]
508 fn test_parse_postman_collection_with_multiple_requests() {
509 let collection_json = r#"{
510 "info": {
511 "name": "Test Collection",
512 "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
513 },
514 "item": [
515 {
516 "name": "Get Users",
517 "request": {
518 "method": "GET",
519 "header": [],
520 "url": "https://api.example.com/users"
521 }
522 },
523 {
524 "name": "Create User",
525 "request": {
526 "method": "POST",
527 "header": [{"key": "Content-Type", "value": "application/json"}],
528 "url": "https://api.example.com/users",
529 "body": {
530 "mode": "raw",
531 "raw": "{\"name\": \"John\", \"age\": 30}"
532 }
533 }
534 },
535 {
536 "name": "Update User",
537 "request": {
538 "method": "PUT",
539 "header": [{"key": "Content-Type", "value": "application/json"}],
540 "url": "https://api.example.com/users/123",
541 "body": {
542 "mode": "raw",
543 "raw": "{\"name\": \"Jane\"}"
544 }
545 }
546 }
547 ]
548 }"#;
549
550 let result =
551 import_postman_collection(collection_json, Some("https://api.example.com")).unwrap();
552
553 assert_eq!(result.routes.len(), 3);
554
555 assert_eq!(result.routes[0].method, "GET");
557 assert_eq!(result.routes[0].path, "/users");
558
559 assert_eq!(result.routes[1].method, "POST");
561 assert_eq!(result.routes[1].path, "/users");
562 assert_eq!(result.routes[1].body, Some("{\"name\": \"John\", \"age\": 30}".to_string()));
563 assert_eq!(
564 result.routes[1].headers.get("Content-Type"),
565 Some(&"application/json".to_string())
566 );
567
568 assert_eq!(result.routes[2].method, "PUT");
570 assert_eq!(result.routes[2].path, "/users/123");
571 assert_eq!(result.routes[2].body, Some("{\"name\": \"Jane\"}".to_string()));
572 }
573
574 #[test]
575 fn test_parse_postman_collection_with_folders() {
576 let collection_json = r#"{
577 "info": {
578 "name": "Test Collection",
579 "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
580 },
581 "item": [
582 {
583 "name": "User Operations",
584 "item": [
585 {
586 "name": "Get Users",
587 "request": {
588 "method": "GET",
589 "header": [],
590 "url": "https://api.example.com/users"
591 }
592 },
593 {
594 "name": "Create User",
595 "request": {
596 "method": "POST",
597 "header": [],
598 "url": "https://api.example.com/users"
599 }
600 }
601 ]
602 },
603 {
604 "name": "Admin Operations",
605 "item": [
606 {
607 "name": "Get Stats",
608 "request": {
609 "method": "GET",
610 "header": [],
611 "url": "https://api.example.com/admin/stats"
612 }
613 }
614 ]
615 }
616 ]
617 }"#;
618
619 let result =
620 import_postman_collection(collection_json, Some("https://api.example.com")).unwrap();
621
622 assert_eq!(result.routes.len(), 3);
623
624 assert_eq!(result.routes[0].method, "GET");
626 assert_eq!(result.routes[0].path, "/users");
627
628 assert_eq!(result.routes[1].method, "POST");
629 assert_eq!(result.routes[1].path, "/users");
630
631 assert_eq!(result.routes[2].method, "GET");
632 assert_eq!(result.routes[2].path, "/admin/stats");
633 }
634
635 #[test]
636 fn test_parse_postman_collection_with_query_parameters() {
637 let collection_json = r#"{
638 "info": {
639 "name": "Test Collection",
640 "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
641 },
642 "item": [
643 {
644 "name": "Search Users",
645 "request": {
646 "method": "GET",
647 "header": [],
648 "url": {
649 "raw": "https://api.example.com/search?q=test&page=1&limit=10",
650 "host": ["api", "example", "com"],
651 "path": ["search"],
652 "query": [
653 {"key": "q", "value": "test"},
654 {"key": "page", "value": "1"},
655 {"key": "limit", "value": "10"}
656 ]
657 }
658 }
659 }
660 ]
661 }"#;
662
663 let result =
664 import_postman_collection(collection_json, Some("https://api.example.com")).unwrap();
665
666 assert_eq!(result.routes.len(), 1);
667 assert_eq!(result.routes[0].method, "GET");
668 assert_eq!(result.routes[0].path, "/search?q=test&page=1&limit=10");
669 }
670
671 #[test]
672 fn test_parse_postman_collection_with_different_methods() {
673 let collection_json = r#"{
674 "info": {
675 "name": "Test Collection",
676 "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
677 },
678 "item": [
679 {"name": "GET Request", "request": {"method": "GET", "url": "https://api.example.com/get"}},
680 {"name": "POST Request", "request": {"method": "POST", "url": "https://api.example.com/post"}},
681 {"name": "PUT Request", "request": {"method": "PUT", "url": "https://api.example.com/put"}},
682 {"name": "DELETE Request", "request": {"method": "DELETE", "url": "https://api.example.com/delete"}},
683 {"name": "PATCH Request", "request": {"method": "PATCH", "url": "https://api.example.com/patch"}},
684 {"name": "HEAD Request", "request": {"method": "HEAD", "url": "https://api.example.com/head"}},
685 {"name": "OPTIONS Request", "request": {"method": "OPTIONS", "url": "https://api.example.com/options"}}
686 ]
687 }"#;
688
689 let result =
690 import_postman_collection(collection_json, Some("https://api.example.com")).unwrap();
691
692 assert_eq!(result.routes.len(), 7);
693
694 let expected_methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
695 for (i, expected_method) in expected_methods.iter().enumerate() {
696 assert_eq!(result.routes[i].method, *expected_method);
697 assert_eq!(result.routes[i].path, format!("/{}", expected_method.to_lowercase()));
698 }
699 }
700
701 #[test]
702 fn test_parse_postman_collection_with_form_data() {
703 let collection_json = r#"{
704 "info": {
705 "name": "Test Collection",
706 "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
707 },
708 "item": [
709 {
710 "name": "Form Submit",
711 "request": {
712 "method": "POST",
713 "header": [{"key": "Content-Type", "value": "application/x-www-form-urlencoded"}],
714 "url": "https://api.example.com/form",
715 "body": {
716 "mode": "urlencoded",
717 "urlencoded": [
718 {"key": "username", "value": "john_doe"},
719 {"key": "password", "value": "secret123"},
720 {"key": "remember", "value": "true"}
721 ]
722 }
723 }
724 }
725 ]
726 }"#;
727
728 let result =
729 import_postman_collection(collection_json, Some("https://api.example.com")).unwrap();
730
731 assert_eq!(result.routes.len(), 1);
732 assert_eq!(result.routes[0].method, "POST");
733 assert_eq!(result.routes[0].path, "/form");
734 assert_eq!(
735 result.routes[0].body,
736 Some("username=john_doe&password=secret123&remember=true".to_string())
737 );
738 }
739
740 #[test]
741 fn test_parse_postman_collection_with_raw_body() {
742 let collection_json = r#"{
743 "info": {
744 "name": "Test Collection",
745 "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
746 },
747 "item": [
748 {
749 "name": "JSON Post",
750 "request": {
751 "method": "POST",
752 "header": [{"key": "Content-Type", "value": "application/json"}],
753 "url": "https://api.example.com/json",
754 "body": {
755 "mode": "raw",
756 "raw": "{\"message\": \"Hello World\", \"data\": {\"key\": \"value\"}}"
757 }
758 }
759 },
760 {
761 "name": "XML Post",
762 "request": {
763 "method": "POST",
764 "header": [{"key": "Content-Type", "value": "application/xml"}],
765 "url": "https://api.example.com/xml",
766 "body": {
767 "mode": "raw",
768 "raw": "<root><message>Hello</message><data><key>value</key></data></root>"
769 }
770 }
771 }
772 ]
773 }"#;
774
775 let result =
776 import_postman_collection(collection_json, Some("https://api.example.com")).unwrap();
777
778 assert_eq!(result.routes.len(), 2);
779
780 assert_eq!(result.routes[0].method, "POST");
782 assert_eq!(result.routes[0].path, "/json");
783 assert_eq!(
784 result.routes[0].body,
785 Some("{\"message\": \"Hello World\", \"data\": {\"key\": \"value\"}}".to_string())
786 );
787
788 assert_eq!(result.routes[1].method, "POST");
790 assert_eq!(result.routes[1].path, "/xml");
791 assert_eq!(
792 result.routes[1].body,
793 Some("<root><message>Hello</message><data><key>value</key></data></root>".to_string())
794 );
795 }
796
797 #[test]
798 fn test_parse_postman_collection_with_auth() {
799 let collection_json = r#"{
800 "info": {
801 "name": "Test Collection",
802 "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
803 },
804 "item": [
805 {
806 "name": "Protected Request",
807 "request": {
808 "method": "GET",
809 "header": [
810 {"key": "Authorization", "value": "Bearer {{token}}"},
811 {"key": "X-API-Key", "value": "api-key-123"}
812 ],
813 "url": "https://api.example.com/protected",
814 "auth": {
815 "type": "bearer",
816 "bearer": [
817 {"key": "token", "value": "{{token}}", "type": "string"}
818 ]
819 }
820 }
821 }
822 ],
823 "variable": [
824 {"key": "token", "value": "test-token-abc"}
825 ]
826 }"#;
827
828 let result =
829 import_postman_collection(collection_json, Some("https://api.example.com")).unwrap();
830
831 assert_eq!(result.routes.len(), 1);
832 assert_eq!(result.routes[0].method, "GET");
833 assert_eq!(result.routes[0].path, "/protected");
834 assert_eq!(
835 result.routes[0].headers.get("Authorization"),
836 Some(&"Bearer test-token-abc".to_string())
837 );
838 assert_eq!(result.routes[0].headers.get("X-API-Key"), Some(&"api-key-123".to_string()));
839 }
840
841 #[test]
842 fn test_parse_postman_collection_with_variables() {
843 let collection_json = r#"{
844 "info": {
845 "name": "Test Collection",
846 "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
847 },
848 "item": [
849 {
850 "name": "Variable Test",
851 "request": {
852 "method": "GET",
853 "header": [
854 {"key": "X-User-ID", "value": "{{userId}}"},
855 {"key": "X-Environment", "value": "{{environment}}"}
856 ],
857 "url": "{{baseUrl}}/test/{{userId}}?env={{environment}}"
858 }
859 }
860 ],
861 "variable": [
862 {"key": "baseUrl", "value": "https://api.example.com"},
863 {"key": "userId", "value": "12345"},
864 {"key": "environment", "value": "production"}
865 ]
866 }"#;
867
868 let result =
869 import_postman_collection(collection_json, Some("https://api.example.com")).unwrap();
870
871 assert_eq!(result.routes.len(), 1);
872 assert_eq!(result.routes[0].method, "GET");
873 assert_eq!(result.routes[0].path, "/test/12345?env=production");
874 assert_eq!(result.routes[0].headers.get("X-User-ID"), Some(&"12345".to_string()));
875 assert_eq!(result.routes[0].headers.get("X-Environment"), Some(&"production".to_string()));
876 }
877
878 #[test]
879 fn test_parse_postman_collection_with_disabled_items() {
880 let collection_json = r#"{
881 "info": {
882 "name": "Test Collection",
883 "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
884 },
885 "item": [
886 {
887 "name": "Enabled Request",
888 "request": {
889 "method": "GET",
890 "url": "https://api.example.com/enabled"
891 }
892 },
893 {
894 "name": "Disabled Request",
895 "request": {
896 "method": "GET",
897 "url": "https://api.example.com/disabled"
898 }
899 }
900 ]
901 }"#;
902
903 let result =
904 import_postman_collection(collection_json, Some("https://api.example.com")).unwrap();
905
906 assert_eq!(result.routes.len(), 2);
909 }
910
911 #[test]
912 fn test_parse_postman_collection_with_complex_headers() {
913 let collection_json = r#"{
914 "info": {
915 "name": "Test Collection",
916 "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
917 },
918 "item": [
919 {
920 "name": "Complex Headers",
921 "request": {
922 "method": "GET",
923 "header": [
924 {"key": "Authorization", "value": "Bearer token123"},
925 {"key": "Content-Type", "value": "application/json"},
926 {"key": "Accept", "value": "application/json"},
927 {"key": "X-Custom-Header", "value": "custom-value"},
928 {"key": "X-Request-ID", "value": "req-123"},
929 {"key": "User-Agent", "value": "PostmanRuntime/7.29.0"},
930 {"key": "Cache-Control", "value": "no-cache"}
931 ],
932 "url": "https://api.example.com/complex"
933 }
934 }
935 ]
936 }"#;
937
938 let result =
939 import_postman_collection(collection_json, Some("https://api.example.com")).unwrap();
940
941 assert_eq!(result.routes.len(), 1);
942 assert_eq!(result.routes[0].method, "GET");
943 assert_eq!(result.routes[0].path, "/complex");
944
945 let headers = &result.routes[0].headers;
947 assert_eq!(headers.get("Authorization"), Some(&"Bearer token123".to_string()));
948 assert_eq!(headers.get("Content-Type"), Some(&"application/json".to_string()));
949 assert_eq!(headers.get("Accept"), Some(&"application/json".to_string()));
950 assert_eq!(headers.get("X-Custom-Header"), Some(&"custom-value".to_string()));
951 assert_eq!(headers.get("X-Request-ID"), Some(&"req-123".to_string()));
952 assert_eq!(headers.get("User-Agent"), Some(&"PostmanRuntime/7.29.0".to_string()));
953 assert_eq!(headers.get("Cache-Control"), Some(&"no-cache".to_string()));
954 }
955}