mirror of
https://github.com/Second-Hand-Friends/kleinanzeigen-bot.git
synced 2026-03-12 02:31:45 +01:00
fix: chores (#565)
This commit is contained in:
@@ -149,7 +149,7 @@ Die Nutzung erfolgt auf eigenes Risiko. Jede rechtswidrige Verwendung ist unters
|
||||
|
||||
1. Execute `bash build-image.sh`
|
||||
|
||||
1. Ensure the image is build:
|
||||
1. Ensure the image is built:
|
||||
|
||||
```
|
||||
$ docker image ls
|
||||
|
||||
@@ -6,15 +6,21 @@ import os
|
||||
|
||||
def abspath(relative_path:str, relative_to:str | None = None) -> str:
|
||||
"""
|
||||
Makes a given relative path absolute based on another file/folder
|
||||
Return a normalized absolute path based on *relative_to*.
|
||||
|
||||
If 'relative_path' is already absolute, it is normalized and returned.
|
||||
Otherwise, the function joins 'relative_path' with 'relative_to' (or the current working directory if not provided),
|
||||
normalizes the result, and returns the absolute path.
|
||||
"""
|
||||
|
||||
if not relative_to:
|
||||
return os.path.abspath(relative_path)
|
||||
|
||||
if os.path.isabs(relative_path):
|
||||
return relative_path
|
||||
return os.path.normpath(relative_path)
|
||||
|
||||
if os.path.isfile(relative_to):
|
||||
relative_to = os.path.dirname(relative_to)
|
||||
base = os.path.abspath(relative_to)
|
||||
if os.path.isfile(base):
|
||||
base = os.path.dirname(base)
|
||||
|
||||
return os.path.normpath(os.path.join(relative_to, relative_path))
|
||||
return os.path.normpath(os.path.join(base, relative_path))
|
||||
|
||||
@@ -17,12 +17,16 @@ def ensure(
|
||||
condition:Any | bool | Callable[[], bool], # noqa: FBT001 Boolean-typed positional argument in function definition
|
||||
error_message:str,
|
||||
timeout:float = 5,
|
||||
poll_requency:float = 0.5
|
||||
poll_frequency:float = 0.5
|
||||
) -> None:
|
||||
"""
|
||||
:param timeout: timespan in seconds until when the condition must become `True`, default is 5 seconds
|
||||
:param poll_requency: sleep interval between calls in seconds, default is 0.5 seconds
|
||||
:raises AssertionError: if condition did not come `True` within given timespan
|
||||
Ensure a condition is true, retrying until timeout.
|
||||
|
||||
:param condition: The condition to check (bool, value, or callable returning bool)
|
||||
:param error_message: The error message to raise if the condition is not met
|
||||
:param timeout: maximum time to wait in seconds, default is 5 seconds
|
||||
:param poll_frequency: sleep interval between calls in seconds, default is 0.5 seconds
|
||||
:raises AssertionError: if the condition is not met within the timeout
|
||||
"""
|
||||
if not isinstance(condition, Callable): # type: ignore[arg-type] # https://github.com/python/mypy/issues/6864
|
||||
if condition:
|
||||
@@ -31,15 +35,15 @@ def ensure(
|
||||
|
||||
if timeout < 0:
|
||||
raise AssertionError("[timeout] must be >= 0")
|
||||
if poll_requency < 0:
|
||||
raise AssertionError("[poll_requency] must be >= 0")
|
||||
if poll_frequency < 0:
|
||||
raise AssertionError("[poll_frequency] must be >= 0")
|
||||
|
||||
start_at = time.time()
|
||||
while not condition(): # type: ignore[operator]
|
||||
elapsed = time.time() - start_at
|
||||
if elapsed >= timeout:
|
||||
raise AssertionError(_(error_message))
|
||||
time.sleep(poll_requency)
|
||||
time.sleep(poll_frequency)
|
||||
|
||||
|
||||
def get_attr(obj:Mapping[str, Any] | Any, key:str, default:Any | None = None) -> Any:
|
||||
|
||||
@@ -54,9 +54,9 @@ class TestFiles:
|
||||
"""Test abspath function with a nonexistent file/directory as relative_to."""
|
||||
nonexistent_path = "nonexistent/path"
|
||||
|
||||
# Test with a relative path
|
||||
# Test with a relative path; should still yield an absolute path
|
||||
result = abspath("test/path", nonexistent_path)
|
||||
expected = os.path.normpath(os.path.join(nonexistent_path, "test/path"))
|
||||
expected = os.path.normpath(os.path.join(os.path.abspath(nonexistent_path), "test/path"))
|
||||
assert result == expected
|
||||
|
||||
# Test with an absolute path
|
||||
|
||||
@@ -1,29 +1,135 @@
|
||||
# SPDX-FileCopyrightText: © Sebastian Thomschke and contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
|
||||
import asyncio
|
||||
import decimal
|
||||
import sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
|
||||
from kleinanzeigen_bot.utils import misc
|
||||
|
||||
|
||||
def test_ensure() -> None:
|
||||
misc.ensure(True, "TRUE")
|
||||
misc.ensure("Some Value", "TRUE")
|
||||
misc.ensure(123, "TRUE")
|
||||
misc.ensure(-123, "TRUE")
|
||||
misc.ensure(lambda: True, "TRUE")
|
||||
def test_now_returns_utc_datetime() -> None:
|
||||
dt = misc.now()
|
||||
assert dt.tzinfo is not None
|
||||
assert dt.tzinfo.utcoffset(dt) == timedelta(0)
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
misc.ensure(False, "FALSE")
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
misc.ensure(0, "FALSE")
|
||||
def test_is_frozen_default() -> None:
|
||||
assert misc.is_frozen() is False
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
misc.ensure("", "FALSE")
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
misc.ensure(None, "FALSE")
|
||||
def test_is_frozen_true(monkeypatch:pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(sys, "frozen", True, raising = False)
|
||||
assert misc.is_frozen() is True
|
||||
|
||||
|
||||
def test_ainput_is_coroutine() -> None:
|
||||
assert asyncio.iscoroutinefunction(misc.ainput)
|
||||
|
||||
|
||||
def test_parse_decimal_valid_inputs() -> None:
|
||||
assert misc.parse_decimal(5) == decimal.Decimal("5")
|
||||
assert misc.parse_decimal(5.5) == decimal.Decimal("5.5")
|
||||
assert misc.parse_decimal("5.5") == decimal.Decimal("5.5")
|
||||
assert misc.parse_decimal("5,5") == decimal.Decimal("5.5")
|
||||
assert misc.parse_decimal("1.005,5") == decimal.Decimal("1005.5")
|
||||
assert misc.parse_decimal("1,005.5") == decimal.Decimal("1005.5")
|
||||
|
||||
|
||||
def test_parse_decimal_invalid_input() -> None:
|
||||
with pytest.raises(decimal.DecimalException):
|
||||
misc.parse_decimal("not_a_number")
|
||||
|
||||
|
||||
def test_parse_datetime_none_returns_none() -> None:
|
||||
assert misc.parse_datetime(None) is None
|
||||
|
||||
|
||||
def test_parse_datetime_from_datetime() -> None:
|
||||
dt = datetime(2020, 1, 1, 0, 0, tzinfo = timezone.utc)
|
||||
assert misc.parse_datetime(dt, add_timezone_if_missing = False) == dt
|
||||
|
||||
|
||||
def test_parse_datetime_from_string() -> None:
|
||||
dt_str = "2020-01-01T00:00:00"
|
||||
result = misc.parse_datetime(dt_str, add_timezone_if_missing = False)
|
||||
assert result == datetime(2020, 1, 1, 0, 0) # noqa: DTZ001
|
||||
|
||||
|
||||
def test_parse_duration_various_inputs() -> None:
|
||||
assert misc.parse_duration("1h 30m") == timedelta(hours = 1, minutes = 30)
|
||||
assert misc.parse_duration("2d 4h 15m 10s") == timedelta(days = 2, hours = 4, minutes = 15, seconds = 10)
|
||||
assert misc.parse_duration("45m") == timedelta(minutes = 45)
|
||||
assert misc.parse_duration("3d") == timedelta(days = 3)
|
||||
assert misc.parse_duration("5h 5h") == timedelta(hours = 10)
|
||||
assert misc.parse_duration("invalid input") == timedelta(0)
|
||||
|
||||
|
||||
def test_format_timedelta_examples() -> None:
|
||||
assert misc.format_timedelta(timedelta(seconds = 90)) == "1 minute, 30 seconds"
|
||||
assert misc.format_timedelta(timedelta(hours = 1)) == "1 hour"
|
||||
assert misc.format_timedelta(timedelta(days = 2, hours = 5)) == "2 days, 5 hours"
|
||||
assert misc.format_timedelta(timedelta(0)) == "0 seconds"
|
||||
|
||||
|
||||
class Dummy:
|
||||
def __init__(self, contact:object) -> None:
|
||||
self.contact = contact
|
||||
|
||||
|
||||
def test_get_attr_object_and_dict() -> None:
|
||||
assert misc.get_attr(Dummy({"email": "user@example.com"}), "contact.email") == "user@example.com"
|
||||
assert misc.get_attr(Dummy({"email": "user@example.com"}), "contact.foo") is None
|
||||
assert misc.get_attr(Dummy({"email": None}), "contact.email", default = "n/a") == "n/a"
|
||||
assert misc.get_attr(Dummy(None), "contact.email", default = "n/a") == "n/a"
|
||||
assert misc.get_attr({"contact": {"email": "data@example.com"}}, "contact.email") == "data@example.com"
|
||||
assert misc.get_attr({"contact": {"email": "user@example.com"}}, "contact.foo") is None
|
||||
assert misc.get_attr({"contact": {"email": None}}, "contact.email", default = "n/a") == "n/a"
|
||||
assert misc.get_attr({}, "contact.email", default = "none") == "none"
|
||||
|
||||
|
||||
def test_ensure_negative_timeout() -> None:
|
||||
with pytest.raises(AssertionError, match = r"\[timeout\] must be >= 0"):
|
||||
misc.ensure(lambda: True, "Should fail", timeout = -1)
|
||||
|
||||
|
||||
def test_ensure_negative_poll_frequency() -> None:
|
||||
with pytest.raises(AssertionError, match = r"\[poll_frequency\] must be >= 0"):
|
||||
misc.ensure(lambda: True, "Should fail", poll_frequency = -1)
|
||||
|
||||
|
||||
def test_ensure_callable_condition_becomes_true(monkeypatch:pytest.MonkeyPatch) -> None:
|
||||
# Should return before timeout if condition becomes True
|
||||
state = {"called": 0}
|
||||
|
||||
def cond() -> bool:
|
||||
state["called"] += 1
|
||||
return state["called"] > 2
|
||||
misc.ensure(cond, "Should not fail", timeout = 1, poll_frequency = 0.01)
|
||||
|
||||
|
||||
def test_ensure_callable_condition_timeout() -> None:
|
||||
# Should raise AssertionError after timeout if condition never True
|
||||
with pytest.raises(AssertionError):
|
||||
misc.ensure(lambda: False, "FALSE", timeout = 2)
|
||||
misc.ensure(lambda: False, "Timeout fail", timeout = 0.05, poll_frequency = 0.01)
|
||||
|
||||
|
||||
def test_ensure_non_callable_truthy_and_falsy() -> None:
|
||||
# Truthy values should not raise
|
||||
misc.ensure(True, "Should not fail for True")
|
||||
misc.ensure("Some Value", "Should not fail for non-empty string")
|
||||
misc.ensure(123, "Should not fail for positive int")
|
||||
misc.ensure(-123, "Should not fail for negative int")
|
||||
|
||||
# Falsy values should raise AssertionError
|
||||
with pytest.raises(AssertionError):
|
||||
misc.ensure(False, "Should fail for False")
|
||||
with pytest.raises(AssertionError):
|
||||
misc.ensure(0, "Should fail for 0")
|
||||
with pytest.raises(AssertionError):
|
||||
misc.ensure("", "Should fail for empty string")
|
||||
with pytest.raises(AssertionError):
|
||||
misc.ensure(None, "Should fail for None")
|
||||
|
||||
Reference in New Issue
Block a user