//! Integration tests for the DNS channel using a fake `TxtResolver`. use std::sync::Arc; use async_trait::async_trait; use chrono::Utc; use kez_channels::{ Channel, ChannelError, dns::{DnsChannel, TxtResolver}, parse_proof, }; use kez_channels::ChannelResult; use kez_core::{ClaimPayload, Identity, NostrSecret, SignedClaim}; struct CapturingResolver { records: Vec, expected_name: String, } #[async_trait] impl TxtResolver for CapturingResolver { async fn lookup_txt(&self, name: &str) -> ChannelResult> { assert_eq!( name, self.expected_name, "DnsChannel must query `_kez.`" ); Ok(self.records.clone()) } } struct FailingResolver; #[async_trait] impl TxtResolver for FailingResolver { async fn lookup_txt(&self, _name: &str) -> ChannelResult> { Err(ChannelError::Unreachable("simulated network failure".into())) } } fn sign(subject: &str) -> SignedClaim { let secret = NostrSecret::generate(); let primary = Identity::parse(format!("nostr:{}", secret.npub())).unwrap(); let subject = Identity::parse(subject).unwrap(); SignedClaim::sign(ClaimPayload::new(subject, primary, Utc::now()), &secret).unwrap() } #[tokio::test] async fn queries_kez_underscore_name() { let signed = sign("dns:jason.example.com"); let compact = signed.to_compact().unwrap(); let channel = DnsChannel::with_resolver(Arc::new(CapturingResolver { records: vec![compact], expected_name: "_kez.jason.example.com".to_owned(), })); let identity = Identity::parse("dns:jason.example.com").unwrap(); let hit = channel.fetch_and_verify(&identity).await.unwrap(); assert_eq!(hit.proof, signed); } #[tokio::test] async fn supports_legacy_kez1_prefix() { let signed = sign("dns:jason.example.com"); let legacy = kez_core::dns_txt_value(&signed).unwrap(); assert!(legacy.starts_with("kez1:")); let channel = DnsChannel::with_resolver(Arc::new(CapturingResolver { records: vec![legacy.clone()], expected_name: "_kez.jason.example.com".to_owned(), })); let identity = Identity::parse("dns:jason.example.com").unwrap(); let hit = channel.fetch_and_verify(&identity).await.unwrap(); // round-trips back to the same envelope. assert_eq!(hit.proof, parse_proof(&legacy).unwrap()); } #[tokio::test] async fn surfaces_resolver_failure_as_unreachable() { let channel = DnsChannel::with_resolver(Arc::new(FailingResolver)); let identity = Identity::parse("dns:jason.example.com").unwrap(); let err = channel.fetch_and_verify(&identity).await.unwrap_err(); assert!( matches!(err, ChannelError::Unreachable(_)), "expected Unreachable, got {err:?}" ); }