diff --git a/src/main/java/gift/auth/AuthenticationResolver.java b/src/main/java/gift/auth/AuthenticationResolver.java index cb428c53..05403337 100644 --- a/src/main/java/gift/auth/AuthenticationResolver.java +++ b/src/main/java/gift/auth/AuthenticationResolver.java @@ -26,7 +26,7 @@ public Member extractMember(String authorization) { final String email = jwtProvider.getEmail(token); return memberService.findByEmail(email).orElse(null); } catch (Exception e) { - return null; + throw new UnauthorizedException("유효하지 않은 토큰입니다"); } } } diff --git a/src/main/java/gift/auth/GlobalExceptionHandler.java b/src/main/java/gift/auth/GlobalExceptionHandler.java new file mode 100644 index 00000000..340d5a69 --- /dev/null +++ b/src/main/java/gift/auth/GlobalExceptionHandler.java @@ -0,0 +1,14 @@ +package gift.auth; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(UnauthorizedException.class) + public ResponseEntity handleUnauthorized(UnauthorizedException e) { + return ResponseEntity.status(401).build(); + } +} diff --git a/src/main/java/gift/auth/UnauthorizedException.java b/src/main/java/gift/auth/UnauthorizedException.java new file mode 100644 index 00000000..6e0e11bc --- /dev/null +++ b/src/main/java/gift/auth/UnauthorizedException.java @@ -0,0 +1,7 @@ +package gift.auth; + +public class UnauthorizedException extends RuntimeException { + public UnauthorizedException(String message) { + super(message); + } +} diff --git a/src/test/java/gift/auth/AuthenticationResolverTest.java b/src/test/java/gift/auth/AuthenticationResolverTest.java new file mode 100644 index 00000000..b3df31a1 --- /dev/null +++ b/src/test/java/gift/auth/AuthenticationResolverTest.java @@ -0,0 +1,75 @@ +package gift.auth; + +import gift.member.MemberService; +import io.restassured.RestAssured; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.jdbc.Sql; + +import static org.hamcrest.Matchers.notNullValue; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class AuthenticationResolverTest { + + @LocalServerPort + int port; + + @Autowired + JwtProvider jwtProvider; + + @Autowired + MemberService memberService; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Test + @Sql(scripts = "/data/truncate.sql", + executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void 잘못된_토큰으로_요청하면_401() { + RestAssured + .given() + .header("Authorization", "Bearer invalid-token") + .when() + .get("/api/wishes") + .then() + .statusCode(401); + } + + @Test + @Sql(scripts = "/data/truncate.sql", + executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void 유효한_토큰이지만_회원이_없으면_401() { + String token = jwtProvider.createToken("nonexistent@example.com"); + + RestAssured + .given() + .header("Authorization", "Bearer " + token) + .when() + .get("/api/wishes") + .then() + .statusCode(401); + } + + @Test + @Sql(scripts = "/data/truncate.sql", + executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void 유효한_토큰과_회원이_존재하면_200() { + memberService.create("valid@example.com"); + String token = jwtProvider.createToken("valid@example.com"); + + RestAssured + .given() + .header("Authorization", "Bearer " + token) + .when() + .get("/api/wishes") + .then() + .statusCode(200) + .body(notNullValue()); + } +} diff --git a/src/test/java/gift/category/CategoryServiceTest.java b/src/test/java/gift/category/CategoryServiceTest.java new file mode 100644 index 00000000..518bbd35 --- /dev/null +++ b/src/test/java/gift/category/CategoryServiceTest.java @@ -0,0 +1,40 @@ +package gift.category; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.jdbc.Sql; + +import java.util.NoSuchElementException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@SpringBootTest +class CategoryServiceTest { + + @Autowired + CategoryService categoryService; + + @Test + @Sql(scripts = {"/data/truncate.sql", "/data/seed/find_category_by_id.sql"}, + executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void 존재하는_ID로_엔티티를_조회하면_카테고리를_반환한다() { + Category category = categoryService.findEntityById(1L); + + assertThat(category.getId()).isEqualTo(1L); + assertThat(category.getName()).isEqualTo("전자기기"); + assertThat(category.getColor()).isEqualTo("#1E90FF"); + assertThat(category.getImageUrl()).isEqualTo("https://example.com/images/electronics.jpg"); + assertThat(category.getDescription()).isEqualTo("전자기기 카테고리"); + } + + @Test + @Sql(scripts = {"/data/truncate.sql"}, + executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void 존재하지_않는_ID로_엔티티를_조회하면_예외가_발생하고_메시지에_ID가_포함된다() { + assertThatThrownBy(() -> categoryService.findEntityById(999L)) + .isInstanceOf(NoSuchElementException.class) + .hasMessageContaining("999"); + } +} diff --git a/src/test/resources/data/seed/delete_member_success.sql b/src/test/resources/data/seed/delete_member_success.sql new file mode 100644 index 00000000..12669c47 --- /dev/null +++ b/src/test/resources/data/seed/delete_member_success.sql @@ -0,0 +1,3 @@ +-- seed for: delete_member_success +INSERT INTO member (id, email, password, point) +VALUES (1, 'delete-target@example.com', 'password123', 0); diff --git a/src/test/resources/data/seed/find_category_by_id.sql b/src/test/resources/data/seed/find_category_by_id.sql new file mode 100644 index 00000000..ae517460 --- /dev/null +++ b/src/test/resources/data/seed/find_category_by_id.sql @@ -0,0 +1,2 @@ +INSERT INTO category (id, name, color, image_url, description) +VALUES (1, '전자기기', '#1E90FF', 'https://example.com/images/electronics.jpg', '전자기기 카테고리');