Files
sideline/tests/test_data_sources.py

221 lines
7.4 KiB
Python

"""
Tests for engine/data_sources/sources.py - data source implementations.
Tests HeadlinesDataSource, PoetryDataSource, EmptyDataSource, and the
base DataSource class functionality.
"""
from unittest.mock import patch
import pytest
from engine.data_sources.sources import (
EmptyDataSource,
HeadlinesDataSource,
PoetryDataSource,
SourceItem,
)
class TestSourceItem:
"""Test SourceItem dataclass."""
def test_source_item_creation(self):
"""SourceItem can be created with required fields."""
item = SourceItem(
content="Test headline",
source="test_source",
timestamp="2024-01-01",
)
assert item.content == "Test headline"
assert item.source == "test_source"
assert item.timestamp == "2024-01-01"
assert item.metadata is None
def test_source_item_with_metadata(self):
"""SourceItem can include optional metadata."""
metadata = {"author": "John", "category": "tech"}
item = SourceItem(
content="Test",
source="test",
timestamp="2024-01-01",
metadata=metadata,
)
assert item.metadata == metadata
class TestEmptyDataSource:
"""Test EmptyDataSource."""
def test_empty_source_name(self):
"""EmptyDataSource has correct name."""
source = EmptyDataSource()
assert source.name == "empty"
def test_empty_source_is_not_dynamic(self):
"""EmptyDataSource is static, not dynamic."""
source = EmptyDataSource()
assert source.is_dynamic is False
def test_empty_source_fetch_returns_blank_content(self):
"""EmptyDataSource.fetch() returns blank lines."""
source = EmptyDataSource(width=80, height=24)
items = source.fetch()
assert len(items) == 1
assert isinstance(items[0], SourceItem)
assert items[0].source == "empty"
# Content should be 24 lines of 80 spaces
lines = items[0].content.split("\n")
assert len(lines) == 24
assert all(len(line) == 80 for line in lines)
def test_empty_source_get_items_caches_result(self):
"""EmptyDataSource.get_items() caches the result."""
source = EmptyDataSource()
items1 = source.get_items()
items2 = source.get_items()
# Should return same cached items (same object reference)
assert items1 is items2
class TestHeadlinesDataSource:
"""Test HeadlinesDataSource."""
def test_headlines_source_name(self):
"""HeadlinesDataSource has correct name."""
source = HeadlinesDataSource()
assert source.name == "headlines"
def test_headlines_source_is_static(self):
"""HeadlinesDataSource is static."""
source = HeadlinesDataSource()
assert source.is_dynamic is False
def test_headlines_fetch_returns_source_items(self):
"""HeadlinesDataSource.fetch() returns SourceItem list."""
mock_items = [
("Test Article 1", "source1", "10:30"),
("Test Article 2", "source2", "11:45"),
]
with patch("engine.fetch.fetch_all") as mock_fetch_all:
mock_fetch_all.return_value = (mock_items, 2, 0)
source = HeadlinesDataSource()
items = source.fetch()
assert len(items) == 2
assert all(isinstance(item, SourceItem) for item in items)
assert items[0].content == "Test Article 1"
assert items[0].source == "source1"
assert items[0].timestamp == "10:30"
def test_headlines_fetch_with_empty_feed(self):
"""HeadlinesDataSource handles empty feeds gracefully."""
with patch("engine.fetch.fetch_all") as mock_fetch_all:
mock_fetch_all.return_value = ([], 0, 1)
source = HeadlinesDataSource()
items = source.fetch()
# Should return empty list
assert isinstance(items, list)
assert len(items) == 0
def test_headlines_get_items_caches_result(self):
"""HeadlinesDataSource.get_items() caches the result."""
mock_items = [("Test Article", "source", "12:00")]
with patch("engine.fetch.fetch_all") as mock_fetch_all:
mock_fetch_all.return_value = (mock_items, 1, 0)
source = HeadlinesDataSource()
items1 = source.get_items()
items2 = source.get_items()
# Should only call fetch once (cached)
assert mock_fetch_all.call_count == 1
assert items1 is items2
def test_headlines_refresh_clears_cache(self):
"""HeadlinesDataSource.refresh() clears cache and refetches."""
mock_items = [("Test Article", "source", "12:00")]
with patch("engine.fetch.fetch_all") as mock_fetch_all:
mock_fetch_all.return_value = (mock_items, 1, 0)
source = HeadlinesDataSource()
source.get_items()
source.refresh()
source.get_items()
# Should call fetch twice (once for initial, once for refresh)
assert mock_fetch_all.call_count == 2
class TestPoetryDataSource:
"""Test PoetryDataSource."""
def test_poetry_source_name(self):
"""PoetryDataSource has correct name."""
source = PoetryDataSource()
assert source.name == "poetry"
def test_poetry_source_is_static(self):
"""PoetryDataSource is static."""
source = PoetryDataSource()
assert source.is_dynamic is False
def test_poetry_fetch_returns_source_items(self):
"""PoetryDataSource.fetch() returns SourceItem list."""
mock_items = [
("Poetry line 1", "Poetry Source 1", ""),
("Poetry line 2", "Poetry Source 2", ""),
]
with patch("engine.fetch.fetch_poetry") as mock_fetch_poetry:
mock_fetch_poetry.return_value = (mock_items, 2, 0)
source = PoetryDataSource()
items = source.fetch()
assert len(items) == 2
assert all(isinstance(item, SourceItem) for item in items)
assert items[0].content == "Poetry line 1"
assert items[0].source == "Poetry Source 1"
def test_poetry_get_items_caches_result(self):
"""PoetryDataSource.get_items() caches result."""
mock_items = [("Poetry line", "Poetry Source", "")]
with patch("engine.fetch.fetch_poetry") as mock_fetch_poetry:
mock_fetch_poetry.return_value = (mock_items, 1, 0)
source = PoetryDataSource()
items1 = source.get_items()
items2 = source.get_items()
# Should only fetch once (cached)
assert mock_fetch_poetry.call_count == 1
assert items1 is items2
class TestDataSourceInterface:
"""Test DataSource base class interface."""
def test_data_source_stream_not_implemented(self):
"""DataSource.stream() raises NotImplementedError."""
source = EmptyDataSource()
with pytest.raises(NotImplementedError):
source.stream()
def test_data_source_is_dynamic_defaults_false(self):
"""DataSource.is_dynamic defaults to False."""
source = EmptyDataSource()
assert source.is_dynamic is False
def test_data_source_refresh_updates_cache(self):
"""DataSource.refresh() updates internal cache."""
source = EmptyDataSource()
source.get_items()
items_refreshed = source.refresh()
# refresh() should return new items
assert isinstance(items_refreshed, list)