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:
pyr0ball 2026-04-27 12:27:16 -07:00
parent 90d30167f8
commit 08aa019439
2 changed files with 63 additions and 0 deletions

View file

@ -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"}

View file

@ -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()