diff --git a/routes/lesson_progress.py b/routes/lesson_progress.py index 863384a..61252f4 100644 --- a/routes/lesson_progress.py +++ b/routes/lesson_progress.py @@ -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/", methods=["GET"]) -@lesson_progress_bp.route("/api/lesson-progress/", methods=["GET"]) def get_lesson_progress(lesson_name: str): """Ambil progress lesson + audit attempt kuis (review-after-refresh). diff --git a/services/tests/test_composite_flow.py b/services/tests/test_composite_flow.py index 08d81ac..5701704 100644 --- a/services/tests/test_composite_flow.py +++ b/services/tests/test_composite_flow.py @@ -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, diff --git a/services/tests/test_lesson_progress_routes.py b/services/tests/test_lesson_progress_routes.py index 456e4db..16ab0a3 100644 --- a/services/tests/test_lesson_progress_routes.py +++ b/services/tests/test_lesson_progress_routes.py @@ -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/ (review-after-refresh) ── +# ── GET /lesson-progress/ (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 diff --git a/services/tests/test_progress_routes.py b/services/tests/test_progress_routes.py index 7647fe8..5763b79 100644 --- a/services/tests/test_progress_routes.py +++ b/services/tests/test_progress_routes.py @@ -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