mirror of
https://github.com/Second-Hand-Friends/kleinanzeigen-bot.git
synced 2026-03-12 10:31:50 +01:00
refact: use ruff instead of autopep8,bandit,pylint for linting
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
"""
|
||||
SPDX-FileCopyrightText: © Sebastian Thomschke and contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
|
||||
"""
|
||||
# SPDX-FileCopyrightText: © Sebastian Thomschke and contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
@@ -33,7 +31,7 @@ def test_calculate_content_hash_with_none_values() -> None:
|
||||
assert len(hash_value) == 64 # SHA-256 hash is 64 characters long
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config,prefix,expected", [
|
||||
@pytest.mark.parametrize(("config", "prefix", "expected"), [
|
||||
# Test new flattened format - prefix
|
||||
(
|
||||
{"ad_defaults": {"description_prefix": "Hello"}},
|
||||
@@ -129,11 +127,11 @@ def test_get_description_affixes(
|
||||
expected: str
|
||||
) -> None:
|
||||
"""Test get_description_affixes function with various inputs."""
|
||||
result = ads.get_description_affixes(config, prefix)
|
||||
result = ads.get_description_affixes(config, prefix = prefix)
|
||||
assert result == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config,prefix,expected", [
|
||||
@pytest.mark.parametrize(("config", "prefix", "expected"), [
|
||||
# Add test for malformed config
|
||||
(
|
||||
{}, # Empty config
|
||||
@@ -161,16 +159,16 @@ def test_get_description_affixes(
|
||||
])
|
||||
def test_get_description_affixes_edge_cases(config: dict[str, Any], prefix: bool, expected: str) -> None:
|
||||
"""Test edge cases for description affix handling."""
|
||||
assert ads.get_description_affixes(config, prefix) == expected
|
||||
assert ads.get_description_affixes(config, prefix = prefix) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config,expected", [
|
||||
(None, ""), # Test with None
|
||||
([], ""), # Test with an empty list
|
||||
@pytest.mark.parametrize(("config", "expected"), [
|
||||
(None, ""), # Test with None
|
||||
([], ""), # Test with an empty list
|
||||
("string", ""), # Test with a string
|
||||
(123, ""), # Test with an integer
|
||||
(3.14, ""), # Test with a float
|
||||
(set(), ""), # Test with an empty set
|
||||
(123, ""), # Test with an integer
|
||||
(3.14, ""), # Test with a float
|
||||
(set(), ""), # Test with an empty set
|
||||
])
|
||||
def test_get_description_affixes_edge_cases_non_dict(config: Any, expected: str) -> None:
|
||||
"""Test get_description_affixes function with non-dict inputs."""
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
"""
|
||||
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 gc, pytest
|
||||
# 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 gc, pytest # isort: skip
|
||||
|
||||
from kleinanzeigen_bot import KleinanzeigenBot
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
"""
|
||||
SPDX-FileCopyrightText: © Jens Bergmann and contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
|
||||
"""
|
||||
import json, os
|
||||
# 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 json, os # isort: skip
|
||||
from typing import Any, TypedDict
|
||||
from unittest.mock import AsyncMock, MagicMock, call, patch
|
||||
|
||||
@@ -30,7 +28,7 @@ class _SpecialAttributesDict(TypedDict, total = False):
|
||||
condition_s: str
|
||||
|
||||
|
||||
class _TestCaseDict(TypedDict):
|
||||
class _TestCaseDict(TypedDict): # noqa: PYI049 Private TypedDict `...` is never used
|
||||
belen_conf: _BelenConfDict
|
||||
expected: _SpecialAttributesDict
|
||||
|
||||
@@ -44,15 +42,12 @@ class TestAdExtractorBasics:
|
||||
assert extractor.browser == browser_mock
|
||||
assert extractor.config == sample_config
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"url,expected_id",
|
||||
[
|
||||
("https://www.kleinanzeigen.de/s-anzeige/test-title/12345678", 12345678),
|
||||
("https://www.kleinanzeigen.de/s-anzeige/another-test/98765432", 98765432),
|
||||
("https://www.kleinanzeigen.de/s-anzeige/invalid-id/abc", -1),
|
||||
("https://www.kleinanzeigen.de/invalid-url", -1),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(("url", "expected_id"), [
|
||||
("https://www.kleinanzeigen.de/s-anzeige/test-title/12345678", 12345678),
|
||||
("https://www.kleinanzeigen.de/s-anzeige/another-test/98765432", 98765432),
|
||||
("https://www.kleinanzeigen.de/s-anzeige/invalid-id/abc", -1),
|
||||
("https://www.kleinanzeigen.de/invalid-url", -1),
|
||||
])
|
||||
def test_extract_ad_id_from_ad_url(self, test_extractor: AdExtractor, url: str, expected_id: int) -> None:
|
||||
"""Test extraction of ad ID from different URL formats."""
|
||||
assert test_extractor.extract_ad_id_from_ad_url(url) == expected_id
|
||||
@@ -61,16 +56,13 @@ class TestAdExtractorBasics:
|
||||
class TestAdExtractorPricing:
|
||||
"""Tests for pricing related functionality."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"price_text,expected_price,expected_type",
|
||||
[
|
||||
("50 €", 50, "FIXED"),
|
||||
("1.234 €", 1234, "FIXED"),
|
||||
("50 € VB", 50, "NEGOTIABLE"),
|
||||
("VB", None, "NEGOTIABLE"),
|
||||
("Zu verschenken", None, "GIVE_AWAY"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(("price_text", "expected_price", "expected_type"), [
|
||||
("50 €", 50, "FIXED"),
|
||||
("1.234 €", 1234, "FIXED"),
|
||||
("50 € VB", 50, "NEGOTIABLE"),
|
||||
("VB", None, "NEGOTIABLE"),
|
||||
("Zu verschenken", None, "GIVE_AWAY"),
|
||||
])
|
||||
@pytest.mark.asyncio
|
||||
# pylint: disable=protected-access
|
||||
async def test_extract_pricing_info(
|
||||
@@ -95,14 +87,11 @@ class TestAdExtractorPricing:
|
||||
class TestAdExtractorShipping:
|
||||
"""Tests for shipping related functionality."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"shipping_text,expected_type,expected_cost",
|
||||
[
|
||||
("+ Versand ab 2,99 €", "SHIPPING", 2.99),
|
||||
("Nur Abholung", "PICKUP", None),
|
||||
("Versand möglich", "SHIPPING", None),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(("shipping_text", "expected_type", "expected_cost"), [
|
||||
("+ Versand ab 2,99 €", "SHIPPING", 2.99),
|
||||
("Nur Abholung", "PICKUP", None),
|
||||
("Versand möglich", "SHIPPING", None),
|
||||
])
|
||||
@pytest.mark.asyncio
|
||||
# pylint: disable=protected-access
|
||||
async def test_extract_shipping_info(
|
||||
@@ -272,9 +261,9 @@ class TestAdExtractorNavigation:
|
||||
# Mocks needed for the actual execution flow
|
||||
ad_list_container_mock = MagicMock()
|
||||
pagination_section_mock = MagicMock()
|
||||
cardbox_mock = MagicMock() # Represents the <li> element
|
||||
link_mock = MagicMock() # Represents the <a> element
|
||||
link_mock.attrs = {'href': '/s-anzeige/test/12345'} # Configure the desired output
|
||||
cardbox_mock = MagicMock() # Represents the <li> element
|
||||
link_mock = MagicMock() # Represents the <a> element
|
||||
link_mock.attrs = {'href': '/s-anzeige/test/12345'} # Configure the desired output
|
||||
|
||||
# Mocks for elements potentially checked but maybe not strictly needed for output
|
||||
# (depending on how robust the mocking is)
|
||||
@@ -287,18 +276,18 @@ class TestAdExtractorNavigation:
|
||||
# 3. Find for ad list container (inside loop)
|
||||
# 4. Find for the link (inside list comprehension)
|
||||
mock_web_find.side_effect = [
|
||||
ad_list_container_mock, # Call 1: find #my-manageitems-adlist (before loop)
|
||||
pagination_section_mock, # Call 2: find .Pagination
|
||||
ad_list_container_mock, # Call 3: find #my-manageitems-adlist (inside loop)
|
||||
link_mock # Call 4: find 'div.manageitems-item-ad h3 a.text-onSurface'
|
||||
ad_list_container_mock, # Call 1: find #my-manageitems-adlist (before loop)
|
||||
pagination_section_mock, # Call 2: find .Pagination
|
||||
ad_list_container_mock, # Call 3: find #my-manageitems-adlist (inside loop)
|
||||
link_mock # Call 4: find 'div.manageitems-item-ad h3 a.text-onSurface'
|
||||
# Add more mocks here if the pagination navigation logic calls web_find again
|
||||
]
|
||||
|
||||
# 1. Find all 'Nächste' buttons (pagination check) - Return empty list for single page test case
|
||||
# 2. Find all '.cardbox' elements (inside loop)
|
||||
mock_web_find_all.side_effect = [
|
||||
[], # Call 1: find 'button[aria-label="Nächste"]' -> No next button = single page
|
||||
[cardbox_mock] # Call 2: find .cardbox -> One ad item
|
||||
[], # Call 1: find 'button[aria-label="Nächste"]' -> No next button = single page
|
||||
[cardbox_mock] # Call 2: find .cardbox -> One ad item
|
||||
# Add more mocks here if pagination navigation calls web_find_all
|
||||
]
|
||||
|
||||
@@ -550,9 +539,9 @@ class TestAdExtractorContact:
|
||||
"""Test contact info extraction when elements are not found."""
|
||||
with patch.object(extractor, 'page', MagicMock()), \
|
||||
patch.object(extractor, 'web_text', new_callable = AsyncMock, side_effect = TimeoutError()), \
|
||||
patch.object(extractor, 'web_find', new_callable = AsyncMock, side_effect = TimeoutError()):
|
||||
patch.object(extractor, 'web_find', new_callable = AsyncMock, side_effect = TimeoutError()), \
|
||||
pytest.raises(TimeoutError):
|
||||
|
||||
with pytest.raises(TimeoutError):
|
||||
await extractor._extract_contact_from_ad_page()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
"""
|
||||
SPDX-FileCopyrightText: © Sebastian Thomschke and contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
|
||||
"""
|
||||
# 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 pytest
|
||||
from _pytest.monkeypatch import MonkeyPatch # pylint: disable=import-private-name
|
||||
|
||||
from kleinanzeigen_bot.utils import i18n
|
||||
|
||||
|
||||
@pytest.mark.parametrize("lang, expected", [
|
||||
@pytest.mark.parametrize(("lang", "expected"), [
|
||||
(None, ("en", "US", "UTF-8")), # Test with no LANG variable (should default to ("en", "US", "UTF-8"))
|
||||
("fr", ("fr", None, "UTF-8")), # Test with just a language code
|
||||
("fr_CA", ("fr", "CA", "UTF-8")), # Test with language + region, no encoding
|
||||
@@ -29,7 +28,7 @@ def test_detect_locale(monkeypatch: MonkeyPatch, lang: str | None, expected: i18
|
||||
assert result == expected, f"For LANG={lang}, expected {expected} but got {result}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("lang, noun, count, prefix_with_count, expected", [
|
||||
@pytest.mark.parametrize(("lang", "noun", "count", "prefix_with_count", "expected"), [
|
||||
("en", "field", 1, True, "1 field"),
|
||||
("en", "field", 2, True, "2 fields"),
|
||||
("en", "field", 2, False, "fields"),
|
||||
@@ -54,5 +53,5 @@ def test_pluralize(
|
||||
) -> None:
|
||||
i18n.set_current_locale(i18n.Locale(lang, "US", "UTF_8"))
|
||||
|
||||
result = i18n.pluralize(noun, count, prefix_with_count)
|
||||
result = i18n.pluralize(noun, count, prefix_with_count = prefix_with_count)
|
||||
assert result == expected, f"For LANG={lang}, expected {expected} but got {result}"
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
"""
|
||||
SPDX-FileCopyrightText: © Jens Bergmann and contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
|
||||
"""
|
||||
import copy, os, tempfile
|
||||
# SPDX-FileCopyrightText: © Jens Bergmann and contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
|
||||
import copy, os, tempfile # isort: skip
|
||||
from collections.abc import Generator
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
@@ -13,7 +11,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||
import pytest
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
from kleinanzeigen_bot import LOG, KleinanzeigenBot
|
||||
from kleinanzeigen_bot import LOG, KleinanzeigenBot, misc
|
||||
from kleinanzeigen_bot._version import __version__
|
||||
from kleinanzeigen_bot.ads import calculate_content_hash
|
||||
from kleinanzeigen_bot.utils import loggers
|
||||
@@ -191,7 +189,7 @@ class TestKleinanzeigenBotLogging:
|
||||
class TestKleinanzeigenBotCommandLine:
|
||||
"""Tests for command line argument parsing."""
|
||||
|
||||
@pytest.mark.parametrize("args,expected_command,expected_selector,expected_keep_old", [
|
||||
@pytest.mark.parametrize(("args", "expected_command", "expected_selector", "expected_keep_old"), [
|
||||
(["publish", "--ads=all"], "publish", "all", False),
|
||||
(["verify"], "verify", "due", False),
|
||||
(["download", "--ads=12345"], "download", "12345", False),
|
||||
@@ -833,7 +831,7 @@ class TestKleinanzeigenBotAdDeletion:
|
||||
patch.object(test_bot, 'web_click', new_callable = AsyncMock), \
|
||||
patch.object(test_bot, 'web_check', new_callable = AsyncMock, return_value = True):
|
||||
mock_find.return_value.attrs = {"content": "some-token"}
|
||||
result = await test_bot.delete_ad(ad_cfg, True, published_ads)
|
||||
result = await test_bot.delete_ad(ad_cfg, published_ads, delete_old_ads_by_title = True)
|
||||
assert result is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -859,7 +857,7 @@ class TestKleinanzeigenBotAdDeletion:
|
||||
patch.object(test_bot, 'web_click', new_callable = AsyncMock), \
|
||||
patch.object(test_bot, 'web_check', new_callable = AsyncMock, return_value = True):
|
||||
mock_find.return_value.attrs = {"content": "some-token"}
|
||||
result = await test_bot.delete_ad(ad_cfg, False, published_ads)
|
||||
result = await test_bot.delete_ad(ad_cfg, published_ads, delete_old_ads_by_title = False)
|
||||
assert result is True
|
||||
|
||||
|
||||
@@ -910,7 +908,7 @@ class TestKleinanzeigenBotAdRepublication:
|
||||
|
||||
def test_check_ad_republication_no_changes(self, test_bot: KleinanzeigenBot, base_ad_config: dict[str, Any]) -> None:
|
||||
"""Test that unchanged ads within interval are not marked for republication."""
|
||||
current_time = datetime.utcnow()
|
||||
current_time = misc.now()
|
||||
three_days_ago = (current_time - timedelta(days = 3)).isoformat()
|
||||
|
||||
# Create ad config with timestamps for republication check
|
||||
@@ -1235,7 +1233,7 @@ class TestKleinanzeigenBotChangedAds:
|
||||
# Mock the loading of the ad configuration
|
||||
with patch('kleinanzeigen_bot.utils.dicts.load_dict', side_effect=[
|
||||
changed_ad, # First call returns the changed ad
|
||||
{} # Second call for ad_fields.yaml
|
||||
{} # Second call for ad_fields.yaml
|
||||
]):
|
||||
ads_to_publish = test_bot.load_ads()
|
||||
|
||||
@@ -1255,7 +1253,7 @@ class TestKleinanzeigenBotChangedAds:
|
||||
}
|
||||
|
||||
# Create a changed ad that is also due for republication
|
||||
current_time = datetime.utcnow()
|
||||
current_time = misc.now()
|
||||
old_date = (current_time - timedelta(days=10)).isoformat() # Past republication interval
|
||||
|
||||
changed_ad = create_ad_config(
|
||||
@@ -1287,7 +1285,7 @@ class TestKleinanzeigenBotChangedAds:
|
||||
# Mock the loading of the ad configuration
|
||||
with patch('kleinanzeigen_bot.utils.dicts.load_dict', side_effect=[
|
||||
changed_ad, # First call returns the changed ad
|
||||
{} # Second call for ad_fields.yaml
|
||||
{} # Second call for ad_fields.yaml
|
||||
]):
|
||||
ads_to_publish = test_bot.load_ads()
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# SPDX-FileCopyrightText: © Jens Bergmann and contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
|
||||
"""
|
||||
SPDX-FileCopyrightText: © Sebastian Thomschke and contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
|
||||
|
||||
This module contains tests for verifying the completeness and correctness of translations in the project.
|
||||
|
||||
It ensures that:
|
||||
1. All log messages in the code have corresponding translations
|
||||
2. All translations in the YAML files are actually used in the code
|
||||
@@ -15,7 +15,7 @@ The tests work by:
|
||||
3. Comparing the extracted messages with translations
|
||||
4. Verifying no unused translations exist
|
||||
"""
|
||||
import ast, os
|
||||
import ast, os # isort: skip
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from importlib.resources import files
|
||||
@@ -105,7 +105,7 @@ def _extract_log_messages(file_path: str, exclude_debug:bool = False) -> Message
|
||||
messages: MessageDict = defaultdict(lambda: defaultdict(set))
|
||||
|
||||
def add_message(function: str, msg: str) -> None:
|
||||
"""Helper to add a message to the messages dictionary."""
|
||||
"""Add a message to the messages dictionary."""
|
||||
if function not in messages:
|
||||
messages[function] = defaultdict(set)
|
||||
if msg not in messages[function]:
|
||||
@@ -128,7 +128,7 @@ def _extract_log_messages(file_path: str, exclude_debug:bool = False) -> Message
|
||||
if (isinstance(node.func, ast.Attribute) and
|
||||
isinstance(node.func.value, ast.Name) and
|
||||
node.func.value.id in {'LOG', 'logger', 'logging'} and
|
||||
node.func.attr in {None if exclude_debug else 'debug', 'info', 'warning', 'error', 'critical'}):
|
||||
node.func.attr in {None if exclude_debug else 'debug', 'info', 'warning', 'error', 'exception', 'critical'}):
|
||||
if node.args:
|
||||
msg = extract_string_value(node.args[0])
|
||||
if msg:
|
||||
@@ -390,7 +390,7 @@ def test_no_obsolete_translations(lang: str) -> None:
|
||||
if not isinstance(function_trans, dict):
|
||||
continue
|
||||
|
||||
for original_message in function_trans.keys():
|
||||
for original_message in function_trans:
|
||||
# Check if this message exists in the code
|
||||
message_exists = _message_exists_in_code(messages_by_file, module, function, original_message)
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
"""
|
||||
SPDX-FileCopyrightText: © Sebastian Thomschke and contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
|
||||
"""
|
||||
# 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 pytest
|
||||
|
||||
from kleinanzeigen_bot.utils import misc
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user