1pub mod core;
12pub mod environment;
13pub mod mock_environment;
14pub mod promotion_trait;
15pub mod rbac;
16pub mod registry;
17pub mod request;
18pub mod scenario_promotion;
19pub mod sync;
20pub mod template_application;
21
22pub use environment::*;
24pub use mock_environment::*;
25pub use rbac::*;
26pub use registry::*;
27pub use request::*;
28pub use scenario_promotion::*;
29pub use sync::*;
30pub use template_application::*;
31
32use crate::config::AuthConfig;
34use crate::encryption::AutoEncryptionConfig;
35use crate::fidelity::FidelityScore;
36use crate::reality::RealityLevel;
37use crate::routing::{HttpMethod, Route, RouteRegistry};
38use crate::{Error, Result};
39use chrono::{DateTime, Utc};
40use serde::{Deserialize, Serialize};
41use std::collections::HashMap;
42use uuid::Uuid;
43
44pub type EntityId = String;
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct Workspace {
50 pub id: EntityId,
52 pub name: String,
54 pub description: Option<String>,
56 pub created_at: DateTime<Utc>,
58 pub updated_at: DateTime<Utc>,
60 pub tags: Vec<String>,
62 pub config: WorkspaceConfig,
64 pub folders: Vec<Folder>,
66 pub requests: Vec<MockRequest>,
68 #[serde(default)]
70 pub order: i32,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, Default)]
75pub struct FolderInheritanceConfig {
76 #[serde(default)]
78 pub headers: HashMap<String, String>,
79 pub auth: Option<AuthConfig>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct Folder {
86 pub id: EntityId,
88 pub name: String,
90 pub description: Option<String>,
92 pub parent_id: Option<EntityId>,
94 pub created_at: DateTime<Utc>,
96 pub updated_at: DateTime<Utc>,
98 pub tags: Vec<String>,
100 #[serde(default)]
102 pub inheritance: FolderInheritanceConfig,
103 pub folders: Vec<Folder>,
105 pub requests: Vec<MockRequest>,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct MockRequest {
112 pub id: EntityId,
114 pub name: String,
116 pub description: Option<String>,
118 pub method: HttpMethod,
120 pub path: String,
122 pub headers: HashMap<String, String>,
124 pub query_params: HashMap<String, String>,
126 pub body: Option<String>,
128 pub response: MockResponse,
130 pub response_history: Vec<ResponseHistoryEntry>,
132 pub created_at: DateTime<Utc>,
134 pub updated_at: DateTime<Utc>,
136 pub tags: Vec<String>,
138 pub auth: Option<AuthConfig>,
140 pub priority: i32,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct MockResponse {
147 pub status_code: u16,
149 pub headers: HashMap<String, String>,
151 pub body: Option<String>,
153 pub content_type: Option<String>,
155 pub delay_ms: Option<u64>,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct ResponseHistoryEntry {
162 pub id: String,
164 pub executed_at: DateTime<Utc>,
166 pub request_method: HttpMethod,
168 pub request_path: String,
170 pub request_headers: HashMap<String, String>,
172 pub request_body: Option<String>,
174 pub response_status_code: u16,
176 pub response_headers: HashMap<String, String>,
178 pub response_body: Option<String>,
180 pub response_time_ms: u64,
182 pub response_size_bytes: u64,
184 pub error_message: Option<String>,
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct EnvironmentColor {
191 pub hex: String,
193 pub name: Option<String>,
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct Environment {
200 pub id: EntityId,
202 pub name: String,
204 pub description: Option<String>,
206 pub color: Option<EnvironmentColor>,
208 pub variables: HashMap<String, String>,
210 pub created_at: DateTime<Utc>,
212 pub updated_at: DateTime<Utc>,
214 #[serde(default)]
216 pub order: i32,
217 #[serde(default)]
219 pub sharable: bool,
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct SyncConfig {
225 pub enabled: bool,
227 pub target_directory: Option<String>,
229 pub directory_structure: SyncDirectoryStructure,
231 pub sync_direction: SyncDirection,
233 pub include_metadata: bool,
235 pub realtime_monitoring: bool,
237 pub filename_pattern: String,
239 pub exclude_pattern: Option<String>,
241 pub force_overwrite: bool,
243 pub last_sync: Option<DateTime<Utc>>,
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize)]
249pub enum SyncDirectoryStructure {
250 Flat,
252 Nested,
254 Grouped,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize)]
260pub enum SyncDirection {
261 Manual,
263 WorkspaceToDirectory,
265 Bidirectional,
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct WorkspaceConfig {
272 pub base_url: Option<String>,
274 pub default_headers: HashMap<String, String>,
276 pub auth: Option<AuthConfig>,
278 pub global_environment: Environment,
280 pub environments: Vec<Environment>,
282 pub active_environment_id: Option<EntityId>,
284 #[serde(default)]
287 pub mock_environments: MockEnvironmentManager,
288 pub sync: SyncConfig,
290 #[serde(default)]
292 pub auto_encryption: AutoEncryptionConfig,
293 #[serde(default)]
297 pub reality_level: Option<RealityLevel>,
298 #[serde(default)]
301 pub ai_mode: Option<crate::ai_studio::config::AiMode>,
302 #[serde(default, skip_serializing_if = "Option::is_none")]
305 pub fidelity_score: Option<FidelityScore>,
306}
307
308#[derive(Debug, Clone)]
310pub struct WorkspaceRegistry {
311 workspaces: HashMap<EntityId, Workspace>,
312 active_workspace: Option<EntityId>,
313}
314
315impl Workspace {
316 pub fn new(name: String) -> Self {
318 let now = Utc::now();
319 let workspace_id = Uuid::new_v4().to_string();
320 let mut workspace = Self {
321 id: workspace_id.clone(),
322 name,
323 description: None,
324 created_at: now,
325 updated_at: now,
326 tags: Vec::new(),
327 config: WorkspaceConfig::default(),
328 folders: Vec::new(),
329 requests: Vec::new(),
330 order: 0, };
332
333 workspace.initialize_default_mock_environments();
335
336 workspace
337 }
338
339 pub fn initialize_default_mock_environments(&mut self) {
343 if self.config.mock_environments.workspace_id.is_empty()
345 || self.config.mock_environments.environments.is_empty()
346 {
347 if self.config.mock_environments.workspace_id.is_empty() {
349 self.config.mock_environments.workspace_id = self.id.clone();
350 }
351
352 if !self
354 .config
355 .mock_environments
356 .environments
357 .contains_key(&MockEnvironmentName::Dev)
358 {
359 let dev_env = MockEnvironment::new(self.id.clone(), MockEnvironmentName::Dev);
360 self.config.mock_environments.add_environment(dev_env);
361 }
362
363 if !self
365 .config
366 .mock_environments
367 .environments
368 .contains_key(&MockEnvironmentName::Test)
369 {
370 let test_env = MockEnvironment::new(self.id.clone(), MockEnvironmentName::Test);
371 self.config.mock_environments.add_environment(test_env);
372 }
373
374 if !self
376 .config
377 .mock_environments
378 .environments
379 .contains_key(&MockEnvironmentName::Prod)
380 {
381 let prod_env = MockEnvironment::new(self.id.clone(), MockEnvironmentName::Prod);
382 self.config.mock_environments.add_environment(prod_env);
383 }
384
385 if self.config.mock_environments.active_environment.is_none() {
387 let _ =
388 self.config.mock_environments.set_active_environment(MockEnvironmentName::Dev);
389 }
390 }
391 }
392
393 pub fn add_folder(&mut self, name: String) -> Result<EntityId> {
395 let folder = Folder::new(name);
396 let id = folder.id.clone();
397 self.folders.push(folder);
398 self.updated_at = Utc::now();
399 Ok(id)
400 }
401
402 pub fn create_environment(
404 &mut self,
405 name: String,
406 description: Option<String>,
407 ) -> Result<EntityId> {
408 if self.config.environments.iter().any(|env| env.name == name) {
410 return Err(Error::validation(format!(
411 "Environment with name '{}' already exists",
412 name
413 )));
414 }
415
416 let mut environment = Environment::new(name);
417 environment.description = description;
418
419 environment.order = self.config.environments.len() as i32;
421
422 let id = environment.id.clone();
423
424 self.config.environments.push(environment);
425 self.updated_at = Utc::now();
426 Ok(id)
427 }
428
429 pub fn get_environments(&self) -> Vec<&Environment> {
431 let mut all_envs = vec![&self.config.global_environment];
432 all_envs.extend(self.config.environments.iter());
433 all_envs
434 }
435
436 pub fn get_environment(&self, id: &str) -> Option<&Environment> {
438 if self.config.global_environment.id == id {
439 Some(&self.config.global_environment)
440 } else {
441 self.config.environments.iter().find(|env| env.id == id)
442 }
443 }
444
445 pub fn get_environment_mut(&mut self, id: &str) -> Option<&mut Environment> {
447 if self.config.global_environment.id == id {
448 Some(&mut self.config.global_environment)
449 } else {
450 self.config.environments.iter_mut().find(|env| env.id == id)
451 }
452 }
453
454 pub fn set_active_environment(&mut self, environment_id: Option<String>) -> Result<()> {
456 if let Some(ref id) = environment_id {
457 if self.get_environment(id).is_none() {
458 return Err(Error::not_found("Environment", id));
459 }
460 }
461 self.config.active_environment_id = environment_id;
462 self.updated_at = Utc::now();
463 Ok(())
464 }
465
466 pub fn get_active_environment(&self) -> &Environment {
468 if let Some(ref active_id) = self.config.active_environment_id {
469 self.get_environment(active_id).unwrap_or(&self.config.global_environment)
470 } else {
471 &self.config.global_environment
472 }
473 }
474
475 pub fn get_active_environment_id(&self) -> Option<&str> {
477 self.config.active_environment_id.as_deref()
478 }
479
480 pub fn get_variable(&self, key: &str) -> Option<&String> {
482 let active_env = self.get_active_environment();
484 active_env.get_variable(key).or_else(|| {
485 if active_env.id != self.config.global_environment.id {
486 self.config.global_environment.get_variable(key)
487 } else {
488 None
489 }
490 })
491 }
492
493 pub fn get_all_variables(&self) -> HashMap<String, String> {
495 let mut variables = HashMap::new();
496
497 variables.extend(self.config.global_environment.variables.clone());
499
500 let active_env = self.get_active_environment();
502 if active_env.id != self.config.global_environment.id {
503 variables.extend(active_env.variables.clone());
504 }
505
506 variables
507 }
508
509 pub fn delete_environment(&mut self, id: &str) -> Result<()> {
511 if id == self.config.global_environment.id {
512 return Err(Error::validation("Cannot delete global environment"));
513 }
514
515 let position = self.config.environments.iter().position(|env| env.id == id);
516 if let Some(pos) = position {
517 self.config.environments.remove(pos);
518
519 if self.config.active_environment_id.as_deref() == Some(id) {
521 self.config.active_environment_id = None;
522 }
523
524 self.updated_at = Utc::now();
525 Ok(())
526 } else {
527 Err(Error::not_found("Environment", id))
528 }
529 }
530
531 pub fn update_environments_order(&mut self, environment_ids: Vec<String>) -> Result<()> {
533 for env_id in &environment_ids {
535 if !self.config.environments.iter().any(|env| env.id == *env_id) {
536 return Err(Error::not_found("Environment", env_id));
537 }
538 }
539
540 for (index, env_id) in environment_ids.iter().enumerate() {
542 if let Some(env) = self.config.environments.iter_mut().find(|env| env.id == *env_id) {
543 env.order = index as i32;
544 env.updated_at = Utc::now();
545 }
546 }
547
548 self.updated_at = Utc::now();
549 Ok(())
550 }
551
552 pub fn get_environments_ordered(&self) -> Vec<&Environment> {
554 let mut all_envs = vec![&self.config.global_environment];
555 all_envs.extend(self.config.environments.iter());
556 all_envs.sort_by_key(|env| env.order);
557 all_envs
558 }
559
560 pub fn get_mock_environments(&self) -> &MockEnvironmentManager {
562 &self.config.mock_environments
563 }
564
565 pub fn get_mock_environments_mut(&mut self) -> &mut MockEnvironmentManager {
567 &mut self.config.mock_environments
568 }
569
570 pub fn get_mock_environment(&self, name: MockEnvironmentName) -> Option<&MockEnvironment> {
572 self.config.mock_environments.get_environment(name)
573 }
574
575 pub fn get_active_mock_environment(&self) -> Option<&MockEnvironment> {
577 self.config.mock_environments.get_active_environment()
578 }
579
580 pub fn set_active_mock_environment(&mut self, name: MockEnvironmentName) -> Result<()> {
582 self.config.mock_environments.set_active_environment(name)?;
583 self.updated_at = Utc::now();
584 Ok(())
585 }
586
587 pub fn list_mock_environments(&self) -> Vec<&MockEnvironment> {
589 self.config.mock_environments.list_environments()
590 }
591
592 pub fn set_mock_environment_config(
594 &mut self,
595 name: MockEnvironmentName,
596 reality_config: Option<crate::reality::RealityConfig>,
597 chaos_config: Option<crate::chaos_utilities::ChaosConfig>,
598 drift_budget_config: Option<crate::contract_drift::DriftBudgetConfig>,
599 ) -> Result<()> {
600 let env = if let Some(existing) = self.config.mock_environments.get_environment(name) {
602 MockEnvironment::with_configs(
603 existing.workspace_id.clone(),
604 name,
605 reality_config,
606 chaos_config,
607 drift_budget_config,
608 )
609 } else {
610 MockEnvironment::with_configs(
611 self.id.clone(),
612 name,
613 reality_config,
614 chaos_config,
615 drift_budget_config,
616 )
617 };
618
619 self.config.mock_environments.add_environment(env);
620 self.updated_at = Utc::now();
621 Ok(())
622 }
623
624 pub fn configure_sync(&mut self, config: SyncConfig) -> Result<()> {
626 self.config.sync = config;
627 self.updated_at = Utc::now();
628 Ok(())
629 }
630
631 pub fn enable_sync(&mut self, target_directory: String) -> Result<()> {
633 self.config.sync.enabled = true;
634 self.config.sync.target_directory = Some(target_directory);
635 self.config.sync.realtime_monitoring = true; self.updated_at = Utc::now();
637 Ok(())
638 }
639
640 pub fn disable_sync(&mut self) -> Result<()> {
642 self.config.sync.enabled = false;
643 self.updated_at = Utc::now();
644 Ok(())
645 }
646
647 pub fn get_sync_config(&self) -> &SyncConfig {
649 &self.config.sync
650 }
651
652 pub fn is_sync_enabled(&self) -> bool {
654 self.config.sync.enabled
655 }
656
657 pub fn get_sync_directory(&self) -> Option<&str> {
659 self.config.sync.target_directory.as_deref()
660 }
661
662 pub fn set_sync_directory(&mut self, directory: Option<String>) -> Result<()> {
664 self.config.sync.target_directory = directory;
665 self.updated_at = Utc::now();
666 Ok(())
667 }
668
669 pub fn set_sync_direction(&mut self, direction: SyncDirection) -> Result<()> {
671 self.config.sync.sync_direction = direction;
672 self.updated_at = Utc::now();
673 Ok(())
674 }
675
676 pub fn get_sync_direction(&self) -> &SyncDirection {
678 &self.config.sync.sync_direction
679 }
680
681 pub fn set_realtime_monitoring(&mut self, enabled: bool) -> Result<()> {
683 self.config.sync.realtime_monitoring = enabled;
684 self.updated_at = Utc::now();
685 Ok(())
686 }
687
688 pub fn is_realtime_monitoring_enabled(&self) -> bool {
690 self.config.sync.realtime_monitoring && self.config.sync.enabled
691 }
692
693 pub fn to_filtered_for_sync(&self) -> Workspace {
695 let mut filtered = self.clone();
696
697 filtered.config.global_environment.variables =
700 self.filter_sensitive_variables(&self.config.global_environment.variables);
701
702 filtered.config.environments = filtered
704 .config
705 .environments
706 .into_iter()
707 .filter(|env| env.sharable)
708 .map(|mut env| {
709 env.variables = self.filter_sensitive_variables(&env.variables);
710 env
711 })
712 .collect();
713
714 filtered
715 }
716
717 fn filter_sensitive_variables(
719 &self,
720 variables: &HashMap<String, String>,
721 ) -> HashMap<String, String> {
722 let sensitive_keys = [
723 "password",
725 "secret",
726 "key",
727 "token",
728 "credential",
729 "api_key",
730 "apikey",
731 "api_secret",
732 "db_password",
733 "database_password",
734 "aws_secret_key",
735 "aws_session_token",
736 "private_key",
737 "authorization",
738 "auth_token",
739 "access_token",
740 "refresh_token",
741 "cookie",
742 "session",
743 "csrf",
744 "jwt",
745 "bearer",
746 ];
747
748 variables
749 .iter()
750 .filter(|(key, _)| {
751 let key_lower = key.to_lowercase();
752 !sensitive_keys.iter().any(|sensitive| key_lower.contains(sensitive))
753 })
754 .map(|(k, v)| (k.clone(), v.clone()))
755 .collect()
756 }
757
758 pub fn should_sync(&self) -> bool {
760 self.config.sync.enabled && self.config.sync.target_directory.is_some()
761 }
762
763 pub fn get_sync_filename(&self) -> String {
765 let pattern = &self.config.sync.filename_pattern;
767
768 let filename = pattern
770 .replace("{name}", &sanitize_filename(&self.name))
771 .replace("{id}", &self.id);
772
773 if filename.ends_with(".yaml") || filename.ends_with(".yml") {
774 filename
775 } else {
776 format!("{}.yaml", filename)
777 }
778 }
779}
780
781fn sanitize_filename(name: &str) -> String {
783 name.chars()
784 .map(|c| match c {
785 '/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|' => '_',
786 c if c.is_control() => '_',
787 c if c.is_whitespace() => '-',
788 c => c,
789 })
790 .collect::<String>()
791 .to_lowercase()
792}
793
794impl Folder {
795 pub fn new(name: String) -> Self {
797 let now = Utc::now();
798 Self {
799 id: Uuid::new_v4().to_string(),
800 name,
801 description: None,
802 parent_id: None,
803 created_at: now,
804 updated_at: now,
805 tags: Vec::new(),
806 inheritance: FolderInheritanceConfig::default(),
807 folders: Vec::new(),
808 requests: Vec::new(),
809 }
810 }
811
812 pub fn add_folder(&mut self, name: String) -> Result<EntityId> {
814 let mut folder = Folder::new(name);
815 folder.parent_id = Some(self.id.clone());
816 let id = folder.id.clone();
817 self.folders.push(folder);
818 self.updated_at = Utc::now();
819 Ok(id)
820 }
821
822 pub fn add_request(&mut self, request: MockRequest) -> Result<EntityId> {
824 let id = request.id.clone();
825 self.requests.push(request);
826 self.updated_at = Utc::now();
827 Ok(id)
828 }
829
830 pub fn find_folder(&self, id: &str) -> Option<&Folder> {
832 for folder in &self.folders {
833 if folder.id == id {
834 return Some(folder);
835 }
836 if let Some(found) = folder.find_folder(id) {
837 return Some(found);
838 }
839 }
840 None
841 }
842
843 pub fn find_folder_mut(&mut self, id: &str) -> Option<&mut Folder> {
845 for folder in &mut self.folders {
846 if folder.id == id {
847 return Some(folder);
848 }
849 if let Some(found) = folder.find_folder_mut(id) {
850 return Some(found);
851 }
852 }
853 None
854 }
855
856 pub fn find_request(&self, id: &str) -> Option<&MockRequest> {
858 for request in &self.requests {
860 if request.id == id {
861 return Some(request);
862 }
863 }
864
865 for folder in &self.folders {
867 if let Some(found) = folder.find_request(id) {
868 return Some(found);
869 }
870 }
871 None
872 }
873
874 pub fn remove_folder(&mut self, id: &str) -> Result<()> {
876 if let Some(pos) = self.folders.iter().position(|f| f.id == id) {
877 self.folders.remove(pos);
878 self.updated_at = Utc::now();
879 return Ok(());
880 }
881 for folder in &mut self.folders {
882 if folder.remove_folder(id).is_ok() {
883 self.updated_at = Utc::now();
884 return Ok(());
885 }
886 }
887 Err(Error::not_found("Folder", id))
888 }
889
890 pub fn remove_request(&mut self, id: &str) -> Result<()> {
892 if let Some(pos) = self.requests.iter().position(|r| r.id == id) {
893 self.requests.remove(pos);
894 self.updated_at = Utc::now();
895 return Ok(());
896 }
897 for folder in &mut self.folders {
898 if folder.remove_request(id).is_ok() {
899 self.updated_at = Utc::now();
900 return Ok(());
901 }
902 }
903 Err(Error::not_found("Request", id))
904 }
905
906 pub fn get_routes(&self, workspace_id: &str) -> Vec<Route> {
908 let mut routes = Vec::new();
909
910 for request in &self.requests {
912 routes.push(
913 Route::new(request.method.clone(), request.path.clone())
914 .with_priority(request.priority)
915 .with_metadata("request_id".to_string(), serde_json::json!(request.id))
916 .with_metadata("folder_id".to_string(), serde_json::json!(self.id))
917 .with_metadata("workspace_id".to_string(), serde_json::json!(workspace_id)),
918 );
919 }
920
921 for folder in &self.folders {
923 routes.extend(folder.get_routes(workspace_id));
924 }
925
926 routes
927 }
928}
929
930impl Workspace {
931 pub fn find_folder(&self, id: &str) -> Option<&Folder> {
933 for folder in &self.folders {
934 if folder.id == id {
935 return Some(folder);
936 }
937 if let Some(found) = folder.find_folder(id) {
938 return Some(found);
939 }
940 }
941 None
942 }
943
944 pub fn find_folder_mut(&mut self, id: &str) -> Option<&mut Folder> {
946 for folder in &mut self.folders {
947 if folder.id == id {
948 return Some(folder);
949 }
950 if let Some(found) = folder.find_folder_mut(id) {
951 return Some(found);
952 }
953 }
954 None
955 }
956
957 pub fn add_request(&mut self, request: MockRequest) -> Result<EntityId> {
959 let id = request.id.clone();
960 self.requests.push(request);
961 self.updated_at = Utc::now();
962 Ok(id)
963 }
964
965 pub fn remove_folder(&mut self, id: &str) -> Result<()> {
967 if let Some(pos) = self.folders.iter().position(|f| f.id == id) {
968 self.folders.remove(pos);
969 self.updated_at = Utc::now();
970 return Ok(());
971 }
972 for folder in &mut self.folders {
973 if folder.remove_folder(id).is_ok() {
974 self.updated_at = Utc::now();
975 return Ok(());
976 }
977 }
978 Err(Error::not_found("Folder", id))
979 }
980
981 pub fn remove_request(&mut self, id: &str) -> Result<()> {
983 if let Some(pos) = self.requests.iter().position(|r| r.id == id) {
984 self.requests.remove(pos);
985 self.updated_at = Utc::now();
986 return Ok(());
987 }
988 for folder in &mut self.folders {
989 if folder.remove_request(id).is_ok() {
990 self.updated_at = Utc::now();
991 return Ok(());
992 }
993 }
994 Err(Error::not_found("Request", id))
995 }
996
997 pub fn get_routes(&self) -> Vec<Route> {
999 let mut routes = Vec::new();
1000
1001 for request in &self.requests {
1003 routes.push(
1004 Route::new(request.method.clone(), request.path.clone())
1005 .with_priority(request.priority)
1006 .with_metadata("request_id".to_string(), serde_json::json!(request.id))
1007 .with_metadata("workspace_id".to_string(), serde_json::json!(self.id)),
1008 );
1009 }
1010
1011 for folder in &self.folders {
1013 routes.extend(folder.get_routes(&self.id));
1014 }
1015
1016 routes
1017 }
1018
1019 pub fn get_effective_auth<'a>(&'a self, folder_path: &[&'a Folder]) -> Option<&'a AuthConfig> {
1021 for folder in folder_path.iter().rev() {
1023 if let Some(auth) = &folder.inheritance.auth {
1024 return Some(auth);
1025 }
1026 }
1027
1028 self.config.auth.as_ref()
1030 }
1031
1032 pub fn get_effective_headers(&self, folder_path: &[&Folder]) -> HashMap<String, String> {
1034 let mut effective_headers = HashMap::new();
1035
1036 for (key, value) in &self.config.default_headers {
1038 effective_headers.insert(key.clone(), value.clone());
1039 }
1040
1041 for folder in folder_path {
1043 for (key, value) in &folder.inheritance.headers {
1044 effective_headers.insert(key.clone(), value.clone());
1045 }
1046 }
1047
1048 effective_headers
1049 }
1050}
1051
1052impl Folder {
1053 pub fn get_inheritance_path<'a>(&'a self, workspace: &'a Workspace) -> Vec<&'a Folder> {
1055 let mut path = Vec::new();
1056 let mut current = Some(self);
1057
1058 while let Some(folder) = current {
1059 path.push(folder);
1060 current =
1061 folder.parent_id.as_ref().and_then(|parent_id| workspace.find_folder(parent_id));
1062 }
1063
1064 path.reverse(); path
1066 }
1067}
1068
1069impl MockRequest {
1070 pub fn apply_inheritance(
1072 &mut self,
1073 effective_headers: HashMap<String, String>,
1074 effective_auth: Option<&AuthConfig>,
1075 ) {
1076 for (key, value) in effective_headers {
1078 self.headers.entry(key).or_insert(value);
1079 }
1080
1081 if let Some(auth) = effective_auth {
1084 self.auth = Some(auth.clone());
1085 }
1086 }
1087
1088 pub fn create_inherited_request(
1090 mut self,
1091 workspace: &Workspace,
1092 folder_path: &[&Folder],
1093 ) -> Self {
1094 let effective_headers = workspace.get_effective_headers(folder_path);
1095 let effective_auth = workspace.get_effective_auth(folder_path);
1096
1097 self.apply_inheritance(effective_headers, effective_auth);
1098 self
1099 }
1100
1101 pub fn new(method: HttpMethod, path: String, name: String) -> Self {
1103 let now = Utc::now();
1104 Self {
1105 id: Uuid::new_v4().to_string(),
1106 name,
1107 description: None,
1108 method,
1109 path,
1110 headers: HashMap::new(),
1111 query_params: HashMap::new(),
1112 body: None,
1113 response: MockResponse::default(),
1114 response_history: Vec::new(),
1115 created_at: now,
1116 updated_at: now,
1117 tags: Vec::new(),
1118 auth: None,
1119 priority: 0,
1120 }
1121 }
1122
1123 pub fn with_response(mut self, response: MockResponse) -> Self {
1125 self.response = response;
1126 self
1127 }
1128
1129 pub fn with_header(mut self, key: String, value: String) -> Self {
1131 self.headers.insert(key, value);
1132 self
1133 }
1134
1135 pub fn with_query_param(mut self, key: String, value: String) -> Self {
1137 self.query_params.insert(key, value);
1138 self
1139 }
1140
1141 pub fn with_body(mut self, body: String) -> Self {
1143 self.body = Some(body);
1144 self
1145 }
1146
1147 pub fn with_tag(mut self, tag: String) -> Self {
1149 self.tags.push(tag);
1150 self
1151 }
1152
1153 pub fn add_response_history(&mut self, entry: ResponseHistoryEntry) {
1155 self.response_history.push(entry);
1156 if self.response_history.len() > 100 {
1158 self.response_history.remove(0);
1159 }
1160 self.response_history.sort_by(|a, b| b.executed_at.cmp(&a.executed_at));
1162 }
1163
1164 pub fn get_response_history(&self) -> &[ResponseHistoryEntry] {
1166 &self.response_history
1167 }
1168}
1169
1170impl Default for MockResponse {
1171 fn default() -> Self {
1172 Self {
1173 status_code: 200,
1174 headers: HashMap::new(),
1175 body: Some("{}".to_string()),
1176 content_type: Some("application/json".to_string()),
1177 delay_ms: None,
1178 }
1179 }
1180}
1181
1182impl Environment {
1183 pub fn new(name: String) -> Self {
1185 let now = Utc::now();
1186 Self {
1187 id: Uuid::new_v4().to_string(),
1188 name,
1189 description: None,
1190 color: None,
1191 variables: HashMap::new(),
1192 created_at: now,
1193 updated_at: now,
1194 order: 0, sharable: false, }
1197 }
1198
1199 pub fn new_global() -> Self {
1201 let mut env = Self::new("Global".to_string());
1202 env.description =
1203 Some("Global environment variables available in all contexts".to_string());
1204 env
1205 }
1206
1207 pub fn set_variable(&mut self, key: String, value: String) {
1209 self.variables.insert(key, value);
1210 self.updated_at = Utc::now();
1211 }
1212
1213 pub fn remove_variable(&mut self, key: &str) -> bool {
1215 let removed = self.variables.remove(key).is_some();
1216 if removed {
1217 self.updated_at = Utc::now();
1218 }
1219 removed
1220 }
1221
1222 pub fn get_variable(&self, key: &str) -> Option<&String> {
1224 self.variables.get(key)
1225 }
1226
1227 pub fn set_color(&mut self, color: EnvironmentColor) {
1229 self.color = Some(color);
1230 self.updated_at = Utc::now();
1231 }
1232}
1233
1234impl Default for SyncConfig {
1235 fn default() -> Self {
1236 Self {
1237 enabled: false,
1238 target_directory: None,
1239 directory_structure: SyncDirectoryStructure::Nested,
1240 sync_direction: SyncDirection::Manual,
1241 include_metadata: true,
1242 realtime_monitoring: false,
1243 filename_pattern: "{name}".to_string(),
1244 exclude_pattern: None,
1245 force_overwrite: false,
1246 last_sync: None,
1247 }
1248 }
1249}
1250
1251impl Default for WorkspaceConfig {
1252 fn default() -> Self {
1253 Self {
1254 base_url: None,
1255 default_headers: HashMap::new(),
1256 auth: None,
1257 global_environment: Environment::new_global(),
1258 environments: Vec::new(),
1259 active_environment_id: None,
1260 mock_environments: MockEnvironmentManager::default(),
1261 sync: SyncConfig::default(),
1262 auto_encryption: AutoEncryptionConfig::default(),
1263 reality_level: None,
1264 fidelity_score: None,
1265 ai_mode: None,
1266 }
1267 }
1268}
1269
1270impl WorkspaceRegistry {
1271 pub fn new() -> Self {
1273 Self {
1274 workspaces: HashMap::new(),
1275 active_workspace: None,
1276 }
1277 }
1278
1279 pub fn add_workspace(&mut self, mut workspace: Workspace) -> Result<EntityId> {
1281 let id = workspace.id.clone();
1282
1283 if workspace.order == 0 && !self.workspaces.is_empty() {
1285 workspace.order = self.workspaces.len() as i32;
1286 }
1287
1288 self.workspaces.insert(id.clone(), workspace);
1289 Ok(id)
1290 }
1291
1292 pub fn get_workspace(&self, id: &str) -> Option<&Workspace> {
1294 self.workspaces.get(id)
1295 }
1296
1297 pub fn get_workspace_mut(&mut self, id: &str) -> Option<&mut Workspace> {
1299 self.workspaces.get_mut(id)
1300 }
1301
1302 pub fn remove_workspace(&mut self, id: &str) -> Result<()> {
1304 if self.workspaces.remove(id).is_some() {
1305 if self.active_workspace.as_deref() == Some(id) {
1307 self.active_workspace = None;
1308 }
1309 Ok(())
1310 } else {
1311 Err(Error::not_found("Workspace", id))
1312 }
1313 }
1314
1315 pub fn set_active_workspace(&mut self, id: Option<String>) -> Result<()> {
1317 if let Some(ref workspace_id) = id {
1318 if !self.workspaces.contains_key(workspace_id) {
1319 return Err(Error::not_found("Workspace", workspace_id));
1320 }
1321 }
1322 self.active_workspace = id;
1323 Ok(())
1324 }
1325
1326 pub fn get_active_workspace(&self) -> Option<&Workspace> {
1328 self.active_workspace.as_ref().and_then(|id| self.workspaces.get(id))
1329 }
1330
1331 pub fn get_active_workspace_id(&self) -> Option<&str> {
1333 self.active_workspace.as_deref()
1334 }
1335
1336 pub fn get_workspaces(&self) -> Vec<&Workspace> {
1338 self.workspaces.values().collect()
1339 }
1340
1341 pub fn get_workspaces_ordered(&self) -> Vec<&Workspace> {
1343 let mut workspaces: Vec<&Workspace> = self.workspaces.values().collect();
1344 workspaces.sort_by_key(|w| w.order);
1345 workspaces
1346 }
1347
1348 pub fn update_workspaces_order(&mut self, workspace_ids: Vec<String>) -> Result<()> {
1350 for workspace_id in &workspace_ids {
1352 if !self.workspaces.contains_key(workspace_id) {
1353 return Err(Error::not_found("Workspace", workspace_id));
1354 }
1355 }
1356
1357 for (index, workspace_id) in workspace_ids.iter().enumerate() {
1359 if let Some(workspace) = self.workspaces.get_mut(workspace_id) {
1360 workspace.order = index as i32;
1361 workspace.updated_at = Utc::now();
1362 }
1363 }
1364
1365 Ok(())
1366 }
1367
1368 pub fn get_all_routes(&self) -> Vec<Route> {
1370 let mut all_routes = Vec::new();
1371 for workspace in self.workspaces.values() {
1372 all_routes.extend(workspace.get_routes());
1373 }
1374 all_routes
1375 }
1376
1377 pub fn create_route_registry(&self) -> Result<RouteRegistry> {
1379 let mut registry = RouteRegistry::new();
1380 let routes = self.get_all_routes();
1381
1382 for route in routes {
1383 registry.add_http_route(route)?;
1384 }
1385
1386 Ok(registry)
1387 }
1388}
1389
1390impl Default for WorkspaceRegistry {
1391 fn default() -> Self {
1392 Self::new()
1393 }
1394}
1395
1396#[cfg(test)]
1397mod tests {
1398 use super::*;
1399
1400 use crate::ApiKeyConfig;
1401
1402 #[test]
1403 fn test_workspace_creation() {
1404 let workspace = Workspace::new("Test Workspace".to_string());
1405 assert_eq!(workspace.name, "Test Workspace");
1406 assert!(!workspace.id.is_empty());
1407 assert!(workspace.folders.is_empty());
1408 assert!(workspace.requests.is_empty());
1409 }
1410
1411 #[test]
1412 fn test_folder_creation() {
1413 let folder = Folder::new("Test Folder".to_string());
1414 assert_eq!(folder.name, "Test Folder");
1415 assert!(!folder.id.is_empty());
1416 assert!(folder.folders.is_empty());
1417 assert!(folder.requests.is_empty());
1418 }
1419
1420 #[test]
1421 fn test_request_creation() {
1422 let request =
1423 MockRequest::new(HttpMethod::GET, "/test".to_string(), "Test Request".to_string());
1424 assert_eq!(request.name, "Test Request");
1425 assert_eq!(request.method, HttpMethod::GET);
1426 assert_eq!(request.path, "/test");
1427 assert_eq!(request.response.status_code, 200);
1428 }
1429
1430 #[test]
1431 fn test_workspace_hierarchy() {
1432 let mut workspace = Workspace::new("Test Workspace".to_string());
1433
1434 let folder_id = workspace.add_folder("Test Folder".to_string()).unwrap();
1436 assert_eq!(workspace.folders.len(), 1);
1437
1438 let request =
1440 MockRequest::new(HttpMethod::GET, "/test".to_string(), "Test Request".to_string());
1441 workspace.add_request(request).unwrap();
1442 assert_eq!(workspace.requests.len(), 1);
1443
1444 let folder = workspace.find_folder_mut(&folder_id).unwrap();
1446 let folder_request = MockRequest::new(
1447 HttpMethod::POST,
1448 "/folder-test".to_string(),
1449 "Folder Request".to_string(),
1450 );
1451 folder.add_request(folder_request).unwrap();
1452 assert_eq!(folder.requests.len(), 1);
1453 }
1454
1455 #[test]
1456 fn test_workspace_registry() {
1457 let mut registry = WorkspaceRegistry::new();
1458
1459 let workspace = Workspace::new("Test Workspace".to_string());
1460 let workspace_id = registry.add_workspace(workspace).unwrap();
1461
1462 registry.set_active_workspace(Some(workspace_id.clone())).unwrap();
1464 assert!(registry.get_active_workspace().is_some());
1465
1466 let retrieved = registry.get_workspace(&workspace_id).unwrap();
1468 assert_eq!(retrieved.name, "Test Workspace");
1469
1470 registry.remove_workspace(&workspace_id).unwrap();
1472 assert!(registry.get_workspace(&workspace_id).is_none());
1473 }
1474
1475 #[test]
1476 fn test_inheritance_header_priority() {
1477 let mut workspace = Workspace::new("Test Workspace".to_string());
1478 workspace
1479 .config
1480 .default_headers
1481 .insert("X-Common".to_string(), "workspace-value".to_string());
1482 workspace
1483 .config
1484 .default_headers
1485 .insert("X-Workspace-Only".to_string(), "workspace-only-value".to_string());
1486
1487 let mut folder = Folder::new("Test Folder".to_string());
1489 folder
1490 .inheritance
1491 .headers
1492 .insert("X-Common".to_string(), "folder-value".to_string());
1493 folder
1494 .inheritance
1495 .headers
1496 .insert("X-Folder-Only".to_string(), "folder-only-value".to_string());
1497
1498 let folder_path = vec![&folder];
1500 let effective_headers = workspace.get_effective_headers(&folder_path);
1501
1502 assert_eq!(effective_headers.get("X-Common").unwrap(), "folder-value"); assert_eq!(effective_headers.get("X-Workspace-Only").unwrap(), "workspace-only-value"); assert_eq!(effective_headers.get("X-Folder-Only").unwrap(), "folder-only-value");
1505 }
1507
1508 #[test]
1509 fn test_inheritance_request_headers_override() {
1510 let mut workspace = Workspace::new("Test Workspace".to_string());
1511 workspace
1512 .config
1513 .default_headers
1514 .insert("Authorization".to_string(), "Bearer workspace-token".to_string());
1515
1516 let folder_path = vec![];
1517 let effective_headers = workspace.get_effective_headers(&folder_path);
1518 let mut request =
1519 MockRequest::new(HttpMethod::GET, "/test".to_string(), "Test Request".to_string());
1520
1521 request
1523 .headers
1524 .insert("Authorization".to_string(), "Bearer request-token".to_string());
1525
1526 request.apply_inheritance(effective_headers, None);
1528
1529 assert_eq!(request.headers.get("Authorization").unwrap(), "Bearer request-token");
1530 }
1531
1532 #[test]
1533 fn test_inheritance_nested_folders() {
1534 let mut workspace = Workspace::new("Test Workspace".to_string());
1535 workspace
1536 .config
1537 .default_headers
1538 .insert("X-Level".to_string(), "workspace".to_string());
1539
1540 let mut parent_folder = Folder::new("Parent Folder".to_string());
1542 parent_folder
1543 .inheritance
1544 .headers
1545 .insert("X-Level".to_string(), "parent".to_string());
1546 parent_folder
1547 .inheritance
1548 .headers
1549 .insert("X-Parent-Only".to_string(), "parent-value".to_string());
1550
1551 let mut child_folder = Folder::new("Child Folder".to_string());
1553 child_folder
1554 .inheritance
1555 .headers
1556 .insert("X-Level".to_string(), "child".to_string());
1557 child_folder
1558 .inheritance
1559 .headers
1560 .insert("X-Child-Only".to_string(), "child-value".to_string());
1561
1562 let folder_path = vec![&parent_folder, &child_folder];
1564 let effective_headers = workspace.get_effective_headers(&folder_path);
1565
1566 assert_eq!(effective_headers.get("X-Level").unwrap(), "child");
1568 assert_eq!(effective_headers.get("X-Parent-Only").unwrap(), "parent-value");
1569 assert_eq!(effective_headers.get("X-Child-Only").unwrap(), "child-value");
1570 }
1571
1572 #[test]
1573 fn test_inheritance_auth_from_folder() {
1574 let workspace = Workspace::new("Test Workspace".to_string());
1576
1577 let mut folder = Folder::new("Test Folder".to_string());
1579 let auth = AuthConfig {
1580 require_auth: true,
1581 api_key: Some(ApiKeyConfig {
1582 header_name: "X-API-Key".to_string(),
1583 query_name: Some("api_key".to_string()),
1584 keys: vec!["folder-key".to_string()],
1585 }),
1586 ..Default::default()
1587 };
1588 folder.inheritance.auth = Some(auth);
1589
1590 let folder_path = vec![&folder];
1591 let effective_auth = workspace.get_effective_auth(&folder_path);
1592
1593 assert!(effective_auth.is_some());
1594 let auth_config = effective_auth.unwrap();
1595 assert!(auth_config.require_auth);
1596 let api_key_config = auth_config.api_key.as_ref().unwrap();
1597 assert_eq!(api_key_config.keys, vec!["folder-key".to_string()]);
1598 }
1599
1600 #[test]
1601 fn test_folder_inheritance_config_default() {
1602 let config = FolderInheritanceConfig::default();
1603 assert!(config.headers.is_empty());
1604 assert!(config.auth.is_none());
1605 }
1606
1607 #[test]
1608 fn test_mock_response_default() {
1609 let response = MockResponse::default();
1610 assert_eq!(response.status_code, 200);
1611 assert!(response.headers.is_empty());
1612 }
1613
1614 #[test]
1615 fn test_mock_response_serialization() {
1616 let mut response = MockResponse {
1617 status_code: 404,
1618 body: Some("Not Found".to_string()),
1619 ..Default::default()
1620 };
1621 response
1622 .headers
1623 .insert("Content-Type".to_string(), "application/json".to_string());
1624
1625 let json = serde_json::to_string(&response).unwrap();
1626 assert!(json.contains("404"));
1627 }
1628
1629 #[test]
1630 fn test_response_history_entry_creation() {
1631 let entry = ResponseHistoryEntry {
1632 id: "exec-123".to_string(),
1633 executed_at: Utc::now(),
1634 request_method: HttpMethod::GET,
1635 request_path: "/api/test".to_string(),
1636 request_headers: HashMap::new(),
1637 request_body: None,
1638 response_status_code: 200,
1639 response_headers: HashMap::new(),
1640 response_body: Some("{}".to_string()),
1641 response_time_ms: 150,
1642 response_size_bytes: 2,
1643 error_message: None,
1644 };
1645
1646 assert_eq!(entry.response_status_code, 200);
1647 assert_eq!(entry.response_time_ms, 150);
1648 assert_eq!(entry.id, "exec-123");
1649 }
1650
1651 #[test]
1652 fn test_environment_color_creation() {
1653 let color = EnvironmentColor {
1654 hex: "#FF8040".to_string(),
1655 name: Some("Orange".to_string()),
1656 };
1657 assert_eq!(color.hex, "#FF8040");
1658 assert_eq!(color.name, Some("Orange".to_string()));
1659 }
1660
1661 #[test]
1662 fn test_environment_color_serialization() {
1663 let color = EnvironmentColor {
1664 hex: "#FF0000".to_string(),
1665 name: None,
1666 };
1667 let json = serde_json::to_string(&color).unwrap();
1668 assert!(json.contains("#FF0000"));
1669 }
1670
1671 #[test]
1672 fn test_sync_config_default() {
1673 let config = SyncConfig::default();
1676 assert!(!config.enabled);
1677 let _ = config;
1679 }
1680
1681 #[test]
1682 fn test_sync_directory_structure_serialization() {
1683 let structures = vec![
1684 SyncDirectoryStructure::Flat,
1685 SyncDirectoryStructure::Nested,
1686 SyncDirectoryStructure::Grouped,
1687 ];
1688
1689 for structure in structures {
1690 let json = serde_json::to_string(&structure).unwrap();
1691 assert!(!json.is_empty());
1692 let _deserialized: SyncDirectoryStructure = serde_json::from_str(&json).unwrap();
1694 }
1695 }
1696
1697 #[test]
1698 fn test_sync_direction_serialization() {
1699 let directions = vec![
1700 SyncDirection::Manual,
1701 SyncDirection::WorkspaceToDirectory,
1702 SyncDirection::Bidirectional,
1703 ];
1704
1705 for direction in directions {
1706 let json = serde_json::to_string(&direction).unwrap();
1707 assert!(!json.is_empty());
1708 let _deserialized: SyncDirection = serde_json::from_str(&json).unwrap();
1710 }
1711 }
1712
1713 #[test]
1714 fn test_workspace_config_default() {
1715 let config = WorkspaceConfig::default();
1716 assert!(config.base_url.is_none());
1717 assert!(config.default_headers.is_empty());
1718 assert!(config.auth.is_none());
1719 assert!(config.environments.is_empty());
1720 assert!(config.active_environment_id.is_none());
1721 }
1722
1723 #[test]
1724 fn test_workspace_registry_new() {
1725 let registry = WorkspaceRegistry::new();
1726 assert!(registry.get_workspaces().is_empty());
1727 assert!(registry.get_active_workspace().is_none());
1728 }
1729
1730 #[test]
1731 fn test_workspace_registry_get_active_workspace_id() {
1732 let mut registry = WorkspaceRegistry::new();
1733 let workspace = Workspace::new("Test".to_string());
1734 let id = registry.add_workspace(workspace).unwrap();
1735 registry.set_active_workspace(Some(id.clone())).unwrap();
1736
1737 assert_eq!(registry.get_active_workspace_id(), Some(id.as_str()));
1738 }
1739
1740 #[test]
1741 fn test_workspace_registry_get_workspaces_ordered() {
1742 let mut registry = WorkspaceRegistry::new();
1743 let mut ws1 = Workspace::new("First".to_string());
1744 ws1.order = 2;
1745 let mut ws2 = Workspace::new("Second".to_string());
1746 ws2.order = 1;
1747
1748 registry.add_workspace(ws1).unwrap();
1749 registry.add_workspace(ws2).unwrap();
1750
1751 let ordered = registry.get_workspaces_ordered();
1752 assert_eq!(ordered.len(), 2);
1753 assert_eq!(ordered[0].name, "Second"); assert_eq!(ordered[1].name, "First");
1755 }
1756
1757 #[test]
1758 fn test_workspace_registry_update_workspaces_order() {
1759 let mut registry = WorkspaceRegistry::new();
1760 let id1 = registry.add_workspace(Workspace::new("First".to_string())).unwrap();
1761 let id2 = registry.add_workspace(Workspace::new("Second".to_string())).unwrap();
1762
1763 registry.update_workspaces_order(vec![id2.clone(), id1.clone()]).unwrap();
1764
1765 let ordered = registry.get_workspaces_ordered();
1766 assert_eq!(ordered[0].id, id2);
1767 assert_eq!(ordered[1].id, id1);
1768 }
1769
1770 #[test]
1771 fn test_workspace_registry_update_workspaces_order_invalid_id() {
1772 let mut registry = WorkspaceRegistry::new();
1773 let id1 = registry.add_workspace(Workspace::new("First".to_string())).unwrap();
1774
1775 let result = registry.update_workspaces_order(vec![id1, "invalid-id".to_string()]);
1776 assert!(result.is_err());
1777 }
1778
1779 #[test]
1780 fn test_workspace_registry_set_active_workspace_invalid() {
1781 let mut registry = WorkspaceRegistry::new();
1782 let result = registry.set_active_workspace(Some("invalid-id".to_string()));
1783 assert!(result.is_err());
1784 }
1785
1786 #[test]
1787 fn test_workspace_registry_remove_active_workspace() {
1788 let mut registry = WorkspaceRegistry::new();
1789 let id = registry.add_workspace(Workspace::new("Test".to_string())).unwrap();
1790 registry.set_active_workspace(Some(id.clone())).unwrap();
1791 registry.remove_workspace(&id).unwrap();
1792
1793 assert!(registry.get_active_workspace().is_none());
1794 }
1795
1796 #[test]
1797 fn test_workspace_clone() {
1798 let workspace1 = Workspace::new("Test Workspace".to_string());
1799 let workspace2 = workspace1.clone();
1800 assert_eq!(workspace1.name, workspace2.name);
1801 assert_eq!(workspace1.id, workspace2.id);
1802 }
1803
1804 #[test]
1805 fn test_workspace_debug() {
1806 let workspace = Workspace::new("Debug Test".to_string());
1807 let debug_str = format!("{:?}", workspace);
1808 assert!(debug_str.contains("Workspace"));
1809 }
1810
1811 #[test]
1812 fn test_workspace_serialization() {
1813 let workspace = Workspace::new("Serialization Test".to_string());
1814 let json = serde_json::to_string(&workspace).unwrap();
1815 assert!(json.contains("Serialization Test"));
1816 }
1817
1818 #[test]
1819 fn test_folder_clone() {
1820 let folder1 = Folder::new("Test Folder".to_string());
1821 let folder2 = folder1.clone();
1822 assert_eq!(folder1.name, folder2.name);
1823 assert_eq!(folder1.id, folder2.id);
1824 }
1825
1826 #[test]
1827 fn test_folder_debug() {
1828 let folder = Folder::new("Debug Folder".to_string());
1829 let debug_str = format!("{:?}", folder);
1830 assert!(debug_str.contains("Folder"));
1831 }
1832
1833 #[test]
1834 fn test_folder_serialization() {
1835 let folder = Folder::new("Serialization Folder".to_string());
1836 let json = serde_json::to_string(&folder).unwrap();
1837 assert!(json.contains("Serialization Folder"));
1838 }
1839
1840 #[test]
1841 fn test_folder_inheritance_config_clone() {
1842 let mut config1 = FolderInheritanceConfig::default();
1843 config1.headers.insert("X-Test".to_string(), "value".to_string());
1844 let config2 = config1.clone();
1845 assert_eq!(config1.headers, config2.headers);
1846 }
1847
1848 #[test]
1849 fn test_folder_inheritance_config_debug() {
1850 let config = FolderInheritanceConfig::default();
1851 let debug_str = format!("{:?}", config);
1852 assert!(debug_str.contains("FolderInheritanceConfig"));
1853 }
1854
1855 #[test]
1856 fn test_workspace_config_clone() {
1857 let config1 = WorkspaceConfig {
1858 base_url: Some("https://api.example.com".to_string()),
1859 ..Default::default()
1860 };
1861 let config2 = config1.clone();
1862 assert_eq!(config1.base_url, config2.base_url);
1863 }
1864
1865 #[test]
1866 fn test_workspace_config_debug() {
1867 let config = WorkspaceConfig::default();
1868 let debug_str = format!("{:?}", config);
1869 assert!(debug_str.contains("WorkspaceConfig"));
1870 }
1871
1872 #[test]
1873 fn test_workspace_registry_clone() {
1874 let mut registry1 = WorkspaceRegistry::new();
1875 let id = registry1.add_workspace(Workspace::new("Test".to_string())).unwrap();
1876 let registry2 = registry1.clone();
1877 assert!(registry2.get_workspace(&id).is_some());
1878 }
1879
1880 #[test]
1881 fn test_workspace_registry_debug() {
1882 let registry = WorkspaceRegistry::new();
1883 let debug_str = format!("{:?}", registry);
1884 assert!(debug_str.contains("WorkspaceRegistry"));
1885 }
1886}