import asyncio import inspect from datetime import datetime, timezone from fastapi.params import Depends from app.api.v1.session import SubmitAnswerResponse from app.core.auth import AuthContext, get_auth_context from app.routers import admin as admin_router from app.routers import reports as reports_router from app.routers import wordpress as wordpress_router from app.schemas.session import SessionCompleteResponse, UserAnswerOutput from app.services.reporting import AggregatePerformanceStats, StudentPerformanceReport def _depends_on_auth(callable_obj, parameter_name: str = "auth") -> bool: parameter = inspect.signature(callable_obj).parameters[parameter_name] default = parameter.default return isinstance(default, Depends) and default.dependency is get_auth_context def test_admin_actions_require_signed_auth_context(): assert _depends_on_auth(admin_router.admin_trigger_calibration) assert _depends_on_auth(admin_router.admin_toggle_ai_generation) assert _depends_on_auth(admin_router.admin_reset_normalization) def test_wordpress_user_lookup_routes_require_signed_auth_context(): assert _depends_on_auth(wordpress_router.get_website_users) assert _depends_on_auth(wordpress_router.get_user_endpoint) def test_wordpress_roles_map_to_api_admin_roles(): assert wordpress_router._api_role_from_wordpress_roles(["subscriber"]) == "student" assert wordpress_router._api_role_from_wordpress_roles(["administrator"]) == "admin" assert wordpress_router._api_role_from_wordpress_roles(["super_admin"]) == "system_admin" def test_adaptive_submit_response_does_not_expose_answer_key_or_correctness(): payload = SubmitAnswerResponse(theta=0.12, theta_se=0.8).model_dump() assert "is_correct" not in payload assert "correct_answer" not in payload assert "explanation" not in payload def test_session_completion_answer_output_does_not_expose_correctness(): answer_payload = UserAnswerOutput( id=1, item_id=10, response="A", time_spent=12, bobot_earned=0.5, scoring_mode_used="ctt", ).model_dump() assert "is_correct" not in answer_payload response_payload = SessionCompleteResponse( id=1, session_id="s-1", wp_user_id="wp-1", website_id=2, tryout_id="tryout-1", start_time=datetime.now(timezone.utc), end_time=datetime.now(timezone.utc), is_completed=True, scoring_mode_used="ctt", total_benar=1, total_bobot_earned=0.5, NM=500, NN=500, rataan_used=500, sb_used=100, user_answers=[ UserAnswerOutput( id=1, item_id=10, response="A", time_spent=12, bobot_earned=0.5, scoring_mode_used="ctt", ) ], ).model_dump() assert "is_correct" not in response_payload["user_answers"][0] def test_student_performance_report_is_scoped_to_student_user(monkeypatch): captured = {} async def fake_generate_student_performance_report(**kwargs): captured.update(kwargs) return StudentPerformanceReport( generated_at=datetime.now(timezone.utc), tryout_id=kwargs["tryout_id"], website_id=kwargs["website_id"], date_range=kwargs["date_range"], aggregate=AggregatePerformanceStats( tryout_id=kwargs["tryout_id"], participant_count=0, avg_nm=None, std_nm=None, min_nm=None, max_nm=None, median_nm=None, avg_nn=None, std_nn=None, avg_theta=None, pass_rate=0.0, avg_time_spent=0.0, ), individual_records=[], ) monkeypatch.setattr( reports_router, "generate_student_performance_report", fake_generate_student_performance_report, ) asyncio.run( reports_router.get_student_performance_report( tryout_id="tryout-1", db=object(), auth=AuthContext(website_id=5, role="student", wp_user_id="wp-1"), ) ) assert captured["website_id"] == 5 assert captured["wp_user_id"] == "wp-1"