Files
yellow-bank-soal/tests/test_security_regressions.py
2026-06-06 19:40:32 +07:00

133 lines
4.3 KiB
Python

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"