diff --git a/src/docs/asciidoc/api.adoc b/src/docs/asciidoc/api.adoc index aa3b04c1..2f4869b1 100644 --- a/src/docs/asciidoc/api.adoc +++ b/src/docs/asciidoc/api.adoc @@ -50,6 +50,10 @@ operation::game-query-controller-test/출전_선수를_조회한다[snippets='ht operation::game-controller-test/경기를_등록한다[snippets='http-request,path-parameters,http-response'] +=== 게임 수정 + +operation::game-controller-test/경기를_수정한다[snippets='http-request,path-parameters,http-response'] + == 라인업 API === 라인업 선수 선발로 변경 diff --git a/src/main/java/com/sports/server/auth/config/SecurityConfig.java b/src/main/java/com/sports/server/auth/config/SecurityConfig.java index 26e49f9e..02c3ab33 100644 --- a/src/main/java/com/sports/server/auth/config/SecurityConfig.java +++ b/src/main/java/com/sports/server/auth/config/SecurityConfig.java @@ -43,6 +43,7 @@ SecurityFilterChain filterChain(HttpSecurity http, MvcRequestMatcher.Builder mvc mvc.pattern(HttpMethod.GET, "/members/info"), mvc.pattern(HttpMethod.POST, "/leagues"), mvc.pattern(HttpMethod.PUT, "/leagues/{leagueId}"), + mvc.pattern(HttpMethod.PUT, "/leagues/{leagueId}/{gameId}"), mvc.pattern(HttpMethod.POST, "/leagues/*/teams"), mvc.pattern(HttpMethod.POST, "/leagues/{leagueId}/teams"), mvc.pattern(HttpMethod.PUT, "/leagues/{leagueId}/teams/{teamId}"), diff --git a/src/main/java/com/sports/server/command/game/application/GameService.java b/src/main/java/com/sports/server/command/game/application/GameService.java index e20353e6..4b7de20e 100644 --- a/src/main/java/com/sports/server/command/game/application/GameService.java +++ b/src/main/java/com/sports/server/command/game/application/GameService.java @@ -3,9 +3,11 @@ import com.sports.server.auth.exception.AuthorizationErrorMessages; import com.sports.server.command.game.domain.Game; import com.sports.server.command.game.domain.GameRepository; +import com.sports.server.command.game.domain.GameState; import com.sports.server.command.game.domain.GameTeam; import com.sports.server.command.game.dto.GameRequestDto; import com.sports.server.command.league.domain.League; +import com.sports.server.command.league.domain.Round; import com.sports.server.command.leagueteam.domain.LeagueTeam; import com.sports.server.command.member.domain.Member; import com.sports.server.command.sport.domain.Sport; @@ -69,4 +71,19 @@ private League getLeagueAndCheckPermission(final Long leagueId, final Member man return league; } + + @Transactional + public void updateGame(Long leagueId, Long gameId, GameRequestDto.Update request, Member manager) { + League league = entityUtils.getEntity(leagueId, League.class); + if (!league.isManagedBy(manager)) { + throw new UnauthorizedException(AuthorizationErrorMessages.PERMISSION_DENIED); + } + Game game = entityUtils.getEntity(gameId, Game.class); + game.updateName(request.name()); + game.updateStartTime(request.startTime()); + game.updateVideoId(request.videoId()); + game.updateGameQuarter(request.quarter()); + game.updateState(GameState.from(request.state())); + game.updateRound(Round.from(request.round())); + } } \ No newline at end of file diff --git a/src/main/java/com/sports/server/command/game/domain/Game.java b/src/main/java/com/sports/server/command/game/domain/Game.java index 33b0cf46..08df3588 100644 --- a/src/main/java/com/sports/server/command/game/domain/Game.java +++ b/src/main/java/com/sports/server/command/game/domain/Game.java @@ -1,5 +1,6 @@ package com.sports.server.command.game.domain; +import com.sports.server.command.game.dto.GameRequestDto; import com.sports.server.command.league.domain.League; import com.sports.server.command.league.domain.Round; import com.sports.server.command.member.domain.Member; @@ -23,6 +24,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.springframework.util.StringUtils; import org.springframework.http.HttpStatus; @Entity @@ -114,6 +116,37 @@ public void cancelScore(LineupPlayer scorer) { scoredTeam.cancelScore(); } + public void updateName(String name) { + if (StringUtils.hasText(name)) { + this.name = name; + } + } + + public void updateStartTime(LocalDateTime startTime) { + this.startTime = startTime; + } + + public void updateVideoId(String videoId) { + if (StringUtils.hasText(videoId)) { + this.videoId = videoId; + } + } + + public void updateGameQuarter(String gameQuarter) { + if (StringUtils.hasText(gameQuarter)) { + this.gameQuarter = gameQuarter; + } + } + + public void updateState(GameState state) { + this.state = state; + } + + public void updateRound(Round round) { + this.round = round; + } + + public Game(Sport sport, Member manager, League league, String name, LocalDateTime startTime, String videoId, String gameQuarter, GameState state, Round round) { this.sport = sport; diff --git a/src/main/java/com/sports/server/command/game/dto/GameRequestDto.java b/src/main/java/com/sports/server/command/game/dto/GameRequestDto.java index 61f45c07..c9032206 100644 --- a/src/main/java/com/sports/server/command/game/dto/GameRequestDto.java +++ b/src/main/java/com/sports/server/command/game/dto/GameRequestDto.java @@ -24,4 +24,14 @@ public Game toEntity(Sport sport, Member manager, League league) { Round.from(round)); } } -} + + public record Update( + String name, + String round, + String quarter, + String state, + LocalDateTime startTime, + String videoId + ) { + } +} \ No newline at end of file diff --git a/src/main/java/com/sports/server/command/game/presentation/GameController.java b/src/main/java/com/sports/server/command/game/presentation/GameController.java index 83458a6e..255e6c54 100644 --- a/src/main/java/com/sports/server/command/game/presentation/GameController.java +++ b/src/main/java/com/sports/server/command/game/presentation/GameController.java @@ -10,11 +10,7 @@ import java.net.URI; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @@ -53,6 +49,15 @@ public ResponseEntity registerGame(@PathVariable final Long leagueId, return ResponseEntity.created(URI.create("")).build(); } + @PutMapping("/leagues/{leagueId}/{gameId}") + public ResponseEntity updateGame(@PathVariable final Long leagueId, + @PathVariable final Long gameId, + @RequestBody final GameRequestDto.Update requestDto, + final Member member) { + gameService.updateGame(leagueId, gameId, requestDto, member); + return ResponseEntity.ok().build(); + } + @PatchMapping("/games/{gameId}/{gameTeamId}/lineup-players/{lineupPlayerId}/captain/register") public ResponseEntity changePlayerToCaptain(@PathVariable final Long gameId, @PathVariable final Long lineupPlayerId, diff --git a/src/main/java/com/sports/server/query/dto/response/GameDetailResponse.java b/src/main/java/com/sports/server/query/dto/response/GameDetailResponse.java index b4ee2d03..aa8e0a15 100644 --- a/src/main/java/com/sports/server/query/dto/response/GameDetailResponse.java +++ b/src/main/java/com/sports/server/query/dto/response/GameDetailResponse.java @@ -13,7 +13,8 @@ public record GameDetailResponse( String gameName, String sportName, List gameTeams, - String state + String state, + String round ) { public GameDetailResponse(Game game, List gameTeams) { @@ -27,7 +28,8 @@ public GameDetailResponse(Game game, List gameTeams) { .sorted(Comparator.comparingLong(GameTeam::getId)) .map(TeamResponse::new) .toList(), - game.getState().name() + game.getState().name(), + game.getRound().name() ); } diff --git a/src/main/resources/static/docs/api.html b/src/main/resources/static/docs/api.html index b22c8662..fd48b790 100644 --- a/src/main/resources/static/docs/api.html +++ b/src/main/resources/static/docs/api.html @@ -462,6 +462,7 @@

훕치치 서버 API 문서

  • 게임 라인업 조회
  • 게임 출전 선수 조회
  • 게임 등록
  • +
  • 게임 수정
  • 라인업 API @@ -765,13 +766,13 @@

    @@ -831,9 +833,9 @@

    종목

    -

    sportName

    +

    round

    String

    -

    종목

    +

    게임의 라운드

    gameTeams[].gameTeamId

    @@ -1644,6 +1646,67 @@

    +

    게임 수정

    +
    +

    HTTP request

    +
    +
    +
    PUT /leagues/1/1 HTTP/1.1
    +Content-Type: application/json
    +Content-Length: 172
    +Host: www.api.hufstreaming.site
    +Cookie: HCC_SES=temp-cookie
    +
    +{
    +  "name" : "게임 이름",
    +  "round" : "16강",
    +  "quarter" : "전반전",
    +  "state" : "PLAYING",
    +  "startTime" : "2024-09-14T01:15:12.238992",
    +  "videoId" : "videoId"
    +}
    +
    +
    +
    +
    +

    Path parameters

    + + ++++ + + + + + + + + + + + + + + + + +
    Table 1. /leagues/{leagueId}/{gameId}
    ParameterDescription

    leagueId

    리그의 ID

    gameId

    경기의 ID

    +
    +
    +

    HTTP response

    +
    +
    +
    HTTP/1.1 200 OK
    +Vary: Origin
    +Vary: Access-Control-Request-Method
    +Vary: Access-Control-Request-Headers
    +
    +
    +
    +
    @@ -2865,7 +2928,11 @@

    putResponse = RestAssured.given().log().all() + .cookie(COOKIE_NAME, mockToken) + .pathParam("leagueId", leagueId) + .pathParam("gameId", gameId) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(request) + .put("/leagues/{leagueId}/{gameId}") + .then().log().all() + .extract(); + ExtractableResponse getResponse = RestAssured.given().log().all() + .when() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .get("/games/{gameId}", gameId) + .then().log().all() + .extract(); + + // then + GameDetailResponse updatedGame = toResponse(getResponse, GameDetailResponse.class); + assertAll( + () -> assertThat(putResponse.statusCode()).isEqualTo(HttpStatus.OK.value()), + () -> assertThat(updatedGame.gameQuarter()).isEqualTo(quarter), + () -> assertThat(updatedGame.round()).isEqualTo(Round.from(round).name()), + () -> assertThat(updatedGame.gameName()).isEqualTo(name), + () -> assertThat(updatedGame.startTime()).isEqualTo(fixedLocalDateTime), + () -> assertThat(updatedGame.state()).isEqualTo(state), + () -> assertThat(updatedGame.videoId()).isEqualTo(videoId) + ); + } + @Test void 라인업_선수를_주장으로_등록한다() throws Exception { @@ -238,5 +287,4 @@ public class GameAcceptanceTest extends AcceptanceTest { () -> assertThat(actual.get(0).isCaptain()).isEqualTo(false) ); } - } diff --git a/src/test/java/com/sports/server/command/game/application/GameServiceTest.java b/src/test/java/com/sports/server/command/game/application/GameServiceTest.java index b13765dc..ae3a6460 100644 --- a/src/test/java/com/sports/server/command/game/application/GameServiceTest.java +++ b/src/test/java/com/sports/server/command/game/application/GameServiceTest.java @@ -1,10 +1,10 @@ package com.sports.server.command.game.application; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertAll; import com.sports.server.command.game.domain.Game; +import com.sports.server.command.game.domain.GameState; import com.sports.server.command.game.domain.GameTeam; import com.sports.server.command.game.domain.LineupPlayer; import com.sports.server.command.game.dto.GameRequestDto; @@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.jdbc.Sql; +import org.testcontainers.shaded.org.checkerframework.checker.units.qual.K; @Sql("/game-fixture.sql") public class GameServiceTest extends ServiceTest { @@ -136,5 +137,52 @@ private void assertLineupPlayers(List gameTeams) { } } + @Nested + @DisplayName("게임을 수정할 때") + class updateGameTest { + + private GameRequestDto.Update updateDto; + private Long leagueId; + private Long gameId; + private Member manager; + + @BeforeEach + void setUp() { + LocalDateTime fixedLocalDateTime = LocalDateTime.of(2024, 9, 11, 12, 0, 0); + updateDto = new GameRequestDto.Update(nameOfGame, "8강", "후반전", "PLAYING", + fixedLocalDateTime, "videoId"); + leagueId = 1L; + gameId = 1L; + manager = entityUtils.getEntity(1L, Member.class); + } + + @Test + void 정상적으로_게임이_수정된다() { + // when + gameService.updateGame(leagueId, gameId, updateDto, manager); + + // then + Game game = entityUtils.getEntity(gameId, Game.class); + assertAll( + () -> assertThat(game.getGameQuarter()).isEqualTo(updateDto.quarter()), + () -> assertThat(game.getRound()).isEqualTo(Round.from(updateDto.round())), + () -> assertThat(game.getName()).isEqualTo(updateDto.name()), + () -> assertThat(game.getStartTime()).isEqualTo(updateDto.startTime()), + () -> assertThat(game.getState()).isEqualTo(GameState.from(updateDto.state())), + () -> assertThat(game.getVideoId()).isEqualTo(updateDto.videoId()) + ); + } + + @Test + void 게임이_속한_리그의_매니저가_아닌_회원이_게임을_수정하려고_하면_예외가_발생한다() { + // given + Member nonManager = entityUtils.getEntity(2L, Member.class); + + // when & then + assertThatThrownBy(() -> gameService.updateGame(leagueId, gameId, updateDto, nonManager)) + .isInstanceOf(UnauthorizedException.class); + } + + } } diff --git a/src/test/java/com/sports/server/command/game/presentation/GameControllerTest.java b/src/test/java/com/sports/server/command/game/presentation/GameControllerTest.java index bd637f81..e7ddc5bf 100644 --- a/src/test/java/com/sports/server/command/game/presentation/GameControllerTest.java +++ b/src/test/java/com/sports/server/command/game/presentation/GameControllerTest.java @@ -1,10 +1,13 @@ package com.sports.server.command.game.presentation; - +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doNothing; import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName; import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; @@ -13,6 +16,7 @@ import com.sports.server.command.game.dto.CheerCountUpdateRequest; import com.sports.server.command.game.dto.GameRequestDto; +import com.sports.server.command.member.domain.Member; import com.sports.server.support.DocumentationTest; import jakarta.servlet.http.Cookie; import java.time.LocalDateTime; @@ -140,59 +144,45 @@ public class GameControllerTest extends DocumentationTest { )); } - @Test - void 라인업_선수를_주장으로_등록한다() throws Exception { + void 경기를_수정한다() throws Exception { - //given + // given + Long leagueId = 1L; Long gameId = 1L; - Long gameTeamId = 1L; - Long lineupPlayerId = 1L; - - //when - ResultActions result = mockMvc.perform( - patch("/games/{gameId}/{gameTeamId}/lineup-players/{lineupPlayerId}/captain/register", gameId, - gameTeamId, - lineupPlayerId) - .contentType(MediaType.APPLICATION_JSON) + GameRequestDto.Update requestDto = new GameRequestDto.Update( + "게임 이름", "16강", "전반전", "PLAYING", LocalDateTime.now(), "videoId" ); - //then - result.andExpect((status().isOk())) - .andDo(restDocsHandler.document( - pathParameters( - parameterWithName("gameId").description("게임의 ID"), - parameterWithName("gameTeamId").description("게임팀의 ID"), - parameterWithName("lineupPlayerId").description("라인업 선수의 ID") - ) - )); - } - - @Test - void 라인업_선수를_주장에서_해제한다() throws Exception { + doNothing().when(gameService).updateGame(anyLong(), anyLong(), any(GameRequestDto.Update.class), any(Member.class)); - //given - Long gameId = 1L; - Long gameTeamId = 1L; - Long lineupPlayerId = 1L; - - //when - ResultActions result = mockMvc.perform( - patch("/games/{gameId}/{gameTeamId}/lineup-players/{lineupPlayerId}/captain/revoke", gameId, - gameTeamId, - lineupPlayerId) - .contentType(MediaType.APPLICATION_JSON) - ); + // when + ResultActions result = mockMvc.perform(put("/leagues/{leagueId}/{gameId}", leagueId, gameId) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .content(objectMapper.writeValueAsString(requestDto)) + .cookie(new Cookie(COOKIE_NAME, "temp-cookie"))); - //then - result.andExpect((status().isOk())) + // then + result.andExpect(status().isOk()) .andDo(restDocsHandler.document( pathParameters( - parameterWithName("gameId").description("게임의 ID"), - parameterWithName("gameTeamId").description("게임팀의 ID"), - parameterWithName("lineupPlayerId").description("라인업 선수의 ID") + parameterWithName("leagueId").description("리그의 ID"), + parameterWithName("gameId").description("경기의 ID") + ), + requestFields( + fieldWithPath("name").type(JsonFieldType.STRING).description("변경할 경기의 이름"), + fieldWithPath("round").type(JsonFieldType.STRING).description("변경할 경기의 라운드 ex. 16강, 결승"), + fieldWithPath("quarter").type(JsonFieldType.STRING).description("쿼터"), + fieldWithPath("state").type(JsonFieldType.STRING).description("경기의 상태"), + fieldWithPath("startTime").type(JsonFieldType.STRING).description("시작 날짜 및 시각"), + fieldWithPath("videoId").type(JsonFieldType.STRING) + .description("경기 영상 링크") + ), + requestCookies( + cookieWithName(COOKIE_NAME).description("로그인을 통해 얻은 토큰") ) )); } + } diff --git a/src/test/java/com/sports/server/query/presentation/GameQueryControllerTest.java b/src/test/java/com/sports/server/query/presentation/GameQueryControllerTest.java index 172fad53..21401dd1 100644 --- a/src/test/java/com/sports/server/query/presentation/GameQueryControllerTest.java +++ b/src/test/java/com/sports/server/query/presentation/GameQueryControllerTest.java @@ -38,7 +38,7 @@ class GameQueryControllerTest extends DocumentationTest { ); LocalDateTime startTime = LocalDateTime.of(2024, 1, 19, 13, 0, 0); GameDetailResponse response = new GameDetailResponse( - startTime, "videoId", "전반전", "4강", "축구", gameTeams, "PLAYING" + startTime, "videoId", "전반전", "여름축구", "축구", gameTeams, "PLAYING", "4강" ); given(gameQueryService.getGameDetail(gameId)) .willReturn(response); @@ -60,7 +60,7 @@ class GameQueryControllerTest extends DocumentationTest { fieldWithPath("gameQuarter").type(JsonFieldType.STRING).description("게임 쿼터"), fieldWithPath("gameName").type(JsonFieldType.STRING).description("게임 이름"), fieldWithPath("sportName").type(JsonFieldType.STRING).description("종목"), - fieldWithPath("sportName").type(JsonFieldType.STRING).description("종목"), + fieldWithPath("round").type(JsonFieldType.STRING).description("게임의 라운드"), fieldWithPath("gameTeams[].gameTeamId").type(JsonFieldType.NUMBER) .description("게임팀의 ID"), fieldWithPath("gameTeams[].gameTeamName").type(JsonFieldType.STRING)