Download tests/test_monitoring.py from wzender/mathly: direct link, hf CLI and curl.
- Browser
- Download file 4.87 kB
-
https://huggingface.co/spaces/wzender/mathly/resolve/main/tests/test_monitoring.py
- Command line
-
hf download hf://spaces/wzender/mathly/tests/test_monitoring.py
-
curl -L -o test_monitoring.py https://huggingface.co/spaces/wzender/mathly/resolve/main/tests/test_monitoring.py
4.87 kB
| import asyncio | |
| import logging | |
| from datetime import datetime, timezone | |
| import httpx | |
| from app import log_errors | |
| from app.main import create_app | |
| from app.monitoring import activity_buckets, photo_summary, seen_summary | |
| TEACHER, STUDENT = "teacher-token", "student-token" | |
| class MonitorAuth: | |
| url, key, secret = "https://test.invalid", "public", "server" | |
| teachers, developers = frozenset(), frozenset({"t@test.invalid"}) # management is for developers | |
| def __init__(self, rows=None): | |
| self.rows = rows | |
| def user(self, token): | |
| return {TEACHER: {"id": "t", "email": "t@test.invalid"}, | |
| STUDENT: {"id": "s", "email": "s@test.invalid"}}.get(token) | |
| def classes(self, fresh=False): | |
| return [{"id": 1, "name": "9א", "emails": ["s@test.invalid"], "teachers": []}] | |
| def access_for(self, _id, **_kwargs): | |
| return None | |
| def ping(self): | |
| return {"auth": {"ok": True, "ms": 12}, "db": {"ok": True, "ms": 30}} | |
| def answer_activity(self, since): | |
| return self.rows | |
| def get(app, path, token): | |
| async def run(): | |
| async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client: | |
| return await client.get(path, headers={"Authorization": f"Bearer {token}"}) | |
| return asyncio.run(run()) | |
| def test_monitoring_is_for_managers_only(): | |
| app = create_app(MonitorAuth()) | |
| for path in ("/api/monitor/summary", "/api/monitor/supabase", "/api/monitor/activity"): | |
| assert get(app, path, STUDENT).status_code == 403 | |
| assert get(app, path, TEACHER).status_code == 200 | |
| def test_summary_counts_sign_ins_photos_and_log_errors(): | |
| app = create_app(MonitorAuth()) | |
| get(app, "/api/me", STUDENT) | |
| today = datetime.now().astimezone().date().isoformat() | |
| app.state.photo_counts.update({("s@test.invalid", today): 2, ("x@test.invalid", "2000-01-01"): 5}) | |
| logging.getLogger("mathly.test").error("something broke") | |
| data = get(app, "/api/monitor/summary", TEACHER).json() | |
| assert data["seen"]["last_5m"] == 2 # the student and the teacher asking | |
| assert data["build"]["version"] | |
| assert data["errors"]["recent"][0]["message"] == "something broke" | |
| assert data["answers"]["open_ended_locked"] is True | |
| assert get(app, "/api/monitor/supabase", TEACHER).json()["db"]["ok"] is True | |
| def test_activity_buckets_and_missing_tables(): | |
| now = datetime(2026, 11, 1, 12, 7, tzinfo=timezone.utc) | |
| rows = {"attempts": [{"user_id": "a", "created_at": "2026-11-01T12:01:00.123+00:00"}, | |
| {"user_id": "a", "created_at": "2026-10-31T09:00:00+00:00"}, | |
| {"user_id": "b", "created_at": "2026-11-01T12:02:00+00:00"}, | |
| {"user_id": "c", "created_at": "2026-09-01T12:00:00+00:00"}], | |
| "grades": [{"user_id": "a", "state": "completed", "verdict": "correct", "created_at": "2026-11-01T12:03:00+00:00"}, | |
| {"user_id": "b", "state": "failed", "verdict": None, "created_at": "2026-11-01T12:04:00+00:00"}]} | |
| day = activity_buckets(rows, "day", now) | |
| assert len(day["buckets"]) == 96 and day["buckets"][-1] == { | |
| "bucket": "2026-11-01T12:00:00+00:00", "attempts": 2, "active_students": 2} | |
| assert day["summary"]["attempts"] == 2 | |
| week = activity_buckets(rows, "week", now)["summary"] | |
| assert week == {"attempts": 3, "active_students": 2, "returning_students": 1, "active_days": 2, | |
| "checks": 2, "verdicts": {"correct": 1, "failed": 1}} | |
| month = activity_buckets(rows, "month", now) | |
| assert len(month["buckets"]) == 30 and month["summary"]["attempts"] == 3 | |
| app = create_app(MonitorAuth(rows=None)) | |
| assert "unavailable" in get(app, "/api/monitor/activity?period=day", TEACHER).json() | |
| assert get(app, "/api/monitor/activity?period=year", TEACHER).status_code == 422 | |
| def test_small_summaries(): | |
| assert seen_summary({"old": 0, "hour": 98_000.0, "new": 100_000.0}, now=100_100.0) == {"last_5m": 1, "last_hour": 2, "last_24h": 2} | |
| assert photo_summary({("a", "d"): 20, ("b", "d"): 1, ("c", "e"): 3}, today="d") == { | |
| "today": 21, "students": 2, "at_limit": 1, "daily_limit": photo_summary({}, "d")["daily_limit"]} | |
| assert log_errors.COUNTER in logging.getLogger().handlers | |
| def test_recent_actions_name_the_action_and_the_user(): | |
| app = create_app(MonitorAuth()) | |
| get(app, "/api/me", STUDENT) | |
| get(app, "/api/me", STUDENT) # a page reload soon after is the same visit | |
| app.state.actions.record("s", "answer_upload", "Question 1") # answer routes pass the user id | |
| actions = get(app, "/api/monitor/summary", TEACHER).json()["actions"] | |
| assert [(a["kind"], a["email"], a["label"]) for a in actions] == [ | |
| ("visit", "t@test.invalid", ""), ("answer_upload", "s@test.invalid", "Question 1"), | |
| ("visit", "s@test.invalid", "")] | |