Commit Graph

225 Commits

Author SHA1 Message Date
Jens
f2da0355d5 fix: classify Auth0 IP range block (#1188)
## ℹ️ Description

- Link to the related issue(s): Issue #1120
- Classifies Kleinanzeigen IP-range block pages explicitly when Auth0
post-submit verification remains on the password URL.
- This makes the failure actionable without changing login/captcha
behavior or attempting to bypass the block.

## 📋 Changes Summary

- Add `IP_RANGE_BLOCKED` to post-submit Auth0 state classification when
the password-page DOM contains the Kleinanzeigen IP-range block heading.
- Keep existing classification facts such as `STILL_ON_PASSWORD_PAGE`,
so combined states remain visible.
- Keep detection scoped to the Auth0 password page and best-effort.
- Add focused unit tests for detection, gating, resilience, and
coexistence with Auth0 inline errors.
- No new dependencies, configuration changes, schema changes, or
translation updates.

### ⚙️ Type of Change
Select the type(s) of change(s) included in this pull request:
- [x] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  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 tests/unit/test_login_flow.py tests/unit/test_translations.py`).
- [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

* **Bug Fixes**
* Improved sign-in diagnostics to detect when login is stalled on the
password step due to an IP-range restriction.
* Added support for preserving existing login-state facts when the
IP-block page probe fails, and for correctly combining IP-block signals
with inline error states.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-01 07:59:25 +02:00
Jens
ce0c5fa5b5 fix: handle DOM retry and button combobox selection (#1186)
## ℹ️ Description

- Link to the related issue(s): Issue #1184
- Fixes two publish-flow failures reported in issue #1184:
- persistent nodriver `ProtocolException(code=-32601)` could spin
forever in `web_await()` while repeatedly re-attaching the CDP session;
- special-attribute button comboboxes in the Handy & Telefon category
needed open/read/select to happen atomically in one browser execution.

## 📋 Changes Summary

- Bound `web_await()` CDP reattach retries by the configured timeout and
add a short nonzero retry delay to avoid event-loop starvation.
- Update special-attribute button-combobox selection to use one async
`web_execute()` script that opens via pointer/mouse events, discovers
options, matches API values, and clicks the selected option before the
dropdown can close.
- Add regression tests for bounded `-32601` retry behavior and atomic
button-combobox selection.
- Update the German translation for the enhanced combobox failure
message.
- Verified with the live `button-combobox` DOM probe.

### ⚙️ Type of Change

Select the type(s) of change(s) included in this pull request:
- [x] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  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.

## Validation

- `pdm run format`
- `pdm run lint`
- `pdm run test` — 1370 passed, 4 skipped
- `pdm run python data/verify_dom_assumptions.py run --yes --probe
button-combobox --report
data/.temp/dom-assumptions-issue-1184-button-combobox.json` — passed


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved dropdown/combobox option selection with stronger matching and
more informative timeout/error reporting.
* Tightened CDP re-attach retry logic to strictly follow the remaining
timeout budget.
* Added a startup runtime warning if the required nodriver CDP re-attach
patch isn’t detected.

* **Documentation / Chores**
* Updated source installation guidance (pip-only users) to ensure the
nodriver patch is applied.
* Made the nodriver fix script marker-based and idempotent, including
normalization of legacy patched installs.

* **Tests**
* Expanded unit tests for combobox selection, re-attach timeout/bounds
behavior, CLI patch warning behavior, and the nodriver fix script.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-30 10:39:41 +02:00
Jens
27b47f7561 fix: capture Auth0 post-submit diagnostics (#1183)
## ℹ️ Description

- Link to the related issue(s): Issue #1120
- Capture diagnostics when Auth0 post-submit verification remains
inconclusive, so future failures include a DOM/screenshot bundle plus
classified metadata.

## 📋 Changes Summary

- Reuse existing login-detection diagnostics for the final Auth0
post-password-submit timeout.
- Add sanitized JSON metadata with `event`, `classification`, and
`page_url`.
- Keep diagnostics best-effort so capture failures do not mask the
original classified timeout.
- Add focused tests for enabled/disabled capture, privacy sanitization,
failure preservation, and diagnostics argument forwarding.
- No new configuration, schema, translation, captcha, or identifier-flow
changes.

### ⚙️ Type of Change
Select the type(s) of change(s) included in this pull request:
- [x] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  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 tests/unit/test_login_flow.py tests/unit/test_translations.py`).
- [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. (Not needed.)

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

* **Bug Fixes**
* Improved login timeout handling with clearer error messages and safer
URL reporting.
* Added fallback diagnostics during failed post-login verification,
while preserving the original timeout if diagnostics capture fails.
  * Prevented sensitive URL parts from appearing in error reports.

* **Tests**
* Expanded coverage for login diagnostics forwarding and timeout
behavior with diagnostics enabled and disabled.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-29 10:40:04 +02:00
Jens
4acbc09fac fix: classify Auth0 post-submit uncertainty (#1181)
## ℹ️ Description

- Link to the related issue(s): Issue #1120
- Adds focused diagnostics/classification for the post-password-submit
Auth0 uncertainty path. This keeps the existing login/captcha/identifier
flow stable while making the new failure mode easier to classify from
logs.

## 📋 Changes Summary

- Classifies final post-submit uncertainty as still on the Auth0
password page, visible Auth0 error text, verification/MFA-like state, or
unknown redirect/session inconclusive state.
- Logs sanitized diagnostic context and raises clearer classified
messages while preserving the existing timeout prefix.
- Adds focused unit tests for the new classifications and snippet
sanitization.
- Updates German translations for new translatable messages.
- No new dependencies or configuration changes.

### ⚙️ Type of Change

- [x] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  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
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

* **Bug Fixes**
* Improved handling when transitioning after Auth0 password submission
by using safer, sanitized diagnostic URLs.
* Added clearer, classification-aware timeout messages when the next
verification step can’t be identified.
* Strengthened detection of post-Auth0 states, including inline Auth0
errors and MFA/verification prompts, with more graceful degradation on
probing failures.

* **Tests**
* Expanded unit tests for post-submit state classification, URL
sanitization, and improved timeout/error-message expectations, including
resilience to probe/URL retrieval failures.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-27 00:50:04 +02:00
Jens
1c34da9298 feat: show APR status indicators (#1180)
## ℹ️ Description

- Link to the related issue(s): Issue #
- Adds display-only APR indicators to the local status table so users
can see repost/update APR state without running publish/update flows.

## 📋 Changes Summary

- Adds optional `APR repost` and `APR update` columns to
`kleinanzeigen-bot status` when APR is effectively enabled for at least
one active ad.
- Reuses existing `evaluate_auto_price_reduction()` logic for REPLACE
and MODIFY modes; does not apply or mutate APR decisions.
- Keeps APR output plain while preserving existing status color
behavior.
- Adds German translations and unit coverage for rendering, evaluator
call modes, guardrails, and mutation safety.
- No new dependencies or configuration changes.

### ⚙️ 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`: 1344 passed, 4 skipped).
- [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

* **New Features**
* Status tables now show optional APR-related columns for repost and
update pricing decisions when available.
* APR results are displayed in a compact, easy-to-read format such as
due, not due, error, or off.

* **Bug Fixes**
* Improved status table layout and spacing so translated status text
fits more reliably.
* Status summaries and row rendering now handle coloured and uncoloured
cells more consistently.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-26 15:45:28 +02:00
Jens
25a379fe67 feat: colorize status output (#1178)
## ℹ️ Description

- Link to the related issue(s): Issue #
- Adds optional color to the `kleinanzeigen-bot status` table using the
existing Colorama dependency.
- Keeps default status behavior local/read-only and gates ANSI output
via terminal/env policy.

## 📋 Changes Summary

- Adds `utils.color.should_use_color()` with `NO_COLOR` > `FORCE_COLOR`
> TTY detection.
- Colors only the status column while preserving plain ASCII layout and
summary output.
- Wires status rendering to the color gate in `_handle_status()`.
- Removes legacy `colorama.init()` from `update_checker.py`; package
init already enables Windows ANSI support.
- Adds unit tests for color gating and colored status rendering.
- No new dependencies or configuration changes.

### ⚙️ Type of Change

- [ ] 🐞 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

- [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`: 1332 passed, 4 skipped).
- [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

* **New Features**
* Status output can now be shown with optional terminal colors for
improved readability.
* Color display automatically follows your terminal and environment
settings.

* **Bug Fixes**
* Status tables keep proper alignment even when colored text is enabled.
  * Unknown statuses remain uncolored, avoiding inconsistent display.

* **Tests**
* Added coverage for color handling and status rendering to improve
reliability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-26 13:48:53 +02:00
Jens
50a4b570cf feat: add ad status command (#1177)
## ℹ️ Description

- Link to the related issue(s): Issue #
- Adds a CLI status overview inspired by useful lifecycle features from
kleinanzeigen-bot-ui, while keeping this project CLI-only.
- The new command helps users see local ad state at a glance without
replacing the existing verify/APR preflight flow.

## 📋 Changes Summary

- Add `kleinanzeigen-bot status` with an ASCII table and summary for
local ad states: disabled, draft, changed, due, and published-local.
- Add shared pure helpers for content-change and republication-due
checks.
- Add a small neutral `load_ad_configs()` loader reused by `load_ads()`
and `status` without broad loader redesign.
- Add German translations and focused unit/guardrail tests.
- No schema/config model changes or generated artifacts.

### ⚙️ 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.

Validation:
- `pdm run format` passed
- `pdm run lint` passed
- `pdm run test`: 1315 passed, 4 skipped; coverage 93.18%


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added a new `status` command to view the current state of configured
ads.
* Introduced a status summary that shows whether ads are disabled,
drafts, changed, due, or published locally.
* Added cleaner handling for ad loading and republication checks,
improving consistency in displayed results.
* **Bug Fixes**
* Improved change detection for ads, including better handling when
stored data is missing or empty.
  * Enhanced due-date checks so timing-based status is more reliable.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-26 12:23:26 +02:00
Jens
d5d596d481 fix: target visible Auth0 submit button and broaden captcha detection (#1120) (#1176)
## ℹ️ Description

Fixes #1120 — Auth0 login fails after email submit because the bot
clicks a hidden submit button and cannot detect non-reCAPTCHA captcha
providers.

**Root causes:**
1. **Hidden submit button** — Auth0 renders two `button[type='submit']`
elements. The hidden one (`class=ulp-hidden-form-submit-button`,
`aria-hidden=true`) appears first in DOM. `querySelector()` was matching
it instead of the visible primary button.
2. **Captcha detection too narrow** — Only matched reCAPTCHA v2 iframes.
Kleinanzeigen.de uses Cloudflare Turnstile via Auth0 v2
(`data-captcha-provider='auth0_v2'`), which renders inline without an
iframe.

**Evidence:** @jochenberger provided HTML from the identifier page
confirming both issues. User CAN manually click 'Weiter' and login
succeeds.

## 📋 Changes Summary

- Add `_click_auth0_submit()` helper using
`[data-action-button-primary='true']` to target the visible button
- Broaden captcha detection: iframe selector covers 6 providers,
container selector uses `[data-captcha-provider]` + substring class
matching
- Simplify `handle_identifier_captcha_state()` with Turnstile token wait
via JS property check
- Use `_click_auth0_submit()` for both identifier and password submits
- Add Auth0 provider provenance comments to selectors

### ⚙️ Type of Change
- [x] 🐞 Bug fix (non-breaking change which fixes an issue)

##  Checklist
- [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.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Bug Fixes**
- Improved captcha/challenge detection to support both iframe-based
providers and Auth0 container-based challenges.
- Updated the Auth0 login flow to click the correct visible Auth0
primary submit button, avoiding hidden controls for identifier and
password steps.
- Centralized captcha detection timeout to make challenge handling more
consistent.

- **Tests**
- Expanded and tightened unit tests for the Auth0 identifier-step
captcha/submit behavior, including selector expectations and
control-flow scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-26 07:50:00 +02:00
Jens
8e70d0411d refactor: decompose WebScrapingMixin (static helpers, diagnostics, exception annotations) (#1174)
## ℹ️ Description

Decompose `WebScrapingMixin` — incremental internal refactors to reduce
class size and improve separation of concerns.

## 📋 Changes Summary

### Static method extraction
- Extract 5 `@staticmethod` members from `WebScrapingMixin` to
module-level private functions in `web_scraping_mixin.py`
- Update internal call sites and test call sites (no compatibility
aliases)

### Browser diagnostics extraction
- Create `src/kleinanzeigen_bot/utils/browser_diagnostics.py` with
module-level diagnostic functions
- `WebScrapingMixin.diagnose_browser_issues()` → thin delegator
(resolves `self` attrs, calls extracted logic)
- Replace `self`-coupled logic with explicit parameter dependencies
- Migrate translations to new module section in `translations.de.yaml`

### Exception annotations
- Add `# noqa: BLE001` + justification comments to 4 bare `except
Exception:` blocks (no behavior changes)

### ⚙️ Type of Change
- [x] 🔧 Refactor (internal code improvements, no behavior changes)

##  Checklist
- [x] I have reviewed my changes to ensure they meet the project
standards.
- [x] I have tested my changes and ensured that all tests pass (`pdm run
test`) — 1287 passed, 4 skipped
- [x] I have formatted the code (`pdm run format`)
- [x] I have verified that linting passes (`pdm run lint`) — ruff, mypy,
pyright clean
- [x] I have updated documentation where necessary.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added enhanced browser troubleshooting with remote-debugging
connectivity checks, running-process inspection, and Chrome/Edge
compatibility validation including recommended configuration updates.
* **Refactor**
* Reorganized browser/session diagnostics and remote-debugging argument
handling into dedicated utilities; introduced a shared browser runtime
configuration container.
* **Chores**
* Suppressed linter warnings for intentionally broad exception handling.
* **Tests**
* Updated unit and integration tests to match the new diagnostics flow
and remote-debugging probing behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-24 10:36:42 +02:00
Jens
b2dce90ef1 refactor: remove dead wrapper methods from KleinanzeigenBot (#1172)
## ℹ️ Description

Remove four private wrapper methods on `KleinanzeigenBot` in `app.py`
that wrap public module-level functions but have zero production call
sites. This is a cleanup-only change with no behavioral impact.

## 📋 Changes Summary

- **Remove** `_has_logged_in_marker` — delegates to
`login_flow.has_logged_in_marker`
- **Remove** `_check_publishing_result` — delegates to
`publishing_workflow.check_publishing_result`
- **Remove** `_delete_old_ad_if_needed` — delegates to
`publishing_workflow.delete_old_ad_if_needed`
- **Remove** `_capture_login_detection_diagnostics_if_enabled` —
delegates to `login_flow.capture_login_detection_diagnostics_if_enabled`
- **Remove** unused `Literal` import
- **Retarget** 4 helper-detail tests that called the deleted wrapper
directly to the real
`login_flow.capture_login_detection_diagnostics_if_enabled` function

### ⚙️ Type of Change
- [x] 🐞 Bug fix (non-breaking change which fixes an issue)

##  Checklist
- [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.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Reorganized login detection diagnostics functionality for improved
accessibility
* Simplified login status verification logic within the authentication
system

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-22 21:37:17 +02:00
Jens
1f142f8339 refactor: extract selector and filter helpers from load_ads (#1169)
## ℹ️ Description

Extract three private helpers from `load_ads()` to remove the last
remaining `# noqa: PLR0915` annotation in main source. `load_ads` goes
from ~100 statements to ~35 — a clean orchestration function.

## 📋 Changes Summary

- **`_parse_ad_selector`**: parse `ads_selector` string into numeric IDs
list or frozenset of tokens
- **`_should_include_ad`**: token-based ad filtering
("changed"/"new"/"due"/"all") — preserves exact ordering, side effects,
and "changed"/price-reduction-for-update logic
- **`_prepare_selected_ad_entry`**: description validation, category
resolution, image resolution — called only after filtering to avoid
validating skipped ads
- Translation keys moved to match new caller function names
(`_should_include_ad:`, `_prepare_selected_ad_entry:`)
- Added regression test: inactive ad with unresolvable images is skipped
without error

### ⚙️ Type of Change
- [x]  New feature (adds new functionality without breaking existing
usage)

##  Checklist
- [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

* **Refactor**
* Reorganized ad loading process with improved selector parsing and
filtering logic for better code maintainability.
* Optimized validation workflow: inactive and non-selected ads now skip
description and image validation, improving overall efficiency.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-22 16:07:04 +02:00
Jens
eaa646874d refactor: replace get_message_template match with dict lookup (#1165)
## ℹ️ Description

Replace the ~115-arm match/case in `__get_message_template` with a
module-level `_MESSAGE_TEMPLATES` dict of raw English strings plus a
3-line lazy `_(template)` lookup function.

- **Motivation**: Eliminate 5 complexity `noqa` annotations (C901,
PLR0911, PLR0912, PLR0915, E701) by removing the real architectural
complexity — a pure flat error-code → message mapping that has zero
branching or logic.
- **Approach**: A `dict[str, str]` of raw English strings (no `_()`
calls at import time) with lazy translation inside
`__get_message_template`. `"custom_error"` intentionally omitted —
handled by `.get()` default.
- **Translation**: Caller function name unchanged → translation keys
under `__get_message_template` preserved. AST-based translation test
extractor updated to recognize `_MESSAGE_TEMPLATES` dict values.

## 📋 Changes Summary

- `src/kleinanzeigen_bot/utils/pydantics.py`: Replaced ~115 match arms
with `_MESSAGE_TEMPLATES` dict + 3-line function; removed all 5 noqa
annotations
- `tests/unit/test_translations.py`: Added AST extraction for
`_MESSAGE_TEMPLATES` dict values to maintain translation coverage checks

### ⚙️ Type of Change
Select the type(s) of change(s) included in this pull request:
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  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.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Improved error message handling system to support better
internationalization and translation workflows

* **Tests**
* Updated test suite to extract and verify translatable message strings

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-22 07:12:23 +02:00
Jens
32c6d3985f refactor: split published ads pagination (#1164)
## ℹ️ Description

Extract content-decoding and paging-navigation logic from
`fetch_published_ads` into focused module-level helpers, reducing
function complexity while preserving all behavior.

## 📋 Changes Summary

- **`published_ads.py`**: Extracted `_parse_published_ads_page()`
(content decode, JSON parse, payload validation, ad filtering) and
`_determine_next_page()` (paging metadata resolution,
last-page/empty-ads guards). Main `fetch_published_ads` is now a clean
orchestration loop: request → response check → parse → extend →
paginate.
- **`translations.de.yaml`**: Split the single `fetch_published_ads:`
translation section into three sections (`fetch_published_ads:`,
`_parse_published_ads_page:`, `_determine_next_page:`) matching the new
caller function names. Added missing translation for "No paging dict
found on page %s".
- **`pyproject.toml`**: Lowered `max-statements` from 115 to 90.
- **`pydantics.py`**: Added `PLR0915` noqa for `__get_message_template`
(already an approved exception for the generated pydantic error-code
mapping).

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

##  Checklist
- [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.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Improved published-ads fetching by separating response decoding and
pagination handling into dedicated helpers, while preserving existing
behavior.
* Reworked strict-mode validation and stopping conditions for clearer
control flow and more consistent error handling.

* **Chores**
  * Updated linting limits to enforce stricter code quality.
  * Added targeted lint suppression for an overly complex section.

* **Tests**
* Expanded unit test coverage for published-ads content decoding and
strict-mode failure cases across multiple malformed/edge responses.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-21 22:39:36 +02:00
Alex Strutsysnkyi
c6843a49c0 fix: avoid TypeError abort when web_request gets a non-dict response (#1162)
## ℹ️ Description

When publishing a batch of ads, a single transient page-load failure
could crash the **entire** run with an unhandled `TypeError`.

During the pre-publish delete step, `delete_flow.delete_ad` calls
`web_request`, which runs a `fetch(...)` via `web_execute`. When the
page context is torn down mid-navigation (the same condition that
surfaces as *"Page did not finish loading within 30.0 seconds"*),
`web_execute` returns a CDP `ExceptionDetails` object (or `None`)
instead of the expected response dict. `web_request` then does
`response["statusCode"]`, raising:

```
TypeError: [ExceptionDetails] object is not subscriptable
  File ".../utils/web_scraping_mixin.py", line 1537, in web_request
    response["statusCode"] in valid_response_codes,
```

This `TypeError` is **not** caught by the publish retry loop (which
handles `TimeoutError` / `ProtocolException`), so it propagates all the
way up and aborts the whole batch — every remaining ad goes unpublished.

Observed live while publishing ~25 ads: 9 published successfully, two
hit transient load timeouts, and the next `delete_ad` request crashed
the process.

- Link to the related issue(s): Issue #

## 📋 Changes Summary

- `web_request`: guard the response — if it is not a `dict` containing
`statusCode`, raise a `ProtocolException` (already imported) instead of
blindly subscripting it.
- `ProtocolException` is already handled by the publish retry/skip
logic, so a single transient failure now retries and then skips that one
ad, letting the rest of the batch continue.
- Add a parametrized regression test in `test_web_scraping_mixin.py`
covering `None` / non-dict / missing-`statusCode` responses.

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

##  Checklist
- [x] I have reviewed my changes to ensure they meet the project's
standards.
- [x] I have tested my changes and ensured the affected tests pass
(`pytest tests/unit/test_web_scraping_mixin.py -k web_request` → 7
passed).
- [ ] I have formatted the code (`pdm run format`).
- [ ] I have verified that linting passes (`pdm run lint`).
- [ ] I have updated documentation where necessary (n/a).

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

* **Bug Fixes**
* Enhanced validation for network operation responses with improved
error messaging for unexpected situations, preventing hidden failures
and providing clearer diagnostics when issues occur.

* **Tests**
* Extended test coverage to comprehensively validate error handling
across various edge cases and unexpected response scenarios in network
operations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-21 20:16:52 +02:00
Jens
b5e2769f2d refactor: extract special attribute helpers from publishing form (#1161)
## ℹ️ Description

Extract three module-level helpers from `set_special_attributes()` in
`publishing_form.py` to reduce local variable count and improve
readability. `set_special_attributes` had 27 locals — after extraction
it has 10.

- Behavior-preserving: no change to exception propagation, logging, or
control flow.
- `condition_s` probe stays in main to avoid translation-key churn.

## 📋 Changes Summary

- Extract `_build_special_attribute_xpath()` — Pure XPath construction
(fix comment: five → six patterns)
- Extract `_resolve_special_attribute_element()` — Wraps `web_find_all`
+ candidate pick, propagates exceptions uncaught
- Extract `_set_special_attribute_input()` — 6-branch input-type
dispatch; hidden-input returns without logging, caller logs once
- Rewire `set_special_attributes` to call the three helpers
- Ratchet `pyproject.toml`: PLR0914 max-locals 27 → 20
- Update `translations.de.yaml`: move 4 debug messages + add `Failed to
set attribute` under `_set_special_attribute_input`

### ⚙️ Type of Change
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  New feature (adds new functionality without breaking existing
usage)
- [ ] 💥 Breaking change (changes that might break existing user setups,
scripts, or configurations)
- [x] ♻️ Refactor (non-breaking restructuring of existing code)

##  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`). → 1268 passed, 4 skipped
- [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. → Translation file
updated


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

* **Chores**
* Tightened linting rules by lowering the allowed maximum number of
local variables.

* **Refactor**
* Reworked special-attribute DOM handling to use clearer selection and
value-setting paths for different field types, including improved
fallback behavior.

* **Tests**
* Added async unit test coverage for `set_special_attributes`, including
input fallback behavior and error cases for unsupported checkbox values
and missing hidden input metadata.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-21 19:36:56 +02:00
Jens
d542f1ea92 feat: use configured --remote-debugging-host for browser diagnostics probes (#1160)
## ℹ️ Description

`diagnose_browser_issues()` previously hardcoded `127.0.0.1` for
port-open and `/json/version` API probes. When Chrome's remote debugging
is exposed on a different host (Docker, LAN, CI), the diagnostics miss
the running browser entirely.

This PR makes diagnostics host-aware by parsing
`--remote-debugging-host` from `browser_config.arguments` and using the
configured host instead of the hardcoded `127.0.0.1`. All diagnostics
probes (port, API, Chrome version) now respect the configured host.

## 📋 Changes Summary

- Added `_diagnostic_remote_debugging_endpoint()` — private diagnostics
parser with first-wins semantics for both host and port, defaults to
`127.0.0.1:0`
- Added `_format_url_host()` — IPv6-safe URL bracketing helper
- Threaded configured host into `net.is_port_open()`,
`_remote_debugging_api_browser()`, and
`_diagnose_chrome_version_issues()`
- Emit `LOG.warning` before probing a non-loopback configured host
(security/documentation guard)
- Updated German translations for changed log message strings
- Added tests: configured host `10.0.0.5` and `192.168.1.100`,
host-after-port ordering, IPv6 URL formatting, Chrome-version host
propagation
- Default behavior (no `--remote-debugging-host` argument) unchanged —
probes remain `127.0.0.1`

### ⚙️ Type of Change
- [ ] 🐞 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
- [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.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved remote browser debugging diagnostics to work with non-local
hosts, including correct IPv6-safe URL formatting.
* Added host-aware reachability checks and clearer warnings when the
configured remote host may not be loopback.
* Enhanced diagnostic messaging to report the configured host and port
during remote debugging checks.

* **Tests**
* Expanded unit test coverage for configured remote-debugging hosts
(including IPv6) and updated expectations for host-specific diagnostic
outputs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-21 09:26:52 +02:00
Jens
d384f172b8 fix: remove individual shipping publishing (#1159)
## ℹ️ Description

- Link to the related issue(s): Issue #1157
- Kleinanzeigen no longer offers individual/custom shipping. This
updates publishing, validation, docs, schemas, translations, and tests
so existing `shipping_costs` configs are handled gracefully instead of
failing against removed DOM controls.

## 📋 Changes Summary

- Keep `shipping_costs` for legacy configs/downloaded ads, but mark it
deprecated in schema/docs and ignore it during publishing.
- Remove publishing interaction with removed individual-shipping DOM
controls.
- Require predefined non-empty `shipping_options` for `sell_directly`.
- Update docs, generated schemas/config artifacts, German translations,
and unit tests.
- Verified live DOM for `sell_directly: true` + `shipping_type:
SHIPPING` + predefined `shipping_options` via local ignored script; no
ad submission performed.

### ⚙️ Type of Change
Select the type(s) of change(s) included in this pull request:
- [x] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  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

* **Documentation**
* Updated shipping guides to deprecate custom individual shipping costs
and make publishing depend on per-ad predefined `shipping_options`
(DHL/Hermes; selectable options must come from a single size group).
* Added migration guidance replacing `shipping_costs` with the correct
`shipping_options`.
* Clarified “Sell directly” requires `shipping_type: SHIPPING`,
non-empty `shipping_options`, and `FIXED`/`NEGOTIABLE` pricing.

* **Bug Fixes**
* Publishing is blocked when `shipping_costs` is provided without
`shipping_options`.
* Removed unsupported individual-shipping handling and tightened “Sell
directly” eligibility rules.

* **Tests**
* Expanded unit coverage for deprecation behavior, content handling, and
direct-buy validation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-21 08:11:02 +02:00
Jens
5ab4015251 refactor: simplify browser diagnostics (#1158)
## ℹ️ Description

- Link to the related issue(s): Issue #
- Refactor browser diagnostics to reduce the complexity of the
diagnostics orchestration while preserving the generated diagnostics
report and browser workflow behavior.

## 📋 Changes Summary

- Extract factual browser diagnostic checks into focused helpers for
binary detection, remote debugging port parsing, remote API probing,
target browser name detection, and process discovery.
- Keep translated diagnostics logging as direct literal log calls in
`diagnose_browser_issues`.
- Add regression coverage for remote debugging host handling, invalid
ports, first-port precedence, and port `0` pass-through behavior.
- No dependencies, configuration changes, or additional requirements
introduced.

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

##  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

* **Refactor**
* Improved browser diagnostics by breaking down remote debugging checks
into clearer steps, enhancing resilience when probing the browser and
inspecting running processes, and refining success vs failure logging.

* **Tests**
* Expanded unit coverage to ensure remote debugging probing always
targets `http://127.0.0.1:<port>`, validates `--remote-debugging-port`
parsing (including invalid and multi-argument cases), and skips remote
debugging when the port is `0` while continuing Chrome version
diagnostics.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-20 22:22:21 +02:00
Jens
c8fb72ae9e refactor: split command orchestration handlers (#1155)
## ℹ️ Description
- Link to the related issue(s): Issue #
- Refactors the CLI command orchestration boundary so command dispatch
remains visible while command execution lives in focused handlers.

## 📋 Changes Summary
- Split command execution paths in `KleinanzeigenBot.run()` into named
handlers for configuration, verification, update checks, content
hashing, and ad workflows.
- Preserved literal command dispatch cases and command-specific behavior
for update checks, download validation, browser login, and cleanup.
- Moved German translation keys to the new handler caller names for
existing runtime messages.
- Added focused tests for update-check behavior, download validation
ordering, workspace state usage, and translation key placement.
- Lowered the configured branch and cyclomatic complexity limits now
that the command orchestration boundary is simpler.

### ⚙️ Type of Change
Select the type(s) of change(s) included in this pull request:
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  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

* **Chores**
* Strengthened code quality standards by implementing stricter
complexity thresholds in the development configuration.

* **Refactor**
* Reorganized command handler architecture to improve internal code
structure and maintainability.

* **Tests**
* Significantly expanded test coverage to validate command execution,
argument handling, and translation data integrity.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-20 12:33:06 +02:00
Jens
66cab91898 refactor: reduce return-count complexity (#1153)
## ℹ️ Description

This refactor reduces return-count complexity for the next PLR0911
ratchet while preserving existing behavior.

No related issue.

## 📋 Changes Summary

- Extract German and English pluralization helpers while keeping
translation and prefix handling in `pluralize()`.
- Remove one early return from `check_for_updates()` by sharing the
existing final state update/save path.
- Ratchet Ruff `PLR0911` `max-returns` from 12 to 10.
- Document the one-lower discovery result: `check_for_updates()` is the
next limiter at 10 returns for `max-returns = 9`.
- No dependencies, configuration requirements, documentation updates, or
generated artifacts are introduced.

### ⚙️ Type of Change
Select the type(s) of change(s) included in this pull request:
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  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. No documentation
changes were needed.

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

* **Refactor**
* Reorganized pluralization logic and simplified update-checking control
flow for improved code maintainability.

* **Tests**
* Expanded test coverage for pluralization with additional German and
English language variations.

* **Chores**
  * Updated code quality configuration standards.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-19 23:15:27 +02:00
Jens
801ed0e223 refactor: remove KleinanzeigenBot re-export from package root (#1150)
## ℹ️ Description

- Link to the related issue(s): N/A
- Removes the unsupported package-root `KleinanzeigenBot` re-export
after tests were moved to their owning modules.
- Updates remaining tests and `conftest.py` to import `KleinanzeigenBot`
from `kleinanzeigen_bot.app` directly.
- Re-enables Ruff's mccabe complexity checks and tightens high Pylint
complexity thresholds left over from the old package-root monolith.

## 📋 Changes Summary

- Removed `KleinanzeigenBot` from `src/kleinanzeigen_bot/__init__.py`.
- Updated all remaining test imports to use `from kleinanzeigen_bot.app
import KleinanzeigenBot`.
- Enabled `C90`/mccabe complexity linting with an explicit
`max-complexity`.
- Lowered the broad Pylint limits for branches, locals, returns,
statements, and public methods.
- Kept the generated/static pydantic error-code mapping explicitly
exempted from complexity checks.
- No runtime behavior, CLI, config, translations, or ad YAML changes.
- No new dependencies.

### ⚙️ Type of Change
Select the type(s) of change(s) included in this pull request:
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  New feature (adds new functionality without breaking existing
usage)
- [x] 💥 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

* **Refactor**
* Reorganized internal module import structure for consistency across
the codebase.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-19 15:00:45 +02:00
Jens
3c17f3c8dd refactor: move cross-drive fallback test (#1149)
## ℹ️ Description

- Link to the related issue(s): Issue #
- Move the Windows cross-drive fallback coverage from the package-init
test module into the publishing workflow test suite.
- This is a behavior-neutral test organization cleanup.

## 📋 Changes Summary

- Moved the cross-drive auto-price-reduction path fallback test into
`TestAutoPriceReductionDispatch`.
- Deleted the now-empty `tests/unit/test_init.py` and its unused local
helpers.
- No runtime code, CLI behavior, config, translations, or ad YAML
changed.

### ⚙️ Type of Change
Select the type(s) of change(s) included in this pull request:
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  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
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**
* Reorganized test structure for Windows cross-drive path handling
validation to improve maintainability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-19 14:29:57 +02:00
Jens
48e88fccd3 refactor: create app test file and move shell tests (#1148)
## ℹ️ Description

- Link to the related issue(s): Issue #
- Move app-shell unit tests into a dedicated test module matching
`src/kleinanzeigen_bot/app.py`.

## 📋 Changes Summary

- Added `tests/unit/test_app.py` for the app-shell test classes and
shared fixture.
- Kept the cross-drive path fallback coverage in
`tests/unit/test_init.py`.
- Updated the moved test module to import `KleinanzeigenBot` from
`kleinanzeigen_bot.app`.
- No runtime, CLI, config, translation, or ad YAML changes.

### ⚙️ Type of Change

- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  New feature (adds new functionality without breaking existing
usage)
- [ ] 💥 Breaking change (changes that might break existing user setups,
scripts, or configurations)
- Test organization refactor only.

##  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**
* Added comprehensive unit tests covering command-line subcommands
(including defaulting/fallback behavior for ad selector options) and
unknown-command handling.
* Added assertions for expected initialization behavior and invalid
selector validation (including SystemExit code 2).
* Refined the unit test modules to simplify imports and improve
maintainability, while retaining key cross-drive publish fallback
verification.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-19 13:54:14 +02:00
Jens
d0d8e5461b refactor: move shipping form tests (#1147)
## ℹ️ Description
- Link to the related issue(s): Issue #
- Move the shipping option mapping test to the publishing form test
module, where the browser-level shipping form behavior is owned.

## 📋 Changes Summary
- Moved `test_shipping_options_mapping` from `tests/unit/test_init.py`
to `tests/unit/test_publishing_form.py`.
- Kept the existing assertions and patch targets intact.
- Renamed the remaining `test_init.py` class to describe its cross-drive
path fallback coverage.
- No runtime behavior, CLI, config, translation, or ad YAML changes.

### ⚙️ Type of Change
Select the type(s) of change(s) included in this pull request:
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  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**
  * Reorganized test coverage for publishing and shipping functionality
  * Added tests for Windows cross-drive path fallback behavior
  * Added tests for shipping options mapping verification

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-19 13:00:36 +02:00
Jens
824ff70e43 refactor: move publishing result tests (#1146)
## ℹ️ Description
- Link to the related issue(s): N/A
- Moves a focused set of publish-ad orchestration/result tests out of
the package initialization test module and into the publishing workflow
test module.

## 📋 Changes Summary
- Move pre-submit timeout retry coverage into
`tests/unit/test_publishing_workflow.py`.
- Move WANTED shipping delegation coverage into the publishing workflow
tests.
- Move auto price reduction dispatch coverage into the publishing
workflow tests.
- Remove now-unused local fixture/imports from
`tests/unit/test_init.py`.
- No runtime, CLI, config, translation, or ad YAML changes.

### ⚙️ Type of Change
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  New feature (adds new functionality without breaking existing
usage)
- [ ] 💥 Breaking change (changes that might break existing user setups,
scripts, or configurations)
- [x] Refactor / test organization only

##  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**
  * Adjusted test coverage for internal functionality validation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-19 12:25:01 +02:00
Jens
01e764a477 refactor: move publish uncertainty tests (#1145)
## ℹ️ Description

- Link to the related issue(s): Issue #
- Moves publish-ad post-submit uncertainty tests into the publishing
workflow test module so `test_init.py` keeps shrinking toward
initializer-focused coverage.

## 📋 Changes Summary

- Moved the post-submit uncertainty `publish_ad()` test slice into
`tests/unit/test_publishing_workflow.py`.
- Kept the pre-submit retryability test and unrelated
initializer/CLI/login/shipping/ad-management tests in
`tests/unit/test_init.py`.
- Adjusted test imports only; no runtime behavior, configuration,
translation, or YAML changes.

### ⚙️ Type of Change

Select the type(s) of change(s) included in this pull request:
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  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**
* Reorganized test coverage for publishing workflows, expanding
validation of post-submit error handling and confirmation fallback
mechanisms to ensure robust publication behavior under various
scenarios.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-19 11:40:26 +02:00
Jens
f54521b8e6 refactor: move publishing diagnostics tests (#1144)
## ℹ️ Description

- Link to the related issue(s): N/A
- Move the publish failure diagnostics tests out of the
initializer-focused unit test module and into the publishing workflow
unit test module.
- This is a behavior-neutral test organization change.

## 📋 Changes Summary

- Moved `TestKleinanzeigenBotDiagnostics` from `tests/unit/test_init.py`
to `tests/unit/test_publishing_workflow.py`.
- Kept existing assertions and publishing workflow patch targets
unchanged.
- Removed stale imports from `tests/unit/test_init.py`.
- No runtime, CLI, config, translation, or ad YAML changes.

### ⚙️ Type of Change

Select the type(s) of change(s) included in this pull request:
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  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
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**
* Enhanced test coverage for diagnostics capture during publishing
failures, including verification of log file handling and behavior when
diagnostics are disabled.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-19 11:14:37 +02:00
Jens
2332682d07 refactor: move publishing orchestration tests (#1143)
## ℹ️ Description

- Link to the related issue(s): Issue #977
- Move another focused set of publishing/update orchestration tests out
of `tests/unit/test_init.py` and into
`tests/unit/test_publishing_workflow.py`.
- This is a behavior-neutral test organization change; no runtime, CLI,
config, translation, or ad YAML behavior changes are intended.

## 📋 Changes Summary

- Moved display counter progression coverage into the publishing
workflow test module.
- Moved publish-ads orchestration coverage into a dedicated publishing
workflow test class.
- Cleaned up imports left behind in `tests/unit/test_init.py`.
- Dependencies/configuration/additional requirements: none.

### ⚙️ Type of Change

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

Test-only refactor; none of the listed runtime change categories apply.

##  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. Not applicable;
test-only relocation.

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**
* Improved the ad publishing workflow test coverage by reorganizing
publish/cleanup, retry behavior, and “no-retry” scenarios.
* Added new async testing utilities (including a reusable mocked page)
to better simulate browser interactions.
* Expanded regression coverage for processing/counter progression across
publishing and updating, including paused ads and “not found” cases.
* Removed several redundant or replaced counter/retry/skip tests from
the prior initialization test module.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-19 10:40:02 +02:00
Jens
29aed68706 refactor: move publishing workflow tests (#1142)
## ℹ️ Description

- Link to the related issue(s): Follow-up to #1141
- Move a publishing/update orchestration test slice into the publishing
workflow test module.
- Share duplicated test fixtures/helpers that are now used by the
relocated publishing workflow tests and existing related tests.
- This remains behavior-neutral test organization only.

## 📋 Changes Summary

- Added `tests/unit/test_publishing_workflow.py` for publishing workflow
orchestration tests.
- Moved `TestKleinanzeigenBotUpdateAdsResilience` out of
`tests/unit/test_init.py`.
- Updated the relocated tests to import `KleinanzeigenBot` directly from
`kleinanzeigen_bot.app`.
- Moved the duplicated `base_ad_config` fixture into
`tests/conftest.py`.
- Extracted shared `build_update_ad` and `build_published_ads` test
helpers into `tests/conftest.py`.
- Removed duplicate fixture/helper definitions from affected unit test
files.
- No runtime behavior, CLI output, config parsing, translations, or ad
YAML handling changes.

### ⚙️ Type of Change

- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  New feature (adds new functionality without breaking existing
usage)
- [ ] 💥 Breaking change (changes that might break existing user setups,
scripts, or configurations)
- [x] Maintenance/refactor (tests only, no runtime behavior change)

##  Checklist

- [x] I have reviewed my changes to ensure they meet the project
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**
* Added comprehensive tests for ad publishing resilience and
orchestration, covering retry mechanisms, error handling, timeout
scenarios, and failure propagation across different ad states
* Reorganized and consolidated test utilities, shared fixtures, and
builder helpers across test modules

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-19 09:35:36 +02:00
Jens
f5f3399a66 refactor: move app shell out of package init (#1140)
## ℹ️ Description

Move the `KleinanzeigenBot` application class out of `__init__.py` into
a dedicated `app.py` module. This continues the maintainability-focused
refactoring of the package initializer that was started in prior PRs
(login flow extraction, publishing workflow extraction, etc.).

- Link to the related issue(s): N/A

## 📋 Changes Summary

- **New `src/kleinanzeigen_bot/app.py`** (525 lines): contains the full
`KleinanzeigenBot` class with all imports, constructor, `run()` command
dispatch, `load_ads()`, and delegator methods
- **`src/kleinanzeigen_bot/__init__.py`**: reduced from 545 lines to 27
lines — thin package initializer with `colorama` init, `LOG` setup,
`KleinanzeigenBot` re-export from `app.py`, `main()` delegator, and
`__name__` guard
- **Translations**: split `__init__.py` section header in
`translations.de.yaml`; class-owned translations moved to new
`kleinanzeigen_bot/app.py:` section
- **Tests**: updated patch targets from package-root paths to concrete
module paths (`kleinanzeigen_bot.UpdateChecker` →
`kleinanzeigen_bot.update_checker.UpdateChecker`)

### ⚙️ Type of Change
- [ ] 🐞 Bug fix
- [ ]  New feature
- [ ] 💥 Breaking change
- [x] 🔧 Refactoring (no user-facing behavior changes)

##  Checklist
- [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.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Centralized runtime command handling in a new application entrypoint
class while keeping the same CLI-style command set.
* Slimmed the package initializer to re-export the main bot class and
delegate CLI execution through `main(args)`.
* **Bug Fixes**
  * Improved Windows console compatibility during startup.
* **Tests**
* Updated unit tests and mocks to reflect the new location of
command-handling logic and related wiring.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-18 21:44:37 +02:00
Jens
35bd393dc4 refactor: extract login/auth flow into login_flow.py (#1139)
## ℹ️ Description

Extract all login, Auth0 form-fill, captcha, SMS/email verification,
GDPR, login-state detection, and diagnostics-capture logic from
`__init__.py` into a new `login_flow.py` module.

## 📋 Changes Summary

- Create `login_flow.py` (~634 lines) with module-level async functions
accepting explicit `web: WebScrapingMixin` + keyword arguments
- Thin delegators in `__init__.py` bind `KleinanzeigenBot` instance
attributes to module calls
- Remove 12 dead delegator methods; tests call module functions directly
- Move `LoginDetectionResult`/`LoginDetectionReason` types to
`login_flow`
- Move `TestKleinanzeigenBotAuthentication` to
`tests/unit/test_login_flow.py`, update patch targets
- Move login translations to `login_flow.py` section in
`translations.de.yaml`
- Fix diagnostics guard reset per login attempt
- Remove `is_logged_in_web()`; pass username explicitly
- Make cross-module functions public: `login_flow` internal helpers and
`WebScrapingMixin` helpers (`dismiss_consent_banner`,
`effective_timeout`, `extract_visible_text`)

### ⚙️ Type of Change
- [x]  New feature (adds new functionality without breaking existing
usage)

##  Checklist
- [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.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

* **Refactor**
* Reworked the authentication/login process to use a dedicated login
flow, improving handling of multi-step sign-in and related verification
prompts.
* Standardized login-status detection and diagnostics behavior when
login state can’t be determined.
* Updated text-extraction and timeout behavior for DOM-based fallbacks,
and aligned consent-banner dismissal to the public API.

* **Tests**
* Added extensive unit tests for the login flow and login-state
detection.
* Updated existing tests to reflect the refactored helpers and
consent-banner method naming.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-17 22:27:48 +02:00
Jens
ecd3ea35cf refactor: extract publishing workflow (#1137)
## ℹ️ Description

- Link to the related issue(s): N/A
- Extracts publish/update orchestration from the monolithic bot class
into `publishing_workflow.py` as Step 21 of the monolith breakdown.

## 📋 Changes Summary

- Added `src/kleinanzeigen_bot/publishing_workflow.py` for
publish/update orchestration, retry/uncertainty handling, result checks,
and delete-before/after-publish wiring.
- Kept `KleinanzeigenBot` publish/update methods as narrow delegation
seams with explicit dependencies.
- Moved WANTED ad type selection into `publishing_form.fill_ad_form()`
so form details stay in the form module.
- Updated German translations and unit tests to patch concrete workflow
module paths.
- No dependency or configuration changes.

### ⚙️ Type of Change

- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  New feature (adds new functionality without breaking existing
usage)
- [ ] 💥 Breaking change (changes that might break existing user setups,
scripts, or configurations)
- [x] Refactor (no user-facing behavior change)

##  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

* **Bug Fixes**
* Improved form rendering for WANTED ad type by ensuring the ad type is
pre-selected before populating other form fields.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-17 14:21:26 +02:00
Jens
eae9dc9fc1 refactor: extract publishing form orchestrator (#1136)
## ℹ️ Description

Move the last form-filling remnant out of `__init__.py` into
`publishing_form.py`, where all individual form section functions
already live.

- `_fill_ad_form()` only delegated to the already-extracted section
functions (`set_category`, `set_special_attributes`,
`set_shipping_form`, `set_pricing_fields`, `set_contact_fields`,
`fill_image_section`).
- Replaced with a public module-level `publishing_form.fill_ad_form()`
that accepts explicit `web`, `root_url`, `ad_defaults`, `ad_file`,
`ad_cfg`, and `mode` parameters.
- `publish_ad()` now calls `_publishing_form.fill_ad_form(self, ...)`
instead of `self._fill_ad_form(...)`.

## 📋 Changes Summary

- Extracted `fill_ad_form()` into `publishing_form.py` as a public
module-level async function
- Removed `_fill_ad_form()` method from `KleinanzeigenBot` class
- Updated call site in `publish_ad()` to pass explicit parameters
- Updated test patch target in `test_publishing_persistence.py` and
docstring in `test_init.py`

### ⚙️ Type of Change
- [ ] 🐞 Bug fix
- [ ]  New feature
- [ ] 💥 Breaking change

##  Checklist
- [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.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Reorganized ad form-filling logic for improved maintainability and
code structure.

* **Tests**
  * Updated unit tests to reflect internal code reorganization.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-17 13:22:39 +02:00
Jens
154c37b26e refactor: extract special-attributes/condition form section (#1135)
## ℹ️ Description

Extracts special-attribute and condition form logic from `__init__.py`
into `publishing_form.py` to continue module decomposition.

## 📋 Changes Summary

- **`publishing_form.py`**: Added `set_special_attributes`,
`_set_condition`, and helpers (`_pick_special_attribute_candidate`,
`_special_attribute_candidate_priority`,
`_describe_special_attribute_candidate`) adapted as module-level
functions taking `web: WebScrapingMixin`
- **`__init__.py`**: Removed the moved functions, cleaned up stale
imports (`re`, `_ad_form_helpers`, `_select_button_combobox`, `ensure`,
`Element`), updated `_fill_ad_form` to delegate via
`_publishing_form.set_special_attributes()`
- **`translations.de.yaml`**: Moved `_set_condition` and
`set_special_attributes` sections from `__init__.py:` →
`publishing_form.py:` block
- **`test_init.py`**: Moved special-attribute and condition tests to
`test_publishing_form.py`, updated orchestration patch targets
- **`test_publishing_form.py`**: Added `TestSpecialAttributes`,
`TestConditionSelector`, and `TestConditionFallbackToGenericHandler`
classes with adapted imports and patches

### ⚙️ Type of Change
- [x] Refactor — no behavior changes, internal module reorganization
only

##  Checklist
- [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.
2026-06-17 12:21:07 +02:00
Jens
875b8b063a refactor: extract publishing shipping form section (#1134)
## ℹ️ Description
- Extracts the publishing shipping form workflow from `KleinanzeigenBot`
into `publishing_form.py`.
- Keeps runtime behavior, selectors, waits, messages, and browser
interactions unchanged while moving translation ownership.

## 📋 Changes Summary
- Move WANTED shipping handling, shipping radio/dialog handling,
shipping option toggling, and the private React-fiber button-combobox
helper into `publishing_form.py`.
- Update `__init__.py` to delegate shipping form work and keep only
orchestration/seam responsibilities.
- Move direct shipping behavior tests to
`tests/unit/test_publishing_form.py`; keep a shipping orchestration seam
test in `tests/unit/test_init.py`.
- Move German translation entries for shipping messages to the
`publishing_form.py` translation section.

### ⚙️ Type of Change
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  New feature (adds new functionality without breaking existing
usage)
- [ ] 💥 Breaking change (changes that might break existing user setups,
scripts, or configurations)

##  Checklist
- [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

## Summary of changes

* **Refactor**
* Improved publishing ad shipping form handling by routing wanted vs
non-wanted ads to the correct UI flow.
* Enhanced Versand (shipping) combobox selection for wanted ads to
ensure the right option is chosen.
* Strengthened the shipping dialog flow for pickup vs
shipping-with-options, including more reliable shipping price
confirmation and retry behavior.

* **Tests**
* Expanded unit tests covering shipping dialog interactions,
carrier/option selection, wanted-shipping combobox behavior, and
timeout/error scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-17 08:13:35 +02:00
Jens
e9ed698dce refactor: extract publishing pricing form section (#1133)
## ℹ️ Description

- Link to the related issue(s): Issue #
- Extracts the publishing pricing/direct-buy/description form section
from the main bot class as the next monolith-breakdown step.

## 📋 Changes Summary

- Added publishing_form.set_pricing_fields() for price type/amount,
sell-directly/direct-buy radios, and description filling.
- Replaced the inline _fill_ad_form() pricing block with the new
publishing form section call.
- Moved direct unit coverage for pricing/direct-buy/description behavior
into test_publishing_form.py and kept orchestration seam tests in
test_init.py.
- Moved existing German translation ownership entries to the new
function path without wording changes.
- No dependencies, configuration changes, CLI changes, schema changes,
or generated artifacts.

### ⚙️ Type of Change

Select the type(s) of change(s) included in this pull request:
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  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
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

## Release Notes

* **Refactor**
* Centralized pricing-related ad form filling (price type, price amount,
ad description with affixes, and direct-buy/buy-now controls) into a
shared publishing helper for more consistent behavior.

* **Bug Fixes**
* Improved handling of pricing/buy-now selection failures by surfacing
clearer, localized timeout errors.

* **Tests**
* Added dedicated coverage for pricing field interactions, including
dropdown skipping when not applicable, correct amount input behavior,
and description generation usage.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-16 22:06:28 +02:00
Jens
137a7691a7 refactor: extract publishing image form section (#1132)
## ℹ️ Description

- Link to the related issue(s): N/A
- Extracts the publishing image cleanup and upload section from the bot
monolith into `publishing_form.py` as Step 16 of the monolith breakdown
plan.

## 📋 Changes Summary

- Move image cleanup/upload behavior into `fill_image_section()` and
`upload_images()`.
- Replace the monolith image block with a publishing form seam call.
- Move direct image behavior tests from `test_init.py` to
`test_publishing_form.py`.
- Update German translation ownership paths without changing translated
text.
- No dependency, configuration, CLI, schema, or generated artifact
changes.

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

##  Checklist
Before requesting a review, confirm the following:
- [x] I have reviewed my changes to ensure they meet the project
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

* **Refactor**
* Image upload handling has been reorganized and consolidated in the
publishing module for improved code organization. The image processing
workflow now includes enhanced timeout management and marker-based
completion detection to ensure all images are reliably processed during
ad publication.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-16 06:57:26 +02:00
Jens
9d146c2b96 refactor: extract publishing contact form section (#1131)
## ℹ️ Description

- Link to the related issue(s): Issue #
- Refactors the publishing form code by moving the contact/location form
section out of the package initializer into the publishing form module.

## 📋 Changes Summary

- Move contact field handling, contact location handling, and city
combobox helpers into publishing_form.py.
- Keep the publishing form orchestration delegating through an explicit
web/contact boundary.
- Update unit tests to patch and call the new publishing form functions.
- Move German translation ownership paths for the relocated messages
without changing translated text.
- No dependencies, configuration changes, or additional requirements
introduced.

### ⚙️ Type of Change

Select the type(s) of change(s) included in this pull request:
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  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

* **Refactor**
* Updated ad publishing to populate contact and location fields via
dedicated public publishing-form helpers, with revised import wiring for
ad model types.
* Removed older internal city/contact handling logic and replaced it
with clearer helper-driven flow.
* **Bug Fixes**
* Improved city/location hardening: safer city extraction,
combobox-style selection with ambiguity checks, timeout-based fallbacks,
and correct handling for read-only city inputs.
* More resilient contact field population (zipcode/location, street,
name, phone), with graceful handling when optional elements are missing.
* **Tests**
* Added/updated unit tests to cover city/contact hardening and category
probe/picker selection behaviors, asserting helper calls rather than
private methods.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-16 06:16:22 +02:00
Jens
f02816cb06 refactor: extract publishing category form section (#1130)
## ℹ️ Description
- Link to the related issue(s): N/A
- Extracts the publishing category form section from the monolithic bot
class into a focused publishing form module while preserving category
selection and suggestion fallback behavior.

## 📋 Changes Summary
- Move category selection and category suggestion fallback into
`src/kleinanzeigen_bot/publishing_form.py`.
- Update publish form orchestration to call the extracted category
function through a narrow web/root_url boundary.
- Update unit tests and German translation ownership for the new module
path.
- No dependencies, configuration changes, or additional requirements
introduced.

### ⚙️ Type of Change
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  New feature (adds new functionality without breaking existing
usage)
- [ ] 💥 Breaking change (changes that might break existing user setups,
scripts, or configurations)

##  Checklist
- [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

* **Refactor**
* Restructured category selection logic for improved maintainability and
code organization.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-15 20:51:02 +02:00
Jens
dd6a1955ef refactor: extract publishing form helpers (#1129)
## ℹ️ Description
- Extract browser-independent publishing form helpers from the monolith
into `ad_form_helpers.py`.
- Prepares later publishing form extraction without changing browser
behavior.

## 📋 Changes Summary
- Move the Versand combobox selector constant to `ad_form_helpers.py`.
- Move location matching logic to `ad_form_helpers.py`.
- Update production call sites and helper ownership tests.
- Keep form sections and browser-dependent logic in `__init__.py`.

### ⚙️ Type of Change
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  New feature (adds new functionality without breaking existing
usage)
- [ ] 💥 Breaking change (changes that might break existing user setups,
scripts, or configurations)

##  Checklist
- [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

* **Refactor**
* Improved internal code organization by consolidating shared helpers
for form-related utilities and location matching logic.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-15 17:34:26 +02:00
Jens
fda56b109c refactor: prepare web helpers for publishing form extraction (#1128)
## ℹ️ Description

- Link to the related issue(s): Issue #930
- Moves a shared consent-banner browser helper onto the generic web
scraping mixin so later publishing form refactors can use it without
depending on the main bot class.

## 📋 Changes Summary

- Move `_dismiss_consent_banner()` from `KleinanzeigenBot` to
`WebScrapingMixin` without changing selector, timeout, click, or sleep
behavior.
- Move the helper's focused unit tests from `test_init.py` to
`test_web_scraping_mixin.py`.
- No dependencies, configuration changes, or additional requirements
introduced.

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

None of the listed categories apply; this is an internal
behavior-equivalent refactor.

##  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

## Release Notes

* **Refactor**
* Moved consent-banner dismissal logic into a shared web-scraping
helper, and updated the bot flow to proceed directly after SMS
verification.

* **Tests**
* Removed unit tests that covered the old bot-level consent-banner
handling.
* Added new async unit tests to verify the shared helper clicks the
consent accept button when present and does nothing when absent.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-15 14:10:47 +02:00
Jens
816cf3ed9e refactor: rename publishing persistence module (#1127)
## ℹ️ Description

- Link to the related issue(s): Issue #
- Rename the persistence-only publishing module so the module name
matches its current responsibility.

## 📋 Changes Summary

- Rename `publishing_flow.py` to `publishing_persistence.py`.
- Update production imports/calls and test patch paths.
- Move persistence tests to `test_publishing_persistence.py` and keep
submission tests under `test_publishing_submission.py`.
- Update the German translation section header for the renamed module.

### ⚙️ Type of Change

- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  New feature (adds new functionality without breaking existing
usage)
- [ ] 💥 Breaking change (changes that might break existing user setups,
scripts, or configurations)

##  Checklist

- [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

* **Refactor**
* Improved the way published ad changes are saved after submitting or
updating, ensuring related fields (such as image updates and pricing
adjustments) are correctly retained.

* **Bug Fixes**
* Publishing now continues even if the follow-up save step fails,
preventing unnecessary interruptions.

* **Tests**
  * Updated existing tests to match the revised persistence behavior.
* Added new unit tests covering persistence outcomes, logging, rollback
behavior, and image/price update handling.
  * Simplified the publishing submission test suite setup.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-15 13:34:39 +02:00
Jens
5c22795728 fix: handle shipping select for pro accounts (#1126)
## ℹ️ Description

- Link to the related issue(s): Issue #1125
- Fixes PRO/commercial account publishing where the Versand field is
rendered as a custom `button[role="combobox"]` dropdown by the
PostListingForm Astro island, not as a native `versand_s` `<select>` and
not as the private-account radio-button UI.
- Uses the confirmed PRO DOM model from the issue comments/screenshots:
category-prefixed IDs such as `kunst.versand` / `uhren.versand`,
placeholder `Bitte wählen`, and options `Versand möglich` (`ja`) / `Nur
Abholung` (`nein`).

## 📋 Changes Summary

- Detect the custom Versand combobox before falling back to the private
radio/dialog shipping flow.
- Locate the combobox by the confirmed category-prefixed id shape and by
the selected-option label relationship
(`aria-labelledby$="versand-selected-option"`) so the lookup is not tied
to one category prefix.
- Select `Versand möglich` for `shipping_type: SHIPPING` and `Nur
Abholung` for `shipping_type: PICKUP` by visible option text via
`web_select_button_combobox()`.
- Apply the same safer Versand combobox selector to the existing
WANTED-ad shipping combobox path.
- Keep the private/non-commercial radio-button and
shipping-options-dialog path as the fallback when the custom combobox is
absent.
- Add regression coverage for the PRO combobox path and relax existing
shipping-flow assertions so they check behavior instead of exact probe
ordering/timeout plumbing.
- Add the German translation for the combobox error message.

### ⚙️ Type of Change

- [x] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  New feature (adds new functionality without breaking existing
usage)
- [ ] 💥 Breaking change (changes that might break existing user setups,
scripts, or configurations)

##  Checklist

- [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

* **New Features**
* Improved the WANTED shipping flow to detect and use native “Versand”
dropdown controls when available, selecting the correct shipping option
directly.
* **Bug Fixes**
* Added clearer errors for unsupported shipping types and standardized
timeout behavior when native selection fails; continues to fall back to
the existing radio/dialog approach when the control is missing.
* **Tests**
* Expanded unit tests to cover the native Versand combobox route,
including selector coverage and updated probe/call-order expectations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-14 21:42:53 +02:00
Jens
f94567a983 fix: use canonical title for owned ad downloads (#1123)
## ℹ️ Description

- Link to the related issue(s): Issue #1119
- Fixes owned-ad downloads where the public/detail page title may
include UI status decoration that makes the title exceed the semantic
65-character title limit.

## 📋 Changes Summary

- Resolve download titles once via manage-ads metadata when available.
- Reuse the resolved title for folder naming, file stems, and persisted
ad config data.
- Keep manual page title extraction as the fallback without stripping or
truncation.
- Add regression coverage for canonical metadata titles and fallback
extraction.

### ⚙️ Type of Change

- [x] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  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

* **Improvements**
* Enhanced ad title resolution to prioritize cached published metadata
when available, with fallback to page extraction for improved
reliability.
* Improved directory handling to consistently use the resolved ad title
for naming and saved info templates.
* **Bug Fixes**
* Corrected title/metadata extraction flow to avoid redundant title
reads and ensure creation dates are reliably sourced from the expected
page elements.
* **Tests**
* Updated and added unit test coverage for title resolution, directory
behaviors, and extraction call ordering.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-14 11:22:16 +02:00
Jens
439c36e851 fix: click Auth0 Weiter after captcha (#1120) (#1122)
## ℹ️ Description

Fixes issue #1120 by handling the Auth0 captcha branch after email
submit. When captcha appears, the bot now probes for a visible/clickable
"Weiter" button and clicks it before waiting for the password page. The
normal no-captcha login flow remains unchanged.

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

## 📋 Changes Summary

- Added a captcha-only Auth0 identifier handler that waits for captcha
completion, probes for "Weiter" with `web_probe`, and clicks it if
present.
- Kept the existing no-captcha login path unchanged.
- Updated German translations for the new log messages.
- Adjusted unit tests for the new login helper.

### ⚙️ Type of Change

- [x] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  New feature (adds new functionality without breaking existing
usage)
- [ ] 💥 Breaking change (changes that might break existing user setups,
scripts, or configurations)

##  Checklist

- [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. No additional docs
changes were needed beyond translations.

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

* **Bug Fixes**
* Improved login robustness to detect optional reCAPTCHA on the
email/identifier step; users are notified when a captcha appears, can
provide input, and the login resumes once advanced.
* **Tests**
* Updated unit tests to cover and control the captcha-handling behavior
during the login flow, including the missing-password-step scenario.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-13 23:28:04 +02:00
Jens
5d5c25d753 refactor: extract publishing submission flow (#1121)
## ℹ️ Description
*Refactor the publishing submission/confirmation flow into a dedicated
module while keeping behavior identical.*

- Link to the related issue(s): N/A
- Describe the motivation and context for this change: Reduce coupling
in the publishing workflow and keep the persistence boundary separate.

## 📋 Changes Summary

- Added `src/kleinanzeigen_bot/publishing_submission.py` for captcha,
submit, confirmation, and ad-id recovery.
- Removed the submission/confirmation helpers from `publishing_flow.py`,
leaving persistence helpers there.
- Updated the `publish_ad()` call site and tests to use the new concrete
module path.
- Moved the translated log-message entries for the extracted submission
flow.

### ⚙️ Type of Change
Select the type(s) of change(s) included in this pull request:
- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  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`).
- [ ] 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

* **Refactor**
* Ad submission and confirmation logic moved into a dedicated submission
module for clearer separation.

* **Bug Fixes**
* Improved publish confirmation: better captcha handling, post-submit
dialogs (upsell/payment, imprint guidance, “no image”) and avoidance of
stale-referrer fallbacks.
* Now fails loudly and avoids persisting when ad confirmation is
uncertain.

* **Tests**
* Unit tests updated to target the new submission flow and
recovery/persistence behaviors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-13 21:35:41 +02:00
Jens
97256c1d70 fix: apply after_delete policy when title-mode delete returns all-404 with id=None (#1116)
## ℹ️ Description

When `delete_old_ads_by_title=True` and `ad_cfg.id=None` and all remote
DELETE requests return 404, the `after_delete` policy (DISABLE/RESET)
was silently skipped because the orchestrator's `id_before`/`id_after`
heuristic could not distinguish "not attempted" from "attempted but all
404."

This PR replaces the heuristic with an explicit `DeleteResult`
NamedTuple returned from `delete_ad()`, communicating whether HTTP
DELETE requests were actually sent (`attempted`) separately from whether
any returned 200 (`deleted`).

Closes #1103.

## 📋 Changes Summary

### 🐞 Bug fix

- **`delete_flow.py`** — New `DeleteResult(deleted, attempted)`
NamedTuple replaces the `bool` return from `delete_ad()`. The
orchestrator now uses `result.attempted` directly to decide whether to
apply `after_delete` policy.
- Removed the fragile `id_before`/`ad_cfg.id`-based heuristic entirely.

### 🧪 Tests

- Updated all 9 existing `delete_ad()` tests for the new tuple return
type
- Updated 3 orchestrator tests (`delete_ads`) for the new `DeleteResult`
API
- Added regression test
`test_cleanup_on_title_match_all_404_with_id_none` — `ad_cfg.id=None`,
title-match, all responses 404, `after_delete=RESET` is applied

### ⚙️ Type of Change
- [x] 🐞 Bug fix (non-breaking change which fixes an issue)

##  Checklist
- [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.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved deletion tracking to distinguish attempted vs successful
deletions, ensuring cleanup policies run correctly across edge cases.

* **Tests**
* Updated unit tests to reflect the new deletion outcomes and added a
regression test to verify cleanup behavior when title-mode deletions all
fail.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-13 09:41:24 +02:00
Jens
d2b944568c fix: close submit uncertainty gap, surface silent TOCTOU race, and translate remaining TimeoutError messages (#1114)
## ℹ️ Description

Three bug fixes affecting browser automation correctness and i18n
completeness:

- **#1110**: Move upsell dialog dismiss inside the post-submit
try/except block so timeouts during upsell interaction are treated as
uncertain submission rather than retryable errors, preventing duplicate
listings.
- **#1112**: Change `web_set_input_value` to detect TOCTOU races
(element disappearing between `web_find` and JS execution). The JS now
returns `false` on missing element, and the Python side raises
`TimeoutError` so callers see a clear failure instead of proceeding with
stale field data. **Approach**: sentinel return (not JS throw) because
nodriver returns `ExceptionDetails` silently rather than raising Python
exceptions.
- Translate two remaining untranslated `TimeoutError` messages in
`__init__.py:_set_condition` and
`web_scraping_mixin.py:_run_with_timeout_retries`.

## 📋 Changes Summary

| File | Change |
|---|---|
| `publishing_flow.py` | Move upsell dismiss inside `try` block (#1110)
|
| `web_scraping_mixin.py` | Sentinel + `TimeoutError` for TOCTOU race
(#1112); translate guard-clause message |
| `__init__.py` | Translate `No condition radio matched values` message
|
| `translations.de.yaml` | Add German translations for all 3 new
messages |

### ⚙️ Type of Change
- [x] 🐞 Bug fix (non-breaking change which fixes an issue)

##  Checklist
- [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`).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved and localized error messages for timeouts and form
interactions.
* Better handling of element race conditions to reduce submission
failures.

* **Refactor**
* Reworked ad submission confirmation flow to unify exception handling
and make upsell dismissal more robust.

* **Tests**
* Added tests covering input-setting behavior and TOCTOU race handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-13 08:31:19 +02:00
Jens
ce3c1adf89 fix: correct republication timing and content hash computation (#1113)
## ℹ️ Description

Two small bug fixes affecting ad lifecycle correctness, both in
`ad_loading.py`.

- Fix off-by-one republication comparison (#1099): ads are now eligible
for republication exactly when their age reaches the configured
interval, instead of one day late.
- Fix content hash divergence (#1100): `update-content-hash` now
computes hashes from the raw ad config (matching `check_ad_changed`),
preventing spurious "ad changed" detection on every publish.

## 📋 Changes Summary

### 🐞 Bug fixes

1. **#1099** — `check_ad_republication`: changed `<=` to `<` so ads are
due at `ad_age.days == republication_interval` rather than one day
later.
2. **#1100** — `update_content_hashes`: switched from
`ad_cfg.update_content_hash()` (hydrated Ad model) to
`AdPartial.model_validate(ad_cfg_orig).update_content_hash()` (raw
config dict), matching `check_ad_changed`. Also uses `.get()` to handle
legacy ad files without a `content_hash` key.

### 🧪 Tests

Updated `test_counter_progression` to pre-compute hashes using the same
`AdPartial` path as production code.

### ⚙️ Type of Change
- [x] 🐞 Bug fix (non-breaking change which fixes an issue)

##  Checklist
- [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.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **Bug Fixes**
* Adjusted ad republication timing logic—ads at the republication
interval boundary are now correctly treated as not yet due for
republication.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-13 07:54:25 +02:00
Jens
2971d650c7 refactor: extract captcha flow and submit/confirm to dedicated modules (#1111)
## ℹ️ Description

Extract captcha detection and ad submit/confirmation logic from
`__init__.py` into focused modules. This is part of an ongoing effort to
decompose the monolithic `KleinanzeigenBot` class into maintainable,
testable module functions with explicit dependencies.

- Related to #1110 (pre-existing submit uncertainty boundary gap,
tracked separately)

## 📋 Changes Summary

- **New module `captcha_flow.py`**: `check_and_wait_for_captcha()`
shared by login and publish flows. Captcha auto-restart raises
`CaptchaEncountered` on non-login pages.
- **`publishing_flow.py` gains `submit_and_confirm_ad()`**: submit
click, upsell dismissal, imprint guidance, no-image confirmation,
payment form detection, confirmation URL polling with tracking-data
fallback, ad ID extraction. Plus private
`_try_recover_ad_id_from_redirect()`.
- **`web_set_input_value()` promoted to `WebScrapingMixin`**: generic
browser primitive for setting DOM values with React-compatible events.
Removed from bot class.
- **Translations moved** to match new module/key locations.
- **Coverage**: `captcha_flow.py` 100%, `publishing_flow.py` 90.78% (up
from 83.98%).
- **Cleanup**: removals from `__init__.py` (233 lines), stale/low-value
tests removed from `test_init.py`.

No CLI, config, YAML, or runtime behavior changes.

### ⚙️ Type of Change
- [x]  New feature (adds new functionality without breaking existing
usage)
  — technically a refactor; no behavioral changes

##  Checklist
- [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.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* More reliable ad publishing with a fallback recovery when confirmation
fails
* Improved shipping-price entry with read-back verification and
automatic retries
* More consistent form field handling (contact info, price, description)
and improved captcha prompting during login

* **Refactor**
* Internal publishing and captcha flows reorganized for more robust user
interactions

* **Tests**
* Added extensive tests for captcha handling and ad
submission/confirmation flows
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-13 06:49:51 +02:00