use std::sync::Arc;
use super::steam_client::SteamClient;
use crate::{
options::SteamOptions,
utils::{
clock::{Clock, MockClock, SystemClock},
http::{HttpClient, MockHttpClient, ReqwestHttpClient},
rng::{MockRng, Rng, ThreadRng},
},
};
pub struct SteamClientBuilder {
options: Option<SteamOptions>,
http_client: Option<Arc<dyn HttpClient>>,
clock: Option<Arc<dyn Clock>>,
rng: Option<Arc<dyn Rng>>,
mock_http: Option<Arc<MockHttpClient>>,
mock_clock: Option<Arc<MockClock>>,
mock_rng: Option<Arc<MockRng>>,
}
impl Default for SteamClientBuilder {
fn default() -> Self {
Self::new()
}
}
impl SteamClientBuilder {
pub fn new() -> Self {
Self { options: None, http_client: None, clock: None, rng: None, mock_http: None, mock_clock: None, mock_rng: None }
}
pub fn with_options(mut self, mut options: SteamOptions) -> Self {
if let Some(proxy) = &mut options.http_proxy {
if !proxy.contains("://") {
*proxy = format!("http://{}", proxy);
}
}
self.options = Some(options);
self
}
pub fn with_http_client(mut self, client: Arc<dyn HttpClient>) -> Self {
self.http_client = Some(client);
self.mock_http = None;
self
}
pub fn with_mock_http(mut self) -> Self {
let mock = Arc::new(MockHttpClient::new());
self.mock_http = Some(mock.clone());
self.http_client = Some(mock);
self
}
pub fn with_mock_http_responses(mut self, responses: Vec<crate::utils::http::HttpResponse>) -> Self {
let mock = MockHttpClient::new();
mock.queue_responses(responses);
let mock = Arc::new(mock);
self.mock_http = Some(mock.clone());
self.http_client = Some(mock);
self
}
pub fn with_clock(mut self, clock: Arc<dyn Clock>) -> Self {
self.clock = Some(clock);
self.mock_clock = None;
self
}
pub fn with_mock_clock(mut self) -> Self {
let mock = Arc::new(MockClock::new());
self.mock_clock = Some(mock.clone());
self.clock = Some(mock);
self
}
pub fn with_rng(mut self, rng: Arc<dyn Rng>) -> Self {
self.rng = Some(rng);
self.mock_rng = None;
self
}
pub fn with_mock_rng(mut self) -> Self {
let mock = Arc::new(MockRng::new());
self.mock_rng = Some(mock.clone());
self.rng = Some(mock);
self
}
pub fn with_mock_rng_values(mut self, usize_val: usize, i32_val: i32, u32_val: u32) -> Self {
let mock = Arc::new(MockRng::with_values(usize_val, i32_val, u32_val));
self.mock_rng = Some(mock.clone());
self.rng = Some(mock);
self
}
pub fn with_all_mocks(self) -> Self {
self.with_mock_http().with_mock_clock().with_mock_rng()
}
pub fn build(self) -> SteamClient {
let mut options = self.options.unwrap_or_default();
if options.web_compatibility_mode && options.protocol == crate::options::EConnectionProtocol::Tcp {
tracing::warn!("web_compatibility_mode is enabled so connection protocol is being forced to WebSocket");
options.protocol = crate::options::EConnectionProtocol::WebSocket;
}
let http_client = self.http_client.unwrap_or_else(|| Arc::new(ReqwestHttpClient::new()));
let clock = self.clock.unwrap_or_else(|| Arc::new(SystemClock));
let rng = self.rng.unwrap_or_else(|| Arc::new(ThreadRng));
SteamClient::with_all_providers(options, http_client, clock, rng)
}
pub fn build_with_mocks(self) -> (SteamClient, MockHandles) {
let mocks = MockHandles { http: self.mock_http.clone(), clock: self.mock_clock.clone(), rng: self.mock_rng.clone() };
(self.build(), mocks)
}
}
#[derive(Clone)]
pub struct MockHandles {
pub http: Option<Arc<MockHttpClient>>,
pub clock: Option<Arc<MockClock>>,
pub rng: Option<Arc<MockRng>>,
}
impl MockHandles {
pub fn has_any(&self) -> bool {
self.http.is_some() || self.clock.is_some() || self.rng.is_some()
}
pub fn http_or_panic(&self) -> &MockHttpClient {
self.http.as_ref().expect("No mock HTTP client configured")
}
pub fn clock_or_panic(&self) -> &MockClock {
self.clock.as_ref().expect("No mock clock configured")
}
pub fn rng_or_panic(&self) -> &MockRng {
self.rng.as_ref().expect("No mock RNG configured")
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
use crate::utils::http::HttpResponse;
#[test]
fn test_builder_default() {
let client = SteamClientBuilder::new().build();
assert!(!client.is_logged_in());
}
#[test]
fn test_builder_with_options() {
let options = SteamOptions { auto_relogin: false, ..Default::default() };
let client = SteamClient::builder().with_options(options).build();
assert!(!client.options.auto_relogin);
}
#[test]
fn test_builder_with_mock_http() {
let (client, mocks) = SteamClient::builder().with_mock_http().build_with_mocks();
assert!(!client.is_logged_in());
assert!(mocks.http.is_some());
assert_eq!(mocks.http_or_panic().request_count(), 0);
}
#[test]
fn test_builder_with_mock_clock() {
let (client, mocks) = SteamClient::builder().with_mock_clock().build_with_mocks();
assert!(!client.is_logged_in());
assert!(mocks.clock.is_some());
let clock = mocks.clock_or_panic();
clock.advance(Duration::from_secs(30));
assert_eq!(clock.current_offset(), Duration::from_secs(30));
}
#[test]
fn test_builder_with_mock_rng() {
let (client, mocks) = SteamClient::builder().with_mock_rng_values(42, -1, 100).build_with_mocks();
assert!(!client.is_logged_in());
assert!(mocks.rng.is_some());
let rng = mocks.rng_or_panic();
assert_eq!(rng.current_usize(), 42);
assert_eq!(rng.current_i32(), -1);
assert_eq!(rng.current_u32(), 100);
}
#[test]
fn test_builder_with_all_mocks() {
let (client, mocks) = SteamClient::builder().with_all_mocks().build_with_mocks();
assert!(!client.is_logged_in());
assert!(mocks.has_any());
assert!(mocks.http.is_some());
assert!(mocks.clock.is_some());
assert!(mocks.rng.is_some());
}
#[test]
fn test_builder_with_mock_http_responses() {
let responses = vec![HttpResponse::ok(b"response1".to_vec()), HttpResponse::ok(b"response2".to_vec())];
let (_, mocks) = SteamClient::builder().with_mock_http_responses(responses).build_with_mocks();
assert!(mocks.http.is_some());
}
#[test]
fn test_steam_client_builder_method() {
let client = SteamClient::builder().build();
assert!(!client.is_logged_in());
}
#[test]
fn test_mock_handles_clone() {
let (_, mocks) = SteamClient::builder().with_mock_http().with_mock_clock().build_with_mocks();
let cloned = mocks.clone();
mocks.clock_or_panic().advance(Duration::from_secs(5));
assert_eq!(cloned.clock_or_panic().current_offset(), Duration::from_secs(5));
}
}