134 lines
4.9 KiB
Python
134 lines
4.9 KiB
Python
# retoor <retoor@molodetz.nl>
|
|||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from devplacepy.database import get_setting, get_table, init_db, set_setting
|
||
|
|
from devplacepy.services.community.runner import TRIGGER_CLI, CommunityRunner
|
||
|
|
from devplacepy.services.community.store import store
|
||
|
|
from devplacepy.services.sync import ai
|
||
|
|
from devplacepy.services.sync.options import ALIAS_KEY, SyncOptions
|
||
|
|
from tests.conftest import run_async
|
||
|
|
from tests.unit.services.community.fakes import FakeFetcher, json_community, no_sleep
|
||
|
|
|
||
|
|
ORIGINALS = ("writer0", "writer1", "replier0", "replier1")
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def world(monkeypatch):
|
||
|
|
init_db()
|
||
|
|
keys = ("sync_enabled", "sync_origins", ALIAS_KEY)
|
||
|
|
previous = {key: get_setting(key, "") for key in keys}
|
||
|
|
set_setting("sync_enabled", "1")
|
||
|
|
set_setting("sync_origins", "")
|
||
|
|
set_setting(ALIAS_KEY, "1")
|
||
|
|
for table in ("communities", "community_runs"):
|
||
|
|
get_table(table).delete()
|
||
|
|
get_table("sync_links").delete(origin="forum.test")
|
||
|
|
get_table("sync_identities").delete(origin="forum.test")
|
||
|
|
|
||
|
|
async def unreachable(system, text, timeout):
|
||
|
|
return None
|
||
|
|
|
||
|
|
monkeypatch.setattr(ai, "complete", unreachable)
|
||
|
|
runner = CommunityRunner(fetcher_factory=lambda: FakeFetcher(json_community()), sleep=no_sleep)
|
||
|
|
store.create("forum-test", "https://forum.test/api/posts", "tester", name="forum")
|
||
|
|
run_async(runner.discover("https://forum.test/api/posts", "tester", relearn_slug="forum-test"))
|
||
|
|
yield runner
|
||
|
|
for key, value in previous.items():
|
||
|
|
set_setting(key, value)
|
||
|
|
get_table("sync_identities").delete(origin="forum.test")
|
||
|
|
|
||
|
|
|
||
|
|
def go(runner, dry_run=False):
|
||
|
|
return run_async(runner.run(store.get("forum-test"), dry_run=dry_run, trigger=TRIGGER_CLI))
|
||
|
|
|
||
|
|
|
||
|
|
def synced(kind: str) -> list[dict]:
|
||
|
|
links = get_table("sync_links").find(origin="forum.test", kind=kind)
|
||
|
|
return [row for row in (get_table(kind).find_one(uid=link["local_uid"]) for link in links) if row]
|
||
|
|
|
||
|
|
|
||
|
|
def local_text() -> str:
|
||
|
|
names = [row["username"] for row in synced("users")]
|
||
|
|
posts = [f"{row['title']} {row['content']}" for row in synced("posts")]
|
||
|
|
comments = [row["content"] for row in synced("comments")]
|
||
|
|
return " ".join(names + posts + comments).lower()
|
||
|
|
|
||
|
|
|
||
|
|
def enable(**changes):
|
||
|
|
row = store.get("forum-test")
|
||
|
|
store.update(row["uid"], sync_options=SyncOptions.from_row(row).merged(**changes).to_dict())
|
||
|
|
|
||
|
|
|
||
|
|
def test_aliasing_off_by_default_keeps_original_names(world):
|
||
|
|
report = go(world)
|
||
|
|
assert report.status == "ok", report.errors
|
||
|
|
assert "writer0" in local_text()
|
||
|
|
assert get_table("sync_identities").count(origin="forum.test") == 0
|
||
|
|
|
||
|
|
|
||
|
|
def test_alias_run_never_stores_an_original_username(world):
|
||
|
|
enable(alias=True)
|
||
|
|
report = go(world)
|
||
|
|
assert report.status == "ok", report.errors
|
||
|
|
text = local_text()
|
||
|
|
assert not [name for name in ORIGINALS if name in text]
|
||
|
|
assert get_table("sync_identities").count(origin="forum.test") == 4
|
||
|
|
again = go(world)
|
||
|
|
assert again.status == "ok"
|
||
|
|
assert again.pushed["users"].get("created", 0) == 0
|
||
|
|
assert get_table("sync_identities").count(origin="forum.test") == 4
|
||
|
|
|
||
|
|
|
||
|
|
def test_kill_switch_stops_aliased_runs_and_creates_nothing(world):
|
||
|
|
enable(alias=True)
|
||
|
|
set_setting(ALIAS_KEY, "0")
|
||
|
|
report = go(world)
|
||
|
|
assert any("switched off" in error for error in report.errors)
|
||
|
|
assert not synced("posts") and not synced("users")
|
||
|
|
assert get_table("sync_identities").count(origin="forum.test") == 0
|
||
|
|
|
||
|
|
|
||
|
|
def test_dry_run_creates_no_identities_and_no_rows(world):
|
||
|
|
enable(alias=True)
|
||
|
|
users = get_table("users").count()
|
||
|
|
report = go(world, dry_run=True)
|
||
|
|
assert report.status == "dry_run", report.errors
|
||
|
|
assert get_table("sync_identities").count(origin="forum.test") == 0
|
||
|
|
assert get_table("users").count() == users
|
||
|
|
|
||
|
|
|
||
|
|
def test_enabling_alias_after_unaliased_sync_is_refused_at_run(world):
|
||
|
|
assert go(world).status == "ok"
|
||
|
|
enable(alias=True)
|
||
|
|
report = go(world)
|
||
|
|
assert any("already has synced records" in error for error in report.errors)
|
||
|
|
assert get_table("sync_identities").count(origin="forum.test") == 0
|
||
|
|
|
||
|
|
|
||
|
|
def test_selection_skips_are_reported_and_sampling_is_stable(world):
|
||
|
|
enable(sample_percent=30)
|
||
|
|
first = go(world)
|
||
|
|
count = len(synced("posts"))
|
||
|
|
assert first.pushed["skipped"]["sampled_out"] + count >= 10
|
||
|
|
assert 0 < count < 10
|
||
|
|
go(world)
|
||
|
|
assert len(synced("posts")) == count
|
||
|
|
|
||
|
|
|
||
|
|
def test_translation_failure_holds_watermark_and_stores_nothing(world):
|
||
|
|
enable(alias=True, translate_to="nl")
|
||
|
|
row = store.get("forum-test")
|
||
|
|
store.update(row["uid"], language="en")
|
||
|
|
report = go(world)
|
||
|
|
assert not synced("posts")
|
||
|
|
assert report.status == "partial"
|
||
|
|
assert report.pushed["skipped"]["translation_failed"] >= 1
|
||
|
|
assert not store.get("forum-test")["watermark"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_jitter_moves_next_run_within_bounds(world):
|
||
|
|
row = store.get("forum-test")
|
||
|
|
moments = {store.schedule_next(row["uid"], 86400, 40) for _ in range(5)}
|
||
|
|
assert len(moments) > 1
|