From d143a5cedd1b531eb27007a6ea9be4f09f7ae2d4 Mon Sep 17 00:00:00 2001 From: art Date: Mon, 29 Jun 2026 19:10:23 +0900 Subject: [PATCH] =?UTF-8?q?fix(game):=20=EC=97=86=EB=8A=94=20=EA=B2=8C?= =?UTF-8?q?=EC=9E=84=20ID=20404=20=EC=A0=84=ED=99=98=20+=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=86=A0=ED=83=80=EC=9E=85=20dead=20code=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - abstracts/(4파일)·GameCatalog·fragments/header.jspf 삭제(외부참조 0 확인) - GameController.gameDetail(): GameCatalog fallback(redirect/정적뷰) 제거 → DB 미존재 게임 ID = HTTP 404(ResponseStatusException) - ApiExceptionControllerAdvice: ResponseStatusException 핸들러 추가(404 status 보존 — 기존 Exception 핸들러가 500으로 가리던 것 수정) - 회귀 가드 gameDetailThrowsNotFoundWhenGameMissing 추가 Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01K3FeMrbtxfTScjrwUukyHD --- .../bibimbap/abstracts/ErrorResult.java | 7 ----- .../bibimbap/abstracts/Request.java | 7 ----- .../pandoli365/bibimbap/abstracts/Result.java | 27 ------------------- .../bibimbap/abstracts/Service.java | 18 ------------- .../api/ApiExceptionControllerAdvice.java | 11 ++++++++ .../controller/api/GameController.java | 27 ++----------------- .../pandoli365/bibimbap/game/GameCatalog.java | 27 ------------------- .../webapp/WEB-INF/jsp/fragments/header.jspf | 24 ----------------- .../api/GameLikeControllerTest.java | 18 +++++++++++++ 9 files changed, 31 insertions(+), 135 deletions(-) delete mode 100644 src/main/java/com/pandoli365/bibimbap/abstracts/ErrorResult.java delete mode 100644 src/main/java/com/pandoli365/bibimbap/abstracts/Request.java delete mode 100644 src/main/java/com/pandoli365/bibimbap/abstracts/Result.java delete mode 100644 src/main/java/com/pandoli365/bibimbap/abstracts/Service.java delete mode 100644 src/main/java/com/pandoli365/bibimbap/game/GameCatalog.java delete mode 100644 src/main/webapp/WEB-INF/jsp/fragments/header.jspf diff --git a/src/main/java/com/pandoli365/bibimbap/abstracts/ErrorResult.java b/src/main/java/com/pandoli365/bibimbap/abstracts/ErrorResult.java deleted file mode 100644 index 6363d6c..0000000 --- a/src/main/java/com/pandoli365/bibimbap/abstracts/ErrorResult.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.pandoli365.bibimbap.abstracts; - -public class ErrorResult extends Result{ - public ErrorResult(int status) { - super(status); - } -} \ No newline at end of file diff --git a/src/main/java/com/pandoli365/bibimbap/abstracts/Request.java b/src/main/java/com/pandoli365/bibimbap/abstracts/Request.java deleted file mode 100644 index 091e41f..0000000 --- a/src/main/java/com/pandoli365/bibimbap/abstracts/Request.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.pandoli365.bibimbap.abstracts; - -public abstract class Request { - public boolean IsReceivedAllField() { - return true; - } -} \ No newline at end of file diff --git a/src/main/java/com/pandoli365/bibimbap/abstracts/Result.java b/src/main/java/com/pandoli365/bibimbap/abstracts/Result.java deleted file mode 100644 index 4b9f588..0000000 --- a/src/main/java/com/pandoli365/bibimbap/abstracts/Result.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.pandoli365.bibimbap.abstracts; - -public abstract class Result { - public int status; - public String message; - - public Result() {} - public Result(int status) { - this.status = status; - switch (status) - { - case 200: - this.message = "Success"; return; - case 400: - this.message = "Invalid Request"; return; - case 401: - this.message = "세션 만료"; return; - case 1000: - this.message = "NULL USERS"; return; - default: - System.out.println("잘못된 status 케이스"); - this.message = ""; - return; - } - } - -} \ No newline at end of file diff --git a/src/main/java/com/pandoli365/bibimbap/abstracts/Service.java b/src/main/java/com/pandoli365/bibimbap/abstracts/Service.java deleted file mode 100644 index ff97cfe..0000000 --- a/src/main/java/com/pandoli365/bibimbap/abstracts/Service.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.pandoli365.bibimbap.abstracts; - -import jakarta.servlet.http.HttpSession; - -public abstract class Service { - - public boolean is_login = false; - - public abstract Res StartService(HttpSession session, Req request); - - public Res ChackService(HttpSession session, Req request){ - if (is_login && session.getAttribute("id") == null) - return (Res) new ErrorResult(401); - if(request != null && !request.IsReceivedAllField()) - return (Res) new ErrorResult(401); - return StartService(session, request); - } -} diff --git a/src/main/java/com/pandoli365/bibimbap/controller/api/ApiExceptionControllerAdvice.java b/src/main/java/com/pandoli365/bibimbap/controller/api/ApiExceptionControllerAdvice.java index 77cb618..1bee1ad 100644 --- a/src/main/java/com/pandoli365/bibimbap/controller/api/ApiExceptionControllerAdvice.java +++ b/src/main/java/com/pandoli365/bibimbap/controller/api/ApiExceptionControllerAdvice.java @@ -7,6 +7,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.multipart.MaxUploadSizeExceededException; +import org.springframework.web.server.ResponseStatusException; import java.util.LinkedHashMap; import java.util.Map; @@ -24,6 +25,16 @@ public class ApiExceptionControllerAdvice { return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE).body(body); } + // ResponseStatusException(예: gameDetail 의 404)은 지정 status 를 보존한다. + // 더 구체적인 타입이라 아래 Exception 핸들러보다 우선 매칭된다. + @ExceptionHandler(ResponseStatusException.class) + public ResponseEntity> handleResponseStatus(ResponseStatusException exception) { + Map body = new LinkedHashMap<>(); + body.put("status", exception.getStatusCode().value()); + body.put("message", exception.getReason() != null ? exception.getReason() : "not found"); + return ResponseEntity.status(exception.getStatusCode()).body(body); + } + @ExceptionHandler(Exception.class) public ResponseEntity> handleException(Exception exception) { log.error("API request failed", exception); diff --git a/src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java b/src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java index 401c776..acfd2f0 100644 --- a/src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java +++ b/src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java @@ -2,7 +2,6 @@ package com.pandoli365.bibimbap.controller.api; import com.pandoli365.bibimbap.data.GameData; import com.pandoli365.bibimbap.data.GameLikeData; -import com.pandoli365.bibimbap.game.GameCatalog; import com.pandoli365.bibimbap.mapper.GameCommentsMapper; import com.pandoli365.bibimbap.mapper.GameLikesMapper; import com.pandoli365.bibimbap.mapper.GameReviewsMapper; @@ -23,9 +22,9 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.server.ResponseStatusException; import java.util.LinkedHashMap; -import java.util.List; import java.util.Locale; import java.util.Map; @@ -147,29 +146,7 @@ public class GameController { return "game-detail"; } - if (id < Integer.MIN_VALUE || id > Integer.MAX_VALUE || !GameCatalog.isValidId((int) id)) { - return "redirect:/"; - } - - int intId = (int) id; - int idx = GameCatalog.toIndex(intId); - model.addAttribute("gameId", intId); - model.addAttribute("gameName", GameCatalog.NAMES[idx]); - model.addAttribute("creator", GameCatalog.CREATORS[idx]); - model.addAttribute("likeCount", GameCatalog.LIKE_COUNTS[idx]); - model.addAttribute("likeCountFormatted", String.format("%,d", GameCatalog.LIKE_COUNTS[idx])); - model.addAttribute("creatorNote", GameCatalog.CREATOR_NOTES[idx]); - model.addAttribute("gitUrl", safeExternalUrl(GameCatalog.GIT_URLS[idx])); - model.addAttribute("webglUrl", webglUrlForGame(intId)); - model.addAttribute("webglFrameSrc", webglFrameSrc(webglUrlForGame(intId))); - model.addAttribute("webglDeployPath", webglUrlForGame(intId)); - model.addAttribute("owner", false); - model.addAttribute("comments", List.of()); - model.addAttribute("reviews", List.of()); - model.addAttribute("currentUserId", sessionUserId(session)); - model.addAttribute("userRole", (String) session.getAttribute("role")); - model.addAttribute("liked", false); - return "game-detail"; + throw new ResponseStatusException(HttpStatus.NOT_FOUND); } @GetMapping("/game/{id}/edit") diff --git a/src/main/java/com/pandoli365/bibimbap/game/GameCatalog.java b/src/main/java/com/pandoli365/bibimbap/game/GameCatalog.java deleted file mode 100644 index f05cb89..0000000 --- a/src/main/java/com/pandoli365/bibimbap/game/GameCatalog.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.pandoli365.bibimbap.game; - -public final class GameCatalog { - - private GameCatalog() { - } - - public static final String[] NAMES = {}; - - public static final String[] CREATORS = {}; - - public static final int[] LIKE_COUNTS = {}; - - public static final String[] CREATOR_NOTES = {}; - - public static final String[] GIT_URLS = {}; - - public static final int COUNT = NAMES.length; - - public static boolean isValidId(int id) { - return id >= 1 && id <= COUNT; - } - - public static int toIndex(int id) { - return id - 1; - } -} diff --git a/src/main/webapp/WEB-INF/jsp/fragments/header.jspf b/src/main/webapp/WEB-INF/jsp/fragments/header.jspf deleted file mode 100644 index c4da0b9..0000000 --- a/src/main/webapp/WEB-INF/jsp/fragments/header.jspf +++ /dev/null @@ -1,24 +0,0 @@ -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> - diff --git a/src/test/java/com/pandoli365/bibimbap/controller/api/GameLikeControllerTest.java b/src/test/java/com/pandoli365/bibimbap/controller/api/GameLikeControllerTest.java index 5fdf7ea..01d5a42 100644 --- a/src/test/java/com/pandoli365/bibimbap/controller/api/GameLikeControllerTest.java +++ b/src/test/java/com/pandoli365/bibimbap/controller/api/GameLikeControllerTest.java @@ -19,10 +19,13 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; +import org.springframework.ui.ExtendedModelMap; +import org.springframework.web.server.ResponseStatusException; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowableOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; @@ -191,6 +194,21 @@ class GameLikeControllerTest { verifyNoInteractions(gameLikesMapper); } + // ---- B2 회귀 가드: 없는 게임 ID 상세 접근 → 404 (GameCatalog fallback 제거 후) ---- + + @Test + void gameDetailThrowsNotFoundWhenGameMissing() { + GameController controller = controller(); + when(gamesMapper.getGame(99999L)).thenReturn(null); + + ResponseStatusException ex = catchThrowableOfType( + () -> controller.gameDetail(99999L, new ExtendedModelMap(), new MockHttpSession()), + ResponseStatusException.class); + + assertThat(ex).isNotNull(); + assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + } + // ---- helpers ---- private GameController controller() {