Commit Graph

995 Commits

Author SHA1 Message Date
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
75f34775e2 refactor: reduce return-count complexity (#1154)
## ℹ️ Description

- Link to the related issue(s): Issue #
- Reduces return-count complexity in the update checker while preserving
existing update-check behavior.

## 📋 Changes Summary

- Extracted GitHub release selection and commit-ish validation from
`check_for_updates` into a private helper.
- Moved German translation keys for logger calls that now originate from
the helper.
- Ratcheted Ruff/Pylint `max-returns` from 10 to 8.

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

* **Bug Fixes**
* The update checker now correctly handles the latest update channel by
excluding pre-release versions and recommending only stable releases.

* **Chores**
  * Code quality configuration settings were adjusted.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-20 10:49:22 +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
kleinanzeigen-bot-tu[bot]
8b0e4f468d chore: Update Python dependencies (#1151)
✔ Update pytest 9.1.0 -> 9.1.1 successful
  ✔ Update msgpack 1.2.0 -> 1.2.1 successful
  ✔ Update ruff 0.15.17 -> 0.15.18 successful

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-19 16:34:44 +02:00
Jens
e8752ce10e refactor: reduce price reduction complexity (#1152)
## ℹ️ Description

- Link to the related issue(s): Issue #
- Extracts the first complexity hotspot in the ratcheting sequence by
separating the final auto price-reduction application flow from
`apply_auto_price_reduction`.
- This keeps behavior unchanged while allowing the Ruff `max-returns`
transitional limit to move from 13 to 12.

## 📋 Changes Summary

- Extracted `_apply_price_reduction_decision` for the final
price-reduction mutation/logging phase.
- Moved German translations for the relocated `LOG.info` calls to the
new helper function key.
- Lowered `tool.ruff.lint.pylint.max-returns` from 13 to 12.
- No dependency or runtime configuration changes 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)

Refactor-only change; none of the listed user-facing change types 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.

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**
  * Updated linting configuration thresholds for code quality standards.

* **Refactor**
  * Improved internal code organization for better maintainability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-19 16:19:33 +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
ee581e383f refactor: clean up source hygiene (#1141)
## ℹ️ Description

- Link to the related issue(s): Issue #
- Clean up minor post-extraction source hygiene without changing runtime
behavior.

## 📋 Changes Summary

- Import `KleinanzeigenBot` from its concrete `app` module in the CLI.
- Update stale source comments/docstrings that referenced the previous
package structure.
- No dependencies, configuration changes, CLI output changes,
translation changes, or ad YAML handling 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

* **Chores**
* Refactored internal code organization by adjusting module imports and
updating code documentation references to enhance maintainability and
clarity.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-19 08:09:14 +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
kleinanzeigen-bot-tu[bot]
d3507c0ff5 chore: Update Python dependencies (#1138)
✔ Update certifi 2026.5.20 -> 2026.6.17 successful
  ✔ Update cyclonedx-python-lib 11.10.0 -> 11.11.0 successful

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-17 22:28:34 +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
kleinanzeigen-bot-tu[bot]
021b887d08 chore: Update Python dependencies (#1124)
✔ Update filelock 3.29.3 -> 3.29.4 successful
  ✔ Update pytest 9.0.3 -> 9.1.0 successful
  ✔ Update pyinstaller 6.20.0 -> 6.21.0 successful
  ✔ Update basedpyright 1.39.7 -> 1.39.8 successful

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-14 14:53:58 +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
9172af0c69 enh: skip runtime bootstrap for update-check command (#1117)
## ℹ️ Description

The `update-check` command was calling `_bootstrap_runtime()` which
triggered config file creation (`create_default_config`), file logging
setup, and browser config application. None of these are needed for a
read-only GitHub release version check.

This PR skips the full bootstrap and instead uses `UpdateChecker` with
sensible defaults from `Config()` and a state path resolved from the
already-available workspace.

Closes #1090.

## 📋 Changes Summary

### 🚀 Enhancement

- **`src/kleinanzeigen_bot/__init__.py`** — `update-check` case: removed
`_bootstrap_runtime()` call, uses `self.config = Config()` with sensible
defaults, state path from already-resolved `self.workspace`.

### ⚙️ Type of Change
- [x] 🚀 New feature / Enhancement (non-breaking change which adds
functionality)

##  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-13 10:37:43 +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
kleinanzeigen-bot-tu[bot]
4ec4e802fd chore: ✔ Update ruff 0.15.16 -> 0.15.17 (#1108)
✔ Update ruff 0.15.16 -> 0.15.17 successful

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-13 08:34:48 +02:00
Jens
f1e37c6f58 fix: disable diagnostics timing collection by default (#1115)
## ℹ️ Description

Change the `diagnostics.timing_collection` default from `true` to
`false` so that timeout timing data is only collected when a user
explicitly opts in.

## 📋 Changes Summary

- `model/config_model.py`: changed
`TimingCollectionConfig.timing_collection` default from `True` to
`False`
- `schemas/config.schema.json`: regenerated with `"default": false`
- `docs/config.default.yaml`: regenerated with `timing_collection:
false`

Existing configs that explicitly set `timing_collection: true` are
unaffected.

### ⚙️ Type of Change

- [ ] 🐞 Bug fix (non-breaking change which fixes an issue)
- [ ]  New feature (adds new functionality without breaking existing
usage)
- [x] 💥 Breaking change (changes default behavior — users who relied on
the implicit `true` default must now explicitly enable it)

##  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-13 08:34:26 +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
Jens
a86e9d5d5e refactor: extract persist_published_ad() to publishing_flow.py (#1107)
## ℹ️ Description

Extract post-publish persistence from `KleinanzeigenBot` into a new
`publishing_flow` module, establishing the first publishing-domain
extraction following the pattern used for `delete_flow`, `extend_flow`,
and `download_flow`.

## 📋 Changes Summary

- **New module `publishing_flow.py`** (~140 lines) with two functions:
- `persist_published_ad()` — public boundary: YAML field updates,
content hash, timestamps, repost/price-reduction counters, save,
rollback, and local path renaming
- `\_log\_local\_path\_rename\_result()` — module-private logging helper
- **`__init__.py`**: added `publishing_flow` import, replaced `await
self.\_persist\_published\_ad(...)` with
`publishing\_flow.persist\_published\_ad(...)` (sync call, no await),
removed both methods (~120 lines)
- Cleaned up unused imports in `__init__.py`: `AdPartial`, `replace`,
`_local_path_renaming`, `_dicts`
- Translation sections moved from `__init__.py` to `publishing_flow.py`
in `translations.de.yaml`

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

Internal refactoring only — no user-facing behavior or CLI 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**
* Post-publication persistence moved into a dedicated publishing flow to
centralize ad update and rename handling.

* **Improvements**
* Enhanced logging and handling of local file/folder renames after
publication.
* Updated persisted ad metadata after publish (IDs, content hashes,
timestamps, repost and price-reduction counts).

* **Bug Fixes**
* Publishing now catches and logs persistence failures instead of
letting them propagate.

* **Tests**
* Added unit tests for rename logging, persistence behavior, rollback on
save failure, and resilience to persistence errors.

* **Chores**
  * Updated file copyright headers.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-12 18:22:31 +02:00
Jens
38399f4dfe refactor: split publish_ad() into three named private methods (#1106)
## ℹ️ Description

Decompose the ~350-line `publish_ad()` monolith into three named private
methods on `KleinanzeigenBot`, creating explicit seams before eventual
module extraction.

## 📋 Changes Summary

- **`_fill_ad_form()`** — form filling: category, special attributes,
shipping, price, sell-directly, description, contact, image cleanup,
image upload
- **`_submit_and_confirm_ad()`** — submission: captcha, title, submit
click, upsell dismissal, confirmation URL polling, ad-ID extraction +
fallback recovery. Returns ad_id.
- **`_persist_published_ad()`** — post-publish persistence: update
fields, content_hash, timestamps, repost_count, price_reduction_count;
save file; local path renaming; log result
- **Prelude** (~33 lines) stays inline: mode-dependent setup, price
reduction, page navigation
- Dropped stale `noqa` complexity comments on `publish_ad()` (now ~45
lines)
- Translation sections split to match new method names

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

Internal refactoring only — no user-facing behavior or CLI 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**
* Improved ad publishing workflow for more reliable publish/update
operations.
  * Better handling of replace vs modify publishing modes.
  * Automatic dismissal of consent banner during publishing.
  * UI switch for WANTED-type ads applied automatically.
* More robust recovery and persistence of published ad IDs, image
renaming, timestamps and counters to prevent inconsistencies.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-12 14:20:54 +02:00
Jens
9b265c7cdb refactor: extract _delete_old_ad_if_needed helper from publish paths (#1105)
## ℹ️ Description

Replace two near-duplicate delete-guard blocks in `publish_ads()`
(`AFTER_PUBLISH`) and `publish_ad()` (`BEFORE_PUBLISH`) with a single
`KleinanzeigenBot._delete_old_ad_if_needed()` private method that
centralises `keep_old_ads`, `delete_old_ads` timing, and
title-based-deletion logic.

## 📋 Changes Summary

- **New helper** `_delete_old_ad_if_needed(ad_cfg, published_ads_list,
*, timing)` centralises `keep_old_ads`, `delete_old_ads` timing, and
title-based-deletion guards; delegates to `delete_flow.delete_ad()`
- **`publish_ads` AFTER_PUBLISH**: 7-line `if success and ... and not
keep_old_ads:` + `delete_flow.delete_ad()` → 1-line call inside existing
`if success:` block
- **`publish_ad` BEFORE_PUBLISH**: Nested 7-line guard +
`delete_flow.delete_ad()` → 1-line call under existing `if mode ==
REPLACE:`
- Added `Literal` to typing import

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

Internal refactoring only — no user-facing behavior or CLI 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**
* Consolidated internal logic for ad deletion handling to improve code
organization and maintainability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-12 13:01:33 +02:00
Jens
b256232939 refactor: rename form methods and remove fetch delegator in preparation for publishing workflow extraction (#1104)
## ℹ️ Description

Mechanical prep work for the publishing/update workflow extraction. No
behavior changes — purely renaming and cleanup.

## 📋 Changes Summary

- Rename 19 double-underscore form methods (`__method` → `_method`) in
`__init__.py`
- Remove the temporary `_fetch_published_ads()` delegator;
`publish_ads()` and `update_ads()` now call
`published_ads.fetch_published_ads()` directly
- Fix local variable naming in publish/update to use
`published_ads_list` to avoid shadowing the `published_ads` module
- Update 95 test references from mangled names (`_KleinanzeigenBot__*`)
to single-underscore names (`_*`)
- Update 8 test patches from `patch.object(test_bot,
"_fetch_published_ads", ...)` to
`patch("kleinanzeigen_bot.published_ads.fetch_published_ads", ...)`
- Update 10 translation section headers in `translations.de.yaml` to
match renamed method names
- Preserve `@staticmethod` decorators on `_location_matches_target`,
`_special_attribute_candidate_priority`, and
`_describe_special_attribute_candidate`

### ⚙️ Type of Change

- [x]  New feature (adds new functionality without breaking existing
usage)
→ Actually a refactor: no user-facing changes, purely internal
restructuring.

##  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 shipping price persistence with retry logic to ensure values
are correctly set and verified.

* **Refactor**
  * Internal code structure improvements for better maintainability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-12 11:28:08 +02:00
Jens
7803872422 refactor: extract delete, extend, download workflows and published-ad fetch into focused modules (#1101)
## ℹ️ Description

> **Why one PR?** The four extractions are mechanically identical:
extract a method body into a module function, replace `self.*` with
explicit parameters, update call sites in `run()`. Splitting into 4
sequential PRs would produce repetitive diffs against the same
`__init__.py`, compound rebase friction, and add no independent review
signal — each PR would be reviewed as "same pattern, different
function." Combined, the mechanical nature is visible at a glance and
the 17 files are mostly new modules + focused test files.

Reduce KleinanzeigenBot.__init__.py from 2865 to 2391 lines by
extracting four browser workflow modules with explicit dependencies.

## 📋 Changes Summary

- New `published_ads.py` — `fetch_published_ads()` and
`PublishedAdsFetchIncompleteError`, shared by all workflows
- New `delete_flow.py` — `delete_ads()` orchestrator and `delete_ad()`
single-ad deletion
- New `extend_flow.py` — `extend_ads()` orchestrator and `_extend_ad()`
single-ad extension
- New `download_flow.py` — `download_ads()`, `resolve_download_dir()`,
and `_download_ad_with_resolved_state()`
- Promote `_timeout` and `_navigate_paginated_ad_overview` to public in
WebScrapingMixin
- Restore `PublishedAdsFetchIncompleteError` base class to
KleinanzeigenBotError
- New focused test files: `test_published_ads.py` (17),
`test_delete_flow.py` (13), `test_extend_flow.py` (17),
`test_download_flow.py` (16)
- Removed: `test_json_pagination.py`, `test_extend_command.py`
- Flow modules receive explicit dependencies (`web`, `root_url`,
`config`, `workspace`, `load_ads_func`) — no `do_work(bot)` signatures
- Temporary `_fetch_published_ads` delegator kept on bot (still used by
publish/update)

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

* **Documentation**
* Clarified timeout configuration guidance and examples (use public
timeout API and when to use effective timeout).

* **New Features**
* Added workflows to delete, download (all/new/IDs) and extend ads;
added API-backed published-ads retrieval and safe ID matching.

* **Refactor**
* Command routing moved to workflow modules; unified timeout and
pagination helpers for consistent UI/navigation behavior.

* **Tests**
* Expanded/updated unit tests covering deletion, download, extension,
pagination, timeout and related edge cases.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-12 09:58:56 +02:00
kleinanzeigen-bot-tu[bot]
eeafd64280 chore: Update Python dependencies (#1095) 2026-06-11 20:03:23 +02:00
Jens
20be5e9528 refactor: extract ad loading and selector resolution into ad_loading.py (#1098)
## ℹ️ Description

Extract browser-free ad discovery, validation, selector filtering,
category resolution, content-hash helpers, and image globbing from
`__init__.py` into a new `ad_loading.py` module with explicit
dependencies.

Non-breaking internal refactor — no CLI, config, or user-facing behavior
changes.

## 📋 Changes Summary

- **New module** `src/kleinanzeigen_bot/ad_loading.py` (373 lines) with
9 public functions
- **Removed** `_is_valid_ads_selector`, `__check_ad_republication`,
`__check_ad_changed`, `load_ad`, `update_content_hashes` from
`KleinanzeigenBot`
- `load_ads()` kept as temporary delegator (7+ call sites in `run()` /
`download_ads()`)
- **37 focused tests** in `tests/unit/test_ad_loading.py`
- **Removed** 14 duplicate tests from `tests/unit/test_init.py`
- **Translations** moved from `__init__.py` to `ad_loading.py` function
names

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

* **Refactor**
* Moved ad discovery, validation, filtering, image/category resolution,
and content-hash management into a dedicated ad-loading component; CLI
selector validation and content-hash updates now use that shared logic.

* **Tests**
* Added comprehensive unit tests for ad discovery, selector
parsing/validation, change detection, republication rules,
image/category resolution, and content-hash update behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-10 16:08:01 +02:00
Jens
c7f16e3361 fix: add fallback for button-combobox special attributes after site redesign (#1097)
## ℹ️ Description

After the Kleinanzeigen.de React redesign, category-specific fields like
Größe, Farbe, and Mädchen & Jungen are rendered as `<button
role="combobox">` whose IDs may not match the existing XPath patterns
used by `__set_special_attributes`. When the XPath only matches the
backing hidden `<input type="hidden" name="attributeMap[...]">`, the
handler falls through to the text-input branch and fails.

- Fixes: #1096

## 📋 Changes Summary

- Added `_find_associated_button_combobox()` to `web_scraping_mixin.py`
— a JS-based fallback that locates the associated `<button
role="combobox">` by searching for the backing hidden input and walking
up the DOM tree
- Integrated the fallback into `__set_special_attributes`: when the
matched element is `type="hidden"`, searches for an associated button
and routes to `__select_button_combobox`
- Added 3 unit tests covering: fallback finds button, no button found
falls through, fallback not triggered when button directly matched

### ⚙️ 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
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 form-field handling to detect hidden-input-backed controls
and automatically select their associated combobox buttons for more
reliable interaction with dynamic forms.

* **Tests**
* Added unit tests covering hidden-input fallback: successful combobox
selection, error when no associated combobox is found, and the
direct-visible-combobox path.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-10 12:40:10 +02:00
kleinanzeigen-bot-tu[bot]
b26e6bfc25 chore: Update Python dependencies (#1093) 2026-06-09 10:29:46 +02:00