refactor(routes): remove /api/lesson-progress aliases and standardize tests to /lesson-progress
This commit is contained in:
parent
41c434f064
commit
ac3fdb8dff
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue