""" 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)