Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .idea/.name

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions .idea/codeStyles/codeStyleConfig.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# 테스트 항목
✅ 1. 기본 기능 테스트 (정상 시나리오)
가장 일반적인 입력을 주었을 때, 예상한 대로 동작하는지 확인

예) 로그인 기능: 올바른 ID/PW 입력 시 로그인 성공

✅ 2. 경계값 테스트 (Boundary Value Testing)
입력값의 경계(최소값, 최대값 등)에서 오류가 없는지 확인

예) 입력 가능한 글자 수가 1~100자일 경우:

0자, 1자, 100자, 101자 테스트

✅ 3. 예외/에러 처리 테스트 (Negative Testing)
잘못된 입력이나 조건에서 시스템이 적절히 대응하는지 확인

예) 이메일 형식이 아닌 문자열 입력 시 에러 메시지 출력

✅ 4. 빈 입력/Null 값 테스트
사용자가 아무것도 입력하지 않거나, null/undefined가 들어왔을 때의 동작 확인

✅ 5. 중복/재입력 테스트
동일한 입력이 반복될 때의 시스템 반응

예) 동일한 ID로 회원가입 시 처리 방식

---

✅ 6. 성능/부하 테스트 (기본 수준)
일정 수준 이상의 데이터가 들어왔을 때 시스템이 버티는지

예) 검색어 자동완성에 초당 1000건 요청 시 응답 지연 여부

✅ 7. 보안 관련 테스트
허용되지 않은 접근이나 데이터 조작이 가능한지 확인

예) 인증되지 않은 사용자가 관리자 페이지 접근 시 차단 여부

✅ 8. 호환성 테스트
여러 환경/브라우저/OS에서 동일하게 동작하는지 (웹/모바일 앱의 경우)

✅ 9. 상태 유지/세션 테스트
세션이 끊긴 뒤의 동작, 로그인 후 특정 시간 지나면 로그아웃 되는지 등

✅ 10. 업데이트/삭제 등 부작용 테스트
특정 기능 실행 후 다른 기능에 영향이 없는지 확인

예) 게시글 삭제 후 목록이 제대로 갱신되는지

2 changes: 2 additions & 0 deletions src/main/java/io/hhplus/tdd/database/UserPointTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ public class UserPointTable {

private final Map<Long, UserPoint> table = new HashMap<>();

// 사용자 Id 기반 데이터 조회
public UserPoint selectById(Long id) {
throttle(200);
return table.getOrDefault(id, UserPoint.empty(id));
}

// USER POINT 삽입 함수
public UserPoint insertOrUpdate(long id, long amount) {
throttle(300);
UserPoint userPoint = new UserPoint(id, amount, System.currentTimeMillis());
Expand Down
12 changes: 8 additions & 4 deletions src/main/java/io/hhplus/tdd/point/PointController.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@

import java.util.List;

import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/point")
@RequiredArgsConstructor
public class PointController {

private static final Logger log = LoggerFactory.getLogger(PointController.class);
private final PointService pointService;

/**
* TODO - 특정 유저의 포인트를 조회하는 기능을 작성해주세요.
Expand All @@ -19,7 +23,7 @@ public class PointController {
public UserPoint point(
@PathVariable long id
) {
return new UserPoint(0, 0, 0);
return pointService.selectPointById(id);
}

/**
Expand All @@ -29,7 +33,7 @@ public UserPoint point(
public List<PointHistory> history(
@PathVariable long id
) {
return List.of();
return pointService.selectHistoryById(id);
}

/**
Expand All @@ -40,7 +44,7 @@ public UserPoint charge(
@PathVariable long id,
@RequestBody long amount
) {
return new UserPoint(0, 0, 0);
return pointService.chargePointById(id, amount);
}

/**
Expand All @@ -51,6 +55,6 @@ public UserPoint use(
@PathVariable long id,
@RequestBody long amount
) {
return new UserPoint(0, 0, 0);
return pointService.usePointById(id, amount);
}
}
6 changes: 6 additions & 0 deletions src/main/java/io/hhplus/tdd/point/PointHistory.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package io.hhplus.tdd.point;

/*
* 포인트 내역 클래스
*
* 포인트 충전/사용 내역을 저장하는 클래스
*
*/
public record PointHistory(
long id,
long userId,
Expand Down
58 changes: 58 additions & 0 deletions src/main/java/io/hhplus/tdd/point/PointService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.hhplus.tdd.point;

import io.hhplus.tdd.point.UserPoint;
import org.springframework.stereotype.Service;

import io.hhplus.tdd.database.PointHistoryTable;
import io.hhplus.tdd.database.UserPointTable;
import lombok.RequiredArgsConstructor;

import java.util.List;

@Service
@RequiredArgsConstructor // 클래스의 final 필드와 @NonNull로 표시된 필드에 대한 생성자를 자동으로 생성해 줍니다.
public class PointService {
private final UserPointTable userPointTable;
private final PointHistoryTable pointHistoryTable;

public UserPoint selectPointById(long userId) {
return userPointTable.selectById(userId);
}

public List<PointHistory> selectHistoryById(long id)
{
return pointHistoryTable.selectAllByUserId(id);
}

public UserPoint chargePointById(long userId, long chargeAmount) {
// check for user exists
UserPoint userPoint = userPointTable.selectById(userId);
if(userPoint == null){
userPoint = UserPoint.empty(userId);
}
/// DDD work
userPoint = userPoint.charge(userPoint, chargeAmount);
userPoint = userPointTable.insertOrUpdate(userId, userPoint.point());

// recording
pointHistoryTable.insert(userId, chargeAmount, TransactionType.CHARGE, System.currentTimeMillis());
return userPoint;
}

public UserPoint usePointById(long userId, long useAmount) {
// check for user exists
UserPoint userPoint = userPointTable.selectById(userId);
if(userPoint == null){
return UserPoint.empty(userId);
}
userPoint.chkForValidate(TransactionType.USE, useAmount);

// DDD
userPoint = userPoint.use(userPoint, useAmount);
userPoint = userPointTable.insertOrUpdate(userId, userPoint.point());

// recording
pointHistoryTable.insert(userId, useAmount, TransactionType.USE, System.currentTimeMillis());
return userPoint;
}
}
39 changes: 39 additions & 0 deletions src/main/java/io/hhplus/tdd/point/UserPoint.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,51 @@
package io.hhplus.tdd.point;

import lombok.val;
import org.apache.catalina.User;

public record UserPoint(
long id,
long point,
long updateMillis
) {

// 1억
private static final long maxPoint = 10000L;
public static UserPoint empty(long id) {
return new UserPoint(id, 0, System.currentTimeMillis());
}

/// point 경계값 체크
public void chkForValidate(TransactionType type, long amount){
// over 0 is validate
if(amount <= 0) throw new IllegalArgumentException("요청한 포인트가 0 보다 커야 합니다.");
switch (type){
case CHARGE -> {
if(point + amount > maxPoint)
{
throw new IllegalArgumentException("포인트 한도를 초과했습니다.");
}
}
case USE -> {
if(point - amount < 0)
{
throw new IllegalArgumentException("포인트가 부족합니다.");
}
}
}
}

/// Charging
public UserPoint charge(UserPoint userPoint, long amount)
{
chkForValidate(TransactionType.CHARGE, amount);
return new UserPoint(userPoint.id, userPoint.point + amount, System.currentTimeMillis());
}

/// Using
public UserPoint use(UserPoint userPoint, long amount)
{
chkForValidate(TransactionType.USE, amount);
return new UserPoint(userPoint.id, userPoint.point - amount, System.currentTimeMillis());
}
}
Loading