feat: speed up and stabilise test suite (#676)

## ℹ️ Description
*Provide a concise summary of the changes introduced in this pull
request.*

- Link to the related issue(s): Issue #
- Describe the motivation and context for this change.

Refactors the test harness for faster and more reliable feedback: adds
deterministic time freezing for update checks, accelerates and refactors
smoke tests to run in-process, defaults pytest to xdist with durations
tracking, and adjusts CI triggers so PRs run the test matrix only once.

## 📋 Changes Summary

- add pytest-xdist + durations reporting defaults, force deterministic
locale and slow markers, and document the workflow adjustments
- run smoke tests in-process (no subprocess churn), mock update
checks/logging, and mark slow specs appropriately
- deflake update check interval tests by freezing datetime and simplify
FixedDateTime helper
- limit GitHub Actions `push` trigger to `main` so feature branches rely
on the single pull_request run

### ⚙️ Type of Change
Select the type(s) of change(s) included in this pull request:
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [x]  New feature (adds new functionality without breaking existing
usage)
- [ ] 💥 Breaking change (changes that might break existing user setups,
scripts, or configurations)


##  Checklist
Before requesting a review, confirm the following:
- [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**
* Ensure tests run in a consistent English locale and restore prior
locale after each run
  * Mark integration scraping tests as slow for clearer categorization
* Replace subprocess-based CLI tests with an in-process runner that
returns structured results and captures combined stdout/stderr/logs;
disable update checks during smoke tests
* Freeze current time in update-check tests for deterministic assertions
* Add mock for process enumeration in web‑scraping unit tests to
stabilize macOS-specific warnings

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Jens
2025-11-12 21:29:51 +01:00
committed by GitHub
parent 91cb677d17
commit 33d1964f86
9 changed files with 181 additions and 24 deletions

View File

@@ -5,7 +5,7 @@
from __future__ import annotations
import json
from datetime import datetime, timedelta, timezone
from datetime import datetime, timedelta, timezone, tzinfo
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
@@ -18,11 +18,47 @@ import requests
if TYPE_CHECKING:
from pytest_mock import MockerFixture
from kleinanzeigen_bot.model import update_check_state as update_check_state_module
from kleinanzeigen_bot.model.config_model import Config
from kleinanzeigen_bot.model.update_check_state import UpdateCheckState
from kleinanzeigen_bot.update_checker import UpdateChecker
def _freeze_update_state_datetime(monkeypatch:pytest.MonkeyPatch, fixed_now:datetime) -> None:
"""Patch UpdateCheckState to return a deterministic datetime.now/utcnow."""
class FixedDateTime(datetime):
@classmethod
def now(cls, tz:tzinfo | None = None) -> "FixedDateTime":
base = fixed_now.replace(tzinfo = None) if tz is None else fixed_now.astimezone(tz)
return cls(
base.year,
base.month,
base.day,
base.hour,
base.minute,
base.second,
base.microsecond,
tzinfo = base.tzinfo
)
@classmethod
def utcnow(cls) -> "FixedDateTime":
base = fixed_now.astimezone(timezone.utc).replace(tzinfo = None)
return cls(
base.year,
base.month,
base.day,
base.hour,
base.minute,
base.second,
base.microsecond
)
datetime_module = getattr(update_check_state_module, "datetime")
monkeypatch.setattr(datetime_module, "datetime", FixedDateTime)
@pytest.fixture
def config() -> Config:
return Config.model_validate({
@@ -256,10 +292,12 @@ class TestUpdateChecker:
# Should not raise an exception
state.save(state_file)
def test_update_check_state_interval_units(self) -> None:
def test_update_check_state_interval_units(self, monkeypatch:pytest.MonkeyPatch) -> None:
"""Test that different interval units are handled correctly."""
state = UpdateCheckState()
now = datetime.now(timezone.utc)
fixed_now = datetime(2025, 1, 15, 8, 0, tzinfo = timezone.utc)
_freeze_update_state_datetime(monkeypatch, fixed_now)
now = fixed_now
# Test seconds (should always be too short, fallback to 7d, only 2 days elapsed, so should_check is False)
state.last_check = now - timedelta(seconds = 30)
@@ -303,10 +341,14 @@ class TestUpdateChecker:
state.last_check = now - timedelta(days = 6)
assert state.should_check("1z") is False
def test_update_check_state_interval_validation(self) -> None:
def test_update_check_state_interval_validation(self, monkeypatch:pytest.MonkeyPatch) -> None:
"""Test that interval validation works correctly."""
state = UpdateCheckState()
now = datetime.now(timezone.utc)
fixed_now = datetime(2025, 1, 1, 12, 0, tzinfo = timezone.utc)
_freeze_update_state_datetime(monkeypatch, fixed_now)
now = fixed_now
state.last_check = now - timedelta(days = 1)
# Test minimum value (1d)