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] ✨ feat : 장바구니 목록 취소 기능 구현 #42 #213

Merged
merged 1 commit into from
Sep 5, 2023
Merged
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
11 changes: 11 additions & 0 deletions Server/src/docs/asciidoc/snippets/video/cartvideo.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ include::{snippets}/video/changecartcancel/http-response.adoc[]
==== Response Fields
include::{snippets}/video/changecartcancel/response-fields.adoc[]

== 비디오 장바구니 목록 취소
=== HTTP Request
include::{snippets}/video/deletecarts/http-request.adoc[]
==== Request Headers
include::{snippets}/video/deletecarts/request-headers.adoc[]
==== Request Body
include::{snippets}/video/deletecarts/request-body.adoc[]
==== Request Fields
include::{snippets}/video/deletecarts/request-fields.adoc[]
=== HTTP Response
include::{snippets}/video/deletecarts/http-response.adoc[]



Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@
import com.server.domain.member.entity.Member;
import com.server.domain.video.entity.Video;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

import java.util.List;
import java.util.Optional;

public interface CartRepository extends JpaRepository<Cart, Long> {

Optional<Cart> findByMemberAndVideo(Member member, Video video);

@Query("delete Cart c " +
"where c.video.videoId in :videoIds " +
"and c.member = :member")
@Modifying
int deleteByMemberAndVideoIds(Member member, List<Long> videoIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,16 @@ public ResponseEntity<ApiSingleResponse<Boolean>> changeCart(
return ResponseEntity.ok(ApiSingleResponse.ok(isInCart, message));
}

@DeleteMapping("/carts")
public ResponseEntity<Void> deleteCarts(
@RequestBody @Valid VideoCartDeleteApiRequest request,
@LoginId Long loginMemberId) {

videoService.deleteCarts(loginMemberId, request.getVideoIds());

return ResponseEntity.noContent().build();
}

@DeleteMapping("/{video-id}")
public ResponseEntity<Void> deleteVideo(@PathVariable("video-id") @Positive(message = "{validation.positive}") Long videoId,
@LoginId Long loginMemberId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.server.domain.video.controller.dto.request;

import com.server.global.validation.EachNotBlank;
import com.server.global.validation.EachPositive;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Builder
public class VideoCartDeleteApiRequest {

@NotNull(message = "{validation.video.cart.videoIds}")
@EachPositive(message = "{validation.positive}")
@Size(min = 1, message = "{validation.video.cart.videoIds}")
public List<Long> videoIds;
}
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,14 @@ public Boolean changeCart(Long loginMemberId, Long videoId) {
return createOrDeleteCart(member, video);
}

@Transactional
public void deleteCarts(Long loginMemberId, List<Long> videoIds) {

Member member = verifiedMember(loginMemberId);

cartRepository.deleteByMemberAndVideoIds(member, videoIds);
}

@Transactional
public void deleteVideo(Long loginMemberId, Long videoId) {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.server.global.validation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EachPositiveValidator.class)
public @interface EachPositive {

String message() default "List contains not positive value";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.server.global.validation;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.List;

public class EachPositiveValidator implements ConstraintValidator<EachPositive, List<? extends Number>> {

@Override
public void initialize(EachPositive constraintAnnotation) {
}

@Override
public boolean isValid(List<? extends Number> list, ConstraintValidatorContext context) {
if (list == null) {
return true;
}

for (Number item : list) {
if (item == null || item.longValue() <= 0) {
return false;
}
}
return true;
}
}

1 change: 1 addition & 0 deletions Server/src/main/resources/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ validation.video.price=가격은 필수입니다.
validation.video.price.min=가격은 0원 이상이어야 합니다.
validation.video.categories=카테고리는 필수입니다.
validation.video.categories.size=카테고리는 {min}개 이상이어야 합니다.
validation.video.cart.videoIds=비디오 id 값은 필수입니다.

validation.announcement.content=공지사항 내용은 필수입니다.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;

import static org.assertj.core.api.Assertions.*;


Expand All @@ -26,6 +28,7 @@ void findByMemberAndVideo() {
Video video = createAndSaveVideo(channel);

Member loginMember = createAndSaveMember();
Channel loginMemberChannel = createAndSaveChannel(loginMember);

Cart cart = Cart.createCart(loginMember, video, video.getPrice());
em.persist(cart);
Expand All @@ -40,4 +43,39 @@ void findByMemberAndVideo() {
assertThat(findCart.getMember().getMemberId()).isEqualTo(loginMember.getMemberId());
assertThat(findCart.getVideo().getVideoId()).isEqualTo(video.getVideoId());
}

@Test
@DisplayName("member 와 videoId 리스트로 모든 cart 를 삭제한다.")
void deleteByMemberAndVideoIds() {
//given
Member owner = createAndSaveMember();
Channel channel = createAndSaveChannel(owner);
Video video1 = createAndSaveVideo(channel);
Video video2 = createAndSaveVideo(channel);
Video video3 = createAndSaveVideo(channel);

Member loginMember = createAndSaveMember();
Channel loginMemberChannel = createAndSaveChannel(loginMember);

Cart cart1 = Cart.createCart(loginMember, video1, video1.getPrice());
Cart cart2 = Cart.createCart(loginMember, video2, video2.getPrice());
Cart cart3 = Cart.createCart(loginMember, video3, video3.getPrice());
em.persist(cart1);
em.persist(cart2);
em.persist(cart3);

em.flush();
em.clear();

//when (cart 에 담긴 3개 중 2개만 삭제)
cartRepository.deleteByMemberAndVideoIds(
loginMember,
List.of(video1.getVideoId(), video2.getVideoId())
);

//then
assertThat(cartRepository.findAll()).hasSize(1)
.extracting("cartId")
.containsExactlyInAnyOrder(cart3.getCartId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,43 @@ void changeCartCancel() throws Exception {
);
}

@Test
@DisplayName("videoId 를 통한 장바구니 전체 취소 API")
void deleteCarts() throws Exception {
//given
VideoCartDeleteApiRequest request = VideoCartDeleteApiRequest.builder()
.videoIds(List.of(1L, 2L, 3L))
.build();

//when
ResultActions actions = mockMvc.perform(
delete(BASE_URL + "/carts")
.contentType(APPLICATION_JSON)
.header(AUTHORIZATION, TOKEN)
.content(objectMapper.writeValueAsString(request))
);

//then
actions.andDo(print())
.andExpect(status().isNoContent());

//restDocs
setConstraintClass(VideoCartDeleteApiRequest.class);

actions
.andDo(
documentHandler.document(
requestHeaders(
headerWithName(AUTHORIZATION).description("Access Token")
),
requestFields(
fieldWithPath("videoIds").description("장바구니에서 삭제할 비디오 ID 목록")
.attributes(getConstraint("videoIds"))
)
)
);
}

@Test
@DisplayName("비디오 삭제 API")
void deleteVideo() throws Exception {
Expand Down Expand Up @@ -1478,6 +1515,83 @@ void changeCartValidation() throws Exception {
.andExpect(jsonPath("$.data[0].reason").value("해당 값은 양수만 가능합니다."));
}

@TestFactory
@DisplayName("장바구니 삭제 시 validation 테스트")
Collection<DynamicTest> deleteCartsValidation() {
//given


return List.of(
dynamicTest("videoIds 가 Null 이면 검증에 실패한다.", ()-> {
//given
VideoCartDeleteApiRequest request = VideoCartDeleteApiRequest.builder()
.videoIds(null)
.build();

//when
ResultActions actions = mockMvc.perform(
delete(BASE_URL + "/carts")
.contentType(APPLICATION_JSON)
.header(AUTHORIZATION, TOKEN)
.content(objectMapper.writeValueAsString(request))
);

//then
actions.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.data[0].field").value("videoIds"))
.andExpect(jsonPath("$.data[0].value").value("null"))
.andExpect(jsonPath("$.data[0].reason").value("비디오 id 값은 필수입니다."));
}),
dynamicTest("videoIds 가 있지만 빈 배열이면 검증에 실패한다.", ()-> {
//given
VideoCartDeleteApiRequest request = VideoCartDeleteApiRequest.builder()
.videoIds(new ArrayList<>())
.build();

//when
ResultActions actions = mockMvc.perform(
delete(BASE_URL + "/carts")
.contentType(APPLICATION_JSON)
.header(AUTHORIZATION, TOKEN)
.content(objectMapper.writeValueAsString(request))
);

//then
actions.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.data[0].field").value("videoIds"))
.andExpect(jsonPath("$.data[0].value").value("[]"))
.andExpect(jsonPath("$.data[0].reason").value("비디오 id 값은 필수입니다."));
}),
dynamicTest("videoIds 가 양수가 아니면 검증에 실패한다.", ()-> {
//given
VideoCartDeleteApiRequest request = VideoCartDeleteApiRequest.builder()
.videoIds(List.of(0L, 1L))
.build();

//when
ResultActions actions = mockMvc.perform(
delete(BASE_URL + "/carts")
.contentType(APPLICATION_JSON)
.header(AUTHORIZATION, TOKEN)
.content(objectMapper.writeValueAsString(request))
);

//then
actions.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.data[0].field").value("videoIds"))
.andExpect(jsonPath("$.data[0].value").value("[0, 1]"))
.andExpect(jsonPath("$.data[0].reason").value("해당 값은 양수만 가능합니다."));
})




);
}

@Test
@DisplayName("비디오 삭제 시 validation 테스트 - videoId 가 양수가 아니면 검증에 실패한다.")
void deleteVideoValidation() throws Exception {
Expand Down
Loading