Skip to content

Commit 4a427cb

Browse files
committed
test: add coverage for auto price reduction defensive checks
Add 7 test cases covering defensive validation code paths in auto price reduction logic. Tests use model_construct() to bypass normal validation and reach defensive checks. Achieves 100% coverage for ad_model.py and config_model.py
1 parent 0482bda commit 4a427cb

File tree

1 file changed

+143
-1
lines changed

1 file changed

+143
-1
lines changed

tests/unit/test_ad_model.py

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import pytest
1010

11-
from kleinanzeigen_bot.model.ad_model import MAX_DESCRIPTION_LENGTH, Ad, AdPartial, ShippingOption
11+
from kleinanzeigen_bot.model.ad_model import MAX_DESCRIPTION_LENGTH, Ad, AdPartial, Contact, ShippingOption
1212
from kleinanzeigen_bot.model.config_model import AdDefaults, AutoPriceReductionConfig
1313
from kleinanzeigen_bot.utils.pydantics import ContextualModel, ContextualValidationError
1414

@@ -348,3 +348,145 @@ def test_ad_model_min_price_must_not_exceed_price(complete_ad_cfg:dict[str, obje
348348
}
349349
with pytest.raises(ContextualValidationError, match = "min_price must not exceed price"):
350350
Ad.model_validate(cfg_copy)
351+
352+
353+
@pytest.mark.unit
354+
def test_calculate_auto_price_with_missing_strategy() -> None:
355+
"""Test calculate_auto_price when strategy is None but enabled is True (defensive check)"""
356+
from kleinanzeigen_bot.model.ad_model import calculate_auto_price
357+
from kleinanzeigen_bot.model.config_model import AutoPriceReductionConfig
358+
359+
# Use model_construct to bypass validation and reach defensive lines 234-235
360+
config = AutoPriceReductionConfig.model_construct(
361+
enabled = True, strategy = None, amount = None, min_price = 50
362+
)
363+
result = calculate_auto_price(
364+
base_price = 100,
365+
auto_price_reduction = config,
366+
target_reduction_cycle = 1
367+
)
368+
assert result == 100 # Should return base price when strategy is None
369+
370+
371+
@pytest.mark.unit
372+
def test_calculate_auto_price_with_missing_amount() -> None:
373+
"""Test calculate_auto_price when amount is None but enabled is True (defensive check)"""
374+
from kleinanzeigen_bot.model.ad_model import calculate_auto_price
375+
from kleinanzeigen_bot.model.config_model import AutoPriceReductionConfig
376+
377+
# Use model_construct to bypass validation and reach defensive lines 234-235
378+
config = AutoPriceReductionConfig.model_construct(
379+
enabled = True, strategy = "FIXED", amount = None, min_price = 50
380+
)
381+
result = calculate_auto_price(
382+
base_price = 100,
383+
auto_price_reduction = config,
384+
target_reduction_cycle = 1
385+
)
386+
assert result == 100 # Should return base price when amount is None
387+
388+
389+
@pytest.mark.unit
390+
def test_calculate_auto_price_raises_when_min_price_none_and_enabled() -> None:
391+
"""Test that calculate_auto_price raises ValueError when min_price is None during calculation (defensive check)"""
392+
from kleinanzeigen_bot.model.ad_model import calculate_auto_price
393+
from kleinanzeigen_bot.model.config_model import AutoPriceReductionConfig
394+
395+
# Use model_construct to bypass validation and reach defensive line 237-238
396+
config = AutoPriceReductionConfig.model_construct(
397+
enabled = True, strategy = "FIXED", amount = 10, min_price = None
398+
)
399+
400+
with pytest.raises(ValueError, match = "min_price must be specified when auto_price_reduction is enabled"):
401+
calculate_auto_price(
402+
base_price = 100,
403+
auto_price_reduction = config,
404+
target_reduction_cycle = 1
405+
)
406+
407+
408+
@pytest.mark.unit
409+
def test_ad_validator_requires_price_when_enabled() -> None:
410+
"""Test Ad model validator requires price when auto_price_reduction is enabled (defensive check on Ad)"""
411+
from kleinanzeigen_bot.model.config_model import AutoPriceReductionConfig
412+
413+
# Use model_construct to bypass AdPartial validation and reach the Ad._validate_auto_price_config validator
414+
ad = Ad.model_construct(
415+
title = "Test Ad",
416+
category = "160",
417+
description = "Test description",
418+
price_type = "NEGOTIABLE",
419+
shipping_type = "PICKUP",
420+
type = "OFFER",
421+
active = True,
422+
sell_directly = False,
423+
republication_interval = 7,
424+
contact = Contact(name = "Test", zipcode = "12345"),
425+
auto_price_reduction = AutoPriceReductionConfig(enabled = True, strategy = "FIXED", amount = 5, min_price = 50),
426+
price = None
427+
)
428+
429+
# Call the validator directly to reach line 283
430+
with pytest.raises(ValueError, match = "price must be specified when auto_price_reduction is enabled"):
431+
ad._validate_auto_price_config()
432+
433+
434+
@pytest.mark.unit
435+
def test_ad_validator_min_price_exceeds_price() -> None:
436+
"""Test Ad model validator when min_price > price (defensive check on Ad)"""
437+
from kleinanzeigen_bot.model.config_model import AutoPriceReductionConfig
438+
439+
# Use model_construct to bypass AdPartial validation and reach the Ad._validate_auto_price_config validator
440+
ad = Ad.model_construct(
441+
title = "Test Ad",
442+
category = "160",
443+
description = "Test description",
444+
price_type = "NEGOTIABLE",
445+
shipping_type = "PICKUP",
446+
type = "OFFER",
447+
active = True,
448+
sell_directly = False,
449+
republication_interval = 7,
450+
contact = Contact(name = "Test", zipcode = "12345"),
451+
price = 50,
452+
auto_price_reduction = AutoPriceReductionConfig(enabled = True, strategy = "FIXED", amount = 5, min_price = 100)
453+
)
454+
455+
# Call the validator directly to reach line 286
456+
with pytest.raises(ValueError, match = "min_price must not exceed price"):
457+
ad._validate_auto_price_config()
458+
459+
460+
@pytest.mark.unit
461+
def test_auto_price_reduction_config_requires_amount_when_enabled() -> None:
462+
"""Test AutoPriceReductionConfig validator requires amount when enabled"""
463+
from kleinanzeigen_bot.model.config_model import AutoPriceReductionConfig
464+
465+
with pytest.raises(ValueError, match = "amount must be specified when auto_price_reduction is enabled"):
466+
AutoPriceReductionConfig(enabled = True, strategy = "FIXED", amount = None, min_price = 50)
467+
468+
469+
@pytest.mark.unit
470+
def test_ad_validator_when_min_price_is_none() -> None:
471+
"""Test Ad model validator when auto_price_reduction is enabled but min_price is None (covers line 284->287 branch)"""
472+
from kleinanzeigen_bot.model.config_model import AutoPriceReductionConfig
473+
474+
# Use model_construct to bypass validation and create an Ad with enabled=True but min_price=None
475+
ad = Ad.model_construct(
476+
title = "Test Ad",
477+
category = "160",
478+
description = "Test description",
479+
price_type = "NEGOTIABLE",
480+
shipping_type = "PICKUP",
481+
type = "OFFER",
482+
active = True,
483+
sell_directly = False,
484+
republication_interval = 7,
485+
contact = Contact(name = "Test", zipcode = "12345"),
486+
price = 100,
487+
auto_price_reduction = AutoPriceReductionConfig.model_construct(enabled = True, strategy = "FIXED", amount = 5, min_price = None)
488+
)
489+
490+
# This should pass validation (line 284 condition is False, goes to line 287)
491+
result = ad._validate_auto_price_config()
492+
assert result == ad

0 commit comments

Comments
 (0)