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 =
174 serde_json::to_value(&state_machine).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
175 let state_machine: StateMachine = serde_json::from_value(state_machine_json)
176 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
177
178 let visual_layout: Option<VisualLayout> = visual_layout
179 .map(|layout| {
180 let layout_json =
181 serde_json::to_value(&layout).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
182 serde_json::from_value(layout_json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
183 })
184 .transpose()
185 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
186
187 Ok(Json(StateMachineResponse {
188 state_machine,
189 visual_layout,
190 }))
191}
192
193pub async fn create_state_machine(
195 State(state): State<ManagementState>,
196 Json(request): Json<StateMachineRequest>,
197) -> Result<Json<StateMachineResponse>, StatusCode> {
198 let manager = state.state_machine_manager.write().await;
199
200 let state_machine_json = serde_json::to_value(&request.state_machine)
205 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
206
207 let mut manifest_json = serde_json::json!({
210 "manifest_version": "1.0",
211 "name": "api",
212 "version": "1.0.0",
213 "title": "API State Machine",
214 "description": "State machine created via API",
215 "author": "api",
216 "category": "other",
217 "compatibility": {
218 "min_version": "0.1.0",
219 "max_version": null
220 },
221 "files": [],
222 "state_machines": [state_machine_json],
223 "state_machine_graphs": {}
224 });
225
226 if let Some(layout) = &request.visual_layout {
227 let layout_json =
228 serde_json::to_value(layout).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
229 manifest_json["state_machine_graphs"][&request.state_machine.resource_type] = layout_json;
230 }
231
232 let manifest: ScenarioManifest =
233 serde_json::from_value(manifest_json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
234
235 if let Some(sm) = manifest.state_machines.first() {
237 if let Err(e) = manager.validate_state_machine(sm) {
238 error!("Invalid state machine: {}", e);
239 return Err(StatusCode::BAD_REQUEST);
240 }
241 }
242
243 if let Err(e) = manager.load_from_manifest(&manifest).await {
244 error!("Failed to load state machine: {}", e);
245 return Err(StatusCode::INTERNAL_SERVER_ERROR);
246 }
247
248 if let Some(ref ws_tx) = state.ws_broadcast {
252 let event = crate::management_ws::MockEvent::state_machine_updated(
253 request.state_machine.resource_type.clone(),
254 request.state_machine.clone(),
255 );
256 let _ = ws_tx.send(event);
257 }
258
259 let state_machine_from_manager = manager
261 .get_state_machine(&request.state_machine.resource_type)
262 .await
263 .ok_or(StatusCode::NOT_FOUND)?;
264 let visual_layout_from_manager =
265 manager.get_visual_layout(&request.state_machine.resource_type).await;
266
267 let state_machine_json = serde_json::to_value(&state_machine_from_manager)
269 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
270 let state_machine: StateMachine = serde_json::from_value(state_machine_json)
271 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
272
273 let visual_layout: Option<VisualLayout> = visual_layout_from_manager
274 .map(|layout| {
275 let layout_json =
276 serde_json::to_value(&layout).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
277 serde_json::from_value(layout_json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
278 })
279 .transpose()
280 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
281
282 Ok(Json(StateMachineResponse {
283 state_machine,
284 visual_layout,
285 }))
286}
287
288pub async fn delete_state_machine(
290 State(state): State<ManagementState>,
291 Path(resource_type): Path<String>,
292) -> Result<StatusCode, StatusCode> {
293 let manager = state.state_machine_manager.write().await;
294
295 let deleted = manager.delete_state_machine(&resource_type).await;
297
298 if !deleted {
299 return Err(StatusCode::NOT_FOUND);
300 }
301
302 if let Some(ref ws_tx) = state.ws_broadcast {
304 let event = crate::management_ws::MockEvent::state_machine_deleted(resource_type);
305 let _ = ws_tx.send(event);
306 }
307
308 Ok(StatusCode::NO_CONTENT)
309}
310
311pub async fn list_instances(
313 State(state): State<ManagementState>,
314) -> Result<Json<StateInstanceListResponse>, StatusCode> {
315 let manager = state.state_machine_manager.read().await;
316
317 let instances = manager.list_instances().await;
318
319 let instance_responses: Vec<StateInstanceResponse> = instances
320 .iter()
321 .map(|i| StateInstanceResponse {
322 resource_id: i.resource_id.clone(),
323 current_state: i.current_state.clone(),
324 resource_type: i.resource_type.clone(),
325 history_count: i.state_history.len(),
326 state_data: i.state_data.clone(),
327 })
328 .collect();
329
330 Ok(Json(StateInstanceListResponse {
331 instances: instance_responses,
332 total: instances.len(),
333 }))
334}
335
336pub async fn get_instance(
338 State(state): State<ManagementState>,
339 Path(resource_id): Path<String>,
340) -> Result<Json<StateInstanceResponse>, StatusCode> {
341 let manager = state.state_machine_manager.read().await;
342
343 let instance = manager.get_instance(&resource_id).await.ok_or(StatusCode::NOT_FOUND)?;
344
345 Ok(Json(StateInstanceResponse {
346 resource_id: instance.resource_id,
347 current_state: instance.current_state,
348 resource_type: instance.resource_type,
349 history_count: instance.state_history.len(),
350 state_data: instance.state_data,
351 }))
352}
353
354pub async fn create_instance(
356 State(state): State<ManagementState>,
357 Json(request): Json<CreateInstanceRequest>,
358) -> Result<Json<StateInstanceResponse>, StatusCode> {
359 let manager = state.state_machine_manager.write().await;
360
361 if let Err(e) = manager.create_instance(&request.resource_id, &request.resource_type).await {
362 error!("Failed to create instance: {}", e);
363 return Err(StatusCode::BAD_REQUEST);
364 }
365
366 let instance = manager
367 .get_instance(&request.resource_id)
368 .await
369 .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
370
371 if let Some(ref ws_tx) = state.ws_broadcast {
373 let event = crate::management_ws::MockEvent::state_instance_created(
374 instance.resource_id.clone(),
375 instance.resource_type.clone(),
376 instance.current_state.clone(),
377 );
378 let _ = ws_tx.send(event);
379 }
380
381 Ok(Json(StateInstanceResponse {
382 resource_id: instance.resource_id,
383 current_state: instance.current_state,
384 resource_type: instance.resource_type,
385 history_count: instance.state_history.len(),
386 state_data: instance.state_data,
387 }))
388}
389
390pub async fn execute_transition(
392 State(state): State<ManagementState>,
393 Json(request): Json<TransitionRequest>,
394) -> Result<Json<StateInstanceResponse>, StatusCode> {
395 let manager = state.state_machine_manager.write().await;
396
397 if let Err(e) = manager
398 .execute_transition(&request.resource_id, &request.to_state, request.context)
399 .await
400 {
401 error!("Failed to execute transition: {}", e);
402 return Err(StatusCode::BAD_REQUEST);
403 }
404
405 let instance = manager.get_instance(&request.resource_id).await.ok_or(StatusCode::NOT_FOUND)?;
406
407 let from_state = instance
409 .state_history
410 .last()
411 .map(|h| h.from_state.clone())
412 .unwrap_or_else(|| instance.current_state.clone());
413
414 if let Some(ref ws_tx) = state.ws_broadcast {
416 let event = crate::management_ws::MockEvent::state_transitioned(
417 instance.resource_id.clone(),
418 instance.resource_type.clone(),
419 from_state,
420 instance.current_state.clone(),
421 instance.state_data.clone(),
422 );
423 let _ = ws_tx.send(event);
424 }
425
426 Ok(Json(StateInstanceResponse {
427 resource_id: instance.resource_id,
428 current_state: instance.current_state,
429 resource_type: instance.resource_type,
430 history_count: instance.state_history.len(),
431 state_data: instance.state_data,
432 }))
433}
434
435pub async fn get_next_states(
437 State(state): State<ManagementState>,
438 Path(resource_id): Path<String>,
439) -> Result<Json<NextStatesResponse>, StatusCode> {
440 let manager = state.state_machine_manager.read().await;
441
442 let next_states =
443 manager.get_next_states(&resource_id).await.map_err(|_| StatusCode::NOT_FOUND)?;
444
445 Ok(Json(NextStatesResponse { next_states }))
446}
447
448pub async fn get_current_state(
450 State(state): State<ManagementState>,
451 Path(resource_id): Path<String>,
452) -> Result<Json<serde_json::Value>, StatusCode> {
453 let manager = state.state_machine_manager.read().await;
454
455 let current_state =
456 manager.get_current_state(&resource_id).await.ok_or(StatusCode::NOT_FOUND)?;
457
458 Ok(Json(serde_json::json!({
459 "resource_id": resource_id,
460 "current_state": current_state
461 })))
462}
463
464pub async fn export_state_machines(
466 State(state): State<ManagementState>,
467) -> Result<Json<ImportExportResponse>, StatusCode> {
468 let manager = state.state_machine_manager.read().await;
469
470 let (state_machines_from_manager, visual_layouts_from_manager) = manager.export_all().await;
472
473 let state_machines: Vec<StateMachine> = state_machines_from_manager
475 .into_iter()
476 .map(|sm| {
477 let json = serde_json::to_value(&sm).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
478 serde_json::from_value(json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
479 })
480 .collect::<Result<Vec<_>, StatusCode>>()?;
481
482 let visual_layouts: HashMap<String, VisualLayout> = visual_layouts_from_manager
483 .into_iter()
484 .map(|(k, v)| {
485 let json = serde_json::to_value(&v).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
486 let layout: VisualLayout =
487 serde_json::from_value(json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
488 Ok((k, layout))
489 })
490 .collect::<Result<HashMap<_, _>, StatusCode>>()?;
491
492 Ok(Json(ImportExportResponse {
493 state_machines,
494 visual_layouts,
495 }))
496}
497
498pub async fn import_state_machines(
500 State(state): State<ManagementState>,
501 Json(request): Json<ImportExportResponse>,
502) -> Result<StatusCode, StatusCode> {
503 let manager = state.state_machine_manager.write().await;
504
505 let manifest_json = serde_json::json!({
508 "manifest_version": "1.0",
509 "name": "imported",
510 "version": "1.0.0",
511 "title": "Imported State Machines",
512 "description": "State machines imported via API",
513 "author": "api",
514 "category": "other",
515 "compatibility": {
516 "min_version": "0.1.0",
517 "max_version": null
518 },
519 "files": [],
520 "state_machines": request.state_machines,
521 "state_machine_graphs": request.visual_layouts
522 });
523
524 let manifest: ScenarioManifest =
525 serde_json::from_value(manifest_json).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
526
527 if let Err(e) = manager.load_from_manifest(&manifest).await {
528 error!("Failed to import state machines: {}", e);
529 return Err(StatusCode::BAD_REQUEST);
530 }
531
532 Ok(StatusCode::CREATED)
535}
536
537pub fn create_state_machine_routes() -> axum::Router<ManagementState> {
542 use axum::{
543 routing::{delete, get, post, put},
544 Router,
545 };
546
547 Router::new()
548 .route("/", get(list_state_machines))
550 .route("/", post(create_state_machine))
551 .route("/{resource_type}", get(get_state_machine))
552 .route("/{resource_type}", put(create_state_machine))
553 .route("/{resource_type}", delete(delete_state_machine))
554
555 .route("/instances", get(list_instances))
557 .route("/instances", post(create_instance))
558 .route("/instances/{resource_id}", get(get_instance))
559 .route("/instances/{resource_id}/state", get(get_current_state))
560 .route("/instances/{resource_id}/next-states", get(get_next_states))
561 .route("/instances/{resource_id}/transition", post(execute_transition))
562
563 .route("/export", get(export_state_machines))
565 .route("/import", post(import_state_machines))
566}
567
568#[cfg(test)]
569mod tests {
570 use super::*;
571 use std::collections::HashMap;
572
573 fn create_test_state_machine() -> StateMachine {
574 StateMachine::new(
575 "test-resource",
576 vec!["pending".to_string(), "active".to_string()],
577 "pending",
578 )
579 }
580
581 #[test]
583 fn test_state_machine_request_serialize() {
584 let request = StateMachineRequest {
585 state_machine: create_test_state_machine(),
586 visual_layout: None,
587 };
588 let json = serde_json::to_string(&request);
589 assert!(json.is_ok());
590 }
591
592 #[test]
593 fn test_state_machine_request_deserialize() {
594 let json = r#"{"state_machine":{"resource_type":"test","states":["a","b"],"initial_state":"a","transitions":[],"sub_scenarios":[]}}"#;
595 let result: Result<StateMachineRequest, _> = serde_json::from_str(json);
596 assert!(result.is_ok());
597 assert!(result.unwrap().visual_layout.is_none());
598 }
599
600 #[test]
601 fn test_state_machine_request_clone() {
602 let request = StateMachineRequest {
603 state_machine: create_test_state_machine(),
604 visual_layout: None,
605 };
606 let cloned = request.clone();
607 assert!(cloned.visual_layout.is_none());
608 }
609
610 #[test]
611 fn test_state_machine_request_debug() {
612 let request = StateMachineRequest {
613 state_machine: create_test_state_machine(),
614 visual_layout: None,
615 };
616 let debug = format!("{:?}", request);
617 assert!(debug.contains("StateMachineRequest"));
618 }
619
620 #[test]
622 fn test_transition_request_new() {
623 let request = TransitionRequest {
624 resource_id: "order-123".to_string(),
625 to_state: "shipped".to_string(),
626 context: None,
627 };
628 assert_eq!(request.resource_id, "order-123");
629 assert_eq!(request.to_state, "shipped");
630 assert!(request.context.is_none());
631 }
632
633 #[test]
634 fn test_transition_request_with_context() {
635 let mut context = HashMap::new();
636 context.insert("priority".to_string(), serde_json::json!("high"));
637
638 let request = TransitionRequest {
639 resource_id: "order-123".to_string(),
640 to_state: "shipped".to_string(),
641 context: Some(context),
642 };
643 assert!(request.context.is_some());
644 assert_eq!(request.context.unwrap().get("priority"), Some(&serde_json::json!("high")));
645 }
646
647 #[test]
648 fn test_transition_request_serialize() {
649 let request = TransitionRequest {
650 resource_id: "test".to_string(),
651 to_state: "active".to_string(),
652 context: None,
653 };
654 let json = serde_json::to_string(&request).unwrap();
655 assert!(json.contains("resource_id"));
656 assert!(json.contains("to_state"));
657 assert!(!json.contains("context")); }
659
660 #[test]
661 fn test_transition_request_deserialize() {
662 let json = r#"{"resource_id":"test","to_state":"active"}"#;
663 let request: TransitionRequest = serde_json::from_str(json).unwrap();
664 assert_eq!(request.resource_id, "test");
665 assert_eq!(request.to_state, "active");
666 }
667
668 #[test]
670 fn test_create_instance_request_new() {
671 let request = CreateInstanceRequest {
672 resource_id: "order-456".to_string(),
673 resource_type: "order".to_string(),
674 };
675 assert_eq!(request.resource_id, "order-456");
676 assert_eq!(request.resource_type, "order");
677 }
678
679 #[test]
680 fn test_create_instance_request_serialize() {
681 let request = CreateInstanceRequest {
682 resource_id: "test-id".to_string(),
683 resource_type: "test-type".to_string(),
684 };
685 let json = serde_json::to_string(&request).unwrap();
686 assert!(json.contains("test-id"));
687 assert!(json.contains("test-type"));
688 }
689
690 #[test]
691 fn test_create_instance_request_deserialize() {
692 let json = r#"{"resource_id":"id-1","resource_type":"type-1"}"#;
693 let request: CreateInstanceRequest = serde_json::from_str(json).unwrap();
694 assert_eq!(request.resource_id, "id-1");
695 assert_eq!(request.resource_type, "type-1");
696 }
697
698 #[test]
700 fn test_state_machine_response_without_layout() {
701 let response = StateMachineResponse {
702 state_machine: create_test_state_machine(),
703 visual_layout: None,
704 };
705 let json = serde_json::to_string(&response).unwrap();
706 assert!(!json.contains("visual_layout")); }
708
709 #[test]
710 fn test_state_machine_response_clone() {
711 let response = StateMachineResponse {
712 state_machine: create_test_state_machine(),
713 visual_layout: None,
714 };
715 let cloned = response.clone();
716 assert!(cloned.visual_layout.is_none());
717 }
718
719 #[test]
721 fn test_state_machine_list_response_empty() {
722 let response = StateMachineListResponse {
723 state_machines: vec![],
724 total: 0,
725 };
726 assert_eq!(response.total, 0);
727 assert!(response.state_machines.is_empty());
728 }
729
730 #[test]
731 fn test_state_machine_list_response_with_items() {
732 let info = StateMachineInfo {
733 resource_type: "order".to_string(),
734 state_count: 5,
735 transition_count: 10,
736 sub_scenario_count: 2,
737 has_visual_layout: true,
738 };
739 let response = StateMachineListResponse {
740 state_machines: vec![info],
741 total: 1,
742 };
743 assert_eq!(response.total, 1);
744 assert_eq!(response.state_machines[0].resource_type, "order");
745 }
746
747 #[test]
748 fn test_state_machine_list_response_serialize() {
749 let response = StateMachineListResponse {
750 state_machines: vec![],
751 total: 0,
752 };
753 let json = serde_json::to_string(&response).unwrap();
754 assert!(json.contains("state_machines"));
755 assert!(json.contains("total"));
756 }
757
758 #[test]
760 fn test_state_machine_info_new() {
761 let info = StateMachineInfo {
762 resource_type: "user".to_string(),
763 state_count: 3,
764 transition_count: 5,
765 sub_scenario_count: 1,
766 has_visual_layout: false,
767 };
768 assert_eq!(info.resource_type, "user");
769 assert_eq!(info.state_count, 3);
770 assert_eq!(info.transition_count, 5);
771 assert_eq!(info.sub_scenario_count, 1);
772 assert!(!info.has_visual_layout);
773 }
774
775 #[test]
776 fn test_state_machine_info_clone() {
777 let info = StateMachineInfo {
778 resource_type: "product".to_string(),
779 state_count: 4,
780 transition_count: 8,
781 sub_scenario_count: 0,
782 has_visual_layout: true,
783 };
784 let cloned = info.clone();
785 assert_eq!(info.resource_type, cloned.resource_type);
786 assert_eq!(info.state_count, cloned.state_count);
787 }
788
789 #[test]
790 fn test_state_machine_info_serialize() {
791 let info = StateMachineInfo {
792 resource_type: "item".to_string(),
793 state_count: 2,
794 transition_count: 3,
795 sub_scenario_count: 0,
796 has_visual_layout: false,
797 };
798 let json = serde_json::to_string(&info).unwrap();
799 assert!(json.contains("\"resource_type\":\"item\""));
800 assert!(json.contains("\"state_count\":2"));
801 }
802
803 #[test]
805 fn test_state_instance_response_new() {
806 let response = StateInstanceResponse {
807 resource_id: "order-1".to_string(),
808 current_state: "pending".to_string(),
809 resource_type: "order".to_string(),
810 history_count: 0,
811 state_data: HashMap::new(),
812 };
813 assert_eq!(response.resource_id, "order-1");
814 assert_eq!(response.current_state, "pending");
815 assert_eq!(response.history_count, 0);
816 }
817
818 #[test]
819 fn test_state_instance_response_with_data() {
820 let mut state_data = HashMap::new();
821 state_data.insert("total".to_string(), serde_json::json!(100.50));
822
823 let response = StateInstanceResponse {
824 resource_id: "order-2".to_string(),
825 current_state: "confirmed".to_string(),
826 resource_type: "order".to_string(),
827 history_count: 3,
828 state_data,
829 };
830 assert_eq!(response.history_count, 3);
831 assert!(response.state_data.contains_key("total"));
832 }
833
834 #[test]
835 fn test_state_instance_response_serialize() {
836 let response = StateInstanceResponse {
837 resource_id: "test".to_string(),
838 current_state: "active".to_string(),
839 resource_type: "resource".to_string(),
840 history_count: 5,
841 state_data: HashMap::new(),
842 };
843 let json = serde_json::to_string(&response).unwrap();
844 assert!(json.contains("resource_id"));
845 assert!(json.contains("current_state"));
846 assert!(json.contains("history_count"));
847 }
848
849 #[test]
851 fn test_state_instance_list_response_empty() {
852 let response = StateInstanceListResponse {
853 instances: vec![],
854 total: 0,
855 };
856 assert_eq!(response.total, 0);
857 assert!(response.instances.is_empty());
858 }
859
860 #[test]
861 fn test_state_instance_list_response_with_instances() {
862 let instance = StateInstanceResponse {
863 resource_id: "inst-1".to_string(),
864 current_state: "ready".to_string(),
865 resource_type: "service".to_string(),
866 history_count: 2,
867 state_data: HashMap::new(),
868 };
869 let response = StateInstanceListResponse {
870 instances: vec![instance],
871 total: 1,
872 };
873 assert_eq!(response.total, 1);
874 }
875
876 #[test]
878 fn test_next_states_response_empty() {
879 let response = NextStatesResponse {
880 next_states: vec![],
881 };
882 assert!(response.next_states.is_empty());
883 }
884
885 #[test]
886 fn test_next_states_response_with_states() {
887 let response = NextStatesResponse {
888 next_states: vec!["shipped".to_string(), "cancelled".to_string()],
889 };
890 assert_eq!(response.next_states.len(), 2);
891 assert!(response.next_states.contains(&"shipped".to_string()));
892 }
893
894 #[test]
895 fn test_next_states_response_serialize() {
896 let response = NextStatesResponse {
897 next_states: vec!["state1".to_string(), "state2".to_string()],
898 };
899 let json = serde_json::to_string(&response).unwrap();
900 assert!(json.contains("state1"));
901 assert!(json.contains("state2"));
902 }
903
904 #[test]
906 fn test_import_export_response_empty() {
907 let response = ImportExportResponse {
908 state_machines: vec![],
909 visual_layouts: HashMap::new(),
910 };
911 assert!(response.state_machines.is_empty());
912 assert!(response.visual_layouts.is_empty());
913 }
914
915 #[test]
916 fn test_import_export_response_serialize() {
917 let response = ImportExportResponse {
918 state_machines: vec![],
919 visual_layouts: HashMap::new(),
920 };
921 let json = serde_json::to_string(&response).unwrap();
922 assert!(json.contains("state_machines"));
923 assert!(json.contains("visual_layouts"));
924 }
925
926 #[test]
927 fn test_import_export_response_deserialize() {
928 let json = r#"{"state_machines":[],"visual_layouts":{}}"#;
929 let response: ImportExportResponse = serde_json::from_str(json).unwrap();
930 assert!(response.state_machines.is_empty());
931 }
932
933 #[test]
935 fn test_create_state_machine_routes() {
936 let router = create_state_machine_routes();
937 let _ = format!("{:?}", router);
939 }
940}