1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct EndpointConfig {
23 pub id: String,
25 pub protocol: Protocol,
27 pub name: String,
29 pub description: Option<String>,
31 pub enabled: bool,
33 pub config: EndpointProtocolConfig,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
39#[serde(rename_all = "lowercase")]
40pub enum Protocol {
41 Http,
43 Grpc,
45 Websocket,
47 Graphql,
49 Mqtt,
51 Smtp,
53 Kafka,
55 Amqp,
57 Ftp,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63#[serde(tag = "type")]
64pub enum EndpointProtocolConfig {
65 Http(HttpEndpointConfig),
67 Grpc(GrpcEndpointConfig),
69 Websocket(WebsocketEndpointConfig),
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct HttpEndpointConfig {
76 pub method: String,
78 pub path: String,
80 pub request: Option<HttpRequestConfig>,
82 pub response: HttpResponseConfig,
84 pub behavior: Option<EndpointBehavior>,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct HttpRequestConfig {
91 pub validation: Option<ValidationConfig>,
93 pub headers: Option<Vec<HeaderConfig>>,
95 pub query_params: Option<Vec<QueryParamConfig>>,
97 pub body_schema: Option<serde_json::Value>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct HttpResponseConfig {
104 pub status: u16,
106 pub headers: Option<Vec<HeaderConfig>>,
108 pub body: ResponseBody,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114#[serde(tag = "type")]
115pub enum ResponseBody {
116 Static {
118 content: serde_json::Value,
120 },
121 Template {
123 template: String,
125 },
126 Faker {
128 schema: serde_json::Value,
130 },
131 AI {
133 prompt: String,
135 },
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct HeaderConfig {
141 pub name: String,
143 pub value: String,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct QueryParamConfig {
150 pub name: String,
152 pub required: bool,
154 pub schema: Option<serde_json::Value>,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ValidationConfig {
161 pub mode: ValidationMode,
163 pub schema: Option<serde_json::Value>,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
169#[serde(rename_all = "lowercase")]
170pub enum ValidationMode {
171 Off,
173 Warn,
175 Enforce,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct EndpointBehavior {
182 pub latency: Option<LatencyConfig>,
184 pub failure: Option<FailureConfig>,
186 pub traffic_shaping: Option<TrafficShapingConfig>,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct LatencyConfig {
193 pub base_ms: u64,
195 pub jitter_ms: u64,
197 pub distribution: LatencyDistribution,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
203#[serde(rename_all = "lowercase")]
204pub enum LatencyDistribution {
205 Fixed,
207 Normal {
209 std_dev_ms: f64,
211 },
212 Pareto {
214 shape: f64,
216 },
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct FailureConfig {
222 pub error_rate: f64,
224 pub status_codes: Vec<u16>,
226 pub error_message: Option<String>,
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct TrafficShapingConfig {
233 pub bandwidth_limit_bps: Option<u64>,
235 pub packet_loss_rate: Option<f64>,
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct GrpcEndpointConfig {
242 pub service: String,
244 pub method: String,
246 pub proto_file: String,
248 pub request_type: String,
250 pub response_type: String,
252 pub response: GrpcResponseConfig,
254 pub behavior: Option<EndpointBehavior>,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct GrpcResponseConfig {
261 pub body: ResponseBody,
263 pub metadata: Option<Vec<HeaderConfig>>,
265}
266
267#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct WebsocketEndpointConfig {
270 pub path: String,
272 pub on_connect: Option<WebsocketAction>,
274 pub on_message: Option<WebsocketAction>,
276 pub on_disconnect: Option<WebsocketAction>,
278 pub behavior: Option<EndpointBehavior>,
280}
281
282#[derive(Debug, Clone, Serialize, Deserialize)]
284#[serde(tag = "type")]
285pub enum WebsocketAction {
286 Send {
288 message: ResponseBody,
290 },
291 Broadcast {
293 message: ResponseBody,
295 },
296 Echo,
298 Close {
300 code: u16,
302 reason: String,
304 },
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize)]
309pub struct ValidationResult {
310 pub valid: bool,
312 pub errors: Vec<ValidationError>,
314 pub warnings: Vec<String>,
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize)]
320pub struct ValidationError {
321 pub field: String,
323 pub message: String,
325}
326
327#[derive(Clone)]
329pub struct UIBuilderState {
330 pub endpoints: Arc<RwLock<Vec<EndpointConfig>>>,
332 pub server_config: Arc<RwLock<ServerConfig>>,
334}
335
336impl UIBuilderState {
337 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
349async 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
364async 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
378async 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 if endpoint.id.is_empty() {
387 endpoint.id = uuid::Uuid::new_v4().to_string();
388 }
389
390 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
405async 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
425async 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
440async 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 match &endpoint.config {
450 EndpointProtocolConfig::Http(http_config) => {
451 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 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 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 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 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 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
520async 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#[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
575async fn get_config(State(state): State<UIBuilderState>) -> Json<ServerConfig> {
577 let config = state.server_config.read().await;
578 Json(config.clone())
579}
580
581async 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#[derive(Debug, Deserialize)]
596struct ImportOpenApiRequest {
597 content: String,
598 base_url: Option<String>,
599 auto_enable: Option<bool>,
600}
601
602#[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
620async 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 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 for route in import_result.routes {
646 let endpoint_id = uuid::Uuid::new_v4().to_string();
647
648 let response_body = ResponseBody::Static {
650 content: route.response.body,
651 };
652
653 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
713async 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 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 for endpoint in endpoints.iter() {
743 if endpoint.protocol != Protocol::Http {
744 continue; }
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 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#[derive(Debug, Deserialize)]
804struct ImportAsyncApiRequest {
805 content: String,
806 base_url: Option<String>,
807 auto_enable: Option<bool>,
808}
809
810#[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
828async 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 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 for channel in import_result.channels {
854 let endpoint_id = uuid::Uuid::new_v4().to_string();
855
856 let (protocol, config) = match channel.protocol {
858 mockforge_core::import::asyncapi_import::ChannelProtocol::Websocket => {
859 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 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
934pub 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 let value = serde_json::Value::String(template.clone());
945 resolve_response_tokens(value).await
946 }
947 ResponseBody::Faker { schema } => {
948 resolve_response_tokens(schema.clone()).await
950 }
951 ResponseBody::AI { prompt: _ } => {
952 Ok(serde_json::json!({"_ai_prompt": true}))
955 }
956 }
957}
958
959pub 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 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 #[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 #[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 #[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 #[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(¶m).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 #[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 #[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 #[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 #[test]
1305 fn test_traffic_shaping_config_bandwidth_limit() {
1306 let config = TrafficShapingConfig {
1307 bandwidth_limit_bps: Some(1024 * 1024), 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 } 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 #[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 #[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 #[test]
1765 fn test_ui_builder_state_creation() {
1766 let config = ServerConfig::default();
1767 let state = UIBuilderState::new(config);
1768 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 let _ = (state, cloned);
1779 }
1780
1781 #[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 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 assert_eq!(endpoint.protocol, Protocol::Http);
1910 }
1911
1912 #[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 #[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 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(); 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; }
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); 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(), 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(), 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 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 let http_endpoint = create_test_http_endpoint();
2210 let _ = create_endpoint(State(state.clone()), Json(http_endpoint)).await;
2211
2212 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 #[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 let _ = router;
2245 }
2246}