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:
@@ -176,3 +176,78 @@ def test_the_dispatch_loop_starts_once_and_stops_with_the_service(service):
|
||||
assert service._dispatch_task is None and first.cancelled()
|
||||
|
||||
run_async(scenario())
|
||||
|
||||
|
||||
def test_a_timed_suspension_tells_the_node_when_it_ends(service):
|
||||
uid = member()
|
||||
device, socket = connected(service, uid)
|
||||
suspend_device(device["uid"], "test", 30)
|
||||
reconcile(service)
|
||||
frame = socket.sent[-1]
|
||||
assert frame["reason"] == "suspended" and frame["until"].startswith("20")
|
||||
|
||||
|
||||
def test_other_kicks_carry_no_until(service):
|
||||
_, socket = connected(service, member())
|
||||
set_setting("nodes_enabled", "0")
|
||||
reconcile(service)
|
||||
assert "until" not in socket.sent[-1]
|
||||
|
||||
|
||||
def test_persist_state_touches_flushes_and_saves_circuits(service):
|
||||
from devplacepy.database import nodes as store
|
||||
|
||||
uid = member()
|
||||
device, _ = connected(service, uid)
|
||||
get_table("node_devices").update({"uid": device["uid"], "last_seen_at": "2000-01-01T00:00:00+00:00"}, ["uid"])
|
||||
store.flush_counters({})
|
||||
service.counters.inc("kicked")
|
||||
service.counters.observe("reddit", "residential", "ok")
|
||||
service.breaker.failure(("destination", "reddit.com"))
|
||||
service.persist_state(force=True)
|
||||
assert store.get_device(device["uid"])["last_seen_at"] > "2020"
|
||||
lifetime = store.lifetime_counters()
|
||||
assert lifetime["kicked"] >= 1 and lifetime["outcome:reddit:residential:ok"] >= 1
|
||||
assert service.counters.take_pending() == {}
|
||||
assert not service.breaker.dirty
|
||||
|
||||
|
||||
def test_a_failed_flush_keeps_the_deltas(service, monkeypatch):
|
||||
from devplacepy.services.nodes import service as module
|
||||
|
||||
service.counters.inc("kicked")
|
||||
|
||||
def boom(deltas):
|
||||
raise RuntimeError("disk full")
|
||||
|
||||
monkeypatch.setattr(module, "flush_counters", boom)
|
||||
with pytest.raises(RuntimeError):
|
||||
service.persist_state(force=True)
|
||||
assert service.counters.take_pending() == {"kicked": 1}
|
||||
|
||||
|
||||
def test_persist_state_respects_the_intervals(service, monkeypatch):
|
||||
from devplacepy.services.nodes import service as module
|
||||
|
||||
connected(service, member())
|
||||
calls = []
|
||||
monkeypatch.setattr(module, "touch_devices", lambda uids: calls.append(list(uids)))
|
||||
service.persist_state(force=True)
|
||||
service.persist_state()
|
||||
assert len(calls) == 1
|
||||
|
||||
|
||||
def test_open_circuits_survive_a_restart(service):
|
||||
from devplacepy.database.nodes import load_circuits
|
||||
|
||||
for _ in range(service.breaker.failures):
|
||||
service.breaker.failure(("destination", "reddit.com"))
|
||||
service.persist_state()
|
||||
assert [row["key"] for row in load_circuits()] == ["destination:reddit.com"]
|
||||
fresh = NodesService()
|
||||
run_async(fresh.on_enable())
|
||||
assert fresh.breaker.open_count() == 1
|
||||
fresh.breaker.success(("destination", "reddit.com"))
|
||||
fresh.persist_state()
|
||||
assert load_circuits() == []
|
||||
run_async(fresh.on_disable())
|
||||
|
||||
Reference in New Issue
Block a user