feat: add occurrence check to poster before strategy dispatch
Parse the occurrence field from sub_row and skip execution when today is not the nth weekday specified (e.g. first_sunday). Check runs after sub_row fetch but before dupe guard. Two new tests confirm skip and pass paths using patched date.today in app.services.poster.
This commit is contained in:
parent
90d30167f8
commit
08aa019439
2 changed files with 63 additions and 0 deletions
|
|
@ -6,11 +6,13 @@ from __future__ import annotations
|
|||
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
from app.core.config import get_settings
|
||||
from app.db.store import Store
|
||||
from app.services.platforms import get_client
|
||||
from app.services.platforms.reddit_comment import is_nth_weekday, parse_occurrence
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -36,6 +38,18 @@ def _run_post(db_path: str, campaign_id: int, target: str,
|
|||
all_subs = store.list_campaign_subs(campaign_id)
|
||||
sub_row = next((s for s in all_subs if s["sub"] == target), {})
|
||||
|
||||
# Occurrence check — skip if not the right week of the month
|
||||
occurrence_str = (sub_row or {}).get("occurrence")
|
||||
parsed = parse_occurrence(occurrence_str)
|
||||
if parsed is not None:
|
||||
weekday, n = parsed
|
||||
if not is_nth_weekday(date.today(), weekday, n):
|
||||
logger.info(
|
||||
"Skipping %s / %s — not occurrence %s today",
|
||||
campaign_id, target, occurrence_str,
|
||||
)
|
||||
return {"skipped": True, "reason": f"occurrence {occurrence_str} not today"}
|
||||
|
||||
# Dupe guard (opt-out allowed per strategy)
|
||||
if strategy.supports_dupe_guard() and store.already_posted_this_week(campaign_id, target):
|
||||
return {"skipped": True, "reason": f"already posted to {target!r} this week"}
|
||||
|
|
|
|||
|
|
@ -113,3 +113,52 @@ def test_run_post_unknown_type_skips(tmp_path):
|
|||
|
||||
assert result["skipped"] is True
|
||||
assert "Unknown campaign type" in result["reason"]
|
||||
|
||||
|
||||
def test_occurrence_skip(tmp_path):
|
||||
"""When occurrence is 'first_sunday' and today is NOT the first Sunday, post is skipped."""
|
||||
db = str(tmp_path / "test.db")
|
||||
# 2026-04-19 is a Sunday but the 3rd Sunday of April 2026
|
||||
mock_store = _make_store(
|
||||
campaign_type="reddit_comment",
|
||||
subs=[{"sub": "selfhosted", "active": 1, "occurrence": "first_sunday"}],
|
||||
)
|
||||
mock_strategy = MagicMock()
|
||||
mock_strategy.supports_dupe_guard.return_value = False
|
||||
|
||||
with patch("app.services.poster.Store", return_value=mock_store):
|
||||
with patch("app.services.poster.get_client", return_value=mock_strategy):
|
||||
with patch("app.services.poster.date") as mock_date:
|
||||
from datetime import date as real_date
|
||||
mock_date.today.return_value = real_date(2026, 4, 19)
|
||||
result = _run_post(db, campaign_id=1, target="selfhosted", triggered_by="scheduler")
|
||||
|
||||
assert result["skipped"] is True
|
||||
assert "occurrence" in result["reason"]
|
||||
mock_strategy.execute.assert_not_called()
|
||||
|
||||
|
||||
def test_occurrence_passes(tmp_path):
|
||||
"""When occurrence is 'first_sunday' and today IS the first Sunday, post proceeds."""
|
||||
db = str(tmp_path / "test.db")
|
||||
# 2026-05-03 is the first Sunday of May 2026
|
||||
mock_store = _make_store(
|
||||
campaign_type="reddit_comment",
|
||||
subs=[{"sub": "selfhosted", "active": 1, "occurrence": "first_sunday"}],
|
||||
)
|
||||
mock_result = MagicMock()
|
||||
mock_result.url = "https://reddit.com/r/selfhosted/comments/abc/"
|
||||
|
||||
mock_strategy = MagicMock()
|
||||
mock_strategy.supports_dupe_guard.return_value = False
|
||||
mock_strategy.execute.return_value = mock_result
|
||||
|
||||
with patch("app.services.poster.Store", return_value=mock_store):
|
||||
with patch("app.services.poster.get_client", return_value=mock_strategy):
|
||||
with patch("app.services.poster.date") as mock_date:
|
||||
from datetime import date as real_date
|
||||
mock_date.today.return_value = real_date(2026, 5, 3)
|
||||
result = _run_post(db, campaign_id=1, target="selfhosted", triggered_by="scheduler")
|
||||
|
||||
assert result.get("skipped") is not True
|
||||
mock_strategy.execute.assert_called_once()
|
||||
|
|
|
|||
Loading…
Reference in a new issue