diff --git a/src/main/java/com/debatetimer/dto/customize/request/BellRequest.java b/src/main/java/com/debatetimer/dto/customize/request/BellRequest.java new file mode 100644 index 00000000..0c846580 --- /dev/null +++ b/src/main/java/com/debatetimer/dto/customize/request/BellRequest.java @@ -0,0 +1,7 @@ +package com.debatetimer.dto.customize.request; + +public record BellRequest( + int time, + int count +) { +} diff --git a/src/main/java/com/debatetimer/dto/customize/request/CustomizeTimeBoxCreateRequest.java b/src/main/java/com/debatetimer/dto/customize/request/CustomizeTimeBoxCreateRequest.java index b3c125a1..97397215 100644 --- a/src/main/java/com/debatetimer/dto/customize/request/CustomizeTimeBoxCreateRequest.java +++ b/src/main/java/com/debatetimer/dto/customize/request/CustomizeTimeBoxCreateRequest.java @@ -7,6 +7,7 @@ import com.debatetimer.entity.customize.CustomizeTimeBox; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import java.util.List; import org.springframework.lang.Nullable; public record CustomizeTimeBoxCreateRequest( @@ -22,6 +23,9 @@ public record CustomizeTimeBoxCreateRequest( @Nullable Integer time, + @Nullable + List bell, + @Nullable Integer timePerTeam, diff --git a/src/main/java/com/debatetimer/dto/customize/response/BellResponse.java b/src/main/java/com/debatetimer/dto/customize/response/BellResponse.java new file mode 100644 index 00000000..ce2dae8c --- /dev/null +++ b/src/main/java/com/debatetimer/dto/customize/response/BellResponse.java @@ -0,0 +1,7 @@ +package com.debatetimer.dto.customize.response; + +public record BellResponse( + int time, + int count +) { +} diff --git a/src/main/java/com/debatetimer/dto/customize/response/CustomizeTableResponse.java b/src/main/java/com/debatetimer/dto/customize/response/CustomizeTableResponse.java index a890db68..587e61c5 100644 --- a/src/main/java/com/debatetimer/dto/customize/response/CustomizeTableResponse.java +++ b/src/main/java/com/debatetimer/dto/customize/response/CustomizeTableResponse.java @@ -15,6 +15,13 @@ public CustomizeTableResponse( toTimeBoxResponses(customizeTimeBoxes)); } + public CustomizeTableResponse( + CustomizeTable customizeTable, + List timeBoxResponses + ) { + this(customizeTable.getId(), new CustomizeTableInfoResponse(customizeTable), timeBoxResponses); + } + private static List toTimeBoxResponses(CustomizeTimeBoxes timeBoxes) { List customizeTimeBoxes = timeBoxes.getTimeBoxes(); return customizeTimeBoxes diff --git a/src/main/java/com/debatetimer/dto/customize/response/CustomizeTimeBoxResponse.java b/src/main/java/com/debatetimer/dto/customize/response/CustomizeTimeBoxResponse.java index cca2eff9..e62f17fb 100644 --- a/src/main/java/com/debatetimer/dto/customize/response/CustomizeTimeBoxResponse.java +++ b/src/main/java/com/debatetimer/dto/customize/response/CustomizeTimeBoxResponse.java @@ -3,12 +3,14 @@ import com.debatetimer.domain.customize.CustomizeBoxType; import com.debatetimer.domain.customize.Stance; import com.debatetimer.entity.customize.CustomizeTimeBox; +import java.util.List; public record CustomizeTimeBoxResponse( Stance stance, String speechType, CustomizeBoxType boxType, Integer time, + List bell, Integer timePerTeam, Integer timePerSpeaking, String speaker @@ -20,6 +22,20 @@ public CustomizeTimeBoxResponse(CustomizeTimeBox customizeTimeBox) { customizeTimeBox.getSpeechType(), customizeTimeBox.getBoxType(), convertTime(customizeTimeBox), + null, + customizeTimeBox.getTimePerTeam(), + customizeTimeBox.getTimePerSpeaking(), + customizeTimeBox.getSpeaker() + ); + } + + public CustomizeTimeBoxResponse(CustomizeTimeBox customizeTimeBox, List bell) { + this( + customizeTimeBox.getStance(), + customizeTimeBox.getSpeechType(), + customizeTimeBox.getBoxType(), + convertTime(customizeTimeBox), + bell, customizeTimeBox.getTimePerTeam(), customizeTimeBox.getTimePerSpeaking(), customizeTimeBox.getSpeaker() diff --git a/src/main/java/com/debatetimer/entity/customize/BellEntity.java b/src/main/java/com/debatetimer/entity/customize/BellEntity.java new file mode 100644 index 00000000..43d4df71 --- /dev/null +++ b/src/main/java/com/debatetimer/entity/customize/BellEntity.java @@ -0,0 +1,60 @@ +package com.debatetimer.entity.customize; + +import com.debatetimer.exception.custom.DTClientErrorException; +import com.debatetimer.exception.errorcode.ClientErrorCode; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Table(name = "bell") +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class BellEntity { + + public static final int MAX_BELL_COUNT = 3; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "customize_time_box_id") + private CustomizeTimeBox customizeTimeBox; + + @Column(name = "bell_time") + private int time; + private int count; + + public BellEntity(CustomizeTimeBox customizeTimeBox, int time, int count) { + validateTime(time); + validateCount(count); + + this.customizeTimeBox = customizeTimeBox; + this.time = time; + this.count = count; + } + + private void validateTime(int time) { + if (time < 0) { + throw new DTClientErrorException(ClientErrorCode.INVALID_BELL_TIME); + } + } + + private void validateCount(int count) { + if (count <= 0 || count > MAX_BELL_COUNT) { + throw new DTClientErrorException(ClientErrorCode.INVALID_BELL_COUNT); + } + } +} diff --git a/src/main/java/com/debatetimer/exception/errorcode/ClientErrorCode.java b/src/main/java/com/debatetimer/exception/errorcode/ClientErrorCode.java index 35fee335..e0ac6cf3 100644 --- a/src/main/java/com/debatetimer/exception/errorcode/ClientErrorCode.java +++ b/src/main/java/com/debatetimer/exception/errorcode/ClientErrorCode.java @@ -3,6 +3,7 @@ import com.debatetimer.domain.customize.Agenda; import com.debatetimer.domain.customize.TableName; import com.debatetimer.domain.customize.TeamName; +import com.debatetimer.entity.customize.BellEntity; import com.debatetimer.entity.customize.CustomizeTimeBox; import lombok.Getter; import org.springframework.http.HttpStatus; @@ -65,6 +66,9 @@ public enum ClientErrorCode implements ResponseErrorCode { ALREADY_DISCONNECTED(HttpStatus.BAD_REQUEST, "이미 클라이언트에서 요청이 종료되었습니다."), NO_COOKIE_FOUND(HttpStatus.BAD_REQUEST, "필수 쿠키 값이 존재하지 않습니다."), FILE_UPLOAD_ERROR(HttpStatus.BAD_REQUEST, "파일 업로드에 실패했습니다."), + + INVALID_BELL_TIME(HttpStatus.BAD_REQUEST, "벨 시간은 0 이상의 정수여야 합니다."), + INVALID_BELL_COUNT(HttpStatus.BAD_REQUEST, "벨 카운트는 1 이상 %d 이하의 정수여야 합니다.".formatted(BellEntity.MAX_BELL_COUNT)), ; private final HttpStatus status; diff --git a/src/main/java/com/debatetimer/repository/customize/BellRepository.java b/src/main/java/com/debatetimer/repository/customize/BellRepository.java new file mode 100644 index 00000000..78569835 --- /dev/null +++ b/src/main/java/com/debatetimer/repository/customize/BellRepository.java @@ -0,0 +1,17 @@ +package com.debatetimer.repository.customize; + +import com.debatetimer.entity.customize.BellEntity; +import com.debatetimer.entity.customize.CustomizeTimeBox; +import java.util.List; +import org.springframework.data.repository.Repository; + +public interface BellRepository extends Repository { + + BellEntity save(BellEntity bell); + + List findByCustomizeTimeBox(CustomizeTimeBox customizeTimeBox); + + void deleteAllByCustomizeTimeBoxIn(List customizeTimeBoxes); + + List findAllByCustomizeTimeBoxIn(List timeBoxes); +} diff --git a/src/main/java/com/debatetimer/service/customize/CustomizeService.java b/src/main/java/com/debatetimer/service/customize/CustomizeService.java index 840a60b0..7281a77a 100644 --- a/src/main/java/com/debatetimer/service/customize/CustomizeService.java +++ b/src/main/java/com/debatetimer/service/customize/CustomizeService.java @@ -6,8 +6,6 @@ import com.debatetimer.dto.customize.request.CustomizeTableCreateRequest; import com.debatetimer.dto.customize.response.CustomizeTableResponse; import com.debatetimer.entity.customize.CustomizeTableEntity; -import com.debatetimer.exception.custom.DTClientErrorException; -import com.debatetimer.exception.errorcode.ClientErrorCode; import com.debatetimer.repository.customize.CustomizeTableRepository; import com.debatetimer.repository.customize.CustomizeTimeBoxRepository; import java.util.List; @@ -33,8 +31,7 @@ public CustomizeTableResponse save(CustomizeTableCreateRequest tableCreateReques @Transactional(readOnly = true) public CustomizeTableResponse findTable(long tableId, Member member) { - CustomizeTableEntity tableEntity = tableRepository.findByIdAndMember(tableId, member) - .orElseThrow(() -> new DTClientErrorException(ClientErrorCode.TABLE_NOT_FOUND)); + CustomizeTableEntity tableEntity = tableRepository.getByIdAndMember(tableId, member); CustomizeTimeBoxes timeBoxes = timeBoxRepository.findTableTimeBoxes(tableEntity); return new CustomizeTableResponse(tableEntity.toDomain(), timeBoxes); } @@ -56,8 +53,7 @@ public CustomizeTableResponse updateTable( @Transactional public CustomizeTableResponse updateUsedAt(long tableId, Member member) { - CustomizeTableEntity tableEntity = tableRepository.findByIdAndMember(tableId, member) - .orElseThrow(() -> new DTClientErrorException(ClientErrorCode.TABLE_NOT_FOUND)); + CustomizeTableEntity tableEntity = tableRepository.getByIdAndMember(tableId, member); CustomizeTimeBoxes timeBoxes = timeBoxRepository.findTableTimeBoxes(tableEntity); tableEntity.updateUsedAt(); @@ -66,8 +62,7 @@ public CustomizeTableResponse updateUsedAt(long tableId, Member member) { @Transactional public void deleteTable(long tableId, Member member) { - CustomizeTableEntity table = tableRepository.findByIdAndMember(tableId, member) - .orElseThrow(() -> new DTClientErrorException(ClientErrorCode.TABLE_NOT_FOUND)); + CustomizeTableEntity table = tableRepository.getByIdAndMember(tableId, member); timeBoxRepository.deleteAllByTable(table); tableRepository.delete(table); } diff --git a/src/main/java/com/debatetimer/service/customize/CustomizeServiceV2.java b/src/main/java/com/debatetimer/service/customize/CustomizeServiceV2.java new file mode 100644 index 00000000..423948a7 --- /dev/null +++ b/src/main/java/com/debatetimer/service/customize/CustomizeServiceV2.java @@ -0,0 +1,136 @@ +package com.debatetimer.service.customize; + +import com.debatetimer.domain.customize.CustomizeTable; +import com.debatetimer.domain.customize.CustomizeTimeBoxes; +import com.debatetimer.domain.member.Member; +import com.debatetimer.dto.customize.request.BellRequest; +import com.debatetimer.dto.customize.request.CustomizeTableCreateRequest; +import com.debatetimer.dto.customize.request.CustomizeTimeBoxCreateRequest; +import com.debatetimer.dto.customize.response.BellResponse; +import com.debatetimer.dto.customize.response.CustomizeTableResponse; +import com.debatetimer.dto.customize.response.CustomizeTimeBoxResponse; +import com.debatetimer.entity.customize.BellEntity; +import com.debatetimer.entity.customize.CustomizeTableEntity; +import com.debatetimer.entity.customize.CustomizeTimeBox; +import com.debatetimer.repository.customize.BellRepository; +import com.debatetimer.repository.customize.CustomizeTableRepository; +import com.debatetimer.repository.customize.CustomizeTimeBoxRepository; +import java.util.List; +import java.util.stream.IntStream; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class CustomizeServiceV2 { + + private final CustomizeTableRepository tableRepository; + private final CustomizeTimeBoxRepository timeBoxRepository; + private final BellRepository bellRepository; + + @Transactional + public CustomizeTableResponse save(CustomizeTableCreateRequest tableCreateRequest, Member member) { + CustomizeTable table = tableCreateRequest.toTable(member); + CustomizeTableEntity savedTable = tableRepository.save(new CustomizeTableEntity(table)); + + return saveTimeBoxesAndBells(tableCreateRequest, savedTable.toDomain()); + } + + @Transactional(readOnly = true) + public CustomizeTableResponse findTable(long tableId, Member member) { + CustomizeTableEntity tableEntity = tableRepository.getByIdAndMember(tableId, member); + CustomizeTimeBoxes timeBoxes = timeBoxRepository.findTableTimeBoxes(tableEntity); + List timeBoxResponses = timeBoxes.getTimeBoxes() + .stream() + .map(this::getTimeBoxResponse) + .toList(); + return new CustomizeTableResponse(tableEntity.toDomain(), timeBoxResponses); + } + + @Transactional + public CustomizeTableResponse updateTable( + CustomizeTableCreateRequest tableCreateRequest, + long tableId, + Member member + ) { + CustomizeTableEntity existingTable = tableRepository.getByIdAndMember(tableId, member); + CustomizeTable renewedTable = tableCreateRequest.toTable(member); + existingTable.updateTable(renewedTable); + + deleteBell(timeBoxRepository.findTableTimeBoxes(existingTable)); + timeBoxRepository.deleteAllByTable(existingTable); + return saveTimeBoxesAndBells(tableCreateRequest, existingTable.toDomain()); + } + + @Transactional + public CustomizeTableResponse updateUsedAt(long tableId, Member member) { + CustomizeTableEntity tableEntity = tableRepository.getByIdAndMember(tableId, member); + CustomizeTimeBoxes timeBoxes = timeBoxRepository.findTableTimeBoxes(tableEntity); + tableEntity.updateUsedAt(); + List timeBoxResponses = timeBoxes.getTimeBoxes() + .stream() + .map(this::getTimeBoxResponse) + .toList(); + return new CustomizeTableResponse(tableEntity.toDomain(), timeBoxResponses); + } + + @Transactional + public void deleteTable(long tableId, Member member) { + CustomizeTableEntity table = tableRepository.getByIdAndMember(tableId, member); + + deleteBell(timeBoxRepository.findTableTimeBoxes(table)); + timeBoxRepository.deleteAllByTable(table); + tableRepository.delete(table); + } + + private CustomizeTableResponse saveTimeBoxesAndBells( + CustomizeTableCreateRequest tableCreateRequest, + CustomizeTable table + ) { + List timeBoxCreateRequests = tableCreateRequest.table(); + List timeBoxResponses = IntStream.range(0, timeBoxCreateRequests.size()) + .mapToObj(i -> createTimeBoxResponse(timeBoxCreateRequests.get(i), table, i + 1)) + .toList(); + return new CustomizeTableResponse(table, timeBoxResponses); + } + + private CustomizeTimeBoxResponse createTimeBoxResponse( + CustomizeTimeBoxCreateRequest request, + CustomizeTable table, + int sequence + ) { + CustomizeTimeBox savedTimeBox = timeBoxRepository.save(request.toTimeBox(table, sequence)); + return createTimeBoxResponse(request.bell(), savedTimeBox); + } + + private CustomizeTimeBoxResponse createTimeBoxResponse(List bellRequests, CustomizeTimeBox timeBox) { + if (timeBox.getBoxType().isTimeBased()) { + return new CustomizeTimeBoxResponse(timeBox, null); + } + + List bellResponses = bellRequests + .stream() + .map(bellRequest -> new BellEntity(timeBox, bellRequest.time(), bellRequest.count())) + .map(bellRepository::save) + .map(bell -> new BellResponse(bell.getTime(), bell.getCount())) + .toList(); + return new CustomizeTimeBoxResponse(timeBox, bellResponses); + } + + private CustomizeTimeBoxResponse getTimeBoxResponse(CustomizeTimeBox timeBox) { + if (timeBox.getBoxType().isTimeBased()) { + return new CustomizeTimeBoxResponse(timeBox, null); + } + + List bellResponses = bellRepository.findByCustomizeTimeBox(timeBox) + .stream() + .map(bell -> new BellResponse(bell.getTime(), bell.getCount())) + .toList(); + return new CustomizeTimeBoxResponse(timeBox, bellResponses); + } + + private void deleteBell(CustomizeTimeBoxes savedCustomizeTimeBoxes) { + bellRepository.deleteAllByCustomizeTimeBoxIn(savedCustomizeTimeBoxes.getTimeBoxes()); + } +} diff --git a/src/main/resources/db/migration/V10__add_bell_table.sql b/src/main/resources/db/migration/V10__add_bell_table.sql new file mode 100644 index 00000000..b6e4c5cf --- /dev/null +++ b/src/main/resources/db/migration/V10__add_bell_table.sql @@ -0,0 +1,13 @@ +create table bell +( + id bigint auto_increment, + bell_time bigint not null, + count bigint not null, + customize_time_box_id bigint not null, + primary key (id) +); + +alter table bell + add constraint bell_to_customize_time_box + foreign key (customize_time_box_id) + references customize_time_box(id); diff --git a/src/test/java/com/debatetimer/controller/BaseControllerTest.java b/src/test/java/com/debatetimer/controller/BaseControllerTest.java index 78976392..2ca415ea 100644 --- a/src/test/java/com/debatetimer/controller/BaseControllerTest.java +++ b/src/test/java/com/debatetimer/controller/BaseControllerTest.java @@ -4,6 +4,7 @@ import com.debatetimer.client.oauth.OAuthClient; import com.debatetimer.domain.customize.CustomizeBoxType; import com.debatetimer.domain.customize.Stance; +import com.debatetimer.dto.customize.request.BellRequest; import com.debatetimer.dto.customize.request.CustomizeTableCreateRequest; import com.debatetimer.dto.customize.request.CustomizeTableInfoCreateRequest; import com.debatetimer.dto.customize.request.CustomizeTimeBoxCreateRequest; @@ -97,8 +98,15 @@ private ArbitraryBuilder getCustomizeTimeBoxCreat .set("speechType", "입론1") .set("boxType", CustomizeBoxType.NORMAL) .set("time", 120) + .set("bell", getBellRequestBuilder().sampleList(2)) .set("timePerTeam", 60) .set("timePerSpeaking", null) .set("speaker", "발언자"); } + + private ArbitraryBuilder getBellRequestBuilder() { + return fixtureMonkey.giveMeBuilder(BellRequest.class) + .set("time", 30) + .set("count", 1); + } } diff --git a/src/test/java/com/debatetimer/controller/customize/CustomizeDocumentTest.java b/src/test/java/com/debatetimer/controller/customize/CustomizeDocumentTest.java index c14b37fd..e7545638 100644 --- a/src/test/java/com/debatetimer/controller/customize/CustomizeDocumentTest.java +++ b/src/test/java/com/debatetimer/controller/customize/CustomizeDocumentTest.java @@ -20,9 +20,11 @@ import com.debatetimer.controller.Tag; import com.debatetimer.domain.customize.CustomizeBoxType; import com.debatetimer.domain.customize.Stance; +import com.debatetimer.dto.customize.request.BellRequest; import com.debatetimer.dto.customize.request.CustomizeTableCreateRequest; import com.debatetimer.dto.customize.request.CustomizeTableInfoCreateRequest; import com.debatetimer.dto.customize.request.CustomizeTimeBoxCreateRequest; +import com.debatetimer.dto.customize.response.BellResponse; import com.debatetimer.dto.customize.response.CustomizeTableInfoResponse; import com.debatetimer.dto.customize.response.CustomizeTableResponse; import com.debatetimer.dto.customize.response.CustomizeTimeBoxResponse; @@ -68,6 +70,9 @@ class Save { fieldWithPath("table[].speechType").type(STRING).description("발언 유형"), fieldWithPath("table[].boxType").type(STRING).description("타임 박스 유형"), fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)").optional(), + fieldWithPath("table[].bell").type(ARRAY).description("종소리 정보").optional(), + fieldWithPath("table[].bell[].time").type(NUMBER).description("종소리 울릴 시간(초)").optional(), + fieldWithPath("table[].bell[].count").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() @@ -89,6 +94,9 @@ class Save { fieldWithPath("table[].speechType").type(STRING).description("발언 유형"), fieldWithPath("table[].boxType").type(STRING).description("타임 박스 유형"), fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)").optional(), + fieldWithPath("table[].bell").type(ARRAY).description("종소리 정보").optional(), + fieldWithPath("table[].bell[].time").type(NUMBER).description("종소리 울릴 시간(초)").optional(), + fieldWithPath("table[].bell[].count").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() @@ -101,13 +109,13 @@ class Save { "반대", true, true), List.of( new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론", CustomizeBoxType.NORMAL, - 120, null, null, "콜리"), + 120, List.of(new BellRequest(90, 1)), null, null, "콜리"), new CustomizeTimeBoxCreateRequest(Stance.CONS, "입론", CustomizeBoxType.NORMAL, - 120, null, null, "비토"), + 120, List.of(new BellRequest(90, 1), new BellRequest(120, 2)), null, null, "비토"), new CustomizeTimeBoxCreateRequest(Stance.NEUTRAL, "난상 토론", CustomizeBoxType.TIME_BASED, - null, 360, 120, null), + null, null, 360, 120, null), new CustomizeTimeBoxCreateRequest(Stance.NEUTRAL, "존중 토론", CustomizeBoxType.TIME_BASED, - null, 360, null, null) + null, null, 360, null, null) ) ); CustomizeTableResponse response = new CustomizeTableResponse( @@ -116,13 +124,13 @@ class Save { "찬성", "반대", true, true), List.of( new CustomizeTimeBoxResponse(Stance.PROS, "입론", CustomizeBoxType.NORMAL, - 120, null, null, "콜리"), + 120, List.of(new BellResponse(90, 1)), null, null, "콜리"), new CustomizeTimeBoxResponse(Stance.CONS, "입론", CustomizeBoxType.NORMAL, - 120, null, null, "비토"), + 120, List.of(new BellResponse(90, 1), new BellResponse(120, 2)), null, null, "비토"), new CustomizeTimeBoxResponse(Stance.NEUTRAL, "난상 토론", CustomizeBoxType.TIME_BASED, - null, 360, 120, null), + null, null, 360, 120, null), new CustomizeTimeBoxResponse(Stance.NEUTRAL, "존중 토론", CustomizeBoxType.TIME_BASED, - null, 360, null, null) + null, null, 360, null, null) ) ); doReturn(response).when(customizeService).save(eq(request), any()); @@ -160,13 +168,13 @@ class Save { "반대", true, true), List.of( new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론", CustomizeBoxType.NORMAL, - 120, null, null, "콜리"), + 120, List.of(new BellRequest(90, 1)), null, null, "콜리"), new CustomizeTimeBoxCreateRequest(Stance.CONS, "입론", CustomizeBoxType.NORMAL, - 120, null, null, "비토"), + 120, List.of(new BellRequest(90, 1), new BellRequest(120, 2)), null, null, "비토"), new CustomizeTimeBoxCreateRequest(Stance.NEUTRAL, "난상 토론", CustomizeBoxType.TIME_BASED, - null, 360, 120, null), + null, null, 360, 120, null), new CustomizeTimeBoxCreateRequest(Stance.NEUTRAL, "존중 토론", CustomizeBoxType.TIME_BASED, - null, 360, null, null) + null, null, 360, null, null) ) ); doThrow(new DTClientErrorException(errorCode)).when(customizeService).save(eq(request), any()); @@ -221,6 +229,9 @@ class GetTable { fieldWithPath("table[].speechType").type(STRING).description("발언 유형"), fieldWithPath("table[].boxType").type(STRING).description("타임 박스 유형"), fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)").optional(), + fieldWithPath("table[].bell").type(ARRAY).description("종소리 정보").optional(), + fieldWithPath("table[].bell[].time").type(NUMBER).description("종소리 울릴 시간(초)").optional(), + fieldWithPath("table[].bell[].count").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() @@ -235,13 +246,13 @@ class GetTable { "찬성", "반대", true, true), List.of( new CustomizeTimeBoxResponse(Stance.PROS, "입론", CustomizeBoxType.NORMAL, - 120, null, null, "콜리"), + 120, List.of(new BellResponse(90, 1)), null, null, "콜리"), new CustomizeTimeBoxResponse(Stance.CONS, "입론", CustomizeBoxType.NORMAL, - 120, null, null, "비토"), + 120, List.of(new BellResponse(90, 1), new BellResponse(120, 2)), null, null, "비토"), new CustomizeTimeBoxResponse(Stance.NEUTRAL, "난상 토론", CustomizeBoxType.TIME_BASED, - null, 360, 120, null), + null, null, 360, 120, null), new CustomizeTimeBoxResponse(Stance.NEUTRAL, "존중 토론", CustomizeBoxType.TIME_BASED, - null, 360, null, null) + null, null, 360, null, null) ) ); doReturn(response).when(customizeService).findTable(eq(tableId), any()); @@ -311,6 +322,9 @@ class UpdateTable { fieldWithPath("table[].speechType").type(STRING).description("발언 유형"), fieldWithPath("table[].boxType").type(STRING).description("타임 박스 유형"), fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)").optional(), + fieldWithPath("table[].bell").type(ARRAY).description("종소리 정보").optional(), + fieldWithPath("table[].bell[].time").type(NUMBER).description("종소리 울릴 시간(초)").optional(), + fieldWithPath("table[].bell[].count").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() @@ -332,6 +346,9 @@ class UpdateTable { fieldWithPath("table[].speechType").type(STRING).description("발언 유형"), fieldWithPath("table[].boxType").type(STRING).description("타임 박스 유형"), fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)").optional(), + fieldWithPath("table[].bell").type(ARRAY).description("종소리 정보").optional(), + fieldWithPath("table[].bell[].time").type(NUMBER).description("종소리 울릴 시간(초)").optional(), + fieldWithPath("table[].bell[].count").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() @@ -345,13 +362,13 @@ class UpdateTable { "반대", true, true), List.of( new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론", CustomizeBoxType.NORMAL, - 120, null, null, "콜리"), + 120, List.of(new BellRequest(90, 1)), null, null, "콜리"), new CustomizeTimeBoxCreateRequest(Stance.CONS, "입론", CustomizeBoxType.NORMAL, - 120, null, null, "비토"), + 120, List.of(new BellRequest(90, 1), new BellRequest(120, 2)), null, null, "비토"), new CustomizeTimeBoxCreateRequest(Stance.NEUTRAL, "난상 토론", CustomizeBoxType.TIME_BASED, - null, 360, 120, null), + null, null, 360, 120, null), new CustomizeTimeBoxCreateRequest(Stance.NEUTRAL, "존중 토론", CustomizeBoxType.TIME_BASED, - null, 360, null, null) + null, null, 360, null, null) ) ); CustomizeTableResponse response = new CustomizeTableResponse( @@ -360,13 +377,13 @@ class UpdateTable { "찬성", "반대", true, true), List.of( new CustomizeTimeBoxResponse(Stance.PROS, "입론", CustomizeBoxType.NORMAL, - 120, null, null, "콜리"), + 120, List.of(new BellResponse(90, 1)), null, null, "콜리"), new CustomizeTimeBoxResponse(Stance.CONS, "입론", CustomizeBoxType.NORMAL, - 120, null, null, "비토"), + 120, List.of(new BellResponse(90, 1), new BellResponse(120, 2)), null, null, "비토"), new CustomizeTimeBoxResponse(Stance.NEUTRAL, "난상 토론", CustomizeBoxType.TIME_BASED, - null, 360, 120, null), + null, null, 360, 120, null), new CustomizeTimeBoxResponse(Stance.NEUTRAL, "존중 토론", CustomizeBoxType.TIME_BASED, - null, 360, null, null) + null, null, 360, null, null) ) ); doReturn(response).when(customizeService).updateTable(eq(request), eq(tableId), any()); @@ -407,13 +424,13 @@ class UpdateTable { "반대", true, true), List.of( new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론", CustomizeBoxType.NORMAL, - 120, null, null, "콜리"), + 120, List.of(new BellRequest(90, 1)), null, null, "콜리"), new CustomizeTimeBoxCreateRequest(Stance.CONS, "입론", CustomizeBoxType.NORMAL, - 120, null, null, "비토"), + 120, List.of(new BellRequest(90, 1), new BellRequest(120, 2)), null, null, "비토"), new CustomizeTimeBoxCreateRequest(Stance.NEUTRAL, "난상 토론", CustomizeBoxType.TIME_BASED, - null, 360, 120, null), + null, null, 360, 120, null), new CustomizeTimeBoxCreateRequest(Stance.NEUTRAL, "존중 토론", CustomizeBoxType.TIME_BASED, - null, 360, null, null) + null, null, 360, null, null) ) ); doThrow(new DTClientErrorException(errorCode)).when(customizeService) @@ -470,6 +487,9 @@ class Debate { fieldWithPath("table[].speechType").type(STRING).description("발언 유형"), fieldWithPath("table[].boxType").type(STRING).description("타임 박스 유형"), fieldWithPath("table[].time").type(NUMBER).description("발언 시간(초)").optional(), + fieldWithPath("table[].bell").type(ARRAY).description("종소리 정보").optional(), + fieldWithPath("table[].bell[].time").type(NUMBER).description("종소리 울릴 시간(초)").optional(), + fieldWithPath("table[].bell[].count").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() @@ -485,13 +505,13 @@ class Debate { "찬성", "반대", true, true), List.of( new CustomizeTimeBoxResponse(Stance.PROS, "입론", CustomizeBoxType.NORMAL, - 120, null, null, "콜리"), + 120, List.of(new BellResponse(90, 1)), null, null, "콜리"), new CustomizeTimeBoxResponse(Stance.CONS, "입론", CustomizeBoxType.NORMAL, - 120, null, null, "비토"), + 120, List.of(new BellResponse(90, 1), new BellResponse(120, 2)), null, null, "비토"), new CustomizeTimeBoxResponse(Stance.NEUTRAL, "난상 토론", CustomizeBoxType.TIME_BASED, - null, 360, 120, null), + null, null, 360, 120, null), new CustomizeTimeBoxResponse(Stance.NEUTRAL, "존중 토론", CustomizeBoxType.TIME_BASED, - null, 360, null, null) + null, null, 360, null, null) ) ); doReturn(response).when(customizeService).updateUsedAt(eq(tableId), any()); diff --git a/src/test/java/com/debatetimer/entity/customize/BellEntityTest.java b/src/test/java/com/debatetimer/entity/customize/BellEntityTest.java new file mode 100644 index 00000000..73535499 --- /dev/null +++ b/src/test/java/com/debatetimer/entity/customize/BellEntityTest.java @@ -0,0 +1,32 @@ +package com.debatetimer.entity.customize; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.debatetimer.exception.custom.DTClientErrorException; +import com.debatetimer.exception.errorcode.ClientErrorCode; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class BellEntityTest { + + @Nested + class Validate { + + @Test + void 벨_시간은_0이상이어야_한다() { + assertThatThrownBy(() -> new BellEntity(null, -1, 1)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.INVALID_BELL_TIME.getMessage()); + } + + @ValueSource(ints = {0, BellEntity.MAX_BELL_COUNT + 1}) + @ParameterizedTest + void 벨_횟수는_정해진_횟수_이내여야_한다(int count) { + assertThatThrownBy(() -> new BellEntity(null, 1, count)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.INVALID_BELL_COUNT.getMessage()); + } + } +} diff --git a/src/test/java/com/debatetimer/fixture/BellGenerator.java b/src/test/java/com/debatetimer/fixture/BellGenerator.java new file mode 100644 index 00000000..b9698fd3 --- /dev/null +++ b/src/test/java/com/debatetimer/fixture/BellGenerator.java @@ -0,0 +1,21 @@ +package com.debatetimer.fixture; + +import com.debatetimer.entity.customize.BellEntity; +import com.debatetimer.entity.customize.CustomizeTimeBox; +import com.debatetimer.repository.customize.BellRepository; +import org.springframework.stereotype.Component; + +@Component +public class BellGenerator { + + private final BellRepository bellRepository; + + public BellGenerator(BellRepository bellRepository) { + this.bellRepository = bellRepository; + } + + public BellEntity generate(CustomizeTimeBox timeBox, int time, int count) { + BellEntity bell = new BellEntity(timeBox, time, count); + return bellRepository.save(bell); + } +} diff --git a/src/test/java/com/debatetimer/service/BaseServiceTest.java b/src/test/java/com/debatetimer/service/BaseServiceTest.java index f512da4e..6220cc2a 100644 --- a/src/test/java/com/debatetimer/service/BaseServiceTest.java +++ b/src/test/java/com/debatetimer/service/BaseServiceTest.java @@ -1,9 +1,11 @@ package com.debatetimer.service; import com.debatetimer.DataBaseCleaner; +import com.debatetimer.fixture.BellGenerator; import com.debatetimer.fixture.CustomizeTableGenerator; import com.debatetimer.fixture.CustomizeTimeBoxGenerator; import com.debatetimer.fixture.MemberGenerator; +import com.debatetimer.repository.customize.BellRepository; import com.debatetimer.repository.customize.CustomizeTableRepository; import com.debatetimer.repository.customize.CustomizeTimeBoxRepository; import com.debatetimer.repository.member.MemberRepository; @@ -26,6 +28,9 @@ public abstract class BaseServiceTest { @Autowired protected CustomizeTimeBoxRepository customizeTimeBoxRepository; + @Autowired + protected BellRepository bellRepository; + @Autowired protected MemberGenerator memberGenerator; @@ -35,6 +40,9 @@ public abstract class BaseServiceTest { @Autowired protected CustomizeTimeBoxGenerator customizeTimeBoxGenerator; + @Autowired + protected BellGenerator bellGenerator; + protected void runAtSameTime(int count, Runnable task) throws InterruptedException { List threads = IntStream.range(0, count) .mapToObj(i -> new Thread(task)) diff --git a/src/test/java/com/debatetimer/service/customize/CustomizeServiceTest.java b/src/test/java/com/debatetimer/service/customize/CustomizeServiceTest.java index ad4d9e16..0223170a 100644 --- a/src/test/java/com/debatetimer/service/customize/CustomizeServiceTest.java +++ b/src/test/java/com/debatetimer/service/customize/CustomizeServiceTest.java @@ -7,6 +7,7 @@ import com.debatetimer.domain.customize.CustomizeBoxType; import com.debatetimer.domain.customize.Stance; import com.debatetimer.domain.member.Member; +import com.debatetimer.dto.customize.request.BellRequest; import com.debatetimer.dto.customize.request.CustomizeTableCreateRequest; import com.debatetimer.dto.customize.request.CustomizeTableInfoCreateRequest; import com.debatetimer.dto.customize.request.CustomizeTimeBoxCreateRequest; @@ -39,19 +40,18 @@ class Save { "반대", true, true), List.of( new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론1", CustomizeBoxType.NORMAL, - 120, 60, null, "발언자1"), + 120, List.of(new BellRequest(90, 1)), 60, null, "발언자1"), new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론2", CustomizeBoxType.NORMAL, - 120, 60, null, "발언자2") + 120, List.of(new BellRequest(90, 1), new BellRequest(120, 2)), 60, null, "발언자2") ) ); CustomizeTableResponse savedTableResponse = customizeService.save(customizeTableCreateRequest, chan); - Optional foundTable = customizeTableRepository.findById(savedTableResponse.id()); - List foundTimeBoxes = customizeTimeBoxRepository.findAllByCustomizeTable( - foundTable.get()); + CustomizeTableEntity foundTable = customizeTableRepository.getByIdAndMember(savedTableResponse.id(), chan); + List foundTimeBoxes = customizeTimeBoxRepository.findAllByCustomizeTable(foundTable); assertAll( - () -> assertThat(foundTable.get().getName()).isEqualTo(customizeTableCreateRequest.info().name()), + () -> assertThat(foundTable.getName()).isEqualTo(customizeTableCreateRequest.info().name()), () -> assertThat(foundTimeBoxes).hasSize(customizeTableCreateRequest.table().size()) ); } @@ -100,21 +100,20 @@ class UpdateTable { "반대", true, true), List.of( new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론1", CustomizeBoxType.NORMAL, - 120, 60, null, "발언자1"), + 120, List.of(new BellRequest(90, 1)), 60, null, "발언자1"), new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론2", CustomizeBoxType.NORMAL, - 120, 60, null, "발언자2") + 120, List.of(new BellRequest(90, 1), new BellRequest(120, 2)), 60, null, "발언자2") ) ); customizeService.updateTable(renewTableRequest, chanTable.getId(), chan); - Optional updatedTable = customizeTableRepository.findById(chanTable.getId()); - List updatedTimeBoxes = customizeTimeBoxRepository.findAllByCustomizeTable( - updatedTable.get()); + CustomizeTableEntity updatedTable = customizeTableRepository.getByIdAndMember(chanTable.getId(), chan); + List updatedTimeBoxes = customizeTimeBoxRepository.findAllByCustomizeTable(updatedTable); assertAll( - () -> assertThat(updatedTable.get().getId()).isEqualTo(chanTable.getId()), - () -> assertThat(updatedTable.get().getName()).isEqualTo(renewTableRequest.info().name()), + () -> assertThat(updatedTable.getId()).isEqualTo(chanTable.getId()), + () -> assertThat(updatedTable.getName()).isEqualTo(renewTableRequest.info().name()), () -> assertThat(updatedTimeBoxes).hasSize(renewTableRequest.table().size()) ); } @@ -130,9 +129,9 @@ class UpdateTable { "반대", true, true), List.of( new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론1", CustomizeBoxType.NORMAL, - 120, 60, null, "발언자1"), + 120, List.of(new BellRequest(90, 1)), 60, null, "발언자1"), new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론2", CustomizeBoxType.NORMAL, - 120, 60, null, "발언자2") + 120, List.of(new BellRequest(90, 1), new BellRequest(120, 2)), 60, null, "발언자2") ) ); @@ -150,9 +149,9 @@ class UpdateTable { "반대", true, true), List.of( new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론1", CustomizeBoxType.NORMAL, - 120, 60, null, "발언자1"), + 120, List.of(new BellRequest(90, 1)), 60, null, "발언자1"), new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론2", CustomizeBoxType.NORMAL, - 120, 60, null, "발언자2") + 120, List.of(new BellRequest(90, 1), new BellRequest(120, 2)), 60, null, "발언자2") ) ); @@ -173,10 +172,10 @@ class UpdateUsedAt { customizeService.updateUsedAt(table.getId(), member); - Optional updatedTable = customizeTableRepository.findById(table.getId()); + CustomizeTableEntity updatedTable = customizeTableRepository.getByIdAndMember(table.getId(), member); assertAll( - () -> assertThat(updatedTable.get().getId()).isEqualTo(table.getId()), - () -> assertThat(updatedTable.get().getUsedAt()).isAfter(beforeUsedAt) + () -> assertThat(updatedTable.getId()).isEqualTo(table.getId()), + () -> assertThat(updatedTable.getUsedAt()).isAfter(beforeUsedAt) ); } @@ -186,18 +185,8 @@ class UpdateUsedAt { Member coli = memberGenerator.generate("default2@gmail.com"); CustomizeTableEntity chanTable = customizeTableGenerator.generate(chan); long chanTableId = chanTable.getId(); - CustomizeTableCreateRequest renewTableRequest = 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") - ) - ); - assertThatThrownBy(() -> customizeService.updateTable(renewTableRequest, chanTableId, coli)) + assertThatThrownBy(() -> customizeService.updateUsedAt(chanTableId, coli)) .isInstanceOf(DTClientErrorException.class) .hasMessage(ClientErrorCode.TABLE_NOT_FOUND.getMessage()); } diff --git a/src/test/java/com/debatetimer/service/customize/CustomizeServiceV2Test.java b/src/test/java/com/debatetimer/service/customize/CustomizeServiceV2Test.java new file mode 100644 index 00000000..50811628 --- /dev/null +++ b/src/test/java/com/debatetimer/service/customize/CustomizeServiceV2Test.java @@ -0,0 +1,241 @@ +package com.debatetimer.service.customize; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.debatetimer.domain.customize.CustomizeBoxType; +import com.debatetimer.domain.customize.Stance; +import com.debatetimer.domain.member.Member; +import com.debatetimer.dto.customize.request.BellRequest; +import com.debatetimer.dto.customize.request.CustomizeTableCreateRequest; +import com.debatetimer.dto.customize.request.CustomizeTableInfoCreateRequest; +import com.debatetimer.dto.customize.request.CustomizeTimeBoxCreateRequest; +import com.debatetimer.dto.customize.response.CustomizeTableResponse; +import com.debatetimer.entity.customize.BellEntity; +import com.debatetimer.entity.customize.CustomizeTableEntity; +import com.debatetimer.entity.customize.CustomizeTimeBox; +import com.debatetimer.exception.custom.DTClientErrorException; +import com.debatetimer.exception.errorcode.ClientErrorCode; +import com.debatetimer.service.BaseServiceTest; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +class CustomizeServiceV2Test extends BaseServiceTest { + + @Autowired + private CustomizeServiceV2 customizeService; + + @Nested + class Save { + + @Test + void 사용자_지정_토론_테이블을_생성한다() { + Member chan = memberGenerator.generate("default@gmail.com"); + CustomizeTableCreateRequest customizeTableCreateRequest = new CustomizeTableCreateRequest( + new CustomizeTableInfoCreateRequest("자유 테이블", "주제", "찬성", + "반대", true, true), + List.of( + new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론1", CustomizeBoxType.NORMAL, + 120, List.of(new BellRequest(90, 1)), 60, null, "발언자1"), + new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론2", CustomizeBoxType.NORMAL, + 120, List.of(new BellRequest(90, 1), new BellRequest(120, 2)), 60, null, "발언자2") + ) + ); + + CustomizeTableResponse savedTableResponse = customizeService.save(customizeTableCreateRequest, chan); + CustomizeTableEntity foundTable = customizeTableRepository.getByIdAndMember(savedTableResponse.id(), chan); + List foundTimeBoxes = customizeTimeBoxRepository.findAllByCustomizeTable(foundTable); + List foundBells = bellRepository.findAllByCustomizeTimeBoxIn(foundTimeBoxes); + + assertAll( + () -> assertThat(foundTable.getName()).isEqualTo(customizeTableCreateRequest.info().name()), + () -> assertThat(foundTimeBoxes).hasSize(customizeTableCreateRequest.table().size()), + () -> assertThat(foundBells).hasSize(3) + ); + } + } + + @Nested + class FindTable { + + @Test + void 사용자_지정_토론_테이블을_조회한다() { + Member chan = memberGenerator.generate("default@gmail.com"); + CustomizeTableEntity chanTable = customizeTableGenerator.generate(chan); + CustomizeTimeBox customizeTimeBox = customizeTimeBoxGenerator.generate(chanTable, CustomizeBoxType.NORMAL, + 1); + customizeTimeBoxGenerator.generate(chanTable, CustomizeBoxType.NORMAL, 2); + bellGenerator.generate(customizeTimeBox, 1, 1); + bellGenerator.generate(customizeTimeBox, 1, 2); + + CustomizeTableResponse foundResponse = customizeService.findTable(chanTable.getId(), chan); + + assertAll( + () -> assertThat(foundResponse.id()).isEqualTo(chanTable.getId()), + () -> assertThat(foundResponse.table()).hasSize(2), + () -> assertThat(foundResponse.table().get(0).bell()).hasSize(2), + () -> assertThat(foundResponse.table().get(1).bell()).hasSize(0) + ); + } + + @Test + void 회원_소유가_아닌_테이블_조회_시_예외를_발생시킨다() { + Member chan = memberGenerator.generate("default@gmail.com"); + Member coli = memberGenerator.generate("default2@gmail.com"); + CustomizeTableEntity chanTable = customizeTableGenerator.generate(chan); + long chanTableId = chanTable.getId(); + + assertThatThrownBy(() -> customizeService.findTable(chanTableId, coli)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.TABLE_NOT_FOUND.getMessage()); + } + } + + @Nested + class UpdateTable { + + @Test + void 사용자_지정_토론_테이블을_수정한다() { + Member chan = memberGenerator.generate("default@gmail.com"); + CustomizeTableEntity chanTable = customizeTableGenerator.generate(chan); + CustomizeTableCreateRequest renewTableRequest = new CustomizeTableCreateRequest( + new CustomizeTableInfoCreateRequest("자유 테이블", "주제", "찬성", + "반대", true, true), + List.of( + new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론1", CustomizeBoxType.NORMAL, + 120, List.of(new BellRequest(90, 1)), 60, null, "발언자1"), + new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론2", CustomizeBoxType.NORMAL, + 120, List.of(new BellRequest(90, 1), new BellRequest(120, 2)), 60, null, "발언자2") + ) + ); + + customizeService.updateTable(renewTableRequest, chanTable.getId(), chan); + + CustomizeTableEntity updatedTable = customizeTableRepository.getByIdAndMember(chanTable.getId(), chan); + List updatedTimeBoxes = customizeTimeBoxRepository.findAllByCustomizeTable(updatedTable); + List bells = bellRepository.findAllByCustomizeTimeBoxIn(updatedTimeBoxes); + + assertAll( + () -> assertThat(updatedTable.getId()).isEqualTo(chanTable.getId()), + () -> assertThat(updatedTable.getName()).isEqualTo(renewTableRequest.info().name()), + () -> assertThat(updatedTimeBoxes).hasSize(renewTableRequest.table().size()), + () -> assertThat(bells).hasSize(3) + ); + } + + @Test + void 회원_소유가_아닌_테이블_수정_시_예외를_발생시킨다() { + Member chan = memberGenerator.generate("default@gmail.com"); + Member coli = memberGenerator.generate("default2@gmail.com"); + CustomizeTableEntity chanTable = customizeTableGenerator.generate(chan); + long chanTableId = chanTable.getId(); + CustomizeTableCreateRequest renewTableRequest = new CustomizeTableCreateRequest( + new CustomizeTableInfoCreateRequest("자유 테이블", "주제", "찬성", + "반대", true, true), + List.of( + new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론1", CustomizeBoxType.NORMAL, + 120, List.of(new BellRequest(90, 1)), 60, null, "발언자1"), + new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론2", CustomizeBoxType.NORMAL, + 120, List.of(new BellRequest(90, 1), new BellRequest(120, 2)), 60, null, "발언자2") + ) + ); + + assertThatThrownBy(() -> customizeService.updateTable(renewTableRequest, chanTableId, coli)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.TABLE_NOT_FOUND.getMessage()); + } + + @Test + void 테이블_정보_수정을_동시에_요청할_때_동시에_처리하지_않는다() throws InterruptedException { + Member member = memberGenerator.generate("default@gmail.com"); + CustomizeTableEntity table = customizeTableGenerator.generate(member); + CustomizeTableCreateRequest request = new CustomizeTableCreateRequest( + new CustomizeTableInfoCreateRequest("자유 테이블", "주제", "찬성", + "반대", true, true), + List.of( + new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론1", CustomizeBoxType.NORMAL, + 120, List.of(new BellRequest(90, 1)), 60, null, "발언자1"), + new CustomizeTimeBoxCreateRequest(Stance.PROS, "입론2", CustomizeBoxType.NORMAL, + 120, List.of(new BellRequest(90, 1), new BellRequest(120, 2)), 60, null, "발언자2") + ) + ); + + runAtSameTime(2, () -> customizeService.updateTable(request, table.getId(), member)); + + assertThat(customizeTimeBoxRepository.findAllByCustomizeTable(table)).hasSize(2); + } + } + + @Nested + class UpdateUsedAt { + + @Test + void 사용자_지정_토론_테이블의_사용_시각을_최신화한다() { + Member member = memberGenerator.generate("default@gmail.com"); + CustomizeTableEntity table = customizeTableGenerator.generate(member); + LocalDateTime beforeUsedAt = table.getUsedAt(); + + customizeService.updateUsedAt(table.getId(), member); + + CustomizeTableEntity updatedTable = customizeTableRepository.getByIdAndMember(table.getId(), member); + assertAll( + () -> assertThat(updatedTable.getId()).isEqualTo(table.getId()), + () -> assertThat(updatedTable.getUsedAt()).isAfter(beforeUsedAt) + ); + } + + @Test + void 회원_소유가_아닌_테이블_수정_시_예외를_발생시킨다() { + Member chan = memberGenerator.generate("default@gmail.com"); + Member coli = memberGenerator.generate("default2@gmail.com"); + CustomizeTableEntity chanTable = customizeTableGenerator.generate(chan); + long chanTableId = chanTable.getId(); + + assertThatThrownBy(() -> customizeService.updateUsedAt(chanTableId, coli)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.TABLE_NOT_FOUND.getMessage()); + } + } + + @Nested + class DeleteTable { + + @Test + void 사용자_지정_토론_테이블을_삭제한다() { + Member chan = memberGenerator.generate("default@gmail.com"); + CustomizeTableEntity chanTable = customizeTableGenerator.generate(chan); + customizeTimeBoxGenerator.generate(chanTable, CustomizeBoxType.NORMAL, 1); + customizeTimeBoxGenerator.generate(chanTable, CustomizeBoxType.NORMAL, 2); + + customizeService.deleteTable(chanTable.getId(), chan); + + Optional foundTable = customizeTableRepository.findById(chanTable.getId()); + List timeBoxes = customizeTimeBoxRepository.findAllByCustomizeTable( + chanTable); + List bells = bellRepository.findAllByCustomizeTimeBoxIn(timeBoxes); + + assertAll( + () -> assertThat(foundTable).isEmpty(), + () -> assertThat(timeBoxes).isEmpty(), + () -> assertThat(bells).isEmpty() + ); + } + + @Test + void 회원_소유가_아닌_테이블_삭제_시_예외를_발생시킨다() { + Member chan = memberGenerator.generate("default@gmail.com"); + Member coli = memberGenerator.generate("default2@gmail.com"); + CustomizeTableEntity chanTable = customizeTableGenerator.generate(chan); + long chanTableId = chanTable.getId(); + + assertThatThrownBy(() -> customizeService.deleteTable(chanTableId, coli)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.TABLE_NOT_FOUND.getMessage()); + } + } +}