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    let paths = spec["paths"].as_object_mut().unwrap();
740
741    // Group endpoints by path
742    for endpoint in endpoints.iter() {
743        if endpoint.protocol != Protocol::Http {
744            continue; // Only export HTTP endpoints for now
745        }
746
747        if let EndpointProtocolConfig::Http(http_config) = &endpoint.config {
748            let path = &http_config.path;
749
750            if !paths.contains_key(path) {
751                paths.insert(path.clone(), serde_json::json!({}));
752            }
753
754            let method = http_config.method.to_lowercase();
755
756            // Build response body
757            let response_body_content = match &http_config.response.body {
758                ResponseBody::Static { content } => content.clone(),
759                ResponseBody::Template { template } => serde_json::json!({
760                    "type": "string",
761                    "example": template
762                }),
763                ResponseBody::Faker { schema } => schema.clone(),
764                ResponseBody::AI { prompt } => serde_json::json!({
765                    "type": "string",
766                    "description": prompt
767                }),
768            };
769
770            let operation = serde_json::json!({
771                "summary": &endpoint.name,
772                "description": endpoint.description.as_ref().unwrap_or(&String::new()),
773                "operationId": &endpoint.id,
774                "responses": {
775                    http_config.response.status.to_string(): {
776                        "description": format!("Response with status {}", http_config.response.status),
777                        "content": {
778                            "application/json": {
779                                "schema": {
780                                    "type": "object"
781                                },
782                                "example": response_body_content
783                            }
784                        }
785                    }
786                }
787            });
788
789            paths[path][&method] = operation;
790        }
791    }
792
793    match serde_json::to_string_pretty(&spec) {
794        Ok(json) => Ok((StatusCode::OK, [("Content-Type", "application/json")], json)),
795        Err(e) => {
796            error!(error = %e, "Failed to serialize OpenAPI spec to JSON");
797            Err(StatusCode::INTERNAL_SERVER_ERROR)
798        }
799    }
800}
801
802/// Import AsyncAPI specification request
803#[derive(Debug, Deserialize)]
804struct ImportAsyncApiRequest {
805    content: String,
806    base_url: Option<String>,
807    auto_enable: Option<bool>,
808}
809
810/// Import AsyncAPI specification response
811#[derive(Debug, Serialize)]
812struct ImportAsyncApiResponse {
813    success: bool,
814    endpoints_created: usize,
815    warnings: Vec<String>,
816    spec_info: AsyncApiSpecInfoResponse,
817}
818
819#[derive(Debug, Serialize)]
820struct AsyncApiSpecInfoResponse {
821    title: String,
822    version: String,
823    description: Option<String>,
824    asyncapi_version: String,
825    servers: Vec<String>,
826}
827
828/// Import AsyncAPI specification and auto-generate WebSocket/MQTT/Kafka endpoints
829async fn import_asyncapi_spec_handler(
830    State(state): State<UIBuilderState>,
831    Json(request): Json<ImportAsyncApiRequest>,
832) -> Result<Json<ImportAsyncApiResponse>, (StatusCode, Json<serde_json::Value>)> {
833    info!("Importing AsyncAPI specification");
834
835    // Import the AsyncAPI spec
836    let import_result = import_asyncapi_spec(&request.content, request.base_url.as_deref())
837        .map_err(|e| {
838            error!(error = %e, "Failed to import AsyncAPI spec");
839            (
840                StatusCode::BAD_REQUEST,
841                Json(serde_json::json!({
842                    "error": "Failed to import AsyncAPI specification",
843                    "details": e
844                })),
845            )
846        })?;
847
848    let auto_enable = request.auto_enable.unwrap_or(true);
849    let mut endpoints = state.endpoints.write().await;
850    let mut created_count = 0;
851
852    // Convert imported channels to EndpointConfig
853    for channel in import_result.channels {
854        let endpoint_id = uuid::Uuid::new_v4().to_string();
855
856        // Map AsyncAPI protocol to MockForge protocol
857        let (protocol, config) = match channel.protocol {
858            mockforge_core::import::asyncapi_import::ChannelProtocol::Websocket => {
859                // Create WebSocket endpoint
860                let on_message = if let Some(op) = channel.operations.first() {
861                    if let Some(example) = &op.example_message {
862                        Some(WebsocketAction::Send {
863                            message: ResponseBody::Static {
864                                content: example.clone(),
865                            },
866                        })
867                    } else {
868                        Some(WebsocketAction::Echo)
869                    }
870                } else {
871                    Some(WebsocketAction::Echo)
872                };
873
874                (
875                    Protocol::Websocket,
876                    EndpointProtocolConfig::Websocket(WebsocketEndpointConfig {
877                        path: channel.path.clone(),
878                        on_connect: None,
879                        on_message,
880                        on_disconnect: None,
881                        behavior: None,
882                    }),
883                )
884            }
885            _ => {
886                // For MQTT/Kafka/AMQP, we'll skip for now as they need special handling
887                // Log a warning and continue
888                warn!(
889                    "Skipping channel '{}' with protocol {:?} - not yet supported for UI Builder",
890                    channel.name, channel.protocol
891                );
892                continue;
893            }
894        };
895
896        let endpoint = EndpointConfig {
897            id: endpoint_id.clone(),
898            protocol,
899            name: format!("{} - {}", channel.name, channel.path),
900            description: channel.description.or_else(|| {
901                Some(format!(
902                    "Auto-generated from AsyncAPI spec: {} v{}",
903                    import_result.spec_info.title, import_result.spec_info.version
904                ))
905            }),
906            enabled: auto_enable,
907            config,
908        };
909
910        info!(
911            endpoint_id = %endpoint_id,
912            name = %endpoint.name,
913            "Created endpoint from AsyncAPI spec"
914        );
915
916        endpoints.push(endpoint);
917        created_count += 1;
918    }
919
920    Ok(Json(ImportAsyncApiResponse {
921        success: true,
922        endpoints_created: created_count,
923        warnings: import_result.warnings,
924        spec_info: AsyncApiSpecInfoResponse {
925            title: import_result.spec_info.title,
926            version: import_result.spec_info.version,
927            description: import_result.spec_info.description,
928            asyncapi_version: import_result.spec_info.asyncapi_version,
929            servers: import_result.spec_info.servers,
930        },
931    }))
932}
933
934/// Resolve tokens in a ResponseBody
935pub async fn resolve_response_body_tokens(
936    body: &ResponseBody,
937) -> Result<serde_json::Value, String> {
938    use crate::token_response::resolve_response_tokens;
939
940    match body {
941        ResponseBody::Static { content } => resolve_response_tokens(content.clone()).await,
942        ResponseBody::Template { template } => {
943            // Templates can also contain tokens, parse and resolve
944            let value = serde_json::Value::String(template.clone());
945            resolve_response_tokens(value).await
946        }
947        ResponseBody::Faker { schema } => {
948            // Faker schemas may contain tokens
949            resolve_response_tokens(schema.clone()).await
950        }
951        ResponseBody::AI { prompt: _ } => {
952            // AI prompts are handled separately by the AI handler
953            // Return as-is since AI generation happens at response time
954            Ok(serde_json::json!({"_ai_prompt": true}))
955        }
956    }
957}
958
959/// Create the UI Builder router
960pub fn create_ui_builder_router(state: UIBuilderState) -> Router {
961    Router::new()
962        .route("/endpoints", get(list_endpoints).post(create_endpoint))
963        .route(
964            "/endpoints/{id}",
965            get(get_endpoint).put(update_endpoint).delete(delete_endpoint),
966        )
967        .route("/endpoints/validate", post(validate_endpoint))
968        .route("/config", get(get_config).put(update_config))
969        .route("/config/export", get(export_config))
970        .route("/config/import", post(import_config))
971        .route("/openapi/import", post(import_openapi_spec_handler))
972        .route("/openapi/export", get(export_openapi_spec_handler))
973        .route("/asyncapi/import", post(import_asyncapi_spec_handler))
974        .with_state(state)
975}
976
977#[cfg(test)]
978mod tests {
979    use super::*;
980
981    // ==================== Helper Functions ====================
982
983    fn create_test_http_endpoint() -> EndpointConfig {
984        EndpointConfig {
985            id: "test-1".to_string(),
986            protocol: Protocol::Http,
987            name: "Test Endpoint".to_string(),
988            description: Some("A test endpoint".to_string()),
989            enabled: true,
990            config: EndpointProtocolConfig::Http(HttpEndpointConfig {
991                method: "GET".to_string(),
992                path: "/test".to_string(),
993                request: None,
994                response: HttpResponseConfig {
995                    status: 200,
996                    headers: None,
997                    body: ResponseBody::Static {
998                        content: serde_json::json!({"message": "Hello"}),
999                    },
1000                },
1001                behavior: None,
1002            }),
1003        }
1004    }
1005
1006    // ==================== Protocol Tests ====================
1007
1008    #[test]
1009    fn test_protocol_http_serialization() {
1010        let protocol = Protocol::Http;
1011        let json = serde_json::to_string(&protocol).unwrap();
1012        assert_eq!(json, "\"http\"");
1013    }
1014
1015    #[test]
1016    fn test_protocol_grpc_serialization() {
1017        let protocol = Protocol::Grpc;
1018        let json = serde_json::to_string(&protocol).unwrap();
1019        assert_eq!(json, "\"grpc\"");
1020    }
1021
1022    #[test]
1023    fn test_protocol_websocket_serialization() {
1024        let protocol = Protocol::Websocket;
1025        let json = serde_json::to_string(&protocol).unwrap();
1026        assert_eq!(json, "\"websocket\"");
1027    }
1028
1029    #[test]
1030    fn test_protocol_graphql_serialization() {
1031        let protocol = Protocol::Graphql;
1032        let json = serde_json::to_string(&protocol).unwrap();
1033        assert_eq!(json, "\"graphql\"");
1034    }
1035
1036    #[test]
1037    fn test_protocol_mqtt_serialization() {
1038        let protocol = Protocol::Mqtt;
1039        let json = serde_json::to_string(&protocol).unwrap();
1040        assert_eq!(json, "\"mqtt\"");
1041    }
1042
1043    #[test]
1044    fn test_protocol_smtp_serialization() {
1045        let protocol = Protocol::Smtp;
1046        let json = serde_json::to_string(&protocol).unwrap();
1047        assert_eq!(json, "\"smtp\"");
1048    }
1049
1050    #[test]
1051    fn test_protocol_kafka_serialization() {
1052        let protocol = Protocol::Kafka;
1053        let json = serde_json::to_string(&protocol).unwrap();
1054        assert_eq!(json, "\"kafka\"");
1055    }
1056
1057    #[test]
1058    fn test_protocol_amqp_serialization() {
1059        let protocol = Protocol::Amqp;
1060        let json = serde_json::to_string(&protocol).unwrap();
1061        assert_eq!(json, "\"amqp\"");
1062    }
1063
1064    #[test]
1065    fn test_protocol_ftp_serialization() {
1066        let protocol = Protocol::Ftp;
1067        let json = serde_json::to_string(&protocol).unwrap();
1068        assert_eq!(json, "\"ftp\"");
1069    }
1070
1071    #[test]
1072    fn test_protocol_deserialization() {
1073        let json = "\"http\"";
1074        let protocol: Protocol = serde_json::from_str(json).unwrap();
1075        assert_eq!(protocol, Protocol::Http);
1076    }
1077
1078    #[test]
1079    fn test_protocol_equality() {
1080        assert_eq!(Protocol::Http, Protocol::Http);
1081        assert_ne!(Protocol::Http, Protocol::Grpc);
1082    }
1083
1084    #[test]
1085    fn test_protocol_clone() {
1086        let protocol = Protocol::Websocket;
1087        let cloned = protocol.clone();
1088        assert_eq!(protocol, cloned);
1089    }
1090
1091    // ==================== ValidationMode Tests ====================
1092
1093    #[test]
1094    fn test_validation_mode_off_serialization() {
1095        let mode = ValidationMode::Off;
1096        let json = serde_json::to_string(&mode).unwrap();
1097        assert_eq!(json, "\"off\"");
1098    }
1099
1100    #[test]
1101    fn test_validation_mode_warn_serialization() {
1102        let mode = ValidationMode::Warn;
1103        let json = serde_json::to_string(&mode).unwrap();
1104        assert_eq!(json, "\"warn\"");
1105    }
1106
1107    #[test]
1108    fn test_validation_mode_enforce_serialization() {
1109        let mode = ValidationMode::Enforce;
1110        let json = serde_json::to_string(&mode).unwrap();
1111        assert_eq!(json, "\"enforce\"");
1112    }
1113
1114    #[test]
1115    fn test_validation_mode_deserialization() {
1116        let json = "\"enforce\"";
1117        let mode: ValidationMode = serde_json::from_str(json).unwrap();
1118        assert!(matches!(mode, ValidationMode::Enforce));
1119    }
1120
1121    // ==================== HeaderConfig Tests ====================
1122
1123    #[test]
1124    fn test_header_config_creation() {
1125        let header = HeaderConfig {
1126            name: "Content-Type".to_string(),
1127            value: "application/json".to_string(),
1128        };
1129        assert_eq!(header.name, "Content-Type");
1130        assert_eq!(header.value, "application/json");
1131    }
1132
1133    #[test]
1134    fn test_header_config_serialization() {
1135        let header = HeaderConfig {
1136            name: "X-Custom-Header".to_string(),
1137            value: "custom-value".to_string(),
1138        };
1139        let json = serde_json::to_string(&header).unwrap();
1140        let deserialized: HeaderConfig = serde_json::from_str(&json).unwrap();
1141        assert_eq!(header.name, deserialized.name);
1142        assert_eq!(header.value, deserialized.value);
1143    }
1144
1145    #[test]
1146    fn test_header_config_clone() {
1147        let header = HeaderConfig {
1148            name: "Authorization".to_string(),
1149            value: "Bearer token".to_string(),
1150        };
1151        let cloned = header.clone();
1152        assert_eq!(header.name, cloned.name);
1153        assert_eq!(header.value, cloned.value);
1154    }
1155
1156    // ==================== QueryParamConfig Tests ====================
1157
1158    #[test]
1159    fn test_query_param_config_required() {
1160        let param = QueryParamConfig {
1161            name: "page".to_string(),
1162            required: true,
1163            schema: Some(serde_json::json!({"type": "integer"})),
1164        };
1165        assert!(param.required);
1166        assert!(param.schema.is_some());
1167    }
1168
1169    #[test]
1170    fn test_query_param_config_optional() {
1171        let param = QueryParamConfig {
1172            name: "filter".to_string(),
1173            required: false,
1174            schema: None,
1175        };
1176        assert!(!param.required);
1177        assert!(param.schema.is_none());
1178    }
1179
1180    #[test]
1181    fn test_query_param_config_serialization() {
1182        let param = QueryParamConfig {
1183            name: "limit".to_string(),
1184            required: true,
1185            schema: Some(serde_json::json!({"type": "integer", "maximum": 100})),
1186        };
1187        let json = serde_json::to_string(&param).unwrap();
1188        let deserialized: QueryParamConfig = serde_json::from_str(&json).unwrap();
1189        assert_eq!(param.name, deserialized.name);
1190        assert_eq!(param.required, deserialized.required);
1191    }
1192
1193    // ==================== ValidationConfig Tests ====================
1194
1195    #[test]
1196    fn test_validation_config_with_schema() {
1197        let config = ValidationConfig {
1198            mode: ValidationMode::Enforce,
1199            schema: Some(serde_json::json!({
1200                "type": "object",
1201                "properties": {
1202                    "name": {"type": "string"}
1203                }
1204            })),
1205        };
1206        assert!(matches!(config.mode, ValidationMode::Enforce));
1207        assert!(config.schema.is_some());
1208    }
1209
1210    #[test]
1211    fn test_validation_config_without_schema() {
1212        let config = ValidationConfig {
1213            mode: ValidationMode::Off,
1214            schema: None,
1215        };
1216        assert!(matches!(config.mode, ValidationMode::Off));
1217        assert!(config.schema.is_none());
1218    }
1219
1220    // ==================== LatencyConfig Tests ====================
1221
1222    #[test]
1223    fn test_latency_config_fixed() {
1224        let config = LatencyConfig {
1225            base_ms: 100,
1226            jitter_ms: 20,
1227            distribution: LatencyDistribution::Fixed,
1228        };
1229        assert_eq!(config.base_ms, 100);
1230        assert_eq!(config.jitter_ms, 20);
1231    }
1232
1233    #[test]
1234    fn test_latency_config_normal_distribution() {
1235        let config = LatencyConfig {
1236            base_ms: 50,
1237            jitter_ms: 10,
1238            distribution: LatencyDistribution::Normal { std_dev_ms: 15.0 },
1239        };
1240        assert!(matches!(config.distribution, LatencyDistribution::Normal { .. }));
1241    }
1242
1243    #[test]
1244    fn test_latency_config_pareto_distribution() {
1245        let config = LatencyConfig {
1246            base_ms: 75,
1247            jitter_ms: 25,
1248            distribution: LatencyDistribution::Pareto { shape: 1.5 },
1249        };
1250        assert!(matches!(config.distribution, LatencyDistribution::Pareto { .. }));
1251    }
1252
1253    #[test]
1254    fn test_latency_config_serialization() {
1255        let config = LatencyConfig {
1256            base_ms: 200,
1257            jitter_ms: 50,
1258            distribution: LatencyDistribution::Fixed,
1259        };
1260        let json = serde_json::to_string(&config).unwrap();
1261        let deserialized: LatencyConfig = serde_json::from_str(&json).unwrap();
1262        assert_eq!(config.base_ms, deserialized.base_ms);
1263        assert_eq!(config.jitter_ms, deserialized.jitter_ms);
1264    }
1265
1266    // ==================== FailureConfig Tests ====================
1267
1268    #[test]
1269    fn test_failure_config_creation() {
1270        let config = FailureConfig {
1271            error_rate: 0.1,
1272            status_codes: vec![500, 502, 503],
1273            error_message: Some("Server error".to_string()),
1274        };
1275        assert!((config.error_rate - 0.1).abs() < 0.001);
1276        assert_eq!(config.status_codes.len(), 3);
1277        assert!(config.error_message.is_some());
1278    }
1279
1280    #[test]
1281    fn test_failure_config_high_error_rate() {
1282        let config = FailureConfig {
1283            error_rate: 0.75,
1284            status_codes: vec![500],
1285            error_message: None,
1286        };
1287        assert!(config.error_rate > 0.5);
1288    }
1289
1290    #[test]
1291    fn test_failure_config_serialization() {
1292        let config = FailureConfig {
1293            error_rate: 0.25,
1294            status_codes: vec![429, 500],
1295            error_message: Some("Rate limited".to_string()),
1296        };
1297        let json = serde_json::to_string(&config).unwrap();
1298        let deserialized: FailureConfig = serde_json::from_str(&json).unwrap();
1299        assert_eq!(config.status_codes, deserialized.status_codes);
1300    }
1301
1302    // ==================== TrafficShapingConfig Tests ====================
1303
1304    #[test]
1305    fn test_traffic_shaping_config_bandwidth_limit() {
1306        let config = TrafficShapingConfig {
1307            bandwidth_limit_bps: Some(1024 * 1024), // 1 MB/s
1308            packet_loss_rate: None,
1309        };
1310        assert_eq!(config.bandwidth_limit_bps, Some(1024 * 1024));
1311    }
1312
1313    #[test]
1314    fn test_traffic_shaping_config_packet_loss() {
1315        let config = TrafficShapingConfig {
1316            bandwidth_limit_bps: None,
1317            packet_loss_rate: Some(0.05),
1318        };
1319        assert!(config.packet_loss_rate.is_some());
1320    }
1321
1322    #[test]
1323    fn test_traffic_shaping_config_both_enabled() {
1324        let config = TrafficShapingConfig {
1325            bandwidth_limit_bps: Some(500_000),
1326            packet_loss_rate: Some(0.02),
1327        };
1328        assert!(config.bandwidth_limit_bps.is_some());
1329        assert!(config.packet_loss_rate.is_some());
1330    }
1331
1332    // ==================== EndpointBehavior Tests ====================
1333
1334    #[test]
1335    fn test_endpoint_behavior_with_latency_only() {
1336        let behavior = EndpointBehavior {
1337            latency: Some(LatencyConfig {
1338                base_ms: 100,
1339                jitter_ms: 10,
1340                distribution: LatencyDistribution::Fixed,
1341            }),
1342            failure: None,
1343            traffic_shaping: None,
1344        };
1345        assert!(behavior.latency.is_some());
1346        assert!(behavior.failure.is_none());
1347    }
1348
1349    #[test]
1350    fn test_endpoint_behavior_with_failure_only() {
1351        let behavior = EndpointBehavior {
1352            latency: None,
1353            failure: Some(FailureConfig {
1354                error_rate: 0.1,
1355                status_codes: vec![500],
1356                error_message: None,
1357            }),
1358            traffic_shaping: None,
1359        };
1360        assert!(behavior.failure.is_some());
1361    }
1362
1363    #[test]
1364    fn test_endpoint_behavior_full_config() {
1365        let behavior = EndpointBehavior {
1366            latency: Some(LatencyConfig {
1367                base_ms: 50,
1368                jitter_ms: 10,
1369                distribution: LatencyDistribution::Fixed,
1370            }),
1371            failure: Some(FailureConfig {
1372                error_rate: 0.05,
1373                status_codes: vec![503],
1374                error_message: None,
1375            }),
1376            traffic_shaping: Some(TrafficShapingConfig {
1377                bandwidth_limit_bps: Some(100_000),
1378                packet_loss_rate: Some(0.01),
1379            }),
1380        };
1381        assert!(behavior.latency.is_some());
1382        assert!(behavior.failure.is_some());
1383        assert!(behavior.traffic_shaping.is_some());
1384    }
1385
1386    // ==================== ResponseBody Tests ====================
1387
1388    #[test]
1389    fn test_response_body_static() {
1390        let body = ResponseBody::Static {
1391            content: serde_json::json!({"message": "Hello"}),
1392        };
1393        if let ResponseBody::Static { content } = body {
1394            assert_eq!(content["message"], "Hello");
1395        } else {
1396            panic!("Expected Static response body");
1397        }
1398    }
1399
1400    #[test]
1401    fn test_response_body_template() {
1402        let body = ResponseBody::Template {
1403            template: "Hello, {{name}}!".to_string(),
1404        };
1405        if let ResponseBody::Template { template } = body {
1406            assert!(template.contains("{{name}}"));
1407        } else {
1408            panic!("Expected Template response body");
1409        }
1410    }
1411
1412    #[test]
1413    fn test_response_body_faker() {
1414        let body = ResponseBody::Faker {
1415            schema: serde_json::json!({
1416                "type": "object",
1417                "properties": {
1418                    "id": {"type": "integer"},
1419                    "name": {"type": "string"}
1420                }
1421            }),
1422        };
1423        if let ResponseBody::Faker { schema } = body {
1424            assert_eq!(schema["type"], "object");
1425        } else {
1426            panic!("Expected Faker response body");
1427        }
1428    }
1429
1430    #[test]
1431    fn test_response_body_ai() {
1432        let body = ResponseBody::AI {
1433            prompt: "Generate a user profile".to_string(),
1434        };
1435        if let ResponseBody::AI { prompt } = body {
1436            assert!(prompt.contains("user profile"));
1437        } else {
1438            panic!("Expected AI response body");
1439        }
1440    }
1441
1442    // ==================== HttpRequestConfig Tests ====================
1443
1444    #[test]
1445    fn test_http_request_config_with_validation() {
1446        let config = HttpRequestConfig {
1447            validation: Some(ValidationConfig {
1448                mode: ValidationMode::Enforce,
1449                schema: Some(serde_json::json!({"type": "object"})),
1450            }),
1451            headers: None,
1452            query_params: None,
1453            body_schema: None,
1454        };
1455        assert!(config.validation.is_some());
1456    }
1457
1458    #[test]
1459    fn test_http_request_config_with_headers() {
1460        let config = HttpRequestConfig {
1461            validation: None,
1462            headers: Some(vec![HeaderConfig {
1463                name: "Authorization".to_string(),
1464                value: "Bearer token".to_string(),
1465            }]),
1466            query_params: None,
1467            body_schema: None,
1468        };
1469        assert_eq!(config.headers.as_ref().unwrap().len(), 1);
1470    }
1471
1472    #[test]
1473    fn test_http_request_config_with_query_params() {
1474        let config = HttpRequestConfig {
1475            validation: None,
1476            headers: None,
1477            query_params: Some(vec![
1478                QueryParamConfig {
1479                    name: "page".to_string(),
1480                    required: true,
1481                    schema: None,
1482                },
1483                QueryParamConfig {
1484                    name: "limit".to_string(),
1485                    required: false,
1486                    schema: None,
1487                },
1488            ]),
1489            body_schema: None,
1490        };
1491        assert_eq!(config.query_params.as_ref().unwrap().len(), 2);
1492    }
1493
1494    // ==================== HttpResponseConfig Tests ====================
1495
1496    #[test]
1497    fn test_http_response_config_ok() {
1498        let config = HttpResponseConfig {
1499            status: 200,
1500            headers: None,
1501            body: ResponseBody::Static {
1502                content: serde_json::json!({"success": true}),
1503            },
1504        };
1505        assert_eq!(config.status, 200);
1506    }
1507
1508    #[test]
1509    fn test_http_response_config_not_found() {
1510        let config = HttpResponseConfig {
1511            status: 404,
1512            headers: Some(vec![HeaderConfig {
1513                name: "X-Error-Code".to_string(),
1514                value: "NOT_FOUND".to_string(),
1515            }]),
1516            body: ResponseBody::Static {
1517                content: serde_json::json!({"error": "Not found"}),
1518            },
1519        };
1520        assert_eq!(config.status, 404);
1521        assert!(config.headers.is_some());
1522    }
1523
1524    #[test]
1525    fn test_http_response_config_server_error() {
1526        let config = HttpResponseConfig {
1527            status: 500,
1528            headers: None,
1529            body: ResponseBody::Static {
1530                content: serde_json::json!({"error": "Internal server error"}),
1531            },
1532        };
1533        assert_eq!(config.status, 500);
1534    }
1535
1536    // ==================== HttpEndpointConfig Tests ====================
1537
1538    #[test]
1539    fn test_http_endpoint_config_get() {
1540        let config = HttpEndpointConfig {
1541            method: "GET".to_string(),
1542            path: "/api/users".to_string(),
1543            request: None,
1544            response: HttpResponseConfig {
1545                status: 200,
1546                headers: None,
1547                body: ResponseBody::Static {
1548                    content: serde_json::json!([]),
1549                },
1550            },
1551            behavior: None,
1552        };
1553        assert_eq!(config.method, "GET");
1554        assert!(config.path.starts_with('/'));
1555    }
1556
1557    #[test]
1558    fn test_http_endpoint_config_post_with_request() {
1559        let config = HttpEndpointConfig {
1560            method: "POST".to_string(),
1561            path: "/api/users".to_string(),
1562            request: Some(HttpRequestConfig {
1563                validation: Some(ValidationConfig {
1564                    mode: ValidationMode::Enforce,
1565                    schema: Some(serde_json::json!({
1566                        "type": "object",
1567                        "required": ["name", "email"]
1568                    })),
1569                }),
1570                headers: None,
1571                query_params: None,
1572                body_schema: None,
1573            }),
1574            response: HttpResponseConfig {
1575                status: 201,
1576                headers: None,
1577                body: ResponseBody::Static {
1578                    content: serde_json::json!({"id": 1}),
1579                },
1580            },
1581            behavior: None,
1582        };
1583        assert_eq!(config.method, "POST");
1584        assert!(config.request.is_some());
1585    }
1586
1587    // ==================== GrpcEndpointConfig Tests ====================
1588
1589    #[test]
1590    fn test_grpc_endpoint_config_creation() {
1591        let config = GrpcEndpointConfig {
1592            service: "users.UserService".to_string(),
1593            method: "GetUser".to_string(),
1594            proto_file: "/path/to/user.proto".to_string(),
1595            request_type: "GetUserRequest".to_string(),
1596            response_type: "GetUserResponse".to_string(),
1597            response: GrpcResponseConfig {
1598                body: ResponseBody::Static {
1599                    content: serde_json::json!({"id": 1, "name": "John"}),
1600                },
1601                metadata: None,
1602            },
1603            behavior: None,
1604        };
1605        assert_eq!(config.service, "users.UserService");
1606        assert_eq!(config.method, "GetUser");
1607    }
1608
1609    #[test]
1610    fn test_grpc_endpoint_config_with_metadata() {
1611        let config = GrpcEndpointConfig {
1612            service: "example.ExampleService".to_string(),
1613            method: "DoSomething".to_string(),
1614            proto_file: "/path/to/example.proto".to_string(),
1615            request_type: "Request".to_string(),
1616            response_type: "Response".to_string(),
1617            response: GrpcResponseConfig {
1618                body: ResponseBody::Static {
1619                    content: serde_json::json!({}),
1620                },
1621                metadata: Some(vec![HeaderConfig {
1622                    name: "x-request-id".to_string(),
1623                    value: "12345".to_string(),
1624                }]),
1625            },
1626            behavior: None,
1627        };
1628        assert!(config.response.metadata.is_some());
1629    }
1630
1631    // ==================== WebsocketEndpointConfig Tests ====================
1632
1633    #[test]
1634    fn test_websocket_endpoint_config_basic() {
1635        let config = WebsocketEndpointConfig {
1636            path: "/ws".to_string(),
1637            on_connect: None,
1638            on_message: Some(WebsocketAction::Echo),
1639            on_disconnect: None,
1640            behavior: None,
1641        };
1642        assert_eq!(config.path, "/ws");
1643    }
1644
1645    #[test]
1646    fn test_websocket_endpoint_config_with_send() {
1647        let config = WebsocketEndpointConfig {
1648            path: "/notifications".to_string(),
1649            on_connect: Some(WebsocketAction::Send {
1650                message: ResponseBody::Static {
1651                    content: serde_json::json!({"type": "connected"}),
1652                },
1653            }),
1654            on_message: None,
1655            on_disconnect: None,
1656            behavior: None,
1657        };
1658        assert!(config.on_connect.is_some());
1659    }
1660
1661    #[test]
1662    fn test_websocket_endpoint_config_with_broadcast() {
1663        let config = WebsocketEndpointConfig {
1664            path: "/chat".to_string(),
1665            on_connect: None,
1666            on_message: Some(WebsocketAction::Broadcast {
1667                message: ResponseBody::Template {
1668                    template: "{{message}}".to_string(),
1669                },
1670            }),
1671            on_disconnect: None,
1672            behavior: None,
1673        };
1674        if let Some(WebsocketAction::Broadcast { .. }) = config.on_message {
1675            // Test passes
1676        } else {
1677            panic!("Expected Broadcast action");
1678        }
1679    }
1680
1681    #[test]
1682    fn test_websocket_endpoint_config_with_close() {
1683        let config = WebsocketEndpointConfig {
1684            path: "/stream".to_string(),
1685            on_connect: None,
1686            on_message: None,
1687            on_disconnect: Some(WebsocketAction::Close {
1688                code: 1000,
1689                reason: "Normal closure".to_string(),
1690            }),
1691            behavior: None,
1692        };
1693        if let Some(WebsocketAction::Close { code, reason }) = config.on_disconnect {
1694            assert_eq!(code, 1000);
1695            assert_eq!(reason, "Normal closure");
1696        } else {
1697            panic!("Expected Close action");
1698        }
1699    }
1700
1701    // ==================== ValidationResult Tests ====================
1702
1703    #[test]
1704    fn test_validation_result_valid() {
1705        let result = ValidationResult {
1706            valid: true,
1707            errors: vec![],
1708            warnings: vec![],
1709        };
1710        assert!(result.valid);
1711        assert!(result.errors.is_empty());
1712    }
1713
1714    #[test]
1715    fn test_validation_result_with_errors() {
1716        let result = ValidationResult {
1717            valid: false,
1718            errors: vec![ValidationError {
1719                field: "method".to_string(),
1720                message: "Invalid HTTP method".to_string(),
1721            }],
1722            warnings: vec![],
1723        };
1724        assert!(!result.valid);
1725        assert_eq!(result.errors.len(), 1);
1726    }
1727
1728    #[test]
1729    fn test_validation_result_with_warnings() {
1730        let result = ValidationResult {
1731            valid: true,
1732            errors: vec![],
1733            warnings: vec!["High error rate configured".to_string()],
1734        };
1735        assert!(result.valid);
1736        assert_eq!(result.warnings.len(), 1);
1737    }
1738
1739    // ==================== ValidationError Tests ====================
1740
1741    #[test]
1742    fn test_validation_error_creation() {
1743        let error = ValidationError {
1744            field: "path".to_string(),
1745            message: "Path must start with /".to_string(),
1746        };
1747        assert_eq!(error.field, "path");
1748        assert!(error.message.contains('/'));
1749    }
1750
1751    #[test]
1752    fn test_validation_error_clone() {
1753        let error = ValidationError {
1754            field: "status".to_string(),
1755            message: "Invalid status code".to_string(),
1756        };
1757        let cloned = error.clone();
1758        assert_eq!(error.field, cloned.field);
1759        assert_eq!(error.message, cloned.message);
1760    }
1761
1762    // ==================== UIBuilderState Tests ====================
1763
1764    #[test]
1765    fn test_ui_builder_state_creation() {
1766        let config = ServerConfig::default();
1767        let state = UIBuilderState::new(config);
1768        // State should be created without panic
1769        let _ = state;
1770    }
1771
1772    #[test]
1773    fn test_ui_builder_state_clone() {
1774        let config = ServerConfig::default();
1775        let state = UIBuilderState::new(config);
1776        let cloned = state.clone();
1777        // Both should exist without panic
1778        let _ = (state, cloned);
1779    }
1780
1781    // ==================== EndpointConfig Tests ====================
1782
1783    #[test]
1784    fn test_endpoint_serialization() {
1785        let endpoint = EndpointConfig {
1786            id: "test-1".to_string(),
1787            protocol: Protocol::Http,
1788            name: "Test Endpoint".to_string(),
1789            description: Some("A test endpoint".to_string()),
1790            enabled: true,
1791            config: EndpointProtocolConfig::Http(HttpEndpointConfig {
1792                method: "GET".to_string(),
1793                path: "/test".to_string(),
1794                request: None,
1795                response: HttpResponseConfig {
1796                    status: 200,
1797                    headers: None,
1798                    body: ResponseBody::Static {
1799                        content: serde_json::json!({"message": "Hello"}),
1800                    },
1801                },
1802                behavior: None,
1803            }),
1804        };
1805
1806        let json = serde_json::to_string(&endpoint).unwrap();
1807        let deserialized: EndpointConfig = serde_json::from_str(&json).unwrap();
1808
1809        assert_eq!(endpoint.id, deserialized.id);
1810        assert_eq!(endpoint.protocol, deserialized.protocol);
1811    }
1812
1813    #[test]
1814    fn test_endpoint_config_disabled() {
1815        let endpoint = EndpointConfig {
1816            id: "disabled-1".to_string(),
1817            protocol: Protocol::Http,
1818            name: "Disabled Endpoint".to_string(),
1819            description: None,
1820            enabled: false,
1821            config: EndpointProtocolConfig::Http(HttpEndpointConfig {
1822                method: "GET".to_string(),
1823                path: "/disabled".to_string(),
1824                request: None,
1825                response: HttpResponseConfig {
1826                    status: 200,
1827                    headers: None,
1828                    body: ResponseBody::Static {
1829                        content: serde_json::json!({}),
1830                    },
1831                },
1832                behavior: None,
1833            }),
1834        };
1835        assert!(!endpoint.enabled);
1836        assert!(endpoint.description.is_none());
1837    }
1838
1839    #[test]
1840    fn test_endpoint_config_grpc() {
1841        let endpoint = EndpointConfig {
1842            id: "grpc-1".to_string(),
1843            protocol: Protocol::Grpc,
1844            name: "gRPC Endpoint".to_string(),
1845            description: Some("A gRPC endpoint".to_string()),
1846            enabled: true,
1847            config: EndpointProtocolConfig::Grpc(GrpcEndpointConfig {
1848                service: "test.Service".to_string(),
1849                method: "Call".to_string(),
1850                proto_file: "/test.proto".to_string(),
1851                request_type: "Request".to_string(),
1852                response_type: "Response".to_string(),
1853                response: GrpcResponseConfig {
1854                    body: ResponseBody::Static {
1855                        content: serde_json::json!({}),
1856                    },
1857                    metadata: None,
1858                },
1859                behavior: None,
1860            }),
1861        };
1862        assert_eq!(endpoint.protocol, Protocol::Grpc);
1863    }
1864
1865    #[test]
1866    fn test_endpoint_config_websocket() {
1867        let endpoint = EndpointConfig {
1868            id: "ws-1".to_string(),
1869            protocol: Protocol::Websocket,
1870            name: "WebSocket Endpoint".to_string(),
1871            description: None,
1872            enabled: true,
1873            config: EndpointProtocolConfig::Websocket(WebsocketEndpointConfig {
1874                path: "/ws".to_string(),
1875                on_connect: None,
1876                on_message: Some(WebsocketAction::Echo),
1877                on_disconnect: None,
1878                behavior: None,
1879            }),
1880        };
1881        assert_eq!(endpoint.protocol, Protocol::Websocket);
1882    }
1883
1884    #[test]
1885    fn test_validation() {
1886        // Test invalid HTTP method
1887        let endpoint = EndpointConfig {
1888            id: "test-1".to_string(),
1889            protocol: Protocol::Http,
1890            name: "Test".to_string(),
1891            description: None,
1892            enabled: true,
1893            config: EndpointProtocolConfig::Http(HttpEndpointConfig {
1894                method: "INVALID".to_string(),
1895                path: "/test".to_string(),
1896                request: None,
1897                response: HttpResponseConfig {
1898                    status: 200,
1899                    headers: None,
1900                    body: ResponseBody::Static {
1901                        content: serde_json::json!({}),
1902                    },
1903                },
1904                behavior: None,
1905            }),
1906        };
1907
1908        // Validation would catch this in the async function
1909        assert_eq!(endpoint.protocol, Protocol::Http);
1910    }
1911
1912    // ==================== ConfigFormat Tests ====================
1913
1914    #[test]
1915    fn test_config_format_yaml_deserialization() {
1916        let json = r#"{"config": "test", "format": "yaml"}"#;
1917        let request: ImportRequest = serde_json::from_str(json).unwrap();
1918        assert!(matches!(request.format, ConfigFormat::Yaml));
1919    }
1920
1921    #[test]
1922    fn test_config_format_json_deserialization() {
1923        let json = r#"{"config": "test", "format": "json"}"#;
1924        let request: ImportRequest = serde_json::from_str(json).unwrap();
1925        assert!(matches!(request.format, ConfigFormat::Json));
1926    }
1927
1928    // ==================== Async Handler Tests ====================
1929
1930    #[tokio::test]
1931    async fn test_list_endpoints_empty() {
1932        let config = ServerConfig::default();
1933        let state = UIBuilderState::new(config);
1934        let result = list_endpoints(State(state)).await;
1935        let response = result.0;
1936        assert_eq!(response["total"], 0);
1937        assert_eq!(response["enabled"], 0);
1938    }
1939
1940    #[tokio::test]
1941    async fn test_create_and_get_endpoint() {
1942        let config = ServerConfig::default();
1943        let state = UIBuilderState::new(config);
1944
1945        let endpoint = create_test_http_endpoint();
1946        let create_result = create_endpoint(State(state.clone()), Json(endpoint.clone())).await;
1947        assert!(create_result.is_ok());
1948
1949        let get_result = get_endpoint(State(state), Path("test-1".to_string())).await;
1950        assert!(get_result.is_ok());
1951        assert_eq!(get_result.unwrap().0.id, "test-1");
1952    }
1953
1954    #[tokio::test]
1955    async fn test_get_endpoint_not_found() {
1956        let config = ServerConfig::default();
1957        let state = UIBuilderState::new(config);
1958
1959        let result = get_endpoint(State(state), Path("nonexistent".to_string())).await;
1960        assert!(result.is_err());
1961        assert_eq!(result.err().unwrap(), StatusCode::NOT_FOUND);
1962    }
1963
1964    #[tokio::test]
1965    async fn test_create_endpoint_duplicate_id() {
1966        let config = ServerConfig::default();
1967        let state = UIBuilderState::new(config);
1968
1969        let endpoint = create_test_http_endpoint();
1970        let _ = create_endpoint(State(state.clone()), Json(endpoint.clone())).await;
1971
1972        // Try to create with same ID
1973        let result = create_endpoint(State(state), Json(endpoint)).await;
1974        assert!(result.is_err());
1975        assert_eq!(result.err().unwrap(), StatusCode::CONFLICT);
1976    }
1977
1978    #[tokio::test]
1979    async fn test_create_endpoint_auto_generate_id() {
1980        let config = ServerConfig::default();
1981        let state = UIBuilderState::new(config);
1982
1983        let mut endpoint = create_test_http_endpoint();
1984        endpoint.id = String::new(); // Empty ID should be auto-generated
1985
1986        let result = create_endpoint(State(state), Json(endpoint)).await;
1987        assert!(result.is_ok());
1988        let created = result.unwrap().0;
1989        assert!(!created.id.is_empty());
1990    }
1991
1992    #[tokio::test]
1993    async fn test_update_endpoint() {
1994        let config = ServerConfig::default();
1995        let state = UIBuilderState::new(config);
1996
1997        let endpoint = create_test_http_endpoint();
1998        let _ = create_endpoint(State(state.clone()), Json(endpoint.clone())).await;
1999
2000        let mut updated = endpoint.clone();
2001        updated.name = "Updated Name".to_string();
2002
2003        let result = update_endpoint(State(state), Path("test-1".to_string()), Json(updated)).await;
2004        assert!(result.is_ok());
2005        assert_eq!(result.unwrap().0.name, "Updated Name");
2006    }
2007
2008    #[tokio::test]
2009    async fn test_update_endpoint_not_found() {
2010        let config = ServerConfig::default();
2011        let state = UIBuilderState::new(config);
2012
2013        let endpoint = create_test_http_endpoint();
2014        let result =
2015            update_endpoint(State(state), Path("nonexistent".to_string()), Json(endpoint)).await;
2016        assert!(result.is_err());
2017        assert_eq!(result.err().unwrap(), StatusCode::NOT_FOUND);
2018    }
2019
2020    #[tokio::test]
2021    async fn test_delete_endpoint() {
2022        let config = ServerConfig::default();
2023        let state = UIBuilderState::new(config);
2024
2025        let endpoint = create_test_http_endpoint();
2026        let _ = create_endpoint(State(state.clone()), Json(endpoint)).await;
2027
2028        let result = delete_endpoint(State(state), Path("test-1".to_string())).await;
2029        assert!(result.is_ok());
2030        assert_eq!(result.unwrap(), StatusCode::NO_CONTENT);
2031    }
2032
2033    #[tokio::test]
2034    async fn test_delete_endpoint_not_found() {
2035        let config = ServerConfig::default();
2036        let state = UIBuilderState::new(config);
2037
2038        let result = delete_endpoint(State(state), Path("nonexistent".to_string())).await;
2039        assert!(result.is_err());
2040        assert_eq!(result.err().unwrap(), StatusCode::NOT_FOUND);
2041    }
2042
2043    #[tokio::test]
2044    async fn test_validate_endpoint_valid_http() {
2045        let config = ServerConfig::default();
2046        let state = UIBuilderState::new(config);
2047
2048        let endpoint = create_test_http_endpoint();
2049        let result = validate_endpoint(State(state), Json(endpoint)).await;
2050        assert!(result.0.valid);
2051        assert!(result.0.errors.is_empty());
2052    }
2053
2054    #[tokio::test]
2055    async fn test_validate_endpoint_invalid_method() {
2056        let config = ServerConfig::default();
2057        let state = UIBuilderState::new(config);
2058
2059        let mut endpoint = create_test_http_endpoint();
2060        if let EndpointProtocolConfig::Http(ref mut http_config) = endpoint.config {
2061            http_config.method = "INVALID".to_string();
2062        }
2063
2064        let result = validate_endpoint(State(state), Json(endpoint)).await;
2065        assert!(!result.0.valid);
2066        assert!(!result.0.errors.is_empty());
2067    }
2068
2069    #[tokio::test]
2070    async fn test_validate_endpoint_invalid_path() {
2071        let config = ServerConfig::default();
2072        let state = UIBuilderState::new(config);
2073
2074        let mut endpoint = create_test_http_endpoint();
2075        if let EndpointProtocolConfig::Http(ref mut http_config) = endpoint.config {
2076            http_config.path = "no-leading-slash".to_string();
2077        }
2078
2079        let result = validate_endpoint(State(state), Json(endpoint)).await;
2080        assert!(!result.0.valid);
2081    }
2082
2083    #[tokio::test]
2084    async fn test_validate_endpoint_invalid_status() {
2085        let config = ServerConfig::default();
2086        let state = UIBuilderState::new(config);
2087
2088        let mut endpoint = create_test_http_endpoint();
2089        if let EndpointProtocolConfig::Http(ref mut http_config) = endpoint.config {
2090            http_config.response.status = 999; // Invalid status code
2091        }
2092
2093        let result = validate_endpoint(State(state), Json(endpoint)).await;
2094        assert!(!result.0.valid);
2095    }
2096
2097    #[tokio::test]
2098    async fn test_validate_endpoint_high_error_rate_warning() {
2099        let config = ServerConfig::default();
2100        let state = UIBuilderState::new(config);
2101
2102        let mut endpoint = create_test_http_endpoint();
2103        if let EndpointProtocolConfig::Http(ref mut http_config) = endpoint.config {
2104            http_config.behavior = Some(EndpointBehavior {
2105                latency: None,
2106                failure: Some(FailureConfig {
2107                    error_rate: 0.75,
2108                    status_codes: vec![500],
2109                    error_message: None,
2110                }),
2111                traffic_shaping: None,
2112            });
2113        }
2114
2115        let result = validate_endpoint(State(state), Json(endpoint)).await;
2116        assert!(result.0.valid); // Still valid, just has warnings
2117        assert!(!result.0.warnings.is_empty());
2118    }
2119
2120    #[tokio::test]
2121    async fn test_validate_grpc_endpoint_empty_service() {
2122        let config = ServerConfig::default();
2123        let state = UIBuilderState::new(config);
2124
2125        let endpoint = EndpointConfig {
2126            id: "grpc-test".to_string(),
2127            protocol: Protocol::Grpc,
2128            name: "gRPC Test".to_string(),
2129            description: None,
2130            enabled: true,
2131            config: EndpointProtocolConfig::Grpc(GrpcEndpointConfig {
2132                service: String::new(), // Empty service name
2133                method: "Method".to_string(),
2134                proto_file: "/test.proto".to_string(),
2135                request_type: "Request".to_string(),
2136                response_type: "Response".to_string(),
2137                response: GrpcResponseConfig {
2138                    body: ResponseBody::Static {
2139                        content: serde_json::json!({}),
2140                    },
2141                    metadata: None,
2142                },
2143                behavior: None,
2144            }),
2145        };
2146
2147        let result = validate_endpoint(State(state), Json(endpoint)).await;
2148        assert!(!result.0.valid);
2149    }
2150
2151    #[tokio::test]
2152    async fn test_validate_websocket_endpoint_invalid_path() {
2153        let config = ServerConfig::default();
2154        let state = UIBuilderState::new(config);
2155
2156        let endpoint = EndpointConfig {
2157            id: "ws-test".to_string(),
2158            protocol: Protocol::Websocket,
2159            name: "WebSocket Test".to_string(),
2160            description: None,
2161            enabled: true,
2162            config: EndpointProtocolConfig::Websocket(WebsocketEndpointConfig {
2163                path: "no-slash".to_string(), // Invalid path
2164                on_connect: None,
2165                on_message: None,
2166                on_disconnect: None,
2167                behavior: None,
2168            }),
2169        };
2170
2171        let result = validate_endpoint(State(state), Json(endpoint)).await;
2172        assert!(!result.0.valid);
2173    }
2174
2175    #[tokio::test]
2176    async fn test_get_config() {
2177        let config = ServerConfig::default();
2178        let state = UIBuilderState::new(config);
2179
2180        let result = get_config(State(state)).await;
2181        // Should return the default config
2182        let _ = result.0;
2183    }
2184
2185    #[tokio::test]
2186    async fn test_update_config() {
2187        let config = ServerConfig::default();
2188        let state = UIBuilderState::new(config.clone());
2189
2190        let result = update_config(State(state), Json(config)).await;
2191        assert!(result.is_ok());
2192    }
2193
2194    #[tokio::test]
2195    async fn test_export_config() {
2196        let config = ServerConfig::default();
2197        let state = UIBuilderState::new(config);
2198
2199        let result = export_config(State(state)).await;
2200        assert!(result.is_ok());
2201    }
2202
2203    #[tokio::test]
2204    async fn test_list_endpoints_with_multiple_protocols() {
2205        let config = ServerConfig::default();
2206        let state = UIBuilderState::new(config);
2207
2208        // Add HTTP endpoint
2209        let http_endpoint = create_test_http_endpoint();
2210        let _ = create_endpoint(State(state.clone()), Json(http_endpoint)).await;
2211
2212        // Add WebSocket endpoint
2213        let ws_endpoint = EndpointConfig {
2214            id: "ws-1".to_string(),
2215            protocol: Protocol::Websocket,
2216            name: "WS Endpoint".to_string(),
2217            description: None,
2218            enabled: true,
2219            config: EndpointProtocolConfig::Websocket(WebsocketEndpointConfig {
2220                path: "/ws".to_string(),
2221                on_connect: None,
2222                on_message: Some(WebsocketAction::Echo),
2223                on_disconnect: None,
2224                behavior: None,
2225            }),
2226        };
2227        let _ = create_endpoint(State(state.clone()), Json(ws_endpoint)).await;
2228
2229        let result = list_endpoints(State(state)).await;
2230        let response = result.0;
2231        assert_eq!(response["total"], 2);
2232        assert_eq!(response["by_protocol"]["http"], 1);
2233        assert_eq!(response["by_protocol"]["websocket"], 1);
2234    }
2235
2236    // ==================== Router Tests ====================
2237
2238    #[test]
2239    fn test_create_ui_builder_router() {
2240        let config = ServerConfig::default();
2241        let state = UIBuilderState::new(config);
2242        let router = create_ui_builder_router(state);
2243        // Router should be created without panic
2244        let _ = router;
2245    }
2246}