diff --git a/src/main/java/com/debatetimer/domain/customize/CustomizeTimeBoxDomain.java b/src/main/java/com/debatetimer/domain/customize/CustomizeTimeBoxDomain.java new file mode 100644 index 00000000..18ac7e84 --- /dev/null +++ b/src/main/java/com/debatetimer/domain/customize/CustomizeTimeBoxDomain.java @@ -0,0 +1,72 @@ +package com.debatetimer.domain.customize; + +import com.debatetimer.exception.custom.DTClientErrorException; +import com.debatetimer.exception.errorcode.ClientErrorCode; +import org.springframework.lang.Nullable; + +public abstract class CustomizeTimeBoxDomain { + + public static final int SPEECH_TYPE_MAX_LENGTH = 10; + public static final int SPEAKER_MAX_LENGTH = 5; + + private final Stance stance; + + private final String speechType; + + @Nullable + private final String speaker; + + protected CustomizeTimeBoxDomain(Stance stance, String speechType, @Nullable String speaker) { + validateStance(stance); + validateSpeechType(speechType); + validateSpeaker(speaker); + + this.stance = stance; + this.speechType = speechType; + this.speaker = speaker; + } + + private void validateStance(Stance stance) { + if (stance == null || !isValidStance(stance)) { + throw new DTClientErrorException(ClientErrorCode.INVALID_TIME_BOX_STANCE); + } + } + + protected abstract boolean isValidStance(Stance stance); + + private void validateSpeechType(String speechType) { + if (speechType == null || speechType.isBlank() || speechType.length() > SPEECH_TYPE_MAX_LENGTH) { + throw new DTClientErrorException(ClientErrorCode.INVALID_TIME_BOX_SPEECH_TYPE_LENGTH); + } + } + + private void validateSpeaker(String speaker) { + if (speaker != null && speaker.length() > SPEAKER_MAX_LENGTH) { + throw new DTClientErrorException(ClientErrorCode.INVALID_TIME_BOX_SPEAKER_LENGTH); + } + } + + public final Stance getStance() { + return stance; + } + + public final String getSpeechType() { + return speechType; + } + + @Nullable + public final String getSpeaker() { + return speaker; + } + + public abstract CustomizeBoxType getBoxType(); + + @Nullable + public abstract Integer getTime(); + + @Nullable + public abstract Integer getTimePerTeam(); + + @Nullable + public abstract Integer getTimePerSpeaking(); +} diff --git a/src/main/java/com/debatetimer/domain/customize/NormalTimeBoxDomain.java b/src/main/java/com/debatetimer/domain/customize/NormalTimeBoxDomain.java new file mode 100644 index 00000000..a17800d8 --- /dev/null +++ b/src/main/java/com/debatetimer/domain/customize/NormalTimeBoxDomain.java @@ -0,0 +1,50 @@ +package com.debatetimer.domain.customize; + +import com.debatetimer.exception.custom.DTClientErrorException; +import com.debatetimer.exception.errorcode.ClientErrorCode; +import org.springframework.lang.Nullable; + +public final class NormalTimeBoxDomain extends CustomizeTimeBoxDomain { + + private final int time; + + public NormalTimeBoxDomain(Stance stance, String speechType, @Nullable String speaker, Integer time) { + super(stance, speechType, speaker); + + validateTime(time); + this.time = time; + } + + private void validateTime(Integer time) { + if (time == null || time <= 0) { + throw new DTClientErrorException(ClientErrorCode.INVALID_TIME_BOX_TIME); + } + } + + @Override + protected boolean isValidStance(Stance stance) { + return true; + } + + @Override + public CustomizeBoxType getBoxType() { + return CustomizeBoxType.NORMAL; + } + + @Override + public Integer getTime() { + return time; + } + + @Nullable + @Override + public Integer getTimePerTeam() { + return null; + } + + @Nullable + @Override + public Integer getTimePerSpeaking() { + return null; + } +} diff --git a/src/main/java/com/debatetimer/domain/customize/Stance.java b/src/main/java/com/debatetimer/domain/customize/Stance.java index fd9b0975..0df12c9d 100644 --- a/src/main/java/com/debatetimer/domain/customize/Stance.java +++ b/src/main/java/com/debatetimer/domain/customize/Stance.java @@ -5,4 +5,9 @@ public enum Stance { PROS, CONS, NEUTRAL, + ; + + public boolean isNeutralStance() { + return this == NEUTRAL; + } } diff --git a/src/main/java/com/debatetimer/domain/customize/TimeBasedTimeBoxDomain.java b/src/main/java/com/debatetimer/domain/customize/TimeBasedTimeBoxDomain.java new file mode 100644 index 00000000..30dba5ab --- /dev/null +++ b/src/main/java/com/debatetimer/domain/customize/TimeBasedTimeBoxDomain.java @@ -0,0 +1,72 @@ +package com.debatetimer.domain.customize; + +import com.debatetimer.exception.custom.DTClientErrorException; +import com.debatetimer.exception.errorcode.ClientErrorCode; +import org.springframework.lang.Nullable; + +public class TimeBasedTimeBoxDomain extends CustomizeTimeBoxDomain { + + private final int timePerTeam; + + @Nullable + private final Integer timePerSpeaking; + + public TimeBasedTimeBoxDomain(Stance stance, + String speechType, + @Nullable String speaker, + Integer timePerTeam, + @Nullable Integer timePerSpeaking) { + super(stance, speechType, speaker); + + validateTimes(timePerTeam, timePerSpeaking); + this.timePerTeam = timePerTeam; + this.timePerSpeaking = timePerSpeaking; + } + + private void validateTimes(Integer timePerTeam, Integer timePerSpeaking) { + validateTimePerTeam(timePerTeam); + validateTimePerSpeaking(timePerSpeaking); + if (timePerSpeaking != null && timePerTeam < timePerSpeaking) { + throw new DTClientErrorException(ClientErrorCode.INVALID_TIME_BASED_TIME); + } + } + + private void validateTimePerTeam(Integer time) { + if (time == null || time <= 0) { + throw new DTClientErrorException(ClientErrorCode.INVALID_TIME_BOX_TIME); + } + } + + private void validateTimePerSpeaking(Integer timePerSpeaking) { + if (timePerSpeaking != null && timePerSpeaking <= 0) { + throw new DTClientErrorException(ClientErrorCode.INVALID_TIME_BOX_TIME); + } + } + + @Override + protected boolean isValidStance(Stance stance) { + return stance.isNeutralStance(); + } + + @Override + public CustomizeBoxType getBoxType() { + return CustomizeBoxType.TIME_BASED; + } + + @Override + @Nullable + public Integer getTime() { + return null; + } + + @Override + public Integer getTimePerTeam() { + return timePerTeam; + } + + @Override + @Nullable + public Integer getTimePerSpeaking() { + return timePerSpeaking; + } +} diff --git a/src/test/java/com/debatetimer/domain/customize/CustomizeTimeBoxDomainTest.java b/src/test/java/com/debatetimer/domain/customize/CustomizeTimeBoxDomainTest.java new file mode 100644 index 00000000..be331821 --- /dev/null +++ b/src/test/java/com/debatetimer/domain/customize/CustomizeTimeBoxDomainTest.java @@ -0,0 +1,113 @@ +package com.debatetimer.domain.customize; + +import static org.assertj.core.api.Assertions.assertThatCode; +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.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +class CustomizeTimeBoxDomainTest { + + @Nested + class ValidateStance { + + @Test + void 발언_입장은_비어있을_수_없다() { + assertThatThrownBy(() -> new InheritedCustomizeTimeBoxDomain(null, "비토", "발언자")) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.INVALID_TIME_BOX_STANCE.getMessage()); + } + + @Test + void 발언_입장은_유효한_값이어야_한다() { + assertThatCode(() -> new InheritedCustomizeTimeBoxDomain(Stance.PROS, "비토", "발언자")) + .doesNotThrowAnyException(); + } + } + + @Nested + class ValidateSpeechType { + + @Test + void 발언_종류는_특정_글자를_초과할_수_없다() { + String speechType = "a".repeat(CustomizeTimeBoxDomain.SPEECH_TYPE_MAX_LENGTH + 1); + + assertThatThrownBy(() -> new InheritedCustomizeTimeBoxDomain(Stance.PROS, speechType, "비토")) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.INVALID_TIME_BOX_SPEECH_TYPE_LENGTH.getMessage()); + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {" ", "\n\t"}) + void 발언_종류는_비어있을_수_없다(String emptySpeechType) { + assertThatThrownBy(() -> new InheritedCustomizeTimeBoxDomain(Stance.PROS, emptySpeechType, "비토")) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.INVALID_TIME_BOX_SPEECH_TYPE_LENGTH.getMessage()); + } + + @Test + void 발언_종류는_특정_글자_이내이어야_한다() { + String speechType = "a".repeat(CustomizeTimeBoxDomain.SPEECH_TYPE_MAX_LENGTH); + + assertThatCode(() -> new InheritedCustomizeTimeBoxDomain(Stance.PROS, speechType, "비토")) + .doesNotThrowAnyException(); + } + } + + @Nested + class ValidateSpeaker { + + @Test + void 발언자_이름은_특정_글자를_초과할_수_없다() { + String speaker = "a".repeat(CustomizeTimeBoxDomain.SPEAKER_MAX_LENGTH + 1); + + assertThatThrownBy(() -> new InheritedCustomizeTimeBoxDomain(Stance.PROS, "비토", speaker)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.INVALID_TIME_BOX_SPEAKER_LENGTH.getMessage()); + } + + @Test + void 발언자_이름은_비어있을_수_있다() { + assertThatCode(() -> new InheritedCustomizeTimeBoxDomain(Stance.PROS, "비토", null)) + .doesNotThrowAnyException(); + } + } + + static class InheritedCustomizeTimeBoxDomain extends CustomizeTimeBoxDomain { + + protected InheritedCustomizeTimeBoxDomain(Stance stance, String speechType, String speaker) { + super(stance, speechType, speaker); + } + + @Override + protected boolean isValidStance(Stance stance) { + return true; + } + + @Override + public CustomizeBoxType getBoxType() { + return null; + } + + @Override + public Integer getTime() { + return 0; + } + + @Override + public Integer getTimePerTeam() { + return 0; + } + + @Override + public Integer getTimePerSpeaking() { + return 0; + } + } +} diff --git a/src/test/java/com/debatetimer/domain/customize/NormalTimeBoxDomainTest.java b/src/test/java/com/debatetimer/domain/customize/NormalTimeBoxDomainTest.java new file mode 100644 index 00000000..810ea4ce --- /dev/null +++ b/src/test/java/com/debatetimer/domain/customize/NormalTimeBoxDomainTest.java @@ -0,0 +1,42 @@ +package com.debatetimer.domain.customize; + +import static org.assertj.core.api.Assertions.assertThatCode; +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; + +class NormalTimeBoxDomainTest { + + @Nested + class ValidateTime { + + @Test + void 시간은_0보다_커야_한다() { + Integer time = 0; + + assertThatThrownBy(() -> new NormalTimeBoxDomain(Stance.PROS, "비토", null, time)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.INVALID_TIME_BOX_TIME.getMessage()); + } + + @Test + void 시간은_비어있지_않아야_한다() { + Integer time = null; + + assertThatThrownBy(() -> new NormalTimeBoxDomain(Stance.PROS, "비토", null, time)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.INVALID_TIME_BOX_TIME.getMessage()); + } + + @Test + void 시간은_양수여야_한다() { + Integer time = 1; + + assertThatCode(() -> new NormalTimeBoxDomain(Stance.PROS, "비토", null, time)) + .doesNotThrowAnyException(); + } + } +} diff --git a/src/test/java/com/debatetimer/domain/customize/TimeBasedTimeBoxDomainTest.java b/src/test/java/com/debatetimer/domain/customize/TimeBasedTimeBoxDomainTest.java new file mode 100644 index 00000000..484d0e1a --- /dev/null +++ b/src/test/java/com/debatetimer/domain/customize/TimeBasedTimeBoxDomainTest.java @@ -0,0 +1,93 @@ +package com.debatetimer.domain.customize; + +import static org.assertj.core.api.Assertions.assertThatCode; +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; + +class TimeBasedTimeBoxDomainTest { + + @Nested + class ValidateStance { + + @Test + void 중립_스탠스가_아니면_예외가_발생한다() { + Stance stance = Stance.PROS; + + assertThatThrownBy( + () -> new TimeBasedTimeBoxDomain(stance, "자유발언", "비토", 120, 60)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.INVALID_TIME_BOX_STANCE.getMessage()); + } + + @Test + void 중립_스탠스면_예외가_발생하지_않는다() { + Stance stance = Stance.NEUTRAL; + + assertThatCode( + () -> new TimeBasedTimeBoxDomain(stance, "자유발언", "비토", 120, 60)) + .doesNotThrowAnyException(); + } + } + + @Nested + class ValidateTimes { + + @Test + void 팀_당_발언_시간이_양수이어야_한다() { + int timePerTeam = 0; + int timePerSpeaking = 1; + + assertThatThrownBy( + () -> new TimeBasedTimeBoxDomain(Stance.NEUTRAL, "자유발언", "비토", timePerTeam, timePerSpeaking)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.INVALID_TIME_BOX_TIME.getMessage()); + } + + @Test + void 팀_당_발언_시간이_비어있으면_안된다() { + Integer timePerTeam = null; + int timePerSpeaking = 1; + + assertThatThrownBy( + () -> new TimeBasedTimeBoxDomain(Stance.NEUTRAL, "자유발언", "비토", timePerTeam, timePerSpeaking)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.INVALID_TIME_BOX_TIME.getMessage()); + } + + @Test + void 회_당_시간이_양수이어야_한다() { + int timePerSpeaking = 0; + int timePerTeam = 1; + + assertThatThrownBy( + () -> new TimeBasedTimeBoxDomain(Stance.NEUTRAL, "자유발언", "비토", timePerTeam, timePerSpeaking)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.INVALID_TIME_BOX_TIME.getMessage()); + } + + @Test + void 회_당_발언_시간은_비어있을_수_있다() { + Integer timePerSpeaking = null; + int timePerTeam = 1; + + assertThatCode( + () -> new TimeBasedTimeBoxDomain(Stance.NEUTRAL, "자유발언", "비토", timePerTeam, timePerSpeaking)) + .doesNotThrowAnyException(); + } + + @Test + void 팀_당_발언시간은_회_당_발언시간보다_많거나_같아야_한다() { + int timePerTeam = 60; + int timePerSpeaking = timePerTeam + 1; + + assertThatThrownBy( + () -> new TimeBasedTimeBoxDomain(Stance.NEUTRAL, "자유발언", "비토", timePerTeam, timePerSpeaking)) + .isInstanceOf(DTClientErrorException.class) + .hasMessage(ClientErrorCode.INVALID_TIME_BASED_TIME.getMessage()); + } + } +}