레벨테스트 신청

TESOL-LEVEL-TEST · /tesol-lab/level-test

실제 화면 열기 ↗
기능
8
API 호출
1
가드레일
8
id 가 테스트 이름
구현 단계
7 / 7
완료시험 · 평가wvctesol-api :8080서버 컴포넌트24

개발 스펙

이 화면에서 할 수 있어야 하는 것 (4)

  • STEP01 신청 — 비로그인으로 성명 · 연락처 · 이메일 · 카카오ID · 문의내용 · 상담시간대 · 약관동의를 넣는다.
  • STEP02 응시 — 객관식 10문항(보기 선택) + 주관식 5문항(단어 배열)을 푼다. 합쳐서 15문항이 만점이다.
  • 제출하면 정답 수를 보여주고 컨설팅 안내로 이어진다.
  • 문항 수는 DB(level_test_list q1~q20)에 채워진 만큼 따른다. 하드코딩하지 않는다.

비즈니스 규칙 (11)

  • 비로그인으로 신청하고 응시한다.
    근거: tesolLab/leveltest.php
  • 신청은 apply 테이블에 type=tfrm 으로 쌓인다(현재 6,483건).
    근거: leveltest_v_01.php:111 hidden ap_type
  • 문의내용 · 카카오ID · 상담요일 · 상담시간은 apply 의 content · kakaoid · day_type · time_range 에 들어간다.
    근거: leveltest_v_01.php:119~157, leveltest_form_v_01_proc.php:247
  • q1~q10 은 객관식, q11~q20 은 주관식이다. 문항 성격을 번호로 가른다 — 보기 유무로 판정하면 안 된다.
    근거: leveltest_form_v_01.php:1002(`$x < 11`) · 1051(`$k = 11; $k < 21`)
  • 주관식도 정답이 있어 함께 채점한다. 만점은 객관식 + 주관식 = 15점이다.
    근거: leveltest_form_v_01_proc.php:147 — E[] 하나로 순회하며 ltRight$i 판정
  • 답안은 객관식 · 주관식 구분 없이 level_test_answer 한 행의 a1~a20 에 저장한다. 실데이터 295건 중 294건이 a11 이상을 채우고 있다.
    근거: leveltest_form_v_01_proc.php:145
  • level_test_poll_answer 는 **설문(Polls)** 용이다. 주관식 답안을 여기 넣으면 안 된다.
    근거: leveltest_form_v_01_proc.php:171 — $clean_post["Polls"]
  • 채점은 공백을 전부 지우고 비교한다. 대소문자와 문장부호는 구분한다.
    근거: leveltest_form_v_01_proc.php:152 `str_replace(" ", "", ...)`
  • 정오는 원본과 같이 right1~right20 에 O · X 로 남긴다.
    근거: leveltest_form_v_01_proc.php:157
  • 같은 접수번호로 두 번 제출할 수 없다.
    근거: leveltest_form_v_01_proc.php:141 "이미 등록된 답안지입니다."
  • 객관식 보기는 한 칸에 콤마(또는 ^)로 이어져 있어 분해해 쓴다: "intersect, collapse, distribute, coexist".
    근거: level_test_examples.exam1~exam20, leveltest_form_v_01.php:1017

API (3)

메서드경로권한모듈 · 설명
GET/api/assessment/level-test공개
문항. questions(객관식) · subjectiveQuestions(주관식) · total(만점). 정답은 담기지 않는다.
domains:assessment:assessment-web
404 LEVEL_TEST_NOT_FOUND
POST/api/assessment/level-test/applications공개
STEP01 신청 접수 → applyId 반환.
domains:assessment:assessment-web
400 INVALID_INPUT
POST/api/assessment/level-test/submissions공개
답안 제출(객관식+주관식 통합) → { rightCount, total }.
domains:assessment:assessment-web
400 ALREADY_SUBMITTED

가드레일 (8) — id 가 곧 테스트 이름이다

