From a91fb7cd178087f1fcd13a858ce950a8f23b79b6 Mon Sep 17 00:00:00 2001 From: Chung-an Lee <44027393+leegwichan@users.noreply.github.com> Date: Tue, 13 May 2025 04:55:11 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[CHORE]=20DataDog=EC=9D=84=20=ED=86=B5?= =?UTF-8?q?=ED=95=9C=20=EB=A1=9C=EA=B7=B8=20=EC=88=98=EC=A7=91=20(#164)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-dev.yml | 4 ++-- src/main/resources/application-prod.yml | 4 ++-- src/main/resources/logging/log4j2-dev.yml | 1 + src/main/resources/logging/log4j2-prod.yml | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index af61d46d..b60259e8 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -37,5 +37,5 @@ discord: token: ${secret.discord.token} channelId: ${secret.discord.channelId} -#logging: -# config: classpath:logging/log4j2-dev.yml +logging: + config: classpath:logging/log4j2-dev.yml diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 79040396..208ac91b 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -36,5 +36,5 @@ discord: token: ${secret.discord.token} channelId: ${secret.discord.channelId} -#logging: -# config: classpath:logging/log4j2-prod.yml +logging: + config: classpath:logging/log4j2-prod.yml diff --git a/src/main/resources/logging/log4j2-dev.yml b/src/main/resources/logging/log4j2-dev.yml index 25ca8591..5225f07e 100644 --- a/src/main/resources/logging/log4j2-dev.yml +++ b/src/main/resources/logging/log4j2-dev.yml @@ -22,6 +22,7 @@ Configuration: TimeBasedTriggeringPolicy: Interval: 1 modulate: true #다음 롤오버 시간을 정각 바운더리에 맞추는 설정 + DefaultRollOverStrategy: max: 10 Delete: diff --git a/src/main/resources/logging/log4j2-prod.yml b/src/main/resources/logging/log4j2-prod.yml index bb3a2793..dee42005 100644 --- a/src/main/resources/logging/log4j2-prod.yml +++ b/src/main/resources/logging/log4j2-prod.yml @@ -1,5 +1,5 @@ Configuration: - name: Dev-Logger + name: Prod-Logger status: debug Properties: From 35a1fc8a39e750555e25b8dba64cea2e1e6ebdf1 Mon Sep 17 00:00:00 2001 From: Chung-an Lee <44027393+leegwichan@users.noreply.github.com> Date: Tue, 13 May 2025 10:51:04 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[DOCS]=20Swagger=EB=A5=BC=20=ED=86=B5?= =?UTF-8?q?=ED=95=B4=20=EC=B6=94=EA=B0=80=20=EC=84=A4=EB=AA=85=20=EC=A0=9C?= =?UTF-8?q?=EC=8B=9C=20(#166)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 1 + .../controller/RestDocumentationRequest.java | 5 + .../customize/CustomizeDocumentTest.java | 140 +++++++++++++----- .../controller/member/MemberDocumentTest.java | 22 ++- 4 files changed, 126 insertions(+), 42 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index eb1d81ff..40e92645 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -10,3 +10,4 @@ springdoc: filter: true persist-authorization: true display-request-duration: true + default-model-expand-depth: 10 diff --git a/src/test/java/com/debatetimer/controller/RestDocumentationRequest.java b/src/test/java/com/debatetimer/controller/RestDocumentationRequest.java index 372422d5..7b07a7fe 100644 --- a/src/test/java/com/debatetimer/controller/RestDocumentationRequest.java +++ b/src/test/java/com/debatetimer/controller/RestDocumentationRequest.java @@ -35,6 +35,11 @@ public RestDocumentationRequest summary(String summary) { return this; } + public RestDocumentationRequest description(String description) { + resourceBuilder.description(description); + return this; + } + public RestDocumentationRequest pathParameter(ParameterDescriptor... descriptors) { snippets.add(pathParameters(descriptors)); return this; diff --git a/src/test/java/com/debatetimer/controller/customize/CustomizeDocumentTest.java b/src/test/java/com/debatetimer/controller/customize/CustomizeDocumentTest.java index 1f3ca5b9..629239c8 100644 --- a/src/test/java/com/debatetimer/controller/customize/CustomizeDocumentTest.java +++ b/src/test/java/com/debatetimer/controller/customize/CustomizeDocumentTest.java @@ -45,6 +45,13 @@ class Save { private final RestDocumentationRequest requestDocument = request() .tag(Tag.CUSTOMIZE_API) .summary("새로운 사용자 지정 토론 시간표 생성") + .description(""" + ### 타임 박스 종류에 따른 요청 값 + | 타임 박스 종류 | 필수 입력 | 선택 입력 | null 입력 | + | :---: | ---| --- | --- | + | 커스텀 타임 박스 | stance, speechType, boxType, time | speaker | timePerTeam, timePerSpeaking | + | 자유 토론 타임 박스 | stance, speechType, boxType, timePerTeam | speaker, timePerSpeaking | time | + """) .requestHeader( headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰") ) @@ -60,7 +67,7 @@ class Save { fieldWithPath("table[].stance").type(STRING).description("입장"), fieldWithPath("table[].speechType").type(STRING).description("발언 유형"), fieldWithPath("table[].boxType").type(STRING).description("타임 박스 유형"), - fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)"), + fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)").optional(), fieldWithPath("table[].timePerTeam").type(NUMBER).description("팀당 발언 시간 (초)").optional(), fieldWithPath("table[].timePerSpeaking").type(NUMBER).description("1회 발언 시간 (초)").optional(), fieldWithPath("table[].speaker").type(STRING).description("발언자 이름").optional() @@ -81,7 +88,7 @@ class Save { fieldWithPath("table[].stance").type(STRING).description("입장"), fieldWithPath("table[].speechType").type(STRING).description("발언 유형"), fieldWithPath("table[].boxType").type(STRING).description("타임 박스 유형"), - fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)"), + fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)").optional(), fieldWithPath("table[].timePerTeam").type(NUMBER).description("팀당 발언 시간 (초)").optional(), fieldWithPath("table[].timePerSpeaking").type(NUMBER).description("1회 발언 시간 (초)").optional(), fieldWithPath("table[].speaker").type(STRING).description("발언자 이름").optional() @@ -93,10 +100,14 @@ class Save { new CustomizeTableInfoCreateRequest("자유 테이블", "주제", "찬성", "반대", true, true), List.of( - new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론1", CustomizeBoxType.TIME_BASED, - 120, 60, null, "발언자1"), - new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론2", CustomizeBoxType.TIME_BASED, - 120, 60, null, "발언자2") + new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론", CustomizeBoxType.NORMAL, + 120, null, null, "콜리"), + new CustomizeTimeBoxCreateRequest(Stance.CONS, "입론", CustomizeBoxType.NORMAL, + 120, null, null, "비토"), + new CustomizeTimeBoxCreateRequest(Stance.NEUTRAL, "난상 토론", CustomizeBoxType.TIME_BASED, + null, 360, 120, null), + new CustomizeTimeBoxCreateRequest(Stance.NEUTRAL, "존중 토론", CustomizeBoxType.TIME_BASED, + null, 360, null, null) ) ); CustomizeTableResponse response = new CustomizeTableResponse( @@ -104,10 +115,14 @@ class Save { new CustomizeTableInfoResponse("나의 테이블", TableType.CUSTOMIZE, "토론 주제", "찬성", "반대", true, true), List.of( - new CustomizeTimeBoxResponse(Stance.PROS, "입론1", CustomizeBoxType.TIME_BASED, - 120, 60, null, "발언자1"), - new CustomizeTimeBoxResponse(Stance.PROS, "입론2", CustomizeBoxType.TIME_BASED, - 120, 60, null, "발언자2") + new CustomizeTimeBoxResponse(Stance.PROS, "입론", CustomizeBoxType.NORMAL, + 120, null, null, "콜리"), + new CustomizeTimeBoxResponse(Stance.CONS, "입론", CustomizeBoxType.NORMAL, + 120, null, null, "비토"), + new CustomizeTimeBoxResponse(Stance.NEUTRAL, "난상 토론", CustomizeBoxType.TIME_BASED, + null, 360, 120, null), + new CustomizeTimeBoxResponse(Stance.NEUTRAL, "존중 토론", CustomizeBoxType.TIME_BASED, + null, 360, null, null) ) ); doReturn(response).when(customizeService).save(eq(request), any()); @@ -143,10 +158,14 @@ class Save { new CustomizeTableInfoCreateRequest("자유 테이블", "주제", "찬성", "반대", true, true), List.of( - new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론1", CustomizeBoxType.TIME_BASED, - 120, 60, null, "발언자1"), - new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론2", CustomizeBoxType.TIME_BASED, - 120, 60, null, "발언자2") + new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론", CustomizeBoxType.NORMAL, + 120, null, null, "콜리"), + new CustomizeTimeBoxCreateRequest(Stance.CONS, "입론", CustomizeBoxType.NORMAL, + 120, null, null, "비토"), + new CustomizeTimeBoxCreateRequest(Stance.NEUTRAL, "난상 토론", CustomizeBoxType.TIME_BASED, + null, 360, 120, null), + new CustomizeTimeBoxCreateRequest(Stance.NEUTRAL, "존중 토론", CustomizeBoxType.TIME_BASED, + null, 360, null, null) ) ); doThrow(new DTClientErrorException(errorCode)).when(customizeService).save(eq(request), any()); @@ -170,6 +189,13 @@ class GetTable { private final RestDocumentationRequest requestDocument = request() .summary("사용자_지정 토론 시간표 조회") + .description(""" + ### 타임 박스 종류에 따른 웅답 값 + | 타임 박스 종류 | 필수 입력 | 선택 입력 | null 입력 | + | :---: | ---| --- | --- | + | 커스텀 타임 박스 | stance, speechType, boxType, time | speaker | timePerTeam, timePerSpeaking | + | 자유 토론 타임 박스 | stance, speechType, boxType, timePerTeam | speaker, timePerSpeaking | time | + """) .tag(Tag.CUSTOMIZE_API) .requestHeader( headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰") @@ -193,7 +219,7 @@ class GetTable { fieldWithPath("table[].stance").type(STRING).description("입장"), fieldWithPath("table[].speechType").type(STRING).description("발언 유형"), fieldWithPath("table[].boxType").type(STRING).description("타임 박스 유형"), - fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)"), + fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)").optional(), fieldWithPath("table[].timePerTeam").type(NUMBER).description("팀당 발언 시간 (초)").optional(), fieldWithPath("table[].timePerSpeaking").type(NUMBER).description("1회 발언 시간 (초)").optional(), fieldWithPath("table[].speaker").type(STRING).description("발언자 이름").optional() @@ -207,10 +233,14 @@ class GetTable { new CustomizeTableInfoResponse("나의 테이블", TableType.CUSTOMIZE, "토론 주제", "찬성", "반대", true, true), List.of( - new CustomizeTimeBoxResponse(Stance.PROS, "입론1", CustomizeBoxType.TIME_BASED, - 120, 60, null, "발언자1"), - new CustomizeTimeBoxResponse(Stance.PROS, "입론2", CustomizeBoxType.TIME_BASED, - 120, 60, null, "발언자2") + new CustomizeTimeBoxResponse(Stance.PROS, "입론", CustomizeBoxType.NORMAL, + 120, null, null, "콜리"), + new CustomizeTimeBoxResponse(Stance.CONS, "입론", CustomizeBoxType.NORMAL, + 120, null, null, "비토"), + new CustomizeTimeBoxResponse(Stance.NEUTRAL, "난상 토론", CustomizeBoxType.TIME_BASED, + null, 360, 120, null), + new CustomizeTimeBoxResponse(Stance.NEUTRAL, "존중 토론", CustomizeBoxType.TIME_BASED, + null, 360, null, null) ) ); doReturn(response).when(customizeService).findTable(eq(tableId), any()); @@ -254,6 +284,13 @@ class UpdateTable { private final RestDocumentationRequest requestDocument = request() .tag(Tag.CUSTOMIZE_API) .summary("사용자 지정 토론 시간표 수정") + .description(""" + ### 타임 박스 종류에 따른 요청/웅답 값 + | 타임 박스 종류 | 필수 입력 | 선택 입력 | null 입력 | + | :---: | ---| --- | --- | + | 커스텀 타임 박스 | stance, speechType, boxType, time | speaker | timePerTeam, timePerSpeaking | + | 자유 토론 타임 박스 | stance, speechType, boxType, timePerTeam | speaker, timePerSpeaking | time | + """) .requestHeader( headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰") ) @@ -272,7 +309,7 @@ class UpdateTable { fieldWithPath("table[].stance").type(STRING).description("입장"), fieldWithPath("table[].speechType").type(STRING).description("발언 유형"), fieldWithPath("table[].boxType").type(STRING).description("타임 박스 유형"), - fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)"), + fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)").optional(), fieldWithPath("table[].timePerTeam").type(NUMBER).description("팀당 발언 시간 (초)").optional(), fieldWithPath("table[].timePerSpeaking").type(NUMBER).description("1회 발언 시간 (초)").optional(), fieldWithPath("table[].speaker").type(STRING).description("발언자 이름").optional() @@ -293,7 +330,7 @@ class UpdateTable { fieldWithPath("table[].stance").type(STRING).description("입장"), fieldWithPath("table[].speechType").type(STRING).description("발언 유형"), fieldWithPath("table[].boxType").type(STRING).description("타임 박스 유형"), - fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)"), + fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)").optional(), fieldWithPath("table[].timePerTeam").type(NUMBER).description("팀당 발언 시간 (초)").optional(), fieldWithPath("table[].timePerSpeaking").type(NUMBER).description("1회 발언 시간 (초)").optional(), fieldWithPath("table[].speaker").type(STRING).description("발언자 이름").optional() @@ -306,21 +343,29 @@ class UpdateTable { new CustomizeTableInfoCreateRequest("자유 테이블", "주제", "찬성", "반대", true, true), List.of( - new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론1", CustomizeBoxType.TIME_BASED, - 120, 60, null, "발언자1"), - new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론2", CustomizeBoxType.TIME_BASED, - 120, 60, null, "발언자2") + new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론", CustomizeBoxType.NORMAL, + 120, null, null, "콜리"), + new CustomizeTimeBoxCreateRequest(Stance.CONS, "입론", CustomizeBoxType.NORMAL, + 120, null, null, "비토"), + new CustomizeTimeBoxCreateRequest(Stance.NEUTRAL, "난상 토론", CustomizeBoxType.TIME_BASED, + null, 360, 120, null), + new CustomizeTimeBoxCreateRequest(Stance.NEUTRAL, "존중 토론", CustomizeBoxType.TIME_BASED, + null, 360, null, null) ) ); CustomizeTableResponse response = new CustomizeTableResponse( 5L, - new CustomizeTableInfoResponse("나의 테이블", TableType.CUSTOMIZE, "토론 주제", + new CustomizeTableInfoResponse("나의 테이블", TableType.CUSTOMIZE, "주제", "찬성", "반대", true, true), List.of( - new CustomizeTimeBoxResponse(Stance.PROS, "입론1", CustomizeBoxType.TIME_BASED, - 120, 60, null, "발언자1"), - new CustomizeTimeBoxResponse(Stance.PROS, "입론2", CustomizeBoxType.TIME_BASED, - 120, 60, null, "발언자2") + new CustomizeTimeBoxResponse(Stance.PROS, "입론", CustomizeBoxType.NORMAL, + 120, null, null, "콜리"), + new CustomizeTimeBoxResponse(Stance.CONS, "입론", CustomizeBoxType.NORMAL, + 120, null, null, "비토"), + new CustomizeTimeBoxResponse(Stance.NEUTRAL, "난상 토론", CustomizeBoxType.TIME_BASED, + null, 360, 120, null), + new CustomizeTimeBoxResponse(Stance.NEUTRAL, "존중 토론", CustomizeBoxType.TIME_BASED, + null, 360, null, null) ) ); doReturn(response).when(customizeService).updateTable(eq(request), eq(tableId), any()); @@ -358,10 +403,14 @@ class UpdateTable { new CustomizeTableInfoCreateRequest("자유 테이블", "주제", "찬성", "반대", true, true), List.of( - new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론1", CustomizeBoxType.TIME_BASED, - 120, 60, null, "발언자1"), - new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론2", CustomizeBoxType.TIME_BASED, - 120, 60, null, "발언자2") + new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론", CustomizeBoxType.NORMAL, + 120, null, null, "콜리"), + new CustomizeTimeBoxCreateRequest(Stance.CONS, "입론", CustomizeBoxType.NORMAL, + 120, null, null, "비토"), + new CustomizeTimeBoxCreateRequest(Stance.NEUTRAL, "난상 토론", CustomizeBoxType.TIME_BASED, + null, 360, 120, null), + new CustomizeTimeBoxCreateRequest(Stance.NEUTRAL, "존중 토론", CustomizeBoxType.TIME_BASED, + null, 360, null, null) ) ); doThrow(new DTClientErrorException(errorCode)).when(customizeService) @@ -387,6 +436,13 @@ class Debate { private final RestDocumentationRequest requestDocument = request() .summary("사용자 지정 토론 시작") + .description(""" + ### 타임 박스 종류에 따른 웅답 값 + | 타임 박스 종류 | 필수 입력 | 선택 입력 | null 입력 | + | :---: | ---| --- | --- | + | 커스텀 타임 박스 | stance, speechType, boxType, time | speaker | timePerTeam, timePerSpeaking | + | 자유 토론 타임 박스 | stance, speechType, boxType, timePerTeam | speaker, timePerSpeaking | time | + """) .tag(Tag.CUSTOMIZE_API) .requestHeader( headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰") @@ -410,7 +466,7 @@ class Debate { fieldWithPath("table[].stance").type(STRING).description("입장"), fieldWithPath("table[].speechType").type(STRING).description("발언 유형"), fieldWithPath("table[].boxType").type(STRING).description("타임 박스 유형"), - fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)"), + fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)").optional(), fieldWithPath("table[].timePerTeam").type(NUMBER).description("팀당 발언 시간 (초)").optional(), fieldWithPath("table[].timePerSpeaking").type(NUMBER).description("1회 발언 시간 (초)").optional(), fieldWithPath("table[].speaker").type(STRING).description("발언자 이름").optional() @@ -422,13 +478,17 @@ class Debate { long tableId = 5L; CustomizeTableResponse response = new CustomizeTableResponse( 5L, - new CustomizeTableInfoResponse("나의 테이블", TableType.CUSTOMIZE, "토론 주제", + new CustomizeTableInfoResponse("나의 테이블", TableType.CUSTOMIZE, "주제", "찬성", "반대", true, true), List.of( - new CustomizeTimeBoxResponse(Stance.PROS, "입론1", CustomizeBoxType.TIME_BASED, - 120, 60, null, "발언자1"), - new CustomizeTimeBoxResponse(Stance.PROS, "입론2", CustomizeBoxType.TIME_BASED, - 120, 60, null, "발언자2") + new CustomizeTimeBoxResponse(Stance.PROS, "입론", CustomizeBoxType.NORMAL, + 120, null, null, "콜리"), + new CustomizeTimeBoxResponse(Stance.CONS, "입론", CustomizeBoxType.NORMAL, + 120, null, null, "비토"), + new CustomizeTimeBoxResponse(Stance.NEUTRAL, "난상 토론", CustomizeBoxType.TIME_BASED, + null, 360, 120, null), + new CustomizeTimeBoxResponse(Stance.NEUTRAL, "존중 토론", CustomizeBoxType.TIME_BASED, + null, 360, null, null) ) ); doReturn(response).when(customizeService).updateUsedAt(eq(tableId), any()); diff --git a/src/test/java/com/debatetimer/controller/member/MemberDocumentTest.java b/src/test/java/com/debatetimer/controller/member/MemberDocumentTest.java index e1e50bf8..19716b59 100644 --- a/src/test/java/com/debatetimer/controller/member/MemberDocumentTest.java +++ b/src/test/java/com/debatetimer/controller/member/MemberDocumentTest.java @@ -50,8 +50,8 @@ class CreateMember { ); @Test - void 회원_생성_성공() { - MemberCreateRequest request = new MemberCreateRequest("dfsfgdsg", "http://redirectUrl"); + void 회원_생성_및_로그인_성공() { + MemberCreateRequest request = new MemberCreateRequest("dfsfgdsg", "http://localhost:3000"); MemberInfo memberInfo = new MemberInfo(EXIST_MEMBER_EMAIL); MemberCreateResponse response = new MemberCreateResponse(EXIST_MEMBER_ID, EXIST_MEMBER_EMAIL); doReturn(memberInfo).when(authService).getMemberInfo(request); @@ -68,6 +68,24 @@ class CreateMember { .when().post("/api/member") .then().statusCode(201); } + + @EnumSource(value = ClientErrorCode.class, names = {"MEMBER_NOT_FOUND"}) // PR #160 병합 시 변경 + @ParameterizedTest + void 회원_생성_및_로그인_실패(ClientErrorCode errorCode) { + MemberCreateRequest request = new MemberCreateRequest("dfsfgdsg", "http://localhost:3000"); + doThrow(new DTClientErrorException(errorCode)).when(authService).getMemberInfo(request); + + var document = document("member/create", errorCode) + .request(requestDocument) + .response(ERROR_RESPONSE) + .build(); + + given(document) + .contentType(ContentType.JSON) + .body(request) + .when().post("/api/member") + .then().statusCode(errorCode.getStatus().value()); + } } @Nested From 0b9e293934199a1793d026ea177236457396f0a3 Mon Sep 17 00:00:00 2001 From: Chung-an Lee <44027393+leegwichan@users.noreply.github.com> Date: Tue, 13 May 2025 10:55:45 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[FIX]=20'=ED=86=A0=EB=A1=A0=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=EB=B3=80=EA=B2=BD=20API'=20=EB=8F=99?= =?UTF-8?q?=EC=8B=9C=EC=84=B1=20=EB=AC=B8=EC=A0=9C=20(#168)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/customize/CustomizeService.java | 3 ++- .../parliamentary/ParliamentaryService.java | 3 ++- .../debatetimer/service/BaseServiceTest.java | 13 ++++++++++++ .../customize/CustomizeServiceTest.java | 20 +++++++++++++++++++ .../ParliamentaryServiceTest.java | 14 +++++++++++++ 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/debatetimer/service/customize/CustomizeService.java b/src/main/java/com/debatetimer/service/customize/CustomizeService.java index 6bd3ec6b..fc6afe60 100644 --- a/src/main/java/com/debatetimer/service/customize/CustomizeService.java +++ b/src/main/java/com/debatetimer/service/customize/CustomizeService.java @@ -13,6 +13,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; @Service @@ -38,7 +39,7 @@ public CustomizeTableResponse findTable(long tableId, Member member) { return new CustomizeTableResponse(table, timeBoxes); } - @Transactional + @Transactional(isolation = Isolation.SERIALIZABLE) public CustomizeTableResponse updateTable( CustomizeTableCreateRequest tableCreateRequest, long tableId, diff --git a/src/main/java/com/debatetimer/service/parliamentary/ParliamentaryService.java b/src/main/java/com/debatetimer/service/parliamentary/ParliamentaryService.java index 5b76f221..7b58a615 100644 --- a/src/main/java/com/debatetimer/service/parliamentary/ParliamentaryService.java +++ b/src/main/java/com/debatetimer/service/parliamentary/ParliamentaryService.java @@ -13,6 +13,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; @Service @@ -45,7 +46,7 @@ public ParliamentaryTableResponse findTableById(long tableId, long id) { return new ParliamentaryTableResponse(table, timeBoxes); } - @Transactional + @Transactional(isolation = Isolation.SERIALIZABLE) public ParliamentaryTableResponse updateTable( ParliamentaryTableCreateRequest tableCreateRequest, long tableId, diff --git a/src/test/java/com/debatetimer/service/BaseServiceTest.java b/src/test/java/com/debatetimer/service/BaseServiceTest.java index 0f93d39c..65d88130 100644 --- a/src/test/java/com/debatetimer/service/BaseServiceTest.java +++ b/src/test/java/com/debatetimer/service/BaseServiceTest.java @@ -11,6 +11,8 @@ import com.debatetimer.repository.member.MemberRepository; import com.debatetimer.repository.parliamentary.ParliamentaryTableRepository; import com.debatetimer.repository.parliamentary.ParliamentaryTimeBoxRepository; +import java.util.List; +import java.util.stream.IntStream; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -48,4 +50,15 @@ public abstract class BaseServiceTest { @Autowired protected CustomizeTimeBoxGenerator customizeTimeBoxGenerator; + + protected void runAtSameTime(int count, Runnable task) throws InterruptedException { + List threads = IntStream.range(0, count) + .mapToObj(i -> new Thread(task)) + .toList(); + + threads.forEach(Thread::start); + for (Thread thread : threads) { + thread.join(); + } + } } diff --git a/src/test/java/com/debatetimer/service/customize/CustomizeServiceTest.java b/src/test/java/com/debatetimer/service/customize/CustomizeServiceTest.java index 3e38b993..33cad149 100644 --- a/src/test/java/com/debatetimer/service/customize/CustomizeServiceTest.java +++ b/src/test/java/com/debatetimer/service/customize/CustomizeServiceTest.java @@ -140,6 +140,26 @@ class UpdateTable { .isInstanceOf(DTClientErrorException.class) .hasMessage(ClientErrorCode.NOT_TABLE_OWNER.getMessage()); } + + @Test + void 테이블_정보_수정을_동시에_요청할_때_동시에_처리하지_않는다() throws InterruptedException { + Member member = memberGenerator.generate("default@gmail.com"); + CustomizeTable table = customizeTableGenerator.generate(member); + CustomizeTableCreateRequest request = new CustomizeTableCreateRequest( + new CustomizeTableInfoCreateRequest("자유 테이블", "주제", "찬성", + "반대", true, true), + List.of( + new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론1", CustomizeBoxType.NORMAL, + 120, 60, null, "발언자1"), + new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론2", CustomizeBoxType.NORMAL, + 120, 60, null, "발언자2") + ) + ); + + runAtSameTime(2, () -> customizeService.updateTable(request, table.getId(), member)); + + assertThat(customizeTimeBoxRepository.findAllByCustomizeTable(table)).hasSize(2); + } } @Nested diff --git a/src/test/java/com/debatetimer/service/parliamentary/ParliamentaryServiceTest.java b/src/test/java/com/debatetimer/service/parliamentary/ParliamentaryServiceTest.java index fe0df654..750b1b90 100644 --- a/src/test/java/com/debatetimer/service/parliamentary/ParliamentaryServiceTest.java +++ b/src/test/java/com/debatetimer/service/parliamentary/ParliamentaryServiceTest.java @@ -122,6 +122,20 @@ class UpdateTable { .isInstanceOf(DTClientErrorException.class) .hasMessage(ClientErrorCode.NOT_TABLE_OWNER.getMessage()); } + + @Test + void 테이블_정보_수정을_동시에_요청할_때_동시에_처리하지_않는다() throws InterruptedException { + Member member = memberGenerator.generate("default@gmail.com"); + ParliamentaryTable table = parliamentaryTableGenerator.generate(member); + ParliamentaryTableCreateRequest request = new ParliamentaryTableCreateRequest( + new ParliamentaryTableInfoCreateRequest("커찬의 테이블", "주제", true, true), + List.of(new ParliamentaryTimeBoxCreateRequest(Stance.PROS, ParliamentaryBoxType.OPENING, 3, 1), + new ParliamentaryTimeBoxCreateRequest(Stance.CONS, ParliamentaryBoxType.OPENING, 3, 1))); + + runAtSameTime(2, () -> parliamentaryService.updateTable(request, member.getId(), member)); + + assertThat(parliamentaryTimeBoxRepository.findAllByParliamentaryTable(table)).hasSize(2); + } } @Nested