TESOL 시험 응시
CLASS-UNIT-QUIZ-DETAIL · /my-class/unit-quiz/[unit]/[kind]
기능
8
API 호출
0
가드레일
9
id 가 테스트 이름
구현 단계
6 / 6
개발 스펙
이 화면에서 할 수 있어야 하는 것 (5)
- 수강생이 열려 있는 유닛의 시험을 친다.
- 진입하면 시험 안내 모달이 뜨고, 시작을 눌러야 타이머가 돈다.
- 남은 시간과 작성 진행(n/20)을 항상 볼 수 있다.
- 30분 · 10분 남았을 때 경고를 받는다.
- 모든 문항에 답해야 제출할 수 있고, 제출 전 확인을 거친다.
비즈니스 규칙 (8)
- 문항당 5점 × 20문항 = 100점. 70점 이상이면 합격.근거: quiz_proc.php:133,159
- 제한 시간 60분.근거: unitSloving.php:595 totalSeconds=3600
- lecture_start_yn(결제+준비완료) 와 quiz_start_yn(사이버승인+수강기간 내) 이 모두 Y 라야 응시할 수 있다.근거: _Env.php:426~443
- UNIT n 은 UNIT n-1 을 통과해야 열린다.근거: unitSloving.php:46~54
- 차수는 A → B → C 순서로만 열린다. 기록이 없으면 A 만, 직전 차수를 마쳐야 다음 차수가 열린다.근거: unitSloving.php:57~91
- 이미 통과한 유닛은 다시 칠 수 없다.근거: unitSloving.php:64
- 이미 친 차수는 다시 칠 수 없다.근거: unitSloving.php:68
- 정답이 2개 이상인 문항은 checkbox 로 렌더하고, 고른 집합이 정답 집합과 완전히 일치해야 득점한다.근거: unitSloving_tec.php:484 / quiz_proc_tec.php:182
API (2)
| 메서드 | 경로 | 권한 | 모듈 · 설명 |
|---|---|---|---|
GET | /api/assessment/exams/{course}/{unit}/{kind} | 수강생 | 응시할 문제지를 받는다. 정답은 내려주지 않는다. domains:assessment:assessment-web응답: { unitTitle, timeLimitSeconds, questions: [{ number, text, multiple, choices: [{key, text}] }] } 403 EXAM_NOT_OPEN — 오픈 플래그 미충족 403 PREV_UNIT_NOT_PASSED — 이전 유닛 미통과 403 KIND_ORDER — 차수 순서 위반 409 ALREADY_PASSED / ALREADY_TAKEN 404 EXAM_NOT_FOUND — 해당 유닛·차수 시험 없음 |
POST | /api/assessment/exams/{course}/{unit}/{kind}/submit | 수강생 | 답안 제출 · 채점 · 저장. 응시 자격을 다시 검사한다. domains:assessment:assessment-web요청: { answers: [{ number, picked: string[] }] } 응답: { score, passed, resultUrl } 400 UNANSWERED — 미응답 문항 있음 GET 과 동일한 403/409 계열 |
가드레일 (9) — id 가 곧 테스트 이름이다
| id | 반드시 만족해야 하는 것 | 등급 |
|---|---|---|
quiz-guard-reapplies-on-submit | 제출 API 는 조회 API 와 같은 자격 검사를 다시 수행해야 한다. 화면을 거치지 않은 직접 호출도 막혀야 한다. quiz_proc.php:18~118 이 unitSloving.php 의 검사를 그대로 복붙해 둔 이유. | critical |
quiz-no-answer-leak | 문제지 조회 응답에 정답(correct)이 절대 포함되면 안 된다. | critical |
quiz-pass-score-70 | 합격선은 70점이다. 69점은 불합격, 70점은 합격이어야 한다. quiz_proc.php:159 | critical |
quiz-sequential-unit-lock | UNIT n-1 을 통과하지 않은 상태로 UNIT n 응시를 요청하면 거부해야 한다. | critical |
quiz-kind-order | 기록이 없는 유닛에 B · C 차수를 바로 요청하면 거부해야 한다. | critical |
quiz-no-retake-after-pass | 이미 통과한 유닛은 남은 차수가 있어도 응시가 거부돼야 한다. | high |
quiz-multi-answer-exact-match | 복수정답 문항은 부분 정답에 점수를 주면 안 된다. 정답 집합과 완전히 일치할 때만 5점. | critical |
quiz-multi-answer-persist | 복수정답은 "b,d" 형태로 저장돼야 한다. 첫 글자만 남으면 안 된다. quiz_result_answer.answer 가 char(1) 이었어서 잘리던 자리. varchar(20) 으로 넓혔다. | high |
quiz-all-questions-required | 미응답 문항이 하나라도 있으면 저장 없이 거부해야 한다. quiz_proc.php:146 | high |
구현 계획 (6/6)
- ●Exam · Question · ExamKind · ExamEligibility · Grader
assessment-domain - ●TakeExamUseCase · SubmitExamUseCase
assessment-app - ●exam · exam_question(jsonb) · quiz_result 어댑터
assessment-infrastructure - ●GET /exams/{course}/{unit}/{kind}
assessment-web - ●POST /exams/{course}/{unit}/{kind}/submit
assessment-web - ●Next.js 를 BFF 로 전환 — Prisma 직접 접근 제거
wvctesol
서버 사이드 구현 계획 — 자바 객체별 요구사항 (12/12)
| 객체 | 모듈 | 책임 · 지켜야 할 것 |
|---|---|---|
● ExamEligibilityclass | assessment-domain | 응시 자격 판정. 원본 unitSloving.php:23~91 의 5단 검사.
|
● Graderclass | assessment-domain | 채점. 원본 quiz_proc.php:120~165.
|
● Questionrecord | assessment-domain | 문항과 채점 판정.
|
● UnitProgressrecord | assessment-domain | quiz_result 한 행 — 유닛의 차수별 점수와 통과 여부.
|
● TakeExamUseCaseservice | assessment-app | 문제지 조회.
|
● SubmitExamUseCaseservice | assessment-app | 채점·저장.
|
● ExamQuestionJpaEntityentity | assessment-infrastructure | exam_question 매핑.
|
● ExamAttemptRepositoryAdapterclass | assessment-infrastructure | quiz_result · quiz_result_answer 저장·조회.
|
● EntitlementReaderAdapterclass | assessment-infrastructure | 수강 자격 조회.
|
● AssessmentControllercontroller | assessment-web | 시험 API.
|
● TokenAuthFilterclass | servers:wvctesol-api | 토큰 검증 후 memberId 를 요청에 심는다.
|
● ExamKindenum | assessment-domain | 응시 차수.
|
테스트 방법 (9)
| 가드레일 | 종류 | 확인 방법 |
|---|---|---|
quiz-guard-reapplies-on-submit | e2e | quiztester 로 UNIT1-A 를 이미 친 상태에서 제출 API 를 직접 POST → 403/409 이고 quiz_result 가 늘지 않는다 |
quiz-no-answer-leak | e2e | 문제지 조회 응답 JSON 을 문자열로 훑어 "correct" 키가 하나도 없음을 단언 |
quiz-pass-score-70 | unit | 정답 14개(70점) 제출 → passed=true, 13개(65점) 제출 → passed=false |
quiz-sequential-unit-lock | unit | UNIT1 미통과 계정으로 UNIT2 응시 요청 → 403 PREV_UNIT_NOT_PASSED |
quiz-kind-order | unit | 응시 기록 없는 UNIT3 에 kind=B 요청 → 403 KIND_ORDER |
quiz-no-retake-after-pass | e2e | UNIT1 통과 계정으로 UNIT1 kind=B 요청 → 409 ALREADY_PASSED |
quiz-multi-answer-exact-match | unit | TEC 2-B Q14 정답이 b,d 일 때 b 만 제출 → 해당 문항 0점 |
quiz-multi-answer-persist | e2e | b,d 제출 후 quiz_result_answer.answer 를 조회 → "b,d" 그대로 저장 |
quiz-all-questions-required | e2e | 19개만 채워 제출 → 400 UNANSWERED, quiz_result 미생성 |
정하지 못한 것 · 확인이 필요한 것
- 제한시간을 서버가 강제하지 않는다. 원본도 클라이언트 타이머뿐이라 새로고침하면 리셋된다. 서버 강제를 하려면 응시 시작 시각을 저장하는 스키마가 필요하다.
스냅샷
로그인 세션으로 촬영한 실제 화면입니다.

기능 목록 (8)
입력
- ·폼
1개 - ·제출 버튼
1개 - ·입력 필드
4개
액션
- ·서버 액션
submitQuiz
상태
- ·화면 상태
started, remaining, warning, answers, validationError
인증
- ·세션 확인
- ·로그인 필요
proxy 가 /login 으로 보냄
하드코딩
- ·하드코딩 데이터
tabs
화면 이동
← 이 화면으로 오는 곳
CLASS-ONLINE/my-class/online
이 화면에서 가는 곳 →
CLASS/my-classCLASS-TEC-ONLINE/my-class/tec-onlineCLASS-UNIVERSITY-REGISTER/my-class/university-registerAUTH-LOGIN/loginCLASS-UNIT-RESULT-DETAIL/my-class/unit-result/[unit]/[kind]
연결
API
직접 호출하는 API 가 없습니다
소스
src/app/my-class/unit-quiz/[unit]/[kind]/page.tsx