mod common; use common::*; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use happy_eyeballs::{ ConnectionAttemptHttpVersions, Endpoint, FailureReason, HappyEyeballs, Id, Output, }; /// All DNS queries fail. No connections are attempted. #[test] fn all_dns_failed() { let (now, mut he) = setup(); he.expect( vec![ (None, Some(out_send_dns_https(Id::from(0)))), (None, Some(out_send_dns_aaaa(Id::from(1)))), (None, Some(out_send_dns_a(Id::from(2)))), ( Some(in_dns_https_negative(Id::from(0))), Some(out_resolution_delay()), ), ( Some(in_dns_aaaa_negative(Id::from(1))), Some(out_resolution_delay()), ), ( Some(in_dns_a_negative(Id::from(2))), Some(Output::Failed(FailureReason::DnsResolution)), ), ], now, ); } /// DNS partially fails (HTTPS and A fail) but AAAA succeeds, then connection fails. #[test] fn dns_partial_failure_then_connection_failed() { let (now, mut he) = setup(); he.expect( vec![ (None, Some(out_send_dns_https(Id::from(0)))), (None, Some(out_send_dns_aaaa(Id::from(1)))), (None, Some(out_send_dns_a(Id::from(2)))), ( Some(in_dns_https_negative(Id::from(0))), Some(out_resolution_delay()), ), ( Some(in_dns_aaaa_positive(Id::from(1))), Some(out_attempt_v6_h1_h2(Id::from(3))), ), ( Some(in_dns_a_negative(Id::from(2))), Some(out_connection_attempt_delay()), ), ( Some(in_connection_result_negative(Id::from(3))), Some(Output::Failed(FailureReason::Connection)), ), ], now, ); } /// All DNS succeeds but all connection attempts fail. #[test] fn all_connections_failed() { let (now, mut he) = setup(); he.expect( vec![ (None, Some(out_send_dns_https(Id::from(0)))), (None, Some(out_send_dns_aaaa(Id::from(1)))), (None, Some(out_send_dns_a(Id::from(2)))), ( Some(in_dns_https_positive_no_alpn(Id::from(0))), Some(out_resolution_delay()), ), ( Some(in_dns_aaaa_positive(Id::from(1))), Some(out_attempt_v6_h1_h2(Id::from(3))), ), ( Some(in_dns_a_positive(Id::from(2))), Some(out_connection_attempt_delay()), ), ( Some(in_connection_result_negative(Id::from(3))), Some(out_attempt_v4_h1_h2(Id::from(4))), ), ( Some(in_connection_result_negative(Id::from(4))), Some(Output::Failed(FailureReason::Connection)), ), ], now, ); } /// When the target is an IP address and the connection fails, the state /// machine must report `Failed(Connection)` instead of retrying the same /// endpoint indefinitely. #[test] fn ip_host_connection_failure() { let now = std::time::Instant::now(); let mut he = HappyEyeballs::new("127.0.0.1", PORT).unwrap(); he.expect( vec![ ( None, Some(Output::AttemptConnection { id: Id::from(0), endpoint: Endpoint { address: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), PORT), http_version: ConnectionAttemptHttpVersions::H2OrH1, ech_config: None, }, }), ), ( Some(in_connection_result_negative(Id::from(0))), Some(Output::Failed(FailureReason::Connection)), ), ], now, ); } /// First connection fails, second succeeds. Should not emit `Failed`. #[test] fn first_connection_fails_second_succeeds() { let (now, mut he) = setup(); he.expect( vec![ (None, Some(out_send_dns_https(Id::from(0)))), (None, Some(out_send_dns_aaaa(Id::from(1)))), (None, Some(out_send_dns_a(Id::from(2)))), ( Some(in_dns_https_positive_no_alpn(Id::from(0))), Some(out_resolution_delay()), ), ( Some(in_dns_aaaa_positive(Id::from(1))), Some(out_attempt_v6_h1_h2(Id::from(3))), ), ( Some(in_dns_a_positive(Id::from(2))), Some(out_connection_attempt_delay()), ), ( Some(in_connection_result_negative(Id::from(3))), Some(out_attempt_v4_h1_h2(Id::from(4))), ), ( Some(in_connection_result_positive(Id::from(4))), Some(Output::Succeeded), ), ], now, ); }