|
# retoor <retoor@molodetz.nl>
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
import uuid_utils
|
|
|
|
from devplacepy.config import PRESENCE_TIMEOUT_SECONDS, PRESENCE_WRITE_SECONDS
|
|
from devplacepy.database import get_table, set_consent
|
|
from devplacepy.services import presence
|
|
|
|
|
|
def _iso(seconds_ago):
|
|
return (datetime.now(timezone.utc) - timedelta(seconds=seconds_ago)).isoformat()
|
|
|
|
|
|
def test_is_online_recent():
|
|
assert presence.is_online({"last_seen": _iso(5)}) is True
|
|
|
|
|
|
def test_is_online_stale():
|
|
assert presence.is_online({"last_seen": _iso(PRESENCE_TIMEOUT_SECONDS + 30)}) is False
|
|
|
|
|
|
def test_is_online_missing_and_empty():
|
|
assert presence.is_online({"last_seen": ""}) is False
|
|
assert presence.is_online({}) is False
|
|
assert presence.is_online(None) is False
|
|
|
|
|
|
def test_is_online_unparseable_is_offline():
|
|
assert presence.is_online({"last_seen": "not-a-timestamp"}) is False
|
|
|
|
|
|
def test_is_online_is_the_first_observation_of_stays_online():
|
|
from devplacepy.config import PRESENCE_ONLINE_MARGIN_SECONDS
|
|
|
|
for seconds in (0, 5, PRESENCE_TIMEOUT_SECONDS - 1, PRESENCE_TIMEOUT_SECONDS + 1,
|
|
PRESENCE_TIMEOUT_SECONDS + PRESENCE_ONLINE_MARGIN_SECONDS + 1):
|
|
row = {"last_seen": _iso(seconds)}
|
|
expected = presence.stays_online(
|
|
presence.seconds_since(row["last_seen"]), was_online=False
|
|
)
|
|
assert presence.is_online(row) is expected
|
|
|
|
|
|
def test_seconds_since_none_on_missing_or_bad():
|
|
assert presence.seconds_since(None) is None
|
|
assert presence.seconds_since("") is None
|
|
assert presence.seconds_since("garbage") is None
|
|
assert presence.seconds_since(_iso(10)) >= 10
|
|
|
|
|
|
def test_touch_writes_once_then_throttles(local_db):
|
|
uid = str(uuid_utils.uuid7())
|
|
users = get_table("users")
|
|
users.insert(
|
|
{
|
|
"uid": uid,
|
|
"username": f"presence_{uid[:8]}",
|
|
"terms_version": "1",
|
|
"email": f"{uid[:8]}@presence.test",
|
|
"last_seen": None,
|
|
}
|
|
)
|
|
set_consent("user", uid, "activity_recording", True)
|
|
presence._last_write.pop(uid, None)
|
|
|
|
presence.touch(uid)
|
|
first = users.find_one(uid=uid)["last_seen"]
|
|
assert first
|
|
|
|
presence.touch(uid)
|
|
assert users.find_one(uid=uid)["last_seen"] == first
|
|
|
|
presence._last_write[uid] = presence.time.monotonic() - PRESENCE_WRITE_SECONDS - 1
|
|
presence.touch(uid)
|
|
second = users.find_one(uid=uid)["last_seen"]
|
|
assert second >= first
|
|
assert presence.is_online(users.find_one(uid=uid)) is True
|
|
|
|
|
|
def test_get_online_users_filters_by_cutoff(local_db):
|
|
from devplacepy.database import get_online_users
|
|
|
|
now = datetime.now(timezone.utc)
|
|
fresh = str(uuid_utils.uuid7())
|
|
stale = str(uuid_utils.uuid7())
|
|
users = get_table("users")
|
|
users.insert(
|
|
{"uid": fresh, "username": f"onl_{fresh[:8]}", "email": f"{fresh[:8]}@o.test", "last_seen": now.isoformat()}
|
|
)
|
|
users.insert(
|
|
{"uid": stale, "username": f"onl_{stale[:8]}", "email": f"{stale[:8]}@o.test", "last_seen": (now - timedelta(seconds=99999)).isoformat()}
|
|
)
|
|
cutoff = (now - timedelta(seconds=60)).isoformat()
|
|
online = {u["uid"] for u in get_online_users(cutoff, limit=1000)}
|
|
assert fresh in online
|
|
assert stale not in online
|
|
|
|
|
|
def test_presence_online_users_includes_recent(local_db):
|
|
fresh = str(uuid_utils.uuid7())
|
|
get_table("users").insert(
|
|
{"uid": fresh, "username": f"po_{fresh[:8]}", "email": f"{fresh[:8]}@o.test", "last_seen": datetime.now(timezone.utc).isoformat()}
|
|
)
|
|
assert fresh in {u["uid"] for u in presence.online_users(limit=1000)}
|
|
|
|
|
|
def test_online_users_sorted_alphabetically(local_db):
|
|
now = datetime.now(timezone.utc).isoformat()
|
|
users = get_table("users")
|
|
made = []
|
|
for label in ("zeta", "alpha", "mike"):
|
|
uid = str(uuid_utils.uuid7())
|
|
name = f"srt{label}{uid[:6]}"
|
|
users.insert({"uid": uid, "username": name, "email": f"{uid[:8]}@o.test", "last_seen": now})
|
|
made.append(name)
|
|
listed = [u["username"] for u in presence.online_users(limit=1000) if u["username"] in made]
|
|
assert listed == sorted(made, key=str.lower)
|
|
|
|
|
|
def test_stays_online_hysteresis():
|
|
from devplacepy.config import (
|
|
PRESENCE_ONLINE_MARGIN_SECONDS,
|
|
PRESENCE_TIMEOUT_SECONDS,
|
|
)
|
|
|
|
within = PRESENCE_TIMEOUT_SECONDS - 5
|
|
band = PRESENCE_TIMEOUT_SECONDS + 5
|
|
beyond = PRESENCE_TIMEOUT_SECONDS + PRESENCE_ONLINE_MARGIN_SECONDS + 5
|
|
# fresh activity: online regardless of prior state
|
|
assert presence.stays_online(within, was_online=False) is True
|
|
assert presence.stays_online(within, was_online=True) is True
|
|
# in the grace band: only an already-online user stays online (hysteresis)
|
|
assert presence.stays_online(band, was_online=False) is False
|
|
assert presence.stays_online(band, was_online=True) is True
|
|
# beyond the grace: offline even if previously online
|
|
assert presence.stays_online(beyond, was_online=True) is False
|
|
# no last_seen: always offline
|
|
assert presence.stays_online(None, was_online=True) is False
|
|
|
|
|
|
def test_online_candidates_uses_grace_window(local_db):
|
|
from devplacepy.config import (
|
|
PRESENCE_ONLINE_MARGIN_SECONDS,
|
|
PRESENCE_TIMEOUT_SECONDS,
|
|
)
|
|
|
|
now = datetime.now(timezone.utc)
|
|
band = str(uuid_utils.uuid7())
|
|
beyond = str(uuid_utils.uuid7())
|
|
users = get_table("users")
|
|
users.insert(
|
|
{"uid": band, "username": f"cb_{band[:8]}", "email": f"{band[:8]}@o.test",
|
|
"last_seen": (now - timedelta(seconds=PRESENCE_TIMEOUT_SECONDS + 3)).isoformat()}
|
|
)
|
|
users.insert(
|
|
{"uid": beyond, "username": f"cb_{beyond[:8]}", "email": f"{beyond[:8]}@o.test",
|
|
"last_seen": (now - timedelta(seconds=PRESENCE_TIMEOUT_SECONDS + PRESENCE_ONLINE_MARGIN_SECONDS + 30)).isoformat()}
|
|
)
|
|
candidates = {u["uid"] for u in presence.online_candidates(limit=1000)}
|
|
strict = {u["uid"] for u in presence.online_users(limit=1000)}
|
|
# a grace-band user is a candidate (for hysteresis) but NOT strictly online
|
|
assert band in candidates
|
|
assert band not in strict
|
|
# a user past the grace window is in neither
|
|
assert beyond not in candidates
|
|
|
|
|
|
def test_online_candidates_tracks_beyond_the_display_limit(local_db):
|
|
from devplacepy.config import PRESENCE_ONLINE_LIMIT, PRESENCE_TRACK_LIMIT
|
|
|
|
# the display roster is capped for the feed panel, the tracked authority set is not
|
|
assert PRESENCE_TRACK_LIMIT > PRESENCE_ONLINE_LIMIT
|
|
now = datetime.now(timezone.utc).isoformat()
|
|
users = get_table("users")
|
|
made = []
|
|
for index in range(PRESENCE_ONLINE_LIMIT + 5):
|
|
uid = str(uuid_utils.uuid7())
|
|
users.insert(
|
|
{"uid": uid, "username": f"trk{index:03d}{uid[:6]}",
|
|
"email": f"{uid[:8]}@o.test", "last_seen": now}
|
|
)
|
|
made.append(uid)
|
|
tracked = {u["uid"] for u in presence.online_candidates()}
|
|
assert set(made) <= tracked
|
|
|
|
|
|
def test_touch_is_silent_without_activity_recording_consent(local_db):
|
|
uid = str(uuid_utils.uuid7())
|
|
users = get_table("users")
|
|
users.insert(
|
|
{
|
|
"uid": uid,
|
|
"username": f"presence_{uid[:8]}",
|
|
"terms_version": "1",
|
|
"email": f"{uid[:8]}@presence.test",
|
|
"last_seen": None,
|
|
}
|
|
)
|
|
presence._last_write.pop(uid, None)
|
|
|
|
presence.touch(uid)
|
|
assert users.find_one(uid=uid)["last_seen"] is None
|
|
|
|
set_consent("user", uid, "activity_recording", True)
|
|
presence._last_write.pop(uid, None)
|
|
presence.touch(uid)
|
|
assert users.find_one(uid=uid)["last_seen"]
|
|
|
|
set_consent("user", uid, "activity_recording", False)
|
|
presence._last_write.pop(uid, None)
|
|
written = users.find_one(uid=uid)["last_seen"]
|
|
presence.touch(uid)
|
|
assert users.find_one(uid=uid)["last_seen"] == written
|