Skip to content

Commit

Permalink
feat: 공모 참여 취소 기능 구현 (#318)
Browse files Browse the repository at this point in the history
* test: 공모 참여 취소 테스트코드 작성

* feat: 공모 참여 취소 기능 구현

* refactor: 불필요한 쿼리 메서드 제거

* style: 불필요한 개행 제거

* refactor: 모집중인 상태가 아닌 경우 공모 참여를 취소할 수 없도록 변경

* refactor: 공모 참여 취소 응답 상태 코드 변경

* refactor: 에러 메시지 명확한 문구로 변경

* refactor: query parameter를 적용해 어떤 공모의 참여를 취소할 것인지 의도를 명확하게 전달하도록 변경

* refactor: 총대 검증 메서드 네이밍 명확하게 변경
  • Loading branch information
fromitive authored Aug 14, 2024
1 parent cb48b12 commit 9f24972
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@ public CommentRoomStatus nextStatus() {
public boolean isGrouping() {
return this == GROUPING;
}

public boolean isInProgress() {
return this != GROUPING;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,14 @@ public OfferingEntity(MemberEntity member, String title, String description, Str
originPrice, roomStatus);
}

public void updateCurrentCount() {
public void participate() {
currentCount++;
}

public void leave() {
currentCount--;
}

public CommentRoomStatus moveStatus() {
this.roomStatus = roomStatus.nextStatus();
return this.roomStatus;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.net.URI;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -28,6 +29,14 @@ public ResponseEntity<Void> participate(
return ResponseEntity.created(URI.create("/participations/" + id)).build();
}

@DeleteMapping("/participations")
public ResponseEntity<Void> cancelParticipate(
@RequestParam(value = "offering-id") Long offeringId,
MemberEntity member) {
offeringMemberService.cancelParticipate(offeringId, member);
return ResponseEntity.noContent().build();
}

@GetMapping("/participants")
public ResponseEntity<ParticipantResponse> getAllParticipant(
@RequestParam(value = "offering-id") Long offeringId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ public enum OfferingMemberErrorCode implements ErrorResponse {
DUPLICATED(BAD_REQUEST, "이미 참여한 공모엔 참여할 수 없습니다."),
OFFERING_NOT_FOUND(BAD_REQUEST, "참여 공모가 존재하지 않습니다."),
PARTICIPANT_NOT_FOUND(BAD_REQUEST, "참여하지 않은 공모입니다."),
PROPOSER_NOT_FOUND(BAD_REQUEST, "총대를 찾을 수 없습니다.");
PROPOSER_NOT_FOUND(BAD_REQUEST, "총대를 찾을 수 없습니다."),
CANNOT_CANCEL_PROPOSER(BAD_REQUEST, "총대는 참여를 취소할 수 없습니다."),
CANNOT_CANCEL_IN_PROGRESS(BAD_REQUEST, "진행중인 공모는 참여를 취소할 수 없습니다.");

private final HttpStatus status;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
import com.zzang.chongdae.offering.repository.entity.OfferingEntity;
import com.zzang.chongdae.offeringmember.repository.entity.OfferingMemberEntity;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OfferingMemberRepository extends JpaRepository<OfferingMemberEntity, Long> {

int countByOffering(OfferingEntity offering);

Boolean existsByOfferingAndMember(OfferingEntity offering, MemberEntity member);

List<OfferingMemberEntity> findAllByOffering(OfferingEntity offering);

Optional<OfferingMemberEntity> findByOfferingAndMember(OfferingEntity offering, MemberEntity member);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.zzang.chongdae.global.exception.MarketException;
import com.zzang.chongdae.member.repository.entity.MemberEntity;
import com.zzang.chongdae.offering.domain.CommentRoomStatus;
import com.zzang.chongdae.offering.domain.OfferingStatus;
import com.zzang.chongdae.offering.exception.OfferingErrorCode;
import com.zzang.chongdae.offering.repository.OfferingRepository;
Expand Down Expand Up @@ -35,8 +36,9 @@ public Long participate(ParticipationRequest request, MemberEntity member) {

OfferingMemberEntity offeringMember = new OfferingMemberEntity(
member, offering, OfferingMemberRole.PARTICIPANT);

offeringMemberRepository.save(offeringMember);
offering.updateCurrentCount();
offering.participate();
return offeringMember.getId();
}

Expand All @@ -58,6 +60,36 @@ private void validateDuplicate(OfferingEntity offering, MemberEntity member) {
}
}

@Transactional
public void cancelParticipate(Long offeringId, MemberEntity member) {
OfferingEntity offering = offeringRepository.findById(offeringId)
.orElseThrow(() -> new MarketException(OfferingErrorCode.NOT_FOUND));
OfferingMemberEntity offeringMember = offeringMemberRepository.findByOfferingAndMember(offering, member)
.orElseThrow(() -> new MarketException(OfferingMemberErrorCode.PARTICIPANT_NOT_FOUND));
validateCancel(offeringMember);
offeringMemberRepository.delete(offeringMember);
offering.leave();
}

private void validateCancel(OfferingMemberEntity offeringMember) {
validateIsProposer(offeringMember);
validateInProgress(offeringMember);
}

private void validateIsProposer(OfferingMemberEntity offeringMember) {
if (offeringMember.isProposer()) {
throw new MarketException(OfferingMemberErrorCode.CANNOT_CANCEL_PROPOSER);
}
}

private void validateInProgress(OfferingMemberEntity offeringMember) {
OfferingEntity offering = offeringMember.getOffering();
CommentRoomStatus roomStatus = offering.getRoomStatus();
if (roomStatus.isInProgress()) {
throw new MarketException(OfferingMemberErrorCode.CANNOT_CANCEL_IN_PROGRESS);
}
}

public ParticipantResponse getAllParticipant(Long offeringId, MemberEntity member) {
OfferingEntity offering = offeringRepository.findById(offeringId)
.orElseThrow(() -> new MarketException(OfferingErrorCode.NOT_FOUND));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class OfferingFixture {
@Autowired
private OfferingRepository offeringRepository;

public OfferingEntity createOffering(MemberEntity member) {
public OfferingEntity createOffering(MemberEntity member, CommentRoomStatus commentRoomStatus) {
OfferingEntity offering = new OfferingEntity(
member,
"title",
Expand All @@ -30,8 +30,14 @@ public OfferingEntity createOffering(MemberEntity member) {
false,
5000,
1000,
CommentRoomStatus.GROUPING
commentRoomStatus
);
return offeringRepository.save(offering);
}

public OfferingEntity createOffering(MemberEntity member) {
return createOffering(member, CommentRoomStatus.GROUPING);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public OfferingMemberEntity createParticipant(MemberEntity member, OfferingEntit
);
OfferingMemberEntity result = offeringMemberRepository.save(offeringMember);
offering = offeringRepository.findById(offering.getId()).orElseThrow();
offering.updateCurrentCount();
offering.participate();
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.epages.restdocs.apispec.ResourceSnippetParameters;
import com.zzang.chongdae.global.integration.IntegrationTest;
import com.zzang.chongdae.member.repository.entity.MemberEntity;
import com.zzang.chongdae.offering.domain.CommentRoomStatus;
import com.zzang.chongdae.offering.repository.entity.OfferingEntity;
import com.zzang.chongdae.offeringmember.service.dto.ParticipationRequest;
import io.restassured.RestAssured;
Expand Down Expand Up @@ -121,6 +122,101 @@ void should_throwException_when_emptyValue() {
}
}

@DisplayName("공모 참여 취소")
@Nested
class CancelParticipate {

List<ParameterDescriptorWithType> queryParameterDescriptors = List.of(
parameterWithName("offering-id").description("공모 id (필수)")
);
ResourceSnippetParameters successSnippets = ResourceSnippetParameters.builder()
.summary("공모 참여 취소")
.description("공모 참여를 취소합니다.")
.queryParameters(queryParameterDescriptors)
.requestSchema(schema("CancelParticipateSuccessRequest"))
.responseSchema(schema("CancelParticipateSuccessResponse"))
.build();
ResourceSnippetParameters failSnippets = ResourceSnippetParameters.builder()
.summary("공모 참여 취소")
.description("공모 참여를 취소합니다.")
.queryParameters(queryParameterDescriptors)
.responseFields(failResponseDescriptors)
.requestSchema(schema("CancelParticipateFailRequest"))
.responseSchema(schema("CancelParticipateFailResponse"))
.build();
MemberEntity proposer;
MemberEntity participant;
OfferingEntity offeringWithGrouping;
OfferingEntity offeringWithInProgress;
OfferingEntity offeringNotParticipated;

@BeforeEach
void setUp() {
proposer = memberFixture.createMember();
participant = memberFixture.createMember("poke");

offeringWithGrouping = offeringFixture.createOffering(proposer);
offeringMemberFixture.createProposer(proposer, offeringWithGrouping);
offeringMemberFixture.createParticipant(participant, offeringWithGrouping);

offeringWithInProgress = offeringFixture.createOffering(proposer, CommentRoomStatus.BUYING);
offeringMemberFixture.createProposer(proposer, offeringWithInProgress);
offeringMemberFixture.createParticipant(participant, offeringWithInProgress);

offeringNotParticipated = offeringFixture.createOffering(proposer);
offeringMemberFixture.createProposer(proposer, offeringNotParticipated);

}

@DisplayName("공모 참여 취소를 할 수 있다.")
@Test
void should_cancelSuccess() {
RestAssured.given(spec).log().all()
.filter(document("cancel-success", resource(successSnippets)))
.cookies(cookieProvider.createCookiesWithCi("poke5678"))
.queryParam("offering-id", offeringWithGrouping.getId())
.when().delete("/participations")
.then().log().all()
.statusCode(204);
}

@DisplayName("공모자는 본인이 만든 공모 참여를 취소할 수 없다.")
@Test
void should_throwException_when_cancelProposer() {
RestAssured.given(spec).log().all()
.filter(document("cancel-fail-offering-proposer", resource(failSnippets)))
.cookies(cookieProvider.createCookies())
.queryParam("offering-id", offeringWithGrouping.getId())
.when().delete("/participations")
.then().log().all()
.statusCode(400);
}

@DisplayName("거래가 진행 중인 공모는 참여를 취소할 수 없다.")
@Test
void should_throwException_when_cancelInProgress() {
RestAssured.given(spec).log().all()
.filter(document("cancel-fail-offering-in-progress", resource(failSnippets)))
.cookies(cookieProvider.createCookiesWithCi("poke5678"))
.queryParam("offering-id", offeringWithInProgress.getId())
.when().delete("/participations")
.then().log().all()
.statusCode(400);
}

@DisplayName("참여하지 않은 공모는 취소할 수 없다.")
@Test
void should_throwException_when_cancelNotParticipated() {
RestAssured.given(spec).log().all()
.filter(document("cancel-fail-offering-not-participated", resource(failSnippets)))
.cookies(cookieProvider.createCookiesWithCi("poke5678"))
.queryParam("offering-id", offeringNotParticipated.getId())
.when().delete("/participations")
.then().log().all()
.statusCode(400);
}
}

@DisplayName("공모 참여자 목록 조회")
@Nested
class OfferingParticipants {
Expand Down

0 comments on commit 9f24972

Please sign in to comment.