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 asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from datetime import date
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from app.core.config import get_settings
|
from app.core.config import get_settings
|
||||||
from app.db.store import Store
|
from app.db.store import Store
|
||||||
from app.services.platforms import get_client
|
from app.services.platforms import get_client
|
||||||
|
from app.services.platforms.reddit_comment import is_nth_weekday, parse_occurrence
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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)
|
all_subs = store.list_campaign_subs(campaign_id)
|
||||||
sub_row = next((s for s in all_subs if s["sub"] == target), {})
|
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)
|
# Dupe guard (opt-out allowed per strategy)
|
||||||
if strategy.supports_dupe_guard() and store.already_posted_this_week(campaign_id, target):
|
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"}
|
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 result["skipped"] is True
|
||||||
assert "Unknown campaign type" in result["reason"]
|
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