Files
foxhunt/testing/api-load/src/clients/authenticated_client.rs
jgrusewski d25c82f8f3 refactor: rename api_gateway → api across workspace, tests, and load crate
- Workspace Cargo.toml: remove web-gateway + api_gateway members, keep api
- trading_service: dep api-gateway → api, update test imports
- testing/api-gateway-load → testing/api-load (crate renamed)
- All test crates: get_api_gateway_addr → get_api_addr + variable renames

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:38:21 +01:00

322 lines
8.9 KiB
Rust

use anyhow::{Context, Result};
use chrono::{Duration, Utc};
use jsonwebtoken::{encode, EncodingKey, Header};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::time::Instant;
use crate::metrics::{RequestMetric, RequestStatus, ServiceType};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String,
username: String,
exp: i64,
iat: i64,
}
pub struct AuthenticatedClient {
client: Client,
gateway_url: String,
jwt_token: String,
}
impl AuthenticatedClient {
pub async fn new(
gateway_url: String,
jwt_secret: &str,
user_id: &str,
username: &str,
) -> Result<Self> {
let client = Client::builder()
.timeout(std::time::Duration::from_secs(30))
.pool_max_idle_per_host(10)
.build()
.context("Failed to create HTTP client")?;
// Generate JWT token
let claims = Claims {
sub: user_id.to_string(),
username: username.to_string(),
iat: Utc::now().timestamp(),
exp: (Utc::now() + Duration::hours(24)).timestamp(),
};
let jwt_token = encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(jwt_secret.as_bytes()),
)
.context("Failed to generate JWT token")?;
Ok(Self {
client,
gateway_url,
jwt_token,
})
}
pub async fn submit_order(&self, client_id: usize, order: TestOrder) -> Result<RequestMetric> {
let start = Instant::now();
let result = self
.client
.post(format!("{}/trading/orders", self.gateway_url))
.header("Authorization", format!("Bearer {}", self.jwt_token))
.json(&order)
.send()
.await;
let latency = start.elapsed();
let status = match result {
Ok(response) => {
if response.status().is_success() {
RequestStatus::Success
} else if response.status().as_u16() == 429 {
RequestStatus::RateLimited
} else if response.status().as_u16() == 503 {
RequestStatus::CircuitBreakerOpen
} else {
RequestStatus::Error
}
},
Err(e) => {
if e.is_timeout() {
RequestStatus::Timeout
} else {
RequestStatus::Error
}
},
};
Ok(RequestMetric {
timestamp: chrono::Utc::now(),
client_id,
service: ServiceType::Trading,
latency,
status,
error_type: if status != RequestStatus::Success {
Some(format!("{:?}", status))
} else {
None
},
})
}
pub async fn get_positions(&self, client_id: usize) -> Result<RequestMetric> {
let start = Instant::now();
let result = self
.client
.get(format!("{}/trading/positions", self.gateway_url))
.header("Authorization", format!("Bearer {}", self.jwt_token))
.send()
.await;
let latency = start.elapsed();
let status = match result {
Ok(response) => {
if response.status().is_success() {
RequestStatus::Success
} else if response.status().as_u16() == 429 {
RequestStatus::RateLimited
} else if response.status().as_u16() == 503 {
RequestStatus::CircuitBreakerOpen
} else {
RequestStatus::Error
}
},
Err(e) => {
if e.is_timeout() {
RequestStatus::Timeout
} else {
RequestStatus::Error
}
},
};
Ok(RequestMetric {
timestamp: chrono::Utc::now(),
client_id,
service: ServiceType::Trading,
latency,
status,
error_type: if status != RequestStatus::Success {
Some(format!("{:?}", status))
} else {
None
},
})
}
pub async fn run_backtest(
&self,
client_id: usize,
config: BacktestConfig,
) -> Result<RequestMetric> {
let start = Instant::now();
let result = self
.client
.post(format!("{}/backtesting/run", self.gateway_url))
.header("Authorization", format!("Bearer {}", self.jwt_token))
.json(&config)
.send()
.await;
let latency = start.elapsed();
let status = match result {
Ok(response) => {
if response.status().is_success() {
RequestStatus::Success
} else if response.status().as_u16() == 429 {
RequestStatus::RateLimited
} else if response.status().as_u16() == 503 {
RequestStatus::CircuitBreakerOpen
} else {
RequestStatus::Error
}
},
Err(e) => {
if e.is_timeout() {
RequestStatus::Timeout
} else {
RequestStatus::Error
}
},
};
Ok(RequestMetric {
timestamp: chrono::Utc::now(),
client_id,
service: ServiceType::Backtesting,
latency,
status,
error_type: if status != RequestStatus::Success {
Some(format!("{:?}", status))
} else {
None
},
})
}
pub async fn train_model(
&self,
client_id: usize,
config: TrainingConfig,
) -> Result<RequestMetric> {
let start = Instant::now();
let result = self
.client
.post(format!("{}/ml/train", self.gateway_url))
.header("Authorization", format!("Bearer {}", self.jwt_token))
.json(&config)
.send()
.await;
let latency = start.elapsed();
let status = match result {
Ok(response) => {
if response.status().is_success() {
RequestStatus::Success
} else if response.status().as_u16() == 429 {
RequestStatus::RateLimited
} else if response.status().as_u16() == 503 {
RequestStatus::CircuitBreakerOpen
} else {
RequestStatus::Error
}
},
Err(e) => {
if e.is_timeout() {
RequestStatus::Timeout
} else {
RequestStatus::Error
}
},
};
Ok(RequestMetric {
timestamp: chrono::Utc::now(),
client_id,
service: ServiceType::MlTraining,
latency,
status,
error_type: if status != RequestStatus::Success {
Some(format!("{:?}", status))
} else {
None
},
})
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TestOrder {
pub symbol: String,
pub quantity: f64,
pub side: String,
pub order_type: String,
}
impl TestOrder {
pub fn random() -> Self {
use rand::Rng;
let mut rng = rand::thread_rng();
let symbols = ["AAPL", "GOOGL", "MSFT", "AMZN", "TSLA"];
let sides = ["buy", "sell"];
let order_types = ["market", "limit"];
Self {
symbol: symbols
.get(rng.gen_range(0..symbols.len()))
.map_or_else(|| "AAPL".to_owned(), |s| (*s).to_string()),
quantity: rng.gen_range(1.0..101.0),
side: sides
.get(rng.gen_range(0..sides.len()))
.map_or_else(|| "buy".to_owned(), |s| (*s).to_string()),
order_type: order_types
.get(rng.gen_range(0..order_types.len()))
.map_or_else(|| "market".to_owned(), |s| (*s).to_string()),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct BacktestConfig {
pub strategy: String,
pub start_date: String,
pub end_date: String,
}
impl BacktestConfig {
pub fn default() -> Self {
Self {
strategy: "momentum".to_owned(),
start_date: "2024-01-01".to_owned(),
end_date: "2024-12-31".to_owned(),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TrainingConfig {
pub model_type: String,
pub epochs: u32,
}
impl TrainingConfig {
pub fn default() -> Self {
Self {
model_type: "mamba2".to_owned(),
epochs: 10_u32,
}
}
}