Code Farm: per-plot auto-replant pause and crop-lock check; community sync and node work
DevPlace CI / test (push) Failing after 44m45s
DevPlace CI / test (push) Failing after 44m45s
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>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import base64
|
||||
from pathlib import Path
|
||||
|
||||
from devplacepy.config import ATTACHMENTS_DIR
|
||||
from devplacepy.database import get_table
|
||||
from tests.api.sync.conftest import PNG_1X1, attachment_record, seed_world, sync_ok
|
||||
|
||||
|
||||
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_an_attachment_is_stored_and_linked_to_its_target(client, admin, origin):
|
||||
seed_world(client, admin, origin)
|
||||
result = sync_ok(client, admin, "attachments", origin, [attachment_record()])["results"][0]
|
||||
assert result["status"] == "created"
|
||||
row = get_table("attachments").find_one(uid=result["local_uid"])
|
||||
assert (row["target_type"], row["target_uid"]) == ("post", local(origin, "posts", "p1"))
|
||||
assert row["user_uid"] == local(origin, "users", "u1") and row["mime_type"] == "image/png"
|
||||
assert (Path(ATTACHMENTS_DIR) / row["directory"] / row["stored_name"]).read_bytes() == base64.b64decode(PNG_1X1)
|
||||
assert row["created_at"].startswith("2025-01-05")
|
||||
|
||||
|
||||
def test_replay_is_unchanged_and_a_content_change_is_refused(client, admin, origin):
|
||||
seed_world(client, admin, origin)
|
||||
sync_ok(client, admin, "attachments", origin, [attachment_record()])
|
||||
assert sync_ok(client, admin, "attachments", origin, [attachment_record()])["results"][0]["status"] == "unchanged"
|
||||
other = base64.b64encode(b"GIF89a" + b"x" * 40).decode()
|
||||
changed = sync_ok(client, admin, "attachments", origin, [attachment_record(content=other, filename="b.gif", updated_at="2025-02-01T00:00:00Z")])["results"][0]
|
||||
assert changed["status"] == "rejected" and changed["reason"] == "immutable"
|
||||
|
||||
|
||||
def test_tombstone_soft_deletes_and_restores(client, admin, origin):
|
||||
seed_world(client, admin, origin)
|
||||
created = sync_ok(client, admin, "attachments", origin, [attachment_record()])["results"][0]
|
||||
gone = attachment_record(updated_at="2025-02-01T00:00:00Z", deleted=True)
|
||||
assert sync_ok(client, admin, "attachments", origin, [gone])["results"][0]["status"] == "updated"
|
||||
assert get_table("attachments").find_one(uid=created["local_uid"])["deleted_by"] == "sync"
|
||||
back = attachment_record(updated_at="2025-03-01T00:00:00Z")
|
||||
sync_ok(client, admin, "attachments", origin, [back])
|
||||
assert get_table("attachments").find_one(uid=created["local_uid"])["deleted_at"] is None
|
||||
|
||||
|
||||
def test_validation_and_type_rules(client, admin, origin):
|
||||
seed_world(client, admin, origin)
|
||||
body = sync_ok(
|
||||
client, admin, "attachments", origin,
|
||||
[
|
||||
attachment_record("a1", content="not base64!!"),
|
||||
attachment_record("a2", content=""),
|
||||
attachment_record("a3", filename="run.exe", content=base64.b64encode(b"MZ" + b"0" * 10).decode()),
|
||||
attachment_record("a4", filename="../../etc/passwd.png"),
|
||||
],
|
||||
)
|
||||
statuses = [item["status"] for item in body["results"]]
|
||||
assert statuses[:3] == ["rejected", "rejected", "rejected"]
|
||||
stored = get_table("attachments").find_one(uid=body["results"][3].get("local_uid") or "x")
|
||||
assert statuses[3] == "created" and stored["original_filename"] == "passwd.png"
|
||||
|
||||
|
||||
def test_an_oversize_file_is_refused(client, admin, origin):
|
||||
from devplacepy.services.sync.spec import LIMITS
|
||||
|
||||
seed_world(client, admin, origin)
|
||||
big = base64.b64encode(b"\x00" * (LIMITS.max_attachment_bytes + 1)).decode()
|
||||
result = sync_ok(client, admin, "attachments", origin, [attachment_record(content=big)])["results"][0]
|
||||
assert result["status"] == "rejected" and "exceeds" in result["errors"][0]
|
||||
|
||||
|
||||
def test_an_unknown_target_waits_and_is_released_when_the_target_arrives(client, admin, origin):
|
||||
from tests.api.sync.conftest import post_record, user_record
|
||||
|
||||
sync_ok(client, admin, "users", origin, [user_record("u1", "attuser1")])
|
||||
waiting = sync_ok(client, admin, "attachments", origin, [attachment_record(target="p1")])["results"][0]
|
||||
assert waiting["status"] == "deferred"
|
||||
body = sync_ok(client, admin, "posts", origin, [post_record("p1", "u1")])
|
||||
assert body["released"] == {"created": 1}
|
||||
assert get_table("attachments").count(target_uid=local(origin, "posts", "p1")) == 1
|
||||
Reference in New Issue
Block a user