1use crate::Result;
7use axum::{
8 extract::Extension,
9 routing::{delete, get, patch, post, put},
10 Router,
11};
12use tower::ServiceBuilder;
13use tower_http::cors::CorsLayer;
14
15pub fn create_vbr_router(api_prefix: &str) -> Result<Router> {
30 let router = Router::new()
31 .route(
33 &format!("{}/health", api_prefix),
34 get(|| async { "OK" }),
35 )
36 .route(
39 &format!("{}/{{entity}}", api_prefix),
40 get(crate::handlers::list_handler),
41 )
42 .route(
45 &format!("{}/{{entity}}", api_prefix),
46 post(crate::handlers::create_handler),
47 )
48 .route(
51 &format!("{}/{{entity}}/{{id}}", api_prefix),
52 get(crate::handlers::get_handler),
53 )
54 .route(
57 &format!("{}/{{entity}}/{{id}}", api_prefix),
58 put(crate::handlers::update_handler),
59 )
60 .route(
63 &format!("{}/{{entity}}/{{id}}", api_prefix),
64 patch(crate::handlers::patch_handler),
65 )
66 .route(
69 &format!("{}/{{entity}}/{{id}}", api_prefix),
70 delete(crate::handlers::delete_handler),
71 )
72 .route(
75 &format!("{}/{{entity}}/{{id}}/{{relationship}}", api_prefix),
76 get(crate::handlers::get_relationship_handler),
77 )
78 .route(
81 &format!("{}/snapshots", api_prefix),
82 post(crate::handlers::create_snapshot_handler),
83 )
84 .route(
86 &format!("{}/snapshots", api_prefix),
87 get(crate::handlers::list_snapshots_handler),
88 )
89 .route(
91 &format!("{}/snapshots/{{name}}/restore", api_prefix),
92 post(crate::handlers::restore_snapshot_handler),
93 )
94 .route(
96 &format!("{}/snapshots/{{name}}", api_prefix),
97 delete(crate::handlers::delete_snapshot_handler),
98 )
99 .route(
101 &format!("{}/reset", api_prefix),
102 post(crate::handlers::reset_handler),
103 )
104 .layer(CorsLayer::permissive());
105
106 Ok(router)
107}
108
109pub fn create_vbr_router_with_context(
114 api_prefix: &str,
115 context: crate::handlers::HandlerContext,
116) -> Result<Router> {
117 let router = create_vbr_router(api_prefix)?;
118 Ok(router.layer(ServiceBuilder::new().layer(Extension(context)).into_inner()))
119}
120
121pub fn register_entity_routes(router: Router, entity_name: &str, api_prefix: &str) -> Router {
126 router
127 .route(
129 &format!("{}/{}", api_prefix, entity_name.to_lowercase()),
130 get(crate::handlers::list_handler),
131 )
132 .route(
134 &format!("{}/{}", api_prefix, entity_name.to_lowercase()),
135 post(crate::handlers::create_handler),
136 )
137 .route(
139 &format!("{}/{}/{{id}}", api_prefix, entity_name.to_lowercase()),
140 get(crate::handlers::get_handler),
141 )
142 .route(
144 &format!("{}/{}/{{id}}", api_prefix, entity_name.to_lowercase()),
145 put(crate::handlers::update_handler),
146 )
147 .route(
149 &format!("{}/{}/{{id}}", api_prefix, entity_name.to_lowercase()),
150 patch(crate::handlers::patch_handler),
151 )
152 .route(
154 &format!("{}/{}/{{id}}", api_prefix, entity_name.to_lowercase()),
155 delete(crate::handlers::delete_handler),
156 )
157 .route(
160 &format!("{}/{}/{{id}}/{{relationship}}", api_prefix, entity_name.to_lowercase()),
161 get(crate::handlers::get_relationship_handler),
162 )
163}
164
165pub fn integrate_vbr_routes(
171 app: Router,
172 api_prefix: &str,
173 context: crate::handlers::HandlerContext,
174) -> Result<Router> {
175 let vbr_router = create_vbr_router_with_context(api_prefix, context)?;
176 Ok(app.merge(vbr_router))
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182 use crate::database::{InMemoryDatabase, VirtualDatabase};
183 use crate::entities::{Entity, EntityRegistry};
184 use crate::handlers::HandlerContext;
185 use crate::schema::VbrSchemaDefinition;
186 use mockforge_data::{FieldDefinition, SchemaDefinition};
187 use std::sync::Arc;
188
189 async fn setup_test_context() -> HandlerContext {
190 let mut db = InMemoryDatabase::new().await.unwrap();
191 db.initialize().await.unwrap();
192 let registry = EntityRegistry::new();
193
194 HandlerContext {
195 database: Arc::new(db),
196 registry,
197 session_manager: None,
198 snapshots_dir: None,
199 }
200 }
201
202 fn create_test_entity(name: &str) -> Entity {
203 let base_schema = SchemaDefinition::new(name.to_string())
204 .with_field(FieldDefinition::new("id".to_string(), "string".to_string()))
205 .with_field(FieldDefinition::new("name".to_string(), "string".to_string()));
206
207 let vbr_schema = VbrSchemaDefinition::new(base_schema);
208 Entity::new(name.to_string(), vbr_schema)
209 }
210
211 #[tokio::test]
212 async fn test_create_vbr_router() {
213 let result = create_vbr_router("/api");
214 assert!(result.is_ok());
215 }
216
217 #[tokio::test]
218 async fn test_create_vbr_router_with_context() {
219 let context = setup_test_context().await;
220 let result = create_vbr_router_with_context("/api", context);
221 assert!(result.is_ok());
222 }
223
224 #[tokio::test]
225 async fn test_create_vbr_router_custom_prefix() {
226 let result = create_vbr_router("/custom-api");
227 assert!(result.is_ok());
228 }
229
230 #[tokio::test]
231 async fn test_register_entity_routes() {
232 let base_router = Router::new();
233 let entity = create_test_entity("User");
234
235 let router = register_entity_routes(base_router, &entity.name, "/api");
236
237 assert!(true);
239 }
240
241 #[tokio::test]
242 async fn test_register_entity_routes_multiple_entities() {
243 let mut router = Router::new();
244
245 let user_entity = create_test_entity("User");
246 let product_entity = create_test_entity("Product");
247
248 router = register_entity_routes(router, &user_entity.name, "/api");
249 router = register_entity_routes(router, &product_entity.name, "/api");
250
251 assert!(true);
253 }
254
255 #[tokio::test]
256 async fn test_integrate_vbr_routes() {
257 let app = Router::new();
258 let context = setup_test_context().await;
259
260 let result = integrate_vbr_routes(app, "/vbr-api", context);
261 assert!(result.is_ok());
262 }
263
264 #[tokio::test]
265 async fn test_integrate_vbr_routes_custom_prefix() {
266 let app = Router::new();
267 let context = setup_test_context().await;
268
269 let result = integrate_vbr_routes(app, "/custom", context);
270 assert!(result.is_ok());
271 }
272
273 #[tokio::test]
274 async fn test_create_vbr_router_with_health_endpoint() {
275 let router = create_vbr_router("/api").unwrap();
276 assert!(true);
280 }
281
282 #[tokio::test]
283 async fn test_create_vbr_router_with_all_crud_routes() {
284 let router = create_vbr_router("/api").unwrap();
285 assert!(true);
294 }
295
296 #[tokio::test]
297 async fn test_create_vbr_router_with_snapshot_routes() {
298 let router = create_vbr_router("/vbr-api").unwrap();
299 assert!(true);
306 }
307
308 #[tokio::test]
309 async fn test_create_vbr_router_with_cors() {
310 let router = create_vbr_router("/api").unwrap();
311 assert!(true);
314 }
315
316 #[tokio::test]
317 async fn test_register_entity_routes_with_lowercase() {
318 let base_router = Router::new();
319
320 let router = register_entity_routes(base_router, "User", "/api");
322
323 assert!(true);
328 }
329
330 #[tokio::test]
331 async fn test_context_with_session_manager() {
332 let mut db = InMemoryDatabase::new().await.unwrap();
333 db.initialize().await.unwrap();
334 let registry = EntityRegistry::new();
335
336 let context = HandlerContext {
337 database: Arc::new(db),
338 registry,
339 session_manager: None, snapshots_dir: None,
341 };
342
343 let result = create_vbr_router_with_context("/api", context);
344 assert!(result.is_ok());
345 }
346
347 #[tokio::test]
348 async fn test_context_with_snapshots_dir() {
349 let mut db = InMemoryDatabase::new().await.unwrap();
350 db.initialize().await.unwrap();
351 let registry = EntityRegistry::new();
352 let temp_dir = tempfile::tempdir().unwrap();
353
354 let context = HandlerContext {
355 database: Arc::new(db),
356 registry,
357 session_manager: None,
358 snapshots_dir: Some(temp_dir.path().to_path_buf()),
359 };
360
361 let result = create_vbr_router_with_context("/api", context);
362 assert!(result.is_ok());
363 }
364
365 #[tokio::test]
366 async fn test_empty_api_prefix() {
367 let result = create_vbr_router("");
368 assert!(result.is_ok());
369 }
370
371 #[tokio::test]
372 async fn test_api_prefix_with_trailing_slash() {
373 let result = create_vbr_router("/api/");
374 assert!(result.is_ok());
375 }
376
377 #[tokio::test]
378 async fn test_nested_api_prefix() {
379 let result = create_vbr_router("/v1/api");
380 assert!(result.is_ok());
381 }
382
383 #[tokio::test]
384 async fn test_router_can_be_merged_multiple_times() {
385 let app1 = Router::new();
386 let context1 = setup_test_context().await;
387 let app1 = integrate_vbr_routes(app1, "/api1", context1).unwrap();
388
389 let context2 = setup_test_context().await;
390 let result = integrate_vbr_routes(app1, "/api2", context2);
391 assert!(result.is_ok());
392 }
393}