### Health
GET http://localhost:8000/health

### Courses - list
GET http://localhost:8000/api/courses?page=1&pageSize=20

### Courses - detail
GET http://localhost:8000/api/courses/c1

### Courses - detail 404
GET http://localhost:8000/api/courses/unknown

### Modules - progress update
POST http://localhost:8000/api/modules/m1/progress
Content-Type: application/json

{
  "progressPercent": 50
}

### Assignments - submission requires idempotencyKey (400)
POST http://localhost:8000/api/assignments/a1/submission
Content-Type: application/json

{
  "content": "Answer"
}

### Assignments - submission created (201)
POST http://localhost:8000/api/assignments/a1/submission
Content-Type: application/json

{
  "content": "Answer",
  "idempotencyKey": "abc"
}

### Assignments - duplicate idempotency (409)
POST http://localhost:8000/api/assignments/a1/submission
Content-Type: application/json

{
  "content": "Answer",
  "idempotencyKey": "dup-key"
}

### Exam Session - create requires fields (400)
POST http://localhost:8000/api/exam-session
Content-Type: application/json

{}

### Exam Session - create (201)
POST http://localhost:8000/api/exam-session
Content-Type: application/json

{
  "examId": "exam-001",
  "userId": "u1"
}

### Exam Session - score answers (200)
POST http://localhost:8000/api/exam-session/{{sessionId}}/score
Content-Type: application/json
Authorization: Bearer test-token

{
  "idempotencyKey": "score-1",
  "mode": "batch",
  "answers": [
    { "questionId": "mcq-1", "type": "enhanced_mcq", "choiceIndex": 2, "confidence": 4 },
    { "questionId": "video-1", "type": "video_scenario", "scenarioAnswers": [ { "stepId": "s1", "selectedOptionId": "optA" }, { "stepId": "s2", "selectedOptionId": "optB" } ] },
    { "questionId": "hotspot-1", "type": "image_hotspot", "selectedHotspots": ["spot1", "spot3"] },
    { "questionId": "gallery-1", "type": "media_gallery", "viewedItems": ["itemA", "itemB", "itemC"] },
    { "questionId": "puzzle-1", "type": "puzzle", "matches": [ { "pieceId": "p1", "targetId": "t1" }, { "pieceId": "p2", "targetId": "t2" } ] },
    { "questionId": "scenario-1", "type": "scenario", "selectedOptionId": "optX" }
  ]
}

### Exam Session - score MCQ only (200)
POST http://localhost:8000/api/exam-session/{{sessionId}}/score
Content-Type: application/json
Authorization: Bearer test-token

{
  "idempotencyKey": "score-mcq-1",
  "mode": "batch",
  "answers": [
    { "questionId": "q1", "choiceIndex": 0 },
    { "questionId": "q2", "choiceIndex": 2 }
  ]
}

### Exam Session - duplicate idempotency (409)
POST http://localhost:8000/api/exam-session/{{sessionId}}/score
Content-Type: application/json

{
  "idempotencyKey": "score-1",
  "answers": []
}

### Exam Session - summary
GET http://localhost:8000/api/exam-session/{{sessionId}}/summary
Authorization: Bearer test-token

### Exam Session - summary example response
{
  "examId": "interactive-dapur-mbg-001",
  "examTitle": "Kuis Interaktif Dapur MBG",
  "totalQuestions": 10,
  "correctAnswers": 8,
  "score": 85,
  "timeSpent": "25 menit 30 detik",
  "completedAt": "2025-11-12T08:30:00Z",
  "answers": [
    {
      "questionId": "1",
      "question": "Apa langkah pertama dalam memastikan keamanan pangan di Dapur MBG?",
      "userAnswer": "Melakukan analisis bahaya",
      "correctAnswer": "Melakukan analisis bahaya",
      "isCorrect": true,
      "type": "enhanced_mcq",
      "details": { "choiceIndex": 0, "confidence": 4 }
    },
    {
      "questionId": "video-scenario-1",
      "question": "Analisis Situasi Dapur: Identifikasi Masalah Keamanan Pangan",
      "userAnswer": "step1: opt4; step2: opt4",
      "correctAnswer": "step1: opt4; step2: opt4",
      "isCorrect": true,
      "type": "video_scenario",
      "details": {
        "steps": [
          { "stepId": "step1", "selectedOptionId": "opt4", "isCorrect": true, "earnedPoints": 10 },
          { "stepId": "step2", "selectedOptionId": "opt4", "isCorrect": true, "earnedPoints": 10 }
        ]
      }
    },
    {
      "questionId": "hotspot-scenario-1",
      "question": "Identifikasi Area Bermasalah dalam Tata Letak Dapur",
      "userAnswer": "hotspot1,hotspot2,hotspot4",
      "correctAnswer": "hotspot1,hotspot2,hotspot3,hotspot4,hotspot5",
      "isCorrect": false,
      "type": "image_hotspot",
      "details": {
        "selectedHotspots": ["hotspot1", "hotspot2", "hotspot4"],
        "correctHotspots": ["hotspot1", "hotspot2", "hotspot3", "hotspot4", "hotspot5"]
      }
    },
    {
      "questionId": "3",
      "question": "Cocokkan istilah keamanan pangan dengan definisinya",
      "userAnswer": "ccp->def1; haccp->def2; sanitasi->def3; kontaminasi->def4",
      "correctAnswer": "ccp->def1; haccp->def2; sanitasi->def3; kontaminasi->def4",
      "isCorrect": true,
      "type": "puzzle",
      "details": {
        "matches": [ { "pieceId": "ccp", "targetId": "def1" }, { "pieceId": "haccp", "targetId": "def2" } ],
        "correctPairs": 4,
        "earnedPoints": 10
      }
    }
  ]
}


### Exams - list
GET http://localhost:8000/api/exams?page=1&pageSize=20

### Exams - filter by q
GET http://localhost:8000/api/exams?page=1&pageSize=20&q=HACCP

### Certificates - resend requires fields (400)
POST http://localhost:8000/api/certificates/abc/resend
Content-Type: application/json

{
  "idempotencyKey": "id1"
}

### Certificates - resend accepted and throttled
POST http://localhost:8000/api/certificates/abc/resend
Content-Type: application/json

{
  "recipientEmail": "x@y.com",
  "idempotencyKey": "cert-1"
}