| from __future__ import annotations |
|
|
| from sqlalchemy import select |
| from app.models.telemetry import TelemetryEvent |
| from app.services.telemetry_service import log_telemetry_event |
|
|
|
|
| def test_log_telemetry_event_directly(client): |
| """Test that log_telemetry_event creates a database row.""" |
| from app.core.database import SessionLocal |
| with SessionLocal() as db: |
| event = log_telemetry_event( |
| db, |
| "custom_test_trigger", |
| user_id="usr_demo_student", |
| metadata={"source": "pytest", "score": 100}, |
| ) |
| assert event is not None |
| assert event.id.startswith("tel") |
| assert event.event_type == "custom_test_trigger" |
| assert "pytest" in event.event_metadata |
|
|
| |
| stmt = select(TelemetryEvent).where(TelemetryEvent.id == event.id) |
| db_event = db.scalar(stmt) |
| assert db_event is not None |
| assert db_event.user_id == "usr_demo_student" |
|
|
|
|
| def test_log_telemetry_event_uses_isolated_session(client): |
| """Telemetry must not depend on the request session being writable.""" |
| from app.core.database import SessionLocal |
|
|
| class PoisonedCallerSession: |
| def add(self, *_args, **_kwargs): |
| raise AssertionError("telemetry should not write through the caller session") |
|
|
| def commit(self): |
| raise AssertionError("telemetry should not commit the caller session") |
|
|
| def rollback(self): |
| raise AssertionError("rollback should not be needed for a successful isolated write") |
|
|
| event = log_telemetry_event( |
| PoisonedCallerSession(), |
| "isolated_session_test", |
| user_id="usr_demo_student", |
| metadata={"source": "pytest"}, |
| ) |
|
|
| assert event is not None |
| with SessionLocal() as db: |
| db_event = db.scalar(select(TelemetryEvent).where(TelemetryEvent.id == event.id)) |
| assert db_event is not None |
| assert db_event.event_type == "isolated_session_test" |
|
|
|
|
| def test_upload_completed_telemetry(auth_client, monkeypatch): |
| """Test that document upload writes an upload_completed telemetry event.""" |
| from app.core.database import SessionLocal |
|
|
| |
| monkeypatch.setattr( |
| "app.routes.documents.extract_text_from_file", |
| lambda *args, **kwargs: "Mocked text context for telemetry test.", |
| ) |
|
|
| |
| signup_response = auth_client.post( |
| "/auth/signup", |
| json={"name": "Telemetry Student", "email": "tel-student@example.test", "password": "Pass123!beta"}, |
| ) |
| assert signup_response.status_code == 201 |
| token = signup_response.json()["access_token"] |
| headers = {"Authorization": f"Bearer {token}"} |
|
|
| file_payload = {"file": ("test.txt", b"Photosynthesis formula is active.", "text/plain")} |
| form_payload = {"title": "Test Chapter", "subject": "Biology"} |
|
|
| upload_response = auth_client.post( |
| "/documents/upload", |
| headers=headers, |
| files=file_payload, |
| data=form_payload, |
| ) |
| assert upload_response.status_code == 201 |
|
|
| |
| with SessionLocal() as db: |
| events = db.scalars( |
| select(TelemetryEvent) |
| .where(TelemetryEvent.event_type == "upload_completed") |
| .order_by(TelemetryEvent.created_at.desc()) |
| ).all() |
| assert len(events) >= 1 |
| assert "Biology" in events[0].event_metadata |
| assert "doc_" in events[0].event_metadata |
| assert "status" in events[0].event_metadata |
|
|
|
|
| def test_grounding_guard_telemetry(auth_client): |
| """Test that triggering grounding guard logs grounding_guard_frequency.""" |
| from app.core.database import SessionLocal |
|
|
| signup_response = auth_client.post( |
| "/auth/signup", |
| json={"name": "Guard Student", "email": "guard-student@example.test", "password": "Pass123!beta"}, |
| ) |
| assert signup_response.status_code == 201 |
| token = signup_response.json()["access_token"] |
| headers = {"Authorization": f"Bearer {token}"} |
|
|
| |
| ask_response = auth_client.post( |
| "/ask", |
| headers=headers, |
| json={"question": "What is Lenz's law?", "mode": "exam_answer"}, |
| ) |
| assert ask_response.status_code == 200 |
|
|
| |
| with SessionLocal() as db: |
| events = db.scalars( |
| select(TelemetryEvent) |
| .where(TelemetryEvent.event_type == "grounding_guard_frequency") |
| .order_by(TelemetryEvent.created_at.desc()) |
| ).all() |
| assert len(events) >= 1 |
| assert "Lenz" in events[0].event_metadata |
| assert "no_source" in events[0].event_metadata |
|
|
|
|
| def test_quiz_retry_behavior_telemetry(auth_client): |
| """Test that generating a quiz logs quiz_retry_behavior telemetry.""" |
| from app.core.database import SessionLocal |
|
|
| signup_response = auth_client.post( |
| "/auth/signup", |
| json={"name": "Quiz Student", "email": "quiz-student@example.test", "password": "Pass123!beta"}, |
| ) |
| assert signup_response.status_code == 201 |
| token = signup_response.json()["access_token"] |
| headers = {"Authorization": f"Bearer {token}"} |
|
|
| |
| quiz_response = auth_client.post( |
| "/generate/quiz", |
| headers=headers, |
| json={ |
| "topic": "Photosynthesis", |
| "subject": "Biology", |
| "level": "Plus Two", |
| "goal": "A+", |
| "time_left": "tonight", |
| "options": {"num_questions": 5}, |
| }, |
| ) |
| assert quiz_response.status_code == 200 |
|
|
| |
| with SessionLocal() as db: |
| events = db.scalars( |
| select(TelemetryEvent) |
| .where(TelemetryEvent.event_type == "quiz_retry_behavior") |
| .order_by(TelemetryEvent.created_at.desc()) |
| ).all() |
| assert len(events) >= 1 |
| assert "Photosynthesis" in events[0].event_metadata |
| assert "Biology" in events[0].event_metadata |
|
|
|
|
| def test_last_night_plan_usage_telemetry(auth_client): |
| """Test that generating a last-night plan logs last_night_plan_usage telemetry.""" |
| from app.core.database import SessionLocal |
|
|
| signup_response = auth_client.post( |
| "/auth/signup", |
| json={"name": "Revision Student", "email": "rev-student@example.test", "password": "Pass123!beta"}, |
| ) |
| assert signup_response.status_code == 201 |
| token = signup_response.json()["access_token"] |
| headers = {"Authorization": f"Bearer {token}"} |
|
|
| |
| plan_response = auth_client.post( |
| "/generate/last-night-plan", |
| headers=headers, |
| json={ |
| "topic": "Organic Chemistry", |
| "subject": "Chemistry", |
| "level": "Plus Two", |
| "goal": "A+", |
| "time_left": "tonight", |
| "options": {}, |
| }, |
| ) |
| assert plan_response.status_code == 200 |
|
|
| |
| with SessionLocal() as db: |
| events = db.scalars( |
| select(TelemetryEvent) |
| .where(TelemetryEvent.event_type == "last_night_plan_usage") |
| .order_by(TelemetryEvent.created_at.desc()) |
| ).all() |
| assert len(events) >= 1 |
| assert "Organic Chemistry" in events[0].event_metadata |
| assert "Chemistry" in events[0].event_metadata |
|
|