//! Integration tests for FlashDNS //! //! Run with: cargo test -p flashdns-server --test integration -- --ignored use std::sync::Arc; use flashdns_server::metadata::DnsMetadataStore; use flashdns_types::{Record, RecordData, RecordType, Ttl, Zone, ZoneName}; /// Test zone and record lifecycle via DnsMetadataStore #[tokio::test] #[ignore = "Integration test"] async fn test_zone_and_record_lifecycle() { let metadata = Arc::new(DnsMetadataStore::new_in_memory()); // 1. Create zone let zone_name = ZoneName::new("example.com").unwrap(); let zone = Zone::new(zone_name, "test-org", "test-project"); metadata.save_zone(&zone).await.unwrap(); // 2. Verify zone was created let loaded_zone = metadata .load_zone("test-org", "test-project", "example.com.") .await .unwrap(); assert!(loaded_zone.is_some()); assert_eq!(loaded_zone.unwrap().id, zone.id); // 3. Add A record let record_data = RecordData::a_from_str("192.168.1.100").unwrap(); let mut record = Record::new(zone.id, "www", record_data); record.ttl = Ttl::new(300).unwrap(); metadata.save_record(&record).await.unwrap(); // 4. Verify record via metadata let loaded = metadata .load_record(&zone.id, "www", RecordType::A) .await .unwrap(); assert!(loaded.is_some()); let loaded_record = loaded.unwrap(); assert_eq!(loaded_record.id, record.id); assert_eq!(loaded_record.ttl.as_secs(), 300); // 5. List records let records = metadata.list_records(&zone.id).await.unwrap(); assert_eq!(records.len(), 1); // 6. Add more records let ipv6: std::net::Ipv6Addr = "2001:db8::1".parse().unwrap(); let aaaa_data = RecordData::Aaaa { address: ipv6.octets() }; let aaaa_record = Record::new(zone.id, "www", aaaa_data); metadata.save_record(&aaaa_record).await.unwrap(); let mx_data = RecordData::Mx { preference: 10, exchange: "mail.example.com.".to_string(), }; let mx_record = Record::new(zone.id, "@", mx_data); metadata.save_record(&mx_record).await.unwrap(); let txt_data = RecordData::Txt { text: "v=spf1 include:_spf.example.com ~all".to_string(), }; let txt_record = Record::new(zone.id, "@", txt_data); metadata.save_record(&txt_record).await.unwrap(); // 7. List all records - should have 4 let all_records = metadata.list_records(&zone.id).await.unwrap(); assert_eq!(all_records.len(), 4); // 8. List records by name let www_records = metadata.list_records_by_name(&zone.id, "www").await.unwrap(); assert_eq!(www_records.len(), 2); // A + AAAA let root_records = metadata.list_records_by_name(&zone.id, "@").await.unwrap(); assert_eq!(root_records.len(), 2); // MX + TXT // 9. Cleanup - delete records metadata.delete_record(&record).await.unwrap(); metadata.delete_record(&aaaa_record).await.unwrap(); metadata.delete_record(&mx_record).await.unwrap(); metadata.delete_record(&txt_record).await.unwrap(); // 10. Verify records deleted let remaining = metadata.list_records(&zone.id).await.unwrap(); assert_eq!(remaining.len(), 0); // 11. Delete zone metadata.delete_zone(&zone).await.unwrap(); // 12. Verify zone deleted let deleted_zone = metadata .load_zone("test-org", "test-project", "example.com.") .await .unwrap(); assert!(deleted_zone.is_none()); } /// Test multi-zone scenario #[tokio::test] #[ignore = "Integration test"] async fn test_multi_zone_scenario() { let metadata = Arc::new(DnsMetadataStore::new_in_memory()); // Create multiple zones let zone1 = Zone::new( ZoneName::new("example.com").unwrap(), "org1", "project1", ); let zone2 = Zone::new( ZoneName::new("example.org").unwrap(), "org1", "project1", ); let zone3 = Zone::new( ZoneName::new("other.net").unwrap(), "org2", "project2", ); metadata.save_zone(&zone1).await.unwrap(); metadata.save_zone(&zone2).await.unwrap(); metadata.save_zone(&zone3).await.unwrap(); // Add records to each zone let a1 = Record::new( zone1.id, "www", RecordData::a_from_str("10.0.0.1").unwrap(), ); let a2 = Record::new( zone2.id, "www", RecordData::a_from_str("10.0.0.2").unwrap(), ); let a3 = Record::new( zone3.id, "www", RecordData::a_from_str("10.0.0.3").unwrap(), ); metadata.save_record(&a1).await.unwrap(); metadata.save_record(&a2).await.unwrap(); metadata.save_record(&a3).await.unwrap(); // List zones for org1 - should have 2 let org1_zones = metadata.list_zones("org1", None).await.unwrap(); assert_eq!(org1_zones.len(), 2); // List zones for org1/project1 - should have 2 let org1_p1_zones = metadata.list_zones("org1", Some("project1")).await.unwrap(); assert_eq!(org1_p1_zones.len(), 2); // List zones for org2 - should have 1 let org2_zones = metadata.list_zones("org2", None).await.unwrap(); assert_eq!(org2_zones.len(), 1); // Load zone by ID let loaded = metadata.load_zone_by_id(&zone1.id).await.unwrap(); assert!(loaded.is_some()); assert_eq!(loaded.unwrap().name.as_str(), "example.com."); // Cleanup metadata.delete_zone_records(&zone1.id).await.unwrap(); metadata.delete_zone_records(&zone2.id).await.unwrap(); metadata.delete_zone_records(&zone3.id).await.unwrap(); metadata.delete_zone(&zone1).await.unwrap(); metadata.delete_zone(&zone2).await.unwrap(); metadata.delete_zone(&zone3).await.unwrap(); } /// Test record type coverage #[tokio::test] #[ignore = "Integration test"] async fn test_record_type_coverage() { let metadata = Arc::new(DnsMetadataStore::new_in_memory()); let zone = Zone::new( ZoneName::new("types.test").unwrap(), "test-org", "test-project", ); metadata.save_zone(&zone).await.unwrap(); // A record let a = Record::new( zone.id, "a", RecordData::a_from_str("192.168.1.1").unwrap(), ); metadata.save_record(&a).await.unwrap(); // AAAA record let ipv6: std::net::Ipv6Addr = "2001:db8::1".parse().unwrap(); let aaaa = Record::new( zone.id, "aaaa", RecordData::Aaaa { address: ipv6.octets() }, ); metadata.save_record(&aaaa).await.unwrap(); // CNAME record let cname = Record::new( zone.id, "cname", RecordData::Cname { target: "target.types.test.".to_string(), }, ); metadata.save_record(&cname).await.unwrap(); // MX record let mx = Record::new( zone.id, "mx", RecordData::Mx { preference: 10, exchange: "mail.types.test.".to_string(), }, ); metadata.save_record(&mx).await.unwrap(); // TXT record let txt = Record::new( zone.id, "txt", RecordData::Txt { text: "test value".to_string(), }, ); metadata.save_record(&txt).await.unwrap(); // NS record let ns = Record::new( zone.id, "ns", RecordData::Ns { nameserver: "ns1.types.test.".to_string(), }, ); metadata.save_record(&ns).await.unwrap(); // SRV record let srv = Record::new( zone.id, "_sip._tcp", RecordData::Srv { priority: 10, weight: 20, port: 5060, target: "sip.types.test.".to_string(), }, ); metadata.save_record(&srv).await.unwrap(); // PTR record let ptr = Record::new( zone.id, "1.1.168.192.in-addr.arpa", RecordData::Ptr { target: "host.types.test.".to_string(), }, ); metadata.save_record(&ptr).await.unwrap(); // CAA record let caa = Record::new( zone.id, "caa", RecordData::Caa { flags: 0, tag: "issue".to_string(), value: "letsencrypt.org".to_string(), }, ); metadata.save_record(&caa).await.unwrap(); // Verify all records let records = metadata.list_records(&zone.id).await.unwrap(); assert_eq!(records.len(), 9); // Cleanup metadata.delete_zone_records(&zone.id).await.unwrap(); metadata.delete_zone(&zone).await.unwrap(); } /// Manual test documentation for DNS query resolution /// /// To test DNS query resolution manually: /// /// 1. Start the server: /// ``` /// cargo run -p flashdns-server /// ``` /// /// 2. Create a zone via gRPC (using grpcurl): /// ``` /// grpcurl -plaintext -d '{"name":"example.com","org_id":"test","project_id":"test"}' \ /// localhost:9053 flashdns.ZoneService/CreateZone /// ``` /// /// 3. Add an A record: /// ``` /// grpcurl -plaintext -d '{"zone_id":"","name":"www","record_type":"A","ttl":300,"data":{"a":{"address":"192.168.1.100"}}}' \ /// localhost:9053 flashdns.RecordService/CreateRecord /// ``` /// /// 4. Query via DNS: /// ``` /// dig @127.0.0.1 -p 5353 www.example.com A /// ``` /// /// Expected: Answer section should contain www.example.com with 192.168.1.100 #[tokio::test] #[ignore = "Integration test - requires DNS handler and manual verification"] async fn test_dns_query_resolution_docs() { // This test documents manual testing procedure // Actual automated DNS query testing would require: // 1. Starting DnsHandler on a test port // 2. Using a DNS client library to send queries // 3. Verifying responses // For CI, we verify the components individually: // - DnsMetadataStore (tested above) // - DnsQueryHandler logic (unit tested in handler.rs) // - Wire format (handled by trust-dns-proto) }