153 lines
5.9 KiB
Python
153 lines
5.9 KiB
Python
|
|
"""Tests for output sinks"""
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||
|
|
from sentiment_engine.output.hazelcast_sink import HazelcastSink
|
||
|
|
from sentiment_engine.output.clickhouse_sink import ClickHouseSink
|
||
|
|
from sentiment_engine.output.manager import OutputManager
|
||
|
|
from sentiment_engine.schemas.output import SentimentOutput, AssetSentiment, MarketSentiment, IndustrySentiment, PumpDumpScore, VelocityMetrics, EventFlag
|
||
|
|
|
||
|
|
|
||
|
|
class TestHazelcastSink:
|
||
|
|
"""Tests for HazelcastSink"""
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def sink(self):
|
||
|
|
return HazelcastSink()
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_publish_scores(self, sink):
|
||
|
|
"""Test publishing scores to Hazelcast"""
|
||
|
|
# This would require a running Hazelcast instance
|
||
|
|
# For now, just verify the sink can be instantiated
|
||
|
|
assert sink is not None
|
||
|
|
|
||
|
|
def test_prepare_exf_data(self, sink):
|
||
|
|
"""Test preparing ExF data structure"""
|
||
|
|
from sentiment_engine.schemas.output import SentimentOutput, MarketSentiment
|
||
|
|
from datetime import datetime
|
||
|
|
|
||
|
|
market = MarketSentiment(
|
||
|
|
fear_state=25.0,
|
||
|
|
greed_state=75.0,
|
||
|
|
sentiment_index=50.0,
|
||
|
|
hype_velocity=65.0,
|
||
|
|
pub_velocity=55.0,
|
||
|
|
aggregate_pump_risk=75.0,
|
||
|
|
aggregate_dump_risk=20.0,
|
||
|
|
top_pump_assets=["BTC", "ETH"],
|
||
|
|
top_dump_assets=[],
|
||
|
|
dominant_events=[],
|
||
|
|
industry_breakdown={},
|
||
|
|
last_update_ts=1234567890.0,
|
||
|
|
total_sources=10,
|
||
|
|
total_assets=50
|
||
|
|
)
|
||
|
|
output = SentimentOutput(
|
||
|
|
timestamp=1234567890.0,
|
||
|
|
market=market,
|
||
|
|
assets={}
|
||
|
|
)
|
||
|
|
|
||
|
|
# Verify ACB signals extraction
|
||
|
|
acb = output.get_acb_signals()
|
||
|
|
assert "market_sentiment_state" in acb
|
||
|
|
assert "aggregate_pump_risk" in acb
|
||
|
|
assert "fear_state" in acb
|
||
|
|
assert "greed_state" in acb
|
||
|
|
assert "hype_velocity" in acb
|
||
|
|
|
||
|
|
|
||
|
|
class TestClickHouseSink:
|
||
|
|
"""Tests for ClickHouseSink"""
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def sink(self):
|
||
|
|
return ClickHouseSink()
|
||
|
|
|
||
|
|
def test_buffer_raw_item(self, sink):
|
||
|
|
"""Test buffering raw item for batch insert"""
|
||
|
|
from sentiment_engine.schemas.payload import NormalizedPayload, SourceType, AssetMention, EngagementMetrics
|
||
|
|
from datetime import datetime
|
||
|
|
|
||
|
|
payload = NormalizedPayload(
|
||
|
|
source_id="test",
|
||
|
|
source_type=SourceType.NEWS,
|
||
|
|
source_credibility_base=0.8,
|
||
|
|
ingest_ts=1234567890.0,
|
||
|
|
publish_ts=1234567880.0,
|
||
|
|
asset_mentions=[AssetMention(asset_id="BTC", mention_span=(0, 3), confidence=0.9, source_text="BTC", mention_type="ticker")],
|
||
|
|
raw_text="Test article",
|
||
|
|
title="Test",
|
||
|
|
url="https://test.com",
|
||
|
|
author="Test",
|
||
|
|
content_length=100,
|
||
|
|
language="en",
|
||
|
|
metadata={}
|
||
|
|
)
|
||
|
|
|
||
|
|
# This should not raise an error
|
||
|
|
sink.buffer_raw_item(payload)
|
||
|
|
assert len(sink._batch_buffer) == 1
|
||
|
|
|
||
|
|
def test_buffer_processed_item(self, sink):
|
||
|
|
"""Test buffering processed item for batch insert"""
|
||
|
|
from sentiment_engine.schemas.processed import ProcessedItem, EntityExtraction, SentimentScores, EmotionScores, EventClassification, EventType, TemporalAnchor, CredibilityScore
|
||
|
|
from datetime import datetime
|
||
|
|
|
||
|
|
item = ProcessedItem(
|
||
|
|
payload_id="test_1",
|
||
|
|
source_id="test",
|
||
|
|
source_type="news",
|
||
|
|
ingest_ts=1234567890.0,
|
||
|
|
publish_ts=1234567880.0,
|
||
|
|
entities=[EntityExtraction(asset_id="BTC", mention_span=(0, 3), confidence=0.9, entity_type="ticker", canonical_name="BTC")],
|
||
|
|
sentiment_per_asset={"BTC": SentimentScores(polarity=0.7, confidence=0.85, positive_prob=0.8, negative_prob=0.1, neutral_prob=0.1)},
|
||
|
|
emotions_per_asset={"BTC": EmotionScores(joy=0.8, fear=0.1, anger=0.05, greed=0.7, sadness=0.05, intensity=0.75)},
|
||
|
|
events=[EventClassification(event_type=EventType.LISTING, confidence=0.7, assets_involved=["BTC"], key_details={}, severity=0.5)],
|
||
|
|
temporal=TemporalAnchor(event_time=None, time_horizon="immediate", is_breaking=True, is_scheduled=False),
|
||
|
|
credibility=CredibilityScore(source_base=0.8, content_quality=0.8, engagement_authenticity=0.7, cross_source_corroboration=0.6, historical_accuracy=0.8, composite=0.78),
|
||
|
|
processed_ts=1234567895.0,
|
||
|
|
processing_latency_ms=45.2,
|
||
|
|
model_versions={}
|
||
|
|
)
|
||
|
|
|
||
|
|
sink.buffer_processed_item(item)
|
||
|
|
assert len(sink._batch_buffer) == 1
|
||
|
|
|
||
|
|
def test_buffer_score_output(self, sink):
|
||
|
|
"""Test buffering scored output"""
|
||
|
|
from sentiment_engine.schemas.output import SentimentOutput, MarketSentiment, AssetSentiment, PumpDumpScore, VelocityMetrics
|
||
|
|
import time
|
||
|
|
|
||
|
|
market = MarketSentiment(
|
||
|
|
fear_state=25.0, greed_state=75.0, sentiment_index=50.0,
|
||
|
|
hype_velocity=65.0, pub_velocity=55.0,
|
||
|
|
aggregate_pump_risk=75.0, aggregate_dump_risk=20.0,
|
||
|
|
top_pump_assets=["BTC", "ETH"],
|
||
|
|
top_dump_assets=[],
|
||
|
|
dominant_events=[],
|
||
|
|
industry_breakdown={},
|
||
|
|
last_update_ts=time.time(),
|
||
|
|
total_sources=10,
|
||
|
|
total_assets=50
|
||
|
|
)
|
||
|
|
|
||
|
|
asset = AssetSentiment(
|
||
|
|
asset_id="BTC",
|
||
|
|
fear_state=20.0,
|
||
|
|
greed_state=80.0,
|
||
|
|
sentiment_polarity=60.0,
|
||
|
|
pump_dump=PumpDumpScore(asset_id="BTC", pump_score=75.0, dump_score=15.0, pump_confidence=0.8, dump_confidence=0.7, last_update_ts=time.time()),
|
||
|
|
last_update_ts=time.time()
|
||
|
|
)
|
||
|
|
|
||
|
|
output = SentimentOutput(
|
||
|
|
timestamp=time.time(),
|
||
|
|
market=market,
|
||
|
|
assets={"BTC": asset}
|
||
|
|
)
|
||
|
|
|
||
|
|
sink.buffer_score_output(output)
|
||
|
|
assert len(sink._batch_buffer) >= 2 # asset + market
|