From bb89e0c44d9ef4005a2c5dcb27500eafe1c25673 Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 20 Feb 2026 11:00:13 +0900 Subject: [PATCH 01/15] docs: add commit history to M2-M4 milestones and update commit workflow Co-Authored-By: Claude Opus 4.6 --- PLAN.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/PLAN.md b/PLAN.md index 90a1c83..ba3d4f1 100755 --- a/PLAN.md +++ b/PLAN.md @@ -15,7 +15,8 @@ ### `commit` - 완료 처리 1. **Update PLAN.md**: 완료된 항목에 체크박스 표시 -2. **Commit**: PLAN.md 변경을 포함하여 커밋한다 +2. **Update Commits**: 해당 마일스톤의 `- Commits:` 줄에 커밋 해시(short)를 추가한다 (기존 값이 있으면 쉼표로 이어붙임) +3. **Commit**: PLAN.md 변경을 포함하여 커밋한다 ### `verify` - 마일스톤 완료 후 수동 검증 @@ -103,7 +104,7 @@ bundle exec rails runner "puts 'OK'" **완료 조건:** `rails db:migrate db:seed` 성공, 스키마 확인 -- Commits: +- Commits: f9f8837, e3198e6, bc21012, 4739793, 399882d, 1645f88 --- @@ -127,7 +128,7 @@ bundle exec rails runner "puts 'OK'" **완료 조건:** 모든 유닛 테스트 통과 -- Commits: +- Commits: 1efce8a, 8ff35e0, 0d0e543, ea6caee, a30813c --- @@ -149,7 +150,7 @@ bundle exec rails runner "puts 'OK'" **완료 조건:** 신청 폼 동작, 정규화 적용, 에러 시 입력값 보존 -- Commits: +- Commits: 74d04d1, b27ac56, b133a69, 1598721, 30734ad, 95653ba, a705d01 --- From 160137369bfcf28c5b373e354c3a8450abebb2fd Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 20 Feb 2026 11:12:27 +0900 Subject: [PATCH 02/15] feat(m5): add Course#full? method Co-Authored-By: Claude Opus 4.6 --- app/models/course.rb | 4 ++++ test/models/course_test.rb | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/app/models/course.rb b/app/models/course.rb index 387983f..21592af 100644 --- a/app/models/course.rb +++ b/app/models/course.rb @@ -5,4 +5,8 @@ class Course < ApplicationRecord def remaining_slots capacity - registrations.where(status: "applied").count end + + def full? + remaining_slots <= 0 + end end diff --git a/test/models/course_test.rb b/test/models/course_test.rb index 1b22818..816c353 100644 --- a/test/models/course_test.rb +++ b/test/models/course_test.rb @@ -15,4 +15,14 @@ class CourseTest < ActiveSupport::TestCase course = courses(:five_km) assert_equal course.capacity - 1, course.remaining_slots end + + test "full? returns true when applied count >= capacity" do + course = courses(:full) + assert course.full? + end + + test "full? returns false when applied count < capacity" do + course = courses(:five_km) + assert_not course.full? + end end From cb3cf65a9674b63f9cd2261ee5cd35a7f5d32d61 Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 20 Feb 2026 11:12:44 +0900 Subject: [PATCH 03/15] docs: mark Course#full? as complete - Commits: 1601373 Co-Authored-By: Claude Opus 4.6 --- PLAN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PLAN.md b/PLAN.md index ba3d4f1..930da3c 100755 --- a/PLAN.md +++ b/PLAN.md @@ -162,7 +162,7 @@ bundle exec rails runner "puts 'OK'" **Unit Tests** -- [ ] Course#full? - applied 수 >= capacity 시 true +- [x] Course#full? - applied 수 >= capacity 시 true - [ ] Course#available? - 마감 + 정원 조합 검증 **Integration Tests (P0)** From 22a4463e2860c455467952653ea03cdfa6703dc9 Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 20 Feb 2026 11:41:28 +0900 Subject: [PATCH 04/15] feat(m5): add Course#available? and Race.upcoming scope Co-Authored-By: Claude Opus 4.6 --- app/controllers/home_controller.rb | 2 +- app/models/course.rb | 4 ++++ app/models/race.rb | 2 ++ test/fixtures/courses.yml | 7 +++++++ test/fixtures/races.yml | 6 ++++++ test/models/course_test.rb | 15 +++++++++++++++ 6 files changed, 35 insertions(+), 1 deletion(-) diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 3400409..58c05b1 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -1,6 +1,6 @@ class HomeController < ApplicationController def show - @race = Race.first! + @race = Race.upcoming.first! @courses = @race.courses.where("capacity > 0") end end diff --git a/app/models/course.rb b/app/models/course.rb index 21592af..db5d62c 100644 --- a/app/models/course.rb +++ b/app/models/course.rb @@ -9,4 +9,8 @@ def remaining_slots def full? remaining_slots <= 0 end + + def available? + !full? && race.registration_deadline > Time.current + end end diff --git a/app/models/race.rb b/app/models/race.rb index f97c1fa..bc8352a 100644 --- a/app/models/race.rb +++ b/app/models/race.rb @@ -1,4 +1,6 @@ class Race < ApplicationRecord has_many :courses, dependent: :destroy has_many :registrations, dependent: :destroy + + scope :upcoming, -> { where("registration_deadline > ?", Time.current).order(:event_date) } end diff --git a/test/fixtures/courses.yml b/test/fixtures/courses.yml index d02b448..66b4056 100644 --- a/test/fixtures/courses.yml +++ b/test/fixtures/courses.yml @@ -25,3 +25,10 @@ full: capacity: 0 fee: 70000 start_time: "07:30:00" + +closed_five_km: + race: closed_race + name: "5km" + capacity: 200 + fee: 20000 + start_time: "09:00:00" diff --git a/test/fixtures/races.yml b/test/fixtures/races.yml index a05068c..f60a8cd 100644 --- a/test/fixtures/races.yml +++ b/test/fixtures/races.yml @@ -3,3 +3,9 @@ marathon_2026: event_date: <%= 3.months.from_now %> location: "서울 여의도공원" registration_deadline: <%= 2.months.from_now %> + +closed_race: + name: "부산마라톤 2025" + event_date: <%= 1.month.ago %> + location: "부산 해운대" + registration_deadline: <%= 2.months.ago %> diff --git a/test/models/course_test.rb b/test/models/course_test.rb index 816c353..dfb9d36 100644 --- a/test/models/course_test.rb +++ b/test/models/course_test.rb @@ -25,4 +25,19 @@ class CourseTest < ActiveSupport::TestCase course = courses(:five_km) assert_not course.full? end + + test "available? returns true when not full and before deadline" do + course = courses(:five_km) + assert course.available? + end + + test "available? returns false when full" do + course = courses(:full) + assert_not course.available? + end + + test "available? returns false when registration deadline has passed" do + course = courses(:closed_five_km) + assert_not course.available? + end end From 47f11e366c0b72b39dcf93ca22560913a6e93165 Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 20 Feb 2026 11:41:47 +0900 Subject: [PATCH 05/15] docs: mark Course#available? as complete - Commits: 22a4463 Co-Authored-By: Claude Opus 4.6 --- PLAN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PLAN.md b/PLAN.md index 930da3c..7ac5ccb 100755 --- a/PLAN.md +++ b/PLAN.md @@ -163,7 +163,7 @@ bundle exec rails runner "puts 'OK'" **Unit Tests** - [x] Course#full? - applied 수 >= capacity 시 true -- [ ] Course#available? - 마감 + 정원 조합 검증 +- [x] Course#available? - 마감 + 정원 조합 검증 **Integration Tests (P0)** From 3f93e90358bf8aca95e64746ce75b8ec67f159fe Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 20 Feb 2026 11:46:16 +0900 Subject: [PATCH 06/15] docs: add skinny controller fat model convention to CLAUDE.md Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CLAUDE.md b/CLAUDE.md index fc2f5b0..132e9dd 100755 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -137,3 +137,4 @@ TECHSPEC 코드 패턴을 **정확히** 따를 것. - **네이밍:** snake_case (Ruby) - **커밋 메시지:** Conventional Commits 형식 - **문자열:** 더블쿼트 우선 (rubocop-rails-omakase 기본) +- **Skinny Controller, Fat Model:** 비즈니스 로직과 쿼리는 모델(scope, 메서드)에 두고, 컨트롤러는 요청/응답 흐름만 담당 From 69de7d8d4508a72127a8c52753495a93dd96c316 Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 20 Feb 2026 12:37:32 +0900 Subject: [PATCH 07/15] docs: add concurrency testing approach to Issues #1 (M5, M6) Co-Authored-By: Claude Opus 4.6 --- PLAN.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/PLAN.md b/PLAN.md index 7ac5ccb..c6955aa 100755 --- a/PLAN.md +++ b/PLAN.md @@ -165,9 +165,12 @@ bundle exec rails runner "puts 'OK'" - [x] Course#full? - applied 수 >= capacity 시 true - [x] Course#available? - 마감 + 정원 조합 검증 -**Integration Tests (P0)** +**Concurrency Tests (P0)** ← Issues #1 참조 - [ ] 정원 1명, 동시 신청 2건 → 1건만 성공 + +**Integration Tests** + - [ ] 정원 초과 시 에러: "선택하신 코스의 정원이 마감되었습니다." **완료 조건:** 동시성 테스트 통과, 정원 초과 차단 @@ -186,9 +189,12 @@ bundle exec rails runner "puts 'OK'" - [ ] 동일 (race_id, name, phone_number) 중복 저장 시 에러 -**Integration Tests (P0)** +**Concurrency Tests (P0)** ← Issues #1 참조 - [ ] 동일 정보로 동시 신청 2건 → 1건만 성공 + +**Integration Tests** + - [ ] 중복 시 에러: "이미 동일한 이름과 전화번호로 신청된 내역이 있습니다." **완료 조건:** 중복 신청 차단, 동시성 테스트 통과 @@ -409,6 +415,6 @@ bundle exec rails runner "puts 'OK'" | # | 마일스톤 | 내용 | 상태 | | --- | -------- | ---- | ---- | -| | | | | +| 1 | M5, M6 | 동시성 테스트: `ActionDispatch::IntegrationTest`의 `post`는 `@response` 등 인스턴스 변수를 공유하여 thread-safe하지 않음. 모델 레벨에서 `Thread` + `ActiveRecord::Base.connection_pool.checkout`으로 별도 커넥션을 확보하여 테스트한다 (TECHSPEC § 8.3 참조) | 적용 | --- From 486c4a426c19d664e54e715f4ae17d73ed538086 Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 20 Feb 2026 12:53:03 +0900 Subject: [PATCH 08/15] feat(m5): add Course#create_registration! with lock! and concurrency test Co-Authored-By: Claude Opus 4.6 --- app/models/course.rb | 14 +++++++++++++ test/fixtures/courses.yml | 7 +++++++ test/models/course_capacity_test.rb | 31 +++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 test/models/course_capacity_test.rb diff --git a/app/models/course.rb b/app/models/course.rb index db5d62c..8d6462d 100644 --- a/app/models/course.rb +++ b/app/models/course.rb @@ -1,4 +1,6 @@ class Course < ApplicationRecord + class CapacityExceededError < StandardError; end + belongs_to :race has_many :registrations, dependent: :destroy @@ -13,4 +15,16 @@ def full? def available? !full? && race.registration_deadline > Time.current end + + def create_registration!(params) + Course.transaction do + lock! + + if full? + raise CapacityExceededError, "선택하신 코스의 정원이 마감되었습니다." + end + + registrations.create!(params) + end + end end diff --git a/test/fixtures/courses.yml b/test/fixtures/courses.yml index 66b4056..4eb240a 100644 --- a/test/fixtures/courses.yml +++ b/test/fixtures/courses.yml @@ -26,6 +26,13 @@ full: fee: 70000 start_time: "07:30:00" +one_slot: + race: marathon_2026 + name: "체험" + capacity: 1 + fee: 10000 + start_time: "10:00:00" + closed_five_km: race: closed_race name: "5km" diff --git a/test/models/course_capacity_test.rb b/test/models/course_capacity_test.rb new file mode 100644 index 0000000..9581680 --- /dev/null +++ b/test/models/course_capacity_test.rb @@ -0,0 +1,31 @@ +require "test_helper" + +class CourseCapacityTest < ActiveSupport::TestCase + test "concurrent registrations with capacity 1 allows only 1 success" do + course = courses(:one_slot) + results = Concurrent::Array.new + + threads = 2.times.map do |i| + Thread.new do + ActiveRecord::Base.connection_pool.with_connection do + course.create_registration!( + name: "신청자#{i}", + phone_number: "0101234000#{i}", + birth_date: "1990-01-01", + gender: "male", + address: "서울시 강남구" + ) + results << :success + rescue Course::CapacityExceededError + results << :capacity_exceeded + end + end + end + + threads.each(&:join) + + assert_equal 1, results.count(:success) + assert_equal 1, results.count(:capacity_exceeded) + assert_equal 1, course.registrations.where(status: "applied").count + end +end From 19b84458cee250085279785757ddefb90efc57ca Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 20 Feb 2026 12:53:19 +0900 Subject: [PATCH 09/15] docs: mark capacity concurrency test as complete - Commits: 486c4a4 Co-Authored-By: Claude Opus 4.6 --- PLAN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PLAN.md b/PLAN.md index c6955aa..8f13d2a 100755 --- a/PLAN.md +++ b/PLAN.md @@ -167,7 +167,7 @@ bundle exec rails runner "puts 'OK'" **Concurrency Tests (P0)** ← Issues #1 참조 -- [ ] 정원 1명, 동시 신청 2건 → 1건만 성공 +- [x] 정원 1명, 동시 신청 2건 → 1건만 성공 **Integration Tests** From 73558661e3a19f9e930f871b1d819cb5901c2d8e Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 20 Feb 2026 13:40:12 +0900 Subject: [PATCH 10/15] feat(m5): use create_registration! in controller with capacity exceeded handling Co-Authored-By: Claude Opus 4.6 --- app/controllers/registrations_controller.rb | 14 +++++++------- test/integration/registration_form_test.rb | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index bcb4d6a..532efee 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -6,13 +6,13 @@ def new def create @course = Course.find(params[:course_id]) - @registration = @course.registrations.new(registration_params) - - if @registration.save - redirect_to root_path - else - render :new, status: :unprocessable_entity - end + @registration = @course.create_registration!(registration_params) + redirect_to root_path + rescue Course::CapacityExceededError => e + redirect_to new_course_registration_path(@course), alert: e.message + rescue ActiveRecord::RecordInvalid => e + @registration = e.record + render :new, status: :unprocessable_entity end private diff --git a/test/integration/registration_form_test.rb b/test/integration/registration_form_test.rb index 9b9b3c9..f62dc39 100644 --- a/test/integration/registration_form_test.rb +++ b/test/integration/registration_form_test.rb @@ -40,6 +40,23 @@ class RegistrationFormTest < ActionDispatch::IntegrationTest assert_select "textarea[name='registration[address]']" end + test "registration fails with error when course is full" do + course = courses(:full) + + post course_registrations_path(course), params: { + registration: { + name: "테스트", + phone_number: "01099999999", + birth_date: "1990-01-01", + gender: "male", + address: "서울시 강남구" + } + } + + assert_redirected_to new_course_registration_path(course) + assert_equal "선택하신 코스의 정원이 마감되었습니다.", flash[:alert] + end + test "submitting with missing fields re-renders form with validation errors and preserves input" do course = courses(:five_km) kept_name = "김철수" From 4b33839b51aef5adfb8bf5c1ffe62832f58f3238 Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 20 Feb 2026 13:40:38 +0900 Subject: [PATCH 11/15] docs: mark M5 capacity management as complete - Commits: 7355866 Co-Authored-By: Claude Opus 4.6 --- PLAN.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PLAN.md b/PLAN.md index 8f13d2a..522bebc 100755 --- a/PLAN.md +++ b/PLAN.md @@ -171,11 +171,11 @@ bundle exec rails runner "puts 'OK'" **Integration Tests** -- [ ] 정원 초과 시 에러: "선택하신 코스의 정원이 마감되었습니다." +- [x] 정원 초과 시 에러: "선택하신 코스의 정원이 마감되었습니다." **완료 조건:** 동시성 테스트 통과, 정원 초과 차단 -- Commits: +- Commits: 1601373, 22a4463, 486c4a4, 7355866 --- From 9e5da471934c1b1a829b0018353bd70a96d33433 Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 20 Feb 2026 13:42:21 +0900 Subject: [PATCH 12/15] docs: add save vs create! issue to Issues #2 Co-Authored-By: Claude Opus 4.6 --- PLAN.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PLAN.md b/PLAN.md index 522bebc..665cf1c 100755 --- a/PLAN.md +++ b/PLAN.md @@ -415,6 +415,7 @@ bundle exec rails runner "puts 'OK'" | # | 마일스톤 | 내용 | 상태 | | --- | -------- | ---- | ---- | -| 1 | M5, M6 | 동시성 테스트: `ActionDispatch::IntegrationTest`의 `post`는 `@response` 등 인스턴스 변수를 공유하여 thread-safe하지 않음. 모델 레벨에서 `Thread` + `ActiveRecord::Base.connection_pool.checkout`으로 별도 커넥션을 확보하여 테스트한다 (TECHSPEC § 8.3 참조) | 적용 | +| 1 | M5, M6 | 동시성 테스트: `ActionDispatch::IntegrationTest`의 `post`는 `@response` 등 인스턴스 변수를 공유하여 thread-safe하지 않음. 모델 레벨에서 `Thread` + `ActiveRecord::Base.connection_pool.with_connection`으로 별도 커넥션을 확보하여 테스트한다 (TECHSPEC § 8.3 참조) | 적용 | +| 2 | M5 | `save` vs `create!`: 컨트롤러에서 `create_registration!`(`create!` 사용)로 전환 시, 예외 발생하면 `@registration`에 할당이 안 되어 뷰 렌더링 실패. `rescue ActiveRecord::RecordInvalid => e`에서 `e.record`로 실패한 객체를 꺼내 `@registration`에 할당하여 해결 | 적용 | --- From b9ffd721e41b2afb502ed6ce22edb50adf76b94d Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 20 Feb 2026 13:43:48 +0900 Subject: [PATCH 13/15] docs: add concurrency test principles and gem usage rules to CLAUDE.md Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 132e9dd..a99fcfb 100755 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -75,6 +75,12 @@ - Association 선언(has_many, belongs_to)은 M3 범위이므로, M2 Seed에서는 association 메서드(`race.courses`) 대신 FK 직접 참조(`race_id: race.id`)를 사용한다 - M3 완료 후 Seed를 association 방식으로 리팩토링할 수 있다 +### 테스트 작성 원칙 + +- 동시성(Thread) 테스트는 반드시 모델 레벨(`ActiveSupport::TestCase`)에서 작성 +- `ActionDispatch::IntegrationTest`에서 Thread + post 조합 금지 (`@response` 공유 충돌) +- 통합 테스트는 단일 요청의 HTTP 플로우 검증에만 사용 + ### 작성하지 않는 테스트 - 스키마 레벨 테스트 (컬럼 타입, 존재 여부) — 마이그레이션이 명세서 역할 @@ -83,6 +89,12 @@ ## CONSTRAINTS +### Gem 사용 규칙 + +- 새로운 gem을 사용하기 전에 반드시 Gemfile에 존재하는지 확인 +- 없으면 코드 작성 전에 gem 추가 필요 여부를 먼저 알려줄 것 +- 기존 의존성만으로 해결 가능한지 우선 검토 + - [ ] 테스트 없이 프로덕션 코드 작성 금지 (단, Association 선언은 선언적 코드이므로 예외) - [ ] 한 번에 여러 기능 구현 금지 - [ ] TECHSPEC.md에 명시되지 않은 기술 스택 도입 금지 From f56828fba8f077ee2c3b6f5306e80eeca0ebe981 Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 20 Feb 2026 13:57:48 +0900 Subject: [PATCH 14/15] feat(m5): add flash message rendering to layout with smoke test Co-Authored-By: Claude Opus 4.6 --- app/views/layouts/application.html.erb | 6 ++++++ test/integration/layout_test.rb | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 test/integration/layout_test.rb diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 879cc0e..7f9674d 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -24,6 +24,12 @@ + <% if flash[:alert] %> +
<%= flash[:alert] %>
+ <% end %> + <% if flash[:notice] %> +
<%= flash[:notice] %>
+ <% end %> <%= yield %> diff --git a/test/integration/layout_test.rb b/test/integration/layout_test.rb new file mode 100644 index 0000000..1da905d --- /dev/null +++ b/test/integration/layout_test.rb @@ -0,0 +1,20 @@ +require "test_helper" + +class LayoutTest < ActionDispatch::IntegrationTest + test "flash alert is rendered in layout" do + course = courses(:full) + + post course_registrations_path(course), params: { + registration: { + name: "테스트", + phone_number: "01099999999", + birth_date: "1990-01-01", + gender: "male", + address: "서울시 강남구" + } + } + + follow_redirect! + assert_select ".flash-alert", "선택하신 코스의 정원이 마감되었습니다." + end +end From 85c92bf46ce2a8ee1beb805155e704ccb67236c4 Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 20 Feb 2026 14:12:01 +0900 Subject: [PATCH 15/15] fix(m3): change phone_number validation from maximum 11 to exactly 11 digits Co-Authored-By: Claude Opus 4.6 --- app/models/registration.rb | 2 +- test/models/registration_test.rb | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/models/registration.rb b/app/models/registration.rb index f161e75..872cdc8 100644 --- a/app/models/registration.rb +++ b/app/models/registration.rb @@ -10,7 +10,7 @@ class Registration < ApplicationRecord normalizes :phone_number, with: ->(phone_number) { phone_number.gsub(/\D/, "") } validates :name, presence: true, length: { maximum: 10 } - validates :phone_number, presence: true, length: { maximum: 11 } + validates :phone_number, presence: true, length: { is: 11 } validates :birth_date, :gender, presence: true validates :address, presence: true, length: { maximum: 30 } diff --git a/test/models/registration_test.rb b/test/models/registration_test.rb index f01239d..79ce208 100644 --- a/test/models/registration_test.rb +++ b/test/models/registration_test.rb @@ -30,11 +30,16 @@ class RegistrationTest < ActiveSupport::TestCase assert_includes registration.errors[:name], "is too long (maximum is 10 characters)" end - test "rejects phone_number longer than 11 digits" do + test "rejects phone_number that is not exactly 11 digits" do registration = registrations(:hong_5km) + + registration.phone_number = "0" * 9 + assert_not registration.valid? + assert_includes registration.errors[:phone_number], "is the wrong length (should be 11 characters)" + registration.phone_number = "0" * 12 assert_not registration.valid? - assert_includes registration.errors[:phone_number], "is too long (maximum is 11 characters)" + assert_includes registration.errors[:phone_number], "is the wrong length (should be 11 characters)" end test "rejects address longer than 30 characters" do