""" Tests for Sentiment Service This module tests sentiment service functionality including database operations. """ import pytest from sqlalchemy.orm import Session from datetime import datetime from app.services.sentiment_service import ( process_tweet_sentiment, process_tweet_batch, process_reddit_post_sentiment, process_reddit_post_batch, get_sentiment_by_entity, get_sentiments_by_match, calculate_match_sentiment_metrics, get_global_sentiment_metrics ) from app.models.sentiment_score import SentimentScore from app.models.tweet import Tweet from app.models.reddit_post import RedditPost @pytest.fixture def sample_tweet(db: Session): """Create a sample tweet for testing.""" tweet = Tweet( tweet_id="test_tweet_1", text="This is a test tweet about a great game!", created_at=datetime.utcnow(), retweet_count=5, like_count=10, match_id=1, source="twitter" ) db.add(tweet) db.commit() db.refresh(tweet) return tweet @pytest.fixture def sample_tweets(db: Session): """Create sample tweets for batch testing.""" tweets = [] texts = [ "I love this team! Best performance ever!", "Terrible game today. Worst performance.", "It was an okay match. Nothing special.", "Amazing comeback! What a victory!", "Disappointed with the result." ] for i, text in enumerate(texts): tweet = Tweet( tweet_id=f"test_tweet_{i}", text=text, created_at=datetime.utcnow(), retweet_count=i * 2, like_count=i * 3, match_id=1, source="twitter" ) db.add(tweet) tweets.append(tweet) db.commit() for tweet in tweets: db.refresh(tweet) return tweets @pytest.fixture def sample_reddit_post(db: Session): """Create a sample Reddit post for testing.""" post = RedditPost( post_id="test_post_1", title="This is a test post about a great game!", text="The team played amazingly well today!", upvotes=15, created_at=datetime.utcnow(), match_id=1, subreddit="test_subreddit", source="reddit" ) db.add(post) db.commit() db.refresh(post) return post @pytest.fixture def sample_reddit_posts(db: Session): """Create sample Reddit posts for batch testing.""" posts = [] titles = [ "Great game today!", "Terrible performance", "Okay match", "Amazing victory", "Disappointed result" ] texts = [ "The team was amazing!", "Worst game ever", "Nothing special", "What a comeback!", "Not good enough" ] for i, (title, text) in enumerate(zip(titles, texts)): post = RedditPost( post_id=f"test_post_{i}", title=title, text=text, upvotes=i * 5, created_at=datetime.utcnow(), match_id=1, subreddit="test_subreddit", source="reddit" ) db.add(post) posts.append(post) db.commit() for post in posts: db.refresh(post) return posts class TestProcessTweetSentiment: """Test processing single tweet sentiment.""" def test_process_tweet_sentiment_creates_record(self, db: Session, sample_tweet: Tweet): """Test that processing tweet sentiment creates a database record.""" sentiment = process_tweet_sentiment(db, sample_tweet.tweet_id, sample_tweet.text) assert sentiment.id is not None assert sentiment.entity_id == sample_tweet.tweet_id assert sentiment.entity_type == 'tweet' assert sentiment.sentiment_type in ['positive', 'negative', 'neutral'] assert -1 <= sentiment.score <= 1 assert 0 <= sentiment.positive <= 1 assert 0 <= sentiment.negative <= 1 assert 0 <= sentiment.neutral <= 1 def test_process_tweet_sentiment_positive(self, db: Session, sample_tweet: Tweet): """Test processing positive tweet sentiment.""" positive_text = "I absolutely love this team! Best performance ever!" sentiment = process_tweet_sentiment(db, "test_pos", positive_text) assert sentiment.sentiment_type == 'positive' assert sentiment.score > 0.05 def test_process_tweet_sentiment_negative(self, db: Session, sample_tweet: Tweet): """Test processing negative tweet sentiment.""" negative_text = "This is terrible! Worst performance ever!" sentiment = process_tweet_sentiment(db, "test_neg", negative_text) assert sentiment.sentiment_type == 'negative' assert sentiment.score < -0.05 class TestProcessTweetBatch: """Test batch processing of tweet sentiments.""" def test_process_tweet_batch(self, db: Session, sample_tweets: list[Tweet]): """Test processing multiple tweets in batch.""" sentiments = process_tweet_batch(db, sample_tweets) assert len(sentiments) == 5 assert all(s.entity_type == 'tweet' for s in sentiments) assert all(s.id is not None for s in sentiments) def test_process_tweet_batch_empty(self, db: Session): """Test processing empty batch.""" sentiments = process_tweet_batch(db, []) assert sentiments == [] def test_process_tweet_batch_sentiments_calculated(self, db: Session, sample_tweets: list[Tweet]): """Test that sentiments are correctly calculated for batch.""" sentiments = process_tweet_batch(db, sample_tweets) # Check that at least one positive and one negative sentiment exists sentiment_types = [s.sentiment_type for s in sentiments] assert 'positive' in sentiment_types assert 'negative' in sentiment_types class TestProcessRedditPostSentiment: """Test processing single Reddit post sentiment.""" def test_process_reddit_post_sentiment_creates_record(self, db: Session, sample_reddit_post: RedditPost): """Test that processing Reddit post sentiment creates a database record.""" sentiment = process_reddit_post_sentiment( db, sample_reddit_post.post_id, f"{sample_reddit_post.title} {sample_reddit_post.text}" ) assert sentiment.id is not None assert sentiment.entity_id == sample_reddit_post.post_id assert sentiment.entity_type == 'reddit_post' assert sentiment.sentiment_type in ['positive', 'negative', 'neutral'] class TestProcessRedditPostBatch: """Test batch processing of Reddit post sentiments.""" def test_process_reddit_post_batch(self, db: Session, sample_reddit_posts: list[RedditPost]): """Test processing multiple Reddit posts in batch.""" sentiments = process_reddit_post_batch(db, sample_reddit_posts) assert len(sentiments) == 5 assert all(s.entity_type == 'reddit_post' for s in sentiments) assert all(s.id is not None for s in sentiments) def test_process_reddit_post_batch_empty(self, db: Session): """Test processing empty batch.""" sentiments = process_reddit_post_batch(db, []) assert sentiments == [] class TestGetSentimentByEntity: """Test retrieving sentiment by entity.""" def test_get_sentiment_by_entity_found(self, db: Session, sample_tweet: Tweet): """Test retrieving existing sentiment by entity.""" process_tweet_sentiment(db, sample_tweet.tweet_id, sample_tweet.text) sentiment = get_sentiment_by_entity( db, sample_tweet.tweet_id, 'tweet' ) assert sentiment is not None assert sentiment.entity_id == sample_tweet.tweet_id def test_get_sentiment_by_entity_not_found(self, db: Session): """Test retrieving non-existent sentiment.""" sentiment = get_sentiment_by_entity(db, "nonexistent_id", "tweet") assert sentiment is None class TestGetSentimentsByMatch: """Test retrieving sentiments by match.""" def test_get_sentiments_by_match(self, db: Session, sample_tweets: list[Tweet]): """Test retrieving all sentiments for a match.""" process_tweet_batch(db, sample_tweets) sentiments = get_sentiments_by_match(db, 1) assert len(sentiments) == 5 assert all(s.entity_type == 'tweet' for s in sentiments) def test_get_sentiments_by_match_empty(self, db: Session): """Test retrieving sentiments for match with no data.""" sentiments = get_sentiments_by_match(db, 999) assert sentiments == [] class TestCalculateMatchSentimentMetrics: """Test calculating sentiment metrics for a match.""" def test_calculate_match_sentiment_metrics(self, db: Session, sample_tweets: list[Tweet]): """Test calculating metrics for a match.""" process_tweet_batch(db, sample_tweets) metrics = calculate_match_sentiment_metrics(db, 1) assert metrics['match_id'] == 1 assert metrics['total_count'] == 5 assert metrics['positive_count'] + metrics['negative_count'] + metrics['neutral_count'] == 5 assert metrics['positive_ratio'] + metrics['negative_ratio'] + metrics['neutral_ratio'] == 1.0 assert -1 <= metrics['average_compound'] <= 1 def test_calculate_match_sentiment_metrics_empty(self, db: Session): """Test calculating metrics for match with no data.""" metrics = calculate_match_sentiment_metrics(db, 999) assert metrics['match_id'] == 999 assert metrics['total_count'] == 0 assert metrics['average_compound'] == 0.0 class TestGetGlobalSentimentMetrics: """Test calculating global sentiment metrics.""" def test_get_global_sentiment_metrics(self, db: Session, sample_tweets: list[Tweet]): """Test calculating global metrics.""" process_tweet_batch(db, sample_tweets) metrics = get_global_sentiment_metrics(db) assert metrics['total_count'] == 5 assert metrics['positive_count'] + metrics['negative_count'] + metrics['neutral_count'] == 5 assert metrics['positive_ratio'] + metrics['negative_ratio'] + metrics['neutral_ratio'] == 1.0 def test_get_global_sentiment_metrics_empty(self, db: Session): """Test calculating global metrics with no data.""" metrics = get_global_sentiment_metrics(db) assert metrics['total_count'] == 0 assert metrics['average_compound'] == 0.0