mockforge_http/
ui_builder.rs

1/// UI Builder API for MockForge
2///
3/// Provides REST endpoints for the low-code UI builder that allows visual
4/// creation and editing of mock endpoints without writing code.
5use axum::{
6    extract::{Path, State},
7    http::StatusCode,
8    response::{IntoResponse, Json},
9    routing::{get, post},
10    Router,
11};
12use mockforge_core::config::ServerConfig;
13use mockforge_core::import::asyncapi_import::import_asyncapi_spec;
14use mockforge_core::import::openapi_import::import_openapi_spec;
15use serde::{Deserialize, Serialize};
16use std::sync::Arc;
17use tokio::sync::RwLock;
18use tracing::*;
19
20/// Endpoint configuration for UI builder
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct EndpointConfig {
23    /// Unique identifier for the endpoint
24    pub id: String,
25    /// Protocol type for this endpoint
26    pub protocol: Protocol,
27    /// Human-readable endpoint name
28    pub name: String,
29    /// Optional endpoint description
30    pub description: Option<String>,
31    /// Whether this endpoint is currently enabled
32    pub enabled: bool,
33    /// Protocol-specific configuration
34    pub config: EndpointProtocolConfig,
35}
36
37/// Supported protocols in UI builder
38#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
39#[serde(rename_all = "lowercase")]
40pub enum Protocol {
41    /// HTTP/REST protocol
42    Http,
43    /// gRPC protocol
44    Grpc,
45    /// WebSocket protocol
46    Websocket,
47    /// GraphQL protocol
48    Graphql,
49    /// MQTT protocol
50    Mqtt,
51    /// SMTP protocol
52    Smtp,
53    /// Kafka protocol
54    Kafka,
55    /// AMQP protocol
56    Amqp,
57    /// FTP protocol
58    Ftp,
59}
60
61/// Protocol-specific endpoint configuration
62#[derive(Debug, Clone, Serialize, Deserialize)]
63#[serde(tag = "type")]
64pub enum EndpointProtocolConfig {
65    /// HTTP endpoint configuration
66    Http(HttpEndpointConfig),
67    /// gRPC endpoint configuration
68    Grpc(GrpcEndpointConfig),
69    /// WebSocket endpoint configuration
70    Websocket(WebsocketEndpointConfig),
71}
72
73/// HTTP endpoint configuration
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct HttpEndpointConfig {
76    /// HTTP method (GET, POST, PUT, etc.)
77    pub method: String,
78    /// API path pattern
79    pub path: String,
80    /// Optional request validation and schema configuration
81    pub request: Option<HttpRequestConfig>,
82    /// Response configuration
83    pub response: HttpResponseConfig,
84    /// Optional behavior configuration (latency, failure injection, etc.)
85    pub behavior: Option<EndpointBehavior>,
86}
87
88/// HTTP request configuration
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct HttpRequestConfig {
91    /// Optional validation settings
92    pub validation: Option<ValidationConfig>,
93    /// Optional custom request headers
94    pub headers: Option<Vec<HeaderConfig>>,
95    /// Optional query parameter definitions
96    pub query_params: Option<Vec<QueryParamConfig>>,
97    /// Optional JSON schema for request body
98    pub body_schema: Option<serde_json::Value>,
99}
100
101/// HTTP response configuration
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct HttpResponseConfig {
104    /// HTTP status code to return
105    pub status: u16,
106    /// Optional custom response headers
107    pub headers: Option<Vec<HeaderConfig>>,
108    /// Response body configuration
109    pub body: ResponseBody,
110}
111
112/// Response body configuration
113#[derive(Debug, Clone, Serialize, Deserialize)]
114#[serde(tag = "type")]
115pub enum ResponseBody {
116    /// Static JSON response content
117    Static {
118        /// JSON value to return
119        content: serde_json::Value,
120    },
121    /// Template-based response with variable expansion
122    Template {
123        /// Template string with variables (e.g., "{{uuid}}", "{{now}}")
124        template: String,
125    },
126    /// Faker-generated response from schema
127    Faker {
128        /// JSON schema for data generation
129        schema: serde_json::Value,
130    },
131    /// AI-generated response
132    AI {
133        /// Prompt for AI generation
134        prompt: String,
135    },
136}
137
138/// Header configuration
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct HeaderConfig {
141    /// Header name
142    pub name: String,
143    /// Header value
144    pub value: String,
145}
146
147/// Query parameter configuration
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct QueryParamConfig {
150    /// Parameter name
151    pub name: String,
152    /// Whether this parameter is required
153    pub required: bool,
154    /// Optional JSON schema for validation
155    pub schema: Option<serde_json::Value>,
156}
157
158/// Validation configuration
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ValidationConfig {
161    /// Validation mode (off, warn, or enforce)
162    pub mode: ValidationMode,
163    /// Optional JSON schema for validation
164    pub schema: Option<serde_json::Value>,
165}
166
167/// Validation mode
168#[derive(Debug, Clone, Serialize, Deserialize)]
169#[serde(rename_all = "lowercase")]
170pub enum ValidationMode {
171    /// Validation disabled
172    Off,
173    /// Validation enabled but only warns on errors
174    Warn,
175    /// Validation enabled and rejects invalid requests
176    Enforce,
177}
178
179/// Endpoint behavior configuration (chaos engineering)
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct EndpointBehavior {
182    /// Optional latency injection configuration
183    pub latency: Option<LatencyConfig>,
184    /// Optional failure injection configuration
185    pub failure: Option<FailureConfig>,
186    /// Optional traffic shaping configuration
187    pub traffic_shaping: Option<TrafficShapingConfig>,
188}
189
190/// Latency configuration
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct LatencyConfig {
193    /// Base latency in milliseconds
194    pub base_ms: u64,
195    /// Random jitter to add to base latency (milliseconds)
196    pub jitter_ms: u64,
197    /// Distribution type for latency simulation
198    pub distribution: LatencyDistribution,
199}
200
201/// Latency distribution type
202#[derive(Debug, Clone, Serialize, Deserialize)]
203#[serde(rename_all = "lowercase")]
204pub enum LatencyDistribution {
205    /// Fixed latency (base + uniform jitter)
206    Fixed,
207    /// Normal/Gaussian distribution
208    Normal {
209        /// Standard deviation in milliseconds
210        std_dev_ms: f64,
211    },
212    /// Pareto distribution (for realistic network simulation)
213    Pareto {
214        /// Shape parameter for Pareto distribution
215        shape: f64,
216    },
217}
218
219/// Failure configuration
220#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct FailureConfig {
222    /// Error injection rate (0.0 to 1.0, where 1.0 = 100%)
223    pub error_rate: f64,
224    /// List of HTTP status codes to randomly return on failure
225    pub status_codes: Vec<u16>,
226    /// Optional custom error message
227    pub error_message: Option<String>,
228}
229
230/// Traffic shaping configuration
231#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct TrafficShapingConfig {
233    /// Optional bandwidth limit in bytes per second
234    pub bandwidth_limit_bps: Option<u64>,
235    /// Optional packet loss rate (0.0 to 1.0)
236    pub packet_loss_rate: Option<f64>,
237}
238
239/// gRPC endpoint configuration
240#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct GrpcEndpointConfig {
242    /// gRPC service name
243    pub service: String,
244    /// gRPC method name
245    pub method: String,
246    /// Path to proto file
247    pub proto_file: String,
248    /// Request message type name
249    pub request_type: String,
250    /// Response message type name
251    pub response_type: String,
252    /// Response configuration
253    pub response: GrpcResponseConfig,
254    /// Optional behavior configuration
255    pub behavior: Option<EndpointBehavior>,
256}
257
258/// gRPC response configuration
259#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct GrpcResponseConfig {
261    /// Response body configuration
262    pub body: ResponseBody,
263    /// Optional metadata (gRPC headers)
264    pub metadata: Option<Vec<HeaderConfig>>,
265}
266
267/// WebSocket endpoint configuration
268#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct WebsocketEndpointConfig {
270    /// WebSocket connection path
271    pub path: String,
272    /// Action to perform when connection is established
273    pub on_connect: Option<WebsocketAction>,
274    /// Action to perform when message is received
275    pub on_message: Option<WebsocketAction>,
276    /// Action to perform when connection is closed
277    pub on_disconnect: Option<WebsocketAction>,
278    /// Optional behavior configuration
279    pub behavior: Option<EndpointBehavior>,
280}
281
282/// WebSocket action type
283#[derive(Debug, Clone, Serialize, Deserialize)]
284#[serde(tag = "type")]
285pub enum WebsocketAction {
286    /// Send a message to the client
287    Send {
288        /// Message body to send
289        message: ResponseBody,
290    },
291    /// Broadcast a message to all connected clients
292    Broadcast {
293        /// Message body to broadcast
294        message: ResponseBody,
295    },
296    /// Echo received messages back to sender
297    Echo,
298    /// Close the connection
299    Close {
300        /// WebSocket close code
301        code: u16,
302        /// Close reason message
303        reason: String,
304    },
305}
306
307/// Configuration validation result
308#[derive(Debug, Clone, Serialize, Deserialize)]
309pub struct ValidationResult {
310    /// Whether the configuration is valid
311    pub valid: bool,
312    /// List of validation errors if any
313    pub errors: Vec<ValidationError>,
314    /// List of validation warnings if any
315    pub warnings: Vec<String>,
316}
317
318/// Validation error
319#[derive(Debug, Clone, Serialize, Deserialize)]
320pub struct ValidationError {
321    /// Field name that failed validation
322    pub field: String,
323    /// Error message describing the issue
324    pub message: String,
325}
326
327/// UI Builder state
328#[derive(Clone)]
329pub struct UIBuilderState {
330    /// Collection of endpoint configurations
331    pub endpoints: Arc<RwLock<Vec<EndpointConfig>>>,
332    /// Server configuration
333    pub server_config: Arc<RwLock<ServerConfig>>,
334}
335
336impl UIBuilderState {
337    /// Create a new UI builder state
338    ///
339    /// # Arguments
340    /// * `server_config` - Server configuration for the mock server
341    pub fn new(server_config: ServerConfig) -> Self {
342        Self {
343            endpoints: Arc::new(RwLock::new(Vec::new())),
344            server_config: Arc::new(RwLock::new(server_config)),
345        }
346    }
347}
348
349/// List all endpoints
350async fn list_endpoints(State(state): State<UIBuilderState>) -> Json<serde_json::Value> {
351    let endpoints = state.endpoints.read().await;
352    Json(serde_json::json!({
353        "endpoints": *endpoints,
354        "total": endpoints.len(),
355        "enabled": endpoints.iter().filter(|e| e.enabled).count(),
356        "by_protocol": {
357            "http": endpoints.iter().filter(|e| e.protocol == Protocol::Http).count(),
358            "grpc": endpoints.iter().filter(|e| e.protocol == Protocol::Grpc).count(),
359            "websocket": endpoints.iter().filter(|e| e.protocol == Protocol::Websocket).count(),
360        }
361    }))
362}
363
364/// Get a specific endpoint by ID
365async fn get_endpoint(
366    State(state): State<UIBuilderState>,
367    Path(id): Path<String>,
368) -> Result<Json<EndpointConfig>, StatusCode> {
369    let endpoints = state.endpoints.read().await;
370    endpoints
371        .iter()
372        .find(|e| e.id == id)
373        .cloned()
374        .map(Json)
375        .ok_or(StatusCode::NOT_FOUND)
376}
377
378/// Create a new endpoint
379async fn create_endpoint(
380    State(state): State<UIBuilderState>,
381    Json(mut endpoint): Json<EndpointConfig>,
382) -> Result<Json<EndpointConfig>, StatusCode> {
383    let mut endpoints = state.endpoints.write().await;
384
385    // Generate ID if not provided
386    if endpoint.id.is_empty() {
387        endpoint.id = uuid::Uuid::new_v4().to_string();
388    }
389
390    // Check for duplicate ID
391    if endpoints.iter().any(|e| e.id == endpoint.id) {
392        return Err(StatusCode::CONFLICT);
393    }
394
395    info!(
396        endpoint_id = %endpoint.id,
397        protocol = ?endpoint.protocol,
398        "Creating new endpoint"
399    );
400
401    endpoints.push(endpoint.clone());
402    Ok(Json(endpoint))
403}
404
405/// Update an existing endpoint
406async fn update_endpoint(
407    State(state): State<UIBuilderState>,
408    Path(id): Path<String>,
409    Json(updated): Json<EndpointConfig>,
410) -> Result<Json<EndpointConfig>, StatusCode> {
411    let mut endpoints = state.endpoints.write().await;
412
413    let endpoint = endpoints.iter_mut().find(|e| e.id == id).ok_or(StatusCode::NOT_FOUND)?;
414
415    info!(
416        endpoint_id = %id,
417        protocol = ?updated.protocol,
418        "Updating endpoint"
419    );
420
421    *endpoint = updated.clone();
422    Ok(Json(updated))
423}
424
425/// Delete an endpoint
426async fn delete_endpoint(
427    State(state): State<UIBuilderState>,
428    Path(id): Path<String>,
429) -> Result<StatusCode, StatusCode> {
430    let mut endpoints = state.endpoints.write().await;
431
432    let index = endpoints.iter().position(|e| e.id == id).ok_or(StatusCode::NOT_FOUND)?;
433
434    info!(endpoint_id = %id, "Deleting endpoint");
435
436    endpoints.remove(index);
437    Ok(StatusCode::NO_CONTENT)
438}
439
440/// Validate endpoint configuration
441async fn validate_endpoint(
442    State(_state): State<UIBuilderState>,
443    Json(endpoint): Json<EndpointConfig>,
444) -> Json<ValidationResult> {
445    let mut errors = Vec::new();
446    let mut warnings = Vec::new();
447
448    // Validate based on protocol
449    match &endpoint.config {
450        EndpointProtocolConfig::Http(http_config) => {
451            // Validate HTTP method
452            let valid_methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
453            if !valid_methods.contains(&http_config.method.to_uppercase().as_str()) {
454                errors.push(ValidationError {
455                    field: "method".to_string(),
456                    message: format!("Invalid HTTP method: {}", http_config.method),
457                });
458            }
459
460            // Validate path
461            if !http_config.path.starts_with('/') {
462                errors.push(ValidationError {
463                    field: "path".to_string(),
464                    message: "Path must start with /".to_string(),
465                });
466            }
467
468            // Validate status code
469            if !(100..600).contains(&http_config.response.status) {
470                errors.push(ValidationError {
471                    field: "status".to_string(),
472                    message: "Status code must be between 100 and 599".to_string(),
473                });
474            }
475        }
476        EndpointProtocolConfig::Grpc(grpc_config) => {
477            // Validate gRPC service and method names
478            if grpc_config.service.is_empty() {
479                errors.push(ValidationError {
480                    field: "service".to_string(),
481                    message: "Service name is required".to_string(),
482                });
483            }
484            if grpc_config.method.is_empty() {
485                errors.push(ValidationError {
486                    field: "method".to_string(),
487                    message: "Method name is required".to_string(),
488                });
489            }
490        }
491        EndpointProtocolConfig::Websocket(ws_config) => {
492            // Validate WebSocket path
493            if !ws_config.path.starts_with('/') {
494                errors.push(ValidationError {
495                    field: "path".to_string(),
496                    message: "Path must start with /".to_string(),
497                });
498            }
499        }
500    }
501
502    // Check for behavior configuration warnings
503    if let Some(EndpointProtocolConfig::Http(http_config)) = Some(&endpoint.config) {
504        if let Some(behavior) = &http_config.behavior {
505            if let Some(failure) = &behavior.failure {
506                if failure.error_rate > 0.5 {
507                    warnings.push("High error rate configured (>50%)".to_string());
508                }
509            }
510        }
511    }
512
513    Json(ValidationResult {
514        valid: errors.is_empty(),
515        errors,
516        warnings,
517    })
518}
519
520/// Export configuration as YAML
521async fn export_config(
522    State(state): State<UIBuilderState>,
523) -> Result<impl IntoResponse, StatusCode> {
524    let server_config = state.server_config.read().await;
525
526    match serde_yaml::to_string(&*server_config) {
527        Ok(yaml) => Ok((StatusCode::OK, [("Content-Type", "application/x-yaml")], yaml)),
528        Err(e) => {
529            error!(error = %e, "Failed to serialize config to YAML");
530            Err(StatusCode::INTERNAL_SERVER_ERROR)
531        }
532    }
533}
534
535/// Import configuration from YAML/JSON
536#[derive(Debug, Deserialize)]
537struct ImportRequest {
538    config: String,
539    format: ConfigFormat,
540}
541
542#[derive(Debug, Deserialize)]
543#[serde(rename_all = "lowercase")]
544enum ConfigFormat {
545    Yaml,
546    Json,
547}
548
549async fn import_config(
550    State(state): State<UIBuilderState>,
551    Json(request): Json<ImportRequest>,
552) -> Result<Json<serde_json::Value>, StatusCode> {
553    let config: ServerConfig = match request.format {
554        ConfigFormat::Yaml => serde_yaml::from_str(&request.config).map_err(|e| {
555            error!(error = %e, "Failed to parse YAML config");
556            StatusCode::BAD_REQUEST
557        })?,
558        ConfigFormat::Json => serde_json::from_str(&request.config).map_err(|e| {
559            error!(error = %e, "Failed to parse JSON config");
560            StatusCode::BAD_REQUEST
561        })?,
562    };
563
564    let mut server_config = state.server_config.write().await;
565    *server_config = config;
566
567    info!("Configuration imported successfully");
568
569    Ok(Json(serde_json::json!({
570        "success": true,
571        "message": "Configuration imported successfully"
572    })))
573}
574
575/// Get current server configuration
576async fn get_config(State(state): State<UIBuilderState>) -> Json<ServerConfig> {
577    let config = state.server_config.read().await;
578    Json(config.clone())
579}
580
581/// Update server configuration
582async fn update_config(
583    State(state): State<UIBuilderState>,
584    Json(new_config): Json<ServerConfig>,
585) -> Result<Json<ServerConfig>, StatusCode> {
586    let mut config = state.server_config.write().await;
587    *config = new_config.clone();
588
589    info!("Server configuration updated");
590
591    Ok(Json(new_config))
592}
593
594/// Import OpenAPI specification request
595#[derive(Debug, Deserialize)]
596struct ImportOpenApiRequest {
597    content: String,
598    base_url: Option<String>,
599    auto_enable: Option<bool>,
600}
601
602/// Import OpenAPI specification response
603#[derive(Debug, Serialize)]
604struct ImportOpenApiResponse {
605    success: bool,
606    endpoints_created: usize,
607    warnings: Vec<String>,
608    spec_info: OpenApiSpecInfoResponse,
609}
610
611#[derive(Debug, Serialize)]
612struct OpenApiSpecInfoResponse {
613    title: String,
614    version: String,
615    description: Option<String>,
616    openapi_version: String,
617    servers: Vec<String>,
618}
619
620/// Import OpenAPI/Swagger specification and auto-generate endpoints
621async fn import_openapi_spec_handler(
622    State(state): State<UIBuilderState>,
623    Json(request): Json<ImportOpenApiRequest>,
624) -> Result<Json<ImportOpenApiResponse>, (StatusCode, Json<serde_json::Value>)> {
625    info!("Importing OpenAPI specification");
626
627    // Import the OpenAPI spec
628    let import_result = import_openapi_spec(&request.content, request.base_url.as_deref())
629        .map_err(|e| {
630            error!(error = %e, "Failed to import OpenAPI spec");
631            (
632                StatusCode::BAD_REQUEST,
633                Json(serde_json::json!({
634                    "error": "Failed to import OpenAPI specification",
635                    "details": e
636                })),
637            )
638        })?;
639
640    let auto_enable = request.auto_enable.unwrap_or(true);
641    let mut endpoints = state.endpoints.write().await;
642    let mut created_count = 0;
643
644    // Convert imported routes to EndpointConfig
645    for route in import_result.routes {
646        let endpoint_id = uuid::Uuid::new_v4().to_string();
647
648        // Convert response body to ResponseBody enum
649        let response_body = ResponseBody::Static {
650            content: route.response.body,
651        };
652
653        // Convert headers to HeaderConfig
654        let response_headers: Option<Vec<HeaderConfig>> = if route.response.headers.is_empty() {
655            None
656        } else {
657            Some(
658                route
659                    .response
660                    .headers
661                    .into_iter()
662                    .map(|(name, value)| HeaderConfig { name, value })
663                    .collect(),
664            )
665        };
666
667        let endpoint = EndpointConfig {
668            id: endpoint_id.clone(),
669            protocol: Protocol::Http,
670            name: format!("{} {}", route.method.to_uppercase(), route.path),
671            description: Some(format!(
672                "Auto-generated from OpenAPI spec: {} v{}",
673                import_result.spec_info.title, import_result.spec_info.version
674            )),
675            enabled: auto_enable,
676            config: EndpointProtocolConfig::Http(HttpEndpointConfig {
677                method: route.method.to_uppercase(),
678                path: route.path,
679                request: None,
680                response: HttpResponseConfig {
681                    status: route.response.status,
682                    headers: response_headers,
683                    body: response_body,
684                },
685                behavior: None,
686            }),
687        };
688
689        info!(
690            endpoint_id = %endpoint_id,
691            method = %endpoint.name,
692            "Created endpoint from OpenAPI spec"
693        );
694
695        endpoints.push(endpoint);
696        created_count += 1;
697    }
698
699    Ok(Json(ImportOpenApiResponse {
700        success: true,
701        endpoints_created: created_count,
702        warnings: import_result.warnings,
703        spec_info: OpenApiSpecInfoResponse {
704            title: import_result.spec_info.title,
705            version: import_result.spec_info.version,
706            description: import_result.spec_info.description,
707            openapi_version: import_result.spec_info.openapi_version,
708            servers: import_result.spec_info.servers,
709        },
710    }))
711}
712
713/// Export endpoints as OpenAPI specification
714async fn export_openapi_spec_handler(
715    State(state): State<UIBuilderState>,
716) -> Result<impl IntoResponse, StatusCode> {
717    let endpoints = state.endpoints.read().await;
718    let server_config = state.server_config.read().await;
719
720    // Build OpenAPI spec from endpoints
721    let http_host = &server_config.http.host;
722    let http_port = server_config.http.port;
723
724    let mut spec = serde_json::json!({
725        "openapi": "3.0.0",
726        "info": {
727            "title": "MockForge Generated API",
728            "version": "1.0.0",
729            "description": "API specification generated from MockForge endpoints"
730        },
731        "servers": [
732            {
733                "url": format!("http://{}:{}", http_host, http_port)
734            }
735        ],
736        "paths": {}
737    });
738
739    // Get the paths object - this should always succeed since we just created it above
740    let paths = spec["paths"].as_object_mut().ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
741
742    // Group endpoints by path
743    for endpoint in endpoints.iter() {
744        if endpoint.protocol != Protocol::Http {
745            continue; // Only export HTTP endpoints for now
746        }
747
748        if let EndpointProtocolConfig::Http(http_config) = &endpoint.config {
749            let path = &http_config.path;
750
751            if !paths.contains_key(path) {
752                paths.insert(path.clone(), serde_json::json!({}));
753            }
754
755            let method = http_config.method.to_lowercase();
756
757            // Build response body
758            let response_body_content = match &http_config.response.body {
759                ResponseBody::Static { content } => content.clone(),
760                ResponseBody::Template { template } => serde_json::json!({
761                    "type": "string",
762                    "example": template
763                }),
764                ResponseBody::Faker { schema } => schema.clone(),
765                ResponseBody::AI { prompt } => serde_json::json!({
766                    "type": "string",
767                    "description": prompt
768                }),
769            };
770
771            let operation = serde_json::json!({
772                "summary": &endpoint.name,
773                "description": endpoint.description.as_ref().unwrap_or(&String::new()),
774                "operationId": &endpoint.id,
775                "responses": {
776                    http_config.response.status.to_string(): {
777                        "description": format!("Response with status {}", http_config.response.status),
778                        "content": {
779                            "application/json": {
780                                "schema": {
781                                    "type": "object"
782                                },
783                                "example": response_body_content
784                            }
785                        }
786                    }
787                }
788            });
789
790            paths[path][&method] = operation;
791        }
792    }
793
794    match serde_json::to_string_pretty(&spec) {
795        Ok(json) => Ok((StatusCode::OK, [("Content-Type", "application/json")], json)),
796        Err(e) => {
797            error!(error = %e, "Failed to serialize OpenAPI spec to JSON");
798            Err(StatusCode::INTERNAL_SERVER_ERROR)
799        }
800    }
801}
802
803/// Import AsyncAPI specification request
804#[derive(Debug, Deserialize)]
805struct ImportAsyncApiRequest {
806    content: String,
807    base_url: Option<String>,
808    auto_enable: Option<bool>,
809}
810
811/// Import AsyncAPI specification response
812#[derive(Debug, Serialize)]
813struct ImportAsyncApiResponse {
814    success: bool,
815    endpoints_created: usize,
816    warnings: Vec<String>,
817    spec_info: AsyncApiSpecInfoResponse,
818}
819
820#[derive(Debug, Serialize)]
821struct AsyncApiSpecInfoResponse {
822    title: String,
823    version: String,
824    description: Option<String>,
825    asyncapi_version: String,
826    servers: Vec<String>,
827}
828
829/// Import AsyncAPI specification and auto-generate WebSocket/MQTT/Kafka endpoints
830async fn import_asyncapi_spec_handler(
831    State(state): State<UIBuilderState>,
832    Json(request): Json<ImportAsyncApiRequest>,
833) -> Result<Json<ImportAsyncApiResponse>, (StatusCode, Json<serde_json::Value>)> {
834    info!("Importing AsyncAPI specification");
835
836    // Import the AsyncAPI spec
837    let import_result = import_asyncapi_spec(&request.content, request.base_url.as_deref())
838        .map_err(|e| {
839            error!(error = %e, "Failed to import AsyncAPI spec");
840            (
841                StatusCode::BAD_REQUEST,
842                Json(serde_json::json!({
843                    "error": "Failed to import AsyncAPI specification",
844                    "details": e
845                })),
846            )
847        })?;
848
849    let auto_enable = request.auto_enable.unwrap_or(true);
850    let mut endpoints = state.endpoints.write().await;
851    let mut created_count = 0;
852
853    // Convert imported channels to EndpointConfig
854    for channel in import_result.channels {
855        let endpoint_id = uuid::Uuid::new_v4().to_string();
856
857        // Map AsyncAPI protocol to MockForge protocol
858        let (protocol, config) = match channel.protocol {
859            mockforge_core::import::asyncapi_import::ChannelProtocol::Websocket => {
860                // Create WebSocket endpoint
861                let on_message = if let Some(op) = channel.operations.first() {
862                    if let Some(example) = &op.example_message {
863                        Some(WebsocketAction::Send {
864                            message: ResponseBody::Static {
865                                content: example.clone(),
866                            },
867                        })
868                    } else {
869                        Some(WebsocketAction::Echo)
870                    }
871                } else {
872                    Some(WebsocketAction::Echo)
873                };
874
875                (
876                    Protocol::Websocket,
877                    EndpointProtocolConfig::Websocket(WebsocketEndpointConfig {
878                        path: channel.path.clone(),
879                        on_connect: None,
880                        on_message,
881                        on_disconnect: None,
882                        behavior: None,
883                    }),
884                )
885            }
886            _ => {
887                // For MQTT/Kafka/AMQP, we'll skip for now as they need special handling
888                // Log a warning and continue
889                warn!(
890                    "Skipping channel '{}' with protocol {:?} - not yet supported for UI Builder",
891                    channel.name, channel.protocol
892                );
893                continue;
894            }
895        };
896
897        let endpoint = EndpointConfig {
898            id: endpoint_id.clone(),
899            protocol,
900            name: format!("{} - {}", channel.name, channel.path),
901            description: channel.description.or_else(|| {
902                Some(format!(
903                    "Auto-generated from AsyncAPI spec: {} v{}",
904                    import_result.spec_info.title, import_result.spec_info.version
905                ))
906            }),
907            enabled: auto_enable,
908            config,
909        };
910
911        info!(
912            endpoint_id = %endpoint_id,
913            name = %endpoint.name,
914            "Created endpoint from AsyncAPI spec"
915        );
916
917        endpoints.push(endpoint);
918        created_count += 1;
919    }
920
921    Ok(Json(ImportAsyncApiResponse {
922        success: true,
923        endpoints_created: created_count,
924        warnings: import_result.warnings,
925        spec_info: AsyncApiSpecInfoResponse {
926            title: import_result.spec_info.title,
927            version: import_result.spec_info.version,
928            description: import_result.spec_info.description,
929            asyncapi_version: import_result.spec_info.asyncapi_version,
930            servers: import_result.spec_info.servers,
931        },
932    }))
933}
934
935/// Resolve tokens in a ResponseBody
936pub async fn resolve_response_body_tokens(
937    body: &ResponseBody,
938) -> Result<serde_json::Value, String> {
939    use crate::token_response::resolve_response_tokens;
940
941    match body {
942        ResponseBody::Static { content } => resolve_response_tokens(content.clone()).await,
943        ResponseBody::Template { template } => {
944            // Templates can also contain tokens, parse and resolve
945            let value = serde_json::Value::String(template.clone());
946            resolve_response_tokens(value).await
947        }
948        ResponseBody::Faker { schema } => {
949            // Faker schemas may contain tokens
950            resolve_response_tokens(schema.clone()).await
951        }
952        ResponseBody::AI { prompt: _ } => {
953            // AI prompts are handled separately by the AI handler
954            // Return as-is since AI generation happens at response time
955            Ok(serde_json::json!({"_ai_prompt": true}))
956        }
957    }
958}
959
960/// Create the UI Builder router
961pub fn create_ui_builder_router(state: UIBuilderState) -> Router {
962    Router::new()
963        .route("/endpoints", get(list_endpoints).post(create_endpoint))
964        .route(
965            "/endpoints/{id}",
966            get(get_endpoint).put(update_endpoint).delete(delete_endpoint),
967        )
968        .route("/endpoints/validate", post(validate_endpoint))
969        .route("/config", get(get_config).put(update_config))
970        .route("/config/export", get(export_config))
971        .route("/config/import", post(import_config))
972        .route("/openapi/import", post(import_openapi_spec_handler))
973        .route("/openapi/export", get(export_openapi_spec_handler))
974        .route("/asyncapi/import", post(import_asyncapi_spec_handler))
975        .with_state(state)
976}
977
978#[cfg(test)]
979mod tests {
980    use super::*;
981
982    // ==================== Helper Functions ====================
983
984    fn create_test_http_endpoint() -> EndpointConfig {
985        EndpointConfig {
986            id: "test-1".to_string(),
987            protocol: Protocol::Http,
988            name: "Test Endpoint".to_string(),
989            description: Some("A test endpoint".to_string()),
990            enabled: true,
991            config: EndpointProtocolConfig::Http(HttpEndpointConfig {
992                method: "GET".to_string(),
993                path: "/test".to_string(),
994                request: None,
995                response: HttpResponseConfig {
996                    status: 200,
997                    headers: None,
998                    body: ResponseBody::Static {
999                        content: serde_json::json!({"message": "Hello"}),
1000                    },
1001                },
1002                behavior: None,
1003            }),
1004        }
1005    }
1006
1007    // ==================== Protocol Tests ====================
1008
1009    #[test]
1010    fn test_protocol_http_serialization() {
1011        let protocol = Protocol::Http;
1012        let json = serde_json::to_string(&protocol).unwrap();
1013        assert_eq!(json, "\"http\"");
1014    }
1015
1016    #[test]
1017    fn test_protocol_grpc_serialization() {
1018        let protocol = Protocol::Grpc;
1019        let json = serde_json::to_string(&protocol).unwrap();
1020        assert_eq!(json, "\"grpc\"");
1021    }
1022
1023    #[test]
1024    fn test_protocol_websocket_serialization() {
1025        let protocol = Protocol::Websocket;
1026        let json = serde_json::to_string(&protocol).unwrap();
1027        assert_eq!(json, "\"websocket\"");
1028    }
1029
1030    #[test]
1031    fn test_protocol_graphql_serialization() {
1032        let protocol = Protocol::Graphql;
1033        let json = serde_json::to_string(&protocol).unwrap();
1034        assert_eq!(json, "\"graphql\"");
1035    }
1036
1037    #[test]
1038    fn test_protocol_mqtt_serialization() {
1039        let protocol = Protocol::Mqtt;
1040        let json = serde_json::to_string(&protocol).unwrap();
1041        assert_eq!(json, "\"mqtt\"");
1042    }
1043
1044    #[test]
1045    fn test_protocol_smtp_serialization() {
1046        let protocol = Protocol::Smtp;
1047        let json = serde_json::to_string(&protocol).unwrap();
1048        assert_eq!(json, "\"smtp\"");
1049    }
1050
1051    #[test]
1052    fn test_protocol_kafka_serialization() {
1053        let protocol = Protocol::Kafka;
1054        let json = serde_json::to_string(&protocol).unwrap();
1055        assert_eq!(json, "\"kafka\"");
1056    }
1057
1058    #[test]
1059    fn test_protocol_amqp_serialization() {
1060        let protocol = Protocol::Amqp;
1061        let json = serde_json::to_string(&protocol).unwrap();
1062        assert_eq!(json, "\"amqp\"");
1063    }
1064
1065    #[test]
1066    fn test_protocol_ftp_serialization() {
1067        let protocol = Protocol::Ftp;
1068        let json = serde_json::to_string(&protocol).unwrap();
1069        assert_eq!(json, "\"ftp\"");
1070    }
1071
1072    #[test]
1073    fn test_protocol_deserialization() {
1074        let json = "\"http\"";
1075        let protocol: Protocol = serde_json::from_str(json).unwrap();
1076        assert_eq!(protocol, Protocol::Http);
1077    }
1078
1079    #[test]
1080    fn test_protocol_equality() {
1081        assert_eq!(Protocol::Http, Protocol::Http);
1082        assert_ne!(Protocol::Http, Protocol::Grpc);
1083    }
1084
1085    #[test]
1086    fn test_protocol_clone() {
1087        let protocol = Protocol::Websocket;
1088        let cloned = protocol.clone();
1089        assert_eq!(protocol, cloned);
1090    }
1091
1092    // ==================== ValidationMode Tests ====================
1093
1094    #[test]
1095    fn test_validation_mode_off_serialization() {
1096        let mode = ValidationMode::Off;
1097        let json = serde_json::to_string(&mode).unwrap();
1098        assert_eq!(json, "\"off\"");
1099    }
1100
1101    #[test]
1102    fn test_validation_mode_warn_serialization() {
1103        let mode = ValidationMode::Warn;
1104        let json = serde_json::to_string(&mode).unwrap();
1105        assert_eq!(json, "\"warn\"");
1106    }
1107
1108    #[test]
1109    fn test_validation_mode_enforce_serialization() {
1110        let mode = ValidationMode::Enforce;
1111        let json = serde_json::to_string(&mode).unwrap();
1112        assert_eq!(json, "\"enforce\"");
1113    }
1114
1115    #[test]
1116    fn test_validation_mode_deserialization() {
1117        let json = "\"enforce\"";
1118        let mode: ValidationMode = serde_json::from_str(json).unwrap();
1119        assert!(matches!(mode, ValidationMode::Enforce));
1120    }
1121
1122    // ==================== HeaderConfig Tests ====================
1123
1124    #[test]
1125    fn test_header_config_creation() {
1126        let header = HeaderConfig {
1127            name: "Content-Type".to_string(),
1128            value: "application/json".to_string(),
1129        };
1130        assert_eq!(header.name, "Content-Type");
1131        assert_eq!(header.value, "application/json");
1132    }
1133
1134    #[test]
1135    fn test_header_config_serialization() {
1136        let header = HeaderConfig {
1137            name: "X-Custom-Header".to_string(),
1138            value: "custom-value".to_string(),
1139        };
1140        let json = serde_json::to_string(&header).unwrap();
1141        let deserialized: HeaderConfig = serde_json::from_str(&json).unwrap();
1142        assert_eq!(header.name, deserialized.name);
1143        assert_eq!(header.value, deserialized.value);
1144    }
1145
1146    #[test]
1147    fn test_header_config_clone() {
1148        let header = HeaderConfig {
1149            name: "Authorization".to_string(),
1150            value: "Bearer token".to_string(),
1151        };
1152        let cloned = header.clone();
1153        assert_eq!(header.name, cloned.name);
1154        assert_eq!(header.value, cloned.value);
1155    }
1156
1157    // ==================== QueryParamConfig Tests ====================
1158
1159    #[test]
1160    fn test_query_param_config_required() {
1161        let param = QueryParamConfig {
1162            name: "page".to_string(),
1163            required: true,
1164            schema: Some(serde_json::json!({"type": "integer"})),
1165        };
1166        assert!(param.required);
1167        assert!(param.schema.is_some());
1168    }
1169
1170    #[test]
1171    fn test_query_param_config_optional() {
1172        let param = QueryParamConfig {
1173            name: "filter".to_string(),
1174            required: false,
1175            schema: None,
1176        };
1177        assert!(!param.required);
1178        assert!(param.schema.is_none());
1179    }
1180
1181    #[test]
1182    fn test_query_param_config_serialization() {
1183        let param = QueryParamConfig {
1184            name: "limit".to_string(),
1185            required: true,
1186            schema: Some(serde_json::json!({"type": "integer", "maximum": 100})),
1187        };
1188        let json = serde_json::to_string(&param).unwrap();
1189        let deserialized: QueryParamConfig = serde_json::from_str(&json).unwrap();
1190        assert_eq!(param.name, deserialized.name);
1191        assert_eq!(param.required, deserialized.required);
1192    }
1193
1194    // ==================== ValidationConfig Tests ====================
1195
1196    #[test]
1197    fn test_validation_config_with_schema() {
1198        let config = ValidationConfig {
1199            mode: ValidationMode::Enforce,
1200            schema: Some(serde_json::json!({
1201                "type": "object",
1202                "properties": {
1203                    "name": {"type": "string"}
1204                }
1205            })),
1206        };
1207        assert!(matches!(config.mode, ValidationMode::Enforce));
1208        assert!(config.schema.is_some());
1209    }
1210
1211    #[test]
1212    fn test_validation_config_without_schema() {
1213        let config = ValidationConfig {
1214            mode: ValidationMode::Off,
1215            schema: None,
1216        };
1217        assert!(matches!(config.mode, ValidationMode::Off));
1218        assert!(config.schema.is_none());
1219    }
1220
1221    // ==================== LatencyConfig Tests ====================
1222
1223    #[test]
1224    fn test_latency_config_fixed() {
1225        let config = LatencyConfig {
1226            base_ms: 100,
1227            jitter_ms: 20,
1228            distribution: LatencyDistribution::Fixed,
1229        };
1230        assert_eq!(config.base_ms, 100);
1231        assert_eq!(config.jitter_ms, 20);
1232    }
1233
1234    #[test]
1235    fn test_latency_config_normal_distribution() {
1236        let config = LatencyConfig {
1237            base_ms: 50,
1238            jitter_ms: 10,
1239            distribution: LatencyDistribution::Normal { std_dev_ms: 15.0 },
1240        };
1241        assert!(matches!(config.distribution, LatencyDistribution::Normal { .. }));
1242    }
1243
1244    #[test]
1245    fn test_latency_config_pareto_distribution() {
1246        let config = LatencyConfig {
1247            base_ms: 75,
1248            jitter_ms: 25,
1249            distribution: LatencyDistribution::Pareto { shape: 1.5 },
1250        };
1251        assert!(matches!(config.distribution, LatencyDistribution::Pareto { .. }));
1252    }
1253
1254    #[test]
1255    fn test_latency_config_serialization() {
1256        let config = LatencyConfig {
1257            base_ms: 200,
1258            jitter_ms: 50,
1259            distribution: LatencyDistribution::Fixed,
1260        };
1261        let json = serde_json::to_string(&config).unwrap();
1262        let deserialized: LatencyConfig = serde_json::from_str(&json).unwrap();
1263        assert_eq!(config.base_ms, deserialized.base_ms);
1264        assert_eq!(config.jitter_ms, deserialized.jitter_ms);
1265    }
1266
1267    // ==================== FailureConfig Tests ====================
1268
1269    #[test]
1270    fn test_failure_config_creation() {
1271        let config = FailureConfig {
1272            error_rate: 0.1,
1273            status_codes: vec![500, 502, 503],
1274            error_message: Some("Server error".to_string()),
1275        };
1276        assert!((config.error_rate - 0.1).abs() < 0.001);
1277        assert_eq!(config.status_codes.len(), 3);
1278        assert!(config.error_message.is_some());
1279    }
1280
1281    #[test]
1282    fn test_failure_config_high_error_rate() {
1283        let config = FailureConfig {
1284            error_rate: 0.75,
1285            status_codes: vec![500],
1286            error_message: None,
1287        };
1288        assert!(config.error_rate > 0.5);
1289    }
1290
1291    #[test]
1292    fn test_failure_config_serialization() {
1293        let config = FailureConfig {
1294            error_rate: 0.25,
1295            status_codes: vec![429, 500],
1296            error_message: Some("Rate limited".to_string()),
1297        };
1298        let json = serde_json::to_string(&config).unwrap();
1299        let deserialized: FailureConfig = serde_json::from_str(&json).unwrap();
1300        assert_eq!(config.status_codes, deserialized.status_codes);
1301    }
1302
1303    // ==================== TrafficShapingConfig Tests ====================
1304
1305    #[test]
1306    fn test_traffic_shaping_config_bandwidth_limit() {
1307        let config = TrafficShapingConfig {
1308            bandwidth_limit_bps: Some(1024 * 1024), // 1 MB/s
1309            packet_loss_rate: None,
1310        };
1311        assert_eq!(config.bandwidth_limit_bps, Some(1024 * 1024));
1312    }
1313
1314    #[test]
1315    fn test_traffic_shaping_config_packet_loss() {
1316        let config = TrafficShapingConfig {
1317            bandwidth_limit_bps: None,
1318            packet_loss_rate: Some(0.05),
1319        };
1320        assert!(config.packet_loss_rate.is_some());
1321    }
1322
1323    #[test]
1324    fn test_traffic_shaping_config_both_enabled() {
1325        let config = TrafficShapingConfig {
1326            bandwidth_limit_bps: Some(500_000),
1327            packet_loss_rate: Some(0.02),
1328        };
1329        assert!(config.bandwidth_limit_bps.is_some());
1330        assert!(config.packet_loss_rate.is_some());
1331    }
1332
1333    // ==================== EndpointBehavior Tests ====================
1334
1335    #[test]
1336    fn test_endpoint_behavior_with_latency_only() {
1337        let behavior = EndpointBehavior {
1338            latency: Some(LatencyConfig {
1339                base_ms: 100,
1340                jitter_ms: 10,
1341                distribution: LatencyDistribution::Fixed,
1342            }),
1343            failure: None,
1344            traffic_shaping: None,
1345        };
1346        assert!(behavior.latency.is_some());
1347        assert!(behavior.failure.is_none());
1348    }
1349
1350    #[test]
1351    fn test_endpoint_behavior_with_failure_only() {
1352        let behavior = EndpointBehavior {
1353            latency: None,
1354            failure: Some(FailureConfig {
1355                error_rate: 0.1,
1356                status_codes: vec![500],
1357                error_message: None,
1358            }),
1359            traffic_shaping: None,
1360        };
1361        assert!(behavior.failure.is_some());
1362    }
1363
1364    #[test]
1365    fn test_endpoint_behavior_full_config() {
1366        let behavior = EndpointBehavior {
1367            latency: Some(LatencyConfig {
1368                base_ms: 50,
1369                jitter_ms: 10,
1370                distribution: LatencyDistribution::Fixed,
1371            }),
1372            failure: Some(FailureConfig {
1373                error_rate: 0.05,
1374                status_codes: vec![503],
1375                error_message: None,
1376            }),
1377            traffic_shaping: Some(TrafficShapingConfig {
1378                bandwidth_limit_bps: Some(100_000),
1379                packet_loss_rate: Some(0.01),
1380            }),
1381        };
1382        assert!(behavior.latency.is_some());
1383        assert!(behavior.failure.is_some());
1384        assert!(behavior.traffic_shaping.is_some());
1385    }
1386
1387    // ==================== ResponseBody Tests ====================
1388
1389    #[test]
1390    fn test_response_body_static() {
1391        let body = ResponseBody::Static {
1392            content: serde_json::json!({"message": "Hello"}),
1393        };
1394        if let ResponseBody::Static { content } = body {
1395            assert_eq!(content["message"], "Hello");
1396        } else {
1397            panic!("Expected Static response body");
1398        }
1399    }
1400
1401    #[test]
1402    fn test_response_body_template() {
1403        let body = ResponseBody::Template {
1404            template: "Hello, {{name}}!".to_string(),
1405        };
1406        if let ResponseBody::Template { template } = body {
1407            assert!(template.contains("{{name}}"));
1408        } else {
1409            panic!("Expected Template response body");
1410        }
1411    }
1412
1413    #[test]
1414    fn test_response_body_faker() {
1415        let body = ResponseBody::Faker {
1416            schema: serde_json::json!({
1417                "type": "object",
1418                "properties": {
1419                    "id": {"type": "integer"},
1420                    "name": {"type": "string"}
1421                }
1422            }),
1423        };
1424        if let ResponseBody::Faker { schema } = body {
1425            assert_eq!(schema["type"], "object");
1426        } else {
1427            panic!("Expected Faker response body");
1428        }
1429    }
1430
1431    #[test]
1432    fn test_response_body_ai() {
1433        let body = ResponseBody::AI {
1434            prompt: "Generate a user profile".to_string(),
1435        };
1436        if let ResponseBody::AI { prompt } = body {
1437            assert!(prompt.contains("user profile"));
1438        } else {
1439            panic!("Expected AI response body");
1440        }
1441    }
1442
1443    // ==================== HttpRequestConfig Tests ====================
1444
1445    #[test]
1446    fn test_http_request_config_with_validation() {
1447        let config = HttpRequestConfig {
1448            validation: Some(ValidationConfig {
1449                mode: ValidationMode::Enforce,
1450                schema: Some(serde_json::json!({"type": "object"})),
1451            }),
1452            headers: None,
1453            query_params: None,
1454            body_schema: None,
1455        };
1456        assert!(config.validation.is_some());
1457    }
1458
1459    #[test]
1460    fn test_http_request_config_with_headers() {
1461        let config = HttpRequestConfig {
1462            validation: None,
1463            headers: Some(vec![HeaderConfig {
1464                name: "Authorization".to_string(),
1465                value: "Bearer token".to_string(),
1466            }]),
1467            query_params: None,
1468            body_schema: None,
1469        };
1470        assert_eq!(config.headers.as_ref().unwrap().len(), 1);
1471    }
1472
1473    #[test]
1474    fn test_http_request_config_with_query_params() {
1475        let config = HttpRequestConfig {
1476            validation: None,
1477            headers: None,
1478            query_params: Some(vec![
1479                QueryParamConfig {
1480                    name: "page".to_string(),
1481                    required: true,
1482                    schema: None,
1483                },
1484                QueryParamConfig {
1485                    name: "limit".to_string(),
1486                    required: false,
1487                    schema: None,
1488                },
1489            ]),
1490            body_schema: None,
1491        };
1492        assert_eq!(config.query_params.as_ref().unwrap().len(), 2);
1493    }
1494
1495    // ==================== HttpResponseConfig Tests ====================
1496
1497    #[test]
1498    fn test_http_response_config_ok() {
1499        let config = HttpResponseConfig {
1500            status: 200,
1501            headers: None,
1502            body: ResponseBody::Static {
1503                content: serde_json::json!({"success": true}),
1504            },
1505        };
1506        assert_eq!(config.status, 200);
1507    }
1508
1509    #[test]
1510    fn test_http_response_config_not_found() {
1511        let config = HttpResponseConfig {
1512            status: 404,
1513            headers: Some(vec![HeaderConfig {
1514                name: "X-Error-Code".to_string(),
1515                value: "NOT_FOUND".to_string(),
1516            }]),
1517            body: ResponseBody::Static {
1518                content: serde_json::json!({"error": "Not found"}),
1519            },
1520        };
1521        assert_eq!(config.status, 404);
1522        assert!(config.headers.is_some());
1523    }
1524
1525    #[test]
1526    fn test_http_response_config_server_error() {
1527        let config = HttpResponseConfig {
1528            status: 500,
1529            headers: None,
1530            body: ResponseBody::Static {
1531                content: serde_json::json!({"error": "Internal server error"}),
1532            },
1533        };
1534        assert_eq!(config.status, 500);
1535    }
1536
1537    // ==================== HttpEndpointConfig Tests ====================
1538
1539    #[test]
1540    fn test_http_endpoint_config_get() {
1541        let config = HttpEndpointConfig {
1542            method: "GET".to_string(),
1543            path: "/api/users".to_string(),
1544            request: None,
1545            response: HttpResponseConfig {
1546                status: 200,
1547                headers: None,
1548                body: ResponseBody::Static {
1549                    content: serde_json::json!([]),
1550                },
1551            },
1552            behavior: None,
1553        };
1554        assert_eq!(config.method, "GET");
1555        assert!(config.path.starts_with('/'));
1556    }
1557
1558    #[test]
1559    fn test_http_endpoint_config_post_with_request() {
1560        let config = HttpEndpointConfig {
1561            method: "POST".to_string(),
1562            path: "/api/users".to_string(),
1563            request: Some(HttpRequestConfig {
1564                validation: Some(ValidationConfig {
1565                    mode: ValidationMode::Enforce,
1566                    schema: Some(serde_json::json!({
1567                        "type": "object",
1568                        "required": ["name", "email"]
1569                    })),
1570                }),
1571                headers: None,
1572                query_params: None,
1573                body_schema: None,
1574            }),
1575            response: HttpResponseConfig {
1576                status: 201,
1577                headers: None,
1578                body: ResponseBody::Static {
1579                    content: serde_json::json!({"id": 1}),
1580                },
1581            },
1582            behavior: None,
1583        };
1584        assert_eq!(config.method, "POST");
1585        assert!(config.request.is_some());
1586    }
1587
1588    // ==================== GrpcEndpointConfig Tests ====================
1589
1590    #[test]
1591    fn test_grpc_endpoint_config_creation() {
1592        let config = GrpcEndpointConfig {
1593            service: "users.UserService".to_string(),
1594            method: "GetUser".to_string(),
1595            proto_file: "/path/to/user.proto".to_string(),
1596            request_type: "GetUserRequest".to_string(),
1597            response_type: "GetUserResponse".to_string(),
1598            response: GrpcResponseConfig {
1599                body: ResponseBody::Static {
1600                    content: serde_json::json!({"id": 1, "name": "John"}),
1601                },
1602                metadata: None,
1603            },
1604            behavior: None,
1605        };
1606        assert_eq!(config.service, "users.UserService");
1607        assert_eq!(config.method, "GetUser");
1608    }
1609
1610    #[test]
1611    fn test_grpc_endpoint_config_with_metadata() {
1612        let config = GrpcEndpointConfig {
1613            service: "example.ExampleService".to_string(),
1614            method: "DoSomething".to_string(),
1615            proto_file: "/path/to/example.proto".to_string(),
1616            request_type: "Request".to_string(),
1617            response_type: "Response".to_string(),
1618            response: GrpcResponseConfig {
1619                body: ResponseBody::Static {
1620                    content: serde_json::json!({}),
1621                },
1622                metadata: Some(vec![HeaderConfig {
1623                    name: "x-request-id".to_string(),
1624                    value: "12345".to_string(),
1625                }]),
1626            },
1627            behavior: None,
1628        };
1629        assert!(config.response.metadata.is_some());
1630    }
1631
1632    // ==================== WebsocketEndpointConfig Tests ====================
1633
1634    #[test]
1635    fn test_websocket_endpoint_config_basic() {
1636        let config = WebsocketEndpointConfig {
1637            path: "/ws".to_string(),
1638            on_connect: None,
1639            on_message: Some(WebsocketAction::Echo),
1640            on_disconnect: None,
1641            behavior: None,
1642        };
1643        assert_eq!(config.path, "/ws");
1644    }
1645
1646    #[test]
1647    fn test_websocket_endpoint_config_with_send() {
1648        let config = WebsocketEndpointConfig {
1649            path: "/notifications".to_string(),
1650            on_connect: Some(WebsocketAction::Send {
1651                message: ResponseBody::Static {
1652                    content: serde_json::json!({"type": "connected"}),
1653                },
1654            }),
1655            on_message: None,
1656            on_disconnect: None,
1657            behavior: None,
1658        };
1659        assert!(config.on_connect.is_some());
1660    }
1661
1662    #[test]
1663    fn test_websocket_endpoint_config_with_broadcast() {
1664        let config = WebsocketEndpointConfig {
1665            path: "/chat".to_string(),
1666            on_connect: None,
1667            on_message: Some(WebsocketAction::Broadcast {
1668                message: ResponseBody::Template {
1669                    template: "{{message}}".to_string(),
1670                },
1671            }),
1672            on_disconnect: None,
1673            behavior: None,
1674        };
1675        if let Some(WebsocketAction::Broadcast { .. }) = config.on_message {
1676            // Test passes
1677        } else {
1678            panic!("Expected Broadcast action");
1679        }
1680    }
1681
1682    #[test]
1683    fn test_websocket_endpoint_config_with_close() {
1684        let config = WebsocketEndpointConfig {
1685            path: "/stream".to_string(),
1686            on_connect: None,
1687            on_message: None,
1688            on_disconnect: Some(WebsocketAction::Close {
1689                code: 1000,
1690                reason: "Normal closure".to_string(),
1691            }),
1692            behavior: None,
1693        };
1694        if let Some(WebsocketAction::Close { code, reason }) = config.on_disconnect {
1695            assert_eq!(code, 1000);
1696            assert_eq!(reason, "Normal closure");
1697        } else {
1698            panic!("Expected Close action");
1699        }
1700    }
1701
1702    // ==================== ValidationResult Tests ====================
1703
1704    #[test]
1705    fn test_validation_result_valid() {
1706        let result = ValidationResult {
1707            valid: true,
1708            errors: vec![],
1709            warnings: vec![],
1710        };
1711        assert!(result.valid);
1712        assert!(result.errors.is_empty());
1713    }
1714
1715    #[test]
1716    fn test_validation_result_with_errors() {
1717        let result = ValidationResult {
1718            valid: false,
1719            errors: vec![ValidationError {
1720                field: "method".to_string(),
1721                message: "Invalid HTTP method".to_string(),
1722            }],
1723            warnings: vec![],
1724        };
1725        assert!(!result.valid);
1726        assert_eq!(result.errors.len(), 1);
1727    }
1728
1729    #[test]
1730    fn test_validation_result_with_warnings() {
1731        let result = ValidationResult {
1732            valid: true,
1733            errors: vec![],
1734            warnings: vec!["High error rate configured".to_string()],
1735        };
1736        assert!(result.valid);
1737        assert_eq!(result.warnings.len(), 1);
1738    }
1739
1740    // ==================== ValidationError Tests ====================
1741
1742    #[test]
1743    fn test_validation_error_creation() {
1744        let error = ValidationError {
1745            field: "path".to_string(),
1746            message: "Path must start with /".to_string(),
1747        };
1748        assert_eq!(error.field, "path");
1749        assert!(error.message.contains('/'));
1750    }
1751
1752    #[test]
1753    fn test_validation_error_clone() {
1754        let error = ValidationError {
1755            field: "status".to_string(),
1756            message: "Invalid status code".to_string(),
1757        };
1758        let cloned = error.clone();
1759        assert_eq!(error.field, cloned.field);
1760        assert_eq!(error.message, cloned.message);
1761    }
1762
1763    // ==================== UIBuilderState Tests ====================
1764
1765    #[test]
1766    fn test_ui_builder_state_creation() {
1767        let config = ServerConfig::default();
1768        let state = UIBuilderState::new(config);
1769        // State should be created without panic
1770        let _ = state;
1771    }
1772
1773    #[test]
1774    fn test_ui_builder_state_clone() {
1775        let config = ServerConfig::default();
1776        let state = UIBuilderState::new(config);
1777        let cloned = state.clone();
1778        // Both should exist without panic
1779        let _ = (state, cloned);
1780    }
1781
1782    // ==================== EndpointConfig Tests ====================
1783
1784    #[test]
1785    fn test_endpoint_serialization() {
1786        let endpoint = EndpointConfig {
1787            id: "test-1".to_string(),
1788            protocol: Protocol::Http,
1789            name: "Test Endpoint".to_string(),
1790            description: Some("A test endpoint".to_string()),
1791            enabled: true,
1792            config: EndpointProtocolConfig::Http(HttpEndpointConfig {
1793                method: "GET".to_string(),
1794                path: "/test".to_string(),
1795                request: None,
1796                response: HttpResponseConfig {
1797                    status: 200,
1798                    headers: None,
1799                    body: ResponseBody::Static {
1800                        content: serde_json::json!({"message": "Hello"}),
1801                    },
1802                },
1803                behavior: None,
1804            }),
1805        };
1806
1807        let json = serde_json::to_string(&endpoint).unwrap();
1808        let deserialized: EndpointConfig = serde_json::from_str(&json).unwrap();
1809
1810        assert_eq!(endpoint.id, deserialized.id);
1811        assert_eq!(endpoint.protocol, deserialized.protocol);
1812    }
1813
1814    #[test]
1815    fn test_endpoint_config_disabled() {
1816        let endpoint = EndpointConfig {
1817            id: "disabled-1".to_string(),
1818            protocol: Protocol::Http,
1819            name: "Disabled Endpoint".to_string(),
1820            description: None,
1821            enabled: false,
1822            config: EndpointProtocolConfig::Http(HttpEndpointConfig {
1823                method: "GET".to_string(),
1824                path: "/disabled".to_string(),
1825                request: None,
1826                response: HttpResponseConfig {
1827                    status: 200,
1828                    headers: None,
1829                    body: ResponseBody::Static {
1830                        content: serde_json::json!({}),
1831                    },
1832                },
1833                behavior: None,
1834            }),
1835        };
1836        assert!(!endpoint.enabled);
1837        assert!(endpoint.description.is_none());
1838    }
1839
1840    #[test]
1841    fn test_endpoint_config_grpc() {
1842        let endpoint = EndpointConfig {
1843            id: "grpc-1".to_string(),
1844            protocol: Protocol::Grpc,
1845            name: "gRPC Endpoint".to_string(),
1846            description: Some("A gRPC endpoint".to_string()),
1847            enabled: true,
1848            config: EndpointProtocolConfig::Grpc(GrpcEndpointConfig {
1849                service: "test.Service".to_string(),
1850                method: "Call".to_string(),
1851                proto_file: "/test.proto".to_string(),
1852                request_type: "Request".to_string(),
1853                response_type: "Response".to_string(),
1854                response: GrpcResponseConfig {
1855                    body: ResponseBody::Static {
1856                        content: serde_json::json!({}),
1857                    },
1858                    metadata: None,
1859                },
1860                behavior: None,
1861            }),
1862        };
1863        assert_eq!(endpoint.protocol, Protocol::Grpc);
1864    }
1865
1866    #[test]
1867    fn test_endpoint_config_websocket() {
1868        let endpoint = EndpointConfig {
1869            id: "ws-1".to_string(),
1870            protocol: Protocol::Websocket,
1871            name: "WebSocket Endpoint".to_string(),
1872            description: None,
1873            enabled: true,
1874            config: EndpointProtocolConfig::Websocket(WebsocketEndpointConfig {
1875                path: "/ws".to_string(),
1876                on_connect: None,
1877                on_message: Some(WebsocketAction::Echo),
1878                on_disconnect: None,
1879                behavior: None,
1880            }),
1881        };
1882        assert_eq!(endpoint.protocol, Protocol::Websocket);
1883    }
1884
1885    #[test]
1886    fn test_validation() {
1887        // Test invalid HTTP method
1888        let endpoint = EndpointConfig {
1889            id: "test-1".to_string(),
1890            protocol: Protocol::Http,
1891            name: "Test".to_string(),
1892            description: None,
1893            enabled: true,
1894            config: EndpointProtocolConfig::Http(HttpEndpointConfig {
1895                method: "INVALID".to_string(),
1896                path: "/test".to_string(),
1897                request: None,
1898                response: HttpResponseConfig {
1899                    status: 200,
1900                    headers: None,
1901                    body: ResponseBody::Static {
1902                        content: serde_json::json!({}),
1903                    },
1904                },
1905                behavior: None,
1906            }),
1907        };
1908
1909        // Validation would catch this in the async function
1910        assert_eq!(endpoint.protocol, Protocol::Http);
1911    }
1912
1913    // ==================== ConfigFormat Tests ====================
1914
1915    #[test]
1916    fn test_config_format_yaml_deserialization() {
1917        let json = r#"{"config": "test", "format": "yaml"}"#;
1918        let request: ImportRequest = serde_json::from_str(json).unwrap();
1919        assert!(matches!(request.format, ConfigFormat::Yaml));
1920    }
1921
1922    #[test]
1923    fn test_config_format_json_deserialization() {
1924        let json = r#"{"config": "test", "format": "json"}"#;
1925        let request: ImportRequest = serde_json::from_str(json).unwrap();
1926        assert!(matches!(request.format, ConfigFormat::Json));
1927    }
1928
1929    // ==================== Async Handler Tests ====================
1930
1931    #[tokio::test]
1932    async fn test_list_endpoints_empty() {
1933        let config = ServerConfig::default();
1934        let state = UIBuilderState::new(config);
1935        let result = list_endpoints(State(state)).await;
1936        let response = result.0;
1937        assert_eq!(response["total"], 0);
1938        assert_eq!(response["enabled"], 0);
1939    }
1940
1941    #[tokio::test]
1942    async fn test_create_and_get_endpoint() {
1943        let config = ServerConfig::default();
1944        let state = UIBuilderState::new(config);
1945
1946        let endpoint = create_test_http_endpoint();
1947        let create_result = create_endpoint(State(state.clone()), Json(endpoint.clone())).await;
1948        assert!(create_result.is_ok());
1949
1950        let get_result = get_endpoint(State(state), Path("test-1".to_string())).await;
1951        assert!(get_result.is_ok());
1952        assert_eq!(get_result.unwrap().0.id, "test-1");
1953    }
1954
1955    #[tokio::test]
1956    async fn test_get_endpoint_not_found() {
1957        let config = ServerConfig::default();
1958        let state = UIBuilderState::new(config);
1959
1960        let result = get_endpoint(State(state), Path("nonexistent".to_string())).await;
1961        assert!(result.is_err());
1962        assert_eq!(result.err().unwrap(), StatusCode::NOT_FOUND);
1963    }
1964
1965    #[tokio::test]
1966    async fn test_create_endpoint_duplicate_id() {
1967        let config = ServerConfig::default();
1968        let state = UIBuilderState::new(config);
1969
1970        let endpoint = create_test_http_endpoint();
1971        let _ = create_endpoint(State(state.clone()), Json(endpoint.clone())).await;
1972
1973        // Try to create with same ID
1974        let result = create_endpoint(State(state), Json(endpoint)).await;
1975        assert!(result.is_err());
1976        assert_eq!(result.err().unwrap(), StatusCode::CONFLICT);
1977    }
1978
1979    #[tokio::test]
1980    async fn test_create_endpoint_auto_generate_id() {
1981        let config = ServerConfig::default();
1982        let state = UIBuilderState::new(config);
1983
1984        let mut endpoint = create_test_http_endpoint();
1985        endpoint.id = String::new(); // Empty ID should be auto-generated
1986
1987        let result = create_endpoint(State(state), Json(endpoint)).await;
1988        assert!(result.is_ok());
1989        let created = result.unwrap().0;
1990        assert!(!created.id.is_empty());
1991    }
1992
1993    #[tokio::test]
1994    async fn test_update_endpoint() {
1995        let config = ServerConfig::default();
1996        let state = UIBuilderState::new(config);
1997
1998        let endpoint = create_test_http_endpoint();
1999        let _ = create_endpoint(State(state.clone()), Json(endpoint.clone())).await;
2000
2001        let mut updated = endpoint.clone();
2002        updated.name = "Updated Name".to_string();
2003
2004        let result = update_endpoint(State(state), Path("test-1".to_string()), Json(updated)).await;
2005        assert!(result.is_ok());
2006        assert_eq!(result.unwrap().0.name, "Updated Name");
2007    }
2008
2009    #[tokio::test]
2010    async fn test_update_endpoint_not_found() {
2011        let config = ServerConfig::default();
2012        let state = UIBuilderState::new(config);
2013
2014        let endpoint = create_test_http_endpoint();
2015        let result =
2016            update_endpoint(State(state), Path("nonexistent".to_string()), Json(endpoint)).await;
2017        assert!(result.is_err());
2018        assert_eq!(result.err().unwrap(), StatusCode::NOT_FOUND);
2019    }
2020
2021    #[tokio::test]
2022    async fn test_delete_endpoint() {
2023        let config = ServerConfig::default();
2024        let state = UIBuilderState::new(config);
2025
2026        let endpoint = create_test_http_endpoint();
2027        let _ = create_endpoint(State(state.clone()), Json(endpoint)).await;
2028
2029        let result = delete_endpoint(State(state), Path("test-1".to_string())).await;
2030        assert!(result.is_ok());
2031        assert_eq!(result.unwrap(), StatusCode::NO_CONTENT);
2032    }
2033
2034    #[tokio::test]
2035    async fn test_delete_endpoint_not_found() {
2036        let config = ServerConfig::default();
2037        let state = UIBuilderState::new(config);
2038
2039        let result = delete_endpoint(State(state), Path("nonexistent".to_string())).await;
2040        assert!(result.is_err());
2041        assert_eq!(result.err().unwrap(), StatusCode::NOT_FOUND);
2042    }
2043
2044    #[tokio::test]
2045    async fn test_validate_endpoint_valid_http() {
2046        let config = ServerConfig::default();
2047        let state = UIBuilderState::new(config);
2048
2049        let endpoint = create_test_http_endpoint();
2050        let result = validate_endpoint(State(state), Json(endpoint)).await;
2051        assert!(result.0.valid);
2052        assert!(result.0.errors.is_empty());
2053    }
2054
2055    #[tokio::test]
2056    async fn test_validate_endpoint_invalid_method() {
2057        let config = ServerConfig::default();
2058        let state = UIBuilderState::new(config);
2059
2060        let mut endpoint = create_test_http_endpoint();
2061        if let EndpointProtocolConfig::Http(ref mut http_config) = endpoint.config {
2062            http_config.method = "INVALID".to_string();
2063        }
2064
2065        let result = validate_endpoint(State(state), Json(endpoint)).await;
2066        assert!(!result.0.valid);
2067        assert!(!result.0.errors.is_empty());
2068    }
2069
2070    #[tokio::test]
2071    async fn test_validate_endpoint_invalid_path() {
2072        let config = ServerConfig::default();
2073        let state = UIBuilderState::new(config);
2074
2075        let mut endpoint = create_test_http_endpoint();
2076        if let EndpointProtocolConfig::Http(ref mut http_config) = endpoint.config {
2077            http_config.path = "no-leading-slash".to_string();
2078        }
2079
2080        let result = validate_endpoint(State(state), Json(endpoint)).await;
2081        assert!(!result.0.valid);
2082    }
2083
2084    #[tokio::test]
2085    async fn test_validate_endpoint_invalid_status() {
2086        let config = ServerConfig::default();
2087        let state = UIBuilderState::new(config);
2088
2089        let mut endpoint = create_test_http_endpoint();
2090        if let EndpointProtocolConfig::Http(ref mut http_config) = endpoint.config {
2091            http_config.response.status = 999; // Invalid status code
2092        }
2093
2094        let result = validate_endpoint(State(state), Json(endpoint)).await;
2095        assert!(!result.0.valid);
2096    }
2097
2098    #[tokio::test]
2099    async fn test_validate_endpoint_high_error_rate_warning() {
2100        let config = ServerConfig::default();
2101        let state = UIBuilderState::new(config);
2102
2103        let mut endpoint = create_test_http_endpoint();
2104        if let EndpointProtocolConfig::Http(ref mut http_config) = endpoint.config {
2105            http_config.behavior = Some(EndpointBehavior {
2106                latency: None,
2107                failure: Some(FailureConfig {
2108                    error_rate: 0.75,
2109                    status_codes: vec![500],
2110                    error_message: None,
2111                }),
2112                traffic_shaping: None,
2113            });
2114        }
2115
2116        let result = validate_endpoint(State(state), Json(endpoint)).await;
2117        assert!(result.0.valid); // Still valid, just has warnings
2118        assert!(!result.0.warnings.is_empty());
2119    }
2120
2121    #[tokio::test]
2122    async fn test_validate_grpc_endpoint_empty_service() {
2123        let config = ServerConfig::default();
2124        let state = UIBuilderState::new(config);
2125
2126        let endpoint = EndpointConfig {
2127            id: "grpc-test".to_string(),
2128            protocol: Protocol::Grpc,
2129            name: "gRPC Test".to_string(),
2130            description: None,
2131            enabled: true,
2132            config: EndpointProtocolConfig::Grpc(GrpcEndpointConfig {
2133                service: String::new(), // Empty service name
2134                method: "Method".to_string(),
2135                proto_file: "/test.proto".to_string(),
2136                request_type: "Request".to_string(),
2137                response_type: "Response".to_string(),
2138                response: GrpcResponseConfig {
2139                    body: ResponseBody::Static {
2140                        content: serde_json::json!({}),
2141                    },
2142                    metadata: None,
2143                },
2144                behavior: None,
2145            }),
2146        };
2147
2148        let result = validate_endpoint(State(state), Json(endpoint)).await;
2149        assert!(!result.0.valid);
2150    }
2151
2152    #[tokio::test]
2153    async fn test_validate_websocket_endpoint_invalid_path() {
2154        let config = ServerConfig::default();
2155        let state = UIBuilderState::new(config);
2156
2157        let endpoint = EndpointConfig {
2158            id: "ws-test".to_string(),
2159            protocol: Protocol::Websocket,
2160            name: "WebSocket Test".to_string(),
2161            description: None,
2162            enabled: true,
2163            config: EndpointProtocolConfig::Websocket(WebsocketEndpointConfig {
2164                path: "no-slash".to_string(), // Invalid path
2165                on_connect: None,
2166                on_message: None,
2167                on_disconnect: None,
2168                behavior: None,
2169            }),
2170        };
2171
2172        let result = validate_endpoint(State(state), Json(endpoint)).await;
2173        assert!(!result.0.valid);
2174    }
2175
2176    #[tokio::test]
2177    async fn test_get_config() {
2178        let config = ServerConfig::default();
2179        let state = UIBuilderState::new(config);
2180
2181        let result = get_config(State(state)).await;
2182        // Should return the default config
2183        let _ = result.0;
2184    }
2185
2186    #[tokio::test]
2187    async fn test_update_config() {
2188        let config = ServerConfig::default();
2189        let state = UIBuilderState::new(config.clone());
2190
2191        let result = update_config(State(state), Json(config)).await;
2192        assert!(result.is_ok());
2193    }
2194
2195    #[tokio::test]
2196    async fn test_export_config() {
2197        let config = ServerConfig::default();
2198        let state = UIBuilderState::new(config);
2199
2200        let result = export_config(State(state)).await;
2201        assert!(result.is_ok());
2202    }
2203
2204    #[tokio::test]
2205    async fn test_list_endpoints_with_multiple_protocols() {
2206        let config = ServerConfig::default();
2207        let state = UIBuilderState::new(config);
2208
2209        // Add HTTP endpoint
2210        let http_endpoint = create_test_http_endpoint();
2211        let _ = create_endpoint(State(state.clone()), Json(http_endpoint)).await;
2212
2213        // Add WebSocket endpoint
2214        let ws_endpoint = EndpointConfig {
2215            id: "ws-1".to_string(),
2216            protocol: Protocol::Websocket,
2217            name: "WS Endpoint".to_string(),
2218            description: None,
2219            enabled: true,
2220            config: EndpointProtocolConfig::Websocket(WebsocketEndpointConfig {
2221                path: "/ws".to_string(),
2222                on_connect: None,
2223                on_message: Some(WebsocketAction::Echo),
2224                on_disconnect: None,
2225                behavior: None,
2226            }),
2227        };
2228        let _ = create_endpoint(State(state.clone()), Json(ws_endpoint)).await;
2229
2230        let result = list_endpoints(State(state)).await;
2231        let response = result.0;
2232        assert_eq!(response["total"], 2);
2233        assert_eq!(response["by_protocol"]["http"], 1);
2234        assert_eq!(response["by_protocol"]["websocket"], 1);
2235    }
2236
2237    // ==================== Router Tests ====================
2238
2239    #[test]
2240    fn test_create_ui_builder_router() {
2241        let config = ServerConfig::default();
2242        let state = UIBuilderState::new(config);
2243        let router = create_ui_builder_router(state);
2244        // Router should be created without panic
2245        let _ = router;
2246    }
2247}