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