forked from retoor/devplacepy
ipdate
This commit is contained in:
@@ -2,10 +2,17 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from devplacepy.database import invalidate_admins_cache
|
||||
from devplacepy.database import invalidate_admins_cache, set_setting
|
||||
from devplacepy.services.devii.errors import ToolInputError
|
||||
from devplacepy.services.devii.tasks import limits
|
||||
from devplacepy.services.devii.tasks.context import task_run_scope
|
||||
from devplacepy.services.devii.tasks.controller import TaskController
|
||||
from devplacepy.services.devii.tasks.guards import AutomationDenied
|
||||
from devplacepy.services.devii.tasks.guards import (
|
||||
REASON_CREATE_QUOTA,
|
||||
REASON_NESTED,
|
||||
REASON_NOT_A_USER,
|
||||
AutomationDenied,
|
||||
)
|
||||
from devplacepy.services.devii.tasks.schedule import now_utc, to_iso
|
||||
from devplacepy.services.devii.tasks.store import TaskStore, claim, due_rows
|
||||
from devplacepy.utils import generate_uid
|
||||
@@ -19,6 +26,7 @@ def _account(local_db, role):
|
||||
"username": f"store-{uid[-10:]}",
|
||||
"role": role,
|
||||
"deleted_at": None,
|
||||
"created_at": to_iso(now_utc()),
|
||||
}
|
||||
)
|
||||
invalidate_admins_cache()
|
||||
@@ -43,39 +51,116 @@ def _record(**overrides):
|
||||
return record
|
||||
|
||||
|
||||
def test_member_cannot_persist_a_task(local_db):
|
||||
def _spend_creation_quota(store, count):
|
||||
for _ in range(count):
|
||||
store.create(_record())
|
||||
|
||||
|
||||
def test_member_can_create_a_task(local_db):
|
||||
store = TaskStore(local_db, "user", _account(local_db, "Member"))
|
||||
record = _record()
|
||||
store.create(record)
|
||||
assert store.get(record["uid"]) is not None
|
||||
|
||||
|
||||
def test_member_creation_stops_at_the_daily_limit(local_db):
|
||||
store = TaskStore(local_db, "user", _account(local_db, "Member"))
|
||||
_spend_creation_quota(store, limits.DEFAULT_MEMBER_CREATE)
|
||||
with pytest.raises(AutomationDenied) as denied:
|
||||
store.create(_record())
|
||||
assert denied.value.reason == REASON_CREATE_QUOTA
|
||||
assert denied.value.retry_at is not None
|
||||
|
||||
|
||||
def test_deleting_a_task_does_not_give_the_creation_slot_back(local_db):
|
||||
store = TaskStore(local_db, "user", _account(local_db, "Member"))
|
||||
records = [_record() for _ in range(limits.DEFAULT_MEMBER_CREATE)]
|
||||
for record in records:
|
||||
store.create(record)
|
||||
for record in records:
|
||||
store.delete(record["uid"])
|
||||
with pytest.raises(AutomationDenied):
|
||||
store.create(_record())
|
||||
|
||||
|
||||
def test_administrator_can_persist_a_task(local_db):
|
||||
def test_administrator_creation_stops_at_its_own_limit(local_db):
|
||||
store = TaskStore(local_db, "user", _account(local_db, "Admin"))
|
||||
_spend_creation_quota(store, limits.DEFAULT_ADMIN_CREATE)
|
||||
with pytest.raises(AutomationDenied) as denied:
|
||||
store.create(_record())
|
||||
assert denied.value.reason == REASON_CREATE_QUOTA
|
||||
|
||||
|
||||
def test_creation_limit_is_configurable(local_db):
|
||||
store = TaskStore(local_db, "user", _account(local_db, "Member"))
|
||||
set_setting(limits.FIELD_MEMBER_CREATE, "1")
|
||||
try:
|
||||
store.create(_record())
|
||||
with pytest.raises(AutomationDenied):
|
||||
store.create(_record())
|
||||
finally:
|
||||
set_setting(limits.FIELD_MEMBER_CREATE, str(limits.DEFAULT_MEMBER_CREATE))
|
||||
|
||||
|
||||
def test_guest_cannot_persist_a_task(local_db):
|
||||
store = TaskStore(local_db, "guest", "guest-cookie")
|
||||
with pytest.raises(AutomationDenied) as denied:
|
||||
store.create(_record())
|
||||
assert denied.value.reason == REASON_NOT_A_USER
|
||||
|
||||
|
||||
def test_member_cannot_create_a_task_from_inside_a_task_run(local_db):
|
||||
store = TaskStore(local_db, "user", _account(local_db, "Member"))
|
||||
with task_run_scope():
|
||||
with pytest.raises(AutomationDenied) as denied:
|
||||
store.create(_record())
|
||||
assert denied.value.reason == REASON_NESTED
|
||||
|
||||
|
||||
def test_administrator_can_create_a_task_from_inside_a_task_run(local_db):
|
||||
store = TaskStore(local_db, "user", _account(local_db, "Admin"))
|
||||
record = _record()
|
||||
with task_run_scope():
|
||||
store.create(record)
|
||||
assert store.get(record["uid"]) is not None
|
||||
|
||||
|
||||
def test_member_cannot_enable_a_task_from_inside_a_task_run(local_db):
|
||||
store = TaskStore(local_db, "user", _account(local_db, "Member"))
|
||||
record = _record()
|
||||
store.create(record)
|
||||
store.update(record["uid"], {"enabled": False, "status": "disabled"})
|
||||
with task_run_scope():
|
||||
with pytest.raises(AutomationDenied) as denied:
|
||||
store.update(record["uid"], {"enabled": True})
|
||||
assert denied.value.reason == REASON_NESTED
|
||||
|
||||
|
||||
def test_administrator_can_enable_a_task_from_inside_a_task_run(local_db):
|
||||
store = TaskStore(local_db, "user", _account(local_db, "Admin"))
|
||||
record = _record()
|
||||
store.create(record)
|
||||
assert store.get(record["uid"]) is not None
|
||||
store.update(record["uid"], {"enabled": False, "status": "disabled"})
|
||||
with task_run_scope():
|
||||
store.update(record["uid"], {"enabled": True})
|
||||
assert store.get(record["uid"])["enabled"]
|
||||
|
||||
|
||||
def test_member_cannot_re_enable_a_task(local_db):
|
||||
admin = _account(local_db, "Admin")
|
||||
member = _account(local_db, "Member")
|
||||
admin_store = TaskStore(local_db, "user", admin)
|
||||
record = _record()
|
||||
admin_store.create(record)
|
||||
local_db["devii_tasks"].update(
|
||||
{"uid": record["uid"], "owner_id": member, "enabled": False}, ["uid"]
|
||||
)
|
||||
member_store = TaskStore(local_db, "user", member)
|
||||
with pytest.raises(AutomationDenied):
|
||||
member_store.update(record["uid"], {"enabled": True})
|
||||
member_store.update(record["uid"], {"enabled": False, "status": "disabled"})
|
||||
|
||||
|
||||
def test_local_operator_store_bypasses_the_role_gate(local_db):
|
||||
store = TaskStore(local_db, "user", "cli", operator=True)
|
||||
def test_disabling_from_inside_a_task_run_is_always_allowed(local_db):
|
||||
store = TaskStore(local_db, "user", _account(local_db, "Member"))
|
||||
record = _record()
|
||||
store.create(record)
|
||||
assert store.get(record["uid"]) is not None
|
||||
with task_run_scope():
|
||||
store.update(record["uid"], {"enabled": False, "status": "disabled"})
|
||||
assert not store.get(record["uid"])["enabled"]
|
||||
|
||||
|
||||
def test_local_operator_store_bypasses_every_gate(local_db):
|
||||
store = TaskStore(local_db, "user", "cli", operator=True)
|
||||
with task_run_scope():
|
||||
for _ in range(limits.DEFAULT_MEMBER_CREATE + 3):
|
||||
store.create(_record())
|
||||
assert store.count_active() >= limits.DEFAULT_MEMBER_CREATE
|
||||
|
||||
|
||||
def test_claim_succeeds_once(local_db):
|
||||
@@ -95,8 +180,7 @@ def test_claim_refuses_a_disabled_task(local_db):
|
||||
|
||||
|
||||
def test_due_rows_skips_future_and_running_tasks(local_db):
|
||||
admin = _account(local_db, "Admin")
|
||||
store = TaskStore(local_db, "user", admin)
|
||||
store = TaskStore(local_db, "user", _account(local_db, "Admin"))
|
||||
now = now_utc()
|
||||
due = _record(next_run_at=to_iso(now.replace(year=now.year - 1)))
|
||||
future = _record(next_run_at=to_iso(now.replace(year=now.year + 1)))
|
||||
@@ -127,10 +211,42 @@ def test_controller_caps_active_tasks_per_owner(local_db, monkeypatch):
|
||||
)
|
||||
|
||||
|
||||
def test_controller_refuses_a_member(local_db):
|
||||
def test_controller_reports_when_the_next_creation_slot_frees(local_db):
|
||||
store = TaskStore(local_db, "user", _account(local_db, "Member"))
|
||||
controller = TaskController(store)
|
||||
with pytest.raises(ToolInputError):
|
||||
for _ in range(limits.DEFAULT_MEMBER_CREATE):
|
||||
controller.create_task(
|
||||
{"prompt": "work", "kind": "interval", "every_seconds": 900}
|
||||
)
|
||||
with pytest.raises(ToolInputError) as failure:
|
||||
controller.create_task(
|
||||
{"prompt": "work", "kind": "interval", "every_seconds": 900}
|
||||
)
|
||||
assert REASON_CREATE_QUOTA in str(failure.value)
|
||||
assert "frees up at" in str(failure.value)
|
||||
|
||||
|
||||
def test_controller_refuses_nested_creation_for_a_member(local_db):
|
||||
store = TaskStore(local_db, "user", _account(local_db, "Member"))
|
||||
controller = TaskController(store)
|
||||
with task_run_scope():
|
||||
with pytest.raises(ToolInputError) as failure:
|
||||
controller.create_task(
|
||||
{"prompt": "work", "kind": "interval", "every_seconds": 900}
|
||||
)
|
||||
assert REASON_NESTED in str(failure.value)
|
||||
|
||||
|
||||
def test_controller_refuses_nested_run_now_for_a_member(local_db):
|
||||
store = TaskStore(local_db, "user", _account(local_db, "Member"))
|
||||
controller = TaskController(store)
|
||||
created = controller.create_task(
|
||||
{"prompt": "work", "kind": "interval", "every_seconds": 900}
|
||||
)
|
||||
import json
|
||||
|
||||
uid = json.loads(created)["task"]["uid"]
|
||||
with task_run_scope():
|
||||
with pytest.raises(ToolInputError) as failure:
|
||||
controller.run_task_now({"uid": uid})
|
||||
assert REASON_NESTED in str(failure.value)
|
||||
|
||||
Reference in New Issue
Block a user