fix: improve ad description length validation

This commit is contained in:
sebthom
2025-04-28 13:17:09 +02:00
parent ef923a8337
commit f98251ade3
2 changed files with 42 additions and 46 deletions

View File

@@ -386,14 +386,6 @@ class KleinanzeigenBot(WebScrapingMixin):
if not should_include:
continue
# Get description with prefix/suffix from ad config if present, otherwise use defaults
description = self.__get_description_with_affixes(ad_cfg)
# Validate total length
ensure(len(description) <= MAX_DESCRIPTION_LENGTH,
f"Length of ad description including prefix and suffix exceeds 4000 chars. "
f"Description length: {len(description)} chars. @ {ad_file}.")
def assert_one_of(path:str, allowed:Iterable[str]) -> None:
# ruff: noqa: B023 function-uses-loop-variable
ensure(dicts.safe_get(ad_cfg, *path.split(".")) in allowed, f"-> property [{path}] must be one of: {allowed} @ [{ad_file}]")
@@ -408,7 +400,8 @@ class KleinanzeigenBot(WebScrapingMixin):
assert_one_of("type", {"OFFER", "WANTED"})
assert_min_len("title", 10)
assert_has_value("description")
ensure(self.__get_description(ad_cfg, with_affixes = False), f"-> property [description] not specified @ [{ad_file}]")
self.__get_description(ad_cfg, with_affixes = True) # validates complete description
assert_one_of("price_type", {"FIXED", "NEGOTIABLE", "GIVE_AWAY", "NOT_APPLICABLE"})
if ad_cfg["price_type"] == "GIVE_AWAY":
ensure(not dicts.safe_get(ad_cfg, "price"), f"-> [price] must not be specified for GIVE_AWAY ad @ [{ad_file}]")
@@ -707,7 +700,7 @@ class KleinanzeigenBot(WebScrapingMixin):
#############################
# set description
#############################
description = self.__get_description_with_affixes(ad_cfg)
description = self.__get_description(ad_cfg, with_affixes = True)
await self.web_execute("document.querySelector('#pstad-descrptn').value = `" + description.replace("`", "'") + "`")
#############################
@@ -1104,8 +1097,8 @@ class KleinanzeigenBot(WebScrapingMixin):
else:
LOG.error("The page with the id %d does not exist!", ad_id)
def __get_description_with_affixes(self, ad_cfg:dict[str, Any]) -> str:
"""Get the complete description with prefix and suffix applied.
def __get_description(self, ad_cfg:dict[str, Any], *, with_affixes:bool) -> str:
"""Get the ad description optionally with prefix and suffix applied.
Precedence (highest to lowest):
1. Direct ad-level affixes (description_prefix/suffix)
@@ -1117,7 +1110,7 @@ class KleinanzeigenBot(WebScrapingMixin):
ad_cfg: The ad configuration dictionary
Returns:
The complete description with prefix and suffix applied
The raw or complete description with prefix and suffix applied
"""
# Get the main description text
description_text = ""
@@ -1126,33 +1119,36 @@ class KleinanzeigenBot(WebScrapingMixin):
elif isinstance(ad_cfg.get("description"), str):
description_text = ad_cfg["description"]
# Get prefix with precedence
prefix = (
# 1. Direct ad-level prefix
ad_cfg.get("description_prefix") if ad_cfg.get("description_prefix") is not None
# 2. Legacy nested ad-level prefix
else dicts.safe_get(ad_cfg, "description", "prefix")
if dicts.safe_get(ad_cfg, "description", "prefix") is not None
# 3. Global prefix from config
else get_description_affixes(self.config, prefix = True)
or "" # Default to empty string if all sources are None
)
if with_affixes:
# Get prefix with precedence
prefix = (
# 1. Direct ad-level prefix
ad_cfg.get("description_prefix") if ad_cfg.get("description_prefix") is not None
# 2. Legacy nested ad-level prefix
else dicts.safe_get(ad_cfg, "description", "prefix")
if dicts.safe_get(ad_cfg, "description", "prefix") is not None
# 3. Global prefix from config
else get_description_affixes(self.config, prefix = True)
or "" # Default to empty string if all sources are None
)
# Get suffix with precedence
suffix = (
# 1. Direct ad-level suffix
ad_cfg.get("description_suffix") if ad_cfg.get("description_suffix") is not None
# 2. Legacy nested ad-level suffix
else dicts.safe_get(ad_cfg, "description", "suffix")
if dicts.safe_get(ad_cfg, "description", "suffix") is not None
# 3. Global suffix from config
else get_description_affixes(self.config, prefix = False)
or "" # Default to empty string if all sources are None
)
# Get suffix with precedence
suffix = (
# 1. Direct ad-level suffix
ad_cfg.get("description_suffix") if ad_cfg.get("description_suffix") is not None
# 2. Legacy nested ad-level suffix
else dicts.safe_get(ad_cfg, "description", "suffix")
if dicts.safe_get(ad_cfg, "description", "suffix") is not None
# 3. Global suffix from config
else get_description_affixes(self.config, prefix = False)
or "" # Default to empty string if all sources are None
)
# Combine the parts and replace @ with (at)
final_description = str(prefix) + str(description_text) + str(suffix)
final_description = final_description.replace("@", "(at)")
# Combine the parts and replace @ with (at)
final_description = str(prefix) + str(description_text) + str(suffix)
final_description = final_description.replace("@", "(at)")
else:
final_description = description_text
# Validate length
ensure(len(final_description) <= MAX_DESCRIPTION_LENGTH,

View File

@@ -1045,7 +1045,7 @@ class TestKleinanzeigenBotPrefixSuffix:
test_bot.config = config
ad_cfg = {"description": raw_description, "active": True}
# Access private method using the correct name mangling
description = getattr(test_bot, "_KleinanzeigenBot__get_description_with_affixes")(ad_cfg)
description = getattr(test_bot, "_KleinanzeigenBot__get_description")(ad_cfg, with_affixes = True)
assert description == expected_description
def test_description_length_validation(self, test_bot:KleinanzeigenBot) -> None:
@@ -1062,7 +1062,7 @@ class TestKleinanzeigenBotPrefixSuffix:
}
with pytest.raises(AssertionError) as exc_info:
getattr(test_bot, "_KleinanzeigenBot__get_description_with_affixes")(ad_cfg)
getattr(test_bot, "_KleinanzeigenBot__get_description")(ad_cfg, with_affixes = True)
assert "Length of ad description including prefix and suffix exceeds 4000 chars" in str(exc_info.value)
assert "Description length: 4001" in str(exc_info.value)
@@ -1087,7 +1087,7 @@ class TestKleinanzeigenBotDescriptionHandling:
}
# The description should be returned as-is without any prefix/suffix
description = getattr(test_bot, "_KleinanzeigenBot__get_description_with_affixes")(ad_cfg)
description = getattr(test_bot, "_KleinanzeigenBot__get_description")(ad_cfg, with_affixes = True)
assert description == "Test Description"
def test_description_with_only_new_format_affixes(self, test_bot:KleinanzeigenBot) -> None:
@@ -1104,7 +1104,7 @@ class TestKleinanzeigenBotDescriptionHandling:
"active": True
}
description = getattr(test_bot, "_KleinanzeigenBot__get_description_with_affixes")(ad_cfg)
description = getattr(test_bot, "_KleinanzeigenBot__get_description")(ad_cfg, with_affixes = True)
assert description == "Prefix: Test Description :Suffix"
def test_description_with_mixed_config_formats(self, test_bot:KleinanzeigenBot) -> None:
@@ -1125,7 +1125,7 @@ class TestKleinanzeigenBotDescriptionHandling:
"active": True
}
description = getattr(test_bot, "_KleinanzeigenBot__get_description_with_affixes")(ad_cfg)
description = getattr(test_bot, "_KleinanzeigenBot__get_description")(ad_cfg, with_affixes = True)
assert description == "New Prefix: Test Description :New Suffix"
def test_description_with_ad_level_affixes(self, test_bot:KleinanzeigenBot) -> None:
@@ -1144,7 +1144,7 @@ class TestKleinanzeigenBotDescriptionHandling:
"active": True
}
description = getattr(test_bot, "_KleinanzeigenBot__get_description_with_affixes")(ad_cfg)
description = getattr(test_bot, "_KleinanzeigenBot__get_description")(ad_cfg, with_affixes = True)
assert description == "Ad Prefix: Test Description :Ad Suffix"
def test_description_with_none_values(self, test_bot:KleinanzeigenBot) -> None:
@@ -1165,7 +1165,7 @@ class TestKleinanzeigenBotDescriptionHandling:
"active": True
}
description = getattr(test_bot, "_KleinanzeigenBot__get_description_with_affixes")(ad_cfg)
description = getattr(test_bot, "_KleinanzeigenBot__get_description")(ad_cfg, with_affixes = True)
assert description == "Test Description"
def test_description_with_email_replacement(self, test_bot:KleinanzeigenBot) -> None:
@@ -1179,7 +1179,7 @@ class TestKleinanzeigenBotDescriptionHandling:
"active": True
}
description = getattr(test_bot, "_KleinanzeigenBot__get_description_with_affixes")(ad_cfg)
description = getattr(test_bot, "_KleinanzeigenBot__get_description")(ad_cfg, with_affixes = True)
assert description == "Contact: test(at)example.com"