Files
foxhunt/data/tests/test_benzinga.rs
jgrusewski c0be3ca530 🔧 Major compilation fixes across entire workspace - Significant progress achieved
## Summary of Compilation Fixes

### Core Infrastructure Improvements
- **Fixed import system**: Established canonical type imports from common::types
- **Resolved syntax errors**: Fixed malformed use statements with embedded comments
- **Import consolidation**: Eliminated duplicate and conflicting type imports
- **Type visibility**: Improved public/private type access patterns

### Major Areas Fixed

#### Trading Engine (trading_engine/)
-  Fixed syntax errors in types/basic.rs with clean re-exports
-  Resolved OrderSide/Side naming conflicts
-  Fixed type_registry.rs malformed imports
-  Consolidated canonical type imports from common::types
-  Fixed broker_client.rs duplicate OrderStatus imports
- 🔄 Remaining: 41 type visibility errors (down from 286+ errors)

#### Common Types (common/)
-  Established as single source of truth for all types
-  Clean type definitions with proper visibility
-  Consistent error handling patterns

#### Data Pipeline (data/)
-  Updated imports to use canonical common::types
-  Fixed provider trait implementations
-  Resolved database integration issues

#### ML Components (ml/)
-  Fixed model interface imports
-  Updated feature extraction systems
-  Resolved training pipeline dependencies

#### Risk Management (risk/)
-  Fixed safety module imports
-  Updated VaR calculator dependencies
-  Consolidated compliance types

#### Services
-  Trading Service: Fixed repository implementations
-  Backtesting Service: Updated strategy engines
-  TLI: Fixed dashboard and UI components

#### Test Infrastructure
-  Updated integration test imports
-  Fixed performance benchmark dependencies
-  Resolved mock implementations

### Technical Achievements

#### Import System Overhaul
- Established common::types as canonical source
- Eliminated circular dependencies
- Fixed visibility modifiers (pub use vs use)
- Resolved naming conflicts (Side → OrderSide)

#### Type System Cleanup
- Consolidated duplicate type definitions
- Fixed malformed syntax (comments in use statements)
- Standardized error handling patterns
- Improved module structure

#### Configuration Management
- Enhanced config crate integration
- Fixed database configuration patterns
- Improved hot-reload mechanisms

### Error Reduction Progress
- **Before**: 371+ compilation errors across workspace
- **After**: ~202 errors remaining (46% reduction achieved)
- **Major**: Fixed critical syntax errors preventing any compilation
- **Infrastructure**: Resolved fundamental import and type system issues

### Files Modified: 347
- Core types and infrastructure
- Service implementations
- Test suites and benchmarks
- Configuration systems
- Database integrations

### Next Steps
- Complete remaining type visibility fixes in trading_engine
- Finalize import resolution in remaining modules
- Validate cross-crate dependencies
- Run comprehensive test suite

This represents a major milestone in achieving zero compilation errors across
the entire Foxhunt HFT trading system workspace. The foundational type system
and import structure has been successfully established and standardized.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-27 20:56:22 +02:00

788 lines
26 KiB
Rust

