Add comprehensive documentation and unit tests
Documentation: - Add detailed README files for all services (auth, character, database, launcher, packet, utils, world) - Create API documentation for the database service with detailed endpoint specifications - Document database schema and relationships - Add service architecture overviews and configuration instructions Unit Tests: - Implement comprehensive test suite for database repositories (user, character, session) - Add gRPC service tests for database interactions - Create tests for packet service components (bufferpool, connection, packets) - Add utility service tests (health check, logging, load balancer, redis cache, service discovery) - Implement auth service user tests - Add character service tests Code Structure: - Reorganize test files into a more consistent structure - Create a dedicated tests crate for integration testing - Add test helpers and mock implementations for easier testing
This commit is contained in:
64
tests/utils/health_check_tests.rs
Normal file
64
tests/utils/health_check_tests.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use reqwest::StatusCode;
|
||||
use std::env;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
use utils::health_check::start_health_check;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_health_check_endpoint() {
|
||||
// Set a custom port for this test to avoid conflicts
|
||||
env::set_var("HEALTH_CHECK_PORT", "8099");
|
||||
|
||||
// Start the health check endpoint
|
||||
let result = start_health_check("127.0.0.1").await;
|
||||
assert!(result.is_ok(), "Failed to start health check: {:?}", result.err());
|
||||
|
||||
// Give the server a moment to start
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
|
||||
// Make a request to the health check endpoint
|
||||
let client = reqwest::Client::new();
|
||||
let response = client.get("http://127.0.0.1:8099/health")
|
||||
.timeout(Duration::from_secs(2))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
// Verify the response
|
||||
assert!(response.is_ok(), "Failed to connect to health check endpoint: {:?}", response.err());
|
||||
let response = response.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let body = response.text().await.unwrap();
|
||||
assert_eq!(body, "OK");
|
||||
|
||||
// Clean up
|
||||
env::remove_var("HEALTH_CHECK_PORT");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_health_check_invalid_path() {
|
||||
// Set a custom port for this test to avoid conflicts
|
||||
env::set_var("HEALTH_CHECK_PORT", "8098");
|
||||
|
||||
// Start the health check endpoint
|
||||
let result = start_health_check("127.0.0.1").await;
|
||||
assert!(result.is_ok(), "Failed to start health check: {:?}", result.err());
|
||||
|
||||
// Give the server a moment to start
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
|
||||
// Make a request to an invalid path
|
||||
let client = reqwest::Client::new();
|
||||
let response = client.get("http://127.0.0.1:8098/invalid")
|
||||
.timeout(Duration::from_secs(2))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
// Verify the response
|
||||
assert!(response.is_ok(), "Failed to connect to health check endpoint: {:?}", response.err());
|
||||
let response = response.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
// Clean up
|
||||
env::remove_var("HEALTH_CHECK_PORT");
|
||||
}
|
||||
42
tests/utils/logging_tests.rs
Normal file
42
tests/utils/logging_tests.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use std::env;
|
||||
use utils::logging::setup_logging;
|
||||
|
||||
#[test]
|
||||
fn test_logging_setup() {
|
||||
// Test with default log level
|
||||
env::remove_var("LOG_LEVEL");
|
||||
setup_logging("test_app", &["test_crate"]);
|
||||
|
||||
// Test with custom log level
|
||||
env::set_var("LOG_LEVEL", "debug");
|
||||
setup_logging("test_app", &["test_crate"]);
|
||||
|
||||
// Test with invalid log level (should default to info)
|
||||
env::set_var("LOG_LEVEL", "invalid_level");
|
||||
setup_logging("test_app", &["test_crate"]);
|
||||
|
||||
// Test with multiple additional crates
|
||||
setup_logging("test_app", &["test_crate1", "test_crate2", "test_crate3"]);
|
||||
|
||||
// Clean up
|
||||
env::remove_var("LOG_LEVEL");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_logging_output() {
|
||||
// This test is more of a smoke test to ensure logging doesn't panic
|
||||
// Actual log output verification would require capturing stdout/stderr
|
||||
|
||||
env::set_var("LOG_LEVEL", "trace");
|
||||
setup_logging("test_logging", &[]);
|
||||
|
||||
// Log at different levels
|
||||
tracing::error!("This is an error message");
|
||||
tracing::warn!("This is a warning message");
|
||||
tracing::info!("This is an info message");
|
||||
tracing::debug!("This is a debug message");
|
||||
tracing::trace!("This is a trace message");
|
||||
|
||||
// Clean up
|
||||
env::remove_var("LOG_LEVEL");
|
||||
}
|
||||
151
tests/utils/multi_service_load_balancer_tests.rs
Normal file
151
tests/utils/multi_service_load_balancer_tests.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
use std::collections::HashSet;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use utils::multi_service_load_balancer::{LoadBalancingStrategy, MultiServiceLoadBalancer, ServiceId};
|
||||
|
||||
// Mock implementation for testing without actual service discovery
|
||||
mod mock {
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
// Mock version of the load balancer for testing
|
||||
pub struct MockMultiServiceLoadBalancer {
|
||||
strategy: LoadBalancingStrategy,
|
||||
services: Arc<Mutex<HashMap<ServiceId, Vec<SocketAddr>>>>,
|
||||
}
|
||||
|
||||
impl MockMultiServiceLoadBalancer {
|
||||
pub fn new(strategy: LoadBalancingStrategy) -> Self {
|
||||
MockMultiServiceLoadBalancer {
|
||||
strategy,
|
||||
services: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_service(&self, service_name: &str, service_protocol: &str, endpoints: Vec<SocketAddr>) {
|
||||
let service_id = ServiceId::new(service_name, service_protocol);
|
||||
let mut services = self.services.lock().unwrap();
|
||||
services.insert(service_id, endpoints);
|
||||
}
|
||||
|
||||
pub fn get_endpoint(&self, service_name: &str, service_protocol: &str) -> Option<SocketAddr> {
|
||||
let service_id = ServiceId::new(service_name, service_protocol);
|
||||
let services = self.services.lock().unwrap();
|
||||
|
||||
if let Some(endpoints) = services.get(&service_id) {
|
||||
if endpoints.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match self.strategy {
|
||||
LoadBalancingStrategy::Random => {
|
||||
let index = rand::random::<usize>() % endpoints.len();
|
||||
Some(endpoints[index])
|
||||
},
|
||||
LoadBalancingStrategy::RoundRobin => {
|
||||
// For simplicity in tests, just return the first endpoint
|
||||
Some(endpoints[0])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_service_id() {
|
||||
let service_id1 = ServiceId::new("service1", "http");
|
||||
let service_id2 = ServiceId::new("service1", "http");
|
||||
let service_id3 = ServiceId::new("service2", "http");
|
||||
let service_id4 = ServiceId::new("service1", "https");
|
||||
|
||||
// Test equality
|
||||
assert_eq!(service_id1, service_id2);
|
||||
assert_ne!(service_id1, service_id3);
|
||||
assert_ne!(service_id1, service_id4);
|
||||
|
||||
// Test hash implementation
|
||||
let mut set = HashSet::new();
|
||||
set.insert(service_id1);
|
||||
assert!(set.contains(&service_id2));
|
||||
assert!(!set.contains(&service_id3));
|
||||
assert!(!set.contains(&service_id4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mock_load_balancer_random() {
|
||||
let lb = mock::MockMultiServiceLoadBalancer::new(LoadBalancingStrategy::Random);
|
||||
|
||||
// Add a service with multiple endpoints
|
||||
let endpoints = vec![
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 8080),
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2)), 8080),
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 3)), 8080),
|
||||
];
|
||||
lb.add_service("test-service", "http", endpoints.clone());
|
||||
|
||||
// Get an endpoint
|
||||
let endpoint = lb.get_endpoint("test-service", "http");
|
||||
assert!(endpoint.is_some());
|
||||
assert!(endpoints.contains(&endpoint.unwrap()));
|
||||
|
||||
// Test non-existent service
|
||||
let endpoint = lb.get_endpoint("non-existent", "http");
|
||||
assert!(endpoint.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mock_load_balancer_round_robin() {
|
||||
let lb = mock::MockMultiServiceLoadBalancer::new(LoadBalancingStrategy::RoundRobin);
|
||||
|
||||
// Add a service with multiple endpoints
|
||||
let endpoints = vec![
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 8080),
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2)), 8080),
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 3)), 8080),
|
||||
];
|
||||
lb.add_service("test-service", "http", endpoints);
|
||||
|
||||
// Get an endpoint
|
||||
let endpoint = lb.get_endpoint("test-service", "http");
|
||||
assert!(endpoint.is_some());
|
||||
|
||||
// Test empty service
|
||||
lb.add_service("empty-service", "http", vec![]);
|
||||
let endpoint = lb.get_endpoint("empty-service", "http");
|
||||
assert!(endpoint.is_none());
|
||||
}
|
||||
|
||||
// Integration test with the actual MultiServiceLoadBalancer
|
||||
// This test is disabled by default as it requires a Consul server
|
||||
#[tokio::test]
|
||||
async fn test_multi_service_load_balancer() {
|
||||
use std::env;
|
||||
|
||||
// Skip test if CONSUL_TEST_ENABLED is not set to true
|
||||
if env::var("CONSUL_TEST_ENABLED").unwrap_or_else(|_| "false".to_string()) != "true" {
|
||||
println!("Skipping MultiServiceLoadBalancer test. Set CONSUL_TEST_ENABLED=true to run.");
|
||||
return;
|
||||
}
|
||||
|
||||
let consul_url = env::var("TEST_CONSUL_URL").unwrap_or_else(|_| "http://localhost:8500".to_string());
|
||||
let service_name = env::var("TEST_CONSUL_SERVICE_NAME").unwrap_or_else(|_| "database-service".to_string());
|
||||
let protocol = "tcp";
|
||||
|
||||
let lb = MultiServiceLoadBalancer::new(&consul_url, LoadBalancingStrategy::Random);
|
||||
|
||||
// Refresh service endpoints
|
||||
let result = lb.refresh_service_endpoints(&service_name, protocol).await;
|
||||
assert!(result.is_ok(), "Failed to refresh service endpoints: {:?}", result.err());
|
||||
|
||||
// Get an endpoint
|
||||
let result = lb.get_endpoint(&service_name, protocol).await;
|
||||
assert!(result.is_ok(), "Failed to get endpoint: {:?}", result.err());
|
||||
|
||||
let endpoint = result.unwrap();
|
||||
assert!(endpoint.is_some(), "No endpoint found for service {}", service_name);
|
||||
|
||||
println!("Found endpoint for service {}: {:?}", service_name, endpoint);
|
||||
}
|
||||
152
tests/utils/redis_cache_tests.rs
Normal file
152
tests/utils/redis_cache_tests.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use tokio::sync::Mutex;
|
||||
use utils::redis_cache::{Cache, RedisCache};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
struct TestData {
|
||||
id: i32,
|
||||
name: String,
|
||||
value: f64,
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_redis_cache_set_get() {
|
||||
// Skip test if REDIS_TEST_ENABLED is not set to true
|
||||
if env::var("REDIS_TEST_ENABLED").unwrap_or_else(|_| "false".to_string()) != "true" {
|
||||
println!("Skipping Redis test. Set REDIS_TEST_ENABLED=true to run.");
|
||||
return;
|
||||
}
|
||||
|
||||
let redis_url = env::var("TEST_REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
|
||||
let cache = RedisCache::new(&redis_url);
|
||||
|
||||
let key = "test_key_set_get".to_string();
|
||||
let test_data = TestData {
|
||||
id: 1,
|
||||
name: "test".to_string(),
|
||||
value: 3.14,
|
||||
};
|
||||
|
||||
// Test setting a value
|
||||
let set_result = cache.set(&key, &test_data, 10).await;
|
||||
assert!(set_result.is_ok(), "Failed to set value: {:?}", set_result.err());
|
||||
|
||||
// Test getting the value
|
||||
let get_result: Result<Option<TestData>, _> = cache.get(&key).await;
|
||||
assert!(get_result.is_ok(), "Failed to get value: {:?}", get_result.err());
|
||||
|
||||
let retrieved_data = get_result.unwrap();
|
||||
assert!(retrieved_data.is_some(), "Retrieved data is None");
|
||||
assert_eq!(retrieved_data.unwrap(), test_data);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_redis_cache_update() {
|
||||
// Skip test if REDIS_TEST_ENABLED is not set to true
|
||||
if env::var("REDIS_TEST_ENABLED").unwrap_or_else(|_| "false".to_string()) != "true" {
|
||||
println!("Skipping Redis test. Set REDIS_TEST_ENABLED=true to run.");
|
||||
return;
|
||||
}
|
||||
|
||||
let redis_url = env::var("TEST_REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
|
||||
let cache = RedisCache::new(&redis_url);
|
||||
|
||||
let key = "test_key_update".to_string();
|
||||
let initial_data = TestData {
|
||||
id: 2,
|
||||
name: "initial".to_string(),
|
||||
value: 2.71,
|
||||
};
|
||||
let updated_data = TestData {
|
||||
id: 2,
|
||||
name: "updated".to_string(),
|
||||
value: 2.71,
|
||||
};
|
||||
|
||||
// Set initial value
|
||||
let set_result = cache.set(&key, &initial_data, 10).await;
|
||||
assert!(set_result.is_ok(), "Failed to set initial value: {:?}", set_result.err());
|
||||
|
||||
// Update the value
|
||||
let update_result = cache.update(&key, Some(&updated_data), Some(10)).await;
|
||||
assert!(update_result.is_ok(), "Failed to update value: {:?}", update_result.err());
|
||||
|
||||
// Get the updated value
|
||||
let get_result: Result<Option<TestData>, _> = cache.get(&key).await;
|
||||
assert!(get_result.is_ok(), "Failed to get updated value: {:?}", get_result.err());
|
||||
|
||||
let retrieved_data = get_result.unwrap();
|
||||
assert!(retrieved_data.is_some(), "Retrieved data is None");
|
||||
assert_eq!(retrieved_data.unwrap(), updated_data);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_redis_cache_delete() {
|
||||
// Skip test if REDIS_TEST_ENABLED is not set to true
|
||||
if env::var("REDIS_TEST_ENABLED").unwrap_or_else(|_| "false".to_string()) != "true" {
|
||||
println!("Skipping Redis test. Set REDIS_TEST_ENABLED=true to run.");
|
||||
return;
|
||||
}
|
||||
|
||||
let redis_url = env::var("TEST_REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
|
||||
let mut cache = RedisCache::new(&redis_url);
|
||||
|
||||
let key = "test_key_delete".to_string();
|
||||
let test_data = TestData {
|
||||
id: 3,
|
||||
name: "delete_test".to_string(),
|
||||
value: 1.618,
|
||||
};
|
||||
|
||||
// Set a value
|
||||
let set_result = cache.set(&key, &test_data, 10).await;
|
||||
assert!(set_result.is_ok(), "Failed to set value: {:?}", set_result.err());
|
||||
|
||||
// Delete the value
|
||||
let delete_result = cache.delete(&key).await;
|
||||
assert!(delete_result.is_ok(), "Failed to delete value: {:?}", delete_result.err());
|
||||
|
||||
// Verify the value is gone
|
||||
let get_result: Result<Option<TestData>, _> = cache.get(&key).await;
|
||||
assert!(get_result.is_ok(), "Failed to get value after deletion: {:?}", get_result.err());
|
||||
assert!(get_result.unwrap().is_none(), "Value still exists after deletion");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_redis_cache_refresh() {
|
||||
// Skip test if REDIS_TEST_ENABLED is not set to true
|
||||
if env::var("REDIS_TEST_ENABLED").unwrap_or_else(|_| "false".to_string()) != "true" {
|
||||
println!("Skipping Redis test. Set REDIS_TEST_ENABLED=true to run.");
|
||||
return;
|
||||
}
|
||||
|
||||
let redis_url = env::var("TEST_REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
|
||||
let cache = RedisCache::new(&redis_url);
|
||||
|
||||
let key = "test_key_refresh".to_string();
|
||||
let test_data = TestData {
|
||||
id: 4,
|
||||
name: "refresh_test".to_string(),
|
||||
value: 0.577,
|
||||
};
|
||||
|
||||
// Set a value with a short TTL
|
||||
let set_result = cache.set(&key, &test_data, 5).await;
|
||||
assert!(set_result.is_ok(), "Failed to set value: {:?}", set_result.err());
|
||||
|
||||
// Refresh the TTL
|
||||
let refresh_result = cache.refresh(&key, 30).await;
|
||||
assert!(refresh_result.is_ok(), "Failed to refresh TTL: {:?}", refresh_result.err());
|
||||
|
||||
// Wait for the original TTL to expire
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(6)).await;
|
||||
|
||||
// Verify the value still exists due to the refresh
|
||||
let get_result: Result<Option<TestData>, _> = cache.get(&key).await;
|
||||
assert!(get_result.is_ok(), "Failed to get value after refresh: {:?}", get_result.err());
|
||||
|
||||
let retrieved_data = get_result.unwrap();
|
||||
assert!(retrieved_data.is_some(), "Value expired despite TTL refresh");
|
||||
assert_eq!(retrieved_data.unwrap(), test_data);
|
||||
}
|
||||
84
tests/utils/service_discovery_tests.rs
Normal file
84
tests/utils/service_discovery_tests.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use std::env;
|
||||
use std::net::SocketAddr;
|
||||
use utils::service_discovery::{get_kube_service_endpoints_by_dns, get_service_endpoints_by_dns};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_kube_service_endpoints_by_dns() {
|
||||
// Skip test if KUBE_TEST_ENABLED is not set to true
|
||||
if env::var("KUBE_TEST_ENABLED").unwrap_or_else(|_| "false".to_string()) != "true" {
|
||||
println!("Skipping Kubernetes DNS test. Set KUBE_TEST_ENABLED=true to run.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Test with a known Kubernetes service
|
||||
let service_name = env::var("TEST_K8S_SERVICE_NAME").unwrap_or_else(|_| "database-service".to_string());
|
||||
let port_name = env::var("TEST_K8S_PORT_NAME").unwrap_or_else(|_| "database-service".to_string());
|
||||
let protocol = "tcp";
|
||||
|
||||
let result = get_kube_service_endpoints_by_dns(&port_name, protocol, &service_name).await;
|
||||
|
||||
assert!(result.is_ok(), "Failed to get Kubernetes service endpoints: {:?}", result.err());
|
||||
|
||||
let endpoints = result.unwrap();
|
||||
assert!(!endpoints.is_empty(), "No endpoints found for service {}", service_name);
|
||||
|
||||
// Verify that the endpoints are valid socket addresses
|
||||
for endpoint in &endpoints {
|
||||
assert!(endpoint.port() > 0, "Invalid port in endpoint: {}", endpoint);
|
||||
}
|
||||
|
||||
println!("Found {} endpoints for service {}: {:?}", endpoints.len(), service_name, endpoints);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_service_endpoints_by_dns() {
|
||||
// Skip test if CONSUL_TEST_ENABLED is not set to true
|
||||
if env::var("CONSUL_TEST_ENABLED").unwrap_or_else(|_| "false".to_string()) != "true" {
|
||||
println!("Skipping Consul DNS test. Set CONSUL_TEST_ENABLED=true to run.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Test with a known Consul service
|
||||
let consul_url = env::var("TEST_CONSUL_URL").unwrap_or_else(|_| "127.0.0.1:8600".to_string());
|
||||
let service_name = env::var("TEST_CONSUL_SERVICE_NAME").unwrap_or_else(|_| "database-service".to_string());
|
||||
let protocol = "tcp";
|
||||
|
||||
let result = get_service_endpoints_by_dns(&consul_url, protocol, &service_name).await;
|
||||
|
||||
assert!(result.is_ok(), "Failed to get Consul service endpoints: {:?}", result.err());
|
||||
|
||||
let endpoints = result.unwrap();
|
||||
assert!(!endpoints.is_empty(), "No endpoints found for service {}", service_name);
|
||||
|
||||
// Verify that the endpoints are valid socket addresses
|
||||
for endpoint in &endpoints {
|
||||
assert!(endpoint.port() > 0, "Invalid port in endpoint: {}", endpoint);
|
||||
}
|
||||
|
||||
println!("Found {} endpoints for service {}: {:?}", endpoints.len(), service_name, endpoints);
|
||||
}
|
||||
|
||||
// Mock tests that don't require actual infrastructure
|
||||
mod mock_tests {
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_socket_addr_parsing() {
|
||||
// Test that we can parse socket addresses correctly
|
||||
let addr_str = "127.0.0.1:8080";
|
||||
let addr = SocketAddr::from_str(addr_str).unwrap();
|
||||
|
||||
assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
|
||||
assert_eq!(addr.port(), 8080);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_socket_addr_formatting() {
|
||||
// Test that we can format socket addresses correctly
|
||||
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 9000);
|
||||
let addr_str = format!("{}", addr);
|
||||
|
||||
assert_eq!(addr_str, "192.168.1.1:9000");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user