mockforge_federation/
router.rs1use crate::federation::Federation;
6use crate::service::ServiceBoundary;
7#[cfg(test)]
8use chrono::Utc;
9use std::sync::Arc;
10use tracing::debug;
11use uuid::Uuid;
12
13#[derive(Debug, Clone)]
15pub struct RoutingResult {
16 pub workspace_id: Uuid,
18 pub service: Arc<ServiceBoundary>,
20 pub service_path: String,
22}
23
24pub struct FederationRouter {
29 federation: Arc<Federation>,
31}
32
33impl FederationRouter {
34 #[must_use]
36 pub const fn new(federation: Arc<Federation>) -> Self {
37 Self { federation }
38 }
39
40 pub fn route(&self, path: &str) -> Option<RoutingResult> {
44 debug!(path = %path, "Routing request in federation");
45
46 let service = self.federation.find_service_by_path(path)?;
47
48 let service_path = service.extract_service_path(path)?;
49
50 debug!(
51 path = %path,
52 service = %service.name,
53 workspace_id = %service.workspace_id,
54 service_path = %service_path,
55 "Routed request to service"
56 );
57
58 Some(RoutingResult {
59 workspace_id: service.workspace_id,
60 service: Arc::new(service.clone()),
61 service_path,
62 })
63 }
64
65 #[must_use]
67 pub fn services(&self) -> &[ServiceBoundary] {
68 &self.federation.services
69 }
70
71 #[must_use]
73 pub fn federation_id(&self) -> Uuid {
74 self.federation.id
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use crate::service::ServiceRealityLevel;
82
83 fn create_test_federation() -> Arc<Federation> {
84 Arc::new(Federation {
85 id: Uuid::new_v4(),
86 name: "test".to_string(),
87 description: String::new(),
88 org_id: Uuid::new_v4(),
89 services: vec![
90 ServiceBoundary::new(
91 "auth".to_string(),
92 Uuid::new_v4(),
93 "/auth".to_string(),
94 ServiceRealityLevel::Real,
95 ),
96 ServiceBoundary::new(
97 "payments".to_string(),
98 Uuid::new_v4(),
99 "/payments".to_string(),
100 ServiceRealityLevel::MockV3,
101 ),
102 ],
103 created_at: Utc::now(),
104 updated_at: Utc::now(),
105 })
106 }
107
108 #[test]
109 fn test_router() {
110 let federation = create_test_federation();
111 let router = FederationRouter::new(federation);
112 let result = router.route("/auth/login");
113
114 assert!(result.is_some());
115 let routing = result.unwrap();
116 assert_eq!(routing.service_path, "/login");
117 }
118
119 #[test]
120 fn test_router_new() {
121 let federation = create_test_federation();
122 let router = FederationRouter::new(federation.clone());
123 assert_eq!(router.federation_id(), federation.id);
124 }
125
126 #[test]
127 fn test_router_route_exact_path() {
128 let federation = create_test_federation();
129 let router = FederationRouter::new(federation);
130
131 let result = router.route("/auth").unwrap();
132 assert_eq!(result.service_path, "/");
133 assert_eq!(result.service.name, "auth");
134 }
135
136 #[test]
137 fn test_router_route_nested_path() {
138 let federation = create_test_federation();
139 let router = FederationRouter::new(federation);
140
141 let result = router.route("/payments/process/order/123").unwrap();
142 assert_eq!(result.service_path, "/process/order/123");
143 assert_eq!(result.service.name, "payments");
144 }
145
146 #[test]
147 fn test_router_route_no_match() {
148 let federation = create_test_federation();
149 let router = FederationRouter::new(federation);
150
151 assert!(router.route("/unknown").is_none());
152 assert!(router.route("/api/users").is_none());
153 assert!(router.route("").is_none());
154 }
155
156 #[test]
157 fn test_router_services() {
158 let federation = create_test_federation();
159 let router = FederationRouter::new(federation);
160
161 let services = router.services();
162 assert_eq!(services.len(), 2);
163 assert!(services.iter().any(|s| s.name == "auth"));
164 assert!(services.iter().any(|s| s.name == "payments"));
165 }
166
167 #[test]
168 fn test_router_federation_id() {
169 let federation = create_test_federation();
170 let expected_id = federation.id;
171 let router = FederationRouter::new(federation);
172
173 assert_eq!(router.federation_id(), expected_id);
174 }
175
176 #[test]
177 fn test_routing_result_contains_workspace_id() {
178 let federation = create_test_federation();
179 let expected_workspace_id = federation.services[0].workspace_id;
180 let router = FederationRouter::new(federation);
181
182 let result = router.route("/auth/login").unwrap();
183 assert_eq!(result.workspace_id, expected_workspace_id);
184 }
185
186 #[test]
187 fn test_routing_result_debug() {
188 let federation = create_test_federation();
189 let router = FederationRouter::new(federation);
190
191 let result = router.route("/auth/login").unwrap();
192 let debug = format!("{result:?}");
193 assert!(debug.contains("RoutingResult"));
194 assert!(debug.contains("service_path"));
195 }
196
197 #[test]
198 fn test_routing_result_clone() {
199 let federation = create_test_federation();
200 let router = FederationRouter::new(federation);
201
202 let result = router.route("/auth/login").unwrap();
203 let cloned = result.clone();
204
205 assert_eq!(result.workspace_id, cloned.workspace_id);
206 assert_eq!(result.service_path, cloned.service_path);
207 assert_eq!(result.service.name, cloned.service.name);
208 }
209
210 #[test]
211 fn test_router_routes_to_different_services() {
212 let federation = create_test_federation();
213 let router = FederationRouter::new(federation);
214
215 let auth_result = router.route("/auth/login").unwrap();
216 let payment_result = router.route("/payments/process").unwrap();
217
218 assert_eq!(auth_result.service.name, "auth");
219 assert_eq!(payment_result.service.name, "payments");
220 assert_ne!(auth_result.workspace_id, payment_result.workspace_id);
221 }
222
223 #[test]
224 fn test_router_with_empty_services() {
225 let federation = Arc::new(Federation {
226 id: Uuid::new_v4(),
227 name: "empty".to_string(),
228 description: String::new(),
229 org_id: Uuid::new_v4(),
230 services: vec![],
231 created_at: Utc::now(),
232 updated_at: Utc::now(),
233 });
234
235 let router = FederationRouter::new(federation);
236 assert!(router.route("/any/path").is_none());
237 assert!(router.services().is_empty());
238 }
239}