1use crate::federation::Federation;
6use crate::service::{ServiceBoundary, ServiceRealityLevel};
7use anyhow::{Context, Result};
8use chrono::{DateTime, Utc};
9use serde_json;
10use sqlx::{sqlite::SqlitePool, Row};
11use tracing::info;
12use uuid::Uuid;
13
14pub struct FederationDatabase {
16 pool: SqlitePool,
17}
18
19impl FederationDatabase {
20 #[allow(clippy::unused_async)]
26 pub async fn new(pool: SqlitePool) -> Result<Self> {
27 Ok(Self { pool })
36 }
37
38 pub async fn run_migrations(&self) -> Result<()> {
44 let migration_sql = include_str!("../migrations/001_federation.sql");
45
46 sqlx::query(migration_sql)
47 .execute(&self.pool)
48 .await
49 .context("Failed to run federation migrations")?;
50
51 info!("Federation database migrations completed");
52 Ok(())
53 }
54
55 pub async fn create_federation(&self, federation: &Federation) -> Result<()> {
61 let id_str = federation.id.to_string();
62 let org_id_str = federation.org_id.to_string();
63 let created_at = federation.created_at.timestamp();
64 let updated_at = federation.updated_at.timestamp();
65
66 sqlx::query(
67 r"
68 INSERT INTO federations (id, name, org_id, description, created_at, updated_at)
69 VALUES (?1, ?2, ?3, ?4, ?5, ?6)
70 ",
71 )
72 .bind(&id_str)
73 .bind(&federation.name)
74 .bind(&org_id_str)
75 .bind(&federation.description)
76 .bind(created_at)
77 .bind(updated_at)
78 .execute(&self.pool)
79 .await
80 .context("Failed to create federation")?;
81
82 for service in &federation.services {
84 self.create_federation_service(&id_str, service).await?;
85 }
86
87 info!(
88 federation_id = %federation.id,
89 federation_name = %federation.name,
90 "Created federation"
91 );
92
93 Ok(())
94 }
95
96 async fn create_federation_service(
98 &self,
99 federation_id: &str,
100 service: &ServiceBoundary,
101 ) -> Result<()> {
102 let service_id = Uuid::new_v4().to_string();
103 let workspace_id_str = service.workspace_id.to_string();
104 let config_json =
105 serde_json::to_string(&service.config).context("Failed to serialize service config")?;
106 let dependencies_json = serde_json::to_string(&service.dependencies)
107 .context("Failed to serialize dependencies")?;
108 let created_at = Utc::now().timestamp();
109
110 sqlx::query(
111 r"
112 INSERT INTO federation_services
113 (id, federation_id, service_name, workspace_id, base_path, reality_level, config, dependencies, created_at)
114 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
115 ",
116 )
117 .bind(&service_id)
118 .bind(federation_id)
119 .bind(&service.name)
120 .bind(&workspace_id_str)
121 .bind(&service.base_path)
122 .bind(service.reality_level.as_str())
123 .bind(&config_json)
124 .bind(&dependencies_json)
125 .bind(created_at)
126 .execute(&self.pool)
127 .await
128 .context("Failed to create federation service")?;
129
130 Ok(())
131 }
132
133 pub async fn get_federation(&self, federation_id: &Uuid) -> Result<Option<Federation>> {
139 let id_str = federation_id.to_string();
140
141 let row = sqlx::query(
142 r"
143 SELECT id, name, org_id, description, created_at, updated_at
144 FROM federations
145 WHERE id = ?1
146 ",
147 )
148 .bind(&id_str)
149 .fetch_optional(&self.pool)
150 .await
151 .context("Failed to query federation")?;
152
153 if let Some(row) = row {
154 let id = Uuid::parse_str(row.get::<String, _>(0).as_str())
155 .context("Invalid federation ID")?;
156 let name: String = row.get(1);
157 let org_id =
158 Uuid::parse_str(row.get::<String, _>(2).as_str()).context("Invalid org ID")?;
159 let description: String = row.get(3);
160 let created_at = DateTime::from_timestamp(row.get::<i64, _>(4), 0)
161 .unwrap_or_else(Utc::now)
162 .with_timezone(&Utc);
163 let updated_at = DateTime::from_timestamp(row.get::<i64, _>(5), 0)
164 .unwrap_or_else(Utc::now)
165 .with_timezone(&Utc);
166
167 let services = self.get_federation_services(&id_str).await?;
169
170 Ok(Some(Federation {
171 id,
172 name,
173 description,
174 org_id,
175 services,
176 created_at,
177 updated_at,
178 }))
179 } else {
180 Ok(None)
181 }
182 }
183
184 async fn get_federation_services(&self, federation_id: &str) -> Result<Vec<ServiceBoundary>> {
186 let rows = sqlx::query(
187 r"
188 SELECT service_name, workspace_id, base_path, reality_level, config, dependencies
189 FROM federation_services
190 WHERE federation_id = ?1
191 ORDER BY base_path
192 ",
193 )
194 .bind(federation_id)
195 .fetch_all(&self.pool)
196 .await
197 .context("Failed to query federation services")?;
198
199 let mut services = Vec::new();
200
201 for row in rows {
202 let name: String = row.get(0);
203 let workspace_id = Uuid::parse_str(row.get::<String, _>(1).as_str())
204 .context("Invalid workspace ID")?;
205 let base_path: String = row.get(2);
206 let reality_level_str: String = row.get(3);
207 let config_json: String = row.get(4);
208 let dependencies_json: String = row.get(5);
209
210 let reality_level = ServiceRealityLevel::from_str(&reality_level_str)
211 .ok_or_else(|| anyhow::anyhow!("Invalid reality level: {reality_level_str}"))?;
212
213 let config: std::collections::HashMap<String, serde_json::Value> =
214 serde_json::from_str(&config_json).context("Failed to parse service config")?;
215 let dependencies: Vec<String> =
216 serde_json::from_str(&dependencies_json).context("Failed to parse dependencies")?;
217
218 let mut service = ServiceBoundary::new(name, workspace_id, base_path, reality_level);
219 service.config = config;
220 service.dependencies = dependencies;
221
222 services.push(service);
223 }
224
225 Ok(services)
226 }
227
228 pub async fn list_federations(&self, org_id: &Uuid) -> Result<Vec<Federation>> {
234 let org_id_str = org_id.to_string();
235
236 let rows = sqlx::query(
237 r"
238 SELECT id, name, org_id, description, created_at, updated_at
239 FROM federations
240 WHERE org_id = ?1
241 ORDER BY created_at DESC
242 ",
243 )
244 .bind(&org_id_str)
245 .fetch_all(&self.pool)
246 .await
247 .context("Failed to query federations")?;
248
249 let mut federations = Vec::new();
250
251 for row in rows {
252 let id = Uuid::parse_str(row.get::<String, _>(0).as_str())
253 .context("Invalid federation ID")?;
254 let name: String = row.get(1);
255 let org_id =
256 Uuid::parse_str(row.get::<String, _>(2).as_str()).context("Invalid org ID")?;
257 let description: String = row.get(3);
258 let created_at = DateTime::from_timestamp(row.get::<i64, _>(4), 0)
259 .unwrap_or_else(Utc::now)
260 .with_timezone(&Utc);
261 let updated_at = DateTime::from_timestamp(row.get::<i64, _>(5), 0)
262 .unwrap_or_else(Utc::now)
263 .with_timezone(&Utc);
264
265 let id_str = id.to_string();
266 let services = self.get_federation_services(&id_str).await?;
267
268 federations.push(Federation {
269 id,
270 name,
271 description,
272 org_id,
273 services,
274 created_at,
275 updated_at,
276 });
277 }
278
279 Ok(federations)
280 }
281
282 pub async fn update_federation(&self, federation: &Federation) -> Result<()> {
288 let id_str = federation.id.to_string();
289 let updated_at = Utc::now().timestamp();
290
291 sqlx::query(
292 r"
293 UPDATE federations
294 SET name = ?1, description = ?2, updated_at = ?3
295 WHERE id = ?4
296 ",
297 )
298 .bind(&federation.name)
299 .bind(&federation.description)
300 .bind(updated_at)
301 .bind(&id_str)
302 .execute(&self.pool)
303 .await
304 .context("Failed to update federation")?;
305
306 sqlx::query("DELETE FROM federation_services WHERE federation_id = ?1")
308 .bind(&id_str)
309 .execute(&self.pool)
310 .await
311 .context("Failed to delete existing services")?;
312
313 for service in &federation.services {
315 self.create_federation_service(&id_str, service).await?;
316 }
317
318 info!(
319 federation_id = %federation.id,
320 "Updated federation"
321 );
322
323 Ok(())
324 }
325
326 pub async fn delete_federation(&self, federation_id: &Uuid) -> Result<()> {
332 let id_str = federation_id.to_string();
333
334 sqlx::query("DELETE FROM federations WHERE id = ?1")
335 .bind(&id_str)
336 .execute(&self.pool)
337 .await
338 .context("Failed to delete federation")?;
339
340 info!(
341 federation_id = %federation_id,
342 "Deleted federation"
343 );
344
345 Ok(())
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352 use crate::federation::Federation;
353 use crate::service::{ServiceBoundary, ServiceRealityLevel};
354 use tempfile::TempDir;
355
356 async fn create_test_db() -> (FederationDatabase, TempDir) {
357 let temp_dir = TempDir::new().unwrap();
358 let db_path = temp_dir.path().join("test.db");
359 let db_url = format!("sqlite://{}?mode=rwc", db_path.display());
360
361 let pool = SqlitePool::connect(&db_url).await.unwrap();
362 let db = FederationDatabase::new(pool).await.unwrap();
363 db.run_migrations().await.unwrap();
364
365 (db, temp_dir)
366 }
367
368 fn create_test_federation() -> Federation {
369 let org_id = Uuid::new_v4();
370 let workspace_id1 = Uuid::new_v4();
371 let workspace_id2 = Uuid::new_v4();
372
373 let mut service1 = ServiceBoundary::new(
374 "auth".to_string(),
375 workspace_id1,
376 "/auth".to_string(),
377 ServiceRealityLevel::Real,
378 );
379 service1.config.insert("timeout".to_string(), serde_json::json!(5000));
380 service1.dependencies.push("database".to_string());
381
382 let service2 = ServiceBoundary::new(
383 "payments".to_string(),
384 workspace_id2,
385 "/payments".to_string(),
386 ServiceRealityLevel::MockV3,
387 );
388
389 Federation {
390 id: Uuid::new_v4(),
391 name: "test-federation".to_string(),
392 description: "Test federation for unit tests".to_string(),
393 org_id,
394 services: vec![service1, service2],
395 created_at: Utc::now(),
396 updated_at: Utc::now(),
397 }
398 }
399
400 #[tokio::test]
401 async fn test_new_database() {
402 let temp_dir = TempDir::new().unwrap();
403 let db_path = temp_dir.path().join("test.db");
404 let db_url = format!("sqlite://{}?mode=rwc", db_path.display());
405
406 let pool = SqlitePool::connect(&db_url).await.unwrap();
407 let result = FederationDatabase::new(pool).await;
408
409 assert!(result.is_ok());
410 }
411
412 #[tokio::test]
413 async fn test_run_migrations() {
414 let (db, _temp_dir) = create_test_db().await;
415
416 let result = sqlx::query("SELECT COUNT(*) FROM federations").fetch_one(&db.pool).await;
418
419 assert!(result.is_ok());
420 }
421
422 #[tokio::test]
423 async fn test_create_federation() {
424 let (db, _temp_dir) = create_test_db().await;
425 let federation = create_test_federation();
426
427 let result = db.create_federation(&federation).await;
428 assert!(result.is_ok());
429
430 let row = sqlx::query("SELECT COUNT(*) FROM federations WHERE id = ?1")
432 .bind(federation.id.to_string())
433 .fetch_one(&db.pool)
434 .await
435 .unwrap();
436
437 let count: i64 = row.get(0);
438 assert_eq!(count, 1);
439 }
440
441 #[tokio::test]
442 async fn test_create_federation_with_services() {
443 let (db, _temp_dir) = create_test_db().await;
444 let federation = create_test_federation();
445 let federation_id = federation.id;
446
447 db.create_federation(&federation).await.unwrap();
448
449 let rows = sqlx::query("SELECT COUNT(*) FROM federation_services WHERE federation_id = ?1")
451 .bind(federation_id.to_string())
452 .fetch_one(&db.pool)
453 .await
454 .unwrap();
455
456 let count: i64 = rows.get(0);
457 assert_eq!(count, 2);
458 }
459
460 #[tokio::test]
461 async fn test_get_federation() {
462 let (db, _temp_dir) = create_test_db().await;
463 let original = create_test_federation();
464 let federation_id = original.id;
465
466 db.create_federation(&original).await.unwrap();
467
468 let result = db.get_federation(&federation_id).await.unwrap();
469 assert!(result.is_some());
470
471 let retrieved = result.unwrap();
472 assert_eq!(retrieved.id, original.id);
473 assert_eq!(retrieved.name, original.name);
474 assert_eq!(retrieved.description, original.description);
475 assert_eq!(retrieved.org_id, original.org_id);
476 assert_eq!(retrieved.services.len(), original.services.len());
477 }
478
479 #[tokio::test]
480 async fn test_get_federation_not_found() {
481 let (db, _temp_dir) = create_test_db().await;
482 let non_existent_id = Uuid::new_v4();
483
484 let result = db.get_federation(&non_existent_id).await.unwrap();
485 assert!(result.is_none());
486 }
487
488 #[tokio::test]
489 async fn test_get_federation_with_services() {
490 let (db, _temp_dir) = create_test_db().await;
491 let original = create_test_federation();
492 let federation_id = original.id;
493
494 db.create_federation(&original).await.unwrap();
495
496 let retrieved = db.get_federation(&federation_id).await.unwrap().unwrap();
497
498 assert_eq!(retrieved.services.len(), 2);
499 assert_eq!(retrieved.services[0].name, "auth");
500 assert_eq!(retrieved.services[0].base_path, "/auth");
501 assert_eq!(retrieved.services[0].reality_level, ServiceRealityLevel::Real);
502 assert_eq!(retrieved.services[1].name, "payments");
503 assert_eq!(retrieved.services[1].reality_level, ServiceRealityLevel::MockV3);
504 }
505
506 #[tokio::test]
507 async fn test_get_federation_preserves_service_config() {
508 let (db, _temp_dir) = create_test_db().await;
509 let original = create_test_federation();
510 let federation_id = original.id;
511
512 db.create_federation(&original).await.unwrap();
513
514 let retrieved = db.get_federation(&federation_id).await.unwrap().unwrap();
515
516 assert_eq!(retrieved.services[0].config.get("timeout"), Some(&serde_json::json!(5000)));
518 }
519
520 #[tokio::test]
521 async fn test_get_federation_preserves_service_dependencies() {
522 let (db, _temp_dir) = create_test_db().await;
523 let original = create_test_federation();
524 let federation_id = original.id;
525
526 db.create_federation(&original).await.unwrap();
527
528 let retrieved = db.get_federation(&federation_id).await.unwrap().unwrap();
529
530 assert_eq!(retrieved.services[0].dependencies, vec!["database".to_string()]);
532 }
533
534 #[tokio::test]
535 async fn test_list_federations() {
536 let (db, _temp_dir) = create_test_db().await;
537 let org_id = Uuid::new_v4();
538
539 for i in 0..3 {
541 let mut federation = create_test_federation();
542 federation.org_id = org_id;
543 federation.name = format!("federation-{i}");
544 db.create_federation(&federation).await.unwrap();
545 }
546
547 let federations = db.list_federations(&org_id).await.unwrap();
548 assert_eq!(federations.len(), 3);
549 }
550
551 #[tokio::test]
552 async fn test_list_federations_empty() {
553 let (db, _temp_dir) = create_test_db().await;
554 let org_id = Uuid::new_v4();
555
556 let federations = db.list_federations(&org_id).await.unwrap();
557 assert!(federations.is_empty());
558 }
559
560 #[tokio::test]
561 async fn test_list_federations_filters_by_org() {
562 let (db, _temp_dir) = create_test_db().await;
563 let org_id1 = Uuid::new_v4();
564 let org_id2 = Uuid::new_v4();
565
566 let mut federation1 = create_test_federation();
568 federation1.org_id = org_id1;
569 db.create_federation(&federation1).await.unwrap();
570
571 let mut federation2 = create_test_federation();
573 federation2.org_id = org_id2;
574 db.create_federation(&federation2).await.unwrap();
575
576 let org1_feds = db.list_federations(&org_id1).await.unwrap();
577 assert_eq!(org1_feds.len(), 1);
578 assert_eq!(org1_feds[0].id, federation1.id);
579
580 let org2_feds = db.list_federations(&org_id2).await.unwrap();
581 assert_eq!(org2_feds.len(), 1);
582 assert_eq!(org2_feds[0].id, federation2.id);
583 }
584
585 #[tokio::test]
586 async fn test_list_federations_with_services() {
587 let (db, _temp_dir) = create_test_db().await;
588 let org_id = Uuid::new_v4();
589
590 let mut federation = create_test_federation();
591 federation.org_id = org_id;
592 db.create_federation(&federation).await.unwrap();
593
594 let federations = db.list_federations(&org_id).await.unwrap();
595 assert_eq!(federations.len(), 1);
596 assert_eq!(federations[0].services.len(), 2);
597 }
598
599 #[tokio::test]
600 async fn test_update_federation() {
601 let (db, _temp_dir) = create_test_db().await;
602 let mut federation = create_test_federation();
603 let federation_id = federation.id;
604
605 db.create_federation(&federation).await.unwrap();
607
608 federation.name = "updated-federation".to_string();
610 federation.description = "Updated description".to_string();
611
612 db.update_federation(&federation).await.unwrap();
613
614 let updated = db.get_federation(&federation_id).await.unwrap().unwrap();
616 assert_eq!(updated.name, "updated-federation");
617 assert_eq!(updated.description, "Updated description");
618 }
619
620 #[tokio::test]
621 async fn test_update_federation_updates_services() {
622 let (db, _temp_dir) = create_test_db().await;
623 let mut federation = create_test_federation();
624 let federation_id = federation.id;
625
626 db.create_federation(&federation).await.unwrap();
628
629 federation.services.push(ServiceBoundary::new(
631 "inventory".to_string(),
632 Uuid::new_v4(),
633 "/inventory".to_string(),
634 ServiceRealityLevel::Blended,
635 ));
636
637 db.update_federation(&federation).await.unwrap();
638
639 let updated = db.get_federation(&federation_id).await.unwrap().unwrap();
641 assert_eq!(updated.services.len(), 3);
642 assert!(updated.services.iter().any(|s| s.name == "inventory"));
643 }
644
645 #[tokio::test]
646 async fn test_update_federation_removes_old_services() {
647 let (db, _temp_dir) = create_test_db().await;
648 let mut federation = create_test_federation();
649 let federation_id = federation.id;
650
651 db.create_federation(&federation).await.unwrap();
653
654 federation.services.clear();
656
657 db.update_federation(&federation).await.unwrap();
658
659 let updated = db.get_federation(&federation_id).await.unwrap().unwrap();
661 assert!(updated.services.is_empty());
662 }
663
664 #[tokio::test]
665 async fn test_delete_federation() {
666 let (db, _temp_dir) = create_test_db().await;
667 let federation = create_test_federation();
668 let federation_id = federation.id;
669
670 db.create_federation(&federation).await.unwrap();
672
673 db.delete_federation(&federation_id).await.unwrap();
675
676 let result = db.get_federation(&federation_id).await.unwrap();
678 assert!(result.is_none());
679 }
680
681 #[tokio::test]
682 async fn test_delete_federation_cascades_services() {
683 let (db, _temp_dir) = create_test_db().await;
684 let federation = create_test_federation();
685 let federation_id = federation.id;
686
687 db.create_federation(&federation).await.unwrap();
689
690 db.delete_federation(&federation_id).await.unwrap();
692
693 let rows = sqlx::query("SELECT COUNT(*) FROM federation_services WHERE federation_id = ?1")
695 .bind(federation_id.to_string())
696 .fetch_one(&db.pool)
697 .await
698 .unwrap();
699
700 let count: i64 = rows.get(0);
701 assert_eq!(count, 0);
702 }
703
704 #[tokio::test]
705 async fn test_create_federation_service_internal() {
706 let (db, _temp_dir) = create_test_db().await;
707 let federation = create_test_federation();
708 let federation_id = federation.id.to_string();
709
710 sqlx::query(
712 r"
713 INSERT INTO federations (id, name, org_id, description, created_at, updated_at)
714 VALUES (?1, ?2, ?3, ?4, ?5, ?6)
715 ",
716 )
717 .bind(&federation_id)
718 .bind("test")
719 .bind(federation.org_id.to_string())
720 .bind("")
721 .bind(Utc::now().timestamp())
722 .bind(Utc::now().timestamp())
723 .execute(&db.pool)
724 .await
725 .unwrap();
726
727 let service = ServiceBoundary::new(
729 "test-service".to_string(),
730 Uuid::new_v4(),
731 "/test".to_string(),
732 ServiceRealityLevel::Real,
733 );
734
735 let result = db.create_federation_service(&federation_id, &service).await;
736 assert!(result.is_ok());
737
738 let rows = sqlx::query("SELECT COUNT(*) FROM federation_services WHERE federation_id = ?1")
740 .bind(&federation_id)
741 .fetch_one(&db.pool)
742 .await
743 .unwrap();
744
745 let count: i64 = rows.get(0);
746 assert_eq!(count, 1);
747 }
748
749 #[tokio::test]
750 async fn test_get_federation_services_internal() {
751 let (db, _temp_dir) = create_test_db().await;
752 let federation = create_test_federation();
753 let federation_id = federation.id;
754
755 db.create_federation(&federation).await.unwrap();
756
757 let services = db.get_federation_services(&federation_id.to_string()).await.unwrap();
758 assert_eq!(services.len(), 2);
759
760 assert_eq!(services[0].base_path, "/auth");
762 assert_eq!(services[1].base_path, "/payments");
763 }
764
765 #[tokio::test]
766 async fn test_get_federation_services_empty() {
767 let (db, _temp_dir) = create_test_db().await;
768 let federation_id = Uuid::new_v4().to_string();
769
770 let services = db.get_federation_services(&federation_id).await.unwrap();
771 assert!(services.is_empty());
772 }
773
774 #[tokio::test]
775 async fn test_federation_timestamps() {
776 let (db, _temp_dir) = create_test_db().await;
777 let federation = create_test_federation();
778 let federation_id = federation.id;
779
780 db.create_federation(&federation).await.unwrap();
781
782 let retrieved = db.get_federation(&federation_id).await.unwrap().unwrap();
783
784 assert!(retrieved.created_at.timestamp() > 0);
786 assert!(retrieved.updated_at.timestamp() > 0);
787 }
788
789 #[tokio::test]
790 async fn test_service_reality_level_persistence() {
791 let (db, _temp_dir) = create_test_db().await;
792 let org_id = Uuid::new_v4();
793
794 let reality_levels = [
796 ServiceRealityLevel::Real,
797 ServiceRealityLevel::MockV3,
798 ServiceRealityLevel::Blended,
799 ServiceRealityLevel::ChaosDriven,
800 ];
801
802 for (i, level) in reality_levels.iter().enumerate() {
803 let service = ServiceBoundary::new(
804 format!("service-{i}"),
805 Uuid::new_v4(),
806 format!("/service{i}"),
807 *level,
808 );
809
810 let federation = Federation {
811 id: Uuid::new_v4(),
812 name: format!("fed-{i}"),
813 description: String::new(),
814 org_id,
815 services: vec![service],
816 created_at: Utc::now(),
817 updated_at: Utc::now(),
818 };
819
820 db.create_federation(&federation).await.unwrap();
821
822 let retrieved = db.get_federation(&federation.id).await.unwrap().unwrap();
823 assert_eq!(retrieved.services[0].reality_level, *level);
824 }
825 }
826
827 #[tokio::test]
828 async fn test_multiple_federations_same_org() {
829 let (db, _temp_dir) = create_test_db().await;
830 let org_id = Uuid::new_v4();
831
832 for i in 0..5 {
834 let mut federation = create_test_federation();
835 federation.org_id = org_id;
836 federation.name = format!("federation-{i}");
837 db.create_federation(&federation).await.unwrap();
838 }
839
840 let federations = db.list_federations(&org_id).await.unwrap();
841 assert_eq!(federations.len(), 5);
842
843 for i in 0..4 {
845 assert!(federations[i].created_at >= federations[i + 1].created_at);
846 }
847 }
848
849 #[tokio::test]
850 async fn test_complex_service_config_persistence() {
851 let (db, _temp_dir) = create_test_db().await;
852 let org_id = Uuid::new_v4();
853
854 let mut service = ServiceBoundary::new(
855 "complex".to_string(),
856 Uuid::new_v4(),
857 "/complex".to_string(),
858 ServiceRealityLevel::Blended,
859 );
860
861 service.config.insert("timeout".to_string(), serde_json::json!(5000));
863 service.config.insert("retries".to_string(), serde_json::json!(3));
864 service.config.insert(
865 "features".to_string(),
866 serde_json::json!({
867 "auth": true,
868 "metrics": false,
869 "tracing": true
870 }),
871 );
872 service.config.insert(
873 "endpoints".to_string(),
874 serde_json::json!(["/api/users", "/api/posts", "/api/comments"]),
875 );
876
877 service.dependencies = vec![
879 "auth".to_string(),
880 "database".to_string(),
881 "cache".to_string(),
882 ];
883
884 let federation = Federation {
885 id: Uuid::new_v4(),
886 name: "complex-test".to_string(),
887 description: String::new(),
888 org_id,
889 services: vec![service],
890 created_at: Utc::now(),
891 updated_at: Utc::now(),
892 };
893
894 db.create_federation(&federation).await.unwrap();
895
896 let retrieved = db.get_federation(&federation.id).await.unwrap().unwrap();
897 let service = &retrieved.services[0];
898
899 assert_eq!(service.config.get("timeout"), Some(&serde_json::json!(5000)));
901 assert_eq!(service.config.get("retries"), Some(&serde_json::json!(3)));
902 assert!(service.config.contains_key("features"));
903 assert!(service.config.contains_key("endpoints"));
904
905 assert_eq!(service.dependencies.len(), 3);
907 assert!(service.dependencies.contains(&"auth".to_string()));
908 assert!(service.dependencies.contains(&"database".to_string()));
909 assert!(service.dependencies.contains(&"cache".to_string()));
910 }
911}