fix(fxt): robust ibapi handshake retry and config test isolation

- ibapi handshake: retry once after 1s on first failure (IB Gateway
  needs time to release stale client_id slots after disconnection)
- test_load_nonexistent_config → test_load_config_succeeds: remove
  default-value assertions that fail when ~/.foxhunt/config.toml exists
  (defaults already tested by test_serde_defaults and test_default_config)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-03-02 14:52:48 +01:00
parent 032be46e90
commit 36d291a8e0
2 changed files with 40 additions and 13 deletions

View File

@@ -151,19 +151,52 @@ async fn check_tcp_connect(host: &str, port: u16) -> CheckResult {
}
/// Full ibapi handshake (blocking, runs on spawn_blocking).
/// Retries once after 1s if the first attempt fails (IB Gateway may need time
/// to release a stale client_id slot after a previous disconnection).
#[cfg(feature = "broker-check")]
async fn check_ibapi_handshake(host: &str, port: u16, client_id: i32) -> CheckResult {
let addr = format!("{host}:{port}");
let start = Instant::now();
match tokio::task::spawn_blocking(move || ibapi::Client::connect(&addr, client_id)).await {
let a1 = addr.clone();
let first = tokio::task::spawn_blocking(move || ibapi::Client::connect(&a1, client_id)).await;
match first {
Ok(Ok(client)) => {
let server_version = client.server_version();
// client is dropped here which disconnects
let sv = client.server_version();
return CheckResult {
name: "ibapi handshake".to_owned(),
passed: true,
detail: format!(
"client_id={client_id}, server v{sv} ({}ms)",
start.elapsed().as_millis()
),
duration_ms: start.elapsed().as_millis(),
};
}
Ok(Err(_)) => {
// Retry once — IB Gateway may need time to release stale client_id slot
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
Err(e) => {
return CheckResult {
name: "ibapi handshake".to_owned(),
passed: false,
detail: format!("Task join error: {e}"),
duration_ms: start.elapsed().as_millis(),
};
}
}
let a2 = addr;
match tokio::task::spawn_blocking(move || ibapi::Client::connect(&a2, client_id)).await {
Ok(Ok(client)) => {
let sv = client.server_version();
CheckResult {
name: "ibapi handshake".to_owned(),
passed: true,
detail: format!(
"client_id={client_id}, server v{server_version} ({}ms)",
"client_id={client_id}, server v{sv} ({}ms, retry)",
start.elapsed().as_millis()
),
duration_ms: start.elapsed().as_millis(),

View File

@@ -226,17 +226,11 @@ mod tests {
}
#[test]
fn test_load_nonexistent_config() {
// This should return default config without error when file doesn't exist
fn test_load_config_succeeds() {
// load() should always succeed: returns file contents if present, defaults otherwise.
// Default-value assertions live in test_serde_defaults and test_default_config.
let result = TliConfig::load();
assert!(result.is_ok());
let config = result.unwrap();
// When config file doesn't exist, load() returns Self::default()
// which should have all default values
assert_eq!(config.api_gateway_url, "http://localhost:50051");
assert_eq!(config.log_level, "info");
assert_eq!(config.token_storage, "keyring");
}
#[test]