Skip to main content

mockforge_core/
workspace.rs

1//! Workspace and folder organization for MockForge requests
2//!
3//! This module has been refactored into sub-modules for better organization:
4//! - core: Core workspace and folder structures
5//! - registry: Workspace registry and management
6//! - environment: Environment configuration and management
7//! - sync: Synchronization functionality
8//! - request: Mock request handling and processing
9
10// Re-export sub-modules for backward compatibility
11pub 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
22// Re-export commonly used types
23pub 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
32// Legacy imports for compatibility
33use 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
44/// Unique identifier for workspace entities
45pub type EntityId = String;
46
47/// Workspace represents a top-level organizational unit
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct Workspace {
50    /// Unique identifier
51    pub id: EntityId,
52    /// Human-readable name
53    pub name: String,
54    /// Optional description
55    pub description: Option<String>,
56    /// Creation timestamp
57    pub created_at: DateTime<Utc>,
58    /// Last modification timestamp
59    pub updated_at: DateTime<Utc>,
60    /// Associated tags for filtering and organization
61    pub tags: Vec<String>,
62    /// Configuration specific to this workspace
63    pub config: WorkspaceConfig,
64    /// Root folders in this workspace
65    pub folders: Vec<Folder>,
66    /// Root requests (not in any folder)
67    pub requests: Vec<MockRequest>,
68    /// Display order for UI sorting (lower numbers appear first)
69    #[serde(default)]
70    pub order: i32,
71}
72
73/// Configuration for request inheritance at folder level
74#[derive(Debug, Clone, Serialize, Deserialize, Default)]
75pub struct FolderInheritanceConfig {
76    /// Headers to be inherited by child requests (if not overridden)
77    #[serde(default)]
78    pub headers: HashMap<String, String>,
79    /// Authentication configuration for inheritance
80    pub auth: Option<AuthConfig>,
81}
82
83/// Folder represents a hierarchical grouping within a workspace
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct Folder {
86    /// Unique identifier
87    pub id: EntityId,
88    /// Human-readable name
89    pub name: String,
90    /// Optional description
91    pub description: Option<String>,
92    /// Parent folder ID (None if root folder)
93    pub parent_id: Option<EntityId>,
94    /// Creation timestamp
95    pub created_at: DateTime<Utc>,
96    /// Last modification timestamp
97    pub updated_at: DateTime<Utc>,
98    /// Associated tags
99    pub tags: Vec<String>,
100    /// Inheritance configuration for this folder
101    #[serde(default)]
102    pub inheritance: FolderInheritanceConfig,
103    /// Child folders
104    pub folders: Vec<Folder>,
105    /// Requests in this folder
106    pub requests: Vec<MockRequest>,
107}
108
109/// Mock request definition with metadata
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct MockRequest {
112    /// Unique identifier
113    pub id: EntityId,
114    /// Human-readable name
115    pub name: String,
116    /// Optional description
117    pub description: Option<String>,
118    /// HTTP method
119    pub method: HttpMethod,
120    /// Request path
121    pub path: String,
122    /// HTTP headers
123    pub headers: HashMap<String, String>,
124    /// Query parameters
125    pub query_params: HashMap<String, String>,
126    /// Request body template
127    pub body: Option<String>,
128    /// Expected response
129    pub response: MockResponse,
130    /// History of actual request executions
131    pub response_history: Vec<ResponseHistoryEntry>,
132    /// Creation timestamp
133    pub created_at: DateTime<Utc>,
134    /// Last modification timestamp
135    pub updated_at: DateTime<Utc>,
136    /// Associated tags
137    pub tags: Vec<String>,
138    /// Authentication configuration
139    pub auth: Option<AuthConfig>,
140    /// Priority for route matching (higher = more specific)
141    pub priority: i32,
142}
143
144/// Mock response definition
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct MockResponse {
147    /// HTTP status code
148    pub status_code: u16,
149    /// Response headers
150    pub headers: HashMap<String, String>,
151    /// Response body template
152    pub body: Option<String>,
153    /// Content type
154    pub content_type: Option<String>,
155    /// Response delay in milliseconds
156    pub delay_ms: Option<u64>,
157}
158
159/// Response history entry for tracking actual request executions
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct ResponseHistoryEntry {
162    /// Unique execution ID
163    pub id: String,
164    /// Execution timestamp
165    pub executed_at: DateTime<Utc>,
166    /// Actual request method used
167    pub request_method: HttpMethod,
168    /// Actual request path used
169    pub request_path: String,
170    /// Request headers sent
171    pub request_headers: HashMap<String, String>,
172    /// Request body sent
173    pub request_body: Option<String>,
174    /// Response status code received
175    pub response_status_code: u16,
176    /// Response headers received
177    pub response_headers: HashMap<String, String>,
178    /// Response body received
179    pub response_body: Option<String>,
180    /// Response time in milliseconds
181    pub response_time_ms: u64,
182    /// Response size in bytes
183    pub response_size_bytes: u64,
184    /// Error message if execution failed
185    pub error_message: Option<String>,
186}
187
188/// Represents a color for environment visualization
189#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct EnvironmentColor {
191    /// Hex color code (e.g., "#FF5733")
192    pub hex: String,
193    /// Optional color name for accessibility
194    pub name: Option<String>,
195}
196
197/// Environment for managing variable collections
198#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct Environment {
200    /// Unique identifier
201    pub id: EntityId,
202    /// Human-readable name
203    pub name: String,
204    /// Optional description
205    pub description: Option<String>,
206    /// Color for visual distinction in UI
207    pub color: Option<EnvironmentColor>,
208    /// Environment variables
209    pub variables: HashMap<String, String>,
210    /// Creation timestamp
211    pub created_at: DateTime<Utc>,
212    /// Last modification timestamp
213    pub updated_at: DateTime<Utc>,
214    /// Display order for UI sorting (lower numbers appear first)
215    #[serde(default)]
216    pub order: i32,
217    /// Whether this environment can be shared/synced
218    #[serde(default)]
219    pub sharable: bool,
220}
221
222/// Directory sync configuration for a workspace
223#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct SyncConfig {
225    /// Enable directory syncing for this workspace
226    pub enabled: bool,
227    /// Target directory for sync (relative or absolute path)
228    pub target_directory: Option<String>,
229    /// Directory structure to use (flat, nested, grouped)
230    pub directory_structure: SyncDirectoryStructure,
231    /// Auto-sync direction (one-way workspace→directory, bidirectional, or manual)
232    pub sync_direction: SyncDirection,
233    /// Whether to include metadata files
234    pub include_metadata: bool,
235    /// Filesystem monitoring enabled for real-time sync
236    pub realtime_monitoring: bool,
237    /// Custom filename pattern for exported files
238    pub filename_pattern: String,
239    /// Regular expression for excluding workspaces/requests
240    pub exclude_pattern: Option<String>,
241    /// Force overwrite existing files during sync
242    pub force_overwrite: bool,
243    /// Last sync timestamp
244    pub last_sync: Option<DateTime<Utc>>,
245}
246
247/// Directory structure options for sync
248#[derive(Debug, Clone, Serialize, Deserialize)]
249pub enum SyncDirectoryStructure {
250    /// All workspaces in flat structure: workspace-name.yaml
251    Flat,
252    /// Nested by workspace: workspaces/{name}/workspace.yaml + requests/
253    Nested,
254    /// Grouped by type: requests/, responses/, metadata/
255    Grouped,
256}
257
258/// Sync direction options
259#[derive(Debug, Clone, Serialize, Deserialize)]
260pub enum SyncDirection {
261    /// Manual sync only (one-off operations)
262    Manual,
263    /// One-way: workspace changes sync silently to directory
264    WorkspaceToDirectory,
265    /// Bidirectional: changes in either direction trigger sync
266    Bidirectional,
267}
268
269/// Workspace-specific configuration
270#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct WorkspaceConfig {
272    /// Base URL for all requests in this workspace
273    pub base_url: Option<String>,
274    /// Default headers for all requests
275    pub default_headers: HashMap<String, String>,
276    /// Authentication configuration
277    pub auth: Option<AuthConfig>,
278    /// Global environment (always available)
279    pub global_environment: Environment,
280    /// Sub-environments (switchable)
281    pub environments: Vec<Environment>,
282    /// Currently active environment ID (None means only global)
283    pub active_environment_id: Option<EntityId>,
284    /// Mock environment manager for dev/test/prod environments
285    /// Manages environment-specific overrides for reality levels, chaos profiles, and drift budgets
286    #[serde(default)]
287    pub mock_environments: MockEnvironmentManager,
288    /// Directory sync configuration
289    pub sync: SyncConfig,
290    /// Automatic encryption configuration
291    #[serde(default)]
292    pub auto_encryption: AutoEncryptionConfig,
293    /// Reality level for this workspace (1-5)
294    /// Controls the realism of mock behavior (chaos, latency, MockAI)
295    /// This is the default reality level; can be overridden per environment
296    #[serde(default)]
297    pub reality_level: Option<RealityLevel>,
298    /// AI mode for this workspace
299    /// Controls how AI-generated artifacts are used at runtime
300    #[serde(default)]
301    pub ai_mode: Option<crate::ai_studio::config::AiMode>,
302    /// Current fidelity score for this workspace
303    /// Measures how close the mock is to the real upstream
304    #[serde(default, skip_serializing_if = "Option::is_none")]
305    pub fidelity_score: Option<FidelityScore>,
306}
307
308/// Workspace registry for managing multiple workspaces
309#[derive(Debug, Clone)]
310pub struct WorkspaceRegistry {
311    workspaces: HashMap<EntityId, Workspace>,
312    active_workspace: Option<EntityId>,
313}
314
315impl Workspace {
316    /// Create a new workspace
317    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, // Default order will be updated when added to registry
331        };
332
333        // Initialize default mock environments (dev/test/prod)
334        workspace.initialize_default_mock_environments();
335
336        workspace
337    }
338
339    /// Initialize default mock environments for this workspace
340    /// This is called when creating a new workspace or when loading an existing one
341    /// that doesn't have mock environments configured
342    pub fn initialize_default_mock_environments(&mut self) {
343        // Only initialize if mock_environments is empty or has no workspace_id set
344        if self.config.mock_environments.workspace_id.is_empty()
345            || self.config.mock_environments.environments.is_empty()
346        {
347            // Update workspace_id if needed
348            if self.config.mock_environments.workspace_id.is_empty() {
349                self.config.mock_environments.workspace_id = self.id.clone();
350            }
351
352            // Create default dev environment if it doesn't exist
353            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            // Create default test environment if it doesn't exist
364            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            // Create default prod environment if it doesn't exist
375            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            // Set dev as the default active environment if none is set
386            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    /// Add a folder to this workspace
394    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    /// Create a new environment
403    pub fn create_environment(
404        &mut self,
405        name: String,
406        description: Option<String>,
407    ) -> Result<EntityId> {
408        // Check if environment name already exists
409        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        // Set order to the end of the list
420        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    /// Get all environments (global + sub-environments)
430    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    /// Get environment by ID
437    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    /// Get environment by ID (mutable)
446    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    /// Set active environment
455    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    /// Get active environment (returns global if no sub-environment is active)
467    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    /// Get active environment ID
476    pub fn get_active_environment_id(&self) -> Option<&str> {
477        self.config.active_environment_id.as_deref()
478    }
479
480    /// Get variable value from current active environment
481    pub fn get_variable(&self, key: &str) -> Option<&String> {
482        // First check active environment, then global environment
483        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    /// Get all variables from current active environment context
494    pub fn get_all_variables(&self) -> HashMap<String, String> {
495        let mut variables = HashMap::new();
496
497        // Start with global environment variables
498        variables.extend(self.config.global_environment.variables.clone());
499
500        // Override with active environment variables if different from global
501        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    /// Delete an environment
510    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            // Clear active environment if it was deleted
520            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    /// Update the order of environments
532    pub fn update_environments_order(&mut self, environment_ids: Vec<String>) -> Result<()> {
533        // Validate that all provided IDs exist
534        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        // Update order for each environment
541        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    /// Get environments sorted by order
553    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    /// Get the mock environment manager
561    pub fn get_mock_environments(&self) -> &MockEnvironmentManager {
562        &self.config.mock_environments
563    }
564
565    /// Get mutable access to the mock environment manager
566    pub fn get_mock_environments_mut(&mut self) -> &mut MockEnvironmentManager {
567        &mut self.config.mock_environments
568    }
569
570    /// Get a specific mock environment by name
571    pub fn get_mock_environment(&self, name: MockEnvironmentName) -> Option<&MockEnvironment> {
572        self.config.mock_environments.get_environment(name)
573    }
574
575    /// Get the active mock environment
576    pub fn get_active_mock_environment(&self) -> Option<&MockEnvironment> {
577        self.config.mock_environments.get_active_environment()
578    }
579
580    /// Set the active mock environment
581    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    /// List all mock environments
588    pub fn list_mock_environments(&self) -> Vec<&MockEnvironment> {
589        self.config.mock_environments.list_environments()
590    }
591
592    /// Add or update a mock environment configuration
593    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        // Get or create the environment
601        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    /// Configure directory sync for this workspace
625    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    /// Enable directory sync with default settings
632    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; // Enable realtime monitoring by default
636        self.updated_at = Utc::now();
637        Ok(())
638    }
639
640    /// Disable directory sync
641    pub fn disable_sync(&mut self) -> Result<()> {
642        self.config.sync.enabled = false;
643        self.updated_at = Utc::now();
644        Ok(())
645    }
646
647    /// Get sync configuration
648    pub fn get_sync_config(&self) -> &SyncConfig {
649        &self.config.sync
650    }
651
652    /// Check if sync is enabled
653    pub fn is_sync_enabled(&self) -> bool {
654        self.config.sync.enabled
655    }
656
657    /// Get the target sync directory
658    pub fn get_sync_directory(&self) -> Option<&str> {
659        self.config.sync.target_directory.as_deref()
660    }
661
662    /// Set sync directory
663    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    /// Set sync direction
670    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    /// Get sync direction
677    pub fn get_sync_direction(&self) -> &SyncDirection {
678        &self.config.sync.sync_direction
679    }
680
681    /// Enable/disable real-time monitoring
682    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    /// Check if real-time monitoring is enabled
689    pub fn is_realtime_monitoring_enabled(&self) -> bool {
690        self.config.sync.realtime_monitoring && self.config.sync.enabled
691    }
692
693    /// Create filtered copy for directory sync (removes sensitive environments and non-sharable environments)
694    pub fn to_filtered_for_sync(&self) -> Workspace {
695        let mut filtered = self.clone();
696
697        // Remove sensitive environment variables before sync
698        // This implementation filters out common sensitive keys
699        filtered.config.global_environment.variables =
700            self.filter_sensitive_variables(&self.config.global_environment.variables);
701
702        // Filter out non-sharable environments
703        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    /// Filter out sensitive environment variables
718    fn filter_sensitive_variables(
719        &self,
720        variables: &HashMap<String, String>,
721    ) -> HashMap<String, String> {
722        let sensitive_keys = [
723            // Common sensitive keys that should not be synced
724            "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    /// Check if this workspace should be included in directory sync
759    pub fn should_sync(&self) -> bool {
760        self.config.sync.enabled && self.config.sync.target_directory.is_some()
761    }
762
763    /// Get the filename for this workspace in directory sync
764    pub fn get_sync_filename(&self) -> String {
765        // Apply filename pattern, default to {name}
766        let pattern = &self.config.sync.filename_pattern;
767
768        // Simple pattern replacement - {name} → workspace name, {id} → workspace id
769        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
781/// Helper function to sanitize filenames for cross-platform compatibility
782fn 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    /// Create a new folder
796    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    /// Add a subfolder
813    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    /// Add a request to this folder
823    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    /// Find a folder by ID recursively
831    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    /// Find a folder by ID recursively (mutable)
844    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    /// Find a request by ID recursively
857    pub fn find_request(&self, id: &str) -> Option<&MockRequest> {
858        // Check this folder's requests
859        for request in &self.requests {
860            if request.id == id {
861                return Some(request);
862            }
863        }
864
865        // Check subfolders
866        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    /// Remove a subfolder by ID. Searches recursively through nested folders.
875    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    /// Remove a request by ID. Searches recursively through nested folders.
891    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    /// Get all routes from this folder and subfolders
907    pub fn get_routes(&self, workspace_id: &str) -> Vec<Route> {
908        let mut routes = Vec::new();
909
910        // Add this folder's requests
911        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        // Add routes from subfolders
922        for folder in &self.folders {
923            routes.extend(folder.get_routes(workspace_id));
924        }
925
926        routes
927    }
928}
929
930impl Workspace {
931    /// Find a folder by ID recursively
932    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    /// Find a folder by ID recursively (mutable)
945    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    /// Add a request to this workspace
958    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    /// Remove a folder by ID. Searches recursively through nested folders.
966    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    /// Remove a request by ID. Searches recursively through nested folders.
982    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    /// Get all routes from this workspace
998    pub fn get_routes(&self) -> Vec<Route> {
999        let mut routes = Vec::new();
1000
1001        // Add workspace-level requests
1002        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        // Add routes from folders
1012        for folder in &self.folders {
1013            routes.extend(folder.get_routes(&self.id));
1014        }
1015
1016        routes
1017    }
1018
1019    /// Get effective authentication for a request at the given path
1020    pub fn get_effective_auth<'a>(&'a self, folder_path: &[&'a Folder]) -> Option<&'a AuthConfig> {
1021        // Check folder inheritance (higher priority)
1022        for folder in folder_path.iter().rev() {
1023            if let Some(auth) = &folder.inheritance.auth {
1024                return Some(auth);
1025            }
1026        }
1027
1028        // Fall back to workspace auth
1029        self.config.auth.as_ref()
1030    }
1031
1032    /// Get merged headers for a request at the given path
1033    pub fn get_effective_headers(&self, folder_path: &[&Folder]) -> HashMap<String, String> {
1034        let mut effective_headers = HashMap::new();
1035
1036        // Start with workspace headers (lowest priority)
1037        for (key, value) in &self.config.default_headers {
1038            effective_headers.insert(key.clone(), value.clone());
1039        }
1040
1041        // Add folder headers (higher priority) in order from parent to child
1042        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    /// Get the inheritance path from this folder to root
1054    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(); // Root first
1065        path
1066    }
1067}
1068
1069impl MockRequest {
1070    /// Apply inheritance to this request, returning headers and auth from the hierarchy
1071    pub fn apply_inheritance(
1072        &mut self,
1073        effective_headers: HashMap<String, String>,
1074        effective_auth: Option<&AuthConfig>,
1075    ) {
1076        // Merge headers - request headers override inherited ones
1077        for (key, value) in effective_headers {
1078            self.headers.entry(key).or_insert(value);
1079        }
1080
1081        // For authentication - store it as a tag or custom field for use by the handler
1082        // This will be used by the request processing middleware
1083        if let Some(auth) = effective_auth {
1084            self.auth = Some(auth.clone());
1085        }
1086    }
1087
1088    /// Create inherited request with merged headers and auth
1089    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    /// Create a new mock request
1102    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    /// Set the response for this request
1124    pub fn with_response(mut self, response: MockResponse) -> Self {
1125        self.response = response;
1126        self
1127    }
1128
1129    /// Add a header
1130    pub fn with_header(mut self, key: String, value: String) -> Self {
1131        self.headers.insert(key, value);
1132        self
1133    }
1134
1135    /// Add a query parameter
1136    pub fn with_query_param(mut self, key: String, value: String) -> Self {
1137        self.query_params.insert(key, value);
1138        self
1139    }
1140
1141    /// Set request body
1142    pub fn with_body(mut self, body: String) -> Self {
1143        self.body = Some(body);
1144        self
1145    }
1146
1147    /// Add a tag
1148    pub fn with_tag(mut self, tag: String) -> Self {
1149        self.tags.push(tag);
1150        self
1151    }
1152
1153    /// Add a response history entry
1154    pub fn add_response_history(&mut self, entry: ResponseHistoryEntry) {
1155        self.response_history.push(entry);
1156        // Keep only last 100 history entries to prevent unbounded growth
1157        if self.response_history.len() > 100 {
1158            self.response_history.remove(0);
1159        }
1160        // Sort by execution time (newest first)
1161        self.response_history.sort_by(|a, b| b.executed_at.cmp(&a.executed_at));
1162    }
1163
1164    /// Get response history (sorted by execution time, newest first)
1165    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    /// Create a new environment
1184    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,        // Default order will be updated when added to workspace
1195            sharable: false, // Default to not sharable
1196        }
1197    }
1198
1199    /// Create a new global environment
1200    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    /// Add or update a variable
1208    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    /// Remove a variable
1214    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    /// Get a variable value
1223    pub fn get_variable(&self, key: &str) -> Option<&String> {
1224        self.variables.get(key)
1225    }
1226
1227    /// Set the environment color
1228    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    /// Create a new workspace registry
1272    pub fn new() -> Self {
1273        Self {
1274            workspaces: HashMap::new(),
1275            active_workspace: None,
1276        }
1277    }
1278
1279    /// Add a workspace
1280    pub fn add_workspace(&mut self, mut workspace: Workspace) -> Result<EntityId> {
1281        let id = workspace.id.clone();
1282
1283        // Set order to the end of the list if not already set
1284        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    /// Get a workspace by ID
1293    pub fn get_workspace(&self, id: &str) -> Option<&Workspace> {
1294        self.workspaces.get(id)
1295    }
1296
1297    /// Get a workspace by ID (mutable)
1298    pub fn get_workspace_mut(&mut self, id: &str) -> Option<&mut Workspace> {
1299        self.workspaces.get_mut(id)
1300    }
1301
1302    /// Remove a workspace
1303    pub fn remove_workspace(&mut self, id: &str) -> Result<()> {
1304        if self.workspaces.remove(id).is_some() {
1305            // Clear active workspace if it was removed
1306            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    /// Set the active workspace
1316    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    /// Get the active workspace
1327    pub fn get_active_workspace(&self) -> Option<&Workspace> {
1328        self.active_workspace.as_ref().and_then(|id| self.workspaces.get(id))
1329    }
1330
1331    /// Get the active workspace ID
1332    pub fn get_active_workspace_id(&self) -> Option<&str> {
1333        self.active_workspace.as_deref()
1334    }
1335
1336    /// Get all workspaces
1337    pub fn get_workspaces(&self) -> Vec<&Workspace> {
1338        self.workspaces.values().collect()
1339    }
1340
1341    /// Get all workspaces sorted by order
1342    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    /// Update the order of workspaces
1349    pub fn update_workspaces_order(&mut self, workspace_ids: Vec<String>) -> Result<()> {
1350        // Validate that all provided IDs exist
1351        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        // Update order for each workspace
1358        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    /// Get all routes from all workspaces
1369    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    /// Create a route registry from all workspaces
1378    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        // Add folder
1435        let folder_id = workspace.add_folder("Test Folder".to_string()).unwrap();
1436        assert_eq!(workspace.folders.len(), 1);
1437
1438        // Add request to workspace
1439        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        // Add request to folder
1445        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        // Set as active
1463        registry.set_active_workspace(Some(workspace_id.clone())).unwrap();
1464        assert!(registry.get_active_workspace().is_some());
1465
1466        // Get workspace
1467        let retrieved = registry.get_workspace(&workspace_id).unwrap();
1468        assert_eq!(retrieved.name, "Test Workspace");
1469
1470        // Remove workspace
1471        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        // Add folder with inheritance
1488        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        // Test single folder
1499        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"); // Folder overrides workspace
1503        assert_eq!(effective_headers.get("X-Workspace-Only").unwrap(), "workspace-only-value"); // Workspace value preserved
1504        assert_eq!(effective_headers.get("X-Folder-Only").unwrap(), "folder-only-value");
1505        // Folder value added
1506    }
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 headers should override inherited ones
1522        request
1523            .headers
1524            .insert("Authorization".to_string(), "Bearer request-token".to_string());
1525
1526        // Apply inheritance - request headers should take priority
1527        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        // Parent folder
1541        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        // Child folder
1552        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        // Parent-to-child hierarchy
1563        let folder_path = vec![&parent_folder, &child_folder];
1564        let effective_headers = workspace.get_effective_headers(&folder_path);
1565
1566        // Child should override parent which overrides workspace
1567        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        // Create workspace without auth
1575        let workspace = Workspace::new("Test Workspace".to_string());
1576
1577        // Create folder with auth
1578        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        // Use the SyncConfig from workspace.rs (not from sync module)
1674        // This is the one used in WorkspaceConfig
1675        let config = SyncConfig::default();
1676        assert!(!config.enabled);
1677        // Just verify it can be created
1678        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            // Just verify it can be deserialized
1693            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            // Just verify it can be deserialized
1709            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"); // Lower order first
1754        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}