refactor(routes): remove /api/lesson-progress aliases and standardize tests to /lesson-progress

This commit is contained in:
a2nr 2026-08-30 04:45:37 +07:00
parent 41c434f064
commit ac3fdb8dff
4 changed files with 41 additions and 43 deletions

View File

@ -51,7 +51,6 @@ def _parse_ts(value, *, required: bool) -> datetime | None:
@lesson_progress_bp.route("/lesson-progress", methods=["POST"])
@lesson_progress_bp.route("/api/lesson-progress", methods=["POST"])
def submit_lesson_progress():
if request.content_length is not None and request.content_length > MAX_PAYLOAD_BYTES:
return jsonify({"success": False, "message": "Payload terlalu besar"}), 413
@ -237,7 +236,6 @@ def submit_lesson_progress():
@lesson_progress_bp.route("/lesson-progress/<lesson_name>", methods=["GET"])
@lesson_progress_bp.route("/api/lesson-progress/<lesson_name>", methods=["GET"])
def get_lesson_progress(lesson_name: str):
"""Ambil progress lesson + audit attempt kuis (review-after-refresh).

View File

@ -1,4 +1,4 @@
"""Flow komposit end-to-end via /api/lesson-progress.
"""Flow komposit end-to-end via /lesson-progress.
Skenario utama:
- Lesson dengan BOTH exercise (tab c) DAN quiz → exercise + quiz 3/4
@ -93,19 +93,19 @@ def _quiz_payload(attempt_id: str, score: str, status: str = "submitted") -> dic
def _post_exercise(client):
return client.post(
"/api/lesson-progress",
"/lesson-progress",
json={"token": STUDENT_TOKEN, "lesson_name": LESSON_BOTH, "type": "exercise"},
)
def _fetch(client):
return client.get(f"/api/lesson-progress/{LESSON_BOTH}?token={STUDENT_TOKEN}")
return client.get(f"/lesson-progress/{LESSON_BOTH}?token={STUDENT_TOKEN}")
def test_exercise_then_quiz_makes_done(client):
_post_exercise(client)
resp = client.post(
"/api/lesson-progress",
"/lesson-progress",
json=_quiz_payload(str(uuid.uuid4()), "3/4"),
)
assert resp.status_code == 200
@ -125,7 +125,7 @@ def test_exercise_only_not_done_without_quiz(client):
def test_low_quiz_below_threshold_not_done(client):
_post_exercise(client)
resp = client.post(
"/api/lesson-progress",
"/lesson-progress",
json=_quiz_payload(str(uuid.uuid4()), "0/4"),
)
assert resp.status_code == 200
@ -147,7 +147,7 @@ def test_reading_flow_returns_audit_answer_fields(client):
}
]
resp = client.post(
"/api/lesson-progress",
"/lesson-progress",
json={
**_quiz_payload(str(uuid.uuid4()), "4/4"),
"answers": ans,

View File

@ -60,7 +60,7 @@ def _seed(seed_demo_users):
def test_exercise_marks_exercise_passed(client):
resp = client.post(
"/api/lesson-progress",
"/lesson-progress",
json={"token": STUDENT_TOKEN, "lesson_name": LESSON, "type": "exercise"},
)
assert resp.status_code == 200
@ -69,7 +69,7 @@ def test_exercise_marks_exercise_passed(client):
def test_exercise_invalid_token_rejected(client):
resp = client.post(
"/api/lesson-progress",
"/lesson-progress",
json={"token": "TOKEN_SALAH", "lesson_name": LESSON, "type": "exercise"},
)
assert resp.status_code == 401
@ -77,7 +77,7 @@ def test_exercise_invalid_token_rejected(client):
def test_exercise_unknown_lesson_rejected(client):
resp = client.post(
"/api/lesson-progress",
"/lesson-progress",
json={"token": STUDENT_TOKEN, "lesson_name": "tidak_ada", "type": "exercise"},
)
assert resp.status_code == 404
@ -87,7 +87,7 @@ def test_exercise_unknown_lesson_rejected(client):
def test_missing_token_rejected(client):
resp = client.post(
"/api/lesson-progress",
"/lesson-progress",
json={"lesson_name": LESSON, "type": "exercise"},
)
assert resp.status_code == 400
@ -95,7 +95,7 @@ def test_missing_token_rejected(client):
def test_missing_lesson_rejected(client):
resp = client.post(
"/api/lesson-progress",
"/lesson-progress",
json={"token": STUDENT_TOKEN, "type": "exercise"},
)
assert resp.status_code == 400
@ -103,7 +103,7 @@ def test_missing_lesson_rejected(client):
def test_missing_type_rejected(client):
resp = client.post(
"/api/lesson-progress",
"/lesson-progress",
json={"token": STUDENT_TOKEN, "lesson_name": LESSON},
)
assert resp.status_code == 400
@ -111,7 +111,7 @@ def test_missing_type_rejected(client):
def test_invalid_type_rejected(client):
resp = client.post(
"/api/lesson-progress",
"/lesson-progress",
json={"token": STUDENT_TOKEN, "lesson_name": LESSON, "type": "biasa"},
)
assert resp.status_code == 400
@ -121,7 +121,7 @@ def test_invalid_type_rejected(client):
def test_quiz_submit_creates_attempt(client):
resp = client.post(
"/api/lesson-progress",
"/lesson-progress",
json=_payload({"status": "submitted", "termination_reason": None, "score": "4/4"}),
)
assert resp.status_code == 200
@ -133,17 +133,17 @@ def test_quiz_submit_creates_attempt(client):
def test_quiz_retry_same_attempt_id_idempotent(client):
payload = _payload()
first = client.post("/api/lesson-progress", json=payload)
first = client.post("/lesson-progress", json=payload)
assert first.status_code == 200
assert first.get_json()["idempotent"] is False
second = client.post("/api/lesson-progress", json=payload)
second = client.post("/lesson-progress", json=payload)
assert second.status_code == 200
assert second.get_json()["idempotent"] is True
def test_quiz_focus_lost_terminated_accepted(client):
resp = client.post("/api/lesson-progress", json=_payload())
resp = client.post("/lesson-progress", json=_payload())
assert resp.status_code == 200
assert resp.get_json()["success"] is True
@ -156,7 +156,7 @@ def test_quiz_beacon_format_accepted(client):
_payload({"status": "terminated", "termination_reason": "page_unload", "score": "0/4"})
)
resp = client.post(
"/api/lesson-progress",
"/lesson-progress",
data=payload,
content_type="application/json",
)
@ -165,14 +165,14 @@ def test_quiz_beacon_format_accepted(client):
def test_quiz_invalid_status_rejected(client):
resp = client.post(
"/api/lesson-progress", json=_payload({"status": "cheated"})
"/lesson-progress", json=_payload({"status": "cheated"})
)
assert resp.status_code == 400
def test_quiz_focus_lost_must_be_terminated(client):
resp = client.post(
"/api/lesson-progress",
"/lesson-progress",
json=_payload({"status": "submitted", "termination_reason": "focus_lost", "score": "4/4"}),
)
assert resp.status_code == 400
@ -180,7 +180,7 @@ def test_quiz_focus_lost_must_be_terminated(client):
def test_quiz_submitted_requires_null_reason(client):
resp = client.post(
"/api/lesson-progress",
"/lesson-progress",
json=_payload({"status": "submitted", "termination_reason": "user_exit"}),
)
assert resp.status_code == 400
@ -188,62 +188,62 @@ def test_quiz_submitted_requires_null_reason(client):
def test_quiz_invalid_reason_rejected(client):
resp = client.post(
"/api/lesson-progress", json=_payload({"termination_reason": "minimize_app"})
"/lesson-progress", json=_payload({"termination_reason": "minimize_app"})
)
assert resp.status_code == 400
def test_quiz_invalid_score_rejected(client):
resp = client.post(
"/api/lesson-progress", json=_payload({"score": "9/4"})
"/lesson-progress", json=_payload({"score": "9/4"})
)
assert resp.status_code == 400
def test_quiz_malformed_attempt_id_rejected(client):
resp = client.post(
"/api/lesson-progress", json=_payload({"attempt_id": "bukan-uuid"})
"/lesson-progress", json=_payload({"attempt_id": "bukan-uuid"})
)
assert resp.status_code == 400
def test_quiz_invalid_timestamp_rejected(client):
resp = client.post(
"/api/lesson-progress", json=_payload({"occurred_at": "kemarin-sore"})
"/lesson-progress", json=_payload({"occurred_at": "kemarin-sore"})
)
assert resp.status_code == 400
def test_quiz_negative_visibility_count_rejected(client):
resp = client.post(
"/api/lesson-progress", json=_payload({"visibility_event_count": -1})
"/lesson-progress", json=_payload({"visibility_event_count": -1})
)
assert resp.status_code == 400
def test_quiz_malformed_json_rejected(client):
resp = client.post(
"/api/lesson-progress", data="{rusak", content_type="application/json"
"/lesson-progress", data="{rusak", content_type="application/json"
)
assert resp.status_code == 400
def test_quiz_invalid_token_rejected(client):
resp = client.post(
"/api/lesson-progress", json=_payload({"token": "TOKEN_SALAH"})
"/lesson-progress", json=_payload({"token": "TOKEN_SALAH"})
)
assert resp.status_code == 401
def test_quiz_unknown_lesson_rejected(client):
resp = client.post(
"/api/lesson-progress", json=_payload({"lesson_name": "tidak_ada"})
"/lesson-progress", json=_payload({"lesson_name": "tidak_ada"})
)
assert resp.status_code == 404
def test_quiz_response_contains_no_raw_token(client):
resp = client.post("/api/lesson-progress", json=_payload())
resp = client.post("/lesson-progress", json=_payload())
assert resp.status_code == 200
text = resp.get_data(as_text=True)
assert STUDENT_TOKEN not in text
@ -251,34 +251,34 @@ def test_quiz_response_contains_no_raw_token(client):
def test_quiz_error_does_not_leak_internal_details(client):
resp = client.post(
"/api/lesson-progress", json=_payload({"attempt_id": "bukan-uuid"})
"/lesson-progress", json=_payload({"attempt_id": "bukan-uuid"})
)
assert resp.status_code == 400
assert "traceback" not in resp.get_data(as_text=True).lower()
# ── GET /api/lesson-progress/<lesson_name> (review-after-refresh) ──
# ── GET /lesson-progress/<lesson_name> (review-after-refresh) ──
def test_get_progress_requires_token(client):
resp = client.get(f"/api/lesson-progress/{LESSON}")
resp = client.get(f"/lesson-progress/{LESSON}")
assert resp.status_code == 400
def test_get_progress_invalid_token_rejected(client):
resp = client.get(f"/api/lesson-progress/{LESSON}?token=TOKEN_SALAH")
resp = client.get(f"/lesson-progress/{LESSON}?token=TOKEN_SALAH")
assert resp.status_code == 401
def test_get_progress_unknown_lesson_rejected(client):
resp = client.get("/api/lesson-progress/tidak_ada?token=" + STUDENT_TOKEN)
resp = client.get("/lesson-progress/tidak_ada?token=" + STUDENT_TOKEN)
assert resp.status_code == 404
def test_get_progress_returns_audit_after_quiz(client):
payload = _payload({"status": "submitted", "termination_reason": None, "score": "3/4"})
client.post("/api/lesson-progress", json=payload)
client.post("/lesson-progress", json=payload)
resp = client.get(f"/api/lesson-progress/{LESSON}?token={STUDENT_TOKEN}")
resp = client.get(f"/lesson-progress/{LESSON}?token={STUDENT_TOKEN}")
assert resp.status_code == 200
body = resp.get_json()
assert body["success"] is True

View File

@ -29,14 +29,14 @@ def _seed(seed_demo_users):
def _post_exercise(client, lesson="hello_world"):
return client.post(
"/api/lesson-progress",
"/lesson-progress",
json={"token": STUDENT_TOKEN, "lesson_name": lesson, "type": "exercise"},
)
def _post_quiz(client, score, answers=None, lesson="hello_world"):
return client.post(
"/api/lesson-progress",
"/lesson-progress",
json={
"token": STUDENT_TOKEN,
"lesson_name": lesson,
@ -61,14 +61,14 @@ def test_lesson_progress_exercise_updates(client):
def test_lesson_progress_invalid_token(client):
resp = client.post(
"/api/lesson-progress",
"/lesson-progress",
json={"token": "TOKEN_SALAH", "lesson_name": "hello_world", "type": "exercise"},
)
assert resp.status_code == 401
def test_lesson_progress_requires_fields(client):
resp = client.post("/api/lesson-progress", json={"token": STUDENT_TOKEN})
resp = client.post("/lesson-progress", json={"token": STUDENT_TOKEN})
assert resp.status_code == 400