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:
Jens
2025-11-17 11:02:18 +01:00
committed by GitHub
parent b99966817c
commit 89df56bf8b
4 changed files with 120 additions and 124 deletions

View File

@@ -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."""