id반드시 만족해야 하는 것등급
leveltest-no-login-required
비로그인 상태에서 신청과 응시가 모두 가능해야 한다.
high
leveltest-step2-exists
신청 후 실제 문제 풀이 화면으로 이어져야 한다. 신청 폼만 있고 끝나면 안 된다.
원본 tesolLab/leveltest_form_v_01.php 대응.
high
leveltest-answer-not-leaked
문항 조회 응답에 정답이 절대 실리면 안 된다.
critical
leveltest-subjective-graded
주관식도 채점 대상이다. 만점이 객관식 개수(10)로 잘리면 안 된다 — 15 여야 한다.
실데이터 level_test_answer.right_count 최대 15.
critical
leveltest-subjective-in-answer-table
주관식 답안이 level_test_answer.a11~a20 에 저장돼야 한다. level_test_poll_answer 는 설문용이므로 쓰면 안 된다.
critical
leveltest-whitespace-tolerant
주관식 답의 띄어쓰기가 달라도 정답으로 인정해야 한다. 공백을 모두 지우고 비교한다.
high
leveltest-no-resubmit
같은 접수번호로 두 번째 제출은 거절해야 한다.
high
leveltest-question-count-from-db
문항 수는 DB 에 채워진 개수를 따른다. 20개 고정으로 렌더해 빈 문항이 나오면 안 된다.
high

구현 계획 (7/7)

  1. LevelTest(객관식·주관식 분리) · LevelTestResult · LevelTestGrader · LevelTestRepository 정의assessment-domain
  2. LevelTestUseCase — paper · apply · submit(재제출 차단 포함)assessment-app
  3. LevelTestRepositoryAdapter — q1~q20 평탄화 컬럼, 번호로 객관식/주관식 분기assessment-infrastructure
  4. LevelTestController — 조회 · 신청 · 제출 3 엔드포인트assessment-web
  5. STEP01 신청 폼 실동작 전환(목업 readOnly 제거)wvctesol
  6. STEP02 응시 화면 · 결과 화면 신설wvctesol
  7. Next.js 를 BFF 로 전환 — Prisma 직접 접근 제거wvctesol

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

객체모듈책임 · 지켜야 할 것
LevelTest
record
assessment-domain
레벨테스트 시험지. 문항만 담고 정답은 담지 않는다.
  • questions(객관식) 와 subjectiveQuestions(주관식) 를 나눠 가진다 — 화면이 다르게 그려야 한다.
  • CHOICE_LAST = 10 · MAX = 20 상수로 원본의 두 루프 경계를 그대로 표현한다.
  • totalQuestions() 가 만점이다. 객관식 개수로 잘라 쓰면 안 된다.
  • 정답 필드를 두지 않는다. 가드레일 leveltest-answer-not-leaked 를 타입으로 보장한다.
LevelTestGrader
class
assessment-domain
객관식 · 주관식 통합 채점.
  • grade(answers, correct) → LevelTestResult(rightCount, total, corrects).
  • 공백을 **전부** 제거하고 비교한다 — 원본 str_replace(" ", "") 와 같다.
  • 대소문자와 문장부호는 구분한다. 원본이 그렇다.
  • 정답이 빈 칸이면 그 문항은 항상 오답으로 둔다 — 채점 대상이 아니라는 뜻이다.
  • 정답 목록보다 답안이 짧아도 예외를 던지지 않는다.
LevelTestRepository
interface
assessment-domain
저장소 포트.
  • findActive() · correctAnswersOf(id) · createApplication(Applicant) · alreadySubmitted(testId, applyId) · saveAnswers(...).
  • Applicant 는 원본 STEP01 이 받는 항목 그대로 — name · phone · email · inquiry · kakaoId · dayType · timeRange · marketingAgreed · regip.
  • 주관식 전용 저장 메서드를 두지 않는다 — 답안은 한 행에 함께 들어간다.
  • correctAnswersOf 는 도메인 밖으로 나가지 않는다 — UseCase 안에서만 쓰인다.
LevelTestUseCase
service
assessment-app
조회 · 신청 · 제출 유스케이스.
  • paper() 는 readOnly 트랜잭션. 없으면 NotFoundException(LEVEL_TEST_NOT_FOUND).
  • apply() 는 성명 · 연락처가 비면 DomainException(INVALID_INPUT).
  • submit() 은 채점 전에 alreadySubmitted 를 먼저 확인해 DomainException(ALREADY_SUBMITTED) 를 던진다.
  • 인증을 요구하지 않는다 — 가드레일 leveltest-no-login-required.
