photoncloud-monorepo/flashdns/crates/flashdns-server/tests/integration.rs
centra a7ec7e2158 Add T026 practical test + k8shost to flake + workspace files
- Created T026-practical-test task.yaml for MVP smoke testing
- Added k8shost-server to flake.nix (packages, apps, overlays)
- Staged all workspace directories for nix flake build
- Updated flake.nix shellHook to include k8shost

Resolves: T026.S1 blocker (R8 - nix submodule visibility)
2025-12-09 06:07:50 +09:00

329 lines
9.7 KiB
Rust

//! 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":"<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)
}