# retoor import json import pytest from devplacepy.database import get_table, init_db from devplacepy.database.sync import claim_link, load_candidates from devplacepy.services.sync.options import SyncOptions from devplacepy.services.sync.selection import Selector from devplacepy.services.sync.spec import LIMITS, SKIP_MIN_COMMENTS ORIGIN = "selection.example.org" @pytest.fixture def world(local_db): init_db() get_table("sync_candidates").delete() get_table("sync_links").delete(origin=ORIGIN) yield get_table("sync_candidates").delete() get_table("sync_links").delete(origin=ORIGIN) def options(**extra): return SyncOptions(min_comments=2, **extra) def user(uid): return {"origin_uid": uid, "username": f"user{uid}", "created_at": "2026-01-01T00:00:00Z"} def post(uid, author="a"): return {"origin_uid": uid, "author_uid": author, "content": f"post body {uid} long enough", "created_at": "2026-01-02T00:00:00Z"} def comment(uid, on, author="b", parent=None): record = {"origin_uid": uid, "post_uid": on, "author_uid": author, "content": f"comment {uid}", "created_at": "2026-01-03T00:00:00Z"} if parent: record["parent_uid"] = parent return record def run(records, persist=True, **extra): selector = Selector(ORIGIN, options(**extra), persist=persist) return selector, *selector.select(records) def test_a_post_below_the_minimum_is_parked_with_its_comments_and_authors(world): selector, kept, skipped = run({"users": [user("a"), user("b")], "posts": [post("p1")], "comments": [comment("c1", "p1")]}) assert kept["posts"] == [] and kept["comments"] == [] and skipped[SKIP_MIN_COMMENTS] == 1 assert selector.parked == 1 stored = json.loads(load_candidates(ORIGIN)[0]["record"]) assert stored["post"]["origin_uid"] == "p1" and [item["origin_uid"] for item in stored["comments"]] == ["c1"] assert {item["origin_uid"] for item in stored["users"]} == {"a", "b"} def test_comments_arriving_in_a_later_run_complete_the_post(world): run({"users": [user("a"), user("b")], "posts": [post("p1")], "comments": [comment("c1", "p1")]}) selector, kept, skipped = run({"users": [user("b")], "posts": [], "comments": [comment("c2", "p1")]}) assert selector.revived == 1 assert [item["origin_uid"] for item in kept["posts"]] == ["p1"] assert [item["origin_uid"] for item in kept["comments"]] == ["c1", "c2"] assert {item["origin_uid"] for item in kept["users"]} == {"a", "b"} assert not skipped and load_candidates(ORIGIN) == [] def test_a_repeated_comment_is_counted_once(world): run({"users": [user("a"), user("b")], "posts": [post("p1")], "comments": [comment("c1", "p1")]}) selector, kept, skipped = run({"users": [], "posts": [], "comments": [comment("c1", "p1")]}) assert kept["posts"] == [] and skipped[SKIP_MIN_COMMENTS] == 1 assert len(load_candidates(ORIGIN)) == 1 def test_reply_order_is_kept_parent_before_reply(world): run({"users": [user("a"), user("b")], "posts": [post("p1")], "comments": [comment("c1", "p1")]}) _, kept, _ = run({"users": [], "posts": [], "comments": [comment("c2", "p1", parent="c1")]}) assert [item["origin_uid"] for item in kept["comments"]] == ["c1", "c2"] def test_a_dry_run_neither_reads_nor_writes_candidates(world): selector, _, skipped = run({"users": [user("a")], "posts": [post("p1")], "comments": []}, persist=False) assert skipped[SKIP_MIN_COMMENTS] == 1 and selector.parked == 0 and load_candidates(ORIGIN) == [] def test_a_known_post_is_never_parked_or_dropped(world): claim_link(ORIGIN, "posts", "p9", "local-9", "x", "2026-01-01T00:00:00+00:00") selector, kept, skipped = run({"users": [user("a")], "posts": [post("p9")], "comments": []}) assert [item["origin_uid"] for item in kept["posts"]] == ["p9"] and not skipped and selector.parked == 0 def test_the_candidate_list_is_capped_per_origin(world, monkeypatch): from dataclasses import replace from devplacepy.services.sync import selection monkeypatch.setattr(selection, "LIMITS", replace(LIMITS, candidate_max_per_origin=2)) selector, _, _ = run({"users": [user("a")], "posts": [post("p1"), post("p2"), post("p3")], "comments": []}) assert selector.parked == 2 and len(load_candidates(ORIGIN)) == 2 def test_an_oversize_candidate_is_dropped_not_stored(world, monkeypatch): from dataclasses import replace from devplacepy.services.sync import selection monkeypatch.setattr(selection, "LIMITS", replace(LIMITS, candidate_max_bytes=50)) selector, _, skipped = run({"users": [user("a")], "posts": [post("p1")], "comments": []}) assert skipped[SKIP_MIN_COMMENTS] == 1 and selector.parked == 0 def test_expired_candidates_are_not_revived(world): run({"users": [user("a")], "posts": [post("p1")], "comments": []}) get_table("sync_candidates").update({"uid": load_candidates(ORIGIN)[0]["uid"], "expires_at": "2000-01-01T00:00:00+00:00"}, ["uid"]) selector, _, _ = run({"users": [], "posts": [], "comments": []}) assert selector.revived == 0 and load_candidates(ORIGIN) == [] def test_other_filters_stay_final_and_clear_the_candidate(world): run({"users": [user("a")], "posts": [post("p1")], "comments": []}) selector, kept, skipped = run({"users": [user("a")], "posts": [], "comments": []}, topics=("devlog",)) from devplacepy.services.sync.spec import SKIP_TOPIC assert kept["posts"] == [] and skipped[SKIP_TOPIC] == 1 and load_candidates(ORIGIN) == []