From f10dc2c0393748f0c730984694ea08f9398b018a Mon Sep 17 00:00:00 2001 From: chanwoo7 Date: Thu, 30 Apr 2026 00:52:00 +0900 Subject: [PATCH] =?UTF-8?q?feat(user):=20=EC=A0=84=ED=99=94=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=A0=95=EA=B7=9C=EC=8B=9D=20010-XXXX-XXXX=20?= =?UTF-8?q?=EA=B3=A0=EC=A0=95=EC=9C=BC=EB=A1=9C=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 ^[0-9-]+\$ + 7~20자 정책은 '01-2-3' 같은 비정상 패턴도 통과시켜 사실상 비검증 상태였음. 기획 figma 명세에 맞춰 010만 13자 고정 규칙으로 강화한다. - PHONE_REGEX = /^010-\\d{4}-\\d{4}\$/ 상수 추가 - PHONE_FORMAT_EXAMPLE 예시 문자열 상수 추가 - MIN_PHONE_LENGTH/MAX_PHONE_LENGTH 제거 (정규식으로 대체) - normalizePhoneNumber: 정규식 단일 검증으로 단순화 + 명확한 에러 메시지 - 회귀 테스트: 정상 3건 + trim 1건 + 비정상 10건으로 보강 --- src/features/user/constants/user.constants.ts | 5 +-- .../user/services/user-base.service.spec.ts | 34 +++++++++++++------ .../user/services/user-base.service.ts | 16 ++++----- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/features/user/constants/user.constants.ts b/src/features/user/constants/user.constants.ts index 5c64aa7..bbabab9 100644 --- a/src/features/user/constants/user.constants.ts +++ b/src/features/user/constants/user.constants.ts @@ -5,8 +5,9 @@ export const MAX_NICKNAME_LENGTH = 20; // ── 전화번호 ── -export const MIN_PHONE_LENGTH = 7; -export const MAX_PHONE_LENGTH = 20; +// 정책: 010-XXXX-XXXX 고정 (13자). figma 명세 기준. +export const PHONE_REGEX = /^010-\d{4}-\d{4}$/; +export const PHONE_FORMAT_EXAMPLE = '010-XXXX-XXXX'; // ── 생년월일 ── diff --git a/src/features/user/services/user-base.service.spec.ts b/src/features/user/services/user-base.service.spec.ts index 6590cd0..d5d639d 100644 --- a/src/features/user/services/user-base.service.spec.ts +++ b/src/features/user/services/user-base.service.spec.ts @@ -183,23 +183,35 @@ describe('UserBaseService (real DB)', () => { expect(service.testNormalizePhoneNumber(' ')).toBeNull(); }); - it('길이가 하한 미만이면 BadRequestException을 던진다', () => { - expect(() => service.testNormalizePhoneNumber('12345')).toThrow( - BadRequestException, + it.each([['010-0000-0000'], ['010-1234-5678'], ['010-9999-9999']])( + '정상 형식 %s 은 그대로 반환한다', + (raw) => { + expect(service.testNormalizePhoneNumber(raw)).toBe(raw); + }, + ); + + it('앞뒤 공백을 trim하여 검증한다', () => { + expect(service.testNormalizePhoneNumber(' 010-1234-5678 ')).toBe( + '010-1234-5678', ); }); - it('숫자와 하이픈 외 문자가 포함되면 BadRequestException을 던진다', () => { - expect(() => service.testNormalizePhoneNumber('010-abc-1234')).toThrow( + it.each([ + ['011-1234-5678'], // 010 prefix 외 + ['019-1234-5678'], // 010 prefix 외 + ['010-123-4567'], // 자릿수 부족 + ['010-12345-6789'], // 자릿수 초과 + ['01012345678'], // 하이픈 없음 + ['010 1234 5678'], // 공백 구분자 + ['+82-10-1234-5678'], // 국가코드 포함 + ['010-abc-1234'], // 문자 포함 + ['010--1234-5678'], // 하이픈 위치 잘못 + ['12345'], // 짧은 임의 문자열 + ])('비정상 형식 %s 은 BadRequestException을 던진다', (raw) => { + expect(() => service.testNormalizePhoneNumber(raw)).toThrow( BadRequestException, ); }); - - it('유효한 전화번호를 반환한다', () => { - expect(service.testNormalizePhoneNumber('010-1234-5678')).toBe( - '010-1234-5678', - ); - }); }); describe('normalizeBirthDate', () => { diff --git a/src/features/user/services/user-base.service.ts b/src/features/user/services/user-base.service.ts index 920512b..dd8709b 100644 --- a/src/features/user/services/user-base.service.ts +++ b/src/features/user/services/user-base.service.ts @@ -9,10 +9,10 @@ import { DEFAULT_PAGINATION_LIMIT, MAX_NICKNAME_LENGTH, MAX_PAGINATION_LIMIT, - MAX_PHONE_LENGTH, MIN_BIRTH_DATE, MIN_NICKNAME_LENGTH, - MIN_PHONE_LENGTH, + PHONE_FORMAT_EXAMPLE, + PHONE_REGEX, } from '@/features/user/constants/user.constants'; import type { UserAccountWithProfile } from '@/features/user/repositories/user.repository'; import { UserRepository } from '@/features/user/repositories/user.repository'; @@ -90,14 +90,10 @@ export abstract class UserBaseService { if (raw === undefined || raw === null) return null; const trimmed = raw.trim(); if (trimmed.length === 0) return null; - if ( - trimmed.length < MIN_PHONE_LENGTH || - trimmed.length > MAX_PHONE_LENGTH - ) { - throw new BadRequestException('Invalid phone number length.'); - } - if (!/^[0-9-]+$/.test(trimmed)) { - throw new BadRequestException('Invalid phone number format.'); + if (!PHONE_REGEX.test(trimmed)) { + throw new BadRequestException( + `Invalid phone number format. Expected ${PHONE_FORMAT_EXAMPLE}.`, + ); } return trimmed; }