Skip to main content

mockforge_federation/
database.rs

1//! Database persistence for federations
2//!
3//! Provides methods to store and retrieve federation configurations from the database.
4
5use 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
14/// Database layer for federation persistence
15pub struct FederationDatabase {
16    pool: SqlitePool,
17}
18
19impl FederationDatabase {
20    /// Create a new federation database instance
21    pub async fn new(pool: SqlitePool) -> Result<Self> {
22        // Run migrations
23        // Note: In a real implementation, you'd use sqlx::migrate! macro
24        // For now, we'll run migrations manually or via a migration runner
25        // sqlx::migrate!("./migrations")
26        //     .run(&pool)
27        //     .await
28        //     .context("Failed to run federation migrations")?;
29
30        Ok(Self { pool })
31    }
32
33    /// Run migrations manually
34    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    /// Create a new federation
47    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        // Insert services
70        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    /// Create a federation service
84    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    /// Get a federation by ID
121    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            // Load services
151            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    /// Get all services for a federation
168    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    /// List all federations for an organization
212    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    /// Update a federation
262    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        // Delete existing services and recreate
282        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        // Recreate services
289        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    /// Delete a federation
302    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        // Verify tables were created by trying to query them
389        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        // Verify it was inserted
403        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        // Verify services were created
422        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        // Check that config was preserved
489        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        // Check that dependencies were preserved
503        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        // Create multiple federations for the same org
512        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        // Create federation for org1
539        let mut federation1 = create_test_federation();
540        federation1.org_id = org_id1;
541        db.create_federation(&federation1).await.unwrap();
542
543        // Create federation for org2
544        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        // Create initial federation
578        db.create_federation(&federation).await.unwrap();
579
580        // Update the federation
581        federation.name = "updated-federation".to_string();
582        federation.description = "Updated description".to_string();
583
584        db.update_federation(&federation).await.unwrap();
585
586        // Verify the update
587        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        // Create initial federation
599        db.create_federation(&federation).await.unwrap();
600
601        // Add a new service
602        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        // Verify services were updated
612        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        // Create initial federation
624        db.create_federation(&federation).await.unwrap();
625
626        // Remove all services
627        federation.services.clear();
628
629        db.update_federation(&federation).await.unwrap();
630
631        // Verify services were removed
632        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        // Create federation
643        db.create_federation(&federation).await.unwrap();
644
645        // Delete it
646        db.delete_federation(&federation_id).await.unwrap();
647
648        // Verify it's gone
649        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        // Create federation
660        db.create_federation(&federation).await.unwrap();
661
662        // Delete it
663        db.delete_federation(&federation_id).await.unwrap();
664
665        // Verify services were also deleted
666        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        // First create the federation without services
683        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        // Now test creating a service
700        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        // Verify it was created
711        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        // Services should be ordered by base_path
733        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        // Timestamps should be set
757        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        // Test all reality levels
767        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        // Create 5 federations
805        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        // Should be ordered by created_at DESC
816        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        // Add complex config
834        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        // Add dependencies
850        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        // Verify complex config was preserved
872        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        // Verify dependencies
878        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}