TESOL 시험 응시

CLASS-UNIT-QUIZ-DETAIL · /my-class/unit-quiz/[unit]/[kind]

기능
8
API 호출
0
가드레일
9
id 가 테스트 이름
구현 단계
6 / 6
완료시험 · 평가wvctesol-api :8080로그인 필요서버 컴포넌트동적 라우트30

개발 스펙

이 화면에서 할 수 있어야 하는 것 (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)

  1. Exam · Question · ExamKind · ExamEligibility · Graderassessment-domain
  2. TakeExamUseCase · SubmitExamUseCaseassessment-app
  3. exam · exam_question(jsonb) · quiz_result 어댑터assessment-infrastructure
  4. GET /exams/{course}/{unit}/{kind}assessment-web
  5. POST /exams/{course}/{unit}/{kind}/submitassessment-web
  6. Next.js 를 BFF 로 전환 — Prisma 직접 접근 제거wvctesol

서버 사이드 구현 계획 — 자바 객체별 요구사항 (12/12)

객체모듈책임 · 지켜야 할 것
ExamEligibility
class
assessment-domain
응시 자격 판정. 원본 unitSloving.php:23~91 의 5단 검사.
  • 원본은 화면(unitSloving)과 제출(quiz_proc)에 같은 검사를 복붙해 두었다. 여기 한 곳에 모아 두 경로에서 모두 부른다.
  • 검사 순서를 지킨다: 오픈플래그 → 이전유닛 통과 → 차수 순서 → 중복 응시.
  • 자격 부족은 ForbiddenException(403), 이미 처리된 건 ConflictException(409) 으로 구분한다.
Grader
class
assessment-domain
채점. 원본 quiz_proc.php:120~165.
  • 문항당 5점, 70점 이상 합격. 이 값은 도메인이 갖는다 — DB 의 pass_score 를 신뢰하지 않는다.
  • 복수정답은 고른 집합이 정답 집합과 완전히 일치해야 득점한다. 부분·과다 선택 모두 0점.
  • 미응답이 하나라도 있으면 저장 전에 예외를 던진다.
Question
record
assessment-domain
문항과 채점 판정.
  • multiple() 은 정답 개수로 판단한다 — 화면이 radio 냐 checkbox 냐를 이걸로 정한다.
  • Choice.correct 는 응시 화면으로 내보내지 않는다.
UnitProgress
record
assessment-domain
quiz_result 한 행 — 유닛의 차수별 점수와 통과 여부.
  • passed 는 유닛 단위다. 차수별 합격 판정에 쓰면 0점인 차수가 PASS 로 보인다.
TakeExamUseCase
service
assessment-app
문제지 조회.
  • Choice → PaperChoice 변환에서 correct 를 뺀다. 이 변환이 정답 유출을 막는 지점이다.
  • 자격 검사를 통과한 뒤에만 시험지를 읽는다.
SubmitExamUseCase
service
assessment-app
채점·저장.
  • 조회와 똑같은 자격 검사를 다시 한다. 직접 호출도 막혀야 한다.
  • @Transactional 안에서 채점→저장한다. 미응답 예외가 나면 아무것도 남지 않는다.
ExamQuestionJpaEntity
entity
assessment-infrastructure
exam_question 매핑.
  • choices 는 jsonb 다. hypersistence-utils 의 JsonType 으로 List<Map> 으로 받는다.
  • question_text · explanation 은 text 라 columnDefinition 을 명시한다.
ExamAttemptRepositoryAdapter
class
assessment-infrastructure
quiz_result · quiz_result_answer 저장·조회.
  • 첫 차수면 새 행, 재응시면 기존 행의 해당 차수만 채운다(quiz_proc.php:166,181).
  • 복수정답은 picked 를 콤마로 이어 저장한다. answer 가 varchar(20) 이라 담긴다.
  • kind_a/b/c 문자열을 UnitProgress 로 옮기는 것이 이 어댑터의 몫이다.
EntitlementReaderAdapter
class
assessment-infrastructure
수강 자격 조회.
  • member 테이블을 읽지만 도메인은 그 사실을 모른다 — 포트로 감싼다.
  • 읽기 전용이다. assessment 가 회원 정보를 바꾸면 안 된다.
AssessmentController
controller
assessment-web
시험 API.
  • 회원 id 는 @RequestAttribute 로 토큰에서 받는다. 파라미터로 받으면 남의 시험을 칠 수 있다.
  • regip 은 varchar(15) 라 IPv6 면 비운다.
TokenAuthFilter
class
servers:wvctesol-api
토큰 검증 후 memberId 를 요청에 심는다.
  • API 서버는 검증만 한다. 발급은 인증 서버 몫이다.
  • 두 서버가 같은 JWT_SECRET 을 써야 한다.
  • 토큰이 없으면 속성을 심지 않고 통과시킨다 — 보호가 필요한 곳에서 401 이 된다.
ExamKind
enum
assessment-domain
응시 차수.
  • TESOL 은 A·B·C, TEC 은 A·B. from(course, raw) 이 과정에 없는 차수를 거른다.

테스트 방법 (9)

가드레일종류확인 방법
quiz-guard-reapplies-on-submite2equiztester 로 UNIT1-A 를 이미 친 상태에서 제출 API 를 직접 POST → 403/409 이고 quiz_result 가 늘지 않는다
quiz-no-answer-leake2e문제지 조회 응답 JSON 을 문자열로 훑어 "correct" 키가 하나도 없음을 단언
quiz-pass-score-70unit정답 14개(70점) 제출 → passed=true, 13개(65점) 제출 → passed=false
quiz-sequential-unit-lockunitUNIT1 미통과 계정으로 UNIT2 응시 요청 → 403 PREV_UNIT_NOT_PASSED
quiz-kind-orderunit응시 기록 없는 UNIT3 에 kind=B 요청 → 403 KIND_ORDER
quiz-no-retake-after-passe2eUNIT1 통과 계정으로 UNIT1 kind=B 요청 → 409 ALREADY_PASSED
quiz-multi-answer-exact-matchunitTEC 2-B Q14 정답이 b,d 일 때 b 만 제출 → 해당 문항 0점
quiz-multi-answer-persiste2eb,d 제출 후 quiz_result_answer.answer 를 조회 → "b,d" 그대로 저장
quiz-all-questions-requirede2e19개만 채워 제출 → 400 UNANSWERED, quiz_result 미생성

정하지 못한 것 · 확인이 필요한 것

  • 제한시간을 서버가 강제하지 않는다. 원본도 클라이언트 타이머뿐이라 새로고침하면 리셋된다. 서버 강제를 하려면 응시 시작 시각을 저장하는 스키마가 필요하다.

스냅샷

로그인 세션으로 촬영한 실제 화면입니다.

CLASS-UNIT-QUIZ-DETAIL 스냅샷

기능 목록 (8)

입력
  • ·1개
  • ·제출 버튼1개
  • ·입력 필드4개
액션
  • ·서버 액션submitQuiz
상태
  • ·화면 상태started, remaining, warning, answers, validationError
인증
  • ·세션 확인
  • ·로그인 필요proxy 가 /login 으로 보냄
하드코딩
  • ·하드코딩 데이터tabs

화면 이동

← 이 화면으로 오는 곳
이 화면에서 가는 곳 →

연결

API

직접 호출하는 API 가 없습니다

소스
src/app/my-class/unit-quiz/[unit]/[kind]/page.tsx