- Fixed systematic array indexing corruption: [0_i32] → [0] - Fixed numeric literal suffixes across 835 files - Fixed iterator patterns on RwLockReadGuard (.iter() required) - Fixed float type annotations (365.25_f64 for sqrt) - Fixed missing semicolons in position manager - Fixed reference dereferencing in data loader Root cause: Mass refactoring incorrectly added _i32 suffixes to array indices Impact: Complete compilation failure (463 errors) Resolution: Automated regex + targeted fixes Result: 100% compilation success (0 errors) Validated: cargo check --workspace passes Ready for: Production deployment
631 lines
21 KiB
Rust
631 lines
21 KiB
Rust
//! End-to-End Integration Tests: API Gateway → Backtesting Service
|
|
//!
|
|
//! This test suite validates the complete backtesting service flow through the API Gateway:
|
|
//! - Backtest start/stop/status operations
|
|
//! - Strategy execution with historical data
|
|
//! - Results retrieval and validation
|
|
//! - Real-time progress monitoring
|
|
//! - Backtest listing and filtering
|
|
//!
|
|
//! Test Count: 12 tests
|
|
//! Coverage: Full backtesting service integration
|
|
|
|
use anyhow::Result;
|
|
use chrono::{Duration, Utc};
|
|
use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::time::Duration as StdDuration;
|
|
use tokio::time::timeout;
|
|
use tonic::{metadata::MetadataValue, transport::Channel, Request};
|
|
use uuid::Uuid;
|
|
|
|
// Generated proto code
|
|
pub mod trading {
|
|
tonic::include_proto!("foxhunt.tli");
|
|
}
|
|
|
|
use trading::{
|
|
backtesting_service_client::BacktestingServiceClient,
|
|
BacktestStatus,
|
|
GetBacktestResultsRequest,
|
|
GetBacktestStatusRequest,
|
|
ListBacktestsRequest,
|
|
StartBacktestRequest,
|
|
StopBacktestRequest,
|
|
SubscribeBacktestProgressRequest,
|
|
};
|
|
|
|
const API_GATEWAY_ADDR: &str = "http://localhost:50050";
|
|
const JWT_SECRET: &str = "dev_secret_key_change_in_production";
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
struct Claims {
|
|
sub: String,
|
|
exp: usize,
|
|
iat: usize,
|
|
roles: Vec<String>,
|
|
permissions: Vec<String>,
|
|
session_id: Option<String>,
|
|
jti: String,
|
|
}
|
|
|
|
/// Generate a test JWT token for authentication
|
|
fn generate_test_token(user_id: &str, roles: Vec<String>, permissions: Vec<String>) -> Result<String> {
|
|
let now = Utc::now();
|
|
let claims = Claims {
|
|
sub: user_id.to_string(),
|
|
exp: (now + Duration::hours(1)).timestamp() as usize,
|
|
iat: now.timestamp() as usize,
|
|
roles,
|
|
permissions,
|
|
session_id: Some(Uuid::new_v4().to_string()),
|
|
jti: Uuid::new_v4().to_string(),
|
|
};
|
|
|
|
let token = encode(
|
|
&Header::new(Algorithm::HS256),
|
|
&claims,
|
|
&EncodingKey::from_secret(JWT_SECRET.as_ref()),
|
|
)?;
|
|
|
|
Ok(token)
|
|
}
|
|
|
|
/// Create an authenticated backtesting service client
|
|
async fn create_authenticated_client() -> Result<BacktestingServiceClient<tonic::service::interceptor::InterceptedService<Channel, impl Fn(Request<()>) -> Result<Request<()>, tonic::Status> + Clone>>> {
|
|
let user_id = "test_backtester_001";
|
|
let role = "analyst";
|
|
|
|
let token = generate_test_token(
|
|
user_id,
|
|
vec![role.to_string()],
|
|
vec![
|
|
"api.access".to_string(),
|
|
"backtesting.execute".to_string(),
|
|
"backtesting.view".to_string(),
|
|
],
|
|
)?;
|
|
|
|
let channel = Channel::from_static(API_GATEWAY_ADDR)
|
|
.connect()
|
|
.await?;
|
|
|
|
// Create interceptor that injects JWT token AND user context into request metadata
|
|
let user_id_owned = user_id.to_string();
|
|
let role_owned = role.to_string();
|
|
|
|
let interceptor = move |mut req: Request<()>| -> Result<Request<()>, tonic::Status> {
|
|
// JWT token in authorization header
|
|
let token_value = format!("Bearer {}", token);
|
|
let metadata_value = MetadataValue::try_from(token_value)
|
|
.map_err(|_| tonic::Status::internal("Failed to create metadata value"))?;
|
|
req.metadata_mut().insert("authorization", metadata_value);
|
|
|
|
// User context in metadata headers
|
|
let user_id_value = MetadataValue::try_from(user_id_owned.clone())
|
|
.map_err(|_| tonic::Status::internal("Failed to create user_id metadata"))?;
|
|
req.metadata_mut().insert("x-user-id", user_id_value);
|
|
|
|
let role_value = MetadataValue::try_from(role_owned.clone())
|
|
.map_err(|_| tonic::Status::internal("Failed to create role metadata"))?;
|
|
req.metadata_mut().insert("x-user-role", role_value);
|
|
|
|
Ok(req)
|
|
};
|
|
|
|
let client = BacktestingServiceClient::with_interceptor(channel, interceptor);
|
|
|
|
Ok(client)
|
|
}
|
|
|
|
// ============================================================================
|
|
// SECTION 1: BACKTEST LIFECYCLE E2E TESTS (5 tests)
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running services
|
|
async fn test_e2e_backtest_start() -> Result<()> {
|
|
println!("\n=== E2E Test: Start Backtest via API Gateway ===");
|
|
|
|
let mut client = create_authenticated_client().await?;
|
|
|
|
let start_date = (Utc::now() - Duration::days(30)).timestamp_nanos_opt().unwrap_or(0);
|
|
let end_date = Utc::now().timestamp_nanos_opt().unwrap_or(0);
|
|
|
|
let mut parameters = HashMap::new();
|
|
parameters.insert("fast_ma".to_string(), "10".to_string());
|
|
parameters.insert("slow_ma".to_string(), "30".to_string());
|
|
parameters.insert("risk_per_trade".to_string(), "0.02".to_string());
|
|
|
|
let request = Request::new(StartBacktestRequest {
|
|
strategy_name: "moving_average_crossover".to_string(),
|
|
symbols: vec!["BTC/USD".to_string(), "ETH/USD".to_string()],
|
|
start_date_unix_nanos: start_date,
|
|
end_date_unix_nanos: end_date,
|
|
initial_capital: 100000.0,
|
|
parameters,
|
|
save_results: true,
|
|
description: "E2E test backtest - MA crossover strategy".to_string(),
|
|
});
|
|
|
|
let response = client.start_backtest(request).await?;
|
|
let result = response.into_inner();
|
|
|
|
assert!(result.success, "Backtest should start successfully");
|
|
assert!(!result.backtest_id.is_empty(), "Should return backtest ID");
|
|
|
|
println!("✓ Backtest started successfully");
|
|
println!(" Backtest ID: {}", result.backtest_id);
|
|
println!(" Estimated Duration: {}s", result.estimated_duration_seconds);
|
|
println!(" Message: {}", result.message);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running services
|
|
async fn test_e2e_backtest_status() -> Result<()> {
|
|
println!("\n=== E2E Test: Get Backtest Status via API Gateway ===");
|
|
|
|
let mut client = create_authenticated_client().await?;
|
|
|
|
// First start a backtest
|
|
let start_date = (Utc::now() - Duration::days(7)).timestamp_nanos_opt().unwrap_or(0);
|
|
let end_date = Utc::now().timestamp_nanos_opt().unwrap_or(0);
|
|
|
|
let start_request = Request::new(StartBacktestRequest {
|
|
strategy_name: "mean_reversion".to_string(),
|
|
symbols: vec!["BTC/USD".to_string()],
|
|
start_date_unix_nanos: start_date,
|
|
end_date_unix_nanos: end_date,
|
|
initial_capital: 50000.0,
|
|
parameters: HashMap::new(),
|
|
save_results: true,
|
|
description: "E2E status test".to_string(),
|
|
});
|
|
|
|
let start_response = client.start_backtest(start_request).await?;
|
|
let backtest_id = start_response.into_inner().backtest_id;
|
|
|
|
println!("✓ Backtest started: {}", backtest_id);
|
|
|
|
// Wait a moment for backtest to start processing
|
|
tokio::time::sleep(StdDuration::from_millis(500)).await;
|
|
|
|
// Query status
|
|
let status_request = Request::new(GetBacktestStatusRequest {
|
|
backtest_id: backtest_id.clone(),
|
|
});
|
|
|
|
let status_response = client.get_backtest_status(status_request).await?;
|
|
let status = status_response.into_inner();
|
|
|
|
assert_eq!(status.backtest_id, backtest_id);
|
|
assert!(status.status != BacktestStatus::Unspecified as i32);
|
|
|
|
println!("✓ Backtest status retrieved");
|
|
println!(" Status: {:?}", status.status);
|
|
println!(" Progress: {:.2}%", status.progress_percentage);
|
|
println!(" Trades Executed: {}", status.trades_executed);
|
|
println!(" Current PnL: ${:.2}", status.current_pnl);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running services
|
|
async fn test_e2e_backtest_stop() -> Result<()> {
|
|
println!("\n=== E2E Test: Stop Running Backtest via API Gateway ===");
|
|
|
|
let mut client = create_authenticated_client().await?;
|
|
|
|
// Start a long-running backtest
|
|
let start_date = (Utc::now() - Duration::days(90)).timestamp_nanos_opt().unwrap_or(0);
|
|
let end_date = Utc::now().timestamp_nanos_opt().unwrap_or(0);
|
|
|
|
let start_request = Request::new(StartBacktestRequest {
|
|
strategy_name: "momentum".to_string(),
|
|
symbols: vec!["BTC/USD".to_string(), "ETH/USD".to_string()],
|
|
start_date_unix_nanos: start_date,
|
|
end_date_unix_nanos: end_date,
|
|
initial_capital: 100000.0,
|
|
parameters: HashMap::new(),
|
|
save_results: true,
|
|
description: "E2E stop test - long backtest".to_string(),
|
|
});
|
|
|
|
let start_response = client.start_backtest(start_request).await?;
|
|
let backtest_id = start_response.into_inner().backtest_id;
|
|
|
|
println!("✓ Long backtest started: {}", backtest_id);
|
|
|
|
// Wait for backtest to begin processing
|
|
tokio::time::sleep(StdDuration::from_secs(1)).await;
|
|
|
|
// Stop the backtest
|
|
let stop_request = Request::new(StopBacktestRequest {
|
|
backtest_id: backtest_id.clone(),
|
|
save_partial_results: true,
|
|
});
|
|
|
|
let stop_response = client.stop_backtest(stop_request).await?;
|
|
let stop_result = stop_response.into_inner();
|
|
|
|
assert!(stop_result.success, "Backtest should stop successfully");
|
|
|
|
println!("✓ Backtest stopped successfully");
|
|
println!(" Message: {}", stop_result.message);
|
|
println!(" Partial Results Saved: {}", stop_result.results_saved);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running services
|
|
async fn test_e2e_backtest_results() -> Result<()> {
|
|
println!("\n=== E2E Test: Get Backtest Results via API Gateway ===");
|
|
|
|
let mut client = create_authenticated_client().await?;
|
|
|
|
// Start a short backtest that will complete quickly
|
|
let start_date = (Utc::now() - Duration::days(7)).timestamp_nanos_opt().unwrap_or(0);
|
|
let end_date = (Utc::now() - Duration::days(6)).timestamp_nanos_opt().unwrap_or(0);
|
|
|
|
let start_request = Request::new(StartBacktestRequest {
|
|
strategy_name: "buy_and_hold".to_string(),
|
|
symbols: vec!["BTC/USD".to_string()],
|
|
start_date_unix_nanos: start_date,
|
|
end_date_unix_nanos: end_date,
|
|
initial_capital: 10000.0,
|
|
parameters: HashMap::new(),
|
|
save_results: true,
|
|
description: "E2E results test - short backtest".to_string(),
|
|
});
|
|
|
|
let start_response = client.start_backtest(start_request).await?;
|
|
let backtest_id = start_response.into_inner().backtest_id;
|
|
|
|
println!("✓ Short backtest started: {}", backtest_id);
|
|
|
|
// Wait for backtest to complete (with timeout)
|
|
let mut completed = false;
|
|
for _ in 0..20 {
|
|
tokio::time::sleep(StdDuration::from_millis(500)).await;
|
|
|
|
let status_request = Request::new(GetBacktestStatusRequest {
|
|
backtest_id: backtest_id.clone(),
|
|
});
|
|
|
|
if let Ok(response) = client.get_backtest_status(status_request).await {
|
|
let status = response.into_inner();
|
|
if status.status == BacktestStatus::Completed as i32 {
|
|
completed = true;
|
|
println!("✓ Backtest completed");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if !completed {
|
|
println!("⚠ Backtest did not complete in expected time, skipping results check");
|
|
return Ok(());
|
|
}
|
|
|
|
// Get results
|
|
let results_request = Request::new(GetBacktestResultsRequest {
|
|
backtest_id: backtest_id.clone(),
|
|
include_trades: true,
|
|
include_metrics: true,
|
|
});
|
|
|
|
let results_response = client.get_backtest_results(results_request).await?;
|
|
let results = results_response.into_inner();
|
|
|
|
assert_eq!(results.backtest_id, backtest_id);
|
|
|
|
if let Some(metrics) = results.metrics {
|
|
println!("✓ Backtest results retrieved");
|
|
println!(" Total Return: {:.2}%", metrics.total_return * 100.0);
|
|
println!(" Sharpe Ratio: {:.2}", metrics.sharpe_ratio);
|
|
println!(" Max Drawdown: {:.2}%", metrics.max_drawdown * 100.0);
|
|
println!(" Total Trades: {}", metrics.total_trades);
|
|
println!(" Win Rate: {:.2}%", metrics.win_rate * 100.0);
|
|
}
|
|
|
|
println!(" Trade Count: {}", results.trades.len());
|
|
println!(" Equity Curve Points: {}", results.equity_curve.len());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running services
|
|
async fn test_e2e_backtest_list() -> Result<()> {
|
|
println!("\n=== E2E Test: List Backtests via API Gateway ===");
|
|
|
|
let mut client = create_authenticated_client().await?;
|
|
|
|
let request = Request::new(ListBacktestsRequest {
|
|
limit: 10,
|
|
offset: 0,
|
|
strategy_name: None,
|
|
status_filter: None,
|
|
});
|
|
|
|
let response = client.list_backtests(request).await?;
|
|
let list_result = response.into_inner();
|
|
|
|
println!("✓ Backtest list retrieved");
|
|
println!(" Total Count: {}", list_result.total_count);
|
|
println!(" Returned: {}", list_result.backtests.len());
|
|
|
|
for (i, backtest) in list_result.backtests.into_iter().enumerate().take(5) {
|
|
println!(" {}. {} - {} ({})",
|
|
i + 1,
|
|
backtest.backtest_id,
|
|
backtest.strategy_name,
|
|
backtest.status
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// ============================================================================
|
|
// SECTION 2: REAL-TIME MONITORING E2E TESTS (3 tests)
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running services
|
|
async fn test_e2e_backtest_progress_subscription() -> Result<()> {
|
|
println!("\n=== E2E Test: Backtest Progress Subscription via API Gateway ===");
|
|
|
|
let mut client = create_authenticated_client().await?;
|
|
|
|
// Start a backtest
|
|
let start_date = (Utc::now() - Duration::days(14)).timestamp_nanos_opt().unwrap_or(0);
|
|
let end_date = Utc::now().timestamp_nanos_opt().unwrap_or(0);
|
|
|
|
let start_request = Request::new(StartBacktestRequest {
|
|
strategy_name: "grid_trading".to_string(),
|
|
symbols: vec!["BTC/USD".to_string()],
|
|
start_date_unix_nanos: start_date,
|
|
end_date_unix_nanos: end_date,
|
|
initial_capital: 50000.0,
|
|
parameters: HashMap::new(),
|
|
save_results: true,
|
|
description: "E2E progress subscription test".to_string(),
|
|
});
|
|
|
|
let start_response = client.start_backtest(start_request).await?;
|
|
let backtest_id = start_response.into_inner().backtest_id;
|
|
|
|
println!("✓ Backtest started: {}", backtest_id);
|
|
|
|
// Subscribe to progress updates
|
|
let progress_request = Request::new(SubscribeBacktestProgressRequest {
|
|
backtest_id: backtest_id.clone(),
|
|
});
|
|
|
|
let mut stream = client.subscribe_backtest_progress(progress_request).await?.into_inner();
|
|
|
|
println!("✓ Progress stream established");
|
|
|
|
// Receive progress updates (with timeout)
|
|
let mut updates_received = 0;
|
|
while let Ok(update_result) = timeout(
|
|
StdDuration::from_secs(10),
|
|
stream.message()
|
|
).await {
|
|
if let Ok(Some(update)) = update_result {
|
|
updates_received += 1;
|
|
println!(" Progress Update #{}: {:.1}% - {} trades, PnL: ${:.2}",
|
|
updates_received,
|
|
update.progress_percentage,
|
|
update.trades_executed,
|
|
update.current_pnl
|
|
);
|
|
|
|
// Stop after receiving 5 updates or if completed
|
|
if updates_received >= 5 || update.status == BacktestStatus::Completed as i32 {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
assert!(updates_received > 0, "Should receive at least one progress update");
|
|
println!("✓ Received {} progress updates", updates_received);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running services
|
|
async fn test_e2e_backtest_filtering_by_strategy() -> Result<()> {
|
|
println!("\n=== E2E Test: Filter Backtests by Strategy via API Gateway ===");
|
|
|
|
let mut client = create_authenticated_client().await?;
|
|
|
|
let request = Request::new(ListBacktestsRequest {
|
|
limit: 10,
|
|
offset: 0,
|
|
strategy_name: Some("moving_average_crossover".to_string()),
|
|
status_filter: None,
|
|
});
|
|
|
|
let response = client.list_backtests(request).await?;
|
|
let list_result = response.into_inner();
|
|
|
|
println!("✓ Filtered backtest list retrieved");
|
|
println!(" Strategy: moving_average_crossover");
|
|
println!(" Count: {}", list_result.backtests.len());
|
|
|
|
for backtest in &list_result.backtests {
|
|
assert_eq!(backtest.strategy_name, "moving_average_crossover");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running services
|
|
async fn test_e2e_backtest_filtering_by_status() -> Result<()> {
|
|
println!("\n=== E2E Test: Filter Backtests by Status via API Gateway ===");
|
|
|
|
let mut client = create_authenticated_client().await?;
|
|
|
|
let request = Request::new(ListBacktestsRequest {
|
|
limit: 10,
|
|
offset: 0,
|
|
strategy_name: None,
|
|
status_filter: Some(BacktestStatus::Completed as i32),
|
|
});
|
|
|
|
let response = client.list_backtests(request).await?;
|
|
let list_result = response.into_inner();
|
|
|
|
println!("✓ Filtered backtest list retrieved");
|
|
println!(" Status Filter: COMPLETED");
|
|
println!(" Count: {}", list_result.backtests.len());
|
|
|
|
for backtest in &list_result.backtests {
|
|
assert_eq!(backtest.status, BacktestStatus::Completed as i32);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// ============================================================================
|
|
// SECTION 3: ERROR HANDLING E2E TESTS (4 tests)
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running services
|
|
async fn test_e2e_backtest_invalid_date_range() -> Result<()> {
|
|
println!("\n=== E2E Test: Invalid Date Range Handling ===");
|
|
|
|
let mut client = create_authenticated_client().await?;
|
|
|
|
// Start date after end date (invalid)
|
|
let start_date = Utc::now().timestamp_nanos_opt().unwrap_or(0);
|
|
let end_date = (Utc::now() - Duration::days(30)).timestamp_nanos_opt().unwrap_or(0);
|
|
|
|
let request = Request::new(StartBacktestRequest {
|
|
strategy_name: "test_strategy".to_string(),
|
|
symbols: vec!["BTC/USD".to_string()],
|
|
start_date_unix_nanos: start_date,
|
|
end_date_unix_nanos: end_date,
|
|
initial_capital: 10000.0,
|
|
parameters: HashMap::new(),
|
|
save_results: false,
|
|
description: "Invalid date range test".to_string(),
|
|
});
|
|
|
|
let result = client.start_backtest(request).await;
|
|
|
|
// Should either return error or unsuccessful response
|
|
if let Err(status) = result {
|
|
println!("✓ Invalid date range rejected with error");
|
|
println!(" Error: {}", status.message());
|
|
} else if let Ok(response) = result {
|
|
assert!(!response.into_inner().success, "Invalid date range should not succeed");
|
|
println!("✓ Invalid date range rejected in response");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running services
|
|
async fn test_e2e_backtest_invalid_capital() -> Result<()> {
|
|
println!("\n=== E2E Test: Invalid Initial Capital Handling ===");
|
|
|
|
let mut client = create_authenticated_client().await?;
|
|
|
|
let start_date = (Utc::now() - Duration::days(7)).timestamp_nanos_opt().unwrap_or(0);
|
|
let end_date = Utc::now().timestamp_nanos_opt().unwrap_or(0);
|
|
|
|
let request = Request::new(StartBacktestRequest {
|
|
strategy_name: "test_strategy".to_string(),
|
|
symbols: vec!["BTC/USD".to_string()],
|
|
start_date_unix_nanos: start_date,
|
|
end_date_unix_nanos: end_date,
|
|
initial_capital: -1000.0, // Invalid negative capital
|
|
parameters: HashMap::new(),
|
|
save_results: false,
|
|
description: "Invalid capital test".to_string(),
|
|
});
|
|
|
|
let result = client.start_backtest(request).await;
|
|
|
|
assert!(result.is_err() || !result.unwrap().into_inner().success,
|
|
"Negative initial capital should be rejected");
|
|
|
|
println!("✓ Negative initial capital correctly rejected");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running services
|
|
async fn test_e2e_backtest_nonexistent_status() -> Result<()> {
|
|
println!("\n=== E2E Test: Query Status of Nonexistent Backtest ===");
|
|
|
|
let mut client = create_authenticated_client().await?;
|
|
|
|
let request = Request::new(GetBacktestStatusRequest {
|
|
backtest_id: "nonexistent_backtest_12345".to_string(),
|
|
});
|
|
|
|
let result = client.get_backtest_status(request).await;
|
|
|
|
assert!(result.is_err(), "Nonexistent backtest should return error");
|
|
|
|
if let Err(status) = result {
|
|
println!("✓ Nonexistent backtest correctly rejected");
|
|
println!(" Error code: {:?}", status.code());
|
|
println!(" Error message: {}", status.message());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running services
|
|
async fn test_e2e_backtest_unauthenticated_access() -> Result<()> {
|
|
println!("\n=== E2E Test: Unauthenticated Backtest Access ===");
|
|
|
|
// Create client without authentication
|
|
let channel = Channel::from_static(API_GATEWAY_ADDR)
|
|
.connect()
|
|
.await?;
|
|
let mut client = BacktestingServiceClient::new(channel);
|
|
|
|
let start_date = (Utc::now() - Duration::days(7)).timestamp_nanos_opt().unwrap_or(0);
|
|
let end_date = Utc::now().timestamp_nanos_opt().unwrap_or(0);
|
|
|
|
let request = Request::new(StartBacktestRequest {
|
|
strategy_name: "test".to_string(),
|
|
symbols: vec!["BTC/USD".to_string()],
|
|
start_date_unix_nanos: start_date,
|
|
end_date_unix_nanos: end_date,
|
|
initial_capital: 10000.0,
|
|
parameters: HashMap::new(),
|
|
save_results: false,
|
|
description: "Unauthenticated test".to_string(),
|
|
});
|
|
|
|
let result = client.start_backtest(request).await;
|
|
|
|
assert!(result.is_err(), "Unauthenticated request should be rejected");
|
|
|
|
if let Err(status) = result {
|
|
assert_eq!(status.code(), tonic::Code::Unauthenticated);
|
|
println!("✓ Unauthenticated request correctly rejected");
|
|
println!(" Error: {}", status.message());
|
|
}
|
|
|
|
Ok(())
|
|
}
|