305 lines
10 KiB
Python
305 lines
10 KiB
Python
"""
|
|
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
|