156 lines
7.1 KiB
Python
156 lines
7.1 KiB
Python
"""
|
|
Simplified tests for sentiment fields migration that don't require database connection.
|
|
Tests the migration script structure and logic.
|
|
"""
|
|
|
|
import pytest
|
|
import ast
|
|
from pathlib import Path
|
|
|
|
|
|
class TestSentimentFieldsMigrationScript:
|
|
"""Test the sentiment fields migration script structure and content."""
|
|
|
|
@pytest.fixture
|
|
def migration_file_path(self):
|
|
"""Path to the migration file."""
|
|
return Path(__file__).parent.parent.parent.parent / "alembic" / "versions" / "20250116_1200_0001_add_sentiment_fields.py"
|
|
|
|
@pytest.fixture
|
|
def migration_content(self, migration_file_path):
|
|
"""Read migration file content."""
|
|
return migration_file_path.read_text()
|
|
|
|
def test_migration_file_exists(self, migration_file_path):
|
|
"""Test that the migration file exists."""
|
|
assert migration_file_path.exists(), "Migration file should exist"
|
|
|
|
def test_migration_has_required_functions(self, migration_content):
|
|
"""Test that migration has upgrade and downgrade functions."""
|
|
# Parse the Python code
|
|
tree = ast.parse(migration_content)
|
|
|
|
function_names = [node.name for node in ast.walk(tree) if isinstance(node, ast.FunctionDef)]
|
|
|
|
assert "upgrade" in function_names, "Migration should have upgrade() function"
|
|
assert "downgrade" in function_names, "Migration should have downgrade() function"
|
|
|
|
def test_migration_has_required_metadata(self, migration_content):
|
|
"""Test that migration has required revision metadata."""
|
|
# Check for required revision identifiers
|
|
assert "revision = " in migration_content, "Should have revision identifier"
|
|
assert "down_revision = " in migration_content, "Should have down_revision identifier"
|
|
assert "upgrade() -> None:" in migration_content, "upgrade function should be typed"
|
|
assert "downgrade() -> None:" in migration_content, "downgrade function should be typed"
|
|
|
|
def test_upgrade_adds_sentiment_confidence_column(self, migration_content):
|
|
"""Test that upgrade adds sentiment_confidence column."""
|
|
assert "op.add_column('news_articles', sa.Column('sentiment_confidence', sa.Float(), nullable=True))" in migration_content, \
|
|
"Should add sentiment_confidence FLOAT column"
|
|
|
|
def test_upgrade_adds_sentiment_label_column(self, migration_content):
|
|
"""Test that upgrade adds sentiment_label column."""
|
|
assert "op.add_column('news_articles', sa.Column('sentiment_label', sa.String(20), nullable=True))" in migration_content, \
|
|
"Should add sentiment_label VARCHAR(20) column"
|
|
|
|
def test_upgrade_creates_index(self, migration_content):
|
|
"""Test that upgrade creates index on sentiment_label."""
|
|
assert "op.create_index('idx_news_sentiment_label', 'news_articles', ['sentiment_label'])" in migration_content, \
|
|
"Should create index on sentiment_label"
|
|
|
|
def test_downgrade_removes_index_first(self, migration_content):
|
|
"""Test that downgrade removes index before columns (correct order)."""
|
|
lines = migration_content.split('\n')
|
|
|
|
# Find downgrade function
|
|
downgrade_start = None
|
|
for i, line in enumerate(lines):
|
|
if "def downgrade()" in line:
|
|
downgrade_start = i
|
|
break
|
|
|
|
assert downgrade_start is not None, "Should find downgrade function"
|
|
|
|
# Check that drop_index comes before drop_column
|
|
drop_index_line = None
|
|
drop_column_line = None
|
|
|
|
for i in range(downgrade_start, len(lines)):
|
|
line = lines[i].strip()
|
|
if "op.drop_index" in line:
|
|
drop_index_line = i
|
|
elif "op.drop_column" in line and "sentiment" in line:
|
|
if drop_column_line is None: # Only capture first sentiment column drop
|
|
drop_column_line = i
|
|
|
|
assert drop_index_line is not None, "Should drop index"
|
|
assert drop_column_line is not None, "Should drop columns"
|
|
assert drop_index_line < drop_column_line, "Should drop index before columns"
|
|
|
|
def test_downgrade_removes_sentiment_columns(self, migration_content):
|
|
"""Test that downgrade removes both sentiment columns."""
|
|
assert "op.drop_column('news_articles', 'sentiment_label')" in migration_content, \
|
|
"Should drop sentiment_label column"
|
|
assert "op.drop_column('news_articles', 'sentiment_confidence')" in migration_content, \
|
|
"Should drop sentiment_confidence column"
|
|
|
|
def test_migration_follows_naming_convention(self, migration_file_path):
|
|
"""Test that migration follows naming convention."""
|
|
filename = migration_file_path.name
|
|
|
|
# Should follow pattern: YYYYMMDD_HHMM_XXXX_descriptive_name.py
|
|
assert filename.startswith("20250116_"), "Should start with date"
|
|
assert "_add_sentiment_fields.py" in filename, "Should have descriptive name"
|
|
|
|
def test_migration_has_proper_imports(self, migration_content):
|
|
"""Test that migration has proper imports."""
|
|
assert "from alembic import op" in migration_content, "Should import op from alembic"
|
|
assert "import sqlalchemy as sa" in migration_content, "Should import sqlalchemy"
|
|
|
|
def test_revision_format(self, migration_content):
|
|
"""Test that revision follows expected format."""
|
|
lines = migration_content.split('\n')
|
|
|
|
# Find revision line
|
|
revision_line = None
|
|
for line in lines:
|
|
if line.strip().startswith("revision = "):
|
|
revision_line = line.strip()
|
|
break
|
|
|
|
assert revision_line is not None, "Should have revision line"
|
|
assert revision_line.startswith("revision = '20250116_1200_0001_add_sentiment_fields'"), \
|
|
"Revision should match filename"
|
|
|
|
|
|
class TestMigrationLogic:
|
|
"""Test migration logic expectations."""
|
|
|
|
def test_sentiment_confidence_column_spec(self):
|
|
"""Test sentiment_confidence column specification."""
|
|
# Should be FLOAT, nullable (for existing data)
|
|
# This represents confidence score from 0.0 to 1.0
|
|
pass # Column spec tested in migration content test above
|
|
|
|
def test_sentiment_label_column_spec(self):
|
|
"""Test sentiment_label column specification."""
|
|
# Should be VARCHAR(20), nullable
|
|
# This stores "positive", "negative", "neutral"
|
|
pass # Column spec tested in migration content test above
|
|
|
|
def test_index_specification(self):
|
|
"""Test index specification for sentiment filtering."""
|
|
# Index on sentiment_label for efficient WHERE clauses
|
|
# Name: idx_news_sentiment_label
|
|
pass # Index spec tested in migration content test above
|
|
|
|
def test_backward_compatibility(self):
|
|
"""Test that migration maintains backward compatibility."""
|
|
# New columns are nullable, so existing code continues to work
|
|
# Index doesn't affect existing queries
|
|
pass # Tested by nullable=True in column specs
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Run tests directly
|
|
pytest.main([__file__, "-v"]) |