1use axum::{
7 extract::Request,
8 http::{HeaderMap, StatusCode},
9 middleware::Next,
10 response::Response,
11};
12use mockforge_collab::models::UserRole;
13use mockforge_collab::permissions::{Permission, RolePermissions};
14use serde::{Deserialize, Serialize};
15
16fn is_truthy_env(name: &str) -> bool {
17 matches!(
18 std::env::var(name).ok().as_deref().map(str::to_ascii_lowercase).as_deref(),
19 Some("1") | Some("true") | Some("yes") | Some("on")
20 )
21}
22
23fn is_development_environment() -> bool {
24 if cfg!(test) {
25 return true;
26 }
27
28 matches!(
29 std::env::var("ENVIRONMENT")
30 .unwrap_or_else(|_| "production".to_string())
31 .to_ascii_lowercase()
32 .as_str(),
33 "development" | "dev" | "local"
34 )
35}
36
37fn is_dev_auth_enabled() -> bool {
38 cfg!(test) || is_truthy_env("MOCKFORGE_ENABLE_DEV_AUTH")
39}
40
41fn allow_unauthenticated_dev_access() -> bool {
42 is_development_environment()
43 && is_dev_auth_enabled()
44 && (is_truthy_env("MOCKFORGE_ALLOW_UNAUTHENTICATED")
45 || is_truthy_env("MOCKFORGE_ALLOW_UNAUTHENTICATED_DEV"))
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct UserContext {
51 pub user_id: String,
53 pub username: String,
55 pub role: UserRole,
57 pub email: Option<String>,
59}
60
61pub struct AdminActionPermissions;
63
64impl AdminActionPermissions {
65 pub fn get_required_permissions(action: &str) -> Vec<Permission> {
68 match action {
69 "update_latency"
71 | "update_faults"
72 | "update_proxy"
73 | "update_traffic_shaping"
74 | "update_validation" => {
75 vec![Permission::ManageSettings]
76 }
77
78 "restart_servers" | "shutdown_servers" => {
80 vec![Permission::ManageSettings]
81 }
82
83 "clear_logs" | "export_logs" => {
85 vec![Permission::ManageSettings]
86 }
87
88 "create_fixture" => {
90 vec![Permission::MockCreate]
91 }
92 "update_fixture" | "rename_fixture" | "move_fixture" => {
93 vec![Permission::MockUpdate]
94 }
95 "delete_fixture" | "delete_fixtures_bulk" => {
96 vec![Permission::MockDelete]
97 }
98
99 "enable_route" | "disable_route" | "create_route" | "update_route" | "delete_route" => {
101 vec![Permission::MockUpdate]
102 }
103
104 "enable_service" | "disable_service" | "update_service_config" => {
106 vec![Permission::ManageSettings]
107 }
108
109 "create_user" | "update_user" | "delete_user" | "change_role" => {
111 vec![Permission::ChangeRoles]
112 }
113
114 "grant_permission" | "revoke_permission" => {
116 vec![Permission::ChangeRoles]
117 }
118
119 "create_api_key" | "delete_api_key" | "rotate_api_key" => {
121 vec![Permission::ManageSettings]
122 }
123
124 "update_security_policy" => {
126 vec![Permission::ManageSettings]
127 }
128
129 "get_dashboard" | "get_logs" | "get_metrics" | "get_routes" | "get_fixtures"
131 | "get_config" => {
132 vec![Permission::WorkspaceRead, Permission::MockRead]
133 }
134
135 "get_audit_logs" | "get_audit_stats" => {
137 vec![Permission::ManageSettings]
138 }
139
140 "modify_scenario_chaos_rules" | "update_scenario_chaos" => {
143 vec![Permission::ScenarioModifyChaosRules]
144 }
145 "modify_scenario_reality_defaults" | "update_scenario_reality" => {
147 vec![Permission::ScenarioModifyRealityDefaults]
148 }
149 "promote_scenario" | "create_scenario_promotion" => {
151 vec![Permission::ScenarioPromote]
152 }
153 "approve_scenario_promotion" | "reject_scenario_promotion" => {
155 vec![Permission::ScenarioApprove]
156 }
157 "modify_scenario_drift_budget" | "update_scenario_drift_budget" => {
159 vec![Permission::ScenarioModifyDriftBudgets]
160 }
161
162 _ => {
164 vec![Permission::ManageSettings]
165 }
166 }
167 }
168}
169
170pub fn extract_user_context(headers: &HeaderMap) -> Option<UserContext> {
175 if let Some(auth_header) = headers.get("authorization") {
177 if let Ok(auth_str) = auth_header.to_str() {
178 if let Some(token) = auth_str.strip_prefix("Bearer ") {
179 if let Some(user) = parse_jwt_token(token) {
180 return Some(user);
181 }
182 }
183 }
184 }
185
186 if is_development_environment() && is_dev_auth_enabled() {
188 let user_id = headers.get("x-user-id")?.to_str().ok()?.to_string();
189 let username = headers.get("x-username")?.to_str().ok()?.to_string();
190 let role_str = headers.get("x-user-role")?.to_str().ok()?;
191 let role = parse_role(role_str)?;
192
193 return Some(UserContext {
194 user_id,
195 username,
196 role,
197 email: headers.get("x-user-email").and_then(|h| h.to_str().ok()).map(|s| s.to_string()),
198 });
199 }
200
201 None
202}
203
204fn parse_jwt_token(token: &str) -> Option<UserContext> {
207 use crate::auth::{claims_to_user_context, validate_token};
208
209 if let Ok(claims) = validate_token(token) {
211 return Some(claims_to_user_context(&claims));
212 }
213
214 if is_development_environment() && is_dev_auth_enabled() && token.starts_with("mock.") {
216 let parts: Vec<&str> = token.split('.').collect();
217 if parts.len() >= 3 {
218 let payload_part = parts[2];
220 let base64_str = payload_part.replace('-', "+").replace('_', "/");
222 let padding = (4 - (base64_str.len() % 4)) % 4;
224 let padded = format!("{}{}", base64_str, "=".repeat(padding));
225
226 use base64::{engine::general_purpose, Engine as _};
228 if let Ok(decoded) = general_purpose::STANDARD.decode(&padded) {
229 if let Ok(payload_str) = String::from_utf8(decoded) {
230 return parse_jwt_payload(&payload_str);
231 }
232 }
233 }
234 }
235
236 None
237}
238
239fn parse_jwt_payload(payload_str: &str) -> Option<UserContext> {
241 if let Ok(payload) = serde_json::from_str::<serde_json::Value>(payload_str) {
242 let user_id = payload.get("sub")?.as_str()?.to_string();
243 let username = payload.get("username")?.as_str()?.to_string();
244 let role_str = payload.get("role")?.as_str()?;
245 let role = parse_role(role_str)?;
246
247 return Some(UserContext {
248 user_id,
249 username,
250 role,
251 email: payload.get("email").and_then(|v| v.as_str()).map(|s| s.to_string()),
252 });
253 }
254 None
255}
256
257fn parse_role(role_str: &str) -> Option<UserRole> {
259 match role_str.to_lowercase().as_str() {
260 "admin" => Some(UserRole::Admin),
261 "editor" => Some(UserRole::Editor),
262 "viewer" => Some(UserRole::Viewer),
263 _ => None,
264 }
265}
266
267pub fn get_default_user_context() -> Option<UserContext> {
270 if allow_unauthenticated_dev_access() {
273 Some(UserContext {
274 user_id: "system".to_string(),
275 username: "system".to_string(),
276 role: UserRole::Admin,
277 email: None,
278 })
279 } else {
280 None
281 }
282}
283
284pub async fn rbac_middleware(mut request: Request, next: Next) -> Result<Response, StatusCode> {
286 let path = request.uri().path();
288 let method = request.method().as_str();
289 let headers = request.headers();
290
291 let is_public_route = path == "/"
293 || path.starts_with("/assets/")
294 || path == "/__mockforge/auth/login"
295 || path == "/__mockforge/auth/refresh"
296 || path == "/__mockforge/auth/logout"
297 || path == "/__mockforge/health"
298 || path.starts_with("/mockforge-")
299 || path == "/manifest.json"
300 || path == "/sw.js"
301 || path == "/api-docs";
302
303 if is_public_route {
304 return Ok(next.run(request).await);
305 }
306
307 let action_name = match (method, path) {
309 (_, p) if p.contains("/config/latency") => "update_latency",
310 (_, p) if p.contains("/config/faults") => "update_faults",
311 (_, p) if p.contains("/config/proxy") => "update_proxy",
312 (_, p) if p.contains("/config/traffic-shaping") => "update_traffic_shaping",
313 ("DELETE", p) if p.contains("/logs") => "clear_logs",
314 ("POST", p) if p.contains("/restart") => "restart_servers",
315 ("DELETE", p) if p.contains("/fixtures") => "delete_fixture",
316 ("POST", p) if p.contains("/fixtures") && p.contains("/rename") => "rename_fixture",
317 ("POST", p) if p.contains("/fixtures") && p.contains("/move") => "move_fixture",
318 ("GET", p) if p.contains("/audit/logs") => "get_audit_logs",
319 ("GET", p) if p.contains("/audit/stats") => "get_audit_stats",
320 ("GET", _) => "read", _ => "unknown",
322 };
323
324 let user_context = extract_user_context(headers).or_else(get_default_user_context);
326
327 let user_context = match user_context {
329 Some(ctx) => ctx,
330 None => {
331 if allow_unauthenticated_dev_access() {
334 get_default_user_context().unwrap_or_else(|| UserContext {
336 user_id: "system".to_string(),
337 username: "system".to_string(),
338 role: UserRole::Admin,
339 email: None,
340 })
341 } else {
342 return Err(StatusCode::UNAUTHORIZED);
343 }
344 }
345 };
346
347 let required_permissions = AdminActionPermissions::get_required_permissions(action_name);
349
350 let has_permission = required_permissions
352 .iter()
353 .any(|&perm| RolePermissions::has_permission(user_context.role, perm));
354
355 if !has_permission {
356 tracing::warn!(
358 user_id = %user_context.user_id,
359 username = %user_context.username,
360 role = ?user_context.role,
361 action = %action_name,
362 "Authorization denied: User does not have required permissions"
363 );
364
365 return Err(StatusCode::FORBIDDEN);
366 }
367
368 request.extensions_mut().insert(user_context);
371
372 Ok(next.run(request).await)
373}
374
375pub fn get_user_context_from_request(request: &Request) -> Option<UserContext> {
377 request.extensions().get::<UserContext>().cloned()
378}
379
380pub fn get_user_context_from_state<T>(_state: &T) -> Option<UserContext>
382where
383 T: std::any::Any,
384{
385 None
387}
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392 use axum::http::{HeaderValue, Method};
393
394 #[test]
395 fn test_parse_role_valid() {
396 assert_eq!(parse_role("admin"), Some(UserRole::Admin));
397 assert_eq!(parse_role("Admin"), Some(UserRole::Admin));
398 assert_eq!(parse_role("ADMIN"), Some(UserRole::Admin));
399 assert_eq!(parse_role("editor"), Some(UserRole::Editor));
400 assert_eq!(parse_role("viewer"), Some(UserRole::Viewer));
401 }
402
403 #[test]
404 fn test_parse_role_invalid() {
405 assert_eq!(parse_role("invalid"), None);
406 assert_eq!(parse_role(""), None);
407 assert_eq!(parse_role("super_admin"), None);
408 }
409
410 #[test]
411 fn test_user_context_serialization() {
412 let context = UserContext {
413 user_id: "user123".to_string(),
414 username: "testuser".to_string(),
415 role: UserRole::Editor,
416 email: Some("test@example.com".to_string()),
417 };
418
419 let serialized = serde_json::to_string(&context).unwrap();
420 let deserialized: UserContext = serde_json::from_str(&serialized).unwrap();
421
422 assert_eq!(deserialized.user_id, context.user_id);
423 assert_eq!(deserialized.username, context.username);
424 assert_eq!(deserialized.role, context.role);
425 assert_eq!(deserialized.email, context.email);
426 }
427
428 #[test]
429 fn test_extract_user_context_from_headers() {
430 let mut headers = HeaderMap::new();
431 headers.insert("x-user-id", HeaderValue::from_static("user123"));
432 headers.insert("x-username", HeaderValue::from_static("testuser"));
433 headers.insert("x-user-role", HeaderValue::from_static("admin"));
434 headers.insert("x-user-email", HeaderValue::from_static("test@example.com"));
435
436 let context = extract_user_context(&headers).unwrap();
437 assert_eq!(context.user_id, "user123");
438 assert_eq!(context.username, "testuser");
439 assert_eq!(context.role, UserRole::Admin);
440 assert_eq!(context.email, Some("test@example.com".to_string()));
441 }
442
443 #[test]
444 fn test_extract_user_context_missing_headers() {
445 let headers = HeaderMap::new();
446 let context = extract_user_context(&headers);
447 assert!(context.is_none());
448 }
449
450 #[test]
451 fn test_extract_user_context_partial_headers() {
452 let mut headers = HeaderMap::new();
453 headers.insert("x-user-id", HeaderValue::from_static("user123"));
454 let context = extract_user_context(&headers);
457 assert!(context.is_none());
458 }
459
460 #[test]
461 fn test_extract_user_context_without_email() {
462 let mut headers = HeaderMap::new();
463 headers.insert("x-user-id", HeaderValue::from_static("user123"));
464 headers.insert("x-username", HeaderValue::from_static("testuser"));
465 headers.insert("x-user-role", HeaderValue::from_static("viewer"));
466
467 let context = extract_user_context(&headers).unwrap();
468 assert_eq!(context.user_id, "user123");
469 assert_eq!(context.username, "testuser");
470 assert_eq!(context.role, UserRole::Viewer);
471 assert_eq!(context.email, None);
472 }
473
474 #[test]
475 fn test_parse_jwt_payload() {
476 let payload_json = r#"{
477 "sub": "user456",
478 "username": "jwtuser",
479 "role": "editor",
480 "email": "jwt@example.com"
481 }"#;
482
483 let context = parse_jwt_payload(payload_json).unwrap();
484 assert_eq!(context.user_id, "user456");
485 assert_eq!(context.username, "jwtuser");
486 assert_eq!(context.role, UserRole::Editor);
487 assert_eq!(context.email, Some("jwt@example.com".to_string()));
488 }
489
490 #[test]
491 fn test_parse_jwt_payload_without_email() {
492 let payload_json = r#"{
493 "sub": "user456",
494 "username": "jwtuser",
495 "role": "viewer"
496 }"#;
497
498 let context = parse_jwt_payload(payload_json).unwrap();
499 assert_eq!(context.email, None);
500 }
501
502 #[test]
503 fn test_parse_jwt_payload_invalid_json() {
504 let payload_json = "invalid json";
505 let context = parse_jwt_payload(payload_json);
506 assert!(context.is_none());
507 }
508
509 #[test]
510 fn test_parse_jwt_payload_missing_fields() {
511 let payload_json = r#"{"sub": "user456"}"#;
512 let context = parse_jwt_payload(payload_json);
513 assert!(context.is_none());
514 }
515
516 #[test]
517 fn test_parse_jwt_payload_invalid_role() {
518 let payload_json = r#"{
519 "sub": "user456",
520 "username": "jwtuser",
521 "role": "invalid_role"
522 }"#;
523
524 let context = parse_jwt_payload(payload_json);
525 assert!(context.is_none());
526 }
527
528 #[test]
529 fn test_admin_action_permissions_config_changes() {
530 let perms = AdminActionPermissions::get_required_permissions("update_latency");
531 assert_eq!(perms, vec![Permission::ManageSettings]);
532
533 let perms = AdminActionPermissions::get_required_permissions("update_faults");
534 assert_eq!(perms, vec![Permission::ManageSettings]);
535
536 let perms = AdminActionPermissions::get_required_permissions("update_proxy");
537 assert_eq!(perms, vec![Permission::ManageSettings]);
538 }
539
540 #[test]
541 fn test_admin_action_permissions_fixture_management() {
542 let perms = AdminActionPermissions::get_required_permissions("create_fixture");
543 assert_eq!(perms, vec![Permission::MockCreate]);
544
545 let perms = AdminActionPermissions::get_required_permissions("update_fixture");
546 assert_eq!(perms, vec![Permission::MockUpdate]);
547
548 let perms = AdminActionPermissions::get_required_permissions("delete_fixture");
549 assert_eq!(perms, vec![Permission::MockDelete]);
550 }
551
552 #[test]
553 fn test_admin_action_permissions_user_management() {
554 let perms = AdminActionPermissions::get_required_permissions("create_user");
555 assert_eq!(perms, vec![Permission::ChangeRoles]);
556
557 let perms = AdminActionPermissions::get_required_permissions("change_role");
558 assert_eq!(perms, vec![Permission::ChangeRoles]);
559 }
560
561 #[test]
562 fn test_admin_action_permissions_read_operations() {
563 let perms = AdminActionPermissions::get_required_permissions("get_dashboard");
564 assert_eq!(perms, vec![Permission::WorkspaceRead, Permission::MockRead]);
565
566 let perms = AdminActionPermissions::get_required_permissions("get_logs");
567 assert_eq!(perms, vec![Permission::WorkspaceRead, Permission::MockRead]);
568 }
569
570 #[test]
571 fn test_admin_action_permissions_scenario_operations() {
572 let perms = AdminActionPermissions::get_required_permissions("modify_scenario_chaos_rules");
573 assert_eq!(perms, vec![Permission::ScenarioModifyChaosRules]);
574
575 let perms =
576 AdminActionPermissions::get_required_permissions("modify_scenario_reality_defaults");
577 assert_eq!(perms, vec![Permission::ScenarioModifyRealityDefaults]);
578
579 let perms = AdminActionPermissions::get_required_permissions("promote_scenario");
580 assert_eq!(perms, vec![Permission::ScenarioPromote]);
581
582 let perms = AdminActionPermissions::get_required_permissions("approve_scenario_promotion");
583 assert_eq!(perms, vec![Permission::ScenarioApprove]);
584 }
585
586 #[test]
587 fn test_admin_action_permissions_unknown_action() {
588 let perms = AdminActionPermissions::get_required_permissions("unknown_action");
589 assert_eq!(perms, vec![Permission::ManageSettings]);
590 }
591
592 #[test]
593 fn test_get_default_user_context_without_env_var() {
594 std::env::remove_var("MOCKFORGE_ALLOW_UNAUTHENTICATED");
595 let context = get_default_user_context();
596 assert!(context.is_none());
597 }
598
599 #[test]
600 fn test_get_default_user_context_with_env_var() {
601 std::env::set_var("MOCKFORGE_ALLOW_UNAUTHENTICATED", "1");
602 let context = get_default_user_context();
603 assert!(context.is_some());
604
605 let context = context.unwrap();
606 assert_eq!(context.user_id, "system");
607 assert_eq!(context.username, "system");
608 assert_eq!(context.role, UserRole::Admin);
609
610 std::env::remove_var("MOCKFORGE_ALLOW_UNAUTHENTICATED");
611 }
612
613 #[test]
614 fn test_all_permission_actions_covered() {
615 let actions = vec![
617 "update_latency",
618 "update_faults",
619 "restart_servers",
620 "create_fixture",
621 "update_fixture",
622 "delete_fixture",
623 "enable_route",
624 "create_user",
625 "grant_permission",
626 "create_api_key",
627 "get_dashboard",
628 "get_audit_logs",
629 "modify_scenario_chaos_rules",
630 "promote_scenario",
631 "approve_scenario_promotion",
632 ];
633
634 for action in actions {
635 let perms = AdminActionPermissions::get_required_permissions(action);
636 assert!(!perms.is_empty(), "Action {} should have permissions", action);
637 }
638 }
639
640 #[test]
641 fn test_role_permissions_admin_has_all() {
642 assert!(RolePermissions::has_permission(UserRole::Admin, Permission::ManageSettings));
644 assert!(RolePermissions::has_permission(UserRole::Admin, Permission::MockCreate));
645 assert!(RolePermissions::has_permission(UserRole::Admin, Permission::MockUpdate));
646 assert!(RolePermissions::has_permission(UserRole::Admin, Permission::MockDelete));
647 assert!(RolePermissions::has_permission(UserRole::Admin, Permission::WorkspaceRead));
648 assert!(RolePermissions::has_permission(UserRole::Admin, Permission::ChangeRoles));
649 }
650
651 #[test]
652 fn test_role_permissions_editor_limited() {
653 assert!(!RolePermissions::has_permission(UserRole::Editor, Permission::ManageSettings));
655 assert!(RolePermissions::has_permission(UserRole::Editor, Permission::MockUpdate));
656 assert!(!RolePermissions::has_permission(UserRole::Editor, Permission::ChangeRoles));
657 }
658
659 #[test]
660 fn test_role_permissions_viewer_readonly() {
661 assert!(!RolePermissions::has_permission(UserRole::Viewer, Permission::ManageSettings));
663 assert!(!RolePermissions::has_permission(UserRole::Viewer, Permission::MockCreate));
664 assert!(!RolePermissions::has_permission(UserRole::Viewer, Permission::MockUpdate));
665 assert!(!RolePermissions::has_permission(UserRole::Viewer, Permission::MockDelete));
666 assert!(RolePermissions::has_permission(UserRole::Viewer, Permission::WorkspaceRead));
667 assert!(RolePermissions::has_permission(UserRole::Viewer, Permission::MockRead));
668 }
669
670 #[test]
671 fn test_scenario_permissions() {
672 assert!(RolePermissions::has_permission(
674 UserRole::Admin,
675 Permission::ScenarioModifyChaosRules
676 ));
677 assert!(RolePermissions::has_permission(
678 UserRole::Admin,
679 Permission::ScenarioModifyRealityDefaults
680 ));
681 assert!(RolePermissions::has_permission(UserRole::Admin, Permission::ScenarioPromote));
682 assert!(RolePermissions::has_permission(UserRole::Admin, Permission::ScenarioApprove));
683 }
684
685 #[tokio::test]
686 async fn test_rbac_middleware_public_routes() {
687 use axum::routing::get;
688 use axum::{body::Body, middleware::from_fn, Router};
689 use tower::ServiceExt;
690
691 async fn handler() -> &'static str {
692 "OK"
693 }
694
695 let app = Router::new().route("/", get(handler)).layer(from_fn(rbac_middleware));
696
697 let request = axum::http::Request::builder()
698 .uri("/")
699 .method(Method::GET)
700 .body(Body::empty())
701 .unwrap();
702
703 let response = app.oneshot(request).await.unwrap();
704 assert_eq!(response.status(), StatusCode::OK);
705 }
706
707 #[tokio::test]
708 async fn test_rbac_middleware_health_route() {
709 use axum::routing::get;
710 use axum::{body::Body, middleware::from_fn, Router};
711 use tower::ServiceExt;
712
713 async fn handler() -> &'static str {
714 "OK"
715 }
716
717 let app = Router::new()
718 .route("/__mockforge/health", get(handler))
719 .layer(from_fn(rbac_middleware));
720
721 let request = axum::http::Request::builder()
722 .uri("/__mockforge/health")
723 .method(Method::GET)
724 .body(Body::empty())
725 .unwrap();
726
727 let response = app.oneshot(request).await.unwrap();
728 assert_eq!(response.status(), StatusCode::OK);
729 }
730
731 #[tokio::test]
732 async fn test_rbac_middleware_assets_route() {
733 use axum::routing::get;
734 use axum::{body::Body, middleware::from_fn, Router};
735 use tower::ServiceExt;
736
737 async fn handler() -> &'static str {
738 "OK"
739 }
740
741 let app = Router::new()
742 .route("/assets/style.css", get(handler))
743 .layer(from_fn(rbac_middleware));
744
745 let request = axum::http::Request::builder()
746 .uri("/assets/style.css")
747 .method(Method::GET)
748 .body(Body::empty())
749 .unwrap();
750
751 let response = app.oneshot(request).await.unwrap();
752 assert_eq!(response.status(), StatusCode::OK);
753 }
754
755 #[tokio::test]
756 async fn test_rbac_middleware_with_valid_headers() {
757 use axum::routing::get;
758 use axum::{body::Body, middleware::from_fn, Router};
759 use tower::ServiceExt;
760
761 async fn handler() -> &'static str {
762 "OK"
763 }
764
765 let app = Router::new().route("/api/test", get(handler)).layer(from_fn(rbac_middleware));
766
767 let request = axum::http::Request::builder()
768 .uri("/api/test")
769 .method(Method::GET)
770 .header("x-user-id", "user123")
771 .header("x-username", "testuser")
772 .header("x-user-role", "admin")
773 .body(Body::empty())
774 .unwrap();
775
776 let response = app.oneshot(request).await.unwrap();
777 assert_eq!(response.status(), StatusCode::OK);
778 }
779
780 #[test]
781 fn test_action_name_mapping() {
782 let test_cases = vec![
784 ("/config/latency", "update_latency"),
785 ("/config/faults", "update_faults"),
786 ("/config/proxy", "update_proxy"),
787 ("/logs", "clear_logs"), ("/fixtures/test", "delete_fixture"), ("/audit/logs", "get_audit_logs"), ];
791
792 for (_path, expected_action) in test_cases {
795 assert!(!expected_action.is_empty());
796 }
797 }
798
799 #[test]
800 fn test_user_context_clone() {
801 let context = UserContext {
802 user_id: "user123".to_string(),
803 username: "testuser".to_string(),
804 role: UserRole::Editor,
805 email: Some("test@example.com".to_string()),
806 };
807
808 let cloned = context.clone();
809 assert_eq!(cloned.user_id, context.user_id);
810 assert_eq!(cloned.username, context.username);
811 assert_eq!(cloned.role, context.role);
812 assert_eq!(cloned.email, context.email);
813 }
814
815 #[test]
816 fn test_user_context_debug() {
817 let context = UserContext {
818 user_id: "user123".to_string(),
819 username: "testuser".to_string(),
820 role: UserRole::Viewer,
821 email: None,
822 };
823
824 let debug_str = format!("{:?}", context);
825 assert!(debug_str.contains("user123"));
826 assert!(debug_str.contains("testuser"));
827 }
828}