LevelTestRepositoryAdapter
class
assessment-infrastructure
q1~q20 로 펼쳐진 레거시 컬럼을 다루는 유일한 지점.
  • MAX=20 을 기준으로 컬럼 이름을 만들어 쓴다(numbered("q") → "q1, q2, ... q20").
  • findActive 는 빈 문항을 건너뛰고, 번호가 CHOICE_LAST 이하면 객관식 · 초과면 주관식으로 나눈다.
  • 보기는 콤마로 이어진 한 칸이므로 split 해서 넘긴다. 주관식에는 빈 리스트를 준다.
  • createApplication 은 apply 에 type=tfrm 으로 넣고 content 가 비면 "레벨테스트 신청" 을 채운다.
  • apply 의 NOT NULL 컬럼(kakaoid · history · mod_* 등)에 빈 문자열을 반드시 넣는다 — 기본값이 없다.
  • saveAnswers 는 a1~a20 · right1~right20 을 한 번에 채운다. 주관식도 같은 행이다.
  • 정오는 O · X 문자열로 저장한다. 원본 표기를 그대로 따른다.
  • 조회 실패는 예외 대신 Optional.empty · 빈 리스트로 흡수한다 — 마케팅 페이지가 500 으로 죽지 않게.
LevelTestController
controller
assessment-web
HTTP 경계. 전부 비인증.
  • GET /api/assessment/level-test → PaperResponse(id, title, total, questions, subjectiveQuestions).
  • POST /applications → ApplyResponse(applyId). 연락처는 숫자만 남겨 저장한다.
  • POST /submissions → ResultResponse(rightCount, total). answers 는 객관식 → 주관식 순의 단일 배열이다.
  • X-Forwarded-For 첫 값을 regip 으로 쓰되 15자를 넘으면 버린다 — regip 이 varchar(15).
  • ApplyRequest 의 name · phone 에 @NotBlank 를 건다.

테스트 방법 (8)

가드레일종류확인 방법
leveltest-no-login-requirede2e쿠키 없이 신청 · 문항 조회 · 제출 3 단계가 모두 200
leveltest-step2-existse2eSTEP01 제출 후 /tesol-lab/level-test/exam?apply=<id> 로 이동하고 객관식 10 + 주관식 5 가 렌더된다
leveltest-answer-not-leakedintegrationGET /api/assessment/level-test 응답 JSON 에 answer · correct 계열 필드가 없다
leveltest-subjective-gradedintegration정답 15개를 그대로 제출 → rightCount=15, total=15
leveltest-subjective-in-answer-tableintegration제출 후 level_test_answer 의 a11~a15 와 right11~right15 가 채워진다
leveltest-whitespace-tolerantunit"I want to buy a couple of things." 를 제출해도 정답 처리
leveltest-no-resubmitintegration같은 applyId 로 재제출 → 400 ALREADY_SUBMITTED
leveltest-question-count-from-dbe2e응시 화면의 입력 name 이 a_1~a_15 만 존재하고 a_16 이상은 없다

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

  • 원본에는 응시 후 **설문(Polls)** 단계가 더 있다(Frame46.png 이후, level_test_poll · level_test_poll_answer). 아직 구현하지 않았다 — 별도 작업으로 뺄지 결정 필요.
  • 원본에 step1~4 분할 버전도 있지만 step5.php 가 없어 미완성이다. 운영본은 leveltest_form_v_01.php.

스냅샷

TESOL-LEVEL-TEST 스냅샷

기능 목록 (8)

데이터
  • ·API 조회/api/marketing/content/tesol-lab/level-test
입력
  • ·1개
  • ·제출 버튼1개
  • ·입력 필드8개
액션
  • ·서버 액션applyForLevelTest
상태
  • ·화면 상태prefix, mid, last
하드코딩
  • ·하드코딩 데이터phone
  • ·하드코딩 데이터answers

화면 이동

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

연결

API
  • /api/marketing/content/tesol-lab/level-test
소스
src/app/tesol-lab/level-test/page.tsx