Move 17 library crates into crates/, CLI binary into bin/fxt, consolidate 10 test crates into testing/, split config crate from deployment config files. Root directory reduced from 38+ to ~17 directories. All Cargo.toml paths and build.rs proto refs updated. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
314 lines
9.5 KiB
Rust
314 lines
9.5 KiB
Rust
//! TLI Regime Command Tests (Wave D)
|
|
//!
|
|
//! Test suite for validating Wave D regime detection commands in the TLI client.
|
|
//! Tests command parsing, execution flow, output formatting, and error handling.
|
|
|
|
// Suppress false-positive unused_crate_dependencies warnings
|
|
// dev-dependencies are shared across ALL test targets in the crate
|
|
// This test may not use all deps, but they are required by other integration tests
|
|
#![allow(unused_crate_dependencies)]
|
|
|
|
use fxt::commands::trade_ml::{TradeMlArgs, TradeMlCommand};
|
|
|
|
#[tokio::test]
|
|
async fn test_regime_command_parses() {
|
|
// Test that regime command structure is correct
|
|
let args = TradeMlArgs {
|
|
command: TradeMlCommand::Regime {
|
|
symbol: "ES.FUT".to_owned(),
|
|
},
|
|
};
|
|
|
|
// Execution will fail without running API Gateway, but command should parse
|
|
let result = args.execute("http://localhost:50051", "mock-token").await;
|
|
assert!(
|
|
result.is_err(),
|
|
"Expected connection error without running server"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_transitions_command_parses() {
|
|
// Test that transitions command structure is correct
|
|
let args = TradeMlArgs {
|
|
command: TradeMlCommand::Transitions {
|
|
symbol: "ES.FUT".to_owned(),
|
|
limit: 20,
|
|
},
|
|
};
|
|
|
|
// Execution will fail without running API Gateway, but command should parse
|
|
let result = args.execute("http://localhost:50051", "mock-token").await;
|
|
assert!(
|
|
result.is_err(),
|
|
"Expected connection error without running server"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_regime_command_default_limit() {
|
|
// Test default limit value for transitions
|
|
let args = TradeMlArgs {
|
|
command: TradeMlCommand::Transitions {
|
|
symbol: "NQ.FUT".to_owned(),
|
|
limit: 100, // Default from clap
|
|
},
|
|
};
|
|
|
|
match args.command {
|
|
TradeMlCommand::Transitions { symbol, limit } => {
|
|
assert_eq!(symbol, "NQ.FUT");
|
|
assert_eq!(limit, 100);
|
|
},
|
|
_ => panic!("Expected Transitions command"),
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_regime_command_custom_limit() {
|
|
// Test custom limit value for transitions
|
|
let args = TradeMlArgs {
|
|
command: TradeMlCommand::Transitions {
|
|
symbol: "CL.FUT".to_owned(),
|
|
limit: 50,
|
|
},
|
|
};
|
|
|
|
match args.command {
|
|
TradeMlCommand::Transitions { symbol, limit } => {
|
|
assert_eq!(symbol, "CL.FUT");
|
|
assert_eq!(limit, 50);
|
|
},
|
|
_ => panic!("Expected Transitions command"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_regime_command_symbol_validation() {
|
|
// Test that various symbol formats are accepted
|
|
let symbols = vec!["ES.FUT", "NQ.FUT", "6E.FUT", "ZN.FUT", "CL.FUT"];
|
|
|
|
for symbol in symbols {
|
|
let args = TradeMlArgs {
|
|
command: TradeMlCommand::Regime {
|
|
symbol: symbol.to_owned(),
|
|
},
|
|
};
|
|
|
|
match args.command {
|
|
TradeMlCommand::Regime { symbol: s } => {
|
|
assert_eq!(s, symbol);
|
|
assert!(s.contains(".FUT"), "Symbol should be a futures contract");
|
|
},
|
|
_ => panic!("Expected Regime command"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_transitions_limit_bounds() {
|
|
// Test limit parameter edge cases
|
|
let test_cases = vec![
|
|
(1, true), // Minimum valid
|
|
(10, true), // Small limit
|
|
(100, true), // Default
|
|
(500, true), // Large limit
|
|
(1000, true), // Very large limit
|
|
];
|
|
|
|
for (limit, should_be_valid) in test_cases {
|
|
let args = TradeMlArgs {
|
|
command: TradeMlCommand::Transitions {
|
|
symbol: "ES.FUT".to_owned(),
|
|
limit,
|
|
},
|
|
};
|
|
|
|
match args.command {
|
|
TradeMlCommand::Transitions { limit: l, .. } => {
|
|
assert_eq!(l, limit);
|
|
if should_be_valid {
|
|
assert!(l > 0, "Limit should be positive");
|
|
}
|
|
},
|
|
_ => panic!("Expected Transitions command"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Integration test: Verify command execution flow (without server connection)
|
|
#[tokio::test]
|
|
async fn test_regime_command_execution_flow() {
|
|
// Test that commands follow correct execution path
|
|
let args = TradeMlArgs {
|
|
command: TradeMlCommand::Regime {
|
|
symbol: "ES.FUT".to_owned(),
|
|
},
|
|
};
|
|
|
|
// Execute should attempt gRPC connection and fail gracefully
|
|
let result = args.execute("http://localhost:50051", "mock-token").await;
|
|
|
|
// Expect either network error (connection refused) or auth error (invalid token)
|
|
assert!(result.is_err());
|
|
let error_msg = format!("{:?}", result.unwrap_err());
|
|
assert!(
|
|
error_msg.contains("connect")
|
|
|| error_msg.contains("connection")
|
|
|| error_msg.contains("refused")
|
|
|| error_msg.contains("authentication")
|
|
|| error_msg.contains("credentials")
|
|
|| error_msg.contains("token"),
|
|
"Expected connection or auth error, got: {}",
|
|
error_msg
|
|
);
|
|
}
|
|
|
|
/// Integration test: Verify transitions command execution flow
|
|
#[tokio::test]
|
|
async fn test_transitions_command_execution_flow() {
|
|
let args = TradeMlArgs {
|
|
command: TradeMlCommand::Transitions {
|
|
symbol: "NQ.FUT".to_owned(),
|
|
limit: 25,
|
|
},
|
|
};
|
|
|
|
// Execute should attempt gRPC connection and fail gracefully
|
|
let result = args.execute("http://localhost:50051", "mock-token").await;
|
|
|
|
// Expect either network error (connection refused) or auth error (invalid token)
|
|
assert!(result.is_err());
|
|
let error_msg = format!("{:?}", result.unwrap_err());
|
|
assert!(
|
|
error_msg.contains("connect")
|
|
|| error_msg.contains("connection")
|
|
|| error_msg.contains("refused")
|
|
|| error_msg.contains("authentication")
|
|
|| error_msg.contains("credentials")
|
|
|| error_msg.contains("token"),
|
|
"Expected connection or auth error, got: {}",
|
|
error_msg
|
|
);
|
|
}
|
|
|
|
/// Test command enum variants are correctly defined
|
|
#[test]
|
|
fn test_regime_command_variants() {
|
|
// Verify Regime variant exists and has correct fields
|
|
let _regime = TradeMlCommand::Regime {
|
|
symbol: "TEST.FUT".to_owned(),
|
|
};
|
|
|
|
// Verify Transitions variant exists and has correct fields
|
|
let _transitions = TradeMlCommand::Transitions {
|
|
symbol: "TEST.FUT".to_owned(),
|
|
limit: 100,
|
|
};
|
|
|
|
// If compilation succeeds, variants are correctly defined
|
|
}
|
|
|
|
/// Test error handling for invalid JWT tokens
|
|
#[tokio::test]
|
|
async fn test_regime_invalid_jwt_handling() {
|
|
let args = TradeMlArgs {
|
|
command: TradeMlCommand::Regime {
|
|
symbol: "ES.FUT".to_owned(),
|
|
},
|
|
};
|
|
|
|
// Test with empty JWT token
|
|
let result = args.execute("http://localhost:50051", "").await;
|
|
assert!(result.is_err(), "Empty JWT should fail");
|
|
|
|
// Test with malformed JWT token
|
|
let result = args
|
|
.execute("http://localhost:50051", "invalid-jwt-format!@#$")
|
|
.await;
|
|
assert!(result.is_err(), "Invalid JWT format should fail");
|
|
}
|
|
|
|
/// Test error handling for invalid URLs
|
|
#[tokio::test]
|
|
async fn test_regime_invalid_url_handling() {
|
|
let args = TradeMlArgs {
|
|
command: TradeMlCommand::Regime {
|
|
symbol: "ES.FUT".to_owned(),
|
|
},
|
|
};
|
|
|
|
// Test with invalid URL format
|
|
let result = args.execute("not-a-valid-url", "mock-token").await;
|
|
assert!(result.is_err(), "Invalid URL should fail");
|
|
|
|
// Test with unreachable host
|
|
let result = args
|
|
.execute(
|
|
"http://invalid-host-that-does-not-exist:50051",
|
|
"mock-token",
|
|
)
|
|
.await;
|
|
assert!(result.is_err(), "Unreachable host should fail");
|
|
}
|
|
|
|
/// Test concurrent command execution (simulated)
|
|
#[tokio::test]
|
|
async fn test_concurrent_regime_commands() {
|
|
let symbols = vec!["ES.FUT", "NQ.FUT", "CL.FUT", "ZN.FUT"];
|
|
|
|
let mut handles = vec![];
|
|
|
|
for symbol in symbols {
|
|
let symbol_owned = symbol.to_owned();
|
|
let handle = tokio::spawn(async move {
|
|
let args = TradeMlArgs {
|
|
command: TradeMlCommand::Regime {
|
|
symbol: symbol_owned,
|
|
},
|
|
};
|
|
args.execute("http://localhost:50051", "mock-token").await
|
|
});
|
|
handles.push(handle);
|
|
}
|
|
|
|
// All commands should fail with connection errors (no server running)
|
|
for handle in handles {
|
|
let result = handle.await.expect("Task should complete");
|
|
assert!(result.is_err(), "Expected connection error without server");
|
|
}
|
|
}
|
|
|
|
/// Test transitions command with multiple symbols concurrently
|
|
#[tokio::test]
|
|
async fn test_concurrent_transitions_commands() {
|
|
let test_cases = vec![
|
|
("ES.FUT", 10),
|
|
("NQ.FUT", 20),
|
|
("CL.FUT", 50),
|
|
("ZN.FUT", 100),
|
|
];
|
|
|
|
let mut handles = vec![];
|
|
|
|
for (symbol, limit) in test_cases {
|
|
let symbol_owned = symbol.to_owned();
|
|
let handle = tokio::spawn(async move {
|
|
let args = TradeMlArgs {
|
|
command: TradeMlCommand::Transitions {
|
|
symbol: symbol_owned,
|
|
limit,
|
|
},
|
|
};
|
|
args.execute("http://localhost:50051", "mock-token").await
|
|
});
|
|
handles.push(handle);
|
|
}
|
|
|
|
// All commands should fail with connection errors (no server running)
|
|
for handle in handles {
|
|
let result = handle.await.expect("Task should complete");
|
|
assert!(result.is_err(), "Expected connection error without server");
|
|
}
|
|
}
|