1use axum::{
7 extract::{Path, State},
8 http::StatusCode,
9 Json,
10};
11use mockforge_core::intelligent_behavior::{rules::StateMachine, visual_layout::VisualLayout};
12use mockforge_scenarios::ScenarioManifest;
13use serde::{Deserialize, Serialize};
14use serde_json::Value;
15use std::collections::HashMap;
16use tracing::error;
17
18use crate::management::ManagementState;
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct StateMachineRequest {
26 pub state_machine: StateMachine,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub visual_layout: Option<VisualLayout>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct TransitionRequest {
36 pub resource_id: String,
38 pub to_state: String,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub context: Option<HashMap<String, Value>>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct CreateInstanceRequest {
48 pub resource_id: String,
50 pub resource_type: String,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct StateMachineResponse {
57 pub state_machine: StateMachine,
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub visual_layout: Option<VisualLayout>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct StateMachineListResponse {
67 pub state_machines: Vec<StateMachineInfo>,
69 pub total: usize,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct StateMachineInfo {
76 pub resource_type: String,
78 pub state_count: usize,
80 pub transition_count: usize,
82 pub sub_scenario_count: usize,
84 pub has_visual_layout: bool,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct StateInstanceResponse {
91 pub resource_id: String,
93 pub current_state: String,
95 pub resource_type: String,
97 pub history_count: usize,
99 pub state_data: HashMap<String, Value>,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct StateInstanceListResponse {
106 pub instances: Vec<StateInstanceResponse>,
108 pub total: usize,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct NextStatesResponse {
115 pub next_states: Vec<String>,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct ImportExportResponse {
122 pub state_machines: Vec<StateMachine>,
124 pub visual_layouts: HashMap<String, VisualLayout>,
126}
127
128pub async fn list_state_machines(
132 State(state): State<ManagementState>,
133) -> Result<Json<StateMachineListResponse>, StatusCode> {
134 let manager = state.state_machine_manager.read().await;
135
136 let machines = manager.list_state_machines().await;
138
139 let mut state_machine_list = Vec::new();
142 for (resource_type, sm) in machines.iter() {
143 let has_visual_layout = manager.get_visual_layout(resource_type).await.is_some();
144 state_machine_list.push(StateMachineInfo {
145 resource_type: resource_type.clone(),
146 state_count: sm.states.len(),
147 transition_count: sm.transitions.len(),
148 sub_scenario_count: sm.sub_scenarios.len(),
149 has_visual_layout,
150 });
151 }
152
153 Ok(Json(StateMachineListResponse {
154 state_machines: state_machine_list.clone(),
155 total: state_machine_list.len(),
156 }))
157}
158
159pub async fn get_state_machine(
161 State(state): State<ManagementState>,
162 Path(resource_type): Path<String>,
163) -> Result<Json<StateMachineResponse>, StatusCode> {
164 let manager = state.state_machine_manager.read().await;
165
166 let state_machine =
167 manager.get_state_machine(&resource_type).await.ok_or(StatusCode::NOT_FOUND)?;
168
169 let visual_layout = manager.get_visual_layout(&resource_type).await;
170
171 let state_machine_json = serde_json::to_value(&state_machine).map_err(|e| {
174 tracing::error!("JSON serialization error: {}", e);
175 StatusCode::INTERNAL_SERVER_ERROR
176 })?;
177 let state_machine: StateMachine = serde_json::from_value(state_machine_json).map_err(|e| {
178 tracing::error!("JSON serialization error: {}", e);
179 StatusCode::INTERNAL_SERVER_ERROR
180 })?;
181
182 let visual_layout: Option<VisualLayout> = visual_layout
183 .map(|layout| {
184 let layout_json = serde_json::to_value(&layout).map_err(|e| {
185 tracing::error!("JSON serialization error: {}", e);
186 StatusCode::INTERNAL_SERVER_ERROR
187 })?;
188 serde_json::from_value(layout_json).map_err(|e| {
189 tracing::error!("JSON deserialization error: {}", e);
190 StatusCode::INTERNAL_SERVER_ERROR
191 })
192 })
193 .transpose()
194 .map_err(|e| {
195 tracing::error!("JSON serialization error: {}", e);
196 StatusCode::INTERNAL_SERVER_ERROR
197 })?;
198
199 Ok(Json(StateMachineResponse {
200 state_machine,
201 visual_layout,
202 }))
203}
204
205pub async fn create_state_machine(
207 State(state): State<ManagementState>,
208 Json(request): Json<StateMachineRequest>,
209) -> Result<Json<StateMachineResponse>, StatusCode> {
210 let manager = state.state_machine_manager.write().await;
211
212 let state_machine_json = serde_json::to_value(&request.state_machine).map_err(|e| {
217 tracing::error!("JSON serialization error: {}", e);
218 StatusCode::INTERNAL_SERVER_ERROR
219 })?;
220
221 let mut manifest_json = serde_json::json!({
224 "manifest_version": "1.0",
225 "name": "api",
226 "version": "1.0.0",
227 "title": "API State Machine",
228 "description": "State machine created via API",
229 "author": "api",
230 "category": "other",
231 "compatibility": {
232 "min_version": "0.1.0",
233 "max_version": null
234 },
235 "files": [],
236 "state_machines": [state_machine_json],
237 "state_machine_graphs": {}
238 });
239
240 if let Some(layout) = &request.visual_layout {
241 let layout_json = serde_json::to_value(layout).map_err(|e| {
242 tracing::error!("JSON serialization error: {}", e);
243 StatusCode::INTERNAL_SERVER_ERROR
244 })?;
245 manifest_json["state_machine_graphs"][&request.state_machine.resource_type] = layout_json;
246 }
247
248 let manifest: ScenarioManifest = serde_json::from_value(manifest_json).map_err(|e| {
249 tracing::error!("JSON serialization error: {}", e);
250 StatusCode::INTERNAL_SERVER_ERROR
251 })?;
252
253 if let Some(sm) = manifest.state_machines.first() {
255 if let Err(e) = manager.validate_state_machine(sm) {
256 error!("Invalid state machine: {}", e);
257 return Err(StatusCode::BAD_REQUEST);
258 }
259 }
260
261 if let Err(e) = manager.load_from_manifest(&manifest).await {
262 error!("Failed to load state machine: {}", e);
263 return Err(StatusCode::INTERNAL_SERVER_ERROR);
264 }
265
266 if let Some(ref ws_tx) = state.ws_broadcast {
270 let event = crate::management_ws::MockEvent::state_machine_updated(
271 request.state_machine.resource_type.clone(),
272 request.state_machine.clone(),
273 );
274 let _ = ws_tx.send(event);
275 }
276
277 let state_machine_from_manager = manager
279 .get_state_machine(&request.state_machine.resource_type)
280 .await
281 .ok_or(StatusCode::NOT_FOUND)?;
282 let visual_layout_from_manager =
283 manager.get_visual_layout(&request.state_machine.resource_type).await;
284
285 let state_machine_json = serde_json::to_value(&state_machine_from_manager).map_err(|e| {
287 tracing::error!("JSON serialization error: {}", e);
288 StatusCode::INTERNAL_SERVER_ERROR
289 })?;
290 let state_machine: StateMachine = serde_json::from_value(state_machine_json).map_err(|e| {
291 tracing::error!("JSON serialization error: {}", e);
292 StatusCode::INTERNAL_SERVER_ERROR
293 })?;
294
295 let visual_layout: Option<VisualLayout> = visual_layout_from_manager
296 .map(|layout| {
297 let layout_json = serde_json::to_value(&layout).map_err(|e| {
298 tracing::error!("JSON serialization error: {}", e);
299 StatusCode::INTERNAL_SERVER_ERROR
300 })?;
301 serde_json::from_value(layout_json).map_err(|e| {
302 tracing::error!("JSON deserialization error: {}", e);
303 StatusCode::INTERNAL_SERVER_ERROR
304 })
305 })
306 .transpose()
307 .map_err(|e| {
308 tracing::error!("JSON serialization error: {}", e);
309 StatusCode::INTERNAL_SERVER_ERROR
310 })?;
311
312 Ok(Json(StateMachineResponse {
313 state_machine,
314 visual_layout,
315 }))
316}
317
318pub async fn delete_state_machine(
320 State(state): State<ManagementState>,
321 Path(resource_type): Path<String>,
322) -> Result<StatusCode, StatusCode> {
323 let manager = state.state_machine_manager.write().await;
324
325 let deleted = manager.delete_state_machine(&resource_type).await;
327
328 if !deleted {
329 return Err(StatusCode::NOT_FOUND);
330 }
331
332 if let Some(ref ws_tx) = state.ws_broadcast {
334 let event = crate::management_ws::MockEvent::state_machine_deleted(resource_type);
335 let _ = ws_tx.send(event);
336 }
337
338 Ok(StatusCode::NO_CONTENT)
339}
340
341pub async fn list_instances(
343 State(state): State<ManagementState>,
344) -> Result<Json<StateInstanceListResponse>, StatusCode> {
345 let manager = state.state_machine_manager.read().await;
346
347 let instances = manager.list_instances().await;
348
349 let instance_responses: Vec<StateInstanceResponse> = instances
350 .iter()
351 .map(|i| StateInstanceResponse {
352 resource_id: i.resource_id.clone(),
353 current_state: i.current_state.clone(),
354 resource_type: i.resource_type.clone(),
355 history_count: i.state_history.len(),
356 state_data: i.state_data.clone(),
357 })
358 .collect();
359
360 Ok(Json(StateInstanceListResponse {
361 instances: instance_responses,
362 total: instances.len(),
363 }))
364}
365
366pub async fn get_instance(
368 State(state): State<ManagementState>,
369 Path(resource_id): Path<String>,
370) -> Result<Json<StateInstanceResponse>, StatusCode> {
371 let manager = state.state_machine_manager.read().await;
372
373 let instance = manager.get_instance(&resource_id).await.ok_or(StatusCode::NOT_FOUND)?;
374
375 Ok(Json(StateInstanceResponse {
376 resource_id: instance.resource_id,
377 current_state: instance.current_state,
378 resource_type: instance.resource_type,
379 history_count: instance.state_history.len(),
380 state_data: instance.state_data,
381 }))
382}
383
384pub async fn create_instance(
386 State(state): State<ManagementState>,
387 Json(request): Json<CreateInstanceRequest>,
388) -> Result<Json<StateInstanceResponse>, StatusCode> {
389 let manager = state.state_machine_manager.write().await;
390
391 if let Err(e) = manager.create_instance(&request.resource_id, &request.resource_type).await {
392 error!("Failed to create instance: {}", e);
393 return Err(StatusCode::BAD_REQUEST);
394 }
395
396 let instance = manager
397 .get_instance(&request.resource_id)
398 .await
399 .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
400
401 if let Some(ref ws_tx) = state.ws_broadcast {
403 let event = crate::management_ws::MockEvent::state_instance_created(
404 instance.resource_id.clone(),
405 instance.resource_type.clone(),
406 instance.current_state.clone(),
407 );
408 let _ = ws_tx.send(event);
409 }
410
411 Ok(Json(StateInstanceResponse {
412 resource_id: instance.resource_id,
413 current_state: instance.current_state,
414 resource_type: instance.resource_type,
415 history_count: instance.state_history.len(),
416 state_data: instance.state_data,
417 }))
418}
419
420pub async fn execute_transition(
422 State(state): State<ManagementState>,
423 Json(request): Json<TransitionRequest>,
424) -> Result<Json<StateInstanceResponse>, StatusCode> {
425 let manager = state.state_machine_manager.write().await;
426
427 if let Err(e) = manager
428 .execute_transition(&request.resource_id, &request.to_state, request.context)
429 .await
430 {
431 error!("Failed to execute transition: {}", e);
432 return Err(StatusCode::BAD_REQUEST);
433 }
434
435 let instance = manager.get_instance(&request.resource_id).await.ok_or(StatusCode::NOT_FOUND)?;
436
437 let from_state = instance
439 .state_history
440 .last()
441 .map(|h| h.from_state.clone())
442 .unwrap_or_else(|| instance.current_state.clone());
443
444 if let Some(ref ws_tx) = state.ws_broadcast {
446 let event = crate::management_ws::MockEvent::state_transitioned(
447 instance.resource_id.clone(),
448 instance.resource_type.clone(),
449 from_state,
450 instance.current_state.clone(),
451 instance.state_data.clone(),
452 );
453 let _ = ws_tx.send(event);
454 }
455
456 Ok(Json(StateInstanceResponse {
457 resource_id: instance.resource_id,
458 current_state: instance.current_state,
459 resource_type: instance.resource_type,
460 history_count: instance.state_history.len(),
461 state_data: instance.state_data,
462 }))
463}
464
465pub async fn get_next_states(
467 State(state): State<ManagementState>,
468 Path(resource_id): Path<String>,
469) -> Result<Json<NextStatesResponse>, StatusCode> {
470 let manager = state.state_machine_manager.read().await;
471
472 let next_states = manager.get_next_states(&resource_id).await.map_err(|e| {
473 tracing::warn!("Failed to get next states for {}: {}", resource_id, e);
474 StatusCode::NOT_FOUND
475 })?;
476
477 Ok(Json(NextStatesResponse { next_states }))
478}
479
480pub async fn get_current_state(
482 State(state): State<ManagementState>,
483 Path(resource_id): Path<String>,
484) -> Result<Json<Value>, StatusCode> {
485 let manager = state.state_machine_manager.read().await;
486
487 let current_state =
488 manager.get_current_state(&resource_id).await.ok_or(StatusCode::NOT_FOUND)?;
489
490 Ok(Json(serde_json::json!({
491 "resource_id": resource_id,
492 "current_state": current_state
493 })))
494}
495
496pub async fn export_state_machines(
498 State(state): State<ManagementState>,
499) -> Result<Json<ImportExportResponse>, StatusCode> {
500 let manager = state.state_machine_manager.read().await;
501
502 let (state_machines_from_manager, visual_layouts_from_manager) = manager.export_all().await;
504
505 let state_machines: Vec<StateMachine> = state_machines_from_manager
507 .into_iter()
508 .map(|sm| {
509 let json = serde_json::to_value(&sm).map_err(|e| {
510 tracing::error!("JSON serialization error: {}", e);
511 StatusCode::INTERNAL_SERVER_ERROR
512 })?;
513 serde_json::from_value(json).map_err(|e| {
514 tracing::error!("JSON deserialization error: {}", e);
515 StatusCode::INTERNAL_SERVER_ERROR
516 })
517 })
518 .collect::<Result<Vec<_>, StatusCode>>()?;
519
520 let visual_layouts: HashMap<String, VisualLayout> = visual_layouts_from_manager
521 .into_iter()
522 .map(|(k, v)| {
523 let json = serde_json::to_value(&v).map_err(|e| {
524 tracing::error!("JSON serialization error: {}", e);
525 StatusCode::INTERNAL_SERVER_ERROR
526 })?;
527 let layout: VisualLayout = serde_json::from_value(json).map_err(|e| {
528 tracing::error!("JSON serialization error: {}", e);
529 StatusCode::INTERNAL_SERVER_ERROR
530 })?;
531 Ok((k, layout))
532 })
533 .collect::<Result<HashMap<_, _>, StatusCode>>()?;
534
535 Ok(Json(ImportExportResponse {
536 state_machines,
537 visual_layouts,
538 }))
539}
540
541pub async fn import_state_machines(
543 State(state): State<ManagementState>,
544 Json(request): Json<ImportExportResponse>,
545) -> Result<StatusCode, StatusCode> {
546 let manager = state.state_machine_manager.write().await;
547
548 let manifest_json = serde_json::json!({
551 "manifest_version": "1.0",
552 "name": "imported",
553 "version": "1.0.0",
554 "title": "Imported State Machines",
555 "description": "State machines imported via API",
556 "author": "api",
557 "category": "other",
558 "compatibility": {
559 "min_version": "0.1.0",
560 "max_version": null
561 },
562 "files": [],
563 "state_machines": request.state_machines,
564 "state_machine_graphs": request.visual_layouts
565 });
566
567 let manifest: ScenarioManifest = serde_json::from_value(manifest_json).map_err(|e| {
568 tracing::error!("JSON serialization error: {}", e);
569 StatusCode::INTERNAL_SERVER_ERROR
570 })?;
571
572 if let Err(e) = manager.load_from_manifest(&manifest).await {
573 error!("Failed to import state machines: {}", e);
574 return Err(StatusCode::BAD_REQUEST);
575 }
576
577 Ok(StatusCode::CREATED)
580}
581
582pub fn create_state_machine_routes() -> axum::Router<ManagementState> {
587 use axum::{
588 routing::{delete, get, post, put},
589 Router,
590 };
591
592 Router::new()
593 .route("/", get(list_state_machines))
595 .route("/", post(create_state_machine))
596 .route("/{resource_type}", get(get_state_machine))
597 .route("/{resource_type}", put(create_state_machine))
598 .route("/{resource_type}", delete(delete_state_machine))
599
600 .route("/instances", get(list_instances))
602 .route("/instances", post(create_instance))
603 .route("/instances/{resource_id}", get(get_instance))
604 .route("/instances/{resource_id}/state", get(get_current_state))
605 .route("/instances/{resource_id}/next-states", get(get_next_states))
606 .route("/instances/{resource_id}/transition", post(execute_transition))
607
608 .route("/export", get(export_state_machines))
610 .route("/import", post(import_state_machines))
611}
612
613#[cfg(test)]
614mod tests {
615 use super::*;
616 use std::collections::HashMap;
617
618 fn create_test_state_machine() -> StateMachine {
619 StateMachine::new(
620 "test-resource",
621 vec!["pending".to_string(), "active".to_string()],
622 "pending",
623 )
624 }
625
626 #[test]
628 fn test_state_machine_request_serialize() {
629 let request = StateMachineRequest {
630 state_machine: create_test_state_machine(),
631 visual_layout: None,
632 };
633 let json = serde_json::to_string(&request);
634 assert!(json.is_ok());
635 }
636
637 #[test]
638 fn test_state_machine_request_deserialize() {
639 let json = r#"{"state_machine":{"resource_type":"test","states":["a","b"],"initial_state":"a","transitions":[],"sub_scenarios":[]}}"#;
640 let result: Result<StateMachineRequest, _> = serde_json::from_str(json);
641 assert!(result.is_ok());
642 assert!(result.unwrap().visual_layout.is_none());
643 }
644
645 #[test]
646 fn test_state_machine_request_clone() {
647 let request = StateMachineRequest {
648 state_machine: create_test_state_machine(),
649 visual_layout: None,
650 };
651 let cloned = request.clone();
652 assert!(cloned.visual_layout.is_none());
653 }
654
655 #[test]
656 fn test_state_machine_request_debug() {
657 let request = StateMachineRequest {
658 state_machine: create_test_state_machine(),
659 visual_layout: None,
660 };
661 let debug = format!("{:?}", request);
662 assert!(debug.contains("StateMachineRequest"));
663 }
664
665 #[test]
667 fn test_transition_request_new() {
668 let request = TransitionRequest {
669 resource_id: "order-123".to_string(),
670 to_state: "shipped".to_string(),
671 context: None,
672 };
673 assert_eq!(request.resource_id, "order-123");
674 assert_eq!(request.to_state, "shipped");
675 assert!(request.context.is_none());
676 }
677
678 #[test]
679 fn test_transition_request_with_context() {
680 let mut context = HashMap::new();
681 context.insert("priority".to_string(), serde_json::json!("high"));
682
683 let request = TransitionRequest {
684 resource_id: "order-123".to_string(),
685 to_state: "shipped".to_string(),
686 context: Some(context),
687 };
688 assert!(request.context.is_some());
689 assert_eq!(request.context.unwrap().get("priority"), Some(&serde_json::json!("high")));
690 }
691
692 #[test]
693 fn test_transition_request_serialize() {
694 let request = TransitionRequest {
695 resource_id: "test".to_string(),
696 to_state: "active".to_string(),
697 context: None,
698 };
699 let json = serde_json::to_string(&request).unwrap();
700 assert!(json.contains("resource_id"));
701 assert!(json.contains("to_state"));
702 assert!(!json.contains("context")); }
704
705 #[test]
706 fn test_transition_request_deserialize() {
707 let json = r#"{"resource_id":"test","to_state":"active"}"#;
708 let request: TransitionRequest = serde_json::from_str(json).unwrap();
709 assert_eq!(request.resource_id, "test");
710 assert_eq!(request.to_state, "active");
711 }
712
713 #[test]
715 fn test_create_instance_request_new() {
716 let request = CreateInstanceRequest {
717 resource_id: "order-456".to_string(),
718 resource_type: "order".to_string(),
719 };
720 assert_eq!(request.resource_id, "order-456");
721 assert_eq!(request.resource_type, "order");
722 }
723
724 #[test]
725 fn test_create_instance_request_serialize() {
726 let request = CreateInstanceRequest {
727 resource_id: "test-id".to_string(),
728 resource_type: "test-type".to_string(),
729 };
730 let json = serde_json::to_string(&request).unwrap();
731 assert!(json.contains("test-id"));
732 assert!(json.contains("test-type"));
733 }
734
735 #[test]
736 fn test_create_instance_request_deserialize() {
737 let json = r#"{"resource_id":"id-1","resource_type":"type-1"}"#;
738 let request: CreateInstanceRequest = serde_json::from_str(json).unwrap();
739 assert_eq!(request.resource_id, "id-1");
740 assert_eq!(request.resource_type, "type-1");
741 }
742
743 #[test]
745 fn test_state_machine_response_without_layout() {
746 let response = StateMachineResponse {
747 state_machine: create_test_state_machine(),
748 visual_layout: None,
749 };
750 let json = serde_json::to_string(&response).unwrap();
751 assert!(!json.contains("visual_layout")); }
753
754 #[test]
755 fn test_state_machine_response_clone() {
756 let response = StateMachineResponse {
757 state_machine: create_test_state_machine(),
758 visual_layout: None,
759 };
760 let cloned = response.clone();
761 assert!(cloned.visual_layout.is_none());
762 }
763
764 #[test]
766 fn test_state_machine_list_response_empty() {
767 let response = StateMachineListResponse {
768 state_machines: vec![],
769 total: 0,
770 };
771 assert_eq!(response.total, 0);
772 assert!(response.state_machines.is_empty());
773 }
774
775 #[test]
776 fn test_state_machine_list_response_with_items() {
777 let info = StateMachineInfo {
778 resource_type: "order".to_string(),
779 state_count: 5,
780 transition_count: 10,
781 sub_scenario_count: 2,
782 has_visual_layout: true,
783 };
784 let response = StateMachineListResponse {
785 state_machines: vec![info],
786 total: 1,
787 };
788 assert_eq!(response.total, 1);
789 assert_eq!(response.state_machines[0].resource_type, "order");
790 }
791
792 #[test]
793 fn test_state_machine_list_response_serialize() {
794 let response = StateMachineListResponse {
795 state_machines: vec![],
796 total: 0,
797 };
798 let json = serde_json::to_string(&response).unwrap();
799 assert!(json.contains("state_machines"));
800 assert!(json.contains("total"));
801 }
802
803 #[test]
805 fn test_state_machine_info_new() {
806 let info = StateMachineInfo {
807 resource_type: "user".to_string(),
808 state_count: 3,
809 transition_count: 5,
810 sub_scenario_count: 1,
811 has_visual_layout: false,
812 };
813 assert_eq!(info.resource_type, "user");
814 assert_eq!(info.state_count, 3);
815 assert_eq!(info.transition_count, 5);
816 assert_eq!(info.sub_scenario_count, 1);
817 assert!(!info.has_visual_layout);
818 }
819
820 #[test]
821 fn test_state_machine_info_clone() {
822 let info = StateMachineInfo {
823 resource_type: "product".to_string(),
824 state_count: 4,
825 transition_count: 8,
826 sub_scenario_count: 0,
827 has_visual_layout: true,
828 };
829 let cloned = info.clone();
830 assert_eq!(info.resource_type, cloned.resource_type);
831 assert_eq!(info.state_count, cloned.state_count);
832 }
833
834 #[test]
835 fn test_state_machine_info_serialize() {
836 let info = StateMachineInfo {
837 resource_type: "item".to_string(),
838 state_count: 2,
839 transition_count: 3,
840 sub_scenario_count: 0,
841 has_visual_layout: false,
842 };
843 let json = serde_json::to_string(&info).unwrap();
844 assert!(json.contains("\"resource_type\":\"item\""));
845 assert!(json.contains("\"state_count\":2"));
846 }
847
848 #[test]
850 fn test_state_instance_response_new() {
851 let response = StateInstanceResponse {
852 resource_id: "order-1".to_string(),
853 current_state: "pending".to_string(),
854 resource_type: "order".to_string(),
855 history_count: 0,
856 state_data: HashMap::new(),
857 };
858 assert_eq!(response.resource_id, "order-1");
859 assert_eq!(response.current_state, "pending");
860 assert_eq!(response.history_count, 0);
861 }
862
863 #[test]
864 fn test_state_instance_response_with_data() {
865 let mut state_data = HashMap::new();
866 state_data.insert("total".to_string(), serde_json::json!(100.50));
867
868 let response = StateInstanceResponse {
869 resource_id: "order-2".to_string(),
870 current_state: "confirmed".to_string(),
871 resource_type: "order".to_string(),
872 history_count: 3,
873 state_data,
874 };
875 assert_eq!(response.history_count, 3);
876 assert!(response.state_data.contains_key("total"));
877 }
878
879 #[test]
880 fn test_state_instance_response_serialize() {
881 let response = StateInstanceResponse {
882 resource_id: "test".to_string(),
883 current_state: "active".to_string(),
884 resource_type: "resource".to_string(),
885 history_count: 5,
886 state_data: HashMap::new(),
887 };
888 let json = serde_json::to_string(&response).unwrap();
889 assert!(json.contains("resource_id"));
890 assert!(json.contains("current_state"));
891 assert!(json.contains("history_count"));
892 }
893
894 #[test]
896 fn test_state_instance_list_response_empty() {
897 let response = StateInstanceListResponse {
898 instances: vec![],
899 total: 0,
900 };
901 assert_eq!(response.total, 0);
902 assert!(response.instances.is_empty());
903 }
904
905 #[test]
906 fn test_state_instance_list_response_with_instances() {
907 let instance = StateInstanceResponse {
908 resource_id: "inst-1".to_string(),
909 current_state: "ready".to_string(),
910 resource_type: "service".to_string(),
911 history_count: 2,
912 state_data: HashMap::new(),
913 };
914 let response = StateInstanceListResponse {
915 instances: vec![instance],
916 total: 1,
917 };
918 assert_eq!(response.total, 1);
919 }
920
921 #[test]
923 fn test_next_states_response_empty() {
924 let response = NextStatesResponse {
925 next_states: vec![],
926 };
927 assert!(response.next_states.is_empty());
928 }
929
930 #[test]
931 fn test_next_states_response_with_states() {
932 let response = NextStatesResponse {
933 next_states: vec!["shipped".to_string(), "cancelled".to_string()],
934 };
935 assert_eq!(response.next_states.len(), 2);
936 assert!(response.next_states.contains(&"shipped".to_string()));
937 }
938
939 #[test]
940 fn test_next_states_response_serialize() {
941 let response = NextStatesResponse {
942 next_states: vec!["state1".to_string(), "state2".to_string()],
943 };
944 let json = serde_json::to_string(&response).unwrap();
945 assert!(json.contains("state1"));
946 assert!(json.contains("state2"));
947 }
948
949 #[test]
951 fn test_import_export_response_empty() {
952 let response = ImportExportResponse {
953 state_machines: vec![],
954 visual_layouts: HashMap::new(),
955 };
956 assert!(response.state_machines.is_empty());
957 assert!(response.visual_layouts.is_empty());
958 }
959
960 #[test]
961 fn test_import_export_response_serialize() {
962 let response = ImportExportResponse {
963 state_machines: vec![],
964 visual_layouts: HashMap::new(),
965 };
966 let json = serde_json::to_string(&response).unwrap();
967 assert!(json.contains("state_machines"));
968 assert!(json.contains("visual_layouts"));
969 }
970
971 #[test]
972 fn test_import_export_response_deserialize() {
973 let json = r#"{"state_machines":[],"visual_layouts":{}}"#;
974 let response: ImportExportResponse = serde_json::from_str(json).unwrap();
975 assert!(response.state_machines.is_empty());
976 }
977
978 #[test]
980 fn test_create_state_machine_routes() {
981 let router = create_state_machine_routes();
982 let _ = format!("{:?}", router);
984 }
985}