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 get_routes(&self, workspace_id: &str) -> Vec<Route> {
876 let mut routes = Vec::new();
877
878 for request in &self.requests {
880 routes.push(
881 Route::new(request.method.clone(), request.path.clone())
882 .with_priority(request.priority)
883 .with_metadata("request_id".to_string(), serde_json::json!(request.id))
884 .with_metadata("folder_id".to_string(), serde_json::json!(self.id))
885 .with_metadata("workspace_id".to_string(), serde_json::json!(workspace_id)),
886 );
887 }
888
889 for folder in &self.folders {
891 routes.extend(folder.get_routes(workspace_id));
892 }
893
894 routes
895 }
896}
897
898impl Workspace {
899 pub fn find_folder(&self, id: &str) -> Option<&Folder> {
901 for folder in &self.folders {
902 if folder.id == id {
903 return Some(folder);
904 }
905 if let Some(found) = folder.find_folder(id) {
906 return Some(found);
907 }
908 }
909 None
910 }
911
912 pub fn find_folder_mut(&mut self, id: &str) -> Option<&mut Folder> {
914 for folder in &mut self.folders {
915 if folder.id == id {
916 return Some(folder);
917 }
918 if let Some(found) = folder.find_folder_mut(id) {
919 return Some(found);
920 }
921 }
922 None
923 }
924
925 pub fn add_request(&mut self, request: MockRequest) -> Result<EntityId> {
927 let id = request.id.clone();
928 self.requests.push(request);
929 self.updated_at = Utc::now();
930 Ok(id)
931 }
932
933 pub fn get_routes(&self) -> Vec<Route> {
935 let mut routes = Vec::new();
936
937 for request in &self.requests {
939 routes.push(
940 Route::new(request.method.clone(), request.path.clone())
941 .with_priority(request.priority)
942 .with_metadata("request_id".to_string(), serde_json::json!(request.id))
943 .with_metadata("workspace_id".to_string(), serde_json::json!(self.id)),
944 );
945 }
946
947 for folder in &self.folders {
949 routes.extend(folder.get_routes(&self.id));
950 }
951
952 routes
953 }
954
955 pub fn get_effective_auth<'a>(&'a self, folder_path: &[&'a Folder]) -> Option<&'a AuthConfig> {
957 for folder in folder_path.iter().rev() {
959 if let Some(auth) = &folder.inheritance.auth {
960 return Some(auth);
961 }
962 }
963
964 self.config.auth.as_ref()
966 }
967
968 pub fn get_effective_headers(&self, folder_path: &[&Folder]) -> HashMap<String, String> {
970 let mut effective_headers = HashMap::new();
971
972 for (key, value) in &self.config.default_headers {
974 effective_headers.insert(key.clone(), value.clone());
975 }
976
977 for folder in folder_path {
979 for (key, value) in &folder.inheritance.headers {
980 effective_headers.insert(key.clone(), value.clone());
981 }
982 }
983
984 effective_headers
985 }
986}
987
988impl Folder {
989 pub fn get_inheritance_path<'a>(&'a self, workspace: &'a Workspace) -> Vec<&'a Folder> {
991 let mut path = Vec::new();
992 let mut current = Some(self);
993
994 while let Some(folder) = current {
995 path.push(folder);
996 current =
997 folder.parent_id.as_ref().and_then(|parent_id| workspace.find_folder(parent_id));
998 }
999
1000 path.reverse(); path
1002 }
1003}
1004
1005impl MockRequest {
1006 pub fn apply_inheritance(
1008 &mut self,
1009 effective_headers: HashMap<String, String>,
1010 effective_auth: Option<&AuthConfig>,
1011 ) {
1012 for (key, value) in effective_headers {
1014 self.headers.entry(key).or_insert(value);
1015 }
1016
1017 if let Some(auth) = effective_auth {
1020 self.auth = Some(auth.clone());
1021 }
1022 }
1023
1024 pub fn create_inherited_request(
1026 mut self,
1027 workspace: &Workspace,
1028 folder_path: &[&Folder],
1029 ) -> Self {
1030 let effective_headers = workspace.get_effective_headers(folder_path);
1031 let effective_auth = workspace.get_effective_auth(folder_path);
1032
1033 self.apply_inheritance(effective_headers, effective_auth);
1034 self
1035 }
1036
1037 pub fn new(method: HttpMethod, path: String, name: String) -> Self {
1039 let now = Utc::now();
1040 Self {
1041 id: Uuid::new_v4().to_string(),
1042 name,
1043 description: None,
1044 method,
1045 path,
1046 headers: HashMap::new(),
1047 query_params: HashMap::new(),
1048 body: None,
1049 response: MockResponse::default(),
1050 response_history: Vec::new(),
1051 created_at: now,
1052 updated_at: now,
1053 tags: Vec::new(),
1054 auth: None,
1055 priority: 0,
1056 }
1057 }
1058
1059 pub fn with_response(mut self, response: MockResponse) -> Self {
1061 self.response = response;
1062 self
1063 }
1064
1065 pub fn with_header(mut self, key: String, value: String) -> Self {
1067 self.headers.insert(key, value);
1068 self
1069 }
1070
1071 pub fn with_query_param(mut self, key: String, value: String) -> Self {
1073 self.query_params.insert(key, value);
1074 self
1075 }
1076
1077 pub fn with_body(mut self, body: String) -> Self {
1079 self.body = Some(body);
1080 self
1081 }
1082
1083 pub fn with_tag(mut self, tag: String) -> Self {
1085 self.tags.push(tag);
1086 self
1087 }
1088
1089 pub fn add_response_history(&mut self, entry: ResponseHistoryEntry) {
1091 self.response_history.push(entry);
1092 if self.response_history.len() > 100 {
1094 self.response_history.remove(0);
1095 }
1096 self.response_history.sort_by(|a, b| b.executed_at.cmp(&a.executed_at));
1098 }
1099
1100 pub fn get_response_history(&self) -> &[ResponseHistoryEntry] {
1102 &self.response_history
1103 }
1104}
1105
1106impl Default for MockResponse {
1107 fn default() -> Self {
1108 Self {
1109 status_code: 200,
1110 headers: HashMap::new(),
1111 body: Some("{}".to_string()),
1112 content_type: Some("application/json".to_string()),
1113 delay_ms: None,
1114 }
1115 }
1116}
1117
1118impl Environment {
1119 pub fn new(name: String) -> Self {
1121 let now = Utc::now();
1122 Self {
1123 id: Uuid::new_v4().to_string(),
1124 name,
1125 description: None,
1126 color: None,
1127 variables: HashMap::new(),
1128 created_at: now,
1129 updated_at: now,
1130 order: 0, sharable: false, }
1133 }
1134
1135 pub fn new_global() -> Self {
1137 let mut env = Self::new("Global".to_string());
1138 env.description =
1139 Some("Global environment variables available in all contexts".to_string());
1140 env
1141 }
1142
1143 pub fn set_variable(&mut self, key: String, value: String) {
1145 self.variables.insert(key, value);
1146 self.updated_at = Utc::now();
1147 }
1148
1149 pub fn remove_variable(&mut self, key: &str) -> bool {
1151 let removed = self.variables.remove(key).is_some();
1152 if removed {
1153 self.updated_at = Utc::now();
1154 }
1155 removed
1156 }
1157
1158 pub fn get_variable(&self, key: &str) -> Option<&String> {
1160 self.variables.get(key)
1161 }
1162
1163 pub fn set_color(&mut self, color: EnvironmentColor) {
1165 self.color = Some(color);
1166 self.updated_at = Utc::now();
1167 }
1168}
1169
1170impl Default for SyncConfig {
1171 fn default() -> Self {
1172 Self {
1173 enabled: false,
1174 target_directory: None,
1175 directory_structure: SyncDirectoryStructure::Nested,
1176 sync_direction: SyncDirection::Manual,
1177 include_metadata: true,
1178 realtime_monitoring: false,
1179 filename_pattern: "{name}".to_string(),
1180 exclude_pattern: None,
1181 force_overwrite: false,
1182 last_sync: None,
1183 }
1184 }
1185}
1186
1187impl Default for WorkspaceConfig {
1188 fn default() -> Self {
1189 Self {
1190 base_url: None,
1191 default_headers: HashMap::new(),
1192 auth: None,
1193 global_environment: Environment::new_global(),
1194 environments: Vec::new(),
1195 active_environment_id: None,
1196 mock_environments: MockEnvironmentManager::default(),
1197 sync: SyncConfig::default(),
1198 auto_encryption: AutoEncryptionConfig::default(),
1199 reality_level: None,
1200 fidelity_score: None,
1201 ai_mode: None,
1202 }
1203 }
1204}
1205
1206impl WorkspaceRegistry {
1207 pub fn new() -> Self {
1209 Self {
1210 workspaces: HashMap::new(),
1211 active_workspace: None,
1212 }
1213 }
1214
1215 pub fn add_workspace(&mut self, mut workspace: Workspace) -> Result<EntityId> {
1217 let id = workspace.id.clone();
1218
1219 if workspace.order == 0 && !self.workspaces.is_empty() {
1221 workspace.order = self.workspaces.len() as i32;
1222 }
1223
1224 self.workspaces.insert(id.clone(), workspace);
1225 Ok(id)
1226 }
1227
1228 pub fn get_workspace(&self, id: &str) -> Option<&Workspace> {
1230 self.workspaces.get(id)
1231 }
1232
1233 pub fn get_workspace_mut(&mut self, id: &str) -> Option<&mut Workspace> {
1235 self.workspaces.get_mut(id)
1236 }
1237
1238 pub fn remove_workspace(&mut self, id: &str) -> Result<()> {
1240 if self.workspaces.remove(id).is_some() {
1241 if self.active_workspace.as_deref() == Some(id) {
1243 self.active_workspace = None;
1244 }
1245 Ok(())
1246 } else {
1247 Err(Error::not_found("Workspace", id))
1248 }
1249 }
1250
1251 pub fn set_active_workspace(&mut self, id: Option<String>) -> Result<()> {
1253 if let Some(ref workspace_id) = id {
1254 if !self.workspaces.contains_key(workspace_id) {
1255 return Err(Error::not_found("Workspace", workspace_id));
1256 }
1257 }
1258 self.active_workspace = id;
1259 Ok(())
1260 }
1261
1262 pub fn get_active_workspace(&self) -> Option<&Workspace> {
1264 self.active_workspace.as_ref().and_then(|id| self.workspaces.get(id))
1265 }
1266
1267 pub fn get_active_workspace_id(&self) -> Option<&str> {
1269 self.active_workspace.as_deref()
1270 }
1271
1272 pub fn get_workspaces(&self) -> Vec<&Workspace> {
1274 self.workspaces.values().collect()
1275 }
1276
1277 pub fn get_workspaces_ordered(&self) -> Vec<&Workspace> {
1279 let mut workspaces: Vec<&Workspace> = self.workspaces.values().collect();
1280 workspaces.sort_by_key(|w| w.order);
1281 workspaces
1282 }
1283
1284 pub fn update_workspaces_order(&mut self, workspace_ids: Vec<String>) -> Result<()> {
1286 for workspace_id in &workspace_ids {
1288 if !self.workspaces.contains_key(workspace_id) {
1289 return Err(Error::not_found("Workspace", workspace_id));
1290 }
1291 }
1292
1293 for (index, workspace_id) in workspace_ids.iter().enumerate() {
1295 if let Some(workspace) = self.workspaces.get_mut(workspace_id) {
1296 workspace.order = index as i32;
1297 workspace.updated_at = Utc::now();
1298 }
1299 }
1300
1301 Ok(())
1302 }
1303
1304 pub fn get_all_routes(&self) -> Vec<Route> {
1306 let mut all_routes = Vec::new();
1307 for workspace in self.workspaces.values() {
1308 all_routes.extend(workspace.get_routes());
1309 }
1310 all_routes
1311 }
1312
1313 pub fn create_route_registry(&self) -> Result<RouteRegistry> {
1315 let mut registry = RouteRegistry::new();
1316 let routes = self.get_all_routes();
1317
1318 for route in routes {
1319 registry.add_http_route(route)?;
1320 }
1321
1322 Ok(registry)
1323 }
1324}
1325
1326impl Default for WorkspaceRegistry {
1327 fn default() -> Self {
1328 Self::new()
1329 }
1330}
1331
1332#[cfg(test)]
1333mod tests {
1334 use super::*;
1335
1336 use crate::ApiKeyConfig;
1337
1338 #[test]
1339 fn test_workspace_creation() {
1340 let workspace = Workspace::new("Test Workspace".to_string());
1341 assert_eq!(workspace.name, "Test Workspace");
1342 assert!(!workspace.id.is_empty());
1343 assert!(workspace.folders.is_empty());
1344 assert!(workspace.requests.is_empty());
1345 }
1346
1347 #[test]
1348 fn test_folder_creation() {
1349 let folder = Folder::new("Test Folder".to_string());
1350 assert_eq!(folder.name, "Test Folder");
1351 assert!(!folder.id.is_empty());
1352 assert!(folder.folders.is_empty());
1353 assert!(folder.requests.is_empty());
1354 }
1355
1356 #[test]
1357 fn test_request_creation() {
1358 let request =
1359 MockRequest::new(HttpMethod::GET, "/test".to_string(), "Test Request".to_string());
1360 assert_eq!(request.name, "Test Request");
1361 assert_eq!(request.method, HttpMethod::GET);
1362 assert_eq!(request.path, "/test");
1363 assert_eq!(request.response.status_code, 200);
1364 }
1365
1366 #[test]
1367 fn test_workspace_hierarchy() {
1368 let mut workspace = Workspace::new("Test Workspace".to_string());
1369
1370 let folder_id = workspace.add_folder("Test Folder".to_string()).unwrap();
1372 assert_eq!(workspace.folders.len(), 1);
1373
1374 let request =
1376 MockRequest::new(HttpMethod::GET, "/test".to_string(), "Test Request".to_string());
1377 workspace.add_request(request).unwrap();
1378 assert_eq!(workspace.requests.len(), 1);
1379
1380 let folder = workspace.find_folder_mut(&folder_id).unwrap();
1382 let folder_request = MockRequest::new(
1383 HttpMethod::POST,
1384 "/folder-test".to_string(),
1385 "Folder Request".to_string(),
1386 );
1387 folder.add_request(folder_request).unwrap();
1388 assert_eq!(folder.requests.len(), 1);
1389 }
1390
1391 #[test]
1392 fn test_workspace_registry() {
1393 let mut registry = WorkspaceRegistry::new();
1394
1395 let workspace = Workspace::new("Test Workspace".to_string());
1396 let workspace_id = registry.add_workspace(workspace).unwrap();
1397
1398 registry.set_active_workspace(Some(workspace_id.clone())).unwrap();
1400 assert!(registry.get_active_workspace().is_some());
1401
1402 let retrieved = registry.get_workspace(&workspace_id).unwrap();
1404 assert_eq!(retrieved.name, "Test Workspace");
1405
1406 registry.remove_workspace(&workspace_id).unwrap();
1408 assert!(registry.get_workspace(&workspace_id).is_none());
1409 }
1410
1411 #[test]
1412 fn test_inheritance_header_priority() {
1413 let mut workspace = Workspace::new("Test Workspace".to_string());
1414 workspace
1415 .config
1416 .default_headers
1417 .insert("X-Common".to_string(), "workspace-value".to_string());
1418 workspace
1419 .config
1420 .default_headers
1421 .insert("X-Workspace-Only".to_string(), "workspace-only-value".to_string());
1422
1423 let mut folder = Folder::new("Test Folder".to_string());
1425 folder
1426 .inheritance
1427 .headers
1428 .insert("X-Common".to_string(), "folder-value".to_string());
1429 folder
1430 .inheritance
1431 .headers
1432 .insert("X-Folder-Only".to_string(), "folder-only-value".to_string());
1433
1434 let folder_path = vec![&folder];
1436 let effective_headers = workspace.get_effective_headers(&folder_path);
1437
1438 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");
1441 }
1443
1444 #[test]
1445 fn test_inheritance_request_headers_override() {
1446 let mut workspace = Workspace::new("Test Workspace".to_string());
1447 workspace
1448 .config
1449 .default_headers
1450 .insert("Authorization".to_string(), "Bearer workspace-token".to_string());
1451
1452 let folder_path = vec![];
1453 let effective_headers = workspace.get_effective_headers(&folder_path);
1454 let mut request =
1455 MockRequest::new(HttpMethod::GET, "/test".to_string(), "Test Request".to_string());
1456
1457 request
1459 .headers
1460 .insert("Authorization".to_string(), "Bearer request-token".to_string());
1461
1462 request.apply_inheritance(effective_headers, None);
1464
1465 assert_eq!(request.headers.get("Authorization").unwrap(), "Bearer request-token");
1466 }
1467
1468 #[test]
1469 fn test_inheritance_nested_folders() {
1470 let mut workspace = Workspace::new("Test Workspace".to_string());
1471 workspace
1472 .config
1473 .default_headers
1474 .insert("X-Level".to_string(), "workspace".to_string());
1475
1476 let mut parent_folder = Folder::new("Parent Folder".to_string());
1478 parent_folder
1479 .inheritance
1480 .headers
1481 .insert("X-Level".to_string(), "parent".to_string());
1482 parent_folder
1483 .inheritance
1484 .headers
1485 .insert("X-Parent-Only".to_string(), "parent-value".to_string());
1486
1487 let mut child_folder = Folder::new("Child Folder".to_string());
1489 child_folder
1490 .inheritance
1491 .headers
1492 .insert("X-Level".to_string(), "child".to_string());
1493 child_folder
1494 .inheritance
1495 .headers
1496 .insert("X-Child-Only".to_string(), "child-value".to_string());
1497
1498 let folder_path = vec![&parent_folder, &child_folder];
1500 let effective_headers = workspace.get_effective_headers(&folder_path);
1501
1502 assert_eq!(effective_headers.get("X-Level").unwrap(), "child");
1504 assert_eq!(effective_headers.get("X-Parent-Only").unwrap(), "parent-value");
1505 assert_eq!(effective_headers.get("X-Child-Only").unwrap(), "child-value");
1506 }
1507
1508 #[test]
1509 fn test_inheritance_auth_from_folder() {
1510 let workspace = Workspace::new("Test Workspace".to_string());
1512
1513 let mut folder = Folder::new("Test Folder".to_string());
1515 let auth = AuthConfig {
1516 require_auth: true,
1517 api_key: Some(ApiKeyConfig {
1518 header_name: "X-API-Key".to_string(),
1519 query_name: Some("api_key".to_string()),
1520 keys: vec!["folder-key".to_string()],
1521 }),
1522 ..Default::default()
1523 };
1524 folder.inheritance.auth = Some(auth);
1525
1526 let folder_path = vec![&folder];
1527 let effective_auth = workspace.get_effective_auth(&folder_path);
1528
1529 assert!(effective_auth.is_some());
1530 let auth_config = effective_auth.unwrap();
1531 assert!(auth_config.require_auth);
1532 let api_key_config = auth_config.api_key.as_ref().unwrap();
1533 assert_eq!(api_key_config.keys, vec!["folder-key".to_string()]);
1534 }
1535
1536 #[test]
1537 fn test_folder_inheritance_config_default() {
1538 let config = FolderInheritanceConfig::default();
1539 assert!(config.headers.is_empty());
1540 assert!(config.auth.is_none());
1541 }
1542
1543 #[test]
1544 fn test_mock_response_default() {
1545 let response = MockResponse::default();
1546 assert_eq!(response.status_code, 200);
1547 assert!(response.headers.is_empty());
1548 }
1549
1550 #[test]
1551 fn test_mock_response_serialization() {
1552 let mut response = MockResponse {
1553 status_code: 404,
1554 body: Some("Not Found".to_string()),
1555 ..Default::default()
1556 };
1557 response
1558 .headers
1559 .insert("Content-Type".to_string(), "application/json".to_string());
1560
1561 let json = serde_json::to_string(&response).unwrap();
1562 assert!(json.contains("404"));
1563 }
1564
1565 #[test]
1566 fn test_response_history_entry_creation() {
1567 let entry = ResponseHistoryEntry {
1568 id: "exec-123".to_string(),
1569 executed_at: Utc::now(),
1570 request_method: HttpMethod::GET,
1571 request_path: "/api/test".to_string(),
1572 request_headers: HashMap::new(),
1573 request_body: None,
1574 response_status_code: 200,
1575 response_headers: HashMap::new(),
1576 response_body: Some("{}".to_string()),
1577 response_time_ms: 150,
1578 response_size_bytes: 2,
1579 error_message: None,
1580 };
1581
1582 assert_eq!(entry.response_status_code, 200);
1583 assert_eq!(entry.response_time_ms, 150);
1584 assert_eq!(entry.id, "exec-123");
1585 }
1586
1587 #[test]
1588 fn test_environment_color_creation() {
1589 let color = EnvironmentColor {
1590 hex: "#FF8040".to_string(),
1591 name: Some("Orange".to_string()),
1592 };
1593 assert_eq!(color.hex, "#FF8040");
1594 assert_eq!(color.name, Some("Orange".to_string()));
1595 }
1596
1597 #[test]
1598 fn test_environment_color_serialization() {
1599 let color = EnvironmentColor {
1600 hex: "#FF0000".to_string(),
1601 name: None,
1602 };
1603 let json = serde_json::to_string(&color).unwrap();
1604 assert!(json.contains("#FF0000"));
1605 }
1606
1607 #[test]
1608 fn test_sync_config_default() {
1609 let config = SyncConfig::default();
1612 assert!(!config.enabled);
1613 let _ = config;
1615 }
1616
1617 #[test]
1618 fn test_sync_directory_structure_serialization() {
1619 let structures = vec![
1620 SyncDirectoryStructure::Flat,
1621 SyncDirectoryStructure::Nested,
1622 SyncDirectoryStructure::Grouped,
1623 ];
1624
1625 for structure in structures {
1626 let json = serde_json::to_string(&structure).unwrap();
1627 assert!(!json.is_empty());
1628 let _deserialized: SyncDirectoryStructure = serde_json::from_str(&json).unwrap();
1630 }
1631 }
1632
1633 #[test]
1634 fn test_sync_direction_serialization() {
1635 let directions = vec![
1636 SyncDirection::Manual,
1637 SyncDirection::WorkspaceToDirectory,
1638 SyncDirection::Bidirectional,
1639 ];
1640
1641 for direction in directions {
1642 let json = serde_json::to_string(&direction).unwrap();
1643 assert!(!json.is_empty());
1644 let _deserialized: SyncDirection = serde_json::from_str(&json).unwrap();
1646 }
1647 }
1648
1649 #[test]
1650 fn test_workspace_config_default() {
1651 let config = WorkspaceConfig::default();
1652 assert!(config.base_url.is_none());
1653 assert!(config.default_headers.is_empty());
1654 assert!(config.auth.is_none());
1655 assert!(config.environments.is_empty());
1656 assert!(config.active_environment_id.is_none());
1657 }
1658
1659 #[test]
1660 fn test_workspace_registry_new() {
1661 let registry = WorkspaceRegistry::new();
1662 assert!(registry.get_workspaces().is_empty());
1663 assert!(registry.get_active_workspace().is_none());
1664 }
1665
1666 #[test]
1667 fn test_workspace_registry_get_active_workspace_id() {
1668 let mut registry = WorkspaceRegistry::new();
1669 let workspace = Workspace::new("Test".to_string());
1670 let id = registry.add_workspace(workspace).unwrap();
1671 registry.set_active_workspace(Some(id.clone())).unwrap();
1672
1673 assert_eq!(registry.get_active_workspace_id(), Some(id.as_str()));
1674 }
1675
1676 #[test]
1677 fn test_workspace_registry_get_workspaces_ordered() {
1678 let mut registry = WorkspaceRegistry::new();
1679 let mut ws1 = Workspace::new("First".to_string());
1680 ws1.order = 2;
1681 let mut ws2 = Workspace::new("Second".to_string());
1682 ws2.order = 1;
1683
1684 registry.add_workspace(ws1).unwrap();
1685 registry.add_workspace(ws2).unwrap();
1686
1687 let ordered = registry.get_workspaces_ordered();
1688 assert_eq!(ordered.len(), 2);
1689 assert_eq!(ordered[0].name, "Second"); assert_eq!(ordered[1].name, "First");
1691 }
1692
1693 #[test]
1694 fn test_workspace_registry_update_workspaces_order() {
1695 let mut registry = WorkspaceRegistry::new();
1696 let id1 = registry.add_workspace(Workspace::new("First".to_string())).unwrap();
1697 let id2 = registry.add_workspace(Workspace::new("Second".to_string())).unwrap();
1698
1699 registry.update_workspaces_order(vec![id2.clone(), id1.clone()]).unwrap();
1700
1701 let ordered = registry.get_workspaces_ordered();
1702 assert_eq!(ordered[0].id, id2);
1703 assert_eq!(ordered[1].id, id1);
1704 }
1705
1706 #[test]
1707 fn test_workspace_registry_update_workspaces_order_invalid_id() {
1708 let mut registry = WorkspaceRegistry::new();
1709 let id1 = registry.add_workspace(Workspace::new("First".to_string())).unwrap();
1710
1711 let result = registry.update_workspaces_order(vec![id1, "invalid-id".to_string()]);
1712 assert!(result.is_err());
1713 }
1714
1715 #[test]
1716 fn test_workspace_registry_set_active_workspace_invalid() {
1717 let mut registry = WorkspaceRegistry::new();
1718 let result = registry.set_active_workspace(Some("invalid-id".to_string()));
1719 assert!(result.is_err());
1720 }
1721
1722 #[test]
1723 fn test_workspace_registry_remove_active_workspace() {
1724 let mut registry = WorkspaceRegistry::new();
1725 let id = registry.add_workspace(Workspace::new("Test".to_string())).unwrap();
1726 registry.set_active_workspace(Some(id.clone())).unwrap();
1727 registry.remove_workspace(&id).unwrap();
1728
1729 assert!(registry.get_active_workspace().is_none());
1730 }
1731
1732 #[test]
1733 fn test_workspace_clone() {
1734 let workspace1 = Workspace::new("Test Workspace".to_string());
1735 let workspace2 = workspace1.clone();
1736 assert_eq!(workspace1.name, workspace2.name);
1737 assert_eq!(workspace1.id, workspace2.id);
1738 }
1739
1740 #[test]
1741 fn test_workspace_debug() {
1742 let workspace = Workspace::new("Debug Test".to_string());
1743 let debug_str = format!("{:?}", workspace);
1744 assert!(debug_str.contains("Workspace"));
1745 }
1746
1747 #[test]
1748 fn test_workspace_serialization() {
1749 let workspace = Workspace::new("Serialization Test".to_string());
1750 let json = serde_json::to_string(&workspace).unwrap();
1751 assert!(json.contains("Serialization Test"));
1752 }
1753
1754 #[test]
1755 fn test_folder_clone() {
1756 let folder1 = Folder::new("Test Folder".to_string());
1757 let folder2 = folder1.clone();
1758 assert_eq!(folder1.name, folder2.name);
1759 assert_eq!(folder1.id, folder2.id);
1760 }
1761
1762 #[test]
1763 fn test_folder_debug() {
1764 let folder = Folder::new("Debug Folder".to_string());
1765 let debug_str = format!("{:?}", folder);
1766 assert!(debug_str.contains("Folder"));
1767 }
1768
1769 #[test]
1770 fn test_folder_serialization() {
1771 let folder = Folder::new("Serialization Folder".to_string());
1772 let json = serde_json::to_string(&folder).unwrap();
1773 assert!(json.contains("Serialization Folder"));
1774 }
1775
1776 #[test]
1777 fn test_folder_inheritance_config_clone() {
1778 let mut config1 = FolderInheritanceConfig::default();
1779 config1.headers.insert("X-Test".to_string(), "value".to_string());
1780 let config2 = config1.clone();
1781 assert_eq!(config1.headers, config2.headers);
1782 }
1783
1784 #[test]
1785 fn test_folder_inheritance_config_debug() {
1786 let config = FolderInheritanceConfig::default();
1787 let debug_str = format!("{:?}", config);
1788 assert!(debug_str.contains("FolderInheritanceConfig"));
1789 }
1790
1791 #[test]
1792 fn test_workspace_config_clone() {
1793 let config1 = WorkspaceConfig {
1794 base_url: Some("https://api.example.com".to_string()),
1795 ..Default::default()
1796 };
1797 let config2 = config1.clone();
1798 assert_eq!(config1.base_url, config2.base_url);
1799 }
1800
1801 #[test]
1802 fn test_workspace_config_debug() {
1803 let config = WorkspaceConfig::default();
1804 let debug_str = format!("{:?}", config);
1805 assert!(debug_str.contains("WorkspaceConfig"));
1806 }
1807
1808 #[test]
1809 fn test_workspace_registry_clone() {
1810 let mut registry1 = WorkspaceRegistry::new();
1811 let id = registry1.add_workspace(Workspace::new("Test".to_string())).unwrap();
1812 let registry2 = registry1.clone();
1813 assert!(registry2.get_workspace(&id).is_some());
1814 }
1815
1816 #[test]
1817 fn test_workspace_registry_debug() {
1818 let registry = WorkspaceRegistry::new();
1819 let debug_str = format!("{:?}", registry);
1820 assert!(debug_str.contains("WorkspaceRegistry"));
1821 }
1822}