mirror of
https://github.com/Second-Hand-Friends/kleinanzeigen-bot.git
synced 2026-07-09 12:41:05 +02:00
fix: persist created_on on first publish (#1204)
## ℹ️ Description - Link to the related issue(s): Issue # - Fixes first-publish persistence so newly published ads write `created_on` when missing. ## 📋 Changes Summary - Capture first-publish state before writing the new ad ID. - Persist one publication timestamp to both `updated_on` and missing first-publish `created_on`. - Add regression tests for first publish with an already-populated ad ID and update preservation of existing `created_on`. - No dependency, configuration, or generated artifact changes. ### ⚙️ Type of Change - [x] 🐞 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (adds new functionality without breaking existing usage) - [ ] 💥 Breaking change (changes that might break existing user setups, scripts, or configurations) ## ✅ Checklist - [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 publish timestamp handling so first-time publishes and later updates record dates consistently. * Preserves the original creation date when it already exists, while keeping the last-updated time current. * **Tests** * Added coverage for timestamp behavior on first publish and subsequent updates. * Verified date values are stored in ISO-8601 format with timezone information. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -72,6 +72,7 @@ def persist_published_ad(
|
||||
) -> None:
|
||||
"""Write the published ad ID, hash, timestamps, and counters back to the
|
||||
YAML file, then rename local paths to match the new ID."""
|
||||
is_first_publish = old_ad_id is None
|
||||
ad_cfg_orig["id"] = ad_id
|
||||
# Rename referenced images before hashing/saving so the YAML content and
|
||||
# content_hash reflect only image file renames that actually succeeded.
|
||||
@@ -89,9 +90,10 @@ def persist_published_ad(
|
||||
# Update content hash after successful publication
|
||||
# Calculate hash on original config to ensure consistent comparison on restart
|
||||
ad_cfg_orig["content_hash"] = AdPartial.model_validate(ad_cfg_orig).update_content_hash().content_hash
|
||||
ad_cfg_orig["updated_on"] = _misc.now().isoformat(timespec = "seconds")
|
||||
if not ad_cfg.created_on and not ad_cfg.id:
|
||||
ad_cfg_orig["created_on"] = ad_cfg_orig["updated_on"]
|
||||
published_at = _misc.now().isoformat(timespec = "seconds")
|
||||
ad_cfg_orig["updated_on"] = published_at
|
||||
if is_first_publish and not ad_cfg.created_on:
|
||||
ad_cfg_orig["created_on"] = published_at
|
||||
|
||||
# Increment repost_count only for REPLACE operations (actual reposts)
|
||||
if mode == AdUpdateStrategy.REPLACE:
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
|
||||
"""Tests for publishing persistence functionality."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
@@ -144,6 +145,90 @@ class TestLogLocalPathRenameResult:
|
||||
assert mock_warning.called == expect_warning
|
||||
|
||||
|
||||
class TestPersistPublishedAdTimestamps:
|
||||
"""Tests for timestamp persistence in persist_published_ad."""
|
||||
|
||||
@staticmethod
|
||||
def _make_ad_cfg_orig(*, created_on:str | None = None) -> dict[str, Any]:
|
||||
"""Minimal ad_cfg_orig with required fields for AdPartial validation."""
|
||||
ad_cfg_orig:dict[str, Any] = {
|
||||
"title": "Test Ad Title",
|
||||
"description": "Test description for the ad listing.",
|
||||
"type": "OFFER",
|
||||
"category": "160",
|
||||
}
|
||||
if created_on is not None:
|
||||
ad_cfg_orig["created_on"] = created_on
|
||||
return ad_cfg_orig
|
||||
|
||||
def test_first_publish_sets_created_on_when_ad_already_has_id(self) -> None:
|
||||
"""First publish sets created_on based on old_ad_id, not current ad_cfg.id."""
|
||||
ad = _make_min_ad()
|
||||
ad.id = 12345
|
||||
ad_cfg_orig = self._make_ad_cfg_orig()
|
||||
cfg = _make_config()
|
||||
published_at = datetime(2026, 7, 4, 12, 34, 56, tzinfo = timezone.utc)
|
||||
|
||||
with (
|
||||
patch("kleinanzeigen_bot.local_path_renaming.rename_referenced_local_image_files_after_id_change",
|
||||
return_value = _make_image_rename_result()),
|
||||
patch("kleinanzeigen_bot.local_path_renaming.rename_local_ad_file_and_folder_after_id_change",
|
||||
return_value = _local_path_renaming.LocalPathRenameResult(
|
||||
ad_file = Path("test.yaml"),
|
||||
file_status = RenameStatus.SAME,
|
||||
folder_status = RenameStatus.SAME,
|
||||
)),
|
||||
patch("kleinanzeigen_bot.utils.dicts.save_dict"),
|
||||
patch("kleinanzeigen_bot.utils.misc.now", return_value = published_at),
|
||||
):
|
||||
publishing_persistence.persist_published_ad(
|
||||
ad_file = "test.yaml",
|
||||
ad_cfg = ad,
|
||||
ad_cfg_orig = ad_cfg_orig,
|
||||
old_ad_id = None,
|
||||
ad_id = 12345,
|
||||
mode = AdUpdateStrategy.REPLACE,
|
||||
config = cfg,
|
||||
)
|
||||
|
||||
assert ad_cfg_orig["created_on"] == "2026-07-04T12:34:56+00:00"
|
||||
assert ad_cfg_orig["updated_on"] == "2026-07-04T12:34:56+00:00"
|
||||
|
||||
def test_update_preserves_existing_created_on(self) -> None:
|
||||
"""Updating an existing ad keeps the original created_on value."""
|
||||
ad = _make_min_ad()
|
||||
ad.id = 12345
|
||||
ad.created_on = datetime(2024, 1, 2, 3, 4, 5, tzinfo = timezone.utc)
|
||||
ad_cfg_orig = self._make_ad_cfg_orig(created_on = "2024-01-02T03:04:05")
|
||||
cfg = _make_config()
|
||||
updated_at = datetime(2026, 7, 4, 12, 34, 56, tzinfo = timezone.utc)
|
||||
|
||||
with (
|
||||
patch("kleinanzeigen_bot.local_path_renaming.rename_referenced_local_image_files_after_id_change",
|
||||
return_value = _make_image_rename_result()),
|
||||
patch("kleinanzeigen_bot.local_path_renaming.rename_local_ad_file_and_folder_after_id_change",
|
||||
return_value = _local_path_renaming.LocalPathRenameResult(
|
||||
ad_file = Path("test.yaml"),
|
||||
file_status = RenameStatus.SAME,
|
||||
folder_status = RenameStatus.SAME,
|
||||
)),
|
||||
patch("kleinanzeigen_bot.utils.dicts.save_dict"),
|
||||
patch("kleinanzeigen_bot.utils.misc.now", return_value = updated_at),
|
||||
):
|
||||
publishing_persistence.persist_published_ad(
|
||||
ad_file = "test.yaml",
|
||||
ad_cfg = ad,
|
||||
ad_cfg_orig = ad_cfg_orig,
|
||||
old_ad_id = 12345,
|
||||
ad_id = 12345,
|
||||
mode = AdUpdateStrategy.MODIFY,
|
||||
config = cfg,
|
||||
)
|
||||
|
||||
assert ad_cfg_orig["created_on"] == "2024-01-02T03:04:05"
|
||||
assert ad_cfg_orig["updated_on"] == "2026-07-04T12:34:56+00:00"
|
||||
|
||||
|
||||
class TestPersistPublishedAdPriceReductionCount:
|
||||
"""Tests for price_reduction_count persistence in persist_published_ad."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user