Files
devplacepy/tests/api/sync/deletions.py
T
retoorandClaude Sonnet 5.5 02ab747cd0
DevPlace CI / test (push) Failing after 44m45s
Code Farm: per-plot auto-replant pause and crop-lock check; community sync and node work
Continuous Delivery could never be turned off once a crop was planted. Add a
one-shot per-plot "stop after harvest" flag (POST /game/plot/stop, Devii tool
game_plot_stop, game_plots.stop_after_harvest), cleared by the next plant,
autoreplant claim or Refactor. Autoreplant now also respects crop locks through
the shared _crop_lock predicate. Plan and measurements in happyplanting.md.

Also includes the pending community sync, node network and related work in the
tree; the full suite passed (4999) with all of it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 02:23:43 +02:00

110 lines
6.0 KiB
Python

# retoor <retoor@molodetz.nl>
from devplacepy.database import get_table
from tests.api.sync.conftest import comment_record, post_record, sync_ok, unique, user_record
def listing(client, admin, origin, **params):
return client.get("/sync/deletions", params={"origin": origin, **params}, headers=admin["headers"])
def ack(client, admin, origin, items):
return client.post("/sync/deletions/ack", json={"origin": origin, "items": items}, headers=admin["headers"])
def seed(client, admin, origin):
sync_ok(client, admin, "users", origin, [user_record("u1", unique("del"))])
sync_ok(client, admin, "posts", origin, [post_record("p1", "u1"), post_record("p2", "u1"), post_record("p3", "u1")])
sync_ok(client, admin, "comments", origin, [comment_record("c1", "p1", "u1")])
def local(origin, kind, origin_uid):
return get_table("sync_links").find_one(origin=origin, kind=kind, origin_uid=origin_uid)["local_uid"]
def test_authz(client, admin):
from tests.api.sync.conftest import make_account
assert client.get("/sync/deletions", params={"origin": "a.b"}).status_code == 401
assert listing(client, make_account("Member"), "a.b").status_code == 403
assert client.get("/sync/deletions", headers=admin["headers"]).status_code == 422
def test_nothing_is_listed_for_a_clean_origin_and_sync_own_deletions_never_are(client, admin, origin):
seed(client, admin, origin)
assert listing(client, admin, origin).json()["items"] == []
sync_ok(client, admin, "posts", origin, [post_record("p1", "u1", updated_at="2025-05-01T00:00:00Z", deleted=True)])
assert listing(client, admin, origin).json()["items"] == []
def test_a_member_deletion_a_system_deletion_and_a_purge_are_told_apart(client, admin, origin):
seed(client, admin, origin)
get_table("posts").update({"uid": local(origin, "posts", "p1"), "deleted_at": "2026-01-01T00:00:00+00:00", "deleted_by": "someone"}, ["uid"])
get_table("posts").update({"uid": local(origin, "posts", "p2"), "deleted_at": "2026-01-02T00:00:00+00:00", "deleted_by": "system"}, ["uid"])
purged_uid = local(origin, "posts", "p3")
get_table("posts").delete(uid=purged_uid)
body = listing(client, admin, origin).json()
found = {(item["kind"], item["origin_uid"]): item["by"] for item in body["items"]}
assert found == {("posts", "p1"): "member", ("posts", "p2"): "system", ("posts", "p3"): "purged"}
assert body["count"] == 3 and body["has_more"] is False
def test_paging_with_a_cursor_covers_every_entry_once(client, admin, origin):
seed(client, admin, origin)
for origin_uid in ("p1", "p2", "p3"):
get_table("posts").update({"uid": local(origin, "posts", origin_uid), "deleted_at": "2026-01-01T00:00:00+00:00", "deleted_by": "m"}, ["uid"])
get_table("comments").update({"uid": local(origin, "comments", "c1"), "deleted_at": "2026-01-01T00:00:00+00:00", "deleted_by": "m"}, ["uid"])
seen, cursor = [], ""
while True:
body = listing(client, admin, origin, limit=2, cursor=cursor).json()
seen += [(item["kind"], item["origin_uid"]) for item in body["items"]]
cursor = body["next_cursor"]
if not body["has_more"]:
break
assert sorted(seen) == [("comments", "c1"), ("posts", "p1"), ("posts", "p2"), ("posts", "p3")]
assert listing(client, admin, origin, cursor="zz").status_code == 400
def test_other_origins_are_never_listed(client, admin, origin):
seed(client, admin, origin)
get_table("posts").update({"uid": local(origin, "posts", "p1"), "deleted_at": "2026-01-01T00:00:00+00:00", "deleted_by": "m"}, ["uid"])
assert listing(client, admin, f"{unique('x')}.example.org").json()["items"] == []
def test_acknowledging_stops_the_listing_and_quiets_the_resend(client, admin, origin):
seed(client, admin, origin)
purged = local(origin, "posts", "p3")
get_table("posts").delete(uid=purged)
resend = sync_ok(client, admin, "posts", origin, [post_record("p3", "u1", updated_at="2025-09-01T00:00:00Z")])["results"][0]
assert resend["status"] == "rejected" and resend["reason"] == "purged_locally"
response = ack(client, admin, origin, [{"kind": "posts", "origin_uid": "p3"}])
assert response.status_code == 200 and response.json()["acknowledged"] == 1
assert listing(client, admin, origin).json()["items"] == []
again = sync_ok(client, admin, "posts", origin, [post_record("p3", "u1", updated_at="2025-10-01T00:00:00Z")])["results"][0]
assert again["status"] == "unchanged" and again["reason"] == "acknowledged_deleted"
assert ack(client, admin, origin, [{"kind": "posts", "origin_uid": "p3"}]).json()["acknowledged"] == 0
def test_a_locally_deleted_row_after_ack_answers_acknowledged(client, admin, origin):
seed(client, admin, origin)
get_table("posts").update({"uid": local(origin, "posts", "p1"), "deleted_at": "2026-01-01T00:00:00+00:00", "deleted_by": "m"}, ["uid"])
ack(client, admin, origin, [{"kind": "posts", "origin_uid": "p1"}])
result = sync_ok(client, admin, "posts", origin, [post_record("p1", "u1", content="edited after local delete", updated_at="2025-09-01T00:00:00Z")])["results"][0]
assert result["status"] == "unchanged" and result["reason"] == "acknowledged_deleted"
def test_ack_validation_and_audit(client, admin, origin):
assert ack(client, admin, origin, []).status_code == 422
assert ack(client, admin, origin, [{"kind": "gists", "origin_uid": "x"}]).status_code == 422
assert ack(client, admin, origin, [{"kind": "posts", "origin_uid": "bad id"}]).status_code == 422
assert ack(client, admin, origin, [{"kind": "posts", "origin_uid": "unknown-one"}]).json()["acknowledged"] == 0
rows = list(get_table("audit_log").find(event_key="sync.deletions.ack", actor_uid=admin["uid"]))
assert rows
def test_a_purged_user_is_listed(client, admin, origin):
seed(client, admin, origin)
get_table("users").delete(uid=local(origin, "users", "u1"))
items = listing(client, admin, origin).json()["items"]
assert ("users", "u1", "purged") in {(item["kind"], item["origin_uid"], item["by"]) for item in items}