Fundamental_Analysis/services/yfinance-provider-service/src/worker.rs

138 lines
4.7 KiB
Rust

use std::sync::Arc;
use common_contracts::{
dtos::{CompanyProfileDto, TimeSeriesFinancialDto},
messages::FetchCompanyDataCommand,
workflow_harness::StandardFetchWorkflow,
abstraction::DataProviderLogic,
persistence_client::PersistenceClient,
};
use crate::error::{Result, AppError};
use crate::state::AppState;
pub struct YFinanceFetcher {
state: Arc<AppState>,
}
impl YFinanceFetcher {
pub fn new(state: Arc<AppState>) -> Self {
Self { state }
}
}
#[async_trait::async_trait]
impl DataProviderLogic for YFinanceFetcher {
fn provider_id(&self) -> &str {
"yfinance"
}
async fn fetch_data(&self, symbol: &str) -> anyhow::Result<(CompanyProfileDto, Vec<TimeSeriesFinancialDto>)> {
// Check dynamic config (Single Source of Truth)
let client = PersistenceClient::new(self.state.config.data_persistence_service_url.clone());
let config = client.get_data_sources_config().await?;
let is_enabled = config.get("yfinance")
.map(|c| c.enabled)
.unwrap_or(false);
if !is_enabled {
return Err(anyhow::anyhow!("YFinance provider is disabled in dynamic config"));
}
// FIX: Use fully qualified call to avoid lifetime inference issue
let (profile, financials) = self.state.yfinance_provider.fetch_all_data(symbol).await
.map_err(|e| anyhow::anyhow!("YFinance API error: {}", e))?;
Ok((profile, financials))
}
}
pub async fn handle_fetch_command(
state: AppState,
command: FetchCompanyDataCommand,
_publisher: async_nats::Client, // Deprecated: harness creates its own scoped connection
) -> Result<()> {
let state_arc = Arc::new(state);
let fetcher = Arc::new(YFinanceFetcher::new(state_arc.clone()));
// Use standard workflow
// Note: YFinance worker signature was slightly different (no completion tx),
// but StandardFetchWorkflow handles Option<Sender>.
StandardFetchWorkflow::run(
state_arc,
fetcher,
command,
None
).await.map_err(|e| AppError::Internal(e.to_string()))?;
Ok(())
}
#[cfg(test)]
mod integration_tests {
use super::*;
use crate::config::AppConfig;
use crate::state::AppState;
use common_contracts::symbol_utils::{CanonicalSymbol, Market};
use common_contracts::config_models::{DataSourceConfig, DataSourceProvider};
use common_contracts::observability::{TaskProgress, ObservabilityTaskStatus};
use uuid::Uuid;
use chrono::Utc;
#[tokio::test]
async fn test_yfinance_fetch_flow() {
if std::env::var("NATS_ADDR").is_err() {
println!("Skipping integration test (no environment)");
return;
}
let config = AppConfig::load().expect("Failed to load config");
let mut config_enabled = config.clone();
config_enabled.yfinance_enabled = true;
let state = AppState::new(config_enabled);
// 1. Enable YFinance in Persistence Service
let persistence_client = PersistenceClient::new(config.data_persistence_service_url.clone());
let mut current_config = persistence_client.get_data_sources_config().await.unwrap_or_default();
current_config.insert("yfinance".to_string(), DataSourceConfig {
provider: DataSourceProvider::Yfinance,
enabled: true,
api_key: None,
api_url: None,
});
persistence_client.update_data_sources_config(&current_config).await
.expect("Failed to enable YFinance in persistence");
// 2. Construct Command
let request_id = Uuid::new_v4();
let cmd = FetchCompanyDataCommand {
request_id,
symbol: CanonicalSymbol::new("MSFT", &Market::US),
market: "US".to_string(),
template_id: Some("default".to_string()),
};
// Init task
state.tasks.insert(request_id, TaskProgress {
request_id,
task_name: "yfinance:MSFT".to_string(),
status: ObservabilityTaskStatus::Queued,
progress_percent: 0,
details: "Init".to_string(),
started_at: Utc::now()
});
// 3. NATS (Dummy client for signature compatibility, though harness uses its own)
let nats_client = async_nats::connect(&config.nats_addr).await
.expect("Failed to connect to NATS");
// 4. Run
let result = handle_fetch_command(state.clone(), cmd, nats_client).await;
// 5. Assert
assert!(result.is_ok(), "Worker execution failed: {:?}", result.err());
let task = state.tasks.get(&request_id).expect("Task should exist");
assert_eq!(task.status, ObservabilityTaskStatus::Completed);
}
}