From da6824c1e90adf090afccb203511079e98499a5c Mon Sep 17 00:00:00 2001 From: gumbi Date: Sat, 2 Nov 2024 21:12:39 +0900 Subject: [PATCH 01/30] =?UTF-8?q?docs:=20=EC=B4=88=EA=B8=B0=20README.md=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=AA=A9=EB=A1=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/README.md b/README.md index 15bb106b5..22e84a4c1 100644 --- a/README.md +++ b/README.md @@ -1 +1,77 @@ # javascript-lotto-precourse + +## 프로젝트 개요 +이 프로젝트는 간단한 로또 발매기를 구현하는 것을 목표로 합니다. +사용자는 로또 구입 금액을 입력하고, 로또 번호를 발행받은 후 당첨 번호와 비교하여 당첨 결과를 확인할 수 있습니다. +프로젝트의 주요 기능은 사용자의 입력을 받아 로또를 발행하고, 당첨 결과를 계산하여 수익률을 계산하는 것입니다. + +## 기능 목록 + +- [ ] **로또 구입 금액 입력 및 검증** + - 로또 구입 금액을 입력받는다. + - 1,000원 단위로 나누어 떨어지는지 확인하여 유효성을 검증한다. + - **예외**: 1,000원 단위로 나누어 떨어지지 않는 경우, `[ERROR]` 메시지 출력 후 재입력 요청. + +- [ ] **로또 발행** + - 입력받은 금액을 바탕으로 로또 발행 개수를 계산한다. (로또 1장당 1,000원) + - 발행한 로또 번호는 1~45 사이의 중복되지 않는 6개의 숫자로 구성된다. + +- [ ] **로또 번호 출력** + - 발행한 모든 로또 번호를 오름차순으로 정렬하여 출력한다. + - 출력 형식: `N개를 구매했습니다.` 및 각 로또 번호를 배열 형식으로 출력한다. + +- [ ] **당첨 번호 및 보너스 번호 입력 및 검증** + - 당첨 번호 6개를 입력받는다. 번호는 쉼표(,)로 구분한다. + - 보너스 번호 1개를 입력받는다. + - 당첨 번호와 보너스 번호는 1 ~ 45 사이의 중복되지 않는 숫자여야 한다. + - **예외**: 입력 형식이 올바르지 않거나 중복된 번호, 범위를 벗어난 경우, `[ERROR]` 메시지 출력 후 재입력 요청. + +- [ ] **당첨 결과 계산** + - 구매한 로또 번호와 당첨 번호를 비교하여 당첨 결과를 계산한다. + - 당첨 등수는 1등부터 5등까지 있으며, 각 등수는 아래 조건에 따른다: + - 1등: 6개 번호 일치 + - 2등: 5개 번호 + 보너스 번호 일치 + - 3등: 5개 번호 일치 + - 4등: 4개 번호 일치 + - 5등: 3개 번호 일치 + +- [ ] **당첨 통계 및 수익률 출력** + - 당첨 결과를 등수별로 정리하여 출력한다. + - 총 수익률을 계산하고, 소수점 둘째 자리에서 반올림하여 출력한다. + - 출력 형식: + - `3개 일치 (5,000원) - 1개` + - `총 수익률은 62.5%입니다.` + +- [ ] **에러 처리** + - 모든 사용자 입력 단계에서 잘못된 값이 들어왔을 경우, `[ERROR]`로 시작하는 메시지를 출력한다. + - 에러 발생 시 해당 단계부터 다시 입력을 받는다. + +## 테스트 목록 +TDD를 적용하여 기능별 테스트 케이스를 작성합니다. 다음은 각 기능에 대해 예상되는 테스트 목록입니다. + +- [ ] **로또 구입 금액 입력 및 검증 테스트** + - 올바른 금액을 입력했을 때 정상적으로 처리되는지 확인한다. + - 1,000원 단위로 나누어 떨어지지 않는 금액을 입력했을 때 에러 메시지가 출력되는지 확인한다. + +- [ ] **로또 발행 테스트** + - 입력한 금액에 따라 올바른 개수의 로또가 발행되는지 확인한다. + - 각 로또 번호가 1 ~ 45 사이의 중복되지 않는 6개의 숫자로 구성되어 있는지 확인한다. + +- [ ] **로또 번호 출력 테스트** + - 발행한 로또 번호가 오름차순으로 출력되는지 확인한다. + +- [ ] **당첨 번호 및 보너스 번호 입력 및 검증 테스트** + - 올바른 형식의 당첨 번호와 보너스 번호를 입력했을 때 정상적으로 처리되는지 확인한다. + - 중복되거나 범위를 벗어난 번호를 입력했을 때 에러 메시지가 출력되는지 확인한다. + +- [ ] **당첨 결과 계산 테스트** + - 구매한 로또 번호와 당첨 번호를 비교하여 각 등수별로 당첨 결과가 정확하게 계산되는지 확인한다. + - 보너스 번호를 포함한 2등 당첨이 올바르게 계산되는지 확인한다. + +- [ ] **당첨 통계 및 수익률 출력 테스트** + - 당첨 결과가 등수별로 올바르게 출력되는지 확인한다. + - 총 수익률이 올바르게 계산되어 출력되는지 확인한다. + +- [ ] **에러 처리 테스트** + - 각 단계에서 잘못된 입력이 주어졌을 때 올바르게 에러 메시지가 출력되고, 재입력을 받는지 확인한다. + From edfd070dce6e09c08c9f2ab0149281ea6663fb06 Mon Sep 17 00:00:00 2001 From: gumbi Date: Sun, 3 Nov 2024 16:51:54 +0900 Subject: [PATCH 02/30] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=B0=8F=20=EC=83=81=EA=B8=88=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자 입력을 위한 메시지(USER_PROMPT_MESSAGES) 추가 - 확인 메시지(CONFIRMATION_MESSAGES) 추가 - 에러 메시지(ERROR_MESSAGES) 추가 - 등수에 따른 상금 정보(PRIZE_AMOUNTS) 추가 --- src/constants/messages.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/constants/messages.js diff --git a/src/constants/messages.js b/src/constants/messages.js new file mode 100644 index 000000000..d352dc623 --- /dev/null +++ b/src/constants/messages.js @@ -0,0 +1,34 @@ +const USER_PROMPT_MESSAGES = Object.freeze({ + GET_PURCHASE_AMOUNT: '구입금액을 입력해 주세요.\n', + GET_WINNING_NUMBER: '당첨 번호를 입력해 주세요.\n', + GET_BONUS_NUMBER: '보너스 번호를 입력해 주세요.\n', +}); + +const CONFIRMATION_MESSAGES = Object.freeze({ + PURCHASE_COMPLETE_MESSAGE: (count) => `${count}개를 구매했습니다.`, + WINNING_STATISTICS_HEADER: '당첨 통계\n---', + TOTAL_YIELD_MESSAGE: (rate) => `총 수익률은 ${rate}%입니다.`, +}); + +const ERROR_MESSAGES = Object.freeze({ + INVALID_PURCHASE_AMOUNT: '[ERROR] 구입 금액은 1,000원 단위로 입력해 주세요.', + INVALID_WINNING_NUMBER: + '[ERROR] 당첨 번호는 1~45 사이의 숫자여야 하며, 중복되지 않아야 합니다.', + INVALID_BONUS_NUMBER: + '[ERROR] 보너스 번호는 1~45 사이의 숫자여야 하며, 당첨 번호와 중복되지 않아야 합니다.', +}); + +const PRIZE_AMOUNTS = Object.freeze({ + FIRST_PRIZE: 2000000000, + SECOND_PRIZE: 30000000, + THIRD_PRIZE: 1500000, + FOURTH_PRIZE: 50000, + FIFTH_PRIZE: 5000, +}); + +export { + USER_PROMPT_MESSAGES, + CONFIRMATION_MESSAGES, + ERROR_MESSAGES, + PRIZE_AMOUNTS, +}; From f2956d9980ca2bb730b30a8a70f546542f8c6e95 Mon Sep 17 00:00:00 2001 From: gumbi Date: Sun, 3 Nov 2024 17:08:54 +0900 Subject: [PATCH 03/30] =?UTF-8?q?feat:=20=EC=BD=98=EC=86=94=20=EC=9C=A0?= =?UTF-8?q?=ED=8B=B8=20=ED=95=A8=EC=88=98=20=EB=AA=A8=EB=93=88=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - printMessage와 promptUserInput 함수로 콘솔 관련 기능 모듈화 --- src/utils/console.js | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/utils/console.js diff --git a/src/utils/console.js b/src/utils/console.js new file mode 100644 index 000000000..58e9ee62c --- /dev/null +++ b/src/utils/console.js @@ -0,0 +1,11 @@ +import { Console } from '@woowacourse/mission-utils'; + +const printMessage = (message) => { + Console.print(message); +}; + +const promptUserInput = async (message) => { + return await Console.readLineAsync(message); +}; + +export { printMessage, promptUserInput }; From bbdcdce570b6cee5f01151b9b1bde2702cbd0198 Mon Sep 17 00:00:00 2001 From: gumbi Date: Sun, 3 Nov 2024 20:38:55 +0900 Subject: [PATCH 04/30] =?UTF-8?q?feat:=20=EC=B6=94=EA=B0=80=EB=90=9C=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=B0=8F=20?= =?UTF-8?q?=EB=AA=85=ED=99=95=ED=95=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 구입 금액이 숫자가 아닌 경우에 대한 에러 메시지 추가 - INVALID_PURCHASE_AMOUNT_NOT_NUMBER - 구입 금액이 티켓 단위로 나누어 떨어지지 않는 경우의 에러 메시지 네이밍 수정 - INVALID_PURCHASE_AMOUNT_NOT_TICKET_UNIT --- src/constants/messages.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/constants/messages.js b/src/constants/messages.js index d352dc623..a29f7b422 100644 --- a/src/constants/messages.js +++ b/src/constants/messages.js @@ -11,7 +11,10 @@ const CONFIRMATION_MESSAGES = Object.freeze({ }); const ERROR_MESSAGES = Object.freeze({ - INVALID_PURCHASE_AMOUNT: '[ERROR] 구입 금액은 1,000원 단위로 입력해 주세요.', + INVALID_PURCHASE_AMOUNT_NOT_NUMBER: + '[ERROR] 구입 금액은 숫자만 입력해 주세요.', + INVALID_PURCHASE_AMOUNT_NOT_TICKET_UNIT: + '[ERROR] 구입 금액은 1,000원 단위로 입력해 주세요.', INVALID_WINNING_NUMBER: '[ERROR] 당첨 번호는 1~45 사이의 숫자여야 하며, 중복되지 않아야 합니다.', INVALID_BONUS_NUMBER: From 48e6af2c6e74ea33c40918f8b0083ad5a154977e Mon Sep 17 00:00:00 2001 From: gumbi Date: Sun, 3 Nov 2024 22:18:31 +0900 Subject: [PATCH 05/30] =?UTF-8?q?feat:=20=EA=B5=AC=EB=A7=A4=20=EA=B8=88?= =?UTF-8?q?=EC=95=A1=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 입력받은 금액이 숫자인지 확인하는 로직 추가 - isPromptAmountNumber - 구매 금액이 로또 티켓 가격 단위로 나누어지는지 확인하는 로직 추가 - isValidTicketUnit - 전체 유효성 검증을 위한 함수 추가 - validateAmount --- src/validations/purchase-amount.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/validations/purchase-amount.js diff --git a/src/validations/purchase-amount.js b/src/validations/purchase-amount.js new file mode 100644 index 000000000..dd6ca0ed4 --- /dev/null +++ b/src/validations/purchase-amount.js @@ -0,0 +1,29 @@ +import { ERROR_MESSAGES } from '../constants/messages.js'; + +const { + INVALID_PURCHASE_AMOUNT_NOT_NUMBER, + INVALID_PURCHASE_AMOUNT_NOT_TICKET_UNIT, +} = ERROR_MESSAGES; + +const LOTTO_TICKET_PRICE = 1000; + +const isPromptAmountNumber = (amount) => { + if (Number.isNaN(Number(amount))) { + throw new Error(INVALID_PURCHASE_AMOUNT_NOT_NUMBER); + } + return amount; +}; + +const isValidTicketUnit = (amount) => { + if (amount % LOTTO_TICKET_PRICE !== 0) { + throw new Error(INVALID_PURCHASE_AMOUNT_NOT_TICKET_UNIT); + } + return amount; +}; + +const validateAmount = (amount) => { + const numericAmount = isPromptAmountNumber(amount); + return isValidTicketUnit(numericAmount); +}; + +export default validateAmount; From 6e6aee8357e84c5af17959de719dcf291e4817d9 Mon Sep 17 00:00:00 2001 From: gumbi Date: Sun, 3 Nov 2024 22:26:50 +0900 Subject: [PATCH 06/30] =?UTF-8?q?feat:=20=EA=B5=AC=EB=A7=A4=20=EA=B8=88?= =?UTF-8?q?=EC=95=A1=20=EC=9E=85=EB=A0=A5=EC=97=90=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=EB=90=9C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자에게 구매 금액 입력을 요청하는 함수 추가 - promptUserAmount - 구매 금액을 입력받고 유효성을 확인하고 반환하는 함수 추가 - getPurchaseAmount - 유효하지 않은 입력의 경우 에러 메시지를 출력하고 재입력 요청 --- src/utils/get-parchase-amount.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/utils/get-parchase-amount.js diff --git a/src/utils/get-parchase-amount.js b/src/utils/get-parchase-amount.js new file mode 100644 index 000000000..b8fe3bd69 --- /dev/null +++ b/src/utils/get-parchase-amount.js @@ -0,0 +1,25 @@ +import { USER_PROMPT_MESSAGES } from '../constants/messages.js'; +import { promptUserInput, printMessage } from './console.js'; +import validateAmount from '../validations/purchase-amount.js'; + +const { GET_PURCHASE_AMOUNT } = USER_PROMPT_MESSAGES; + +const promptUserAmount = () => { + return promptUserInput(GET_PURCHASE_AMOUNT); +}; + +const getPurchaseAmount = async () => { + while (true) { + try { + const amount = await promptUserAmount(); + + const validatedAmount = validateAmount(amount); + + return validatedAmount; + } catch (error) { + printMessage(error.message); + } + } +}; + +export default getPurchaseAmount; From 41e009627faa4f57d025d47900e70e98ccbe01b7 Mon Sep 17 00:00:00 2001 From: gumbi Date: Sun, 3 Nov 2024 22:33:52 +0900 Subject: [PATCH 07/30] =?UTF-8?q?docs:=20=EB=A1=9C=EB=98=90=20=EA=B5=AC?= =?UTF-8?q?=EC=9E=85=20=EA=B8=88=EC=95=A1=20=EC=9E=85=EB=A0=A5=20=EB=B0=8F?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=8A=A5=20=EC=99=84=EB=A3=8C?= =?UTF-8?q?=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로또 구입 금액 입력 및 1,000원 단위 검증 기능을 완료 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 22e84a4c1..af7348318 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ## 기능 목록 -- [ ] **로또 구입 금액 입력 및 검증** +- [x] **로또 구입 금액 입력 및 검증** - 로또 구입 금액을 입력받는다. - 1,000원 단위로 나누어 떨어지는지 확인하여 유효성을 검증한다. - **예외**: 1,000원 단위로 나누어 떨어지지 않는 경우, `[ERROR]` 메시지 출력 후 재입력 요청. From 649c1346a125eff8c82b171db3df45b5ba3fb952 Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 12:59:38 +0900 Subject: [PATCH 08/30] =?UTF-8?q?feat:=20=EA=B3=B5=EB=B0=B1=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EC=B2=98=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80?= =?UTF-8?q?=EC=82=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 공백 입력 시 발생하는 에러 메시지 추가 - INVALID_EMPTY_INPUT - 공통 유효성 검사 파일 common.js 추가 - 공백 제거 후 빈 값 체크 함수 추가 - trimInputAndCheckEmpty - purchase-amount.js에서 입력 값을 공백 제거 후 사용하도록 개선 --- src/constants/messages.js | 2 ++ src/validations/common.js | 15 +++++++++++++++ src/validations/purchase-amount.js | 4 +++- 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/validations/common.js diff --git a/src/constants/messages.js b/src/constants/messages.js index a29f7b422..759e3f7c2 100644 --- a/src/constants/messages.js +++ b/src/constants/messages.js @@ -11,6 +11,8 @@ const CONFIRMATION_MESSAGES = Object.freeze({ }); const ERROR_MESSAGES = Object.freeze({ + INVALID_EMPTY_INPUT: + '[ERROR] 아무것도 입력하지 않았습니다. 값을 입력해 주세요.', INVALID_PURCHASE_AMOUNT_NOT_NUMBER: '[ERROR] 구입 금액은 숫자만 입력해 주세요.', INVALID_PURCHASE_AMOUNT_NOT_TICKET_UNIT: diff --git a/src/validations/common.js b/src/validations/common.js new file mode 100644 index 000000000..51c3314cf --- /dev/null +++ b/src/validations/common.js @@ -0,0 +1,15 @@ +import { ERROR_MESSAGES } from '../constants/messages'; + +const { INVALID_EMPTY_INPUT } = ERROR_MESSAGES; + +const trimInputAndCheckEmpty = (input) => { + const trimmedInput = input.trim(); + + if (trimmedInput === '') { + throw new Error(INVALID_EMPTY_INPUT); + } + + return trimmedInput; +}; + +export { trimInputAndCheckEmpty }; diff --git a/src/validations/purchase-amount.js b/src/validations/purchase-amount.js index dd6ca0ed4..33cd60112 100644 --- a/src/validations/purchase-amount.js +++ b/src/validations/purchase-amount.js @@ -1,3 +1,4 @@ +import { trimInputAndCheckEmpty } from './common.js'; import { ERROR_MESSAGES } from '../constants/messages.js'; const { @@ -22,7 +23,8 @@ const isValidTicketUnit = (amount) => { }; const validateAmount = (amount) => { - const numericAmount = isPromptAmountNumber(amount); + const trimmedAmount = trimInputAndCheckEmpty(amount); + const numericAmount = isPromptAmountNumber(trimmedAmount); return isValidTicketUnit(numericAmount); }; From 869f4f3447bb4753b5087fd4b4c5505f10a7b48a Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 13:31:31 +0900 Subject: [PATCH 09/30] =?UTF-8?q?test:=20=EB=A1=9C=EB=98=90=20=EA=B5=AC?= =?UTF-8?q?=EC=9E=85=20=EA=B8=88=EC=95=A1=20=EC=9E=85=EB=A0=A5=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80?= =?UTF-8?q?=EC=82=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 올바른 금액 입력 시 정상적으로 처리되는지 확인하는 테스트 추가 - 잘못된 입력 값에 대해 에러 메시지가 발생하는지 확인하는 테스트 추가 --- __tests__/purchase-amount-test.js | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 __tests__/purchase-amount-test.js diff --git a/__tests__/purchase-amount-test.js b/__tests__/purchase-amount-test.js new file mode 100644 index 000000000..13a144fc8 --- /dev/null +++ b/__tests__/purchase-amount-test.js @@ -0,0 +1,32 @@ +import validateAmount from '../src/validations/purchase-amount.js'; +import { ERROR_MESSAGES } from '../src/constants/messages.js'; + +const { + INVALID_EMPTY_INPUT, + INVALID_PURCHASE_AMOUNT_NOT_NUMBER, + INVALID_PURCHASE_AMOUNT_NOT_TICKET_UNIT, +} = ERROR_MESSAGES; + +describe('로또 구입 금액 입력 테스트', () => { + test.each([1000, 8000, 14000])( + '올바른 금액 %i을 입력시 기능 테스트', + (amount) => { + const result = validateAmount(amount); + expect(result).toBe(amount); + }, + ); + + test.each([ + { input: '', expectedError: INVALID_EMPTY_INPUT }, + { input: '100j', expectedError: INVALID_PURCHASE_AMOUNT_NOT_NUMBER }, + { input: 'a', expectedError: INVALID_PURCHASE_AMOUNT_NOT_NUMBER }, + { input: '@#!', expectedError: INVALID_PURCHASE_AMOUNT_NOT_NUMBER }, + { input: '4500', expectedError: INVALID_PURCHASE_AMOUNT_NOT_TICKET_UNIT }, + { input: '400', expectedError: INVALID_PURCHASE_AMOUNT_NOT_TICKET_UNIT }, + ])( + '잘못된 입력 값 %s을 입력시 상응하는 에러 메시지 %s 발생', + ({ input, expectedError }) => { + expect(() => validateAmount(input)).toThrow(expectedError); + }, + ); +}); From a3109b3a08cc8b8c5622ca625993b6917a266dbe Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 13:32:15 +0900 Subject: [PATCH 10/30] =?UTF-8?q?fix:=20=EA=B5=AC=EB=A7=A4=20=EA=B8=88?= =?UTF-8?q?=EC=95=A1=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - trim이 작동하지 않던 문제 해결: 입력 값을 문자열로 변환 후 처리 - 문자열로 변환한 후 trim 적용하여 공백 제거 및 유효성 검사 개선 - 숫자 여부 검증 후 최종적으로 Number로 변환 --- src/validations/purchase-amount.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/validations/purchase-amount.js b/src/validations/purchase-amount.js index 33cd60112..55a5561e4 100644 --- a/src/validations/purchase-amount.js +++ b/src/validations/purchase-amount.js @@ -12,7 +12,7 @@ const isPromptAmountNumber = (amount) => { if (Number.isNaN(Number(amount))) { throw new Error(INVALID_PURCHASE_AMOUNT_NOT_NUMBER); } - return amount; + return Number(amount); }; const isValidTicketUnit = (amount) => { @@ -23,7 +23,8 @@ const isValidTicketUnit = (amount) => { }; const validateAmount = (amount) => { - const trimmedAmount = trimInputAndCheckEmpty(amount); + const amountAsString = String(amount); + const trimmedAmount = trimInputAndCheckEmpty(amountAsString); const numericAmount = isPromptAmountNumber(trimmedAmount); return isValidTicketUnit(numericAmount); }; From 44b45563430879a3104c42dc47e329c19072d87d Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 13:39:35 +0900 Subject: [PATCH 11/30] =?UTF-8?q?docs:=20=EB=A1=9C=EB=98=90=20=EA=B5=AC?= =?UTF-8?q?=EC=9E=85=20=EA=B8=88=EC=95=A1=20=EC=9E=85=EB=A0=A5=20=EB=B0=8F?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index af7348318..98a1e7abf 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ ## 테스트 목록 TDD를 적용하여 기능별 테스트 케이스를 작성합니다. 다음은 각 기능에 대해 예상되는 테스트 목록입니다. -- [ ] **로또 구입 금액 입력 및 검증 테스트** +- [x] **로또 구입 금액 입력 및 검증 테스트** - 올바른 금액을 입력했을 때 정상적으로 처리되는지 확인한다. - 1,000원 단위로 나누어 떨어지지 않는 금액을 입력했을 때 에러 메시지가 출력되는지 확인한다. From d790e93013db9d0160b8b9224fbd63dcc74f3f33 Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 14:59:02 +0900 Subject: [PATCH 12/30] =?UTF-8?q?refactor:=20=EC=83=81=EC=88=98=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=ED=86=B5=ED=95=A9=20=EB=B0=8F=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - constants.js 파일로 상수 통합 (기존 messages.js 파일 변경) - 로또 티켓 가격 상수를 constants.js 파일로 이동 (LOTTO_TICKET_PRICE) - 공통 상수 관리로 코드 일관성과 재사용성 향상 --- __tests__/purchase-amount-test.js | 2 +- src/constants/{messages.js => constants.js} | 3 +++ src/utils/get-parchase-amount.js | 2 +- src/validations/common.js | 2 +- src/validations/purchase-amount.js | 4 +--- 5 files changed, 7 insertions(+), 6 deletions(-) rename src/constants/{messages.js => constants.js} (96%) diff --git a/__tests__/purchase-amount-test.js b/__tests__/purchase-amount-test.js index 13a144fc8..59cdf8ca5 100644 --- a/__tests__/purchase-amount-test.js +++ b/__tests__/purchase-amount-test.js @@ -1,5 +1,5 @@ import validateAmount from '../src/validations/purchase-amount.js'; -import { ERROR_MESSAGES } from '../src/constants/messages.js'; +import { ERROR_MESSAGES } from '../src/constants/constants.js'; const { INVALID_EMPTY_INPUT, diff --git a/src/constants/messages.js b/src/constants/constants.js similarity index 96% rename from src/constants/messages.js rename to src/constants/constants.js index 759e3f7c2..286947249 100644 --- a/src/constants/messages.js +++ b/src/constants/constants.js @@ -1,3 +1,5 @@ +const LOTTO_TICKET_PRICE = 1000; + const USER_PROMPT_MESSAGES = Object.freeze({ GET_PURCHASE_AMOUNT: '구입금액을 입력해 주세요.\n', GET_WINNING_NUMBER: '당첨 번호를 입력해 주세요.\n', @@ -32,6 +34,7 @@ const PRIZE_AMOUNTS = Object.freeze({ }); export { + LOTTO_TICKET_PRICE, USER_PROMPT_MESSAGES, CONFIRMATION_MESSAGES, ERROR_MESSAGES, diff --git a/src/utils/get-parchase-amount.js b/src/utils/get-parchase-amount.js index b8fe3bd69..aab5c31b2 100644 --- a/src/utils/get-parchase-amount.js +++ b/src/utils/get-parchase-amount.js @@ -1,4 +1,4 @@ -import { USER_PROMPT_MESSAGES } from '../constants/messages.js'; +import { USER_PROMPT_MESSAGES } from '../constants/constants.js'; import { promptUserInput, printMessage } from './console.js'; import validateAmount from '../validations/purchase-amount.js'; diff --git a/src/validations/common.js b/src/validations/common.js index 51c3314cf..70b4a796b 100644 --- a/src/validations/common.js +++ b/src/validations/common.js @@ -1,4 +1,4 @@ -import { ERROR_MESSAGES } from '../constants/messages'; +import { ERROR_MESSAGES } from '../constants/constants'; const { INVALID_EMPTY_INPUT } = ERROR_MESSAGES; diff --git a/src/validations/purchase-amount.js b/src/validations/purchase-amount.js index 55a5561e4..6d238a0ce 100644 --- a/src/validations/purchase-amount.js +++ b/src/validations/purchase-amount.js @@ -1,13 +1,11 @@ import { trimInputAndCheckEmpty } from './common.js'; -import { ERROR_MESSAGES } from '../constants/messages.js'; +import { ERROR_MESSAGES, LOTTO_TICKET_PRICE } from '../constants/constants.js'; const { INVALID_PURCHASE_AMOUNT_NOT_NUMBER, INVALID_PURCHASE_AMOUNT_NOT_TICKET_UNIT, } = ERROR_MESSAGES; -const LOTTO_TICKET_PRICE = 1000; - const isPromptAmountNumber = (amount) => { if (Number.isNaN(Number(amount))) { throw new Error(INVALID_PURCHASE_AMOUNT_NOT_NUMBER); From 6ecb7f15c98fe8a0d1fe742aa68d64a89a4dd5c1 Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 18:06:09 +0900 Subject: [PATCH 13/30] =?UTF-8?q?feat:=20=EB=A1=9C=EB=98=90=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=97=90=EB=9F=AC=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로또 번호의 개수, 중복 여부에 대한 에러 메시지 추가 - INVALID_LOTTO_NUMBER_COUNT - INVALID_DUPLICATE_NUMBERS --- src/constants/constants.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/constants/constants.js b/src/constants/constants.js index 286947249..8dbc85076 100644 --- a/src/constants/constants.js +++ b/src/constants/constants.js @@ -19,6 +19,8 @@ const ERROR_MESSAGES = Object.freeze({ '[ERROR] 구입 금액은 숫자만 입력해 주세요.', INVALID_PURCHASE_AMOUNT_NOT_TICKET_UNIT: '[ERROR] 구입 금액은 1,000원 단위로 입력해 주세요.', + INVALID_LOTTO_NUMBER_COUNT: '[ERROR] 로또 번호는 6개여야 합니다.', + INVALID_DUPLICATE_NUMBERS: '[ERROR] 로또 번호는 중복되지 않아야 합니다.', INVALID_WINNING_NUMBER: '[ERROR] 당첨 번호는 1~45 사이의 숫자여야 하며, 중복되지 않아야 합니다.', INVALID_BONUS_NUMBER: From 432d96cc5833f45a914348e555fcb962645c4e97 Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 18:10:16 +0900 Subject: [PATCH 14/30] =?UTF-8?q?feat:=20=EB=A1=9C=EB=98=90=20=EB=B0=9C?= =?UTF-8?q?=ED=96=89=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로또 번호 발행 로직 추가 - 중복되지 않는 6개의 숫자 생성 및 정렬 - 로또 리스트 생성 기능 추가 - 구매 금액에 따라 로또 개수 계산 - Lotto 클래스 수정 - 로또 번호 개수 및 중복 유효성 검사 추가가 - 로또 번호 반환 메서드 추가 (getNumbers) --- src/Lotto.js | 20 +++++++++++++++++++- src/utils/generate-lotto.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/utils/generate-lotto.js diff --git a/src/Lotto.js b/src/Lotto.js index cb0b1527e..2b21c37f2 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -1,3 +1,8 @@ +import { ERROR_MESSAGES } from './constants/constants.js'; + +const { INVALID_DUPLICATE_NUMBERS, INVALID_LOTTO_NUMBER_COUNT } = + ERROR_MESSAGES; + class Lotto { #numbers; @@ -8,11 +13,24 @@ class Lotto { #validate(numbers) { if (numbers.length !== 6) { - throw new Error("[ERROR] 로또 번호는 6개여야 합니다."); + throw new Error(INVALID_LOTTO_NUMBER_COUNT); } + + this.#checkUniqueNumbers(numbers); } // TODO: 추가 기능 구현 + #checkUniqueNumbers(numbers) { + const uniqueNumbers = new Set(numbers); + + if (uniqueNumbers.size !== numbers.length) { + throw new Error(INVALID_DUPLICATE_NUMBERS); + } + } + + getNumbers() { + return this.#numbers; + } } export default Lotto; diff --git a/src/utils/generate-lotto.js b/src/utils/generate-lotto.js new file mode 100644 index 000000000..34beb7cc5 --- /dev/null +++ b/src/utils/generate-lotto.js @@ -0,0 +1,31 @@ +import { Random } from '@woowacourse/mission-utils'; +import { LOTTO_TICKET_PRICE } from '../constants/constants.js'; +import Lotto from '../Lotto.js'; + +const calculateLottoCount = (amount) => amount / LOTTO_TICKET_PRICE; + +const generateUniqueLottoNumbers = () => { + return Random.pickUniqueNumbersInRange(1, 45, 6); +}; + +const sortLottoNumbers = (lottoNumbers) => lottoNumbers.sort((a, b) => a - b); + +const generateLotto = () => { + const lottoNumbers = generateUniqueLottoNumbers(); + const sortedLottoNumbers = sortLottoNumbers(lottoNumbers); + + return new Lotto(sortedLottoNumbers); +}; + +const generateLottoList = (amount) => { + const lottoCount = calculateLottoCount(amount); + const lottos = []; + + for (let i = 0; i < lottoCount; i++) { + lottos.push(generateLotto()); + } + + return lottos; +}; + +export default generateLottoList; From b789fb4e33c9eba8c466c7c3c4876cebdcfa7f90 Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 18:14:29 +0900 Subject: [PATCH 15/30] =?UTF-8?q?feat:=20=EB=A1=9C=EB=98=90=20=EB=B0=9C?= =?UTF-8?q?=ED=96=89=20=EA=B2=B0=EA=B3=BC=20=EC=B6=9C=EB=A0=A5=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 구매한 로또 개수 출력 로직 추가 - PURCHASE_COMPLETE_MESSAGE 사용하여 로또 개수 출력 - 각 로또 번호 출력 기능 추가 - printLotto 함수를 통해 오름차순 정렬된 번호를 콘솔에 출력 - printLottoList 함수에서 모든 로또 번호를 순회하며 개별 출력 처리 --- src/App.js | 12 +++++++++++- src/utils/console.js | 16 +++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/App.js b/src/App.js index 091aa0a5d..089aa97fe 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,15 @@ +import { printMessage, printLottoList } from './utils/console.js'; +import getPurchaseAmount from './utils/get-parchase-amount.js'; +import generateLottoList from './utils/generate-lotto.js'; + class App { - async run() {} + async run() { + const purchaseAmount = await getPurchaseAmount(); + + const lottos = generateLottoList(purchaseAmount); + + printLottoList(lottos); + } } export default App; diff --git a/src/utils/console.js b/src/utils/console.js index 58e9ee62c..cec089646 100644 --- a/src/utils/console.js +++ b/src/utils/console.js @@ -1,4 +1,7 @@ import { Console } from '@woowacourse/mission-utils'; +import { CONFIRMATION_MESSAGES } from '../constants/constants.js'; + +const { PURCHASE_COMPLETE_MESSAGE } = CONFIRMATION_MESSAGES; const printMessage = (message) => { Console.print(message); @@ -8,4 +11,15 @@ const promptUserInput = async (message) => { return await Console.readLineAsync(message); }; -export { printMessage, promptUserInput }; +const printLotto = (lotto) => { + Console.print(`[${lotto.getNumbers().join(', ')}]`); +}; + +const printLottoList = (lottos) => { + printMessage(`\n${PURCHASE_COMPLETE_MESSAGE(lottos.length)}`); + lottos.forEach((lotto) => { + printLotto(lotto); + }); +}; + +export { printMessage, promptUserInput, printLottoList }; From 665973d909e323bf61f2b168d3fc468a85e62350 Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 18:16:07 +0900 Subject: [PATCH 16/30] =?UTF-8?q?test:=20=EB=A1=9C=EB=98=90=20=EB=B0=9C?= =?UTF-8?q?=ED=96=89=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 발행된 로또의 개수가 입력된 금액에 비례하여 올바르게 계산되는지 테스트 - 각 로또 번호가 1 ~ 45 사이의 중복되지 않는 6개의 숫자로 구성되어 있는지 테스트 - 각 로또 번호가 오름차순으로 정렬되어 있는지 테스트 --- __tests__/generate-lotto-test.js | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 __tests__/generate-lotto-test.js diff --git a/__tests__/generate-lotto-test.js b/__tests__/generate-lotto-test.js new file mode 100644 index 000000000..93d624832 --- /dev/null +++ b/__tests__/generate-lotto-test.js @@ -0,0 +1,34 @@ +import generateLottoList from '../src/utils/generate-lotto.js'; +import { LOTTO_TICKET_PRICE } from '../src/constants/constants.js'; + +describe('로또 발행 테스트', () => { + const amount = 5000; + const lottos = generateLottoList(amount); + + test('입력한 금액에 따라 올바른 개수의 로또가 발행되는지 확인한다', () => { + const expectedLottoCount = amount / LOTTO_TICKET_PRICE; + + expect(lottos.length).toBe(expectedLottoCount); + }); + + test('각 로또 번호가 1 ~ 45 사이의 중복되지 않는 6개의 숫자로 구성되어 있는지 확인한다', () => { + lottos.forEach((lotto) => { + const numbers = lotto.getNumbers(); + + expect(numbers.length).toBe(6); + expect(new Set(numbers).size).toBe(6); + numbers.forEach((number) => { + expect(number).toBeGreaterThanOrEqual(1); + expect(number).toBeLessThanOrEqual(45); + }); + }); + }); + + test('각 로또 번호가 오름차순으로 정렬되어 있는지 확인한다', () => { + lottos.forEach((lotto) => { + const numbers = lotto.getNumbers(); + const sortedNumbers = [...numbers].sort((a, b) => a - b); + expect(numbers).toEqual(sortedNumbers); + }); + }); +}); From 19ef35e2300477063c2f54d35d7b8c65e7475823 Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 18:19:00 +0900 Subject: [PATCH 17/30] =?UTF-8?q?docs:=20=EB=A1=9C=EB=98=90=20=EB=B0=9C?= =?UTF-8?q?=ED=96=89=20=EB=B0=8F=20=EB=B2=88=ED=98=B8=20=EC=B6=9C=EB=A0=A5?= =?UTF-8?q?=20=EC=99=84=EB=A3=8C=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 98a1e7abf..bebd76c83 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,11 @@ - 1,000원 단위로 나누어 떨어지는지 확인하여 유효성을 검증한다. - **예외**: 1,000원 단위로 나누어 떨어지지 않는 경우, `[ERROR]` 메시지 출력 후 재입력 요청. -- [ ] **로또 발행** +- [x] **로또 발행** - 입력받은 금액을 바탕으로 로또 발행 개수를 계산한다. (로또 1장당 1,000원) - 발행한 로또 번호는 1~45 사이의 중복되지 않는 6개의 숫자로 구성된다. -- [ ] **로또 번호 출력** +- [x] **로또 번호 출력** - 발행한 모든 로또 번호를 오름차순으로 정렬하여 출력한다. - 출력 형식: `N개를 구매했습니다.` 및 각 로또 번호를 배열 형식으로 출력한다. @@ -53,11 +53,11 @@ TDD를 적용하여 기능별 테스트 케이스를 작성합니다. 다음은 - 올바른 금액을 입력했을 때 정상적으로 처리되는지 확인한다. - 1,000원 단위로 나누어 떨어지지 않는 금액을 입력했을 때 에러 메시지가 출력되는지 확인한다. -- [ ] **로또 발행 테스트** +- [x] **로또 발행 테스트** - 입력한 금액에 따라 올바른 개수의 로또가 발행되는지 확인한다. - 각 로또 번호가 1 ~ 45 사이의 중복되지 않는 6개의 숫자로 구성되어 있는지 확인한다. -- [ ] **로또 번호 출력 테스트** +- [x] **로또 번호 출력 테스트** - 발행한 로또 번호가 오름차순으로 출력되는지 확인한다. - [ ] **당첨 번호 및 보너스 번호 입력 및 검증 테스트** From 890a64c16b053aea29c440f094bed165e7a55806 Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 18:35:29 +0900 Subject: [PATCH 18/30] =?UTF-8?q?feat:=20=EC=B5=9C=EB=8C=80=20=EA=B5=AC?= =?UTF-8?q?=EC=9E=85=20=EA=B8=88=EC=95=A1=20=EC=A0=9C=ED=95=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MAX_PURCHASE_AMOUNT 상수 및 관련 에러 메시지 추가 - validateAmount 함수에 최대 구입 금액 검증 로직 추가 --- src/constants/constants.js | 4 ++++ src/validations/purchase-amount.js | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/constants/constants.js b/src/constants/constants.js index 8dbc85076..bedce9342 100644 --- a/src/constants/constants.js +++ b/src/constants/constants.js @@ -1,4 +1,5 @@ const LOTTO_TICKET_PRICE = 1000; +const MAX_PURCHASE_AMOUNT = 100000; const USER_PROMPT_MESSAGES = Object.freeze({ GET_PURCHASE_AMOUNT: '구입금액을 입력해 주세요.\n', @@ -15,6 +16,8 @@ const CONFIRMATION_MESSAGES = Object.freeze({ const ERROR_MESSAGES = Object.freeze({ INVALID_EMPTY_INPUT: '[ERROR] 아무것도 입력하지 않았습니다. 값을 입력해 주세요.', + INVALID_PURCHASE_AMOUNT_LIMIT: + '[ERROR] 구입 금액은 최대 100,000원까지 가능합니다.', INVALID_PURCHASE_AMOUNT_NOT_NUMBER: '[ERROR] 구입 금액은 숫자만 입력해 주세요.', INVALID_PURCHASE_AMOUNT_NOT_TICKET_UNIT: @@ -41,4 +44,5 @@ export { CONFIRMATION_MESSAGES, ERROR_MESSAGES, PRIZE_AMOUNTS, + MAX_PURCHASE_AMOUNT, }; diff --git a/src/validations/purchase-amount.js b/src/validations/purchase-amount.js index 6d238a0ce..89d6ebcea 100644 --- a/src/validations/purchase-amount.js +++ b/src/validations/purchase-amount.js @@ -1,9 +1,14 @@ import { trimInputAndCheckEmpty } from './common.js'; -import { ERROR_MESSAGES, LOTTO_TICKET_PRICE } from '../constants/constants.js'; +import { + ERROR_MESSAGES, + LOTTO_TICKET_PRICE, + MAX_PURCHASE_AMOUNT, +} from '../constants/constants.js'; const { INVALID_PURCHASE_AMOUNT_NOT_NUMBER, INVALID_PURCHASE_AMOUNT_NOT_TICKET_UNIT, + INVALID_PURCHASE_AMOUNT_LIMIT, } = ERROR_MESSAGES; const isPromptAmountNumber = (amount) => { @@ -13,6 +18,13 @@ const isPromptAmountNumber = (amount) => { return Number(amount); }; +const isAmountWithinLimit = (amount) => { + if (amount > MAX_PURCHASE_AMOUNT) { + throw new Error(INVALID_PURCHASE_AMOUNT_LIMIT); + } + return amount; +}; + const isValidTicketUnit = (amount) => { if (amount % LOTTO_TICKET_PRICE !== 0) { throw new Error(INVALID_PURCHASE_AMOUNT_NOT_TICKET_UNIT); @@ -24,7 +36,8 @@ const validateAmount = (amount) => { const amountAsString = String(amount); const trimmedAmount = trimInputAndCheckEmpty(amountAsString); const numericAmount = isPromptAmountNumber(trimmedAmount); - return isValidTicketUnit(numericAmount); + const result = isAmountWithinLimit(numericAmount); + return isValidTicketUnit(result); }; export default validateAmount; From 91ac8ae8471cf9a646abb644b9a8c9e73ac445d1 Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 18:36:08 +0900 Subject: [PATCH 19/30] =?UTF-8?q?test:=20=EC=B5=9C=EB=8C=80=20=EA=B5=AC?= =?UTF-8?q?=EC=9E=85=20=EA=B8=88=EC=95=A1=20=EC=B4=88=EA=B3=BC=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 구입 금액이 10만 원을 초과할 경우 예외 처리 검증 테스트 추가 --- __tests__/purchase-amount-test.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/__tests__/purchase-amount-test.js b/__tests__/purchase-amount-test.js index 59cdf8ca5..ae7d853d4 100644 --- a/__tests__/purchase-amount-test.js +++ b/__tests__/purchase-amount-test.js @@ -5,6 +5,7 @@ const { INVALID_EMPTY_INPUT, INVALID_PURCHASE_AMOUNT_NOT_NUMBER, INVALID_PURCHASE_AMOUNT_NOT_TICKET_UNIT, + INVALID_PURCHASE_AMOUNT_LIMIT, } = ERROR_MESSAGES; describe('로또 구입 금액 입력 테스트', () => { @@ -29,4 +30,11 @@ describe('로또 구입 금액 입력 테스트', () => { expect(() => validateAmount(input)).toThrow(expectedError); }, ); + + test('구입 금액이 최대 금액을 초과할 경우 예외가 발생한다', () => { + const invalidAmount = 150000; // 최대 금액을 초과하는 금액 + expect(() => validateAmount(invalidAmount)).toThrow( + INVALID_PURCHASE_AMOUNT_LIMIT, + ); + }); }); From 9b14bb05e83a3d0ced686eec406143d3c212972c Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 20:16:09 +0900 Subject: [PATCH 20/30] =?UTF-8?q?feat:=20=EB=A1=9C=EB=98=90=20=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B2=94=EC=9C=84=EC=99=80=20=ED=8F=AC=EB=A7=B7=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EA=B4=80=EB=A0=A8=20=EC=83=81=EC=88=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=98=A4=EB=A5=98=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로또 번호의 최소 및 최대 값을 정의하는 LOTTO_NUMBER_RANGE 추가 - 당첨 번호의 형식에 맞지 않는 경우의 에러 메시지 추가 (INVALID_FORMAT) - 로또 번호 범위 검증 관련 에러 메시지를 포괄적으로 변경 (INVALID_LOTTO_NUMBER) - 보너스 번호 중복에 대한 에러 메시지 개선 (INVALID_BONUS_NUMBER) - 당첨 번호의 포맷을 검증하는 WINNING_NUMBER_FORMAT_REGEX 상수 추가 --- src/constants/constants.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/constants/constants.js b/src/constants/constants.js index bedce9342..9ffbec11a 100644 --- a/src/constants/constants.js +++ b/src/constants/constants.js @@ -1,10 +1,15 @@ const LOTTO_TICKET_PRICE = 1000; const MAX_PURCHASE_AMOUNT = 100000; +const LOTTO_NUMBER_RANGE = { + MIN_NUMBER: 1, + MAX_NUMBER: 45, +}; + const USER_PROMPT_MESSAGES = Object.freeze({ GET_PURCHASE_AMOUNT: '구입금액을 입력해 주세요.\n', - GET_WINNING_NUMBER: '당첨 번호를 입력해 주세요.\n', - GET_BONUS_NUMBER: '보너스 번호를 입력해 주세요.\n', + GET_WINNING_NUMBER: '\n당첨 번호를 입력해 주세요.\n', + GET_BONUS_NUMBER: '\n보너스 번호를 입력해 주세요.\n', }); const CONFIRMATION_MESSAGES = Object.freeze({ @@ -24,10 +29,11 @@ const ERROR_MESSAGES = Object.freeze({ '[ERROR] 구입 금액은 1,000원 단위로 입력해 주세요.', INVALID_LOTTO_NUMBER_COUNT: '[ERROR] 로또 번호는 6개여야 합니다.', INVALID_DUPLICATE_NUMBERS: '[ERROR] 로또 번호는 중복되지 않아야 합니다.', - INVALID_WINNING_NUMBER: - '[ERROR] 당첨 번호는 1~45 사이의 숫자여야 하며, 중복되지 않아야 합니다.', + INVALID_FORMAT: + '[ERROR] 당첨 번호는 콤마(,)로 구분된 숫자 6개여야 합니다. (예: 1,2,3,4,5,6)', + INVALID_LOTTO_NUMBER: '[ERROR] 로또 번호는 1~45 사이의 숫자여야 합니다.', INVALID_BONUS_NUMBER: - '[ERROR] 보너스 번호는 1~45 사이의 숫자여야 하며, 당첨 번호와 중복되지 않아야 합니다.', + '[ERROR] 보너스 번호는 당첨 번호와 중복되지 않아야 합니다.', }); const PRIZE_AMOUNTS = Object.freeze({ @@ -38,6 +44,8 @@ const PRIZE_AMOUNTS = Object.freeze({ FIFTH_PRIZE: 5000, }); +const WINNING_NUMBER_FORMAT_REGEX = /^(\d{1,2})(,\d{1,2}){5}$/; + export { LOTTO_TICKET_PRICE, USER_PROMPT_MESSAGES, @@ -45,4 +53,6 @@ export { ERROR_MESSAGES, PRIZE_AMOUNTS, MAX_PURCHASE_AMOUNT, + LOTTO_NUMBER_RANGE, + WINNING_NUMBER_FORMAT_REGEX, }; From a6d8623a2fc98e6294b92ead76b9d0bdb5c187d4 Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 20:24:20 +0900 Subject: [PATCH 21/30] =?UTF-8?q?refactor:=20=EC=88=AB=EC=9E=90=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=EA=B8=B0=EB=8A=A5=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로또 번호의 오름차순 정렬 기능을 별도의 유틸 파일로 분리 - `sort-numbers.js` 파일 생성 - 다른 곳에서도 재사용할 수 있도록 모듈화 --- src/utils/generate-lotto.js | 5 ++--- src/utils/sort-numbers.js | 4 ++++ 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 src/utils/sort-numbers.js diff --git a/src/utils/generate-lotto.js b/src/utils/generate-lotto.js index 34beb7cc5..f8dd75212 100644 --- a/src/utils/generate-lotto.js +++ b/src/utils/generate-lotto.js @@ -1,5 +1,6 @@ import { Random } from '@woowacourse/mission-utils'; import { LOTTO_TICKET_PRICE } from '../constants/constants.js'; +import sortNumbersAscending from './sort-numbers.js'; import Lotto from '../Lotto.js'; const calculateLottoCount = (amount) => amount / LOTTO_TICKET_PRICE; @@ -8,11 +9,9 @@ const generateUniqueLottoNumbers = () => { return Random.pickUniqueNumbersInRange(1, 45, 6); }; -const sortLottoNumbers = (lottoNumbers) => lottoNumbers.sort((a, b) => a - b); - const generateLotto = () => { const lottoNumbers = generateUniqueLottoNumbers(); - const sortedLottoNumbers = sortLottoNumbers(lottoNumbers); + const sortedLottoNumbers = sortNumbersAscending(lottoNumbers); return new Lotto(sortedLottoNumbers); }; diff --git a/src/utils/sort-numbers.js b/src/utils/sort-numbers.js new file mode 100644 index 000000000..8113960a6 --- /dev/null +++ b/src/utils/sort-numbers.js @@ -0,0 +1,4 @@ +const sortNumbersAscending = (lottoNumbers) => + lottoNumbers.sort((a, b) => a - b); + +export default sortNumbersAscending; From d1bd6f429313037dbee5f2c73dc82c40b4242051 Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 20:30:10 +0900 Subject: [PATCH 22/30] =?UTF-8?q?feat:=20=EB=8B=B9=EC=B2=A8=20=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9E=85=EB=A0=A5=20=EC=B2=98=EB=A6=AC=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 당첨 번호 유효성 검사 모듈화 (`validateWinningNumbers`) - 입력 형식 검증 (`isValidFormat`): 콤마로 구분된 숫자 6개인지 확인 - 숫자 범위 검증 (`validateNumberRange`): 로또 번호는 1~45 사이의 숫자여야 함 - 중복 숫자 검증 (`validateUniqueNumbers`): 중복된 번호가 없어야 함 - 당첨 번호 입력 및 처리 함수 추가 (`getWinningNumbers`) - 사용자 입력을 받아 유효성 검사 후 오름차순으로 정렬 - 잘못된 입력 시 에러 메시지 출력 후 재입력 요청 --- src/utils/get-winning-numbers.js | 25 ++++++++++++++ src/validations/winning-number.js | 55 +++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 src/utils/get-winning-numbers.js create mode 100644 src/validations/winning-number.js diff --git a/src/utils/get-winning-numbers.js b/src/utils/get-winning-numbers.js new file mode 100644 index 000000000..08f1a783c --- /dev/null +++ b/src/utils/get-winning-numbers.js @@ -0,0 +1,25 @@ +import { promptUserInput, printMessage } from './console.js'; +import { USER_PROMPT_MESSAGES } from '../constants/constants.js'; +import validateWinningNumbers from '../validations/winning-number.js'; +import sortNumbersAscending from './sort-numbers.js'; + +const { GET_WINNING_NUMBER } = USER_PROMPT_MESSAGES; + +const getWinningNumbers = async () => { + while (true) { + try { + const winningNumbers = await promptUserInput(GET_WINNING_NUMBER); + + const validatedWinningNumbers = validateWinningNumbers(winningNumbers); + const sortedWinningNumbers = sortNumbersAscending( + validatedWinningNumbers, + ); + + return sortedWinningNumbers; + } catch (error) { + printMessage(error.message); + } + } +}; + +export default getWinningNumbers; diff --git a/src/validations/winning-number.js b/src/validations/winning-number.js new file mode 100644 index 000000000..3ad86b3a1 --- /dev/null +++ b/src/validations/winning-number.js @@ -0,0 +1,55 @@ +import { + ERROR_MESSAGES, + LOTTO_NUMBER_RANGE, + WINNING_NUMBER_FORMAT_REGEX, +} from '../constants/constants.js'; + +const { + INVALID_LOTTO_NUMBER, + INVALID_DUPLICATE_NUMBERS, + INVALID_LOTTO_NUMBER_COUNT, + INVALID_FORMAT, +} = ERROR_MESSAGES; +const { MIN_NUMBER, MAX_NUMBER } = LOTTO_NUMBER_RANGE; + +const isValidFormat = (numbers) => { + if (!WINNING_NUMBER_FORMAT_REGEX.test(numbers)) { + throw new Error(INVALID_FORMAT); + } +}; + +const isValidRange = (number) => number >= MIN_NUMBER && number <= MAX_NUMBER; + +const validateNumberCount = (numbers) => { + if (numbers.length !== 6) { + throw new Error(INVALID_LOTTO_NUMBER_COUNT); + } +}; + +const validateUniqueNumbers = (numbers) => { + if (new Set(numbers).size !== numbers.length) { + throw new Error(INVALID_DUPLICATE_NUMBERS); + } +}; + +const validateNumberRange = (numbers) => { + numbers.forEach((number) => { + if (!isValidRange(number)) { + throw new Error(INVALID_LOTTO_NUMBER); + } + }); +}; + +const validateWinningNumbers = (input) => { + isValidFormat(input); + + const numberArray = input.split(','); + + validateNumberCount(numberArray); + validateUniqueNumbers(numberArray); + validateNumberRange(numberArray); + + return numberArray; +}; + +export default validateWinningNumbers; From dbab83165a3b96a3e9ede405a48720b180ec6013 Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 20:53:30 +0900 Subject: [PATCH 23/30] =?UTF-8?q?refactor:=20=EB=8B=B9=EC=B2=A8=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80?= =?UTF-8?q?=EC=82=AC=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20=EB=B0=8F?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - validateNumberCount 함수 삭제하여 숫자 개수 유효성 검증을 통합 처리 - 파일명 'winning-number.js'에서 'winning-numbers.js'로 변경하여 파일명 통일 --- src/utils/get-winning-numbers.js | 2 +- .../{winning-number.js => winning-numbers.js} | 15 ++------------- 2 files changed, 3 insertions(+), 14 deletions(-) rename src/validations/{winning-number.js => winning-numbers.js} (76%) diff --git a/src/utils/get-winning-numbers.js b/src/utils/get-winning-numbers.js index 08f1a783c..56591c5f6 100644 --- a/src/utils/get-winning-numbers.js +++ b/src/utils/get-winning-numbers.js @@ -1,6 +1,6 @@ import { promptUserInput, printMessage } from './console.js'; import { USER_PROMPT_MESSAGES } from '../constants/constants.js'; -import validateWinningNumbers from '../validations/winning-number.js'; +import validateWinningNumbers from '../validations/winning-numbers.js'; import sortNumbersAscending from './sort-numbers.js'; const { GET_WINNING_NUMBER } = USER_PROMPT_MESSAGES; diff --git a/src/validations/winning-number.js b/src/validations/winning-numbers.js similarity index 76% rename from src/validations/winning-number.js rename to src/validations/winning-numbers.js index 3ad86b3a1..7c1b63fdd 100644 --- a/src/validations/winning-number.js +++ b/src/validations/winning-numbers.js @@ -4,12 +4,8 @@ import { WINNING_NUMBER_FORMAT_REGEX, } from '../constants/constants.js'; -const { - INVALID_LOTTO_NUMBER, - INVALID_DUPLICATE_NUMBERS, - INVALID_LOTTO_NUMBER_COUNT, - INVALID_FORMAT, -} = ERROR_MESSAGES; +const { INVALID_LOTTO_NUMBER, INVALID_DUPLICATE_NUMBERS, INVALID_FORMAT } = + ERROR_MESSAGES; const { MIN_NUMBER, MAX_NUMBER } = LOTTO_NUMBER_RANGE; const isValidFormat = (numbers) => { @@ -20,12 +16,6 @@ const isValidFormat = (numbers) => { const isValidRange = (number) => number >= MIN_NUMBER && number <= MAX_NUMBER; -const validateNumberCount = (numbers) => { - if (numbers.length !== 6) { - throw new Error(INVALID_LOTTO_NUMBER_COUNT); - } -}; - const validateUniqueNumbers = (numbers) => { if (new Set(numbers).size !== numbers.length) { throw new Error(INVALID_DUPLICATE_NUMBERS); @@ -45,7 +35,6 @@ const validateWinningNumbers = (input) => { const numberArray = input.split(','); - validateNumberCount(numberArray); validateUniqueNumbers(numberArray); validateNumberRange(numberArray); From 2717f2ad09e45734f2a9264dd030f781884ee92c Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 20:54:46 +0900 Subject: [PATCH 24/30] =?UTF-8?q?test:=20=EB=8B=B9=EC=B2=A8=20=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9E=85=EB=A0=A5=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 올바른 형식의 당첨 번호 입력 시 정상 처리되는지 확인하는 테스트 추가 - 잘못된 형식의 당첨 번호 입력 시 각 에러 메시지 발생 여부를 확인하는 테스트 추가 --- __tests__/winning-numbers-test.js | 53 +++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 __tests__/winning-numbers-test.js diff --git a/__tests__/winning-numbers-test.js b/__tests__/winning-numbers-test.js new file mode 100644 index 000000000..376f65a4b --- /dev/null +++ b/__tests__/winning-numbers-test.js @@ -0,0 +1,53 @@ +import validateWinningNumbers from '../src/validations/winning-numbers.js'; +import { ERROR_MESSAGES } from '../src/constants/constants.js'; + +const { INVALID_FORMAT, INVALID_LOTTO_NUMBER, INVALID_DUPLICATE_NUMBERS } = + ERROR_MESSAGES; + +describe('당첨 번호 입력 테스트', () => { + test.each([ + [ + '올바른 형식의 당첨 번호를 입력할 때 정상적으로 처리되는지 확인', + '1,2,3,4,5,6', + ['1', '2', '3', '4', '5', '6'], + ], + ])('%s', (_, input, expected) => { + const result = validateWinningNumbers(input); + expect(result).toEqual(expected); + }); + + test.each([ + [ + '숫자 형식이 아닌 경우 에러 메시지가 발생하는지 확인', + '1,2,a,4,5,6', + INVALID_FORMAT, + ], + [ + '구분자가 , 가 아닌 경우 에러 메시지가 발생하는지 확인', + '1/2/3/4/5/6', + INVALID_FORMAT, + ], + [ + '6개의 숫자가 아닌 경우 에러 메시지가 발생하는지 확인', + '1,2,3,4,5', + INVALID_FORMAT, + ], + [ + '중복된 번호가 있는 경우 에러 메시지가 발생하는지 확인', + '1,2,3,4,5,5', + INVALID_DUPLICATE_NUMBERS, + ], + [ + '범위를 벗어난 번호가 있는 경우 에러 메시지가 발생하는지 확인 (0 포함)', + '0,2,3,4,5,6', + INVALID_LOTTO_NUMBER, + ], + [ + '범위를 벗어난 번호가 있는 경우 에러 메시지가 발생하는지 확인 (46 포함)', + '1,2,3,4,5,46', + INVALID_LOTTO_NUMBER, + ], + ])('%s', (_, input, expectedError) => { + expect(() => validateWinningNumbers(input)).toThrow(expectedError); + }); +}); From 07d741ae1c2bc305ee6118299910621487a4ae59 Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 22:17:36 +0900 Subject: [PATCH 25/30] =?UTF-8?q?feat:=20=EC=97=90=EB=9F=AC=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EC=83=81=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 숫자 입력 유효성 검증을 위한 에러 메시지 상수 추가 --- src/constants/constants.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/constants/constants.js b/src/constants/constants.js index 9ffbec11a..361521b2b 100644 --- a/src/constants/constants.js +++ b/src/constants/constants.js @@ -34,6 +34,7 @@ const ERROR_MESSAGES = Object.freeze({ INVALID_LOTTO_NUMBER: '[ERROR] 로또 번호는 1~45 사이의 숫자여야 합니다.', INVALID_BONUS_NUMBER: '[ERROR] 보너스 번호는 당첨 번호와 중복되지 않아야 합니다.', + INVALID_NUMBER_INPUT: '[ERROR] 숫자만 입력해 주세요', }); const PRIZE_AMOUNTS = Object.freeze({ From b79b78582b24f0fb67bfc373f5f82a12eddb2aa9 Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 22:20:31 +0900 Subject: [PATCH 26/30] =?UTF-8?q?feat:=20=EB=B3=B4=EB=84=88=EC=8A=A4=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=9E=85=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 보너스 번호 입력을 처리하는 함수 생성 (get-bouns-number.js) - 보너스 번호 유효성 검사 모듈 추가 (bouns-number.js) - 보너스 번호가 당첨 번호와 중복되지 않도록 검증하는 로직 포함 --- src/utils/get-bouns-number.js | 19 +++++++++++++++++ src/validations/bouns-number.js | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/utils/get-bouns-number.js create mode 100644 src/validations/bouns-number.js diff --git a/src/utils/get-bouns-number.js b/src/utils/get-bouns-number.js new file mode 100644 index 000000000..0058797d9 --- /dev/null +++ b/src/utils/get-bouns-number.js @@ -0,0 +1,19 @@ +import { promptUserInput, printMessage } from './console.js'; +import { USER_PROMPT_MESSAGES } from '../constants/constants.js'; +import validateBonusNumber from '../validations/bouns-number.js'; +const { GET_BONUS_NUMBER } = USER_PROMPT_MESSAGES; + +const getBonusNumber = async (winningNumbers) => { + while (true) { + try { + const bonusNumber = await promptUserInput(GET_BONUS_NUMBER); + validateBonusNumber(bonusNumber, winningNumbers); + + return Number(bonusNumber); + } catch (error) { + printMessage(error.message); + } + } +}; + +export default getBonusNumber; diff --git a/src/validations/bouns-number.js b/src/validations/bouns-number.js new file mode 100644 index 000000000..e451b5722 --- /dev/null +++ b/src/validations/bouns-number.js @@ -0,0 +1,38 @@ +import { ERROR_MESSAGES, LOTTO_NUMBER_RANGE } from '../constants/constants.js'; + +const { INVALID_BONUS_NUMBER, INVALID_LOTTO_NUMBER, INVALID_NUMBER_INPUT } = + ERROR_MESSAGES; +const { MIN_NUMBER, MAX_NUMBER } = LOTTO_NUMBER_RANGE; + +const isValidRange = (number) => number >= MIN_NUMBER && number <= MAX_NUMBER; + +const validateNumberRange = (number) => { + if (!isValidRange(number)) { + throw new Error(INVALID_LOTTO_NUMBER); + } + return number; +}; + +const validateNumber = (input) => { + if (Number.isNaN(input)) { + throw new Error(INVALID_NUMBER_INPUT); + } + return input; +}; + +const validateDuplicateBonusNumber = (bonusNumber, winningNumbers) => { + if (winningNumbers.includes(bonusNumber)) { + throw new Error(INVALID_BONUS_NUMBER); + } +}; + +const validateBonusNumber = (input, winningNumbers) => { + const bonusNumber = validateNumber(input); + + validateDuplicateBonusNumber(bonusNumber, winningNumbers); + validateNumberRange(bonusNumber); + + return bonusNumber; +}; + +export default validateBonusNumber; From bdfab80102c88072d89191b58549f533c491dfe3 Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 22:24:39 +0900 Subject: [PATCH 27/30] =?UTF-8?q?refactor:=20=EB=8B=B9=EC=B2=A8=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=9E=85=EB=A0=A5=20=EC=8B=9C=20=EC=88=AB?= =?UTF-8?q?=EC=9E=90=20=ED=98=95=EC=8B=9D=20=EB=B3=80=ED=99=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - '01'과 같은 입력값을 숫자 '1'로 처리하기 위해 입력 문자열을 map(Number)로 숫자 변환 --- src/validations/winning-numbers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validations/winning-numbers.js b/src/validations/winning-numbers.js index 7c1b63fdd..03bc9f05f 100644 --- a/src/validations/winning-numbers.js +++ b/src/validations/winning-numbers.js @@ -33,7 +33,7 @@ const validateNumberRange = (numbers) => { const validateWinningNumbers = (input) => { isValidFormat(input); - const numberArray = input.split(','); + const numberArray = input.split(',').map(Number); validateUniqueNumbers(numberArray); validateNumberRange(numberArray); From 5308de18d2f3b3af8c7a43f4702052b92d4ca213 Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 22:36:57 +0900 Subject: [PATCH 28/30] =?UTF-8?q?refactor:=20=EB=8B=B9=EC=B2=A8=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 반환 값의 형식 변경에 따른 테스트 케이스 수정 - 올바른 형식의 당첨 번호 입력 시 예상 결과를 문자열 배열에서 숫자 배열로 변경 --- __tests__/winning-numbers-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/winning-numbers-test.js b/__tests__/winning-numbers-test.js index 376f65a4b..ec2dc1df3 100644 --- a/__tests__/winning-numbers-test.js +++ b/__tests__/winning-numbers-test.js @@ -9,7 +9,7 @@ describe('당첨 번호 입력 테스트', () => { [ '올바른 형식의 당첨 번호를 입력할 때 정상적으로 처리되는지 확인', '1,2,3,4,5,6', - ['1', '2', '3', '4', '5', '6'], + [1, 2, 3, 4, 5, 6], ], ])('%s', (_, input, expected) => { const result = validateWinningNumbers(input); From 34b04ac16f3c5fa2725ecb152d675a5bc1570118 Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 22:47:57 +0900 Subject: [PATCH 29/30] =?UTF-8?q?test:=20=EB=B3=B4=EB=84=88=EC=8A=A4=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=9E=85=EB=A0=A5=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 숫자가 아닌 값을 입력했을 때 에러 메시지가 발생하는지 확인하는 테스트 추가 - 보너스 번호가 당첨 번호와 중복될 때 에러 메시지가 발생하는지 확인하는 테스트 추가 - 보너스 번호가 범위를 벗어난 경우 에러 메시지가 발생하는지 확인하는 테스트 추가 --- __tests__/bonus-number-test.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 __tests__/bonus-number-test.js diff --git a/__tests__/bonus-number-test.js b/__tests__/bonus-number-test.js new file mode 100644 index 000000000..c735b2a09 --- /dev/null +++ b/__tests__/bonus-number-test.js @@ -0,0 +1,26 @@ +import validateBonusNumber from '../src/validations/bouns-number.js'; +import { ERROR_MESSAGES } from '../src/constants/constants.js'; +const { INVALID_NUMBER_INPUT, INVALID_BONUS_NUMBER, INVALID_LOTTO_NUMBER } = + ERROR_MESSAGES; + +describe('보너스 번호 입력 테스트', () => { + const testArray = [1, 2, 3, 4, 5, 6]; + + test('숫자가 아닌 값을 입력했을 때 에러 메시지가 발생하는지 확인', () => { + expect(() => validateBonusNumber('a', testArray)).toThrow( + INVALID_NUMBER_INPUT, + ); + }); + + test('중복된 번호를 입력했을 때 에러 메시지가 발생하는지 확인', () => { + expect(() => validateBonusNumber(3, testArray)).toThrow( + INVALID_BONUS_NUMBER, + ); + }); + + test('범위를 벗어난 번호를 입력했을 때 에러 메시지가 발생하는지 확인', () => { + expect(() => validateBonusNumber(46, testArray)).toThrow( + INVALID_LOTTO_NUMBER, + ); + }); +}); From eb6a91d7d6196e5aaa959a46f1e618c1b998b73d Mon Sep 17 00:00:00 2001 From: gumbi Date: Mon, 4 Nov 2024 22:49:30 +0900 Subject: [PATCH 30/30] =?UTF-8?q?fix:=20=EC=88=AB=EC=9E=90=EA=B0=80=20?= =?UTF-8?q?=EC=95=84=EB=8B=8C=20=EC=9E=85=EB=A0=A5=20=EA=B0=92=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - validateNumber 함수에서 숫자가 아닌 값을 올바르게 처리하도록 로직 수정 - 기존 테스트에서 문자열 'a' 입력 시 올바른 에러 메시지가 발생하지 않는 문제 해결 --- src/validations/bouns-number.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/validations/bouns-number.js b/src/validations/bouns-number.js index e451b5722..3e2443235 100644 --- a/src/validations/bouns-number.js +++ b/src/validations/bouns-number.js @@ -14,10 +14,11 @@ const validateNumberRange = (number) => { }; const validateNumber = (input) => { - if (Number.isNaN(input)) { + const parsedNumber = Number(input); + if (Number.isNaN(parsedNumber)) { throw new Error(INVALID_NUMBER_INPUT); } - return input; + return parsedNumber; }; const validateDuplicateBonusNumber = (bonusNumber, winningNumbers) => {