forked from retoor/devplacepy
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>
This commit is contained in:
@@ -209,6 +209,27 @@ def test_password_is_stored_but_never_returned(client, admin, fake_world):
|
||||
assert store.get("forum-test")["login_password"] == ""
|
||||
|
||||
|
||||
def test_the_authenticator_secret_is_stored_but_never_returned(client, admin, fake_world):
|
||||
client.post(
|
||||
"/admin/communities",
|
||||
data={"query": "https://forum.test/api/posts", "login_username": "bot", "login_password": "s3cret-pw", "login_totp_secret": "JBSWY3DPEHPK3PXP"},
|
||||
headers=admin["headers"],
|
||||
)
|
||||
row = wait_for(lambda: (found := store.get("forum-test")) and found["status"] == "ready" and found)
|
||||
assert row["login_totp_secret"] == "JBSWY3DPEHPK3PXP"
|
||||
assert store.credentials(row)["totp"] == "JBSWY3DPEHPK3PXP"
|
||||
for url in ("/admin/communities", "/admin/communities/forum-test", "/admin/communities/forum-test/ledger"):
|
||||
for headers in (admin["headers"], {"X-API-KEY": admin["headers"]["X-API-KEY"]}):
|
||||
body = client.get(url, headers=headers)
|
||||
assert "JBSWY3DPEHPK3PXP" not in body.text
|
||||
as_json = client.get("/admin/communities/forum-test", headers=admin["headers"]).json()
|
||||
assert as_json["community"]["has_totp"] is True and "login_totp_secret" not in as_json["community"]
|
||||
client.post("/admin/communities/forum-test/update", data={"interval_seconds": "7200", "login_username": "other"}, headers=admin["headers"])
|
||||
assert store.get("forum-test")["login_totp_secret"] == "JBSWY3DPEHPK3PXP"
|
||||
client.post("/admin/communities/forum-test/update", data={"interval_seconds": "7200", "clear_login": "on"}, headers=admin["headers"])
|
||||
assert store.get("forum-test")["login_totp_secret"] == ""
|
||||
|
||||
|
||||
def test_devii_tools_are_admin_only_and_delete_needs_confirmation():
|
||||
from devplacepy.services.devii.actions.catalog.community import COMMUNITY_ACTIONS
|
||||
from devplacepy.services.devii.actions.dispatcher import CONFIRM_REQUIRED
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import json
|
||||
|
||||
from devplacepy.database import get_table
|
||||
from devplacepy.services.community.files import files
|
||||
from devplacepy.services.community.store import store
|
||||
from tests.api.admin.communities.index import add_ready, admin, client, fake_world, make_account # noqa: F401
|
||||
|
||||
|
||||
def save(client, admin, schema_text, slug="forum-test"):
|
||||
return client.post(f"/admin/communities/{slug}/schema", data={"schema": schema_text}, headers=admin["headers"])
|
||||
|
||||
|
||||
def test_authz(client, admin, fake_world):
|
||||
add_ready(client, admin)
|
||||
member = make_account()
|
||||
assert client.get("/admin/communities/forum-test/schema", headers={"Accept": "application/json"}).status_code in (401, 303)
|
||||
assert client.get("/admin/communities/forum-test/schema", headers=member["headers"]).status_code == 403
|
||||
assert client.post("/admin/communities/forum-test/schema", data={"schema": "{}"}, headers=member["headers"]).status_code == 403
|
||||
assert client.post("/admin/communities/forum-test/schema/restore", headers=member["headers"]).status_code == 403
|
||||
|
||||
|
||||
def test_the_page_shows_the_schema_and_never_a_credential(client, admin, fake_world):
|
||||
add_ready(client, admin)
|
||||
store.update(store.get("forum-test")["uid"], login_password="topsecret-pw", login_totp_secret="TOTPSECRETVALUE")
|
||||
body = client.get("/admin/communities/forum-test/schema", headers=admin["headers"]).json()
|
||||
assert json.loads(body["schema_text"])["origin"] == "forum.test" and body["has_previous"] is False
|
||||
page = client.get("/admin/communities/forum-test/schema", headers={"X-API-KEY": admin["headers"]["X-API-KEY"]})
|
||||
assert page.status_code == 200 and "community-schema" in page.text
|
||||
assert "topsecret-pw" not in page.text and "TOTPSECRETVALUE" not in page.text
|
||||
assert "Edit schema" in client.get("/admin/communities/forum-test", headers={"X-API-KEY": admin["headers"]["X-API-KEY"]}).text
|
||||
|
||||
|
||||
def test_a_verified_schema_is_stored_regenerated_and_the_old_one_kept(client, admin, fake_world):
|
||||
row = add_ready(client, admin)
|
||||
schema = json.loads(json.dumps(row["schema"]))
|
||||
schema["feeds"][0]["post"]["fields"]["title"] = ["$.title", "$.id"]
|
||||
response = save(client, admin, json.dumps(schema))
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json()["data"]["posts"] >= 1
|
||||
after = store.get("forum-test")
|
||||
assert after["schema"]["feeds"][0]["post"]["fields"]["title"] == ["$.title", "$.id"]
|
||||
assert after["schema_previous"] == row["schema"] and after["status"] == "ready"
|
||||
assert files.load("forum-test", after["schema"]).SCHEMA["feeds"][0]["post"]["fields"]["title"] == ["$.title", "$.id"]
|
||||
rows = list(get_table("audit_log").find(event_key="admin.community.schema", target_label="forum-test"))
|
||||
assert rows
|
||||
|
||||
|
||||
def test_a_structurally_invalid_schema_is_refused_with_every_reason_and_changes_nothing(client, admin, fake_world):
|
||||
row = add_ready(client, admin)
|
||||
broken = json.loads(json.dumps(row["schema"]))
|
||||
broken["feeds"][0]["type"] = "soap"
|
||||
broken["origin"] = "NOT A HOST"
|
||||
response = save(client, admin, json.dumps(broken))
|
||||
assert response.status_code == 400
|
||||
assert store.get("forum-test")["schema"] == row["schema"]
|
||||
html = client.post("/admin/communities/forum-test/schema", data={"schema": json.dumps(broken)}, headers={"X-API-KEY": admin["headers"]["X-API-KEY"]})
|
||||
assert html.status_code == 400 and "Not saved" in html.text and "feeds[0].type" in html.text and "origin" in html.text
|
||||
assert 'name="schema"' in html.text and "NOT A HOST" in html.text
|
||||
|
||||
|
||||
def test_invalid_json_is_refused(client, admin, fake_world):
|
||||
add_ready(client, admin)
|
||||
html = client.post("/admin/communities/forum-test/schema", data={"schema": "{not json"}, headers={"X-API-KEY": admin["headers"]["X-API-KEY"]})
|
||||
assert html.status_code == 400 and "not valid JSON" in html.text
|
||||
|
||||
|
||||
def test_a_schema_that_collects_nothing_live_is_not_saved(client, admin, fake_world):
|
||||
row = add_ready(client, admin)
|
||||
dead = json.loads(json.dumps(row["schema"]))
|
||||
dead["feeds"][0]["list_path"] = "$.nothing_here"
|
||||
response = client.post("/admin/communities/forum-test/schema", data={"schema": json.dumps(dead)}, headers={"X-API-KEY": admin["headers"]["X-API-KEY"]})
|
||||
assert response.status_code == 422 and "no posts" in response.text and "not saved" in response.text
|
||||
assert store.get("forum-test")["schema"] == row["schema"] and not store.get("forum-test")["schema_previous"]
|
||||
|
||||
|
||||
def test_restore_swaps_back_and_refuses_when_there_is_nothing(client, admin, fake_world):
|
||||
row = add_ready(client, admin)
|
||||
assert client.post("/admin/communities/forum-test/schema/restore", headers=admin["headers"]).status_code == 409
|
||||
changed = json.loads(json.dumps(row["schema"]))
|
||||
changed["feeds"][0]["post"]["fields"]["title"] = ["$.title", "$.id"]
|
||||
assert save(client, admin, json.dumps(changed)).status_code == 200
|
||||
assert client.post("/admin/communities/forum-test/schema/restore", headers=admin["headers"]).status_code == 200
|
||||
restored = store.get("forum-test")
|
||||
assert restored["schema"] == row["schema"] and restored["schema_previous"] == changed
|
||||
assert list(get_table("audit_log").find(event_key="admin.community.schema_restore", target_label="forum-test"))
|
||||
|
||||
|
||||
def test_the_devii_tools_exist_and_the_save_needs_confirmation():
|
||||
from devplacepy.services.devii.actions.catalog.community import COMMUNITY_ACTIONS
|
||||
from devplacepy.services.devii.actions.dispatcher import CONFIRM_REQUIRED
|
||||
|
||||
tools = {action.name: action for action in COMMUNITY_ACTIONS}
|
||||
assert tools["community_schema_get"].requires_admin and tools["community_schema_set"].requires_admin
|
||||
assert "community_schema_set" in CONFIRM_REQUIRED
|
||||
assert any(param.name == "confirm" for param in tools["community_schema_set"].params)
|
||||
@@ -28,6 +28,8 @@ def test_admin_overview_shape(client, admin):
|
||||
"nodes_cache_ttl_seconds",
|
||||
"nodes_daily_social_user",
|
||||
"nodes_social_via_nodes",
|
||||
"nodes_allow_unknown_class",
|
||||
"nodes_redundancy_percent",
|
||||
}
|
||||
listed = {row["uid"]: row for row in body["devices"]}
|
||||
assert listed[device["uid"]]["owner_username"] == owner["username"]
|
||||
@@ -40,3 +42,26 @@ def test_html_page_renders(client, admin):
|
||||
assert response.status_code == 200
|
||||
assert "Community nodes" in response.text
|
||||
assert "data-live=\"summary\"" in response.text
|
||||
|
||||
|
||||
def test_overview_carries_circuits_outcomes_lifetime_geo_and_cache(client, admin):
|
||||
from devplacepy.database.nodes import flush_counters, save_circuits
|
||||
|
||||
flush_counters({"outcome:reddit:residential:ok": 3, "outcome:reddit:residential:blocked": 1, "kicked": 2})
|
||||
save_circuits([{"key": "destination:reddit.com", "failures": 9, "state": "open", "opened_at": 4_000_000_000.0}])
|
||||
try:
|
||||
body = client.get("/admin/nodes", headers=admin_headers(admin)).json()
|
||||
row = next(item for item in body["outcomes"] if item["adapter"] == "reddit" and item["asn_class"] == "residential")
|
||||
assert row["ok"] >= 3 and row["blocked"] >= 1 and row["total"] == row["ok"] + row["blocked"] + row["failed"]
|
||||
assert body["lifetime"]["kicked"] >= 2
|
||||
assert not any(name.startswith("outcome:") for name in body["lifetime"])
|
||||
assert body["circuits"][0]["key"] == "destination:reddit.com" and body["circuits"][0]["state"] == "open"
|
||||
assert set(body["geo"]) >= {"available", "path"}
|
||||
assert set(body["cache"]) == {"entries", "bytes"}
|
||||
finally:
|
||||
save_circuits([])
|
||||
|
||||
|
||||
def test_html_shows_the_new_sections_and_the_empty_outcome_state(client, admin):
|
||||
text = client.get("/admin/nodes", headers=admin["headers"]).text
|
||||
assert "Circuit breakers" in text and "Success by address class" in text and "Address database" in text
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from devplacepy.database.nodes import get_device
|
||||
from tests.api.admin.nodes.conftest import admin_headers, make_admin, make_device
|
||||
from tests.api.nodes.conftest import make_member
|
||||
|
||||
|
||||
def test_member_cannot_override(client):
|
||||
member = make_member()
|
||||
device = make_device(member["uid"])
|
||||
response = client.post(f"/admin/nodes/devices/{device['uid']}/class", json={"value": "allow"}, headers=admin_headers(member))
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_override_round_trip_is_visible_in_the_overview(client, admin):
|
||||
owner = make_member()
|
||||
device = make_device(owner["uid"])
|
||||
url = f"/admin/nodes/devices/{device['uid']}/class"
|
||||
response = client.post(url, json={"value": "allow"}, headers=admin_headers(admin))
|
||||
assert response.status_code == 200 and response.json()["data"] == {"uid": device["uid"], "class_override": "allow"}
|
||||
assert get_device(device["uid"])["class_override"] == "allow"
|
||||
listed = {row["uid"]: row for row in client.get("/admin/nodes", headers=admin_headers(admin)).json()["devices"]}
|
||||
assert listed[device["uid"]]["class_override"] == "allow"
|
||||
client.post(url, json={"value": "clear"}, headers=admin_headers(admin))
|
||||
assert not get_device(device["uid"])["class_override"]
|
||||
|
||||
|
||||
def test_only_known_values_are_accepted(client, admin):
|
||||
owner = make_member()
|
||||
device = make_device(owner["uid"])
|
||||
response = client.post(f"/admin/nodes/devices/{device['uid']}/class", json={"value": "deny"}, headers=admin_headers(admin))
|
||||
assert response.status_code in (400, 422)
|
||||
|
||||
|
||||
def test_unknown_device_is_404(client, admin):
|
||||
assert client.post("/admin/nodes/devices/nope/class", json={"value": "allow"}, headers=admin_headers(admin)).status_code == 404
|
||||
|
||||
|
||||
def test_junior_admin_cannot_override_a_senior_admins_device(client):
|
||||
senior, junior = make_admin(), make_admin()
|
||||
device = make_device(senior["uid"])
|
||||
client.post(f"/admin/nodes/devices/{device['uid']}/class", json={"value": "allow"}, headers=admin_headers(junior))
|
||||
assert not get_device(device["uid"]).get("class_override")
|
||||
|
||||
|
||||
def test_the_change_is_audited(client, admin):
|
||||
from devplacepy.database import get_table
|
||||
|
||||
owner = make_member()
|
||||
device = make_device(owner["uid"])
|
||||
client.post(f"/admin/nodes/devices/{device['uid']}/class", json={"value": "allow"}, headers=admin_headers(admin))
|
||||
rows = list(get_table("audit_log").find(event_key="node.device.override", target_uid=device["uid"]))
|
||||
assert rows and rows[0]["new_value"] == "allow"
|
||||
Reference in New Issue
Block a user