//! Comprehensive tests for BenzingaHistoricalProvider
//!
//! This module contains extensive tests for the Benzinga news provider,
//! covering news processing, sentiment analysis, analyst ratings, earnings events,
//! economic indicators, rate limiting, and error handling.
use chrono::{DateTime, Duration as ChronoDuration, Utc};
use data::error::{DataError, Result};
use data::providers::benzinga::{
BenzingaChannel, BenzingaConfig, BenzingaEarnings, BenzingaEconomicEvent,
BenzingaHistoricalProvider, BenzingaNewsArticle, BenzingaRating, BenzingaTag, NewsEvent,
NewsEventType, OptionsContract, OptionsSentiment, OptionsType, RatingAction, SentimentPeriod,
UnusualOptionsEvent, UnusualOptionsType,
};
use rust_decimal_macros::dec;
use serde_json::json;
use std::collections::HashMap;
use std::time::Duration;
use tokio::time::{sleep, timeout};
use tokio_test;
use common::types::Decimal;
use common::types::Symbol;
use wiremock::matchers::{method, path, query_param};
use wiremock::{Mock, MockServer, ResponseTemplate};
/// Test BenzingaConfig creation and defaults
#[test]
fn test_config_creation_defaults() {
let config = BenzingaConfig::default();
assert_eq!(config.base_url, "https://api.benzinga.com/api/v2");
assert_eq!(config.timeout_seconds, 30);
assert_eq!(config.rate_limit, 5);
assert_eq!(config.max_retries, 3);
assert_eq!(config.retry_delay_ms, 1000);
}
/// Test BenzingaConfig creation with custom values
#[test]
fn test_config_creation_custom() {
let config = BenzingaConfig {
api_key: "test-api-key".to_string(),
base_url: "https://custom-api.example.com".to_string(),
timeout_seconds: 60,
rate_limit: 10,
max_retries: 5,
retry_delay_ms: 2000,
};
assert_eq!(config.api_key, "test-api-key");
assert_eq!(config.base_url, "https://custom-api.example.com");
assert_eq!(config.timeout_seconds, 60);
assert_eq!(config.rate_limit, 10);
assert_eq!(config.max_retries, 5);
assert_eq!(config.retry_delay_ms, 2000);
}
/// Test BenzingaConfig serialization
#[test]
fn test_config_serialization() {
let config = BenzingaConfig {
api_key: "test-key".to_string(),
..Default::default()
};
let json = serde_json::to_string(&config);
assert!(json.is_ok());
let deserialized: Result<BenzingaConfig, _> = serde_json::from_str(&json.unwrap());
assert!(deserialized.is_ok());
let deserialized_config = deserialized.unwrap();
assert_eq!(deserialized_config.api_key, config.api_key);
assert_eq!(deserialized_config.base_url, config.base_url);
}
/// Test provider creation success
#[tokio::test]
async fn test_provider_creation_success() {
let config = BenzingaConfig {
api_key: "test-api-key".to_string(),
..Default::default()
};
let provider = BenzingaHistoricalProvider::new(config);
assert!(provider.is_ok());
}
/// Test provider creation with invalid timeout
#[tokio::test]
async fn test_provider_creation_zero_timeout() {
let config = BenzingaConfig {
api_key: "test-api-key".to_string(),
timeout_seconds: 0,
..Default::default()
};
let provider = BenzingaHistoricalProvider::new(config);
// Should still create successfully, validation happens during requests
assert!(provider.is_ok());
}
/// Test news article conversion to news event
#[test]
fn test_news_article_conversion() {
let config = BenzingaConfig::default();
let provider = BenzingaHistoricalProvider::new(config).unwrap();
let article = BenzingaNewsArticle {
id: 12345,
title: "Apple Reports Strong Q4 Earnings".to_string(),
body: "Apple Inc. (AAPL) reported strong Q4 earnings with revenue beating estimates..."
.to_string(),
author: Some("Jane Doe".to_string()),
created: Utc::now(),
updated: Utc::now(),
url: "https://example.com/article/12345".to_string(),
image: Some("https://example.com/image.jpg".to_string()),
symbols: vec!["AAPL".to_string()],
channels: vec![BenzingaChannel {
id: 1,
name: "News".to_string(),
}],
tags: vec![
BenzingaTag {
id: 1,
name: "Earnings".to_string(),
},
BenzingaTag {
id: 2,
name: "Breaking".to_string(),
},
],
sentiment: Some(0.75),
};
let news_event = provider.convert_news_article(article.clone());
assert_eq!(news_event.id, format!("benzinga_news_{}", article.id));
assert_eq!(news_event.event_type, NewsEventType::News);
assert_eq!(news_event.symbols, article.symbols);
assert_eq!(news_event.title, article.title);
assert_eq!(news_event.content, article.body);
assert_eq!(news_event.sentiment, Some(0.75));
assert_eq!(news_event.importance, 0.8); // Breaking news gets higher importance
assert_eq!(news_event.source, "Benzinga News");
assert!(news_event.categories.contains(&"Earnings".to_string()));
assert!(news_event.categories.contains(&"Breaking".to_string()));
// Check metadata
assert_eq!(
news_event.metadata.get("article_id"),
Some(&"12345".to_string())
);
assert_eq!(news_event.metadata.get("url"), Some(&article.url));
assert_eq!(
news_event.metadata.get("author"),
Some(&"Jane Doe".to_string())
);
assert_eq!(
news_event.metadata.get("image_url"),
Some(&"https://example.com/image.jpg".to_string())
);
}
/// Test news article conversion without breaking tags
#[test]
fn test_news_article_conversion_non_breaking() {
let config = BenzingaConfig::default();
let provider = BenzingaHistoricalProvider::new(config).unwrap();
let article = BenzingaNewsArticle {
id: 67890,
title: "Regular News Article".to_string(),
body: "This is regular news content...".to_string(),
author: None,
created: Utc::now(),
updated: Utc::now(),
url: "https://example.com/article/67890".to_string(),
image: None,
symbols: vec!["SPY".to_string()],
channels: vec![],
tags: vec![BenzingaTag {
id: 3,
name: "Market Update".to_string(),
}],
sentiment: None,
};
let news_event = provider.convert_news_article(article);
assert_eq!(news_event.importance, 0.5); // Non-breaking news gets standard importance
assert_eq!(news_event.sentiment, None);
assert!(!news_event.metadata.contains_key("author"));
assert!(!news_event.metadata.contains_key("image_url"));
}
/// Test earnings event conversion
#[test]
fn test_earnings_event_conversion() {
let config = BenzingaConfig::default();
let provider = BenzingaHistoricalProvider::new(config).unwrap();
let earnings = BenzingaEarnings {
id: 54321,
ticker: "TSLA".to_string(),
name: "Tesla Inc.".to_string(),
date: Utc::now(),
period: "Q4".to_string(),
period_year: 2023,
eps_est: Some(0.85),
eps: Some(0.95),
eps_surprise: Some(0.10),
revenue_est: Some(25_000_000_000.0),
revenue: Some(26_500_000_000.0),
revenue_surprise: Some(1_500_000_000.0),
time: Some("AMC".to_string()),
importance: Some(4),
};
let news_event = provider.convert_earnings_event(earnings.clone());
assert_eq!(news_event.id, format!("benzinga_earnings_{}", earnings.id));
assert_eq!(news_event.event_type, NewsEventType::Earnings);
assert_eq!(news_event.symbols, vec![earnings.ticker]);
assert_eq!(news_event.title, format!("Earnings: {}", earnings.name));
assert_eq!(news_event.importance, 0.8); // 4/5 importance
assert_eq!(news_event.source, "Benzinga Earnings");
assert!(news_event.categories.contains(&"Earnings".to_string()));
// Check content format
assert!(news_event.content.contains("Tesla Inc."));
assert!(news_event.content.contains("TSLA"));
assert!(news_event.content.contains("Q4 2023"));
// Check metadata
assert_eq!(
news_event.metadata.get("earnings_id"),
Some(&"54321".to_string())
);
assert_eq!(news_event.metadata.get("period"), Some(&"Q4".to_string()));
assert_eq!(
news_event.metadata.get("period_year"),
Some(&"2023".to_string())
);
assert_eq!(
news_event.metadata.get("earnings_time"),
Some(&"AMC".to_string())
);
assert_eq!(
news_event.metadata.get("eps_estimate"),
Some(&"0.85".to_string())
);
assert_eq!(
news_event.metadata.get("eps_actual"),
Some(&"0.95".to_string())
);
}
/// Test rating event conversion with upgrade
#[test]
fn test_rating_event_conversion_upgrade() {
let config = BenzingaConfig::default();
let provider = BenzingaHistoricalProvider::new(config).unwrap();
let rating = BenzingaRating {
id: 98765,
ticker: "NVDA".to_string(),
name: "NVIDIA Corporation".to_string(),
analyst: "Goldman Sachs".to_string(),
firm: "Goldman Sachs".to_string(),
action: "Upgrades".to_string(),
current_rating: "Buy".to_string(),
previous_rating: Some("Hold".to_string()),
price_target: Some(dec!(450.00)),
previous_price_target: Some(dec!(400.00)),
comment: Some("Strong AI growth prospects".to_string()),
rating_date: Utc::now(),
timestamp: Utc::now(),
importance: Some(5),
};
let news_event = provider.convert_rating_event(rating.clone());
assert_eq!(news_event.id, format!("benzinga_rating_{}", rating.id));
assert_eq!(news_event.event_type, NewsEventType::Rating);
assert_eq!(news_event.symbols, vec![rating.ticker]);
assert_eq!(news_event.title, "Rating: NVIDIA Corporation - Upgrades");
assert_eq!(news_event.importance, 1.0); // 5/5 importance
assert_eq!(news_event.sentiment, Some(0.7)); // Upgrade is positive
assert_eq!(news_event.source, "Benzinga Ratings");
assert!(news_event
.categories
.contains(&"Analyst Rating".to_string()));
assert!(news_event.categories.contains(&"Upgrades".to_string()));
// Check content format
assert!(news_event.content.contains("Goldman Sachs"));
assert!(news_event.content.contains("Upgrades"));
assert!(news_event.content.contains("NVIDIA Corporation"));
// Check metadata
assert_eq!(
news_event.metadata.get("rating_id"),
Some(&"98765".to_string())
);
assert_eq!(
news_event.metadata.get("analyst"),
Some(&"Goldman Sachs".to_string())
);
assert_eq!(
news_event.metadata.get("action"),
Some(&"Upgrades".to_string())
);
assert_eq!(news_event.metadata.get("rating"), Some(&"Buy".to_string()));
assert_eq!(
news_event.metadata.get("price_target"),
Some(&"450.00".to_string())
);
}
/// Test rating event conversion with downgrade
#[test]
fn test_rating_event_conversion_downgrade() {
let config = BenzingaConfig::default();
let provider = BenzingaHistoricalProvider::new(config).unwrap();
let rating = BenzingaRating {
id: 13579,
ticker: "META".to_string(),
name: "Meta Platforms Inc.".to_string(),
analyst: "Morgan Stanley".to_string(),
firm: "Morgan Stanley".to_string(),
action: "Downgrades".to_string(),
current_rating: "Hold".to_string(),
previous_rating: Some("Buy".to_string()),
price_target: Some(dec!(300.00)),
previous_price_target: Some(dec!(350.00)),
comment: None,
rating_date: Utc::now(),
timestamp: Utc::now(),
importance: Some(3),
};
let news_event = provider.convert_rating_event(rating);
assert_eq!(news_event.sentiment, Some(-0.7)); // Downgrade is negative
assert_eq!(news_event.importance, 0.6); // 3/5 importance
}
/// Test economic event conversion
#[test]
fn test_economic_event_conversion() {
let config = BenzingaConfig::default();
let provider = BenzingaHistoricalProvider::new(config).unwrap();
let economic = BenzingaEconomicEvent {
id: 24680,
name: "Non-Farm Payrolls".to_string(),
description: Some("Monthly employment data".to_string()),
date: Utc::now(),
country: "US".to_string(),
category: "Employment".to_string(),
importance: "High".to_string(),
actual: Some("250K".to_string()),
consensus: Some("200K".to_string()),
previous: Some("180K".to_string()),
previous_revised: Some("185K".to_string()),
};
let news_event = provider.convert_economic_event(economic.clone());
assert_eq!(news_event.id, format!("benzinga_economic_{}", economic.id));
assert_eq!(news_event.event_type, NewsEventType::Economic);
assert!(news_event.symbols.is_empty()); // Economic events don't have specific symbols
assert_eq!(news_event.title, "Economic: Non-Farm Payrolls (US)");
assert_eq!(news_event.importance, 0.8); // High importance
assert_eq!(news_event.source, "Benzinga Economic");
assert!(news_event.categories.contains(&"Employment".to_string()));
assert!(news_event.categories.contains(&"High".to_string()));
// Check content format
assert!(news_event.content.contains("Non-Farm Payrolls"));
assert!(news_event.content.contains("US"));
assert!(news_event.content.contains("250K"));
assert!(news_event.content.contains("200K"));
// Check metadata
assert_eq!(
news_event.metadata.get("economic_id"),
Some(&"24680".to_string())
);
assert_eq!(news_event.metadata.get("country"), Some(&"US".to_string()));
assert_eq!(
news_event.metadata.get("category"),
Some(&"Employment".to_string())
);
assert_eq!(
news_event.metadata.get("importance"),
Some(&"High".to_string())
);
assert_eq!(
news_event.metadata.get("description"),
Some(&"Monthly employment data".to_string())
);
assert_eq!(news_event.metadata.get("actual"), Some(&"250K".to_string()));
}
/// Test rate limiting functionality
#[tokio::test]
async fn test_rate_limiting() {
let config = BenzingaConfig {
api_key: "test-key".to_string(),
rate_limit: 2, // 2 requests per second
..Default::default()
};
let provider = BenzingaHistoricalProvider::new(config).unwrap();
let start = std::time::Instant::now();
// Make 4 requests, should take at least 1.5 seconds with 2 req/sec limit
for _ in 0..4 {
provider.enforce_rate_limit().await;
}
let elapsed = start.elapsed();
assert!(elapsed >= Duration::from_millis(1400)); // Allow some margin for timing variations
}
/// Test rate limiting with high rate limit
#[tokio::test]
async fn test_rate_limiting_high_rate() {
let config = BenzingaConfig {
api_key: "test-key".to_string(),
rate_limit: 100, // 100 requests per second
..Default::default()
};
let provider = BenzingaHistoricalProvider::new(config).unwrap();
let start = std::time::Instant::now();
// Make 5 requests, should be very fast with high rate limit
for _ in 0..5 {
provider.enforce_rate_limit().await;
}
let elapsed = start.elapsed();
assert!(elapsed < Duration::from_millis(100));
}
/// Test news event type serialization
#[test]
fn test_news_event_type_serialization() {
let types = vec![
NewsEventType::News,
NewsEventType::Earnings,
NewsEventType::Rating,
NewsEventType::Economic,
NewsEventType::CorporateAction,
];
for event_type in types {
let json = serde_json::to_string(&event_type);
assert!(json.is_ok());
let deserialized: Result<NewsEventType, _> = serde_json::from_str(&json.unwrap());
assert!(deserialized.is_ok());
assert_eq!(deserialized.unwrap(), event_type);
}
}
/// Test rating action serialization
#[test]
fn test_rating_action_serialization() {
let actions = vec![
RatingAction::Initiate,
RatingAction::Upgrade,
RatingAction::Downgrade,
RatingAction::Maintain,
RatingAction::Discontinue,
];
for action in actions {
let json = serde_json::to_string(&action);
assert!(json.is_ok());
let deserialized: Result<RatingAction, _> = serde_json::from_str(&json.unwrap());
assert!(deserialized.is_ok());
assert_eq!(deserialized.unwrap(), action);
}
}
/// Test sentiment period serialization
#[test]
fn test_sentiment_period_serialization() {
let periods = vec![
SentimentPeriod::RealTime,
SentimentPeriod::Hourly,
SentimentPeriod::Daily,
SentimentPeriod::Weekly,
];
for period in periods {
let json = serde_json::to_string(&period);
assert!(json.is_ok());
let deserialized: Result<SentimentPeriod, _> = serde_json::from_str(&json.unwrap());
assert!(deserialized.is_ok());
assert_eq!(deserialized.unwrap(), period);
}
}
/// Test unusual options types serialization
#[test]
fn test_unusual_options_types_serialization() {
let types = vec![
UnusualOptionsType::BlockTrade,
UnusualOptionsType::Sweep,
UnusualOptionsType::VolumeSpike,
UnusualOptionsType::OpenInterestSpike,
UnusualOptionsType::VolatilitySpike,
];
for opt_type in types {
let json = serde_json::to_string(&opt_type);
assert!(json.is_ok());
let deserialized: Result<UnusualOptionsType, _> = serde_json::from_str(&json.unwrap());
assert!(deserialized.is_ok());
assert_eq!(deserialized.unwrap(), opt_type);
}
}
/// Test options sentiment serialization
#[test]
fn test_options_sentiment_serialization() {
let sentiments = vec![
OptionsSentiment::Bullish,
OptionsSentiment::Bearish,
OptionsSentiment::Neutral,
];
for sentiment in sentiments {
let json = serde_json::to_string(&sentiment);
assert!(json.is_ok());
let deserialized: Result<OptionsSentiment, _> = serde_json::from_str(&json.unwrap());
assert!(deserialized.is_ok());
assert_eq!(deserialized.unwrap(), sentiment);
}
}
/// Test options type serialization
#[test]
fn test_options_type_serialization() {
let types = vec![OptionsType::Call, OptionsType::Put];
for opt_type in types {
let json = serde_json::to_string(&opt_type);
assert!(json.is_ok());
let deserialized: Result<OptionsType, _> = serde_json::from_str(&json.unwrap());
assert!(deserialized.is_ok());
assert_eq!(deserialized.unwrap(), opt_type);
}
}
/// Test options contract serialization
#[test]
fn test_options_contract_serialization() {
let contract = OptionsContract {
strike: dec!(150.00),
expiration: chrono::NaiveDate::from_ymd_opt(2024, 3, 15).unwrap(),
option_type: OptionsType::Call,
multiplier: 100,
};
let json = serde_json::to_string(&contract);
assert!(json.is_ok());
let deserialized: Result<OptionsContract, _> = serde_json::from_str(&json.unwrap());
assert!(deserialized.is_ok());
let deserialized_contract = deserialized.unwrap();
assert_eq!(deserialized_contract.strike, contract.strike);
assert_eq!(deserialized_contract.expiration, contract.expiration);
assert_eq!(deserialized_contract.option_type, contract.option_type);
assert_eq!(deserialized_contract.multiplier, contract.multiplier);
}
/// Test unusual options event serialization
#[test]
fn test_unusual_options_event_serialization() {
let options_event = UnusualOptionsEvent {
symbol: Symbol::from("AAPL"),
contract: OptionsContract {
strike: dec!(160.00),
expiration: chrono::NaiveDate::from_ymd_opt(2024, 1, 19).unwrap(),
option_type: OptionsType::Call,
multiplier: 100,
},
activity_type: UnusualOptionsType::Sweep,
volume: 5000,
open_interest: Some(10000),
premium: Some(dec!(250000.00)),
implied_volatility: Some(0.35),
sentiment: OptionsSentiment::Bullish,
confidence: 0.85,
description: "Large call sweep near market".to_string(),
timestamp: Utc::now(),
};
let json = serde_json::to_string(&options_event);
assert!(json.is_ok());
let deserialized: Result<UnusualOptionsEvent, _> = serde_json::from_str(&json.unwrap());
assert!(deserialized.is_ok());
let deserialized_event = deserialized.unwrap();
assert_eq!(deserialized_event.symbol, options_event.symbol);
assert_eq!(
deserialized_event.activity_type,
options_event.activity_type
);
assert_eq!(deserialized_event.volume, options_event.volume);
assert_eq!(deserialized_event.sentiment, options_event.sentiment);
assert_eq!(deserialized_event.confidence, options_event.confidence);
}
/// Test news event complete serialization
#[test]
fn test_news_event_serialization() {
let mut metadata = HashMap::new();
metadata.insert("article_id".to_string(), "12345".to_string());
metadata.insert("author".to_string(), "Test Author".to_string());
let news_event = NewsEvent {
id: "test_news_123".to_string(),
timestamp: Utc::now(),
event_type: NewsEventType::News,
symbols: vec!["AAPL".to_string(), "MSFT".to_string()],
title: "Tech Stocks Rise".to_string(),
content: "Technology stocks showed strong performance today...".to_string(),
importance: 0.75,
sentiment: Some(0.6),
source: "Test Source".to_string(),
categories: vec!["Technology".to_string(), "Markets".to_string()],
metadata,
};
let json = serde_json::to_string(&news_event);
assert!(json.is_ok());
let deserialized: Result<NewsEvent, _> = serde_json::from_str(&json.unwrap());
assert!(deserialized.is_ok());
let deserialized_event = deserialized.unwrap();
assert_eq!(deserialized_event.id, news_event.id);
assert_eq!(deserialized_event.event_type, news_event.event_type);
assert_eq!(deserialized_event.symbols, news_event.symbols);
assert_eq!(deserialized_event.title, news_event.title);
assert_eq!(deserialized_event.importance, news_event.importance);
assert_eq!(deserialized_event.sentiment, news_event.sentiment);
assert_eq!(deserialized_event.metadata.len(), news_event.metadata.len());
}
/// Test benzinga article with minimal data
#[test]
fn test_benzinga_article_minimal() {
let article = BenzingaNewsArticle {
id: 1,
title: "Minimal Article".to_string(),
body: "Content".to_string(),
author: None,
created: Utc::now(),
updated: Utc::now(),
url: "https://example.com/1".to_string(),
image: None,
symbols: vec![],
channels: vec![],
tags: vec![],
sentiment: None,
};
let json = serde_json::to_string(&article);
assert!(json.is_ok());
let deserialized: Result<BenzingaNewsArticle, _> = serde_json::from_str(&json.unwrap());
assert!(deserialized.is_ok());
let config = BenzingaConfig::default();
let provider = BenzingaHistoricalProvider::new(config).unwrap();
let event = provider.convert_news_article(article);
assert_eq!(event.importance, 0.5); // Default importance for non-breaking
assert!(event.symbols.is_empty());
assert!(event.categories.is_empty());
assert_eq!(event.sentiment, None);
}
/// Test earnings with minimal data
#[test]
fn test_earnings_minimal() {
let earnings = BenzingaEarnings {
id: 1,
ticker: "TEST".to_string(),
name: "Test Company".to_string(),
date: Utc::now(),
period: "Q1".to_string(),
period_year: 2024,
eps_est: None,
eps: None,
eps_surprise: None,
revenue_est: None,
revenue: None,
revenue_surprise: None,
time: None,
importance: None,
};
let config = BenzingaConfig::default();
let provider = BenzingaHistoricalProvider::new(config).unwrap();
let event = provider.convert_earnings_event(earnings);
assert_eq!(event.importance, 0.6); // Default importance (3/5)
assert!(!event.metadata.contains_key("earnings_time"));
assert!(!event.metadata.contains_key("eps_estimate"));
assert!(!event.metadata.contains_key("eps_actual"));
}
/// Test rating with maintain action
#[test]
fn test_rating_maintain_action() {
let config = BenzingaConfig::default();
let provider = BenzingaHistoricalProvider::new(config).unwrap();
let rating = BenzingaRating {
id: 1,
ticker: "SPY".to_string(),
name: "SPDR S&P 500".to_string(),
analyst: "Test Analyst".to_string(),
firm: "Test Firm".to_string(),
action: "Maintains".to_string(),
current_rating: "Buy".to_string(),
previous_rating: Some("Buy".to_string()),
price_target: None,
previous_price_target: None,
comment: None,
rating_date: Utc::now(),
timestamp: Utc::now(),
importance: None,
};
let event = provider.convert_rating_event(rating);
assert_eq!(event.sentiment, None); // Maintain action has no sentiment
assert_eq!(event.importance, 0.6); // Default importance (3/5)
}
/// Test economic event with low importance
#[test]
fn test_economic_event_low_importance() {
let config = BenzingaConfig::default();
let provider = BenzingaHistoricalProvider::new(config).unwrap();
let economic = BenzingaEconomicEvent {
id: 1,
name: "Minor Indicator".to_string(),
description: None,
date: Utc::now(),
country: "CA".to_string(),
category: "Other".to_string(),
importance: "Low".to_string(),
actual: None,
consensus: None,
previous: None,
previous_revised: None,
};
let event = provider.convert_economic_event(economic);
assert_eq!(event.importance, 0.2); // Low importance
assert!(!event.metadata.contains_key("description"));
assert!(!event.metadata.contains_key("actual"));
}