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, FederationConfig, FederationService};
353 use crate::service::{ServiceBoundary, ServiceRealityLevel};
354 use std::collections::HashMap;
355 use tempfile::TempDir;
356
357 async fn create_test_db() -> (FederationDatabase, TempDir) {
358 let temp_dir = TempDir::new().unwrap();
359 let db_path = temp_dir.path().join("test.db");
360 let db_url = format!("sqlite://{}?mode=rwc", db_path.display());
361
362 let pool = SqlitePool::connect(&db_url).await.unwrap();
363 let db = FederationDatabase::new(pool).await.unwrap();
364 db.run_migrations().await.unwrap();
365
366 (db, temp_dir)
367 }
368
369 fn create_test_federation() -> Federation {
370 let org_id = Uuid::new_v4();
371 let workspace_id1 = Uuid::new_v4();
372 let workspace_id2 = Uuid::new_v4();
373
374 let mut service1 = ServiceBoundary::new(
375 "auth".to_string(),
376 workspace_id1,
377 "/auth".to_string(),
378 ServiceRealityLevel::Real,
379 );
380 service1.config.insert("timeout".to_string(), serde_json::json!(5000));
381 service1.dependencies.push("database".to_string());
382
383 let service2 = ServiceBoundary::new(
384 "payments".to_string(),
385 workspace_id2,
386 "/payments".to_string(),
387 ServiceRealityLevel::MockV3,
388 );
389
390 Federation {
391 id: Uuid::new_v4(),
392 name: "test-federation".to_string(),
393 description: "Test federation for unit tests".to_string(),
394 org_id,
395 services: vec![service1, service2],
396 created_at: Utc::now(),
397 updated_at: Utc::now(),
398 }
399 }
400
401 #[tokio::test]
402 async fn test_new_database() {
403 let temp_dir = TempDir::new().unwrap();
404 let db_path = temp_dir.path().join("test.db");
405 let db_url = format!("sqlite://{}?mode=rwc", db_path.display());
406
407 let pool = SqlitePool::connect(&db_url).await.unwrap();
408 let result = FederationDatabase::new(pool).await;
409
410 assert!(result.is_ok());
411 }
412
413 #[tokio::test]
414 async fn test_run_migrations() {
415 let (db, _temp_dir) = create_test_db().await;
416
417 let result = sqlx::query("SELECT COUNT(*) FROM federations").fetch_one(&db.pool).await;
419
420 assert!(result.is_ok());
421 }
422
423 #[tokio::test]
424 async fn test_create_federation() {
425 let (db, _temp_dir) = create_test_db().await;
426 let federation = create_test_federation();
427
428 let result = db.create_federation(&federation).await;
429 assert!(result.is_ok());
430
431 let row = sqlx::query("SELECT COUNT(*) FROM federations WHERE id = ?1")
433 .bind(federation.id.to_string())
434 .fetch_one(&db.pool)
435 .await
436 .unwrap();
437
438 let count: i64 = row.get(0);
439 assert_eq!(count, 1);
440 }
441
442 #[tokio::test]
443 async fn test_create_federation_with_services() {
444 let (db, _temp_dir) = create_test_db().await;
445 let federation = create_test_federation();
446 let federation_id = federation.id;
447
448 db.create_federation(&federation).await.unwrap();
449
450 let rows = sqlx::query("SELECT COUNT(*) FROM federation_services WHERE federation_id = ?1")
452 .bind(federation_id.to_string())
453 .fetch_one(&db.pool)
454 .await
455 .unwrap();
456
457 let count: i64 = rows.get(0);
458 assert_eq!(count, 2);
459 }
460
461 #[tokio::test]
462 async fn test_get_federation() {
463 let (db, _temp_dir) = create_test_db().await;
464 let original = create_test_federation();
465 let federation_id = original.id;
466
467 db.create_federation(&original).await.unwrap();
468
469 let result = db.get_federation(&federation_id).await.unwrap();
470 assert!(result.is_some());
471
472 let retrieved = result.unwrap();
473 assert_eq!(retrieved.id, original.id);
474 assert_eq!(retrieved.name, original.name);
475 assert_eq!(retrieved.description, original.description);
476 assert_eq!(retrieved.org_id, original.org_id);
477 assert_eq!(retrieved.services.len(), original.services.len());
478 }
479
480 #[tokio::test]
481 async fn test_get_federation_not_found() {
482 let (db, _temp_dir) = create_test_db().await;
483 let non_existent_id = Uuid::new_v4();
484
485 let result = db.get_federation(&non_existent_id).await.unwrap();
486 assert!(result.is_none());
487 }
488
489 #[tokio::test]
490 async fn test_get_federation_with_services() {
491 let (db, _temp_dir) = create_test_db().await;
492 let original = create_test_federation();
493 let federation_id = original.id;
494
495 db.create_federation(&original).await.unwrap();
496
497 let retrieved = db.get_federation(&federation_id).await.unwrap().unwrap();
498
499 assert_eq!(retrieved.services.len(), 2);
500 assert_eq!(retrieved.services[0].name, "auth");
501 assert_eq!(retrieved.services[0].base_path, "/auth");
502 assert_eq!(retrieved.services[0].reality_level, ServiceRealityLevel::Real);
503 assert_eq!(retrieved.services[1].name, "payments");
504 assert_eq!(retrieved.services[1].reality_level, ServiceRealityLevel::MockV3);
505 }
506
507 #[tokio::test]
508 async fn test_get_federation_preserves_service_config() {
509 let (db, _temp_dir) = create_test_db().await;
510 let original = create_test_federation();
511 let federation_id = original.id;
512
513 db.create_federation(&original).await.unwrap();
514
515 let retrieved = db.get_federation(&federation_id).await.unwrap().unwrap();
516
517 assert_eq!(retrieved.services[0].config.get("timeout"), Some(&serde_json::json!(5000)));
519 }
520
521 #[tokio::test]
522 async fn test_get_federation_preserves_service_dependencies() {
523 let (db, _temp_dir) = create_test_db().await;
524 let original = create_test_federation();
525 let federation_id = original.id;
526
527 db.create_federation(&original).await.unwrap();
528
529 let retrieved = db.get_federation(&federation_id).await.unwrap().unwrap();
530
531 assert_eq!(retrieved.services[0].dependencies, vec!["database".to_string()]);
533 }
534
535 #[tokio::test]
536 async fn test_list_federations() {
537 let (db, _temp_dir) = create_test_db().await;
538 let org_id = Uuid::new_v4();
539
540 for i in 0..3 {
542 let mut federation = create_test_federation();
543 federation.org_id = org_id;
544 federation.name = format!("federation-{i}");
545 db.create_federation(&federation).await.unwrap();
546 }
547
548 let federations = db.list_federations(&org_id).await.unwrap();
549 assert_eq!(federations.len(), 3);
550 }
551
552 #[tokio::test]
553 async fn test_list_federations_empty() {
554 let (db, _temp_dir) = create_test_db().await;
555 let org_id = Uuid::new_v4();
556
557 let federations = db.list_federations(&org_id).await.unwrap();
558 assert!(federations.is_empty());
559 }
560
561 #[tokio::test]
562 async fn test_list_federations_filters_by_org() {
563 let (db, _temp_dir) = create_test_db().await;
564 let org_id1 = Uuid::new_v4();
565 let org_id2 = Uuid::new_v4();
566
567 let mut federation1 = create_test_federation();
569 federation1.org_id = org_id1;
570 db.create_federation(&federation1).await.unwrap();
571
572 let mut federation2 = create_test_federation();
574 federation2.org_id = org_id2;
575 db.create_federation(&federation2).await.unwrap();
576
577 let org1_feds = db.list_federations(&org_id1).await.unwrap();
578 assert_eq!(org1_feds.len(), 1);
579 assert_eq!(org1_feds[0].id, federation1.id);
580
581 let org2_feds = db.list_federations(&org_id2).await.unwrap();
582 assert_eq!(org2_feds.len(), 1);
583 assert_eq!(org2_feds[0].id, federation2.id);
584 }
585
586 #[tokio::test]
587 async fn test_list_federations_with_services() {
588 let (db, _temp_dir) = create_test_db().await;
589 let org_id = Uuid::new_v4();
590
591 let mut federation = create_test_federation();
592 federation.org_id = org_id;
593 db.create_federation(&federation).await.unwrap();
594
595 let federations = db.list_federations(&org_id).await.unwrap();
596 assert_eq!(federations.len(), 1);
597 assert_eq!(federations[0].services.len(), 2);
598 }
599
600 #[tokio::test]
601 async fn test_update_federation() {
602 let (db, _temp_dir) = create_test_db().await;
603 let mut federation = create_test_federation();
604 let federation_id = federation.id;
605
606 db.create_federation(&federation).await.unwrap();
608
609 federation.name = "updated-federation".to_string();
611 federation.description = "Updated description".to_string();
612
613 db.update_federation(&federation).await.unwrap();
614
615 let updated = db.get_federation(&federation_id).await.unwrap().unwrap();
617 assert_eq!(updated.name, "updated-federation");
618 assert_eq!(updated.description, "Updated description");
619 }
620
621 #[tokio::test]
622 async fn test_update_federation_updates_services() {
623 let (db, _temp_dir) = create_test_db().await;
624 let mut federation = create_test_federation();
625 let federation_id = federation.id;
626
627 db.create_federation(&federation).await.unwrap();
629
630 federation.services.push(ServiceBoundary::new(
632 "inventory".to_string(),
633 Uuid::new_v4(),
634 "/inventory".to_string(),
635 ServiceRealityLevel::Blended,
636 ));
637
638 db.update_federation(&federation).await.unwrap();
639
640 let updated = db.get_federation(&federation_id).await.unwrap().unwrap();
642 assert_eq!(updated.services.len(), 3);
643 assert!(updated.services.iter().any(|s| s.name == "inventory"));
644 }
645
646 #[tokio::test]
647 async fn test_update_federation_removes_old_services() {
648 let (db, _temp_dir) = create_test_db().await;
649 let mut federation = create_test_federation();
650 let federation_id = federation.id;
651
652 db.create_federation(&federation).await.unwrap();
654
655 federation.services.clear();
657
658 db.update_federation(&federation).await.unwrap();
659
660 let updated = db.get_federation(&federation_id).await.unwrap().unwrap();
662 assert!(updated.services.is_empty());
663 }
664
665 #[tokio::test]
666 async fn test_delete_federation() {
667 let (db, _temp_dir) = create_test_db().await;
668 let federation = create_test_federation();
669 let federation_id = federation.id;
670
671 db.create_federation(&federation).await.unwrap();
673
674 db.delete_federation(&federation_id).await.unwrap();
676
677 let result = db.get_federation(&federation_id).await.unwrap();
679 assert!(result.is_none());
680 }
681
682 #[tokio::test]
683 async fn test_delete_federation_cascades_services() {
684 let (db, _temp_dir) = create_test_db().await;
685 let federation = create_test_federation();
686 let federation_id = federation.id;
687
688 db.create_federation(&federation).await.unwrap();
690
691 db.delete_federation(&federation_id).await.unwrap();
693
694 let rows = sqlx::query("SELECT COUNT(*) FROM federation_services WHERE federation_id = ?1")
696 .bind(federation_id.to_string())
697 .fetch_one(&db.pool)
698 .await
699 .unwrap();
700
701 let count: i64 = rows.get(0);
702 assert_eq!(count, 0);
703 }
704
705 #[tokio::test]
706 async fn test_create_federation_service_internal() {
707 let (db, _temp_dir) = create_test_db().await;
708 let federation = create_test_federation();
709 let federation_id = federation.id.to_string();
710
711 sqlx::query(
713 r"
714 INSERT INTO federations (id, name, org_id, description, created_at, updated_at)
715 VALUES (?1, ?2, ?3, ?4, ?5, ?6)
716 ",
717 )
718 .bind(&federation_id)
719 .bind("test")
720 .bind(federation.org_id.to_string())
721 .bind("")
722 .bind(Utc::now().timestamp())
723 .bind(Utc::now().timestamp())
724 .execute(&db.pool)
725 .await
726 .unwrap();
727
728 let service = ServiceBoundary::new(
730 "test-service".to_string(),
731 Uuid::new_v4(),
732 "/test".to_string(),
733 ServiceRealityLevel::Real,
734 );
735
736 let result = db.create_federation_service(&federation_id, &service).await;
737 assert!(result.is_ok());
738
739 let rows = sqlx::query("SELECT COUNT(*) FROM federation_services WHERE federation_id = ?1")
741 .bind(&federation_id)
742 .fetch_one(&db.pool)
743 .await
744 .unwrap();
745
746 let count: i64 = rows.get(0);
747 assert_eq!(count, 1);
748 }
749
750 #[tokio::test]
751 async fn test_get_federation_services_internal() {
752 let (db, _temp_dir) = create_test_db().await;
753 let federation = create_test_federation();
754 let federation_id = federation.id;
755
756 db.create_federation(&federation).await.unwrap();
757
758 let services = db.get_federation_services(&federation_id.to_string()).await.unwrap();
759 assert_eq!(services.len(), 2);
760
761 assert_eq!(services[0].base_path, "/auth");
763 assert_eq!(services[1].base_path, "/payments");
764 }
765
766 #[tokio::test]
767 async fn test_get_federation_services_empty() {
768 let (db, _temp_dir) = create_test_db().await;
769 let federation_id = Uuid::new_v4().to_string();
770
771 let services = db.get_federation_services(&federation_id).await.unwrap();
772 assert!(services.is_empty());
773 }
774
775 #[tokio::test]
776 async fn test_federation_timestamps() {
777 let (db, _temp_dir) = create_test_db().await;
778 let federation = create_test_federation();
779 let federation_id = federation.id;
780
781 db.create_federation(&federation).await.unwrap();
782
783 let retrieved = db.get_federation(&federation_id).await.unwrap().unwrap();
784
785 assert!(retrieved.created_at.timestamp() > 0);
787 assert!(retrieved.updated_at.timestamp() > 0);
788 }
789
790 #[tokio::test]
791 async fn test_service_reality_level_persistence() {
792 let (db, _temp_dir) = create_test_db().await;
793 let org_id = Uuid::new_v4();
794
795 let reality_levels = [
797 ServiceRealityLevel::Real,
798 ServiceRealityLevel::MockV3,
799 ServiceRealityLevel::Blended,
800 ServiceRealityLevel::ChaosDriven,
801 ];
802
803 for (i, level) in reality_levels.iter().enumerate() {
804 let service = ServiceBoundary::new(
805 format!("service-{i}"),
806 Uuid::new_v4(),
807 format!("/service{i}"),
808 *level,
809 );
810
811 let federation = Federation {
812 id: Uuid::new_v4(),
813 name: format!("fed-{i}"),
814 description: String::new(),
815 org_id,
816 services: vec![service],
817 created_at: Utc::now(),
818 updated_at: Utc::now(),
819 };
820
821 db.create_federation(&federation).await.unwrap();
822
823 let retrieved = db.get_federation(&federation.id).await.unwrap().unwrap();
824 assert_eq!(retrieved.services[0].reality_level, *level);
825 }
826 }
827
828 #[tokio::test]
829 async fn test_multiple_federations_same_org() {
830 let (db, _temp_dir) = create_test_db().await;
831 let org_id = Uuid::new_v4();
832
833 for i in 0..5 {
835 let mut federation = create_test_federation();
836 federation.org_id = org_id;
837 federation.name = format!("federation-{i}");
838 db.create_federation(&federation).await.unwrap();
839 }
840
841 let federations = db.list_federations(&org_id).await.unwrap();
842 assert_eq!(federations.len(), 5);
843
844 for i in 0..4 {
846 assert!(federations[i].created_at >= federations[i + 1].created_at);
847 }
848 }
849
850 #[tokio::test]
851 async fn test_complex_service_config_persistence() {
852 let (db, _temp_dir) = create_test_db().await;
853 let org_id = Uuid::new_v4();
854
855 let mut service = ServiceBoundary::new(
856 "complex".to_string(),
857 Uuid::new_v4(),
858 "/complex".to_string(),
859 ServiceRealityLevel::Blended,
860 );
861
862 service.config.insert("timeout".to_string(), serde_json::json!(5000));
864 service.config.insert("retries".to_string(), serde_json::json!(3));
865 service.config.insert(
866 "features".to_string(),
867 serde_json::json!({
868 "auth": true,
869 "metrics": false,
870 "tracing": true
871 }),
872 );
873 service.config.insert(
874 "endpoints".to_string(),
875 serde_json::json!(["/api/users", "/api/posts", "/api/comments"]),
876 );
877
878 service.dependencies = vec![
880 "auth".to_string(),
881 "database".to_string(),
882 "cache".to_string(),
883 ];
884
885 let federation = Federation {
886 id: Uuid::new_v4(),
887 name: "complex-test".to_string(),
888 description: String::new(),
889 org_id,
890 services: vec![service],
891 created_at: Utc::now(),
892 updated_at: Utc::now(),
893 };
894
895 db.create_federation(&federation).await.unwrap();
896
897 let retrieved = db.get_federation(&federation.id).await.unwrap().unwrap();
898 let service = &retrieved.services[0];
899
900 assert_eq!(service.config.get("timeout"), Some(&serde_json::json!(5000)));
902 assert_eq!(service.config.get("retries"), Some(&serde_json::json!(3)));
903 assert!(service.config.contains_key("features"));
904 assert!(service.config.contains_key("endpoints"));
905
906 assert_eq!(service.dependencies.len(), 3);
908 assert!(service.dependencies.contains(&"auth".to_string()));
909 assert!(service.dependencies.contains(&"database".to_string()));
910 assert!(service.dependencies.contains(&"cache".to_string()));
911 }
912}