|
# retoor <retoor@molodetz.nl>
|
|
|
|
from datetime import timedelta
|
|
|
|
from devplacepy.database import invalidate_admins_cache, set_setting
|
|
from devplacepy.services.devii.tasks import limits
|
|
from devplacepy.services.devii.tasks.schedule import now_utc, to_iso
|
|
from devplacepy.utils import generate_uid
|
|
|
|
|
|
def _account(local_db, role):
|
|
uid = generate_uid()
|
|
local_db["users"].insert(
|
|
{
|
|
"uid": uid,
|
|
"username": f"lim-{uid[-10:]}",
|
|
"role": role,
|
|
"deleted_at": None,
|
|
"created_at": to_iso(now_utc()),
|
|
}
|
|
)
|
|
invalidate_admins_cache()
|
|
return uid
|
|
|
|
|
|
def _run_at(local_db, owner, moment):
|
|
limits.record_run(local_db, "user", owner, generate_uid(), moment)
|
|
|
|
|
|
def _task_at(local_db, owner, moment, deleted=None):
|
|
local_db["devii_tasks"].insert(
|
|
{
|
|
"uid": generate_uid(),
|
|
"owner_kind": "user",
|
|
"owner_id": owner,
|
|
"prompt": "work",
|
|
"enabled": True,
|
|
"status": "pending",
|
|
"created_at": to_iso(moment),
|
|
"deleted_at": deleted,
|
|
}
|
|
)
|
|
|
|
|
|
def test_default_limits_follow_the_role(local_db):
|
|
assert limits.create_limit(False) == 5
|
|
assert limits.run_limit(False) == 10
|
|
assert limits.create_limit(True) == 5
|
|
assert limits.run_limit(True) == 100
|
|
|
|
|
|
def test_limits_are_configurable(local_db):
|
|
set_setting(limits.FIELD_MEMBER_RUNS, "3")
|
|
try:
|
|
assert limits.run_limit(False) == 3
|
|
finally:
|
|
set_setting(limits.FIELD_MEMBER_RUNS, str(limits.DEFAULT_MEMBER_RUNS))
|
|
assert limits.run_limit(False) == limits.DEFAULT_MEMBER_RUNS
|
|
|
|
|
|
def test_run_quota_counts_only_the_window(local_db):
|
|
owner = _account(local_db, "Member")
|
|
now = now_utc()
|
|
for offset in (1, 2, 3):
|
|
_run_at(local_db, owner, now - timedelta(hours=offset))
|
|
_run_at(local_db, owner, now - timedelta(hours=30))
|
|
quota = limits.run_quota(local_db, "user", owner, now)
|
|
assert quota.used == 3
|
|
assert quota.limit == 10
|
|
assert quota.exceeded is False
|
|
assert quota.remaining == 7
|
|
|
|
|
|
def test_run_quota_reports_when_the_next_slot_frees(local_db):
|
|
owner = _account(local_db, "Member")
|
|
now = now_utc()
|
|
oldest = now - timedelta(hours=20)
|
|
_run_at(local_db, owner, oldest)
|
|
for index in range(9):
|
|
_run_at(local_db, owner, now - timedelta(hours=1, minutes=index))
|
|
quota = limits.run_quota(local_db, "user", owner, now)
|
|
assert quota.used == 10
|
|
assert quota.exceeded is True
|
|
assert quota.remaining == 0
|
|
assert quota.free_at == oldest + timedelta(hours=24)
|
|
|
|
|
|
def test_run_quota_is_per_owner(local_db):
|
|
first = _account(local_db, "Member")
|
|
second = _account(local_db, "Member")
|
|
now = now_utc()
|
|
for _ in range(10):
|
|
_run_at(local_db, first, now)
|
|
assert limits.run_quota(local_db, "user", first, now).exceeded is True
|
|
assert limits.run_quota(local_db, "user", second, now).used == 0
|
|
|
|
|
|
def test_administrators_get_the_larger_run_quota(local_db):
|
|
owner = _account(local_db, "Admin")
|
|
now = now_utc()
|
|
for _ in range(50):
|
|
_run_at(local_db, owner, now)
|
|
quota = limits.run_quota(local_db, "user", owner, now)
|
|
assert quota.limit == 100
|
|
assert quota.exceeded is False
|
|
|
|
|
|
def test_create_quota_counts_tasks_in_the_window(local_db):
|
|
owner = _account(local_db, "Member")
|
|
now = now_utc()
|
|
for offset in range(4):
|
|
_task_at(local_db, owner, now - timedelta(hours=offset))
|
|
_task_at(local_db, owner, now - timedelta(hours=25))
|
|
quota = limits.create_quota(local_db, "user", owner, now)
|
|
assert quota.used == 4
|
|
assert quota.limit == 5
|
|
assert quota.exceeded is False
|
|
|
|
|
|
def test_create_quota_still_counts_a_deleted_task(local_db):
|
|
owner = _account(local_db, "Member")
|
|
now = now_utc()
|
|
for _ in range(5):
|
|
_task_at(local_db, owner, now, deleted=to_iso(now))
|
|
quota = limits.create_quota(local_db, "user", owner, now)
|
|
assert quota.used == 5
|
|
assert quota.exceeded is True
|
|
|
|
|
|
def test_zero_limit_means_unlimited(local_db):
|
|
owner = _account(local_db, "Member")
|
|
now = now_utc()
|
|
set_setting(limits.FIELD_MEMBER_RUNS, "0")
|
|
try:
|
|
for _ in range(30):
|
|
_run_at(local_db, owner, now)
|
|
quota = limits.run_quota(local_db, "user", owner, now)
|
|
assert quota.limit == 0
|
|
assert quota.exceeded is False
|
|
assert quota.remaining == -1
|
|
finally:
|
|
set_setting(limits.FIELD_MEMBER_RUNS, str(limits.DEFAULT_MEMBER_RUNS))
|
|
|
|
|
|
def test_reserve_run_grants_exactly_the_quota(local_db):
|
|
owner = _account(local_db, "Member")
|
|
now = now_utc()
|
|
granted = [
|
|
limits.reserve_run(local_db, "user", owner, generate_uid(), now)
|
|
for _ in range(15)
|
|
]
|
|
assert granted.count(True) == limits.DEFAULT_MEMBER_RUNS
|
|
assert granted[limits.DEFAULT_MEMBER_RUNS] is False
|
|
assert limits.run_quota(local_db, "user", owner, now).used == limits.DEFAULT_MEMBER_RUNS
|
|
|
|
|
|
def test_reserve_run_grants_an_administrator_more(local_db):
|
|
owner = _account(local_db, "Admin")
|
|
now = now_utc()
|
|
granted = [
|
|
limits.reserve_run(local_db, "user", owner, generate_uid(), now)
|
|
for _ in range(limits.DEFAULT_MEMBER_RUNS + 5)
|
|
]
|
|
assert all(granted)
|
|
|
|
|
|
def test_reserve_run_is_unbounded_when_the_limit_is_zero(local_db):
|
|
owner = _account(local_db, "Member")
|
|
now = now_utc()
|
|
set_setting(limits.FIELD_MEMBER_RUNS, "0")
|
|
try:
|
|
granted = [
|
|
limits.reserve_run(local_db, "user", owner, generate_uid(), now)
|
|
for _ in range(25)
|
|
]
|
|
assert all(granted)
|
|
finally:
|
|
set_setting(limits.FIELD_MEMBER_RUNS, str(limits.DEFAULT_MEMBER_RUNS))
|
|
|
|
|
|
def test_reserve_run_ignores_rows_outside_the_window(local_db):
|
|
owner = _account(local_db, "Member")
|
|
now = now_utc()
|
|
for index in range(limits.DEFAULT_MEMBER_RUNS):
|
|
_run_at(local_db, owner, now - timedelta(hours=25, minutes=index))
|
|
assert limits.reserve_run(local_db, "user", owner, generate_uid(), now) is True
|
|
|
|
|
|
def test_prune_drops_rows_outside_the_retention_window(local_db):
|
|
owner = _account(local_db, "Member")
|
|
now = now_utc()
|
|
_run_at(local_db, owner, now)
|
|
_run_at(local_db, owner, now - timedelta(hours=72))
|
|
removed = limits.prune_runs(local_db, now)
|
|
assert removed >= 1
|
|
assert limits.run_quota(local_db, "user", owner, now).used == 1
|