#![cfg(feature = "remote")]
mod common;
use std::time::Duration;
use confers::remote::{HttpPolledSourceBuilder, PolledSource};
#[test]
fn test_builder_requires_url() {
let result = HttpPolledSourceBuilder::new().build();
assert!(result.is_err());
let err = result.err().unwrap();
assert!(matches!(err, confers::ConfigError::InvalidValue { .. }));
}
#[test]
fn test_builder_accepts_url() {
let source = HttpPolledSourceBuilder::new()
.url("https://example.com/config.json")
.build()
.unwrap();
assert_eq!(
source.source_id().as_str(),
"http:https://example.com/config.json"
);
}
#[test]
fn test_builder_custom_interval() {
let source = HttpPolledSourceBuilder::new()
.url("https://example.com/config.json")
.interval(Duration::from_secs(45))
.build()
.unwrap();
assert_eq!(source.poll_interval(), Some(Duration::from_secs(45)));
}
#[test]
fn test_default_poll_interval() {
let source = HttpPolledSourceBuilder::new()
.url("https://example.com/config.json")
.build()
.unwrap();
assert_eq!(source.poll_interval(), Some(Duration::from_secs(60)));
}
#[test]
fn test_builder_with_format() {
use confers::loader::Format;
let source = HttpPolledSourceBuilder::new()
.url("https://example.com/config.toml")
.format(Format::Toml)
.build()
.unwrap();
assert!(source.source_id().as_str().contains("example.com"));
}
#[test]
fn test_builder_with_timeout() {
let source = HttpPolledSourceBuilder::new()
.url("https://example.com/config.json")
.timeout(Duration::from_secs(10))
.build()
.unwrap();
assert!(source.source_id().as_str().contains("example.com"));
}
#[test]
fn test_various_url_formats() {
let source = HttpPolledSourceBuilder::new()
.url("https://www.example.com/config.json")
.build()
.unwrap();
assert!(source.source_id().as_str().contains("www.example.com"));
let source = HttpPolledSourceBuilder::new()
.url("https://example.com/v1/config")
.build()
.unwrap();
assert!(source.source_id().as_str().contains("example.com"));
let source = HttpPolledSourceBuilder::new()
.url("https://example.com/config?env=prod")
.build()
.unwrap();
assert!(source.source_id().as_str().contains("env=prod"));
}
#[test]
fn test_builder_pattern() {
use confers::loader::Format;
let source = HttpPolledSourceBuilder::new()
.url("https://example.com/config.yaml")
.interval(Duration::from_secs(30))
.format(Format::Yaml)
.timeout(Duration::from_secs(5))
.build()
.unwrap();
assert_eq!(source.poll_interval(), Some(Duration::from_secs(30)));
}
#[test]
fn test_trait_object() {
let source: Box<dyn PolledSource> = Box::new(
HttpPolledSourceBuilder::new()
.url("https://example.com/config.json")
.build()
.unwrap(),
);
let _source_id = source.source_id();
let _interval = source.poll_interval();
}
#[test]
fn test_http_url_rejected() {
let result = HttpPolledSourceBuilder::new()
.url("http://example.com/config.json")
.build();
assert!(result.is_err());
}
#[test]
fn test_private_ip_rejected() {
let result = HttpPolledSourceBuilder::new()
.url("https://127.0.0.1/config.json")
.build();
assert!(result.is_err());
let result = HttpPolledSourceBuilder::new()
.url("https://10.0.0.1/config.json")
.build();
assert!(result.is_err());
let result = HttpPolledSourceBuilder::new()
.url("https://192.168.1.1/config.json")
.build();
assert!(result.is_err());
let result = HttpPolledSourceBuilder::new()
.url("https://172.16.0.1/config.json")
.build();
assert!(result.is_err());
}
#[test]
fn test_invalid_url_rejected() {
let result = HttpPolledSourceBuilder::new()
.url("not-a-valid-url")
.build();
assert!(result.is_err());
}
#[test]
fn test_empty_url_rejected() {
let result = HttpPolledSourceBuilder::new().url("").build();
assert!(result.is_err());
}
#[cfg(feature = "etcd")]
mod etcd_tests {
use super::*;
use confers::remote::{EtcdSourceBuilder, EtcdTlsConfig};
#[test]
fn test_etcd_builder_default() {
let builder = EtcdSourceBuilder::new();
let _ = builder;
}
#[test]
fn test_etcd_builder_endpoints() {
let builder = EtcdSourceBuilder::new()
.endpoint("etcd1.example.com:2379")
.endpoints(vec![
"etcd2.example.com:2379".to_string(),
"etcd3.example.com:2379".to_string(),
]);
let _ = builder;
}
#[test]
fn test_etcd_builder_auth() {
let builder = EtcdSourceBuilder::new()
.username("testuser")
.password("testpass");
let _ = builder;
}
#[test]
fn test_etcd_builder_prefix() {
let builder = EtcdSourceBuilder::new().prefix("my-app-config");
let _ = builder;
}
#[test]
fn test_etcd_builder_interval() {
let _builder = EtcdSourceBuilder::new().interval(Duration::from_secs(45));
}
#[test]
fn test_etcd_builder_tls() {
let tls_config = EtcdTlsConfig {
ca_file: "/path/to/ca.pem".to_string(),
cert_file: "/path/to/cert.pem".to_string(),
key_file: "/path/to/key.pem".to_string(),
};
let _builder = EtcdSourceBuilder::new().tls(tls_config);
}
#[test]
fn test_etcd_builder_chaining() {
let builder = EtcdSourceBuilder::new()
.endpoint("etcd.example.com:2379")
.username("admin")
.password("secret")
.prefix("my-app")
.interval(Duration::from_secs(60));
let _ = builder;
}
#[tokio::test]
async fn test_etcd_invalid_endpoint() {
let builder = EtcdSourceBuilder::new()
.endpoint("invalid-hostname:2379")
.prefix("test");
let source = builder
.build()
.await
.expect("build() should succeed even with an invalid endpoint (lazy connect)");
let _sid = source.source_id();
}
#[tokio::test]
async fn test_etcd_connection_failure() {
let builder = EtcdSourceBuilder::new()
.endpoint("127.0.0.1:9999") .prefix("test");
let source = builder
.build()
.await
.expect("build() should succeed even with a closed port (lazy connect)");
let _sid = source.source_id();
}
#[test]
fn test_etcd_auth_env_var() {
common::with_env_var("ETCD_USERNAME", "env_user", || {
common::with_env_var("ETCD_PASSWORD", "env_pass", || {
let builder = EtcdSourceBuilder::new()
.username("ETCD_USERNAME")
.password("ETCD_PASSWORD");
let _ = builder;
});
});
}
#[tokio::test]
async fn test_etcd_tls_connection() {
let tls_config = EtcdTlsConfig {
ca_file: "/nonexistent/ca.pem".to_string(),
cert_file: "/nonexistent/cert.pem".to_string(),
key_file: "/nonexistent/key.pem".to_string(),
};
let builder = EtcdSourceBuilder::new()
.endpoint("localhost:2379")
.tls(tls_config)
.prefix("test");
let result = builder.build().await;
assert!(
result.is_ok(),
"builder should accept TLS config, but build failed: {:?}",
result.err()
);
}
}
#[cfg(feature = "consul")]
mod consul_tests {
use super::*;
use confers::remote::ConsulSourceBuilder;
#[test]
fn test_consul_builder_default() {
let builder = ConsulSourceBuilder::new();
let _ = builder;
}
#[test]
fn test_consul_builder_address() {
let builder = ConsulSourceBuilder::new().address("consul.example.com:8500");
let _ = builder;
}
#[test]
fn test_consul_builder_token() {
let builder = ConsulSourceBuilder::new().token("my-consul-token");
let _ = builder;
}
#[test]
fn test_consul_builder_prefix() {
let builder = ConsulSourceBuilder::new().prefix("my-app-config");
let _ = builder;
}
#[test]
fn test_consul_builder_interval() {
let builder = ConsulSourceBuilder::new().interval(Duration::from_secs(45));
let _ = builder;
}
#[test]
fn test_consul_builder_tls_skip_verify_debug() {
let builder = ConsulSourceBuilder::new().tls_skip_verify(true);
#[cfg(debug_assertions)]
{
let _ = builder;
}
}
#[test]
fn test_consul_builder_chaining() {
let builder = ConsulSourceBuilder::new()
.address("consul.example.com:8500")
.token("my-token")
.prefix("my-app")
.interval(Duration::from_secs(60));
let _ = builder;
}
#[tokio::test]
async fn test_consul_connection_failure() {
let builder = ConsulSourceBuilder::new()
.address("127.0.0.1:9999") .prefix("test");
let source = builder.build().expect("Builder should succeed");
let result = source.poll().await;
assert!(result.is_err());
}
#[test]
fn test_consul_tls_config() {
use confers::remote::ConsulTlsConfig;
let tls_config = ConsulTlsConfig {
ca_file: "/path/to/ca.pem".to_string(),
cert_file: "/path/to/cert.pem".to_string(),
key_file: "/path/to/key.pem".to_string(),
};
assert_eq!(tls_config.ca_file, "/path/to/ca.pem");
assert_eq!(tls_config.cert_file, "/path/to/cert.pem");
assert_eq!(tls_config.key_file, "/path/to/key.pem");
}
}
#[cfg(feature = "etcd")]
#[tokio::test]
async fn test_etcd_source_connect() {
if !common::is_service_available("http://127.0.0.1:2379/health", Duration::from_secs(2)).await {
eprintln!("Skipping test: Etcd not available");
return;
}
use confers::remote::EtcdSourceBuilder;
let source = EtcdSourceBuilder::new()
.endpoint("127.0.0.1:2379")
.prefix("test-config")
.interval(Duration::from_secs(10))
.build()
.await
.expect("Failed to build Etcd source");
let result = source.poll().await;
match result {
Ok(config) => {
println!("Got config: {:?}", config);
}
Err(e) => {
println!("Expected error (no config): {:?}", e);
}
}
}
#[cfg(feature = "etcd")]
#[tokio::test]
async fn test_etcd_source_with_auth() {
if !common::is_service_available("http://127.0.0.1:2379/health", Duration::from_secs(2)).await {
eprintln!("Skipping test: Etcd not available");
return;
}
use confers::remote::EtcdSourceBuilder;
let source = EtcdSourceBuilder::new()
.endpoint("127.0.0.1:2379")
.prefix("test-config")
.build()
.await
.expect("Failed to build Etcd source");
let result = source.poll().await;
println!("Poll result: {:?}", result);
}
#[cfg(feature = "etcd")]
#[tokio::test]
async fn test_etcd_source_parse_config() {
if !common::is_service_available("http://127.0.0.1:2379/health", Duration::from_secs(2)).await {
eprintln!("Skipping test: Etcd not available");
return;
}
use confers::remote::EtcdSourceBuilder;
let client = reqwest::Client::new();
let host_resp = client
.post("http://127.0.0.1:2379/v3/kv/put")
.json(&serde_json::json!({
"key": "dGVzdC1jb25maWcvYXBwL2hvc3Q=", "value": "bG9jYWxob3N0" }))
.send()
.await;
if host_resp.is_err() {
eprintln!("Failed to write test config to Etcd");
return;
}
let source = EtcdSourceBuilder::new()
.endpoint("127.0.0.1:2379")
.prefix("test-config")
.build()
.await
.expect("Failed to build Etcd source");
let result = source.poll().await;
match result {
Ok(config) => {
println!("Got config: {:?}", config);
assert!(!config.is_empty(), "Config should not be empty");
}
Err(e) => {
println!("Error: {:?}", e);
}
}
let _ = client
.post("http://127.0.0.1:2379/v3/kv/del")
.json(&serde_json::json!({
"key": "dGVzdC1jb25maWc="
}))
.send()
.await;
}
#[cfg(feature = "consul")]
#[tokio::test]
async fn test_consul_source_connect() {
if !common::is_service_available(
"http://127.0.0.1:8500/v1/status/leader",
Duration::from_secs(2),
)
.await
{
eprintln!("Skipping test: Consul not available");
return;
}
use confers::remote::ConsulSourceBuilder;
let source = ConsulSourceBuilder::new()
.address("127.0.0.1:8500")
.prefix("test-config")
.interval(Duration::from_secs(10))
.build()
.expect("Failed to build Consul source");
let result = source.poll().await;
match result {
Ok(config) => {
println!("Got config: {:?}", config);
}
Err(e) => {
println!("Expected error (no config): {:?}", e);
}
}
}
#[cfg(feature = "consul")]
#[tokio::test]
async fn test_consul_source_with_token() {
if !common::is_service_available(
"http://127.0.0.1:8500/v1/status/leader",
Duration::from_secs(2),
)
.await
{
eprintln!("Skipping test: Consul not available");
return;
}
use confers::remote::ConsulSourceBuilder;
let source = ConsulSourceBuilder::new()
.address("127.0.0.1:8500")
.prefix("test-config")
.build()
.expect("Failed to build Consul source");
let result = source.poll().await;
println!("Poll result: {:?}", result);
}
#[cfg(feature = "consul")]
#[tokio::test]
async fn test_consul_source_parse_config() {
if !common::is_service_available(
"http://127.0.0.1:8500/v1/status/leader",
Duration::from_secs(2),
)
.await
{
eprintln!("Skipping test: Consul not available");
return;
}
use confers::remote::ConsulSourceBuilder;
let http_client = reqwest::Client::new();
let put_result: Result<reqwest::Response, _> = http_client
.put("http://127.0.0.1:8500/v1/kv/test-config/app")
.body(r#"{"host": "localhost", "port": 8080}"#)
.send()
.await;
if put_result.is_err() {
eprintln!("Failed to write test config to Consul");
return;
}
let source = ConsulSourceBuilder::new()
.address("127.0.0.1:8500")
.prefix("test-config")
.build()
.expect("Failed to build Consul source");
let result = source.poll().await;
match result {
Ok(config) => {
println!("Got config: {:?}", config);
assert!(!config.is_empty(), "Config should not be empty");
}
Err(e) => {
println!("Error: {:?}", e);
}
}
let _ = http_client
.delete("http://127.0.0.1:8500/v1/kv/test-config")
.send()
.await;
}