Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] 스터디 탈퇴 로직을 수정한다 #556

Open
wants to merge 1 commit into
base: BE/develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public class Round extends BaseEntity {

@Cascade(CascadeType.PERSIST)
@OnDelete(action = OnDeleteAction.CASCADE)
@OneToMany
@OneToMany(orphanRemoval = true)
@JoinColumn(name = "round_id", nullable = false)
private List<RoundOfMember> roundOfMembers = new ArrayList<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.yigongil.backend.domain.member.Member;
import com.yigongil.backend.domain.study.Study;
import java.util.List;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
Expand Down Expand Up @@ -59,4 +60,23 @@ public void completeRound() {
public boolean isMemberEquals(Member member) {
return this.member.equals(member);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof RoundOfMember roundOfMember)) {
return false;
}
if (id == null || roundOfMember.getId() == null) {
return false;
}
return id.equals(roundOfMember.getId());
}

@Override
public int hashCode() {
return Objects.hash(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -413,8 +413,19 @@ private boolean isAlreadyExist(Member member) {
}

public void exit(Member member) {
validateCanExitMemberSize();
rounds.forEach(round -> round.exit(member));
findStudyMemberBy(member).failStudy();
findStudyMemberBy(member).exit();
}

private void validateCanExitMemberSize() {
long studyMemberCount = studyMembers.stream()
.filter(StudyMember::isStudyMember)
.count();
if (studyMemberCount <= MIN_MEMBER_SIZE) {
throw new InvalidMemberSizeException("스터디 멤버 수가 2명 이하일 때는 탈퇴할 수 없습니다.",
(int) studyMemberCount);
}
}

private StudyMember findStudyMemberBy(Member member) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public enum Role {
STUDY_MEMBER(1),
APPLICANT(2),
NO_ROLE(3),
EXIT(4),
;

private final int code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ public boolean isMaster() {
return this.role == Role.MASTER;
}

public boolean isExit() {
return this.role == Role.EXIT;
}

public void completeSuccessfully() {
int successfulRoundCount = study.calculateSuccessfulRoundCount(member);
int defaultRoundExperience = EXPERIENCE_BASE_UNIT * 2;
Expand All @@ -94,7 +98,8 @@ public void completeSuccessfully() {
this.studyResult = StudyResult.SUCCESS;
}

public void failStudy() {
public void exit() {
this.role = Role.EXIT;
this.studyResult = StudyResult.FAIL;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import com.yigongil.backend.response.RoundResponse;
import com.yigongil.backend.response.StudyDetailResponse;
import com.yigongil.backend.response.StudyListItemResponse;
import com.yigongil.backend.response.StudyMemberResponse;
import com.yigongil.backend.response.UpcomingStudyResponse;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
Expand Down Expand Up @@ -451,24 +450,15 @@ public StudySteps(ObjectMapper objectMapper, SharedContext sharedContext) {
.when()
.delete("/studies/{studyId}/exit", studyId)
.then().log().all();

ExtractableResponse<Response> response = given().log().all()
.header(HttpHeaders.AUTHORIZATION, token)
.when()
.get("/studies/{studyId}/", studyId)
.then().log().all()
.extract();

sharedContext.setResponse(response);
}

@Then("{string} 이 {string} 스터디에 참여하지 않는다.")
public void 스터디에_참여하지_않는다(String githubId, String studyName) {
Long id = sharedContext.getId(githubId);

StudyDetailResponse response = sharedContext.getResponse().as(StudyDetailResponse.class);
MembersCertificationResponse response = sharedContext.getResponse().as(MembersCertificationResponse.class);

assertThat(response.members()).map(StudyMemberResponse::id).doesNotContain(id);
assertThat(response.others()).map(MemberCertificationResponse::id).doesNotContain(id);
}

@Then("{string}는 {string} 스터디 구성원에 포함되어 있지않다.")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.yigongil.backend.domain.round;

import static com.yigongil.backend.fixture.MemberFixture.폰노이만;
import static com.yigongil.backend.fixture.RoundOfMemberFixture.김진우_라오멤;
import static com.yigongil.backend.fixture.RoundOfMemberFixture.노이만_라오멤;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import com.yigongil.backend.domain.member.Member;
import com.yigongil.backend.exception.InvalidTodoLengthException;
import com.yigongil.backend.exception.NotStudyMasterException;
import com.yigongil.backend.fixture.RoundFixture;
import com.yigongil.backend.fixture.RoundOfMemberFixture;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -80,9 +82,9 @@ class 머스트두_진행률_계산 {
void 멤버들의_진행률을_계산한다() {
//given
Round round = RoundFixture.아이디없는_라운드.toRoundWithRoundOfMember(
RoundOfMemberFixture.노이만_라오멤,
RoundOfMemberFixture.노이만_라오멤,
RoundOfMemberFixture.노이만_라오멤
노이만_라오멤,
노이만_라오멤,
노이만_라오멤
);
round.getRoundOfMembers().get(0).completeRound();

Expand All @@ -97,9 +99,9 @@ class 머스트두_진행률_계산 {
void 머스트두를_완료한_멤버가_없다() {
//given
Round round = RoundFixture.아이디없는_라운드.toRoundWithRoundOfMember(
RoundOfMemberFixture.노이만_라오멤,
RoundOfMemberFixture.노이만_라오멤,
RoundOfMemberFixture.노이만_라오멤
노이만_라오멤,
노이만_라오멤,
노이만_라오멤
);

//when
Expand All @@ -109,4 +111,24 @@ class 머스트두_진행률_계산 {
assertThat(result).isZero();
}
}

@Nested
class 라운드_나가기 {
@Test
void 라운드를_나가면_RoundOfMember에서_사라진다() {
// given
Round round = RoundFixture.아이디없는_라운드.toRoundWithRoundOfMember(
김진우_라오멤,
노이만_라오멤
);

// when
round.exit(폰노이만.toMember());
boolean roundOfMemberExists = round.getRoundOfMembers().stream()
.anyMatch(roundOfMember -> roundOfMember.equals(노이만_라오멤));

// then
assertThat(roundOfMemberExists).isFalse();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

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.yigongil.backend.domain.member.Member;
import com.yigongil.backend.domain.round.Round;
import com.yigongil.backend.domain.studymember.StudyMember;
import com.yigongil.backend.exception.InvalidMemberSizeException;
import com.yigongil.backend.exception.InvalidProcessingStatusException;
import com.yigongil.backend.fixture.MemberFixture;
Expand Down Expand Up @@ -169,4 +171,50 @@ void setUp() {
assertThatThrownBy(() -> study.apply(member3))
.isInstanceOf(InvalidMemberSizeException.class);
}

@Nested
class 스터디_탈퇴 {
@Test
void 스터디_탈퇴() {
// given
Study study = StudyFixture.자바_스터디_모집중.toStudy();
Member master = MemberFixture.김진우.toMember();
Member member1 = MemberFixture.마틴파울러.toMember();
study.apply(member1);
study.permit(member1, master);

Member member2 = MemberFixture.폰노이만.toMember();
study.apply(member2);
study.permit(member2, master);

StudyMember studyMember1 = study.getStudyMembers().stream()
.filter(studyMember -> studyMember.getMember()
.equals(member1))
.findAny()
.get();
// when
study.exit(member1);

// then
assertAll(
() -> assertThat(study.sizeOfCurrentMembers()).isEqualTo(2),
() -> assertThat(studyMember1.isExit()).isTrue()
);

}

@Test
void 멤버의_수가_2명_이하면_예외가_발생한다() {
// given
Study study = StudyFixture.자바_스터디_모집중_정원_2.toStudy();
Member master = MemberFixture.김진우.toMember();
Member member = MemberFixture.마틴파울러.toMember();
study.apply(member);
study.permit(member, master);

// when, then
assertThatThrownBy(() -> study.exit(member))
.isInstanceOf(InvalidMemberSizeException.class);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,20 @@ public Round toRound() {
.id(id)
.mustDo(content)
.master(master)
.roundOfMembers(new ArrayList<>(List.of(RoundOfMemberFixture.김진우_라운드_삼.toRoundOfMember(), RoundOfMemberFixture.노이만_라오멤.toRoundOfMember())))
.roundOfMembers(new ArrayList<>(List.of(RoundOfMemberFixture.김진우_라오멤.toRoundOfMember(), RoundOfMemberFixture.노이만_라오멤.toRoundOfMember())))
.build();
}

public Round toRoundWithRoundOfMember(RoundOfMemberFixture... roundOfMemberFixtures) {
List<RoundOfMember> roundOfMembers = Arrays.stream(roundOfMemberFixtures)
.map(RoundOfMemberFixture::toRoundOfMember)
.toList();

return Round.builder()
.id(id)
.mustDo(content)
.master(master)
.roundOfMembers(roundOfMembers)
.roundOfMembers(new ArrayList<>(roundOfMembers))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

public enum RoundOfMemberFixture {

김진우_라운드_삼(1L, MemberFixture.김진우.toMember(), false),
김진우_라오멤(2L, MemberFixture.김진우.toMember(), false),
노이만_라오멤(1L, MemberFixture.폰노이만.toMember(), false),
;

Expand Down
19 changes: 19 additions & 0 deletions backend/src/test/resources/features/study-exit.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Feature: 스터디를 탈퇴한다.

Scenario: 스터디를 탈퇴한다.
Given "jinwoo"의 깃허브 아이디로 회원가입을 한다.
Given "jinwoo"가 제목-"자바1", 정원-"6"명, 최소 주차-"1"주, 주당 진행 횟수-"3"회, 소개-"스터디소개1"로 스터디를 개설한다.

Given "noiman"의 깃허브 아이디로 회원가입을 한다.
Given 깃허브 아이디가 "noiman"인 멤버가 이름이 "자바1"스터디에 신청한다.
Given "jinwoo"가 "noiman"의 "자바1" 스터디 신청을 수락한다.

Given "yujamint"의 깃허브 아이디로 회원가입을 한다.
Given 깃허브 아이디가 "yujamint"인 멤버가 이름이 "자바1"스터디에 신청한다.
Given "jinwoo"가 "yujamint"의 "자바1" 스터디 신청을 수락한다.

Given "jinwoo"가 이름이 "자바1"인 스터디를 "MONDAY"에 진행되도록 하여 시작한다.

When "noiman" 이 "자바1" 스터디에서 탈퇴한다.
When "jinwoo"가 "자바1" 스터디의 인증 목록을 조회한다.
Then "noiman" 이 "자바1" 스터디에 참여하지 않는다.
13 changes: 0 additions & 13 deletions backend/src/test/resources/features/study-progress.feature
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,3 @@ Feature: 스터디를 진행한다

When "noiman"가 마이페이지를 조회한다.
Then 조회한 멤버의 경험치가 상승했다.

Scenario: 스터디를 탈퇴한다.

Given "jinwoo"의 깃허브 아이디로 회원가입을 한다.
Given "jinwoo"가 제목-"자바1", 정원-"6"명, 최소 주차-"1"주, 주당 진행 횟수-"3"회, 소개-"스터디소개1"로 스터디를 개설한다.
Given "noiman"의 깃허브 아이디로 회원가입을 한다.
Given 깃허브 아이디가 "noiman"인 멤버가 이름이 "자바1"스터디에 신청한다.
Given "jinwoo"가 "noiman"의 "자바1" 스터디 신청을 수락한다.
Given "jinwoo"가 이름이 "자바1"인 스터디를 "MONDAY"에 진행되도록 하여 시작한다.

When "noiman" 이 "자바1" 스터디에서 탈퇴한다.

Then "noiman" 이 "자바1" 스터디에 참여하지 않는다.