mirror of
https://github.com/Second-Hand-Friends/kleinanzeigen-bot.git
synced 2026-03-12 10:31:50 +01:00
test: strengthen coverage for sessions, logging, and update check (#686)
## ℹ️ Description * Strengthen the session/logging/update-check tests to exercise real resources and guards while bringing the update-check docs in line with the supported interval units. - Link to the related issue(s): Issue #N/A ## 📋 Changes Summary - Reworked the `WebScrapingMixin` session tests so they capture each `stop` handler before the browser reference is nulled, ensuring cleanup logic is exercised without crashing. - Added targeted publish and update-check tests that patch the async helpers, guard logic, and logging handlers while confirming `requests.get` is skipped when the state gate is closed. - Updated `docs/update-check.md` to list only the actually supported interval units (up to 30 days) and noted the new guard coverage in the changelog. ### ⚙️ Type of Change - [x] 🐞 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (adds new functionality without breaking existing usage) - [ ] 💥 Breaking change (changes that might break existing user setups, scripts, or configurations) ## ✅ Checklist - [x] I have reviewed my changes to ensure they meet the project's standards. - [x] I have tested my changes and ensured that all tests pass (`pdm run test`). - [x] I have formatted the code (`pdm run format`). - [x] I have verified that linting passes (`pdm run lint`). - [x] I have updated documentation where necessary. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Expanded test coverage for publish workflow orchestration and update checking interval behavior. * Added comprehensive browser session cleanup tests, including idempotent operations and edge case handling. * Consolidated logging configuration tests with improved handler management validation. * Refined test fixtures and assertions for better test reliability. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -431,51 +431,70 @@ class TestSelectorTimeoutMessages:
|
||||
class TestWebScrapingSessionManagement:
|
||||
"""Test session management edge cases in WebScrapingMixin."""
|
||||
|
||||
def test_close_browser_session_cleans_up(self, mock_browser:AsyncMock) -> None:
|
||||
"""Test that close_browser_session cleans up browser and page references and kills child processes."""
|
||||
def test_close_browser_session_cleans_up_resources(self) -> None:
|
||||
"""Ensure browser and page references are cleared and child processes are killed."""
|
||||
scraper = WebScrapingMixin()
|
||||
scraper.browser = MagicMock()
|
||||
scraper.page = MagicMock()
|
||||
scraper.browser._process_pid = 12345
|
||||
scraper.browser._process_pid = 42
|
||||
stop_mock = scraper.browser.stop = MagicMock()
|
||||
# Patch psutil.Process and its children
|
||||
scraper.page = MagicMock(spec = Page)
|
||||
|
||||
with patch("psutil.Process") as mock_proc:
|
||||
mock_child = MagicMock()
|
||||
mock_child.is_running.return_value = True
|
||||
mock_proc.return_value.children.return_value = [mock_child]
|
||||
scraper.close_browser_session()
|
||||
# Browser stop should be called
|
||||
stop_mock.assert_called_once()
|
||||
# Child process kill should be called
|
||||
mock_child.kill.assert_called_once()
|
||||
# Browser and page references should be cleared
|
||||
assert scraper.browser is None
|
||||
assert scraper.page is None
|
||||
|
||||
def test_close_browser_session_double_close(self) -> None:
|
||||
"""Test that calling close_browser_session twice does not raise and is idempotent."""
|
||||
scraper.close_browser_session()
|
||||
|
||||
mock_proc.assert_called_once_with(42)
|
||||
stop_mock.assert_called_once()
|
||||
mock_child.kill.assert_called_once()
|
||||
assert scraper.browser is None
|
||||
assert scraper.page is None
|
||||
|
||||
def test_close_browser_session_idempotent(self) -> None:
|
||||
"""Repeated calls should leave the state clean without re-running cleanup logic."""
|
||||
scraper = WebScrapingMixin()
|
||||
scraper.browser = MagicMock()
|
||||
scraper.page = MagicMock()
|
||||
scraper.browser._process_pid = 12345
|
||||
scraper.browser.stop = MagicMock()
|
||||
scraper.browser._process_pid = 99
|
||||
stop_mock = scraper.browser.stop = MagicMock()
|
||||
scraper.page = MagicMock(spec = Page)
|
||||
|
||||
with patch("psutil.Process") as mock_proc:
|
||||
mock_child = MagicMock()
|
||||
mock_child.is_running.return_value = True
|
||||
mock_proc.return_value.children.return_value = [mock_child]
|
||||
mock_proc.return_value.children.return_value = []
|
||||
scraper.close_browser_session()
|
||||
# Second call should not raise
|
||||
scraper.close_browser_session()
|
||||
|
||||
def test_close_browser_session_no_browser(self) -> None:
|
||||
"""Test that close_browser_session is a no-op if browser is None."""
|
||||
mock_proc.assert_called_once()
|
||||
stop_mock.assert_called_once()
|
||||
|
||||
def test_close_browser_session_without_browser_skips_inspection(self) -> None:
|
||||
"""When no browser exists, no process inspection should run and the page should stay untouched."""
|
||||
scraper = WebScrapingMixin()
|
||||
scraper.browser = None # type: ignore[unused-ignore,reportAttributeAccessIssue]
|
||||
scraper.page = MagicMock()
|
||||
# Should not raise
|
||||
scraper.close_browser_session()
|
||||
# Page should remain unchanged
|
||||
assert scraper.page is not None
|
||||
preserved_page = MagicMock(spec = Page)
|
||||
scraper.page = preserved_page
|
||||
|
||||
with patch("psutil.Process") as mock_proc:
|
||||
scraper.close_browser_session()
|
||||
|
||||
mock_proc.assert_not_called()
|
||||
assert scraper.page is preserved_page
|
||||
|
||||
def test_close_browser_session_handles_missing_children(self) -> None:
|
||||
"""Child-less browsers should still stop cleanly without raising."""
|
||||
scraper = WebScrapingMixin()
|
||||
scraper.browser = MagicMock()
|
||||
scraper.browser._process_pid = 123
|
||||
stop_mock = scraper.browser.stop = MagicMock()
|
||||
scraper.page = MagicMock(spec = Page)
|
||||
|
||||
with patch("psutil.Process") as mock_proc:
|
||||
mock_proc.return_value.children.return_value = []
|
||||
scraper.close_browser_session()
|
||||
|
||||
mock_proc.assert_called_once()
|
||||
stop_mock.assert_called_once()
|
||||
|
||||
def test_get_compatible_browser_raises_on_unknown_os(self) -> None:
|
||||
"""Test get_compatible_browser raises AssertionError on unknown OS."""
|
||||
@@ -494,22 +513,6 @@ class TestWebScrapingSessionManagement:
|
||||
):
|
||||
scraper.get_compatible_browser()
|
||||
|
||||
def test_close_browser_session_no_children(self) -> None:
|
||||
"""Test that close_browser_session handles case when browser has no child processes."""
|
||||
scraper = WebScrapingMixin()
|
||||
scraper.browser = MagicMock()
|
||||
scraper.page = MagicMock()
|
||||
scraper.browser._process_pid = 12345
|
||||
stop_mock = scraper.browser.stop = MagicMock()
|
||||
|
||||
# Mock Process to return no children
|
||||
with patch("psutil.Process") as mock_proc:
|
||||
mock_proc.return_value.children.return_value = []
|
||||
scraper.close_browser_session()
|
||||
stop_mock.assert_called_once()
|
||||
assert scraper.browser is None
|
||||
assert scraper.page is None
|
||||
|
||||
|
||||
class TestWebScrolling:
|
||||
"""Test scrolling helpers."""
|
||||
|
||||
Reference in New Issue
Block a user