From 17a13290410e81fa6d2ae628504508c6375c401d Mon Sep 17 00:00:00 2001 From: dohyoungK Date: Tue, 19 Sep 2023 17:37:57 +0900 Subject: [PATCH 001/129] =?UTF-8?q?[BE]=20=E2=99=BB=EF=B8=8F=20Refactor=20?= =?UTF-8?q?:=20Security=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../growstory/global/auth/filter/JwtVerificationFilter.java | 5 ++--- .../java/com/growstory/global/auth/jwt/JwtTokenizer.java | 2 +- .../com/growstory/global/email/service/EmailService.java | 3 ++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/growstory/global/auth/filter/JwtVerificationFilter.java b/server/src/main/java/com/growstory/global/auth/filter/JwtVerificationFilter.java index 68b4bf47..a1f10f90 100644 --- a/server/src/main/java/com/growstory/global/auth/filter/JwtVerificationFilter.java +++ b/server/src/main/java/com/growstory/global/auth/filter/JwtVerificationFilter.java @@ -69,8 +69,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse // 이걸 저장할 때 설정 Date accessTokenExpiration = new Date((Long) accessTokenClaims.get("exp") * 1000L); Date refreshTokenExpiration = new Date((Long) refreshTokenClaims.get("exp") * 1000L); - System.out.println("before:" + accessTokenExpiration); - System.out.println("before:" + refreshTokenExpiration); +// System.out.println("before:" + accessTokenExpiration); +// System.out.println("before:" + refreshTokenExpiration); Date now = new Date(); // accessToken 만료시간이 지금보다 이전이면(accessToken 만료 O), refreshToken 만료시간이 지금보다 이후라면(refreshToken 만료 X) @@ -88,7 +88,6 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse Map recreatedAccessTokenClaims = verifyJws(accessToken); // JWT 검증 verifyJws(refreshToken); setAuthenticationToContext(recreatedAccessTokenClaims); - //jwt 검증에 실패할 경우 발생하는 예외를 HttpServletRequest의 속성(Attribute)으로 추가 } catch (SignatureException se) { request.setAttribute("exception", se); diff --git a/server/src/main/java/com/growstory/global/auth/jwt/JwtTokenizer.java b/server/src/main/java/com/growstory/global/auth/jwt/JwtTokenizer.java index 53a9f5ce..462a6bbf 100644 --- a/server/src/main/java/com/growstory/global/auth/jwt/JwtTokenizer.java +++ b/server/src/main/java/com/growstory/global/auth/jwt/JwtTokenizer.java @@ -71,7 +71,7 @@ public Jws getClaims(String jws, String base64EncodedSecretKey) { .build() .parseClaimsJws(jws); // 토큰의 유효성 검사 - System.out.println("after:" + claims.getBody().getExpiration()); +// System.out.println("after:" + claims.getBody().getExpiration()); return claims; } diff --git a/server/src/main/java/com/growstory/global/email/service/EmailService.java b/server/src/main/java/com/growstory/global/email/service/EmailService.java index 8d687514..0e3df115 100644 --- a/server/src/main/java/com/growstory/global/email/service/EmailService.java +++ b/server/src/main/java/com/growstory/global/email/service/EmailService.java @@ -24,6 +24,7 @@ public class EmailService { private final AccountRepository accountRepository; private final PasswordEncoder passwordEncoder; + // 부하 테스트 때 비동기 처리 public EmailDto.SignUpResponse sendAuthCodeMail(EmailDto.Post emailPostDto) { String authCode = getAuthCode(); MimeMessage mimeMessage = mailSender.createMimeMessage(); @@ -74,7 +75,7 @@ public EmailDto.PasswordResponse sendPasswordMail(EmailDto.Post emailPostDto) { // 인증 번호 겸 임시 비밀번호 private String getAuthCode() { Random random = new Random(); - StringBuffer authCode = new StringBuffer(); + StringBuilder authCode = new StringBuilder(); for (int i = 0; i < 8; i++) { int index = random.nextInt(4); From 61b2af14099101f9d6c7274239e123097feb7ffe Mon Sep 17 00:00:00 2001 From: LST Date: Thu, 21 Sep 2023 16:13:32 +0900 Subject: [PATCH 002/129] =?UTF-8?q?:recycle:=20Refactor:=20=EC=9D=B4?= =?UTF-8?q?=EB=A9=94=EC=9D=BC=20=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A02?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/email/service/EmailService.java | 75 ++++++++++++------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/server/src/main/java/com/growstory/global/email/service/EmailService.java b/server/src/main/java/com/growstory/global/email/service/EmailService.java index f232d8db..a68d74bc 100644 --- a/server/src/main/java/com/growstory/global/email/service/EmailService.java +++ b/server/src/main/java/com/growstory/global/email/service/EmailService.java @@ -4,6 +4,7 @@ import com.growstory.domain.account.repository.AccountRepository; import com.growstory.global.email.dto.EmailDto; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.scheduling.annotation.Async; @@ -16,10 +17,12 @@ import javax.mail.internet.MimeMessage; import java.util.Optional; import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; -@RequiredArgsConstructor -@Async @Service +@Slf4j +@RequiredArgsConstructor public class EmailService { private final JavaMailSender mailSender; private final SpringTemplateEngine templateEngine; @@ -27,52 +30,72 @@ public class EmailService { private final PasswordEncoder passwordEncoder; public EmailDto.SignUpResponse sendAuthCodeMail(EmailDto.Post emailPostDto) { - String authCode = getAuthCode(); - MimeMessage mimeMessage = mailSender.createMimeMessage(); - try { - MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8"); - mimeMessageHelper.setTo(emailPostDto.getEmail()); // 수신 이메일 - mimeMessageHelper.setSubject("[GrowStory] 회원가입 인증번호 안내"); // 이메일 제목 - mimeMessageHelper.setText(setContext(authCode, "authCode"), true); // 이메일 본문 - mailSender.send(mimeMessage); - } catch (MessagingException e) { + String authCode = getAuthCode(); + + CompletableFuture.runAsync(() -> { + String finalText = setContext(authCode, "authCode"); + try { + MimeMessage mimeMessage = mailSender.createMimeMessage(); + MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8"); + + mimeMessageHelper.setTo(emailPostDto.getEmail()); // 수신 이메일 + mimeMessageHelper.setSubject("[GrowStory] 회원가입 인증번호 안내"); // 이메일 제목 + mimeMessageHelper.setText(finalText, true); // 이메일 본문 + + mailSender.send(mimeMessage); + } catch (MessagingException e) { + throw new RuntimeException(e); + } + }); + + return EmailDto.SignUpResponse.builder() + .authCode(authCode) + .build(); + } catch (Exception e) { + // 예외 처리 throw new RuntimeException(e); } - - return EmailDto.SignUpResponse.builder() - .authCode(authCode) - .build(); } public EmailDto.PasswordResponse sendPasswordMail(EmailDto.Post emailPostDto) { - Optional optionalAccount = accountRepository.findByEmail(emailPostDto - .getEmail()); + Optional optionalAccount = accountRepository.findByEmail(emailPostDto.getEmail()); + + if (optionalAccount.isEmpty()) { + return EmailDto.PasswordResponse.builder() + .isMatched(false) + .build(); + } + + + Account findAccount = optionalAccount.get(); String password = getAuthCode(); - MimeMessage mimeMessage = mailSender.createMimeMessage(); - optionalAccount.ifPresent(findAccount -> { + CompletableFuture.runAsync(() -> { try { + MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8"); + mimeMessageHelper.setTo(emailPostDto.getEmail()); // 수신 이메일 mimeMessageHelper.setSubject("[GrowStory] 임시 비밀번호 안내"); // 이메일 제목 mimeMessageHelper.setText(setContext(password, "password"), true); // 이메일 본문 mailSender.send(mimeMessage); + + // password encode 필요 + accountRepository.save(findAccount.toBuilder() + .password(passwordEncoder.encode(password)) + .build()); } catch (MessagingException e) { throw new RuntimeException(e); } - - // password encode 필요 - accountRepository.save(findAccount.toBuilder() - .password(passwordEncoder.encode(password)) - .build()); }); return EmailDto.PasswordResponse.builder() - .isMatched(optionalAccount.isPresent()) + .isMatched(true) .build(); } + // 인증 번호 겸 임시 비밀번호 private String getAuthCode() { Random random = new Random(); @@ -96,4 +119,4 @@ private String setContext(String code, String type) { context.setVariable(type, code); return templateEngine.process(type, context); } -} +} \ No newline at end of file From 6f13580ed65f5dd28a8bf567e414c96769b89e11 Mon Sep 17 00:00:00 2001 From: LST Date: Thu, 21 Sep 2023 16:13:32 +0900 Subject: [PATCH 003/129] =?UTF-8?q?:bug:=20Fix:=20=EC=98=A4=EB=B8=8C?= =?UTF-8?q?=EC=A0=9D=ED=8A=B8=20=EB=B0=B0=EC=B9=98=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plant_object/service/PlantObjService.java | 6 +- .../global/email/service/EmailService.java | 75 ++++++++++++------- 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/server/src/main/java/com/growstory/domain/plant_object/service/PlantObjService.java b/server/src/main/java/com/growstory/domain/plant_object/service/PlantObjService.java index 871e8c21..0bced48e 100644 --- a/server/src/main/java/com/growstory/domain/plant_object/service/PlantObjService.java +++ b/server/src/main/java/com/growstory/domain/plant_object/service/PlantObjService.java @@ -130,9 +130,9 @@ public void saveLocation(Long accountId, List patchLo .forEach(patchLocationDto -> { LocationDto.Patch locationPatchDto = patchLocationDto.getLocationDto(); //프로덕트 id와 로케이션 id가 일치하지 않으면 예외 발생 - if(patchLocationDto.getPlantObjId()!=locationPatchDto.getLocationId()) { - throw new BusinessLogicException(ExceptionCode.LOCATION_NOT_ALLOW); - } +// if(patchLocationDto.getPlantObjId()!=locationPatchDto.getLocationId()) { +// throw new BusinessLogicException(ExceptionCode.LOCATION_NOT_ALLOW); +// } if(locationPatchDto.getX()<0 || locationPatchDto.getX()>11 || locationPatchDto.getY()<0 || locationPatchDto.getY()>7) { throw new BusinessLogicException(ExceptionCode.INVALID_LOCATION); diff --git a/server/src/main/java/com/growstory/global/email/service/EmailService.java b/server/src/main/java/com/growstory/global/email/service/EmailService.java index f232d8db..a68d74bc 100644 --- a/server/src/main/java/com/growstory/global/email/service/EmailService.java +++ b/server/src/main/java/com/growstory/global/email/service/EmailService.java @@ -4,6 +4,7 @@ import com.growstory.domain.account.repository.AccountRepository; import com.growstory.global.email.dto.EmailDto; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.scheduling.annotation.Async; @@ -16,10 +17,12 @@ import javax.mail.internet.MimeMessage; import java.util.Optional; import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; -@RequiredArgsConstructor -@Async @Service +@Slf4j +@RequiredArgsConstructor public class EmailService { private final JavaMailSender mailSender; private final SpringTemplateEngine templateEngine; @@ -27,52 +30,72 @@ public class EmailService { private final PasswordEncoder passwordEncoder; public EmailDto.SignUpResponse sendAuthCodeMail(EmailDto.Post emailPostDto) { - String authCode = getAuthCode(); - MimeMessage mimeMessage = mailSender.createMimeMessage(); - try { - MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8"); - mimeMessageHelper.setTo(emailPostDto.getEmail()); // 수신 이메일 - mimeMessageHelper.setSubject("[GrowStory] 회원가입 인증번호 안내"); // 이메일 제목 - mimeMessageHelper.setText(setContext(authCode, "authCode"), true); // 이메일 본문 - mailSender.send(mimeMessage); - } catch (MessagingException e) { + String authCode = getAuthCode(); + + CompletableFuture.runAsync(() -> { + String finalText = setContext(authCode, "authCode"); + try { + MimeMessage mimeMessage = mailSender.createMimeMessage(); + MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8"); + + mimeMessageHelper.setTo(emailPostDto.getEmail()); // 수신 이메일 + mimeMessageHelper.setSubject("[GrowStory] 회원가입 인증번호 안내"); // 이메일 제목 + mimeMessageHelper.setText(finalText, true); // 이메일 본문 + + mailSender.send(mimeMessage); + } catch (MessagingException e) { + throw new RuntimeException(e); + } + }); + + return EmailDto.SignUpResponse.builder() + .authCode(authCode) + .build(); + } catch (Exception e) { + // 예외 처리 throw new RuntimeException(e); } - - return EmailDto.SignUpResponse.builder() - .authCode(authCode) - .build(); } public EmailDto.PasswordResponse sendPasswordMail(EmailDto.Post emailPostDto) { - Optional optionalAccount = accountRepository.findByEmail(emailPostDto - .getEmail()); + Optional optionalAccount = accountRepository.findByEmail(emailPostDto.getEmail()); + + if (optionalAccount.isEmpty()) { + return EmailDto.PasswordResponse.builder() + .isMatched(false) + .build(); + } + + + Account findAccount = optionalAccount.get(); String password = getAuthCode(); - MimeMessage mimeMessage = mailSender.createMimeMessage(); - optionalAccount.ifPresent(findAccount -> { + CompletableFuture.runAsync(() -> { try { + MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8"); + mimeMessageHelper.setTo(emailPostDto.getEmail()); // 수신 이메일 mimeMessageHelper.setSubject("[GrowStory] 임시 비밀번호 안내"); // 이메일 제목 mimeMessageHelper.setText(setContext(password, "password"), true); // 이메일 본문 mailSender.send(mimeMessage); + + // password encode 필요 + accountRepository.save(findAccount.toBuilder() + .password(passwordEncoder.encode(password)) + .build()); } catch (MessagingException e) { throw new RuntimeException(e); } - - // password encode 필요 - accountRepository.save(findAccount.toBuilder() - .password(passwordEncoder.encode(password)) - .build()); }); return EmailDto.PasswordResponse.builder() - .isMatched(optionalAccount.isPresent()) + .isMatched(true) .build(); } + // 인증 번호 겸 임시 비밀번호 private String getAuthCode() { Random random = new Random(); @@ -96,4 +119,4 @@ private String setContext(String code, String type) { context.setVariable(type, code); return templateEngine.process(type, context); } -} +} \ No newline at end of file From 615d5c2a81adb229765e10e6e8be42c81fa4a386 Mon Sep 17 00:00:00 2001 From: LST Date: Mon, 25 Sep 2023 13:44:23 +0900 Subject: [PATCH 004/129] =?UTF-8?q?:sparkles:=20Feat:=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=A7=80=EA=B8=89=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../point/controller/PointController.java | 45 +++++++++++++++++++ .../domain/point/service/PointService.java | 37 ++++++++++++++- .../src/main/resources/application-prod.yml | 3 ++ 3 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 server/src/main/java/com/growstory/domain/point/controller/PointController.java diff --git a/server/src/main/java/com/growstory/domain/point/controller/PointController.java b/server/src/main/java/com/growstory/domain/point/controller/PointController.java new file mode 100644 index 00000000..9dbcff1e --- /dev/null +++ b/server/src/main/java/com/growstory/domain/point/controller/PointController.java @@ -0,0 +1,45 @@ +package com.growstory.domain.point.controller; + +import com.growstory.domain.point.service.PointService; +import com.growstory.global.response.SingleResponseDto; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.constraints.Positive; + +@RequiredArgsConstructor +@RequestMapping("/v1/points") +@RestController +public class PointController { + + private final PointService pointService; + + @Operation(summary = "Patch All Event Point", description = "Points awarded through events to everyone") + @PatchMapping("/all") + public ResponseEntity patchAllEventPoints(@Positive @RequestParam("point") int pointScore, + @RequestParam("event-key") String eventKey) { + + pointService.updateAllEventPoint(pointScore, eventKey); + + return ResponseEntity.noContent().build(); + } + + @Operation(summary = "Patch Someone Event Point", description = "Points awarded through events to someone") + @PatchMapping + public ResponseEntity patchEventPoint( + @Positive @RequestParam("account-id") Long accountId, + @Positive @RequestParam("point") int point, + @RequestParam("event-key")String eventKey) { + + pointService.updateEventPoint(accountId, point, eventKey); + + return ResponseEntity.noContent().build(); + } +} diff --git a/server/src/main/java/com/growstory/domain/point/service/PointService.java b/server/src/main/java/com/growstory/domain/point/service/PointService.java index a296af5d..e56ef510 100644 --- a/server/src/main/java/com/growstory/domain/point/service/PointService.java +++ b/server/src/main/java/com/growstory/domain/point/service/PointService.java @@ -1,29 +1,40 @@ package com.growstory.domain.point.service; +import com.growstory.domain.account.entity.Account; +import com.growstory.domain.account.repository.AccountRepository; +import com.growstory.domain.account.service.AccountService; import com.growstory.domain.point.entity.Point; import com.growstory.domain.point.repository.PointRepository; import com.growstory.global.exception.BusinessLogicException; import com.growstory.global.exception.ExceptionCode; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Transactional -@Service @RequiredArgsConstructor +@Slf4j +@Service public class PointService { @Value("${mail.admin.address}") private String adminMailAddress; private static final int ADMIN_POINT = 100000000; - private static final int REGISTER_POINT = 500; // 회원 가입시 지급 포인트 + private static final int REGISTER_POINT = 2500; // 회원 가입시 지급 포인트 private static final int POSTING_POINT = 30; // 게시글 등록시 지급 포인트 private static final int DAILY_LOGIN_POINT = 10; // 일일 로그인시 지급 포인트 (출석 체크 창이나 버튼이 필요, 로그인을 지속하며 날짜가 갱신되었을 때 판별이 어려워보임) private static final int JOURNAL_WRITTEN_POINT = 10; // 저널 작성 포인트 + @Value("${event.key}") + private String EVENT_KEY; + private final PointRepository pointRepository; + private final AccountRepository accountRepository; public Point createPoint(String email) { // pointRepo.save 없이 account의 cascade로 자동 저장됨(account가 삭제되면 자동 삭제) @@ -70,4 +81,26 @@ public Point updatePoint(Point presentPoint, int updateScore) { .score(presentPoint.getScore() + updateScore) .build()); } + + // 계정 전원 포인트 지급 + public void updateAllEventPoint(int updateScore, String eventKey) { + if(!EVENT_KEY.equals(eventKey)) { + log.info("eventError"); + return; + } + + List findPoints = pointRepository.findAll(); + findPoints + .forEach(point -> updatePoint(point, updateScore)); + } + + // 특정 계정 포인트 지급 + public void updateEventPoint(Long accountId, Integer updateScore, String eventKey) { + if(!EVENT_KEY.equals(eventKey)) { + log.info("eventError"); + return; + } + Account findAccount = accountRepository.findById(accountId).get(); + updatePoint(findAccount.getPoint(), updateScore); + } } diff --git a/server/src/main/resources/application-prod.yml b/server/src/main/resources/application-prod.yml index ef78e06f..282ae3cf 100644 --- a/server/src/main/resources/application-prod.yml +++ b/server/src/main/resources/application-prod.yml @@ -89,6 +89,9 @@ my: scheduled: cron: 0 0 0 ? * MON +event: + key: ${EVENT_KEY} + server: port: 443 ssl: From 4416b7ad6906d31391212da41c47d9e466a1889f Mon Sep 17 00:00:00 2001 From: LST Date: Mon, 25 Sep 2023 14:00:53 +0900 Subject: [PATCH 005/129] =?UTF-8?q?:rocket:=20Deploy:=20CI/CD=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/server.yml | 10 ++++++---- server/Dockerfile | 6 ++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/server.yml b/.github/workflows/server.yml index 83b1a9cd..37f5ff55 100644 --- a/.github/workflows/server.yml +++ b/.github/workflows/server.yml @@ -22,6 +22,7 @@ jobs: G_CLIENT_SECRET: ${{secrets.G_CLIENT_SECRET}} JWT_SECRET_KEY: ${{secrets.JWT_SECRET_KEY}} KEY_STORE_PASSWORD: ${{secrets.KEY_STORE_PASSWORD}} + EVENT_KEY: ${{secrets.EVENT_KEY}} working-directory: ./server steps: @@ -57,9 +58,10 @@ jobs: --build-arg G_CLIENT_SECRET="${{env.G_CLIENT_SECRET}}" \ --build-arg JWT_SECRET_KEY="${{env.JWT_SECRET_KEY}}" \ --build-arg KEY_STORE_PASSWORD="${{env.KEY_STORE_PASSWORD}}" \ + --build-arg EVENT_KEY="${{env.EVENT_KEY}}" \ -t growstory-cicd . - docker tag growstory-cicd dokyung94/growstory-cicd:${GITHUB_SHA::7} - docker push dokyung94/growstory-cicd:${GITHUB_SHA::7} + docker tag growstory-cicd leest/growstory-cicd:${GITHUB_SHA::7} + docker push leest/growstory-cicd:${GITHUB_SHA::7} working-directory: ${{ env.working-directory }} - name: Configure AWS credentials @@ -78,6 +80,6 @@ jobs: envs: GITHUB_SHA script: | sudo docker rm -f server - sudo docker pull dokyung94/growstory-cicd:${GITHUB_SHA::7} - sudo docker tag dokyung94/growstory-cicd:${GITHUB_SHA::7} growstory-cicd + sudo docker pull leest/growstory-cicd:${GITHUB_SHA::7} + sudo docker tag leest/growstory-cicd:${GITHUB_SHA::7} growstory-cicd sudo docker run -d --name server -e TZ=Asia/Seoul -p 443:443 growstory-cicd diff --git a/server/Dockerfile b/server/Dockerfile index 7e1571b5..cb76bab5 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -14,7 +14,8 @@ ARG MYSQL_USER \ GROWSTORY_EMAIL_PASSWORD \ GROWSTORY_EMAIL_USERNAME \ JWT_SECRET_KEY \ - KEY_STORE_PASSWORD + KEY_STORE_PASSWORD \ + EVENT_KEY # ⭐ 'ENV' 예약어를 통해 전달받은 값을 실제 값과 매칭시켜야 한다. ENV MYSQL_USER=${MYSQL_USER} \ @@ -29,7 +30,8 @@ ENV MYSQL_USER=${MYSQL_USER} \ GROWSTORY_EMAIL_PASSWORD=${GROWSTORY_EMAIL_PASSWORD} \ GROWSTORY_EMAIL_USERNAME=${GROWSTORY_EMAIL_USERNAME} \ JWT_SECRET_KEY=${JWT_SECRET_KEY} \ - KEY_STORE_PASSWORD=${KEY_STORE_PASSWORD} + KEY_STORE_PASSWORD=${KEY_STORE_PASSWORD} \ + EVENT_KEY=${EVENT_KEY} # (2) COPY에서 사용될 경로 변수 ARG JAR_FILE=build/libs/*-SNAPSHOT.jar From 5c2e7d736d694d6b9f3bce394e33ce2f7469be85 Mon Sep 17 00:00:00 2001 From: LST Date: Tue, 26 Sep 2023 08:24:52 +0900 Subject: [PATCH 006/129] =?UTF-8?q?:test=5Ftube:=20Test:=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EB=B0=B1=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../growstory/GrowstoryApplicationTests.java | 9 + .../controller/AccountControllerTest.java | 339 ++++++++++ .../account/service/AccountServiceTest.java | 617 ++++++++++++++++++ .../board/controller/BoardControllerTest.java | 77 +++ .../board/service/BoardRankingTest.java | 63 ++ .../board/service/BoardServiceTest.java | 192 ++++++ .../controller/CommentControllerTest.java | 127 ++++ .../domain/journal/JournalControllerTest.java | 64 ++ .../leaf/controller/LeafControllerTest.java | 191 ++++++ .../domain/leaf/service/LeafServiceTest.java | 408 ++++++++++++ .../controller/PlantObjectControllerTest.java | 160 +++++ .../service/PlantObjServiceTest.java | 407 ++++++++++++ .../com/growstory/domain/stubdata/Stub.java | 499 ++++++++++++++ .../annotation/WithMockCustomUser.java | 19 + ...hMockCustomUserSecurityContextFactory.java | 46 ++ 15 files changed, 3218 insertions(+) create mode 100644 server/src/test/java/com/growstory/GrowstoryApplicationTests.java create mode 100644 server/src/test/java/com/growstory/domain/account/controller/AccountControllerTest.java create mode 100644 server/src/test/java/com/growstory/domain/account/service/AccountServiceTest.java create mode 100644 server/src/test/java/com/growstory/domain/board/controller/BoardControllerTest.java create mode 100644 server/src/test/java/com/growstory/domain/board/service/BoardRankingTest.java create mode 100644 server/src/test/java/com/growstory/domain/board/service/BoardServiceTest.java create mode 100644 server/src/test/java/com/growstory/domain/comment/controller/CommentControllerTest.java create mode 100644 server/src/test/java/com/growstory/domain/journal/JournalControllerTest.java create mode 100644 server/src/test/java/com/growstory/domain/leaf/controller/LeafControllerTest.java create mode 100644 server/src/test/java/com/growstory/domain/leaf/service/LeafServiceTest.java create mode 100644 server/src/test/java/com/growstory/domain/plant_object/controller/PlantObjectControllerTest.java create mode 100644 server/src/test/java/com/growstory/domain/plant_object/service/PlantObjServiceTest.java create mode 100644 server/src/test/java/com/growstory/domain/stubdata/Stub.java create mode 100644 server/src/test/java/com/growstory/global/customUser/annotation/WithMockCustomUser.java create mode 100644 server/src/test/java/com/growstory/global/customUser/factory/WithMockCustomUserSecurityContextFactory.java diff --git a/server/src/test/java/com/growstory/GrowstoryApplicationTests.java b/server/src/test/java/com/growstory/GrowstoryApplicationTests.java new file mode 100644 index 00000000..f0a1353d --- /dev/null +++ b/server/src/test/java/com/growstory/GrowstoryApplicationTests.java @@ -0,0 +1,9 @@ +package com.growstory; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class GrowstoryApplicationTests { + +} diff --git a/server/src/test/java/com/growstory/domain/account/controller/AccountControllerTest.java b/server/src/test/java/com/growstory/domain/account/controller/AccountControllerTest.java new file mode 100644 index 00000000..e24b9e91 --- /dev/null +++ b/server/src/test/java/com/growstory/domain/account/controller/AccountControllerTest.java @@ -0,0 +1,339 @@ +package com.growstory.domain.account.controller; + +import com.google.gson.Gson; +import com.growstory.domain.account.dto.AccountDto; +import com.growstory.domain.account.service.AccountService; +import com.growstory.domain.point.entity.Point; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.web.multipart.MultipartFile; + +import java.io.FileInputStream; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +public class AccountControllerTest { + @Autowired + private MockMvc mockMvc; + + @Autowired + private Gson gson; + + @MockBean + private AccountService accountService; + + @Test + void 회원가입() throws Exception { + // given + AccountDto.Post requestDto = AccountDto.Post.builder() + .email("user1@gmail.com") + .displayName("user1") + .password("user1234") + .build(); + + AccountDto.Response responseDto = getResponseDto(1L, "user1@gmail.com", "user1"); + + given(accountService.createAccount(Mockito.any(AccountDto.Post.class))) + .willReturn(responseDto); + + // when + ResultActions actions = mockMvc.perform( + post("/v1/accounts/signup") + .contentType(MediaType.APPLICATION_JSON) // contentType은 default가 application/octet-stream + .content(gson.toJson(requestDto))); + + // then + actions + .andExpect(status().isCreated()) + .andExpect(header().string("Location", is("/v1/accounts/" + responseDto.getAccountId().toString()))) + .andDo(print()); + } + + @Test + void 프로필_사진_변경() throws Exception { + // given + MockMultipartFile testImage = new MockMultipartFile("profileImage", + "testImage.jpg", + "jpg", + new FileInputStream("src/test/resources/images/testImage.jpg")); + + willDoNothing().given(accountService).updateProfileImage(Mockito.any(MultipartFile.class)); + + // when + ResultActions actions = mockMvc.perform( + multipart(HttpMethod.PATCH, "/v1/accounts/profileimage") + .file(testImage)); + + // then + actions + .andExpect(status().isNoContent()) + .andDo(print()); + } + + @Test + void 닉네임_수정() throws Exception { + // given + AccountDto.DisplayNamePatch requestDto = AccountDto.DisplayNamePatch.builder() + .displayName("user2") + .build(); + + willDoNothing().given(accountService).updateDisplayName(Mockito.any(AccountDto.DisplayNamePatch.class)); + + // when + ResultActions actions = mockMvc.perform( + patch("/v1/accounts/displayname") + .contentType("application/json") + .content(gson.toJson(requestDto))); + + // then + actions + .andExpect(status().isNoContent()) + .andDo(print()); + } + + @Test + void 비밀번호_수정() throws Exception { + // given + AccountDto.PasswordPatch requestDto = AccountDto.PasswordPatch.builder() + .presentPassword("user1234") + .changedPassword("user4321") + .build(); + + willDoNothing().given(accountService).updatePassword(Mockito.any(AccountDto.PasswordPatch.class)); + + // when + ResultActions actions = mockMvc.perform( + patch("/v1/accounts/password") + .contentType("application/json") + .content(gson.toJson(requestDto))); + + // then + actions + .andExpect(status().isNoContent()) + .andDo(print()); + } + + @Test + void 나의_계정_조회() throws Exception { + // given + Long accountId = 1L; + + AccountDto.Response responseDto = getResponseDto(accountId, "user1@gmail.com", "user1"); + + given(accountService.getAccount(Mockito.anyLong())) + .willReturn(responseDto); + + // when + ResultActions actions = mockMvc.perform( + get("/v1/accounts/" + accountId)); + + // then + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.email", is("user1@gmail.com"))) + .andDo(print()); + } + + @Test + void 전체_계정_조회() throws Exception { + // given + List responseDtos = getResponseDtos(); + + given(accountService.getAccounts()) + .willReturn(responseDtos); + + // when + ResultActions actions = mockMvc.perform( + get("/v1/accounts/all")); + + // then + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data[0].email", is("user1@gmail.com"))) + .andExpect(jsonPath("$.data[1].email", is("user2@gmail.com"))) + .andDo(print()); + } + + @Test + void 계정이_작성한_게시글_조회() throws Exception { + // given + Long accountId = 1L; + int page = 1; + + List responseDtos = getBoardResponseDtos(); + + Page responsePage = new PageImpl<>(responseDtos, PageRequest.of(page - 1,12), responseDtos.size()); + + given(accountService.getAccountBoardWritten(Mockito.anyInt(), Mockito.anyInt(), Mockito.anyLong())) + .willReturn(responsePage); + + // when + ResultActions actions = mockMvc.perform( + get("/v1/accounts/boardWritten/" + accountId)); + + // then + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data[0].title", is("제목1"))) + .andExpect(jsonPath("$.data[1].title", is("제목2"))) + .andExpect(jsonPath("$.pageInfo.page", is(page))) + .andExpect(jsonPath("$.pageInfo.totalElements", is(responseDtos.size()))) + .andDo(print()); + } + + @Test + void 계정이_좋아요_누른_게시글_조회() throws Exception { + // given + Long accountId = 1L; + int page = 1; + + List responseDtos = getBoardResponseDtos(); + + Page responsePage = new PageImpl<>(responseDtos, PageRequest.of(page - 1,12), responseDtos.size()); + + given(accountService.getAccountBoardLiked(Mockito.anyInt(), Mockito.anyInt(), Mockito.anyLong())) + .willReturn(responsePage); + + // when + ResultActions actions = mockMvc.perform( + get("/v1/accounts/boardLiked/" + accountId)); + + // then + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data[*]..likes[?(1 == @)]").exists()) + .andExpect(jsonPath("$.pageInfo.page", is(page))) + .andExpect(jsonPath("$.pageInfo.totalElements", is(responseDtos.size()))) + .andDo(print()); + } + + @Test + void 계정이_댓글_작성한_게시글_조회() throws Exception { + // given + Long accountId = 1L; + int page = 1; + + List responseDtos = getBoardResponseDtos(); + + Page responsePage = new PageImpl<>(responseDtos, PageRequest.of(page - 1,12), responseDtos.size()); + + given(accountService.getAccountCommentWrittenBoard(Mockito.anyInt(), Mockito.anyInt(), Mockito.anyLong())) + .willReturn(responsePage); + + // when + ResultActions actions = mockMvc.perform( + get("/v1/accounts/commentWritten/" + accountId)); + + // then + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data[*].commentNums", is(not(0)))) + .andExpect(jsonPath("$.pageInfo.page", is(page))) + .andExpect(jsonPath("$.pageInfo.totalElements", is(responseDtos.size()))) + .andDo(print()); + } + + @Test + void 비밀번호_검증() throws Exception { + // given + AccountDto.PasswordVerify requestDto = AccountDto.PasswordVerify.builder() + .password("user1234") + .build(); + + Boolean isMatched = true; + + given(accountService.verifyPassword(Mockito.any(AccountDto.PasswordVerify.class))) + .willReturn(isMatched); + + // when + ResultActions actions = mockMvc.perform( + post("/v1/accounts/password/verification") + .contentType("application/json") + .content(gson.toJson(requestDto))); + + // then + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data", is(isMatched))) + .andDo(print()); + } + + @Test + void 회원탈퇴() throws Exception { + // given + willDoNothing().given(accountService).deleteAccount(); + + // when + ResultActions actions = mockMvc.perform( + delete("/v1/accounts")); + + // then + actions + .andExpect(status().isNoContent()) + .andDo(print()); + } + + private static AccountDto.Response getResponseDto(Long accountId, String email, String displayName) { + return AccountDto.Response.builder() + .accountId(accountId) + .email(email) + .displayName(displayName) + .point(Point.builder().score(500).build()) + .build(); + } + + private static List getResponseDtos() { + List responseDtos = new ArrayList<>(); + + AccountDto.Response responseDto1 = getResponseDto(1L, "user1@gmail.com", "user1"); + AccountDto.Response responseDto2 = getResponseDto(2L, "user2@gmail.com", "user2"); + + responseDtos.add(responseDto1); + responseDtos.add(responseDto2); + + return responseDtos; + } + + private static AccountDto.BoardResponse getBoardResponseDto(Long boardId, String title, List likes, int commentNums) { + return AccountDto.BoardResponse.builder() + .boardId(boardId) + .title(title) + .likes(likes) + .commentNums(commentNums) + .build(); + } + + private static List getBoardResponseDtos() { + List responseDtos = new ArrayList<>(); + + AccountDto.BoardResponse responseDto1 = getBoardResponseDto(1L, "제목1", List.of(1L), 1); + AccountDto.BoardResponse responseDto2 = getBoardResponseDto(2L, "제목2", List.of(1L, 2L), 2); + + responseDtos.add(responseDto1); + responseDtos.add(responseDto2); + + return responseDtos; + } +} diff --git a/server/src/test/java/com/growstory/domain/account/service/AccountServiceTest.java b/server/src/test/java/com/growstory/domain/account/service/AccountServiceTest.java new file mode 100644 index 00000000..2c1c8920 --- /dev/null +++ b/server/src/test/java/com/growstory/domain/account/service/AccountServiceTest.java @@ -0,0 +1,617 @@ +package com.growstory.domain.account.service; + +import com.growstory.domain.account.dto.AccountDto; +import com.growstory.domain.account.entity.Account; +import com.growstory.domain.account.repository.AccountRepository; +import com.growstory.domain.board.entity.Board; +import com.growstory.domain.comment.entity.Comment; +import com.growstory.domain.likes.entity.BoardLike; +import com.growstory.domain.point.entity.Point; +import com.growstory.domain.point.service.PointService; +import com.growstory.global.auth.utils.AuthUserUtils; +import com.growstory.global.auth.utils.CustomAuthorityUtils; +import com.growstory.global.aws.service.S3Uploader; +import com.growstory.global.exception.BusinessLogicException; +import com.growstory.global.exception.ExceptionCode; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.*; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +public class AccountServiceTest { + @InjectMocks // @Mock으로 만들어진 객체를 의존성 주입받는 객체 + private AccountService accountService; + @Mock + private PasswordEncoder passwordEncoder; + @Mock + private CustomAuthorityUtils customAuthorityUtils; + @Mock + private PointService pointService; + @Mock + private S3Uploader s3Uploader; + @Mock + private AuthUserUtils authUserUtils; + @Mock + private static Authentication authentication; + @Mock + private AccountRepository accountRepository; + @Mock + private static SecurityContext securityContext = mock(SecurityContext.class); + @Mock + private Point point = mock(Point.class); + + @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class 회원가입 { + // given + AccountDto.Post requestDto = AccountDto.Post.builder() + .email("user@gmail.com") + .displayName("user1") + .password("user1234") + .build(); + List roles = List.of("USER"); + + @Test + @Order(1) + public void 중복된_이메일이면_회원가입_실패() { + given(accountRepository.findByEmail(Mockito.anyString())) + .willReturn(Optional.of(Account.builder().build())); + + // when, then + BusinessLogicException exception = assertThrows(BusinessLogicException.class, + () -> accountService.createAccount(requestDto)); + assertThat(exception.getExceptionCode().getStatus(), is(409)); + assertThat(exception.getExceptionCode().getMessage(), is("Account already exists")); + } + + @Test + @Order(2) + public void 아니면_회원가입_성공() { + given(accountRepository.findByEmail(Mockito.anyString())) + .willReturn(Optional.empty()); + + given(passwordEncoder.encode(Mockito.anyString())) + .willReturn(requestDto.getPassword()); + given(customAuthorityUtils.createRoles(Mockito.anyString())) + .willReturn(roles); + given(pointService.createPoint(Mockito.anyString())) + .willReturn(point); + + Account savedAccount = getAccount(1L, requestDto.getEmail(), requestDto.getDisplayName(), + requestDto.getPassword(), "path", point, roles, Account.AccountGrade.GRADE_BRONZE); + + given(accountRepository.save(Mockito.any(Account.class))) + .willReturn(savedAccount); + + willDoNothing().given(point).updateAccount(Mockito.any(Account.class)); + + // when + AccountDto.Response responseDto = accountService.createAccount(requestDto); + + // then + assertThat(responseDto.getAccountId(), is(savedAccount.getAccountId())); + } + } + + @Nested + class 이미지_수정 { + // given + String s3ImageUrl = "s3/path"; + + MockMultipartFile testImage = new MockMultipartFile("profileImage", + "testImage.jpg", + "jpg", + new FileInputStream("src/test/resources/images/testImage.jpg")); + + Account account = getAccount(1L, "user1@gmail.com", "user1", + "user1234", "image/path", Point.builder().build(), + List.of("USER"), Account.AccountGrade.GRADE_BRONZE); + + 이미지_수정() throws IOException { + } + + private void uploadImage() { + given(s3Uploader.uploadImageToS3(Mockito.any(MockMultipartFile.class), Mockito.anyString())) + .willReturn(s3ImageUrl); + given(accountRepository.save(Mockito.any(Account.class))) + .willReturn(account.toBuilder().profileImageUrl(s3ImageUrl).build()); + } + + @Test + public void 기존_이미지가_존재할_때() { + given(authUserUtils.getAuthUser()) + .willReturn(account); + + willDoNothing().given(s3Uploader).deleteImageFromS3(Mockito.anyString(), Mockito.anyString()); + + uploadImage(); + + // when , then + assertDoesNotThrow(() -> accountService.updateProfileImage(testImage)); + verify(s3Uploader, times(1)).deleteImageFromS3(Mockito.anyString(), Mockito.anyString()); + } + + @Test + public void 기존_이미지가_없을_때() { + given(authUserUtils.getAuthUser()) + .willReturn(account.toBuilder().profileImageUrl(null).build()); + + uploadImage(); + + // when, then + assertDoesNotThrow(() -> accountService.updateProfileImage(testImage)); + verify(s3Uploader, times(0)).deleteImageFromS3(Mockito.anyString(), Mockito.anyString()); + } + } + + @Test + public void 닉네임_수정() { + // given + String updatedDisplayName = "updatedDisplayName"; + + AccountDto.DisplayNamePatch displayNamePatchDto = AccountDto.DisplayNamePatch.builder().displayName(updatedDisplayName).build(); + + Account account = getAccount(1L, "user1@gmail.com", "user1", + "user1234", "image/path", Point.builder().build(), + List.of("USER"), Account.AccountGrade.GRADE_BRONZE); + + given(authUserUtils.getAuthUser()) + .willReturn(account); + + given(accountRepository.save(Mockito.any(Account.class))) + .willReturn(account.toBuilder().displayName(displayNamePatchDto.getDisplayName()).build()); + + // when, then + assertDoesNotThrow(() -> accountService.updateDisplayName(displayNamePatchDto)); + } + + @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class 비밀번호_수정 { + // given + String updatedPassword = "updatedPassword"; + String samePassword = "user1234"; + Account account = getAccount(1L, "user1@gmail.com", "user1", + "user1234", "image/path", Point.builder().build(), + List.of("USER"), Account.AccountGrade.GRADE_BRONZE); + + AccountDto.PasswordPatch passwordPatchDto = AccountDto.PasswordPatch.builder() + .presentPassword(account.getPassword()) + .changedPassword(updatedPassword) + .build(); + + private void init(String updatedPassword) { + given(authUserUtils.getAuthUser()) + .willReturn(account); + given(passwordEncoder.encode(Mockito.anyString())) + .willReturn(updatedPassword); + } + + @Test + @Order(1) + public void 현재_비밀번호가_일치하지_않으면_실패() { + init(updatedPassword); + given(passwordEncoder.matches(Mockito.anyString(), Mockito.anyString())) + .willReturn(false); + + // when, then + BadCredentialsException exception = assertThrows(BadCredentialsException.class, + () -> accountService.updatePassword(passwordPatchDto)); + assertThat(exception.getMessage(), is("현재 비밀번호가 일치하지 않습니다.")); + } + + @Test + @Order(2) + public void 새로운_비밀번호가_현재와_같으면_실패() { + init(samePassword); + given(passwordEncoder.matches(Mockito.anyString(), Mockito.anyString())) + .willReturn(true); + + // when, then + BadCredentialsException exception = assertThrows(BadCredentialsException.class, + () -> accountService.updatePassword(passwordPatchDto)); + assertThat(exception.getMessage(), is("새로운 비밀번호와 현재 비밀번호가 일치합니다.")); + } + + @Test + @Order(3) + public void 아니면_수정_성공() { + init(updatedPassword); + given(passwordEncoder.matches(Mockito.anyString(), Mockito.anyString())) + .willReturn(true); + + given(accountRepository.save(Mockito.any(Account.class))) + .willReturn(account.toBuilder().password(updatedPassword).build()); + // when, then + assertDoesNotThrow(() -> accountService.updatePassword(passwordPatchDto)); + } + } + + @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class 단일_사용자_조회 { + // given + Long accountId = 1L; + Account account = getAccount(1L, "user1@gmail.com", "user1", + "user1234", "image/path", Point.builder().build(), + List.of("USER"), Account.AccountGrade.GRADE_BRONZE); + + @Test + @Order(1) + public void 입력한_ID의_사용자가_존재하지_않으면_실패() { + accountVerification(accountId); + } + + @Test + @Order(2) + public void 존재한다면_성공() { + given(accountRepository.findById(accountId)) + .willReturn(Optional.of(account)); + + // when + AccountDto.Response responseDto = accountService.getAccount(accountId); + + // then + assertThat(responseDto.getAccountId(), is(accountId)); + } + } + + @Test + public void 전체_사용자_조회() { + // given + Account account1 = getAccount(1L, "user1@gmail.com", "user1", + "user1234", "image/path", Point.builder().build(), + List.of("USER"), Account.AccountGrade.GRADE_BRONZE); + + Account account2 = getAccount(2L, "user2@gmail.com", "user2", + "user1234", "image/path", Point.builder().build(), + List.of("USER"), Account.AccountGrade.GRADE_BRONZE); + + Account account3 = getAccount(3L, "user3@gmail.com", "user3", + "user1234", "image/path", Point.builder().build(), + List.of("USER"), Account.AccountGrade.GRADE_BRONZE); + + given(accountRepository.findAll()) + .willReturn(List.of(account1, account2, account3)); + + // when + List responseDtos = accountService.getAccounts(); + + // then + assertThat(responseDtos.size(), is(3)); + assertThat(responseDtos.get(0).getAccountId(), is(1L)); + assertThat(responseDtos.get(1).getAccountId(), is(2L)); + assertThat(responseDtos.get(2).getAccountId(), is(3L)); + } + + @Nested + class 사용자_관련_게시글_조회 { + // given + int page = 1; + int size = 12; + Long accountId = 1L; + Account account = getAccount(1L, "user1@gmail.com", "user1", + "user1234", "image/path", Point.builder().build(), + List.of("USER"), Account.AccountGrade.GRADE_BRONZE); + BoardLike boardLike = BoardLike.builder().account(account).build(); + Comment comment1 = Comment.builder().account(account).build(); + Comment comment2 = Comment.builder().account(account).build(); + Board board1 = getBoard(1L, account, List.of(comment1), new ArrayList<>()); + Board board2 = getBoard(2L, account, List.of(comment2), List.of(boardLike)); + Board board3 = getBoard(3L, account, new ArrayList<>(), new ArrayList<>()); + + + + @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class 사용자가_쓴_게시글_조회 { + @Test + @Order(1) + public void 입력한_ID의_사용자가_존재하지_않으면_실패() { + accountVerification(accountId); + } + + @Test + @Order(2) + public void 존재한다면_성공() { + given(accountRepository.findById(accountId)) + .willReturn(Optional.of(account.toBuilder() + .boards(List.of(board1, board2, board3)) + .build())); + + // when + Page boardResponsePages = accountService.getAccountBoardWritten(page - 1, size, accountId); + + // then + assertThat(boardResponsePages.getContent().size(), is(3)); + assertThat(boardResponsePages.getNumber(), is(page - 1)); + } + } + + @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class 사용자가_좋아요_누른_게시글_조회 { + @Test + @Order(1) + public void 입력한_ID의_사용자가_존재하지_않으면_실패() { + accountVerification(accountId); + } + + @Test + @Order(2) + public void 존재한다면_성공() { + given(accountRepository.findById(accountId)) + .willReturn(Optional.of(account.toBuilder() + .boards(List.of(board2)) + .boardLikes(List.of(boardLike.toBuilder().board(board2).build())) + .build())); + + // when + Page boardResponsePages = accountService.getAccountBoardLiked(page - 1, size, accountId); + + // then + assertThat(boardResponsePages.getContent().size(), is(1)); + assertThat(boardResponsePages.getNumber(), is(page - 1)); + assertThat(boardResponsePages.getContent().get(0).getLikes(), contains(1L)); + } + } + + @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class 사용자가_댓글_쓴_게시글_조회 { + @Test + @Order(1) + public void 입력한_ID의_사용자가_존재하지_않으면_실패() { + accountVerification(accountId); + } + + @Test + @Order(2) + public void 존재한다면_성공() { + given(accountRepository.findById(accountId)) + .willReturn(Optional.of(account.toBuilder() + .boards(List.of(board1, board2)) + .comments(List.of( + comment1.toBuilder().board(board1).build(), + comment2.toBuilder().board(board2).build())) + .build())); + + // when + Page boardResponsePages = accountService.getAccountCommentWrittenBoard(page - 1, size, accountId); + + // then + assertThat(boardResponsePages.getContent().size(), is(2)); + assertThat(boardResponsePages.getNumber(), is(page - 1)); + assertThat(boardResponsePages.getContent().get(0).getCommentNums(), is(1)); + assertThat(boardResponsePages.getContent().get(1).getCommentNums(), is(1)); + } + } + } + + @Nested + class 회원탈퇴 { + // given + Account account = getAccount(1L, "user1@gmail.com", "user1", + "user1234", "image/path", Point.builder().build(), + List.of("USER"), Account.AccountGrade.GRADE_BRONZE); + + @Test + public void 기존_이미지가_존재할_때() { + given(authUserUtils.getAuthUser()) + .willReturn(account); + + willDoNothing().given(s3Uploader).deleteImageFromS3(Mockito.anyString(), Mockito.anyString()); + + // when , then + assertDoesNotThrow(() -> accountService.deleteAccount()); + verify(s3Uploader, times(1)).deleteImageFromS3(Mockito.anyString(), Mockito.anyString()); + } + + @Test + public void 기존_이미지가_없을_때() { + given(authUserUtils.getAuthUser()) + .willReturn(account.toBuilder().profileImageUrl(null).build()); + + // when, then + assertDoesNotThrow(() -> accountService.deleteAccount()); + verify(s3Uploader, times(0)).deleteImageFromS3(Mockito.anyString(), Mockito.anyString()); + } + } + + @Nested + class 회원탈퇴_시_비밀번호_검증 { + // given + String password = "user1234"; + String diffPassword = "admin1234"; + + AccountDto.PasswordVerify passwordVerifyDto = AccountDto.PasswordVerify + .builder() + .password(password) + .build(); + + Account account = getAccount(1L, "user1@gmail.com", "user1", + password, "image/path", Point.builder().build(), + List.of("USER"), Account.AccountGrade.GRADE_BRONZE); + @Test + public void 입력이_현재_비밀번호와_같을_때() { + given(authUserUtils.getAuthUser()) + .willReturn(account); + + given(passwordEncoder.matches(Mockito.anyString(), Mockito.anyString())) + .willReturn(passwordVerifyDto.getPassword().equals(account.getPassword())); + + // when + Boolean response = accountService.verifyPassword(passwordVerifyDto); + + // then + assertThat(response, is(true)); + } + + @Test + public void 입력이_현재_비밀번호와_다를_때() { + passwordVerifyDto = passwordVerifyDto.toBuilder().password(diffPassword).build(); + + given(authUserUtils.getAuthUser()) + .willReturn(account); + + given(passwordEncoder.matches(Mockito.anyString(), Mockito.anyString())) + .willReturn(passwordVerifyDto.getPassword().equals(account.getPassword())); + + // when + Boolean response = accountService.verifyPassword(passwordVerifyDto); + + // then + assertThat(response, is(false));} + } + + @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class 사용자_검증 { + // given + Map claims = new HashMap<>(); + Long accountId = 1L; + + @BeforeEach + private void securityInit() { + SecurityContextHolder.setContext(securityContext); + securityContext.setAuthentication(authentication); + + given(SecurityContextHolder.getContext().getAuthentication()) + .willReturn(authentication); + } + + @Test + @Order(1) + public void 로그인된_사용자가_없다면_실패() { + given(SecurityContextHolder.getContext().getAuthentication()) + .willReturn(null); + + //when, then + BusinessLogicException exception = assertThrows(BusinessLogicException.class, + () -> accountService.isAuthIdMatching(accountId)); + assertThat(exception.getExceptionCode().getStatus(), is(404)); + assertThat(exception.getExceptionCode().getMessage(), is("Account not found")); + } + + @Test + @Order(2) + public void 인증되지_않은_사용자라면_실패() { + given(authentication.getPrincipal()) + .willReturn(claims); + + given(authentication.getName()) + .willReturn(null); + + //when, then + BusinessLogicException exception = assertThrows(BusinessLogicException.class, + () -> accountService.isAuthIdMatching(accountId)); + assertThat(exception.getExceptionCode().getStatus(), is(401)); + assertThat(exception.getExceptionCode().getMessage(), is("Account unauthorized")); + } + + @Test + @Order(3) + public void 익명_사용자라면_실패() { + given(authentication.getPrincipal()) + .willReturn(claims); + + given(authentication.getName()) + .willReturn("anonymousUser"); + + //when, then + BusinessLogicException exception = assertThrows(BusinessLogicException.class, + () -> accountService.isAuthIdMatching(accountId)); + assertThat(exception.getExceptionCode().getStatus(), is(401)); + assertThat(exception.getExceptionCode().getMessage(), is("Account unauthorized")); + } + + @Test + @Order(4) + public void 로그인된_사용자와_입력된_사용자가_다르면_실패() { + given(authentication.getPrincipal()) + .willReturn(claims); + + claims.put("accountId", "999"); + + given(authentication.getName()) + .willReturn("SeungTaeLee"); + + //when, then + BusinessLogicException exception = assertThrows(BusinessLogicException.class, + () -> accountService.isAuthIdMatching(accountId)); + assertThat(exception.getExceptionCode().getStatus(), is(405)); + assertThat(exception.getExceptionCode().getMessage(), is("That Account doesn't have authority")); + } + + @Test + @Order(5) + public void 로그인된_사용자가_입력과_동일하면_성공() { + given(authentication.getPrincipal()) + .willReturn(claims); + + claims.put("accountId", "1"); + + given(authentication.getName()) + .willReturn("SeungTaeLee"); + + //when, then + assertDoesNotThrow(() -> accountService.isAuthIdMatching(accountId)); + } + } + + private static Account getAccount(Long accountId, String email, String displayName, String password, + String profileImageUrl, Point point, List roles, Account.AccountGrade accountGrade) { + return Account.builder() + .accountId(accountId) + .email(email) + .displayName(displayName) + .password(password) + .profileImageUrl(profileImageUrl) + .point(point) + .roles(roles) + .accountGrade(accountGrade) + .build(); + } + + private static Board getBoard(Long boardId, Account account, List boardComments, List boardLikes) { + return Board.builder() + .boardId(boardId) + .boardImages(new ArrayList<>()) + .account(account) + .boardComments(boardComments) + .boardLikes(boardLikes) + .build(); + } + + private void accountVerification(Long accountId) { + given(accountRepository.findById(accountId)) + .willReturn(Optional.empty()); + + // when, then + BusinessLogicException exception = assertThrows(BusinessLogicException.class, + () -> accountService.getAccount(accountId)); + assertThat(exception.getExceptionCode().getStatus(), is(404)); + assertThat(exception.getExceptionCode().getMessage(), is("Account not found")); + } +} diff --git a/server/src/test/java/com/growstory/domain/board/controller/BoardControllerTest.java b/server/src/test/java/com/growstory/domain/board/controller/BoardControllerTest.java new file mode 100644 index 00000000..edeec4b2 --- /dev/null +++ b/server/src/test/java/com/growstory/domain/board/controller/BoardControllerTest.java @@ -0,0 +1,77 @@ +package com.growstory.domain.board.controller; + +import com.google.gson.Gson; +import com.growstory.domain.board.service.BoardService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + + +@SpringBootTest +@AutoConfigureMockMvc +class BoardControllerTest { + + // CICD plz + @Autowired + private MockMvc mockMvc; + + @Autowired + private Gson gson; + + @MockBean + private BoardService boardService; + + private final String BOARD_DEFAULT_URL = "/v1/boards"; + + + @DisplayName("게시판 등록") + @Test + void 게시판_등록() throws Exception { + + // given + String title = "게시글 제목"; + String content = "게시글 본문"; + List hashTagList = new ArrayList<>(); + hashTagList.add("tag1"); + hashTagList.add("tag2"); + + // when + + // then + + } + + @Test + void getBoard() { + } + + @Test + void getBoards() { + } + + @Test + void getBoardsByKeyword() { + } + + @Test + void patchBoard() { + } + + @Test + void deleteBoard() { + } + + @Test + void getTop3LikedBoardsOfWeek() { + } +} \ No newline at end of file diff --git a/server/src/test/java/com/growstory/domain/board/service/BoardRankingTest.java b/server/src/test/java/com/growstory/domain/board/service/BoardRankingTest.java new file mode 100644 index 00000000..f56a291a --- /dev/null +++ b/server/src/test/java/com/growstory/domain/board/service/BoardRankingTest.java @@ -0,0 +1,63 @@ +package com.growstory.domain.board.service; + +import com.growstory.domain.account.entity.Account; +import com.growstory.domain.board.entity.Board; +import com.growstory.domain.board.repository.BoardRepository; +import com.growstory.domain.rank.board_likes.dto.BoardLikesRankDto; +import com.growstory.domain.rank.board_likes.entity.BoardLikesRank; +import com.growstory.domain.stubdata.Stub; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.context.TestPropertySource; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +@TestPropertySource(properties = { + "my.scheduled.cron=0 0 0 * * ?" // 예제 크론 표현식 +}) +public class BoardRankingTest { + + @InjectMocks + private BoardService boardService; + + @Mock + private BoardRepository boardRepository; + + @Test + void testFindTop3LikedBoards() { + // given, 가짜 데이터 생성 + List fakeTopBoardsWithLikes = new ArrayList<>(); + fakeTopBoardsWithLikes.add(new Object[] { Board.builder().boardId(1L).title("제목1").account(Account.builder().displayName("빵빵스").build()).boardLikes(Stub.MockBoardLikes.getBoardLikes1()).build(), 3L}); + fakeTopBoardsWithLikes.add(new Object[] { Board.builder().boardId(2L).title("제목2").account(Account.builder().displayName("김크크").build()).boardLikes(Stub.MockBoardLikes.getBoardLikes2()).build(), 2L}); + fakeTopBoardsWithLikes.add(new Object[] { Board.builder().boardId(3L).title("제목3").account(Account.builder().displayName("박크크").build()).boardLikes(Stub.MockBoardLikes.getBoardLikes3()).build(), 1L}); + + // 가짜 데이터를 반환하도록 Mock 설정 + given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) + .willReturn(fakeTopBoardsWithLikes); + + //when, 테스트 대상 메서드 호출 + List response = boardService.findTop3LikedBoards(); + + //then, 결과 검증 + assertEquals(3, response.size()); + + assertEquals(1L, response.get(0).getBoardId()); + assertEquals(3, response.get(0).getLikeNum()); + + assertEquals(2L, response.get(1).getBoardId()); + assertEquals(2, response.get(1).getLikeNum()); + + assertEquals(3L, response.get(2).getBoardId()); + assertEquals(1, response.get(2).getLikeNum()); + } +} diff --git a/server/src/test/java/com/growstory/domain/board/service/BoardServiceTest.java b/server/src/test/java/com/growstory/domain/board/service/BoardServiceTest.java new file mode 100644 index 00000000..e1531023 --- /dev/null +++ b/server/src/test/java/com/growstory/domain/board/service/BoardServiceTest.java @@ -0,0 +1,192 @@ +package com.growstory.domain.board.service; + +import com.growstory.domain.board.repository.BoardHashTagRepository; +import com.growstory.domain.board.repository.BoardRepository; +import com.growstory.domain.comment.service.CommentService; +import com.growstory.domain.hashTag.repository.HashTagRepository; +import com.growstory.domain.hashTag.service.HashTagService; +import com.growstory.domain.images.service.BoardImageService; +import com.growstory.domain.rank.board_likes.entity.BoardLikesRank; +import com.growstory.domain.stubdata.Stub; +import com.growstory.global.auth.utils.AuthUserUtils; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +public class BoardServiceTest { + + @InjectMocks + private BoardService boardService; + @Mock + private BoardRepository boardRepository; + @Mock + private HashTagService hashTagService; + @Mock + private BoardImageService boardImageService; + @Mock + private AuthUserUtils authUserUtils; + @Mock + private HashTagRepository hashTagRepository; + @Mock + private BoardHashTagRepository boardHashtagRepository; + @Mock + private CommentService commentService; + + + @DisplayName("좋아요 기준 상위 3개의 게시글 랭킹과 함께 반환") + @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class FindTop3LikedBoardRanksTest { + + //given + List mockTopBoardsWithLikes = new ArrayList<>(); + Object[] mockObjectsLike3_1 = {Stub.MockBoard.getMockBoard1(), 3L}; + Object[] mockObjectsLike3_2 = {Stub.MockBoard.getMockBoard2(), 3L}; + Object[] mockObjectsLike3_3 = {Stub.MockBoard.getMockBoard3(), 3L}; + Object[] mockObjectsLike3_4 = {Stub.MockBoard.getMockBoard4(), 3L}; + Object[] mockObjectsLike3_5 = {Stub.MockBoard.getMockBoard5(), 3L}; + Object[] mockObjectsLike2_1 = {Stub.MockBoard.getMockBoard2(), 2L}; + Object[] mockObjectsLike2_2 = {Stub.MockBoard.getMockBoard4(), 2L}; + Object[] mockObjectsLike2_3 = {Stub.MockBoard.getMockBoard3(), 2L}; + Object[] mockObjectsLike2_4 = {Stub.MockBoard.getMockBoard5(), 2L}; + Object[] mockObjectsLike2_5 = {Stub.MockBoard.getMockBoard1(), 2L}; + Object[] mockObjectsLike1_1 = {Stub.MockBoard.getMockBoard3(), 1L}; + Object[] mockObjectsLike1_2 = {Stub.MockBoard.getMockBoard5(), 1L}; + @BeforeEach + public void setUp() { + mockTopBoardsWithLikes.add(mockObjectsLike3_1); + } + +// @AfterEach +// public void tearDown() { +// mockTopBoardsWithLikes.clear(); +// } + + @Test + @Order(1) + void 동점자_없는_상위_3개_게시글() { + //given + // 좋아요 3, 2, 1 + mockTopBoardsWithLikes.add(mockObjectsLike2_1); + mockTopBoardsWithLikes.add(mockObjectsLike1_1); + given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) + .willReturn(mockTopBoardsWithLikes); + + //when + List responses = boardService.findTop3LikedBoardRanks(); + + //then + assertThat(responses.get(0).getLikeNum(), is(3L)); + assertThat(responses.get(2).getLikeNum(), is(1L)); + assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(3)); + assertThat(responses.size(), is(3)); + } + + @Test + @Order(2) + void 일등1_이등2_삼등1() { + //given + // 좋아요 3, 2, 2, 1 + mockTopBoardsWithLikes.add(mockObjectsLike2_1); + mockTopBoardsWithLikes.add(mockObjectsLike2_2); + mockTopBoardsWithLikes.add(mockObjectsLike1_2); + given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) + .willReturn(mockTopBoardsWithLikes); + + //when + List responses = boardService.findTop3LikedBoardRanks(); + + //then + assertThat(responses.get(0).getLikeNum(), is(3L)); + assertThat(responses.get(2).getLikeNum(), is(2L)); + assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(2)); + assertThat(responses.size(), is(3)); + } + + @Test + @Order(3) + void 일등3_이등1() { + //given + // 좋아요 3, 3, 3, 1 + mockTopBoardsWithLikes.add(mockObjectsLike3_2); + mockTopBoardsWithLikes.add(mockObjectsLike3_3); + mockTopBoardsWithLikes.add(mockObjectsLike1_1); + given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) + .willReturn(mockTopBoardsWithLikes); + + //when + List responses = boardService.findTop3LikedBoardRanks(); + + //then + assertThat(responses.get(0).getLikeNum(), is(3L)); + assertThat(responses.get(2).getLikeNum(), is(3L)); + assertThat(responses.get(0).getRankStatus().getRank(), is(1)); + assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(1)); + assertThat(responses.size(), is(3)); + } + @Test + @Order(4) + void 일등5_이등2() { + //given + // 좋아요 3, 3, 3, 3, 3, 1, 1 + mockTopBoardsWithLikes.add(mockObjectsLike3_2); + mockTopBoardsWithLikes.add(mockObjectsLike3_3); + mockTopBoardsWithLikes.add(mockObjectsLike3_4); + mockTopBoardsWithLikes.add(mockObjectsLike3_5); + mockTopBoardsWithLikes.add(mockObjectsLike1_1); + mockTopBoardsWithLikes.add(mockObjectsLike1_2); + given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) + .willReturn(mockTopBoardsWithLikes); + + //when + List responses = boardService.findTop3LikedBoardRanks(); + + //then + assertThat(responses.get(0).getLikeNum(), is(3L)); + assertThat(responses.get(responses.size()-1).getLikeNum(), is(3L)); + assertThat(responses.get(0).getRankStatus().getRank(), is(1)); + assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(1)); + assertThat(responses.size(), is(5)); + } + @Test + @Order(5) + void 일등1_이등5_삼등1() { + //given + // 좋아요 3, 2, 2, 2, 2, 2, 1 + mockTopBoardsWithLikes.add(mockObjectsLike2_1); + mockTopBoardsWithLikes.add(mockObjectsLike2_2); + mockTopBoardsWithLikes.add(mockObjectsLike2_3); + mockTopBoardsWithLikes.add(mockObjectsLike2_4); + mockTopBoardsWithLikes.add(mockObjectsLike2_5); + mockTopBoardsWithLikes.add(mockObjectsLike1_1); + given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) + .willReturn(mockTopBoardsWithLikes); + + //when + List responses = boardService.findTop3LikedBoardRanks(); + + //then + assertThat(responses.get(0).getLikeNum(), is(3L)); + assertThat(responses.get(responses.size()-1).getLikeNum(), is(2L)); + assertThat(responses.get(0).getRankStatus().getRank(), is(1)); + assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(2)); + assertThat(responses.size(), is(6)); + } + + + + + } +} diff --git a/server/src/test/java/com/growstory/domain/comment/controller/CommentControllerTest.java b/server/src/test/java/com/growstory/domain/comment/controller/CommentControllerTest.java new file mode 100644 index 00000000..417c95fe --- /dev/null +++ b/server/src/test/java/com/growstory/domain/comment/controller/CommentControllerTest.java @@ -0,0 +1,127 @@ +package com.growstory.domain.comment.controller; + +import com.google.gson.Gson; +import com.growstory.domain.comment.dto.CommentDto; +import com.growstory.domain.comment.service.CommentService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.startsWith; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@SpringBootTest +@AutoConfigureMockMvc +class CommentControllerTest { + + @Autowired + private MockMvc mockMvc; + + + @Autowired + private Gson gson; + + @MockBean + private CommentService commentService; + + + @Test + void postComment() throws Exception { + + // given + String content = "Comment Test"; + Long boardId = 1L; + Long commentId = 1L; + + CommentDto.Post commentDto = new CommentDto.Post(content); + + + // when + given(commentService.saveComment(Mockito.eq(boardId), Mockito.any(CommentDto.Post.class))) + .willReturn(commentId); + + String content1 = gson.toJson(boardId); + String content2 = gson.toJson(commentDto); + + // then + ResultActions actions = + mockMvc.perform( + post("/v1/comments/boards/{boardId}", boardId) + .accept(MediaType.APPLICATION_JSON) // 요청에서 받을 응답 데이터 타입을 JSON으로 설정합니다. 이것은 클라이언트가 JSON 형식의 응답을 기대한다고 나타냅니다. + .contentType(MediaType.APPLICATION_JSON) // 요청 데이터의 타입을 JSON으로 설정합니다. 이것은 요청의 본문(content)이 JSON 형식임을 나타냅니다. + .content(content1) + .content(content2) + ); + + actions + .andExpect(status().isCreated()) + .andExpect(header().string("Location", is(startsWith("/v1/comments/")))) + .andDo(print()); + } + + @Test + @WithMockUser(username = "testuser", roles = "USER") // 사용자 권한으로 요청 보내기 + void patchComment() throws Exception{ + // given + Long commentId = 1L; + String content = "TestContent"; + CommentDto.Patch commentDto = new CommentDto.Patch(content); + + + // when + doNothing().when(commentService).editComment(commentId, commentDto); + + String content1 = gson.toJson(commentId); + String content2 = gson.toJson(commentDto); + + + // then + ResultActions actions = + mockMvc.perform( + patch("/v1/comments/{commentId}", commentId) + .accept(MediaType.APPLICATION_JSON) // 요청에서 받을 응답 데이터 타입을 JSON으로 설정합니다. 이것은 클라이언트가 JSON 형식의 응답을 기대한다고 나타냅니다. + .contentType(MediaType.APPLICATION_JSON) // 요청 데이터의 타입을 JSON으로 설정합니다. 이것은 요청의 본문(content)이 JSON 형식임을 나타냅니다 + .content(content1) + .content(content2) + ); + + actions + .andExpect(MockMvcResultMatchers.status().isNoContent()) + .andDo(print()); + } + + @Test + @WithMockUser(username = "testuser", roles = "USER") // 사용자 권한으로 요청 보내기 + void deleteComment() throws Exception { + // given + Long commentId = 1L; + + // when + doNothing().when(commentService).deleteComment(commentId); + + // then + ResultActions actions = mockMvc.perform( + MockMvcRequestBuilders.delete("/v1/comments/{commentId}", commentId) + ); + actions + .andExpect(MockMvcResultMatchers.status().isNoContent()) + .andDo(print()); + } +} \ No newline at end of file diff --git a/server/src/test/java/com/growstory/domain/journal/JournalControllerTest.java b/server/src/test/java/com/growstory/domain/journal/JournalControllerTest.java new file mode 100644 index 00000000..64d93f87 --- /dev/null +++ b/server/src/test/java/com/growstory/domain/journal/JournalControllerTest.java @@ -0,0 +1,64 @@ +package com.growstory.domain.journal; + +import com.google.gson.Gson; +import com.growstory.domain.journal.dto.JournalDto; +import com.growstory.domain.journal.service.JournalService; +import com.growstory.domain.stubdata.Stub; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +import java.util.List; + +import static org.hamcrest.Matchers.*; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +@SpringBootTest +@AutoConfigureMockMvc +public class JournalControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private Gson gson; + + @MockBean + private JournalService journalService; + + private final String JOURNAL_DEFAULT_URL = "/v1/leaves"; + + @DisplayName("식물 일지 전체 조회") + @Test + void getJournalsTest() throws Exception { + //given + Long accountId = 1L; + Long leafId = 1L; + List journals = Stub.MockJournal.getStubJournalResponseDtos(); + given(journalService.findAllJournals(anyLong(), anyLong())) + .willReturn(journals); + //when + ResultActions actions = mockMvc.perform( + get(JOURNAL_DEFAULT_URL + "/{leaf-id}/journals", leafId) + .param("accountId", accountId.toString()) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + //then + actions.andExpect(status().isOk()) + .andExpect(jsonPath("$.data[0].journalId", is(1))) + .andExpect(jsonPath("$.data[0].title", is(journals.get(0).getTitle()))) + .andDo(print()); + } +} diff --git a/server/src/test/java/com/growstory/domain/leaf/controller/LeafControllerTest.java b/server/src/test/java/com/growstory/domain/leaf/controller/LeafControllerTest.java new file mode 100644 index 00000000..5442f987 --- /dev/null +++ b/server/src/test/java/com/growstory/domain/leaf/controller/LeafControllerTest.java @@ -0,0 +1,191 @@ +package com.growstory.domain.leaf.controller; + +import com.google.gson.Gson; +import com.growstory.domain.account.dto.AccountDto; +import com.growstory.domain.leaf.dto.LeafDto; +import com.growstory.domain.leaf.service.LeafService; +import com.growstory.domain.point.entity.Point; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.mock.web.MockPart; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.Part; +import java.io.FileInputStream; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +@SpringBootTest +@AutoConfigureMockMvc +public class LeafControllerTest { + @Autowired + private MockMvc mockMvc; + + @Autowired + private Gson gson; + + @MockBean + private LeafService leafService; + + @Test + void 식물카드_생성() throws Exception { + // given + Long leafId = 1L; + + LeafDto.Post requestDto = LeafDto.Post.builder() + .leafName("식물1") + .content("본문1") + .build(); + + LeafDto.Response responseDto = getResponseDto("김별명", leafId, requestDto.getLeafName(), requestDto.getContent(), "s3/path"); + + MockMultipartFile testImage = new MockMultipartFile("leafImage", + "testImage.jpg", + "jpg", + new FileInputStream("src/test/resources/images/testImage.jpg")); + + + given(leafService.createLeaf(Mockito.any(LeafDto.Post.class), Mockito.any(MultipartFile.class))) + .willReturn(responseDto); + + // when + ResultActions actions = mockMvc.perform( + multipart(HttpMethod.POST, "/v1/leaves") + .file(testImage) + .file(new MockMultipartFile("leafPostDto", "", "application/json", gson.toJson(requestDto).getBytes()))); + + // then + actions + .andExpect(status().isCreated()) + .andExpect(header().string("Location", is("/v1/leaves/" + responseDto.getLeafId().toString()))) + .andDo(print()); + } + + @Test + void 식물카드_수정() throws Exception { + // given + LeafDto.Patch requestDto = LeafDto.Patch.builder() + .leafId(1L) + .leafName("식물1") + .content("본문1") + .build(); + + MockMultipartFile testImage = new MockMultipartFile("leafImage", + "testImage.jpg", + "jpg", + new FileInputStream("src/test/resources/images/testImage.jpg")); + + willDoNothing().given(leafService).updateLeaf(Mockito.any(LeafDto.Patch.class), Mockito.any(MultipartFile.class)); + + // when + ResultActions actions = mockMvc.perform( + multipart(HttpMethod.PATCH, "/v1/leaves") + .file(testImage) + .file(new MockMultipartFile("leafPatchDto", "", "application/json", gson.toJson(requestDto).getBytes()))); + + // then + actions + .andExpect(status().isNoContent()) + .andDo(print()); + } + + @Test + void 나의_식물카드_조회() throws Exception { + // given + Long accountId = 1L; + + List responseDtos = getResponseDtos(); + + given(leafService.findLeaves(Mockito.anyLong())) + .willReturn(responseDtos); + + // when + ResultActions actions = mockMvc.perform( + get("/v1/leaves/account/" + accountId)); + + // then + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data[0].leafName", is("식물1"))) + .andExpect(jsonPath("$.data[1].leafName", is("식물2"))) + .andDo(print()); + } + + @Test + void 식물카드_단일_조회() throws Exception { + // given + Long leafId = 1L; + + LeafDto.Response responseDto = getResponseDto("김별명", 1L, "식물1", "본문1", "s3/path1"); + + given(leafService.findLeaf(Mockito.anyLong())) + .willReturn(responseDto); + + // when + ResultActions actions = mockMvc.perform( + get("/v1/leaves/" + leafId)); + + // then + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.leafName", is("식물1"))) + .andDo(print()); + } + + @Test + void 식물카드_삭제() throws Exception { + // given + Long leafId = 1L; + + willDoNothing().given(leafService).deleteLeaf(Mockito.anyLong()); + + // when + ResultActions actions = mockMvc.perform( + delete("/v1/leaves/" + leafId)); + + // then + actions + .andExpect(status().isNoContent()) + .andDo(print()); + } + + private static LeafDto.Response getResponseDto(String displayName, Long leafId, String leafName, String content, String leafImageUrl) { + return LeafDto.Response.builder() + .displayName(displayName) + .leafId(leafId) + .leafName(leafName) + .content(content) + .leafImageUrl(leafImageUrl) + .build(); + } + + private static List getResponseDtos() { + List responseDtos = new ArrayList<>(); + + LeafDto.Response responseDto1 = getResponseDto("김별명", 1L, "식물1", "본문1", "s3/path1"); + LeafDto.Response responseDto2 = getResponseDto("김별명", 2L, "식물2", "본문2", "s3/path2"); + + responseDtos.add(responseDto1); + responseDtos.add(responseDto2); + + return responseDtos; + } +} diff --git a/server/src/test/java/com/growstory/domain/leaf/service/LeafServiceTest.java b/server/src/test/java/com/growstory/domain/leaf/service/LeafServiceTest.java new file mode 100644 index 00000000..d3337524 --- /dev/null +++ b/server/src/test/java/com/growstory/domain/leaf/service/LeafServiceTest.java @@ -0,0 +1,408 @@ +package com.growstory.domain.leaf.service; + +import com.growstory.domain.account.entity.Account; +import com.growstory.domain.account.service.AccountService; +import com.growstory.domain.images.entity.JournalImage; +import com.growstory.domain.images.service.JournalImageService; +import com.growstory.domain.journal.entity.Journal; +import com.growstory.domain.leaf.dto.LeafDto; +import com.growstory.domain.leaf.entity.Leaf; +import com.growstory.domain.leaf.repository.LeafRepository; +import com.growstory.domain.plant_object.entity.PlantObj; +import com.growstory.domain.point.entity.Point; +import com.growstory.global.auth.utils.AuthUserUtils; +import com.growstory.global.aws.service.S3Uploader; +import com.growstory.global.exception.BusinessLogicException; +import com.growstory.global.exception.ExceptionCode; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.web.multipart.MultipartFile; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +public class LeafServiceTest { + @InjectMocks + private LeafService leafService; + @Mock + private LeafRepository leafRepository; + @Mock + private AccountService accountService; + @Mock + private S3Uploader s3Uploader; + @Mock + private AuthUserUtils authUserUtils; + @Mock + private JournalImageService journalImageService; + + @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class 식물카드_생성 { + // given + String s3ImageUrl = "s3/path"; + + LeafDto.Post leafPostDto = LeafDto.Post.builder() + .leafName("식물1") + .content("본문1") + .build(); + + MockMultipartFile testImage = new MockMultipartFile("profileImage", + "testImage.jpg", + "jpg", + new FileInputStream("src/test/resources/images/testImage.jpg")); + + Account account = getAccount(1L, "user1@gmail.com", "user1", + "user1234", "image/path", Point.builder().build(), + List.of("USER"), Account.AccountGrade.GRADE_BRONZE); + + Leaf leaf = getLeaf(1L, leafPostDto.getLeafName(), leafPostDto.getContent(), s3ImageUrl); + + private List getLeaves(int size) { + List leaves = new ArrayList<>(); + for (int i = 0; i < size; i++) leaves.add(leaf); + + return leaves; + } + private void uploadImage() { + given(s3Uploader.uploadImageToS3(Mockito.any(MockMultipartFile.class), Mockito.anyString())) + .willReturn(s3ImageUrl); + given(leafRepository.save(Mockito.any(Leaf.class))) + .willReturn(leaf.toBuilder().leafImageUrl(s3ImageUrl).build()); + } + + 식물카드_생성() throws IOException { + } + + @Test + @Order(1) + public void 사용자의_식물카드가_50개_미만이면() { + int leafSize = 1; + List leaves = getLeaves(leafSize); + + // when + Account.AccountGrade grade = leafService.updateAccountGrade(account.toBuilder().leaves(leaves).build()); + + // then + assertThat(grade.getStepDescription(), is("브론즈 가드너")); + } + + @Test + @Order(2) + public void 사용자의_식물카드가_50개_이상_100개_미만이면() { + int leafSize = 50; + List leaves = getLeaves(leafSize); + + // when + Account.AccountGrade grade = leafService.updateAccountGrade(account.toBuilder().leaves(leaves).build()); + + // then + assertThat(grade.getStepDescription(), is("실버 가드너")); + } + + @Test + @Order(3) + public void 사용자의_식물카드가_100개_이상이면() { + int leafSize = 100; + List leaves = getLeaves(leafSize); + + // when + Account.AccountGrade grade = leafService.updateAccountGrade(account.toBuilder().leaves(leaves).build()); + + // then + assertThat(grade.getStepDescription(), is("골드 가드너")); + } + + @Test + @Order(4) + public void 생성_성공() { + given(authUserUtils.getAuthUser()) + .willReturn(account.toBuilder().leaves(new ArrayList<>()).build()); + + uploadImage(); + + // when + LeafDto.Response responseDto = leafService.createLeaf(leafPostDto, testImage); + + // then + assertThat(responseDto.getLeafId(), is(leaf.getLeafId())); + } + } + + @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class 식물카드_수정 { + // given + String s3ImageUrl = "s3/path"; + + LeafDto.Patch leafPatchDto = LeafDto.Patch.builder() + .leafId(1L) + .leafName("식물1") + .content("본문1") + .build(); + + MockMultipartFile testImage = new MockMultipartFile("profileImage", + "testImage.jpg", + "jpg", + new FileInputStream("src/test/resources/images/testImage.jpg")); + + Account account = getAccount(1L, "user1@gmail.com", "user1", + "user1234", "image/path", Point.builder().build(), + List.of("USER"), Account.AccountGrade.GRADE_BRONZE); + + Leaf leaf = getLeaf(leafPatchDto.getLeafId(), leafPatchDto.getLeafName(), leafPatchDto.getContent(), s3ImageUrl); + + 식물카드_수정() throws IOException { + } + + @Test + @Order(1) + public void 입력받은_식물카드가_존재하지_않으면_실패() { + given(authUserUtils.getAuthUser()) + .willReturn(account); + + given(leafRepository.findById(Mockito.anyLong())) + .willReturn(Optional.empty()); + + // when, then + BusinessLogicException exception = assertThrows(BusinessLogicException.class, + () -> leafService.updateLeaf(leafPatchDto, testImage)); + assertThat(exception.getExceptionCode().getStatus(), is(404)); + assertThat(exception.getExceptionCode().getMessage(), is("Leaf not found")); + } + + @Test + @Order(2) + public void 입력받은_사용자와_식물카드의_주인이_다르면_실패() { + given(authUserUtils.getAuthUser()) + .willReturn(account.toBuilder().accountId(2L).build()); + + given(leafRepository.findById(Mockito.anyLong())) + .willReturn(Optional.of(leaf.toBuilder().account(account).build())); + + // when, then + BusinessLogicException exception = assertThrows(BusinessLogicException.class, + () -> leafService.updateLeaf(leafPatchDto, testImage)); + assertThat(exception.getExceptionCode().getStatus(), is(405)); + assertThat(exception.getExceptionCode().getMessage(), is("That Account doesn't have authority")); + } + +// @Test +// @Order(3) +// public void 입력받은_이미지가_없으면() { +// given(authUserUtils.getAuthUser()) +// .willReturn(account); +// +// given(leafRepository.findById(Mockito.anyLong())) +// .willReturn(Optional.of(leaf.toBuilder().account(account).build())); +// +// willDoNothing().given(s3Uploader).deleteImageFromS3(Mockito.anyString(), Mockito.anyString()); +// +// // when, then +// assertDoesNotThrow(() -> leafService.updateLeaf(leafPatchDto, null)); +// verify(s3Uploader, times(0)).uploadImageToS3(Mockito.any(MultipartFile.class), Mockito.anyString()); +// } +// +// @Test +// @Order(4) +// public void 입력받은_이미지가_있으면() { +// given(authUserUtils.getAuthUser()) +// .willReturn(account); +// +// given(leafRepository.findById(Mockito.anyLong())) +// .willReturn(Optional.of(leaf.toBuilder().account(account).build())); +// +// willDoNothing().given(s3Uploader).deleteImageFromS3(Mockito.anyString(), Mockito.anyString()); +// +// // when, then +// assertDoesNotThrow(() -> leafService.updateLeaf(leafPatchDto, testImage)); +// verify(s3Uploader, times(1)).uploadImageToS3(Mockito.any(MultipartFile.class), Mockito.anyString()); +// } + } + + @Nested + class 식물카드_단일_조회 { + // given + Long leafId = 1L; + + Account account = getAccount(1L, "user1@gmail.com", "user1", + "user1234", "image/path", Point.builder().build(), + List.of("USER"), Account.AccountGrade.GRADE_BRONZE); + + Leaf leaf = getLeaf(leafId, "식물1", "본문1", "s3/path"); + + @Test + public void 입력받은_식물카드가_존재할_때_성공() { + given(leafRepository.findById(Mockito.anyLong())) + .willReturn(Optional.of(leaf.toBuilder() + .account(account) + .build())); + + // when + LeafDto.Response responseDto = leafService.findLeaf(leafId); + + // then + assertThat(responseDto.getLeafId(), is(leafId)); + assertThat(responseDto.getLeafName(), is(leaf.getLeafName())); + } + + @Test + public void 입력받은_식물카드가_존재하지_않을_때_실패() { + given(leafRepository.findById(Mockito.anyLong())) + .willReturn(Optional.empty()); + + // when, then + BusinessLogicException exception = assertThrows(BusinessLogicException.class, + () -> leafService.findLeaf(leafId)); + assertThat(exception.getExceptionCode().getStatus(), is(404)); + assertThat(exception.getExceptionCode().getMessage(), is("Leaf not found")); + } + } + + @Test + public void 전체_식물카드_조회() { + // given + Long accountId = 1L; + + Account account = getAccount(accountId, "user1@gmail.com", "user1", + "user1234", "image/path", Point.builder().build(), + List.of("USER"), Account.AccountGrade.GRADE_BRONZE); + + Leaf leaf1 = getLeaf(1L, "식물1", "본문1", "s3ImageUrl"); + Leaf leaf2 = getLeaf(2L, "식물2", "본문1", "s3ImageUrl2"); + + + given(accountService.findVerifiedAccount(Mockito.anyLong())) + .willReturn(account); + + given(leafRepository.findByAccount(Mockito.any(Account.class))) + .willReturn(List.of( + leaf1.toBuilder().account(account).build(), + leaf2.toBuilder().account(account).build())); + + // when + List responseDtos = leafService.findLeaves(accountId); + + // then + assertThat(responseDtos.size(), is(2)); + assertThat(responseDtos.get(0).getLeafId(), is(1L)); + assertThat(responseDtos.get(1).getLeafId(), is(2L)); + } + + @Test + public void findLeafEntityByTest() { + // given + Long leafId = 1L; + + Account account = getAccount(1L, "user1@gmail.com", "user1", + "user1234", "image/path", Point.builder().build(), + List.of("USER"), Account.AccountGrade.GRADE_BRONZE); + + Leaf leaf = getLeaf(leafId, "식물1", "본문1", "s3ImageUrl"); + + given(authUserUtils.getAuthUser()) + .willReturn(account); + + given(leafRepository.findById(leafId)) + .willReturn(Optional.of(leaf.toBuilder().account(account).build())); + + // when + Leaf findLeaf = leafService.findLeafEntityBy(leafId); + + // then + assertThat(findLeaf.getLeafId(), is(leafId)); + } + + @Nested + class 식물카드_삭제 { + // given + Long leafId = 1L; + + Account account = getAccount(1L, "user1@gmail.com", "user1", + "user1234", "image/path", Point.builder().build(), + List.of("USER"), Account.AccountGrade.GRADE_BRONZE); + + Leaf leaf = getLeaf(leafId, "식물1", "본문1", "s3ImageUrl"); + + Journal journal1 = Journal.builder().journalImage(JournalImage.builder().build()).build(); + Journal journal2 = Journal.builder().build(); + + PlantObj plantObj = PlantObj.builder().build(); + + @BeforeEach + private void init() { + given(authUserUtils.getAuthUser()) + .willReturn(account.toBuilder().leaves(new ArrayList<>(List.of(leaf))).build()); + + willDoNothing().given(journalImageService).deleteJournalImageWithS3(Mockito.any(JournalImage.class), Mockito.anyString()); + } + + @Test + public void 연결된_plantObj가_없으면(){ + given(leafRepository.findById(Mockito.anyLong())) + .willReturn(Optional.of(leaf.toBuilder() + .account(account) + .journals(new ArrayList<>(List.of(journal1, journal2))) + .build())); + + // when, then + assertDoesNotThrow(() -> leafService.deleteLeaf(leafId)); + } + + @Test + public void 연결된_plantObj가_있으면(){ + given(leafRepository.findById(Mockito.anyLong())) + .willReturn(Optional.of(leaf.toBuilder() + .account(account) + .journals(new ArrayList<>(List.of(journal1, journal2))) + .plantObj(plantObj) + .build())); + // when + leafService.deleteLeaf(leafId); + + // then + assertThat(Optional.ofNullable(plantObj.getLeaf()), is(Optional.empty())); + } + } + + private static Account getAccount(Long accountId, String email, String displayName, String password, + String profileImageUrl, Point point, List roles, Account.AccountGrade accountGrade) { + return Account.builder() + .accountId(accountId) + .email(email) + .displayName(displayName) + .password(password) + .profileImageUrl(profileImageUrl) + .point(point) + .roles(roles) + .accountGrade(accountGrade) + .build(); + } + + private Leaf getLeaf(Long leafId, String leafName, String content, String leafImageUrl) { + return Leaf.builder() + .leafId(leafId) + .leafName(leafName) + .content(content) + .leafImageUrl(leafImageUrl) + .build(); + } +} diff --git a/server/src/test/java/com/growstory/domain/plant_object/controller/PlantObjectControllerTest.java b/server/src/test/java/com/growstory/domain/plant_object/controller/PlantObjectControllerTest.java new file mode 100644 index 00000000..b6641070 --- /dev/null +++ b/server/src/test/java/com/growstory/domain/plant_object/controller/PlantObjectControllerTest.java @@ -0,0 +1,160 @@ +package com.growstory.domain.plant_object.controller; + +import com.google.gson.Gson; +import com.growstory.domain.plant_object.dto.PlantObjDto; +import com.growstory.domain.plant_object.location.dto.LocationDto; +import com.growstory.domain.plant_object.service.PlantObjService; +import com.growstory.domain.plant_object.service.PlantObjServiceTest; +import com.growstory.domain.point.dto.PointDto; +import com.growstory.domain.stubdata.Stub; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.mockito.stubbing.Answer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import java.util.List; + +import static org.hamcrest.Matchers.*; +import static org.mockito.BDDMockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +public class PlantObjectControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private Gson gson; + + @MockBean + private PlantObjService plantObjService; + + private final String DEFAULT_URL = "/v1/gardens"; + + @DisplayName("정원 전체 정보 조회 API 테스트") + @Test + void getGardenInfoTest() throws Exception { + //given + Long accountId = 1L; + PlantObjDto.GardenInfoResponse response = Stub.MockPlantObj.getStubGardenInfo(); + given(plantObjService.findAllGardenInfo(Mockito.anyLong())) + .willReturn(response); + //when + ResultActions actions = mockMvc.perform( + get(DEFAULT_URL +"/{account-id}" ,accountId)); + //then + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.plantObjs[0].plantObjId", is(1))) + .andExpect(jsonPath("$.data.plantObjs[1].plantObjId", is(2))) + .andDo(print()); + } + + @DisplayName("유저 포인트로 오브젝트 구입 API 테스트") + @Test + void postPurchaseObjTest() throws Exception { + //given + Long accountId = 1L; + Long productId = 1L; + PlantObjDto.TradeResponse response = Stub.MockPlantObj.getStubTradeResponse(); + given(plantObjService.buyProduct(Mockito.anyLong(), Mockito.anyLong())) + .willReturn(response); + + //when + ResultActions actions = mockMvc.perform( + post(DEFAULT_URL +"/{account-id}/purchase", accountId) + .param("product-id", productId.toString()) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + //then + actions.andExpect(status().isCreated()) + .andExpect(jsonPath("$.data.plantObj.productId", is(productId.intValue()))) + .andDo(print()); + } + + @DisplayName("오브젝트 되팔기 API 테스트") + @Test + void deleteRefundObj() throws Exception { + //given + Long accountId = 1L; + Long plantObjId = 1L; + PointDto.Response response = PointDto.Response.builder().score(500).build(); + given(plantObjService.refundPlantObj(anyLong(), anyLong())) + .willReturn(response); + + //when + ResultActions actions = mockMvc.perform( + delete(DEFAULT_URL+"/{account-id}/refund", accountId) + .param("plantobj-id", plantObjId.toString()) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + //then + actions.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.score", is(500))); + } + + @DisplayName("오브젝트 배치 (편집 완료) API 테스트") + @Test + void patchLocationsTest() throws Exception { + //given + Long accountId = 1L; + List patchLocations + = Stub.MockLocation.getStubPatchLocationResponses(); + //gardenInfo, patchLocations의 위치 정보를 포함하고 있는 plantObjs를 목 데이터로 가지고 있음. + PlantObjDto.GardenInfoResponse gardenInfo + = Stub.MockPlantObj.getStubGardenInfo(); + given(plantObjService.findAllGardenInfo(anyLong())).willReturn(gardenInfo); + //when + ResultActions actions = mockMvc.perform( + patch(DEFAULT_URL+"/{account-id}/location", accountId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(gson.toJson(patchLocations)) + ); + //then + actions.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.plantObjs[0].location.locationId", + is(accountId.intValue()))) + .andDo(print()); + } + + @DisplayName("오브젝트와 식물 카드 연결 / 해제 / 교체") + @Test + void patchObjConnectionToLeafTest() throws Exception { + //given + Long accountId = 1L; + Long plantObjId = 1L; + Long leafId = 1L; + PlantObjDto.Response response = Stub.MockPlantObj.getStubPlantObjResponseDto1(); + given(plantObjService.updateLeafConnection(anyLong(), anyLong(), anyLong())) + .willReturn(response); + //when + ResultActions actions = mockMvc.perform( + patch(DEFAULT_URL+"/{account-id}/connection", accountId) + .param("plantobj-id", String.valueOf(plantObjId)) + .param("leaf-id", String.valueOf(leafId)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + //then + actions.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.leafDto.id", is(leafId.intValue()))) + .andDo(print()); + } +} diff --git a/server/src/test/java/com/growstory/domain/plant_object/service/PlantObjServiceTest.java b/server/src/test/java/com/growstory/domain/plant_object/service/PlantObjServiceTest.java new file mode 100644 index 00000000..3e053d3f --- /dev/null +++ b/server/src/test/java/com/growstory/domain/plant_object/service/PlantObjServiceTest.java @@ -0,0 +1,407 @@ +package com.growstory.domain.plant_object.service; + +import com.growstory.domain.account.entity.Account; +import com.growstory.domain.account.service.AccountService; +import com.growstory.domain.leaf.dto.LeafDto; +import com.growstory.domain.leaf.entity.Leaf; +import com.growstory.domain.leaf.service.LeafService; +import com.growstory.domain.plant_object.dto.PlantObjDto; +import com.growstory.domain.plant_object.entity.PlantObj; +import com.growstory.domain.plant_object.location.dto.LocationDto; +import com.growstory.domain.plant_object.location.entity.Location; +import com.growstory.domain.plant_object.location.service.LocationService; +import com.growstory.domain.plant_object.mapper.PlantObjMapper; +import com.growstory.domain.plant_object.repository.PlantObjRepository; +import com.growstory.domain.point.entity.Point; +import com.growstory.domain.product.dto.ProductDto; +import com.growstory.domain.product.entity.Product; +import com.growstory.domain.product.service.ProductService; +import com.growstory.domain.stubdata.Stub; +import com.growstory.global.auth.utils.AuthUserUtils; +import com.growstory.global.customUser.annotation.WithMockCustomUser; +import com.growstory.global.exception.BusinessLogicException; +import com.growstory.global.exception.ExceptionCode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.BDDMockito.*; + + +@ExtendWith(MockitoExtension.class) +public class PlantObjServiceTest { + + @InjectMocks + private PlantObjService plantObjService; + @Mock + private PlantObjRepository plantObjRepository; + @Mock + private ProductService productService; + @Mock + private AccountService accountService; + @Mock + private LocationService locationService; + @Mock + private LeafService leafService; + @Mock + private PlantObjMapper plantObjMapper; + @Mock + private AuthUserUtils authUserUtils; + + @BeforeEach + public void init() { + System.out.println("=".repeat(10) +"PlantObjServiceTest init"+"=".repeat(10)); + } + + @DisplayName("buyProduct Test : 구매 금액이 충분하지 않을 때") + @WithMockCustomUser(accountId = 1L, displayName = "관리자", email = "admin@gmail.com", password = "1234", profileImageUrl = "112", roles = "{ADMIN, USER}") + @Test + public void testBuyProduct_구매금액_불충분() { + //given + Long gardenAccountId = 1L; + Long productId = 1L; + // 물건을 구입할 계정은 0 포인트를 가지고 있음 + Account findAccount = Stub.MockAccount.getStubAccount(); + Point mockPoint = Stub.MockPoint.getStubPointResponseDtoWithNoScore(); + findAccount.updatePoint(mockPoint); + // 가격이 500원인 Product + Product boughtProduct = Stub.MockProduct.getStubProduct1(); + PlantObj boughtPlantObj = Stub.MockPlantObj.getStubPlantObj1(); + // 스텁 데이터 + given(authUserUtils.getAuthUser()).willReturn(findAccount); + given(productService.findVerifiedProduct(Mockito.anyLong())) + .willReturn(boughtProduct); + // given(plantObjRepository.save(Mockito.any(PlantObj.class))) + // .willReturn(boughtPlantObj); + + //when + BusinessLogicException exception = assertThrows(BusinessLogicException.class, + () -> plantObjService.buyProduct(gardenAccountId, productId)); + int httpStatusCode = exception.getExceptionCode().getStatus(); + + //then + assertThat(exception.getClass(), is(BusinessLogicException.class)); + assertThat(httpStatusCode, is(403)); + } + + @DisplayName("buyProduct Test : 성공") +// @WithMockCustomUser(accountId = 3L, displayName = "관리자", email = "admin@gmail.com", password = "1234", profileImageUrl = "112", roles = "{ADMIN, USER}") + @Test + public void testBuyProduct_구매금액_충분_성공() { + //given + Long gardenAccountId = 1L; + Long productId = 1L; + // 물건을 구입할 계정은 500 포인트를 가지고 있음 + Account findAccount = Stub.MockAccount.getStubAccount(); + Point mockPoint = Stub.MockPoint.getStubPointResponseDtoWith500Score(); + findAccount.updatePoint(mockPoint); + // 가격이 500원인 Product + Product boughtProduct = Stub.MockProduct.getStubProduct1(); + PlantObj boughtPlantObj = Stub.MockPlantObj.getStubPlantObj1(); + + // Mock 리턴 + willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); + given(authUserUtils.getAuthUser()).willReturn(findAccount); + given(productService.findVerifiedProduct(Mockito.anyLong())) + .willReturn(boughtProduct); + given(plantObjRepository.save(Mockito.any(PlantObj.class))) + .willReturn(boughtPlantObj); + + //when + plantObjService.buyProduct(gardenAccountId, productId); + + //then + verify(plantObjRepository).save(Mockito.any(PlantObj.class)); + assertThat(findAccount.getPlantObjs(), hasItem(boughtPlantObj)); + assertThat(findAccount.getPoint().getScore(), is(0)); + } + + @DisplayName("refundPlantObj test : 사용자 소유의 환불 가능 품목이 없을 때") +// @WithMockUser + @Test + public void testRefundPlantObj_환불_가능_품목_없음() { + //given + willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); + + Long accountId = 1L; + Long plantObjId = 1L; + Account findAccount = Stub.MockAccount.getStubAccount(); + // 0 Point 매핑 (이미 물건을 구입 했다고 가정) + Point point = Stub.MockPoint.getStubPointResponseDtoWithNoScore(); + findAccount.updatePoint(point); + PlantObj plantObj = Stub.MockPlantObj.getStubPlantObj1(); + // findAccount.addPlantObj(plantObj)를 실행하지 않는다. + // 목 데이터 리턴 + given(authUserUtils.getAuthUser()).willReturn(findAccount); + given(plantObjRepository.findById(Mockito.anyLong())) + .willReturn(Optional.of(plantObj)); + //when + BusinessLogicException exception = assertThrows(BusinessLogicException.class, + () -> plantObjService.refundPlantObj(accountId, plantObjId)); + //then + //환불 가능 품목이 존재하지 않을 때 PLANT_OBJ_NOT_FOUND 반환 + assertThat(exception.getExceptionCode(), is(ExceptionCode.PLANT_OBJ_NOT_FOUND)); + } + + @DisplayName("refundPlantObj test : 사용자 소유의 환불 가능 품목이 존재할 때") + @Test + public void testRefundPlantObj_환불_성공() { + //given + willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); + + Long accountId = 1L; + Account findAccount = Stub.MockAccount.getStubAccount(); + // 0 Point 매핑 (이미 물건을 구입 했다고 가정) + Point point = Stub.MockPoint.getStubPointResponseDtoWithNoScore(); + findAccount.updatePoint(point); + PlantObj plantObj1 = Stub.MockPlantObj.getStubPlantObj1(); + PlantObj plantObj2 = Stub.MockPlantObj.getStubPlantObj2(); + findAccount.addPlantObj(plantObj1); + findAccount.addPlantObj(plantObj2); + // 목 데이터 리턴 + given(authUserUtils.getAuthUser()).willReturn(findAccount); + given(plantObjRepository.findById(Mockito.anyLong())) + .willReturn(Optional.of(plantObj2)); + //when + plantObjService.refundPlantObj(accountId, plantObj2.getPlantObjId()); + //then + assertThat(findAccount.getPoint().getScore(), is(plantObj2.getProduct().getPrice())); + } + + + @DisplayName("findAllGardenInfo Test : 전체 정원 정보 조회 성공") + @Test + public void testFindAllGardenInfo_전체_정원_정보조회_성공() { + //given + //정원 소유주 accountId + Long gardenAccountId = 1L; + //정원 소유자와 Point 목 데이터 매핑 + Account findAccount = Stub.MockAccount.getStubAccount(); + Point userPoint = Stub.MockPoint.getStubPointResponseDtoWith500Score(); + findAccount.updatePoint(userPoint); + //소유 PlantObjs 목 데이터 매핑 + List plantObjList = Stub.MockPlantObj.getStubPlantObjs(); + plantObjList.stream().forEach(findAccount::addPlantObj); + List plantObjResponseList = Stub.MockPlantObj.getStubPlantObjsResponseDtos(); + //상품 리스트 + List products = Stub.MockProduct.getStubProductResponses(); + //스텁 데이터 동작 지정 + given(accountService.findVerifiedAccount(Mockito.anyLong())).willReturn(findAccount); + given(productService.findAllProducts()).willReturn(products); + given(plantObjMapper.toPlantObjResponseList(Mockito.anyList())).willReturn(plantObjResponseList); + + //when + PlantObjDto.GardenInfoResponse gardenInfo = plantObjService.findAllGardenInfo(gardenAccountId); + + //then + assertThat(gardenInfo.getPoint().getScore(), is(userPoint.getScore())); + assertThat(gardenInfo.getDisplayName(), is(findAccount.getDisplayName())); + assertThat(gardenInfo.getProducts(), is(products)); + assertThat(gardenInfo.getPlantObjs().get(0).getPlantObjId(), is(plantObjList.get(0).getPlantObjId())); + } + + @DisplayName("saveLocation Test : 프로덕트 id와 로케이션 id 불일치") + @Test + public void testSaveLocation_프로덕트id_로케이션id_불일치() { + //given + willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); + Long accountId = 1L; + List patchLocations = Stub.MockLocation.getStubPatchLocationResponses(); + patchLocations.add(PlantObjDto.PatchLocation.builder() + .plantObjId(3L) + .locationDto(LocationDto.Patch.builder() + .locationId(99L).x(0).y(0).isInstalled(false) + .build()) + .build()); + //when + BusinessLogicException exception = assertThrows(BusinessLogicException.class, + () -> plantObjService.saveLocation(accountId, patchLocations)); + //then + assertThat(exception.getExceptionCode(), is(ExceptionCode.LOCATION_NOT_ALLOW)); + } + + @DisplayName("saveLocation Test : X축에 부적절한 위치 삽입1 (+좌표)") + @Test + public void testSaveLocation_X축_부적절한_위치_삽입() { + //given + willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); + Long accountId = 1L; + List patchLocations = Stub.MockLocation.getStubPatchLocationResponses(); + patchLocations.add(PlantObjDto.PatchLocation.builder() + .plantObjId(3L) + .locationDto(LocationDto.Patch.builder() + .locationId(3L).x(12).y(0).isInstalled(false) + .build()) + .build()); + //when + BusinessLogicException exception = assertThrows(BusinessLogicException.class, + () -> plantObjService.saveLocation(accountId, patchLocations)); + //then + assertThat(exception.getExceptionCode(), is(ExceptionCode.INVALID_LOCATION)); + } + + @DisplayName("saveLocation Test : X축에 부적절한 위치 삽입 (-좌표)") + @Test + public void testSaveLocation_X축_부적절한_위치_삽입2() { + //given + willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); + Long accountId = 1L; + List patchLocations = Stub.MockLocation.getStubPatchLocationResponses(); + patchLocations.add(PlantObjDto.PatchLocation.builder() + .plantObjId(3L) + .locationDto(LocationDto.Patch.builder() + .locationId(3L).x(-1).y(0).isInstalled(false) + .build()) + .build()); + //when + BusinessLogicException exception = assertThrows(BusinessLogicException.class, + () -> plantObjService.saveLocation(accountId, patchLocations)); + //then + assertThat(exception.getExceptionCode(), is(ExceptionCode.INVALID_LOCATION)); + } + + @DisplayName("saveLocation Test : Y축에 부적절한 위치 삽입 (+좌표)") + @Test + public void testSaveLocation_Y축_부적절한_위치_삽입1() { + //given + willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); + Long accountId = 1L; + List patchLocations = Stub.MockLocation.getStubPatchLocationResponses(); + patchLocations.add(PlantObjDto.PatchLocation.builder() + .plantObjId(3L) + .locationDto(LocationDto.Patch.builder() + .locationId(3L).x(0).y(99).isInstalled(false) + .build()) + .build()); + //when + BusinessLogicException exception = assertThrows(BusinessLogicException.class, + () -> plantObjService.saveLocation(accountId, patchLocations)); + //then + assertThat(exception.getExceptionCode(), is(ExceptionCode.INVALID_LOCATION)); + } + + @DisplayName("saveLocation Test : Y축에 부적절한 위치 삽입 (-좌표)") + @Test + public void testSaveLocation_Y축_부적절한_위치_삽입2() { + //given + willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); + Long accountId = 1L; + List patchLocations = Stub.MockLocation.getStubPatchLocationResponses(); + patchLocations.add(PlantObjDto.PatchLocation.builder() + .plantObjId(3L) + .locationDto(LocationDto.Patch.builder() + .locationId(3L).x(0).y(-1).isInstalled(false) + .build()) + .build()); + //when + BusinessLogicException exception = assertThrows(BusinessLogicException.class, + () -> plantObjService.saveLocation(accountId, patchLocations)); + //then + assertThat(exception.getExceptionCode(), is(ExceptionCode.INVALID_LOCATION)); + } + + @DisplayName("saveLocation Test : 성공") + @Test + public void testSaveLocation_성공() { + //given + willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); + Long accountId = 1L; + List patchLocations = new ArrayList<>(); + PlantObjDto.PatchLocation patchLocation = Stub.MockLocation.getStubPatchLocation1(); + patchLocations.add(patchLocation); + // PatchLocation 객체에 의해 저장된 Location 엔티티 객체 + Location savedLocation = Stub.MockLocation.getStubLocation(); + given(locationService.updateLocation(Mockito.any())).willReturn(savedLocation); + //when + plantObjService.saveLocation(accountId,patchLocations); + //then + assertThat(savedLocation.getLocationId(), is(patchLocation.getLocationDto().getLocationId())); + assertThat(savedLocation.getX(), is(patchLocation.getLocationDto().getX())); + assertThat(savedLocation.getY(), is(patchLocation.getLocationDto().getY())); + assertThat(savedLocation.isInstalled(), is(patchLocation.getLocationDto().isInstalled())); + } + + @DisplayName("updateLeafConnection Test : leafId가 null일 경우 연결해제") + @Test + public void testUpdateLeafConnection_Leaf_연결해제() { + //given + willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); + Long accountId = 1L; + Long plantObjId = 1L; + Long leafId = null; + PlantObj findPlantObj = Stub.MockPlantObj.getStubPlantObj1(); + findPlantObj.updateLeaf(Stub.MockLeaf.getStubLeaf()); + given(plantObjRepository.findById(Mockito.anyLong())).willReturn(Optional.of(findPlantObj)); + given(plantObjMapper.toPlantObjResponse(Mockito.any(PlantObj.class))) + .willReturn(PlantObjDto.Response.builder() + .plantObjId(findPlantObj.getPlantObjId()) + .leafDto(null) + .build()); + //when + PlantObjDto.Response response = plantObjService.updateLeafConnection(accountId, plantObjId, leafId); + + //then + verify(leafService, never()).findLeafEntityBy(Mockito.anyLong()); +// verify(mock(findPlantObj.getClass()), times(1)).updateLeaf(null); + assertThat(response, is(notNullValue())); + } + + @DisplayName("updateLeafConnection Test : plantObj 미확인 예외발생") + @Test + public void testUpdateLeafConnection_Leaf_PlantObj_미확인() { + //given + willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); + Long accountId = 1L; + Long plantObjId = 99L; + Long leafId = 1L; + given(plantObjRepository.findById(Mockito.anyLong())) + .willReturn(Optional.empty()); + + //when + BusinessLogicException exception = assertThrows(BusinessLogicException.class, + () -> plantObjService.updateLeafConnection(accountId, plantObjId, leafId)); + + //then + assertThat(exception.getExceptionCode(), is(ExceptionCode.PLANT_OBJ_NOT_FOUND)); + } + + @DisplayName("updateLeafConnection Test : leafId가 null이 아닐 경우 연결 성공") + @Test + public void testUpdateLeafConnection_Leaf_연결성공() { + //given + willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); + Long accountId = 1L; + Long plantObjId = 1L; + Long leafId = 1L; + PlantObj findPlantObj = Stub.MockPlantObj.getStubPlantObj1(); + Leaf findLeaf = Stub.MockLeaf.getStubLeaf(); + findPlantObj.updateLeaf(findLeaf); + LeafDto.ResponseForGardenInfo leafResponseDto = Stub.MockLeaf.getStubLeafResponseDto(); + given(plantObjRepository.findById(Mockito.anyLong())).willReturn(Optional.of(findPlantObj)); + given(leafService.findLeafEntityBy(leafId)).willReturn(findLeaf); + given(plantObjMapper.toPlantObjResponse(Mockito.any(PlantObj.class))) + .willReturn(PlantObjDto.Response.builder() + .plantObjId(findPlantObj.getPlantObjId()) + .leafDto(leafResponseDto) + .build()); + //when + PlantObjDto.Response response = plantObjService.updateLeafConnection(accountId, plantObjId, leafId); + + //then + verify(leafService).findLeafEntityBy(Mockito.anyLong()); + assertThat(response, is(notNullValue())); + assertThat(response.getLeafDto().getId(), is(leafId)); + } +} diff --git a/server/src/test/java/com/growstory/domain/stubdata/Stub.java b/server/src/test/java/com/growstory/domain/stubdata/Stub.java new file mode 100644 index 00000000..deb86635 --- /dev/null +++ b/server/src/test/java/com/growstory/domain/stubdata/Stub.java @@ -0,0 +1,499 @@ +package com.growstory.domain.stubdata; + +import com.growstory.domain.account.dto.AccountDto; +import com.growstory.domain.account.entity.Account; +import com.growstory.domain.board.entity.Board; +import com.growstory.domain.images.entity.JournalImage; +import com.growstory.domain.journal.dto.JournalDto; +import com.growstory.domain.journal.entity.Journal; +import com.growstory.domain.leaf.dto.LeafDto; +import com.growstory.domain.leaf.entity.Leaf; +import com.growstory.domain.likes.entity.BoardLike; +import com.growstory.domain.plant_object.dto.PlantObjDto; +import com.growstory.domain.plant_object.entity.PlantObj; +import com.growstory.domain.plant_object.location.dto.LocationDto; +import com.growstory.domain.plant_object.location.entity.Location; +import com.growstory.domain.point.entity.Point; +import com.growstory.domain.product.dto.ProductDto; +import com.growstory.domain.product.entity.Product; +import org.springframework.http.HttpMethod; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Stub { + + public static class MockAccount { + private static Map stubRequestBody; + static { + stubRequestBody = new HashMap<>(); + stubRequestBody.put(HttpMethod.POST, AccountDto.Post.builder() + .displayName("관리자") + .email("admin@gmail.com") + .password("1111") + .build()); + } + + public static Object getRequestBody(HttpMethod method) { + return stubRequestBody.get(method); + } + + public static Account getStubAccount() { + return Account.builder() + .accountId(1L) + .displayName("김닉네임") + .profileImageUrl("https://growstory.s3.ap-northeast-2.amazonaws.com/image/profiles/" + + "e617d918-a5e4-479b-9e3d-386e5346c184book-1822474__340.jpg") +// .point(MockPoint.getStubPointResponseDtoWith500Score()) + .plantObjs(new ArrayList<>()) + .build(); + } + + public static AccountDto.Response getSingleResponseBody() { + return AccountDto.Response + .builder() + .accountId(1L) +// .boardLiked(boardLikes) +// .boardWritten(boardWrittens) + .displayName("김닉네임") + .profileImageUrl("https://growstory.s3.ap-northeast-2.amazonaws.com/image/profiles/" + + "e617d918-a5e4-479b-9e3d-386e5346c184book-1822474__340.jpg") +// .commentWritten(commentWrittens) + .point(Point.builder().pointId(1L).score(500).build()) + .build(); + } + } + + public static class MockGarden { + public static PlantObjDto.GardenInfoResponse getStubGardenInfo() { + return PlantObjDto.GardenInfoResponse.builder() + .displayName(MockAccount.getSingleResponseBody().getDisplayName()) + .products(MockProduct.getStubProductResponses()) +// .plantObjs(MockPlantObj.getStubPlantObjsResponseDtos()) +// .point(MockPoint.getStubPointResponseDto()) + .build(); + } + } + + public static class MockProduct { + public static Product getStubProduct1() { + return Product.builder() + .productId(1L) + .name("building_brown") + .korName("벽돌 유적") + .price(500) + .imageUrlSmall("https://growstory.s3.ap-northeast-2.amazonaws.com/image/products/building_brown_sm.svg") + .imageUrlLarge("https://growstory.s3.ap-northeast-2.amazonaws.com/image/products/building_brown_lg.svg") + .build(); + } + + public static Product getStubProduct2() { + return Product.builder() + .productId(2L) + .name("building_yellow") + .korName("콜로세움") + .price(500) + .imageUrlSmall("https://growstory.s3.ap-northeast-2.amazonaws.com/image/products/building_yellow_sm.svg") + .imageUrlLarge("https://growstory.s3.ap-northeast-2.amazonaws.com/image/products/building_yellow_lg.svg") + .build(); + } + + public static ProductDto.ImageUrlTable getStubImageUrlTable1() { + return new ProductDto.ImageUrlTable( + getStubProduct1().getImageUrlSmall(), + getStubProduct1().getImageUrlLarge()); + } + + public static ProductDto.ImageUrlTable getStubImageUrlTable2() { + return new ProductDto.ImageUrlTable( + getStubProduct2().getImageUrlSmall(), + getStubProduct2().getImageUrlLarge()); + } + + public static List getStubProductResponses() { + List productResponseDtos = new ArrayList<>(); + productResponseDtos.add( + ProductDto.Response.builder() + .productId(getStubProduct1().getProductId()) + .imageUrlTable(getStubImageUrlTable1()) + .price(getStubProduct1().getPrice()) + .korName(getStubProduct1().getKorName()) + .name(getStubProduct1().getName()) + .build()); + productResponseDtos.add( + ProductDto.Response.builder() + .productId(getStubProduct2().getProductId()) + .imageUrlTable(getStubImageUrlTable2()) + .price(getStubProduct2().getPrice()) + .korName(getStubProduct2().getKorName()) + .name(getStubProduct2().getName()) + .build()); + return productResponseDtos; + } + } + + public static class MockPlantObj { + + public static PlantObj getStubPlantObj1() { + return PlantObj.builder() + .plantObjId(1L) + .product(MockProduct.getStubProduct1()) +// .account(MockAccount.getStubAccount()) +// .leaf(MockLeaf.getStubLeaf()) + .build(); + } + + public static PlantObj getStubPlantObj2() { + return PlantObj.builder() + .plantObjId(2L) + .product(MockProduct.getStubProduct2()) + .build(); + } + + public static List getStubPlantObjs() { + ArrayList plantObjs = new ArrayList<>(); + plantObjs.add(getStubPlantObj1()); + plantObjs.add(getStubPlantObj2()); + return plantObjs; + } + + public static PlantObjDto.Response getStubPlantObjResponseDto1() { + PlantObjDto.PatchLocation location1 = MockLocation.getStubPatchLocation1(); + LeafDto.ResponseForGardenInfo leaf1 = MockLeaf.getStubLeafResponseDto(); + return PlantObjDto.Response.builder() + .productId(MockAccount.getStubAccount().getAccountId()) + .plantObjId(MockProduct.getStubProduct1().getProductId()) + .productName(MockProduct.getStubProduct1().getName()) + .korName(MockProduct.getStubProduct1().getKorName()) + .price(MockProduct.getStubProduct1().getPrice()) + .location(LocationDto.Response.builder() + .locationId(location1.getLocationDto().getLocationId()) // 1 + .x(location1.getLocationDto().getX()) //5 + .y(location1.getLocationDto().getY()) //6 + .isInstalled(location1.getLocationDto().isInstalled()) //true + .build()) + .leafDto(LeafDto.ResponseForGardenInfo.builder() + .id(leaf1.getId()) + .build()) + .imageUrlTable(MockProduct.getStubImageUrlTable1()) + .build(); + } + public static PlantObjDto.Response getStubPlantObjResponseDto2() { + PlantObjDto.PatchLocation location2 = MockLocation.getStubPatchLocation2(); + return PlantObjDto.Response.builder() + .productId(MockAccount.getStubAccount().getAccountId()) + .plantObjId(MockProduct.getStubProduct2().getProductId()) + .productName(MockProduct.getStubProduct2().getName()) + .korName(MockProduct.getStubProduct2().getKorName()) + .price(MockProduct.getStubProduct2().getPrice()) + .location(LocationDto.Response.builder() + .locationId(location2.getLocationDto().getLocationId()) // 2 + .x(location2.getLocationDto().getX()) //0 + .y(location2.getLocationDto().getY()) //0 + .isInstalled(location2.getLocationDto().isInstalled()) //false + .build()) +// .leafDto(MockLeaf.getStubLeafResponseDto()) + .imageUrlTable(MockProduct.getStubImageUrlTable2()) + .build(); + } + + public static List getStubPlantObjsResponseDtos() { + + return List.of(getStubPlantObjResponseDto1(), + getStubPlantObjResponseDto2()); + } + + public static PlantObjDto.GardenInfoResponse getStubGardenInfo() { + return PlantObjDto.GardenInfoResponse.builder() + .plantObjs(getStubPlantObjsResponseDtos()) + .build(); + } + + public static PlantObjDto.TradeResponse getStubTradeResponse() { + return PlantObjDto.TradeResponse.builder() + .plantObj(getStubPlantObjResponseDto1()) + .build(); + } + } + + public static class MockLocation { + public static LocationDto.Response getStubLocationResponseDto1() { + return LocationDto.Response.builder() + .locationId(1L) + .x(0) + .y(0) + .isInstalled(false) + .build(); + } + public static LocationDto.Response getStubLocationResponseDto2() { + return LocationDto.Response.builder() + .locationId(2L) + .x(4) + .y(8) + .isInstalled(true) + .build(); + } + public static PlantObjDto.PatchLocation getStubPatchLocation1() { + return PlantObjDto.PatchLocation.builder() + .plantObjId(1L) + .locationDto(LocationDto.Patch.builder() + .locationId(1L).x(5).y(6).isInstalled(true).build()) + .build(); + } + public static PlantObjDto.PatchLocation getStubPatchLocation2() { + return PlantObjDto.PatchLocation.builder() + .plantObjId(2L) + .locationDto(LocationDto.Patch.builder() + .locationId(2L).x(0).y(0).isInstalled(false).build()) + .build(); + } + public static List getStubPatchLocationResponses() { + List patchLocations = new ArrayList<>(); + patchLocations.add(getStubPatchLocation1()); + patchLocations.add(getStubPatchLocation2()); + + return patchLocations; + } + + public static Location getStubLocation() { + PlantObjDto.PatchLocation patchLocation = getStubPatchLocation1(); + + return Location.builder() + .locationId(patchLocation.getLocationDto().getLocationId()) + .x(patchLocation.getLocationDto().getX()) + .y(patchLocation.getLocationDto().getY()) + .isInstalled(patchLocation.getLocationDto().isInstalled()) + .build(); + } + } + + public static class MockLeaf { + public static Leaf getStubLeaf() { + return Leaf.builder() + .leafId(1L) + .leafName("월동자 선인장") + .leafImageUrl("https://growstory.s3.ap-northeast-2.amazonaws.com/image/leaves/4b8b4998-bf12-4c1c-a6ec-dfcaa1dcd339book-1822474__340.jpg") + .content("나의 월동자 선인장은 귀엽다.") +// .account(MockAccount.getStubAccount()) + .journals(null) +// .plantObj(MockPlantObj.getStubPlantObj()) + .build(); + } + public static LeafDto.ResponseForGardenInfo getStubLeafResponseDto() { + return LeafDto.ResponseForGardenInfo.builder() + .id(getStubLeaf().getLeafId()) + .name(getStubLeaf().getLeafName()) + .imageUrl(getStubLeaf().getLeafImageUrl()) + .journalCount(MockJournal.getStubJournalResponseDtos().size()) + .build(); + } + } + + public static class MockJournal { + public static Journal getStubJournal1() { + return Journal.builder() + .journalId(1L) + .title("230909 일지") + .content("오늘은 물을 줬다.") +// .journalImage(MockJournalImage.getStubJournalImage1()) + .leaf(MockLeaf.getStubLeaf()) + .build(); + } + public static Journal getStubJournal2() { + return Journal.builder() + .journalId(2L) + .title("230910 일지") + .content("오늘은 물을 안줬다.") +// .journalImage(MockJournalImage.getStubJournalImage2()) + .leaf(MockLeaf.getStubLeaf()) + .build(); + } + public static JournalDto.Response getStubJournalResponse1() { + LocalDateTime dateTime = LocalDateTime.of(2023, 9, 9, 14, 30, 0); + return JournalDto.Response.builder() + .journalId(getStubJournal1().getJournalId()) + .title(getStubJournal1().getTitle()) + .content(getStubJournal1().getContent()) + .imageUrl(MockJournalImage.getStubJournalImage1().getImageUrl()) + .createdAt(dateTime) + .build(); + } + public static JournalDto.Response getStubJournalResponse2() { + LocalDateTime dateTime = LocalDateTime.of(2023, 9, 10, 14, 30, 0); + return JournalDto.Response.builder() + .journalId(getStubJournal2().getJournalId()) + .title(getStubJournal2().getTitle()) + .content(getStubJournal2().getContent()) + .imageUrl(MockJournalImage.getStubJournalImage2().getImageUrl()) + .createdAt(dateTime) + .build(); + } + public static List getStubJournalResponseDtos() { + return List.of(getStubJournalResponse1(), getStubJournalResponse2()); + } + } + + public static class MockJournalImage { + public static JournalImage getStubJournalImage1() { + return JournalImage.builder() + .journalImageId(1L) + .journal(MockJournal.getStubJournal1()) + .originName("cdde9ac4bc61logo") + .imageUrl("https://growstory.s3.ap-northeast-2.amazonaws.com/image/journal_image/1a4ee5d0-3f55-40c6-aff9-cdde9ac4bc61logo.png") + .build(); + } + public static JournalImage getStubJournalImage2() { + return JournalImage.builder() + .journalImageId(2L) + .journal(MockJournal.getStubJournal2()) + .originName("cdde9ac4bc61logo") + .imageUrl("https://growstory.s3.ap-northeast-2.amazonaws.com/image/journal_image/1a4ee5d0-3f55-40c6-aff9-cdde9ac4bc61logo.png") + .build(); + } + + + } + + public static class MockPoint { + public static Point getStubPointResponseDtoWith500Score() { + return Point.builder() + .score(500) + .build(); + } + public static Point getStubPointResponseDtoWithNoScore() { + return Point.builder() + .score(0) + .build(); + } + } + + public static class MockBoardLikes { + public static BoardLike getBoardLike1_1() { + Account account1 = Account.builder() + .accountId(1L).displayName("김별명1").build(); + return BoardLike.builder() + .boardLikeId(1L) + .account(account1) + .board(Board.builder().boardId(1L).build()) + .build(); + } + public static BoardLike getBoardLike1_2() { + Account account1 = Account.builder() + .accountId(2L).displayName("김별명2").build(); + return BoardLike.builder() + .boardLikeId(2L) + .account(account1) + .board(Board.builder().boardId(1L).build()) + .build(); + } + public static BoardLike getBoardLike1_3() { + Account account1 = Account.builder() + .accountId(3L).displayName("김별명3").build(); + return BoardLike.builder() + .boardLikeId(3L) + .account(account1) + .board(Board.builder().boardId(1L).build()) + .build(); + } + + public static BoardLike getBoardLike2_1() { + Account account1 = Account.builder() + .accountId(1L).displayName("김별명1").build(); + return BoardLike.builder() + .boardLikeId(1L) + .account(account1) + .board(Board.builder().boardId(2L).build()) + .build(); + } + public static BoardLike getBoardLike2_2() { + Account account1 = Account.builder() + .accountId(2L).displayName("김별명2").build(); + return BoardLike.builder() + .boardLikeId(2L) + .account(account1) + .board(Board.builder().boardId(2L).build()) + .build(); + } + + public static BoardLike getBoardLike3_1() { + Account account1 = Account.builder() + .accountId(1L).displayName("김별명1").build(); + return BoardLike.builder() + .boardLikeId(1L) + .account(account1) + .board(Board.builder().boardId(3L).build()) + .build(); + } + + public static List getBoardLikes1() { + List boardLikes = new ArrayList<>(); + boardLikes.add(getBoardLike1_1()); + boardLikes.add(getBoardLike1_2()); + boardLikes.add(getBoardLike1_3()); + return boardLikes; + } + + public static List getBoardLikes2() { + List boardLikes = new ArrayList<>(); + boardLikes.add(getBoardLike2_1()); + boardLikes.add(getBoardLike2_2()); + return boardLikes; + } + + public static List getBoardLikes3() { + List boardLikes = new ArrayList<>(); + boardLikes.add(getBoardLike3_1()); + return boardLikes; + } + } + + public static class MockBoard { + + public static Board getMockBoard1() { + return Board.builder() + .account(Account.builder().accountId(1L).displayName("김별명1").build()) + .boardId(1L) + .title("제목1") + .content("내용1") + .build(); + } + + public static Board getMockBoard2() { + return Board.builder() + .account(Account.builder().accountId(2L).displayName("김별명2").build()) + .boardId(2L) + .title("제목2") + .content("내용2") + .build(); + } + + public static Board getMockBoard3() { + return Board.builder() + .account(Account.builder().accountId(3L).displayName("김별명3").build()) + .boardId(3L) + .title("제목3") + .content("내용3") + .build(); + } + public static Board getMockBoard4() { + return Board.builder() + .account(Account.builder().accountId(4L).displayName("김별명4").build()) + .boardId(4L) + .title("제목4") + .content("내용4") + .build(); + } + public static Board getMockBoard5() { + return Board.builder() + .account(Account.builder().accountId(5L).displayName("김별명5").build()) + .boardId(5L) + .title("제목4") + .content("내용4") + .build(); + } + + } +} diff --git a/server/src/test/java/com/growstory/global/customUser/annotation/WithMockCustomUser.java b/server/src/test/java/com/growstory/global/customUser/annotation/WithMockCustomUser.java new file mode 100644 index 00000000..d6167f3d --- /dev/null +++ b/server/src/test/java/com/growstory/global/customUser/annotation/WithMockCustomUser.java @@ -0,0 +1,19 @@ +package com.growstory.global.customUser.annotation; + +import com.growstory.global.customUser.factory.WithMockCustomUserSecurityContextFactory; +import org.springframework.security.test.context.support.WithSecurityContext; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class) +public @interface WithMockCustomUser { + long accountId() default 1L; + String email() default "admin@gmail.com"; + String displayName() default "관리자"; + String password() default "admin1234"; + String profileImageUrl() default ""; + String[] roles() default {"USER", "ADMIN"}; + +} diff --git a/server/src/test/java/com/growstory/global/customUser/factory/WithMockCustomUserSecurityContextFactory.java b/server/src/test/java/com/growstory/global/customUser/factory/WithMockCustomUserSecurityContextFactory.java new file mode 100644 index 00000000..bc444564 --- /dev/null +++ b/server/src/test/java/com/growstory/global/customUser/factory/WithMockCustomUserSecurityContextFactory.java @@ -0,0 +1,46 @@ +package com.growstory.global.customUser.factory; + +import com.growstory.global.auth.utils.CustomAuthorityUtils; +import com.growstory.global.customUser.annotation.WithMockCustomUser; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.test.context.support.WithSecurityContextFactory; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory { + private static CustomAuthorityUtils authorityUtils; + private static PasswordEncoder passwordEncoder; + + public WithMockCustomUserSecurityContextFactory(CustomAuthorityUtils authorityUtils, PasswordEncoder passwordEncoder) { + this.authorityUtils = authorityUtils; + this.passwordEncoder = passwordEncoder; + } + + @Override + public SecurityContext createSecurityContext(WithMockCustomUser customUser) { + SecurityContext context = SecurityContextHolder.createEmptyContext(); + + Map claims = new HashMap<>(); + claims.put("accountId", customUser.accountId()); + claims.put("username", customUser.email()); + claims.put("displayName", customUser.displayName()); + claims.put("profileImageUrl", customUser.profileImageUrl()); + claims.put("roles", customUser.roles()); + System.out.println(Arrays.asList((String[]) claims.get("roles"))); + List authorities = authorityUtils.createAuthorities(Arrays.asList((String[]) claims.get("roles"))); + // 인증 토큰을 만들어 authentication으로 어퍼 캐스팅하여 SecurityContextHolder에 저장한다. + Authentication authentication = new UsernamePasswordAuthenticationToken(claims, passwordEncoder.encode(customUser.password()), authorities); + context.setAuthentication(authentication); + + return context; + } +} From 65e0aad719b046507d7dc060fc826f2202b39981 Mon Sep 17 00:00:00 2001 From: LST Date: Sat, 14 Oct 2023 23:54:54 +0900 Subject: [PATCH 007/129] :test_tube: Test: BoardRankingServiceTest , JournalControllerTest --- .../journal/controller/JournalController.java | 10 +- .../domain/journal/dto/JournalDto.java | 10 +- .../growstory/global/utils/UriCreator.java | 11 + .../service/BoardRankingServiceTest.java | 193 ++++++++++++++++++ .../board/service/BoardRankingTest.java | 63 ------ .../board/service/BoardServiceTest.java | 190 +++-------------- .../domain/journal/JournalControllerTest.java | 136 +++++++++++- 7 files changed, 379 insertions(+), 234 deletions(-) create mode 100644 server/src/test/java/com/growstory/domain/board/service/BoardRankingServiceTest.java delete mode 100644 server/src/test/java/com/growstory/domain/board/service/BoardRankingTest.java diff --git a/server/src/main/java/com/growstory/domain/journal/controller/JournalController.java b/server/src/main/java/com/growstory/domain/journal/controller/JournalController.java index 03617aa1..0a9c4800 100644 --- a/server/src/main/java/com/growstory/domain/journal/controller/JournalController.java +++ b/server/src/main/java/com/growstory/domain/journal/controller/JournalController.java @@ -1,6 +1,5 @@ package com.growstory.domain.journal.controller; -import com.growstory.domain.account.service.AccountService; import com.growstory.domain.journal.dto.JournalDto; import com.growstory.domain.journal.service.JournalService; import com.growstory.global.response.SingleResponseDto; @@ -24,6 +23,7 @@ public class JournalController { private final JournalService journalService; + private final UriCreator uriCreator; private static final String DEFAULT_URL = "/v1/leaves"; @@ -43,13 +43,13 @@ public ResponseEntity getJournals( // POST, 식물 일지를 등록 @PostMapping("/{leaf-id}/journals") - public ResponseEntity postJournal(@RequestPart JournalDto.LeafAuthor leafAuthor, + public ResponseEntity postJournal(@RequestPart(value = "leafAuthor") JournalDto.LeafAuthor leafAuthor, @Positive @PathVariable("leaf-id") Long leafId, - @Valid @RequestPart JournalDto.Post postDto, - @RequestPart(required = false) MultipartFile image) { + @Valid @RequestPart(value = "postDto") JournalDto.Post postDto, + @RequestPart(required = false, value = "image") MultipartFile image) { JournalDto.Response journal = journalService.createJournal(leafAuthor.getAccountId(), leafId, postDto, image); - URI location = UriCreator.createUri(DEFAULT_URL, journal.getJournalId()); + URI location = uriCreator.createUri_Test(DEFAULT_URL, journal.getJournalId()); return ResponseEntity.created(location).build(); } diff --git a/server/src/main/java/com/growstory/domain/journal/dto/JournalDto.java b/server/src/main/java/com/growstory/domain/journal/dto/JournalDto.java index f1b05b25..5ee14b8e 100644 --- a/server/src/main/java/com/growstory/domain/journal/dto/JournalDto.java +++ b/server/src/main/java/com/growstory/domain/journal/dto/JournalDto.java @@ -1,8 +1,6 @@ package com.growstory.domain.journal.dto; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; +import lombok.*; import org.springframework.lang.Nullable; import javax.validation.constraints.NotBlank; @@ -14,6 +12,9 @@ public class JournalDto { @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor public static class Post { @NotBlank String title; @@ -40,6 +41,9 @@ public static class Response { } @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor public static class LeafAuthor { long accountId; } diff --git a/server/src/main/java/com/growstory/global/utils/UriCreator.java b/server/src/main/java/com/growstory/global/utils/UriCreator.java index 16b9c516..c4ae220c 100644 --- a/server/src/main/java/com/growstory/global/utils/UriCreator.java +++ b/server/src/main/java/com/growstory/global/utils/UriCreator.java @@ -1,9 +1,11 @@ package com.growstory.global.utils; +import org.springframework.stereotype.Component; import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; +@Component public class UriCreator { public static URI createUri(String defaultUrl, long resourceId) { return UriComponentsBuilder @@ -20,4 +22,13 @@ public static URI creatUri(String defaultUrl, long resourceId1, String anotherRe .buildAndExpand(resourceId1, resourceId2) .toUri(); } + + // 테스트용 + public URI createUri_Test(String defaultUrl, long resourceId) { + return UriComponentsBuilder + .newInstance() + .path(defaultUrl+ "/{resource-id}") + .buildAndExpand(resourceId) + .toUri(); + } } \ No newline at end of file diff --git a/server/src/test/java/com/growstory/domain/board/service/BoardRankingServiceTest.java b/server/src/test/java/com/growstory/domain/board/service/BoardRankingServiceTest.java new file mode 100644 index 00000000..45a0be51 --- /dev/null +++ b/server/src/test/java/com/growstory/domain/board/service/BoardRankingServiceTest.java @@ -0,0 +1,193 @@ +package com.growstory.domain.board.service; + +import com.growstory.domain.board.repository.BoardHashTagRepository; +import com.growstory.domain.board.repository.BoardRepository; +import com.growstory.domain.board.service.BoardService; +import com.growstory.domain.comment.service.CommentService; +import com.growstory.domain.hashTag.repository.HashTagRepository; +import com.growstory.domain.hashTag.service.HashTagService; +import com.growstory.domain.images.service.BoardImageService; +import com.growstory.domain.rank.board_likes.entity.BoardLikesRank; +import com.growstory.domain.stubdata.Stub; +import com.growstory.global.auth.utils.AuthUserUtils; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +public class BoardRankingServiceTest { + + @InjectMocks + private BoardService boardService; + @Mock + private BoardRepository boardRepository; + @Mock + private HashTagService hashTagService; + @Mock + private BoardImageService boardImageService; + @Mock + private AuthUserUtils authUserUtils; + @Mock + private HashTagRepository hashTagRepository; + @Mock + private BoardHashTagRepository boardHashtagRepository; + @Mock + private CommentService commentService; + + + @DisplayName("좋아요 기준 상위 3개의 게시글 랭킹과 함께 반환") + @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class FindTop3LikedBoardRanksTest { + + //given + List mockTopBoardsWithLikes = new ArrayList<>(); + Object[] mockObjectsLike3_1 = {Stub.MockBoard.getMockBoard1(), 3L}; + Object[] mockObjectsLike3_2 = {Stub.MockBoard.getMockBoard2(), 3L}; + Object[] mockObjectsLike3_3 = {Stub.MockBoard.getMockBoard3(), 3L}; + Object[] mockObjectsLike3_4 = {Stub.MockBoard.getMockBoard4(), 3L}; + Object[] mockObjectsLike3_5 = {Stub.MockBoard.getMockBoard5(), 3L}; + Object[] mockObjectsLike2_1 = {Stub.MockBoard.getMockBoard2(), 2L}; + Object[] mockObjectsLike2_2 = {Stub.MockBoard.getMockBoard4(), 2L}; + Object[] mockObjectsLike2_3 = {Stub.MockBoard.getMockBoard3(), 2L}; + Object[] mockObjectsLike2_4 = {Stub.MockBoard.getMockBoard5(), 2L}; + Object[] mockObjectsLike2_5 = {Stub.MockBoard.getMockBoard1(), 2L}; + Object[] mockObjectsLike1_1 = {Stub.MockBoard.getMockBoard3(), 1L}; + Object[] mockObjectsLike1_2 = {Stub.MockBoard.getMockBoard5(), 1L}; + @BeforeEach + public void setUp() { + mockTopBoardsWithLikes.add(mockObjectsLike3_1); + } + +// @AfterEach +// public void tearDown() { +// mockTopBoardsWithLikes.clear(); +// } + + @Test + @Order(1) + void 동점자_없는_상위_3개_게시글() { + //given + // 좋아요 3, 2, 1 + mockTopBoardsWithLikes.add(mockObjectsLike2_1); + mockTopBoardsWithLikes.add(mockObjectsLike1_1); + given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) + .willReturn(mockTopBoardsWithLikes); + + //when + List responses = boardService.findTop3LikedBoardRanks(); + + //then + assertThat(responses.get(0).getLikeNum(), is(3L)); + assertThat(responses.get(2).getLikeNum(), is(1L)); + assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(3)); + assertThat(responses.size(), is(3)); + } + + @Test + @Order(2) + void 일등1_이등2_삼등1() { + //given + // 좋아요 3, 2, 2, 1 + mockTopBoardsWithLikes.add(mockObjectsLike2_1); + mockTopBoardsWithLikes.add(mockObjectsLike2_2); + mockTopBoardsWithLikes.add(mockObjectsLike1_2); + given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) + .willReturn(mockTopBoardsWithLikes); + + //when + List responses = boardService.findTop3LikedBoardRanks(); + + //then + assertThat(responses.get(0).getLikeNum(), is(3L)); + assertThat(responses.get(2).getLikeNum(), is(2L)); + assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(2)); + assertThat(responses.size(), is(3)); + } + + @Test + @Order(3) + void 일등3_이등1() { + //given + // 좋아요 3, 3, 3, 1 + mockTopBoardsWithLikes.add(mockObjectsLike3_2); + mockTopBoardsWithLikes.add(mockObjectsLike3_3); + mockTopBoardsWithLikes.add(mockObjectsLike1_1); + given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) + .willReturn(mockTopBoardsWithLikes); + + //when + List responses = boardService.findTop3LikedBoardRanks(); + + //then + assertThat(responses.get(0).getLikeNum(), is(3L)); + assertThat(responses.get(2).getLikeNum(), is(3L)); + assertThat(responses.get(0).getRankStatus().getRank(), is(1)); + assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(1)); + assertThat(responses.size(), is(3)); + } + @Test + @Order(4) + void 일등5_이등2() { + //given + // 좋아요 3, 3, 3, 3, 3, 1, 1 + mockTopBoardsWithLikes.add(mockObjectsLike3_2); + mockTopBoardsWithLikes.add(mockObjectsLike3_3); + mockTopBoardsWithLikes.add(mockObjectsLike3_4); + mockTopBoardsWithLikes.add(mockObjectsLike3_5); + mockTopBoardsWithLikes.add(mockObjectsLike1_1); + mockTopBoardsWithLikes.add(mockObjectsLike1_2); + given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) + .willReturn(mockTopBoardsWithLikes); + + //when + List responses = boardService.findTop3LikedBoardRanks(); + + //then + assertThat(responses.get(0).getLikeNum(), is(3L)); + assertThat(responses.get(responses.size()-1).getLikeNum(), is(3L)); + assertThat(responses.get(0).getRankStatus().getRank(), is(1)); + assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(1)); + assertThat(responses.size(), is(5)); + } + @Test + @Order(5) + void 일등1_이등5_삼등1() { + //given + // 좋아요 3, 2, 2, 2, 2, 2, 1 + mockTopBoardsWithLikes.add(mockObjectsLike2_1); + mockTopBoardsWithLikes.add(mockObjectsLike2_2); + mockTopBoardsWithLikes.add(mockObjectsLike2_3); + mockTopBoardsWithLikes.add(mockObjectsLike2_4); + mockTopBoardsWithLikes.add(mockObjectsLike2_5); + mockTopBoardsWithLikes.add(mockObjectsLike1_1); + given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) + .willReturn(mockTopBoardsWithLikes); + + //when + List responses = boardService.findTop3LikedBoardRanks(); + + //then + assertThat(responses.get(0).getLikeNum(), is(3L)); + assertThat(responses.get(responses.size()-1).getLikeNum(), is(2L)); + assertThat(responses.get(0).getRankStatus().getRank(), is(1)); + assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(2)); + assertThat(responses.size(), is(6)); + } + + + + + } +} diff --git a/server/src/test/java/com/growstory/domain/board/service/BoardRankingTest.java b/server/src/test/java/com/growstory/domain/board/service/BoardRankingTest.java deleted file mode 100644 index f56a291a..00000000 --- a/server/src/test/java/com/growstory/domain/board/service/BoardRankingTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.growstory.domain.board.service; - -import com.growstory.domain.account.entity.Account; -import com.growstory.domain.board.entity.Board; -import com.growstory.domain.board.repository.BoardRepository; -import com.growstory.domain.rank.board_likes.dto.BoardLikesRankDto; -import com.growstory.domain.rank.board_likes.entity.BoardLikesRank; -import com.growstory.domain.stubdata.Stub; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.test.context.TestPropertySource; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.BDDMockito.given; - -@ExtendWith(MockitoExtension.class) -@TestPropertySource(properties = { - "my.scheduled.cron=0 0 0 * * ?" // 예제 크론 표현식 -}) -public class BoardRankingTest { - - @InjectMocks - private BoardService boardService; - - @Mock - private BoardRepository boardRepository; - - @Test - void testFindTop3LikedBoards() { - // given, 가짜 데이터 생성 - List fakeTopBoardsWithLikes = new ArrayList<>(); - fakeTopBoardsWithLikes.add(new Object[] { Board.builder().boardId(1L).title("제목1").account(Account.builder().displayName("빵빵스").build()).boardLikes(Stub.MockBoardLikes.getBoardLikes1()).build(), 3L}); - fakeTopBoardsWithLikes.add(new Object[] { Board.builder().boardId(2L).title("제목2").account(Account.builder().displayName("김크크").build()).boardLikes(Stub.MockBoardLikes.getBoardLikes2()).build(), 2L}); - fakeTopBoardsWithLikes.add(new Object[] { Board.builder().boardId(3L).title("제목3").account(Account.builder().displayName("박크크").build()).boardLikes(Stub.MockBoardLikes.getBoardLikes3()).build(), 1L}); - - // 가짜 데이터를 반환하도록 Mock 설정 - given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) - .willReturn(fakeTopBoardsWithLikes); - - //when, 테스트 대상 메서드 호출 - List response = boardService.findTop3LikedBoards(); - - //then, 결과 검증 - assertEquals(3, response.size()); - - assertEquals(1L, response.get(0).getBoardId()); - assertEquals(3, response.get(0).getLikeNum()); - - assertEquals(2L, response.get(1).getBoardId()); - assertEquals(2, response.get(1).getLikeNum()); - - assertEquals(3L, response.get(2).getBoardId()); - assertEquals(1, response.get(2).getLikeNum()); - } -} diff --git a/server/src/test/java/com/growstory/domain/board/service/BoardServiceTest.java b/server/src/test/java/com/growstory/domain/board/service/BoardServiceTest.java index e1531023..82b7facf 100644 --- a/server/src/test/java/com/growstory/domain/board/service/BoardServiceTest.java +++ b/server/src/test/java/com/growstory/domain/board/service/BoardServiceTest.java @@ -1,192 +1,62 @@ package com.growstory.domain.board.service; -import com.growstory.domain.board.repository.BoardHashTagRepository; +import com.growstory.domain.account.entity.Account; +import com.growstory.domain.board.entity.Board; import com.growstory.domain.board.repository.BoardRepository; -import com.growstory.domain.comment.service.CommentService; -import com.growstory.domain.hashTag.repository.HashTagRepository; -import com.growstory.domain.hashTag.service.HashTagService; -import com.growstory.domain.images.service.BoardImageService; -import com.growstory.domain.rank.board_likes.entity.BoardLikesRank; +import com.growstory.domain.rank.board_likes.dto.BoardLikesRankDto; import com.growstory.domain.stubdata.Stub; -import com.growstory.global.auth.utils.AuthUserUtils; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.context.TestPropertySource; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.BDDMockito.given; @ExtendWith(MockitoExtension.class) +@TestPropertySource(properties = { + "my.scheduled.cron=0 0 0 * * ?" // 예제 크론 표현식 +}) public class BoardServiceTest { @InjectMocks private BoardService boardService; + @Mock private BoardRepository boardRepository; - @Mock - private HashTagService hashTagService; - @Mock - private BoardImageService boardImageService; - @Mock - private AuthUserUtils authUserUtils; - @Mock - private HashTagRepository hashTagRepository; - @Mock - private BoardHashTagRepository boardHashtagRepository; - @Mock - private CommentService commentService; - - - @DisplayName("좋아요 기준 상위 3개의 게시글 랭킹과 함께 반환") - @Nested - @TestMethodOrder(MethodOrderer.OrderAnnotation.class) - class FindTop3LikedBoardRanksTest { - - //given - List mockTopBoardsWithLikes = new ArrayList<>(); - Object[] mockObjectsLike3_1 = {Stub.MockBoard.getMockBoard1(), 3L}; - Object[] mockObjectsLike3_2 = {Stub.MockBoard.getMockBoard2(), 3L}; - Object[] mockObjectsLike3_3 = {Stub.MockBoard.getMockBoard3(), 3L}; - Object[] mockObjectsLike3_4 = {Stub.MockBoard.getMockBoard4(), 3L}; - Object[] mockObjectsLike3_5 = {Stub.MockBoard.getMockBoard5(), 3L}; - Object[] mockObjectsLike2_1 = {Stub.MockBoard.getMockBoard2(), 2L}; - Object[] mockObjectsLike2_2 = {Stub.MockBoard.getMockBoard4(), 2L}; - Object[] mockObjectsLike2_3 = {Stub.MockBoard.getMockBoard3(), 2L}; - Object[] mockObjectsLike2_4 = {Stub.MockBoard.getMockBoard5(), 2L}; - Object[] mockObjectsLike2_5 = {Stub.MockBoard.getMockBoard1(), 2L}; - Object[] mockObjectsLike1_1 = {Stub.MockBoard.getMockBoard3(), 1L}; - Object[] mockObjectsLike1_2 = {Stub.MockBoard.getMockBoard5(), 1L}; - @BeforeEach - public void setUp() { - mockTopBoardsWithLikes.add(mockObjectsLike3_1); - } - -// @AfterEach -// public void tearDown() { -// mockTopBoardsWithLikes.clear(); -// } - - @Test - @Order(1) - void 동점자_없는_상위_3개_게시글() { - //given - // 좋아요 3, 2, 1 - mockTopBoardsWithLikes.add(mockObjectsLike2_1); - mockTopBoardsWithLikes.add(mockObjectsLike1_1); - given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) - .willReturn(mockTopBoardsWithLikes); - - //when - List responses = boardService.findTop3LikedBoardRanks(); - - //then - assertThat(responses.get(0).getLikeNum(), is(3L)); - assertThat(responses.get(2).getLikeNum(), is(1L)); - assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(3)); - assertThat(responses.size(), is(3)); - } - - @Test - @Order(2) - void 일등1_이등2_삼등1() { - //given - // 좋아요 3, 2, 2, 1 - mockTopBoardsWithLikes.add(mockObjectsLike2_1); - mockTopBoardsWithLikes.add(mockObjectsLike2_2); - mockTopBoardsWithLikes.add(mockObjectsLike1_2); - given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) - .willReturn(mockTopBoardsWithLikes); - - //when - List responses = boardService.findTop3LikedBoardRanks(); - - //then - assertThat(responses.get(0).getLikeNum(), is(3L)); - assertThat(responses.get(2).getLikeNum(), is(2L)); - assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(2)); - assertThat(responses.size(), is(3)); - } - - @Test - @Order(3) - void 일등3_이등1() { - //given - // 좋아요 3, 3, 3, 1 - mockTopBoardsWithLikes.add(mockObjectsLike3_2); - mockTopBoardsWithLikes.add(mockObjectsLike3_3); - mockTopBoardsWithLikes.add(mockObjectsLike1_1); - given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) - .willReturn(mockTopBoardsWithLikes); - - //when - List responses = boardService.findTop3LikedBoardRanks(); - - //then - assertThat(responses.get(0).getLikeNum(), is(3L)); - assertThat(responses.get(2).getLikeNum(), is(3L)); - assertThat(responses.get(0).getRankStatus().getRank(), is(1)); - assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(1)); - assertThat(responses.size(), is(3)); - } - @Test - @Order(4) - void 일등5_이등2() { - //given - // 좋아요 3, 3, 3, 3, 3, 1, 1 - mockTopBoardsWithLikes.add(mockObjectsLike3_2); - mockTopBoardsWithLikes.add(mockObjectsLike3_3); - mockTopBoardsWithLikes.add(mockObjectsLike3_4); - mockTopBoardsWithLikes.add(mockObjectsLike3_5); - mockTopBoardsWithLikes.add(mockObjectsLike1_1); - mockTopBoardsWithLikes.add(mockObjectsLike1_2); - given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) - .willReturn(mockTopBoardsWithLikes); - - //when - List responses = boardService.findTop3LikedBoardRanks(); - //then - assertThat(responses.get(0).getLikeNum(), is(3L)); - assertThat(responses.get(responses.size()-1).getLikeNum(), is(3L)); - assertThat(responses.get(0).getRankStatus().getRank(), is(1)); - assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(1)); - assertThat(responses.size(), is(5)); - } - @Test - @Order(5) - void 일등1_이등5_삼등1() { - //given - // 좋아요 3, 2, 2, 2, 2, 2, 1 - mockTopBoardsWithLikes.add(mockObjectsLike2_1); - mockTopBoardsWithLikes.add(mockObjectsLike2_2); - mockTopBoardsWithLikes.add(mockObjectsLike2_3); - mockTopBoardsWithLikes.add(mockObjectsLike2_4); - mockTopBoardsWithLikes.add(mockObjectsLike2_5); - mockTopBoardsWithLikes.add(mockObjectsLike1_1); - given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) - .willReturn(mockTopBoardsWithLikes); + @Test + void testFindTop3LikedBoards() { + // given, 가짜 데이터 생성 + List fakeTopBoardsWithLikes = new ArrayList<>(); + fakeTopBoardsWithLikes.add(new Object[] { Board.builder().boardId(1L).title("제목1").account(Account.builder().displayName("빵빵스").build()).boardLikes(Stub.MockBoardLikes.getBoardLikes1()).build(), 3L}); + fakeTopBoardsWithLikes.add(new Object[] { Board.builder().boardId(2L).title("제목2").account(Account.builder().displayName("김크크").build()).boardLikes(Stub.MockBoardLikes.getBoardLikes2()).build(), 2L}); + fakeTopBoardsWithLikes.add(new Object[] { Board.builder().boardId(3L).title("제목3").account(Account.builder().displayName("박크크").build()).boardLikes(Stub.MockBoardLikes.getBoardLikes3()).build(), 1L}); - //when - List responses = boardService.findTop3LikedBoardRanks(); + // 가짜 데이터를 반환하도록 Mock 설정 + given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) + .willReturn(fakeTopBoardsWithLikes); - //then - assertThat(responses.get(0).getLikeNum(), is(3L)); - assertThat(responses.get(responses.size()-1).getLikeNum(), is(2L)); - assertThat(responses.get(0).getRankStatus().getRank(), is(1)); - assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(2)); - assertThat(responses.size(), is(6)); - } + //when, 테스트 대상 메서드 호출 + List response = boardService.findTop3LikedBoards(); + //then, 결과 검증 + assertEquals(3, response.size()); + assertEquals(1L, response.get(0).getBoardId()); + assertEquals(3, response.get(0).getLikeNum()); + assertEquals(2L, response.get(1).getBoardId()); + assertEquals(2, response.get(1).getLikeNum()); + assertEquals(3L, response.get(2).getBoardId()); + assertEquals(1, response.get(2).getLikeNum()); } } diff --git a/server/src/test/java/com/growstory/domain/journal/JournalControllerTest.java b/server/src/test/java/com/growstory/domain/journal/JournalControllerTest.java index 64d93f87..91d1cdc6 100644 --- a/server/src/test/java/com/growstory/domain/journal/JournalControllerTest.java +++ b/server/src/test/java/com/growstory/domain/journal/JournalControllerTest.java @@ -4,22 +4,33 @@ import com.growstory.domain.journal.dto.JournalDto; import com.growstory.domain.journal.service.JournalService; import com.growstory.domain.stubdata.Stub; +import com.growstory.global.utils.UriCreator; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import static org.hamcrest.Matchers.*; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willDoNothing; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -36,7 +47,10 @@ public class JournalControllerTest { @MockBean private JournalService journalService; - private final String JOURNAL_DEFAULT_URL = "/v1/leaves"; + @MockBean + private UriCreator uriCreator; + + private final String DEFAULT_URL = "/v1/leaves"; @DisplayName("식물 일지 전체 조회") @Test @@ -49,7 +63,7 @@ void getJournalsTest() throws Exception { .willReturn(journals); //when ResultActions actions = mockMvc.perform( - get(JOURNAL_DEFAULT_URL + "/{leaf-id}/journals", leafId) + get(DEFAULT_URL + "/{leaf-id}/journals", leafId) .param("accountId", accountId.toString()) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) @@ -61,4 +75,120 @@ void getJournalsTest() throws Exception { .andExpect(jsonPath("$.data[0].title", is(journals.get(0).getTitle()))) .andDo(print()); } + + @DisplayName("식물 일지 등록 기능") + @Nested + class PostJournalTest { + + long leafId; + JournalDto.LeafAuthor leafAuthor; + JournalDto.Post requestDto; + MockMultipartFile leafAuthorPart; + MockMultipartFile requestDtoPart; + MockMultipartFile imagePart; + JournalDto.Response mockJournalResponse; + + @BeforeEach + void init() { + leafId = 1L; + leafAuthor = JournalDto.LeafAuthor.builder().accountId(1L).build(); + requestDto = JournalDto.Post.builder().title("식물 일지 제목").content("내용").build(); + + // MockMultipartFile 객체화 + leafAuthorPart = createMockMultipartFile("leafAuthor", gson.toJson(leafAuthor), "application/json"); + requestDtoPart = createMockMultipartFile("postDto", gson.toJson(requestDto), "application/json"); + imagePart = createImageMockFile("src/test/resources/images/testImage.jpg"); + + //given + mockJournalResponse = Stub.MockJournal.getStubJournalResponse1(); + given(journalService.createJournal(anyLong(), anyLong(), Mockito.any(JournalDto.Post.class), Mockito.any(MultipartFile.class))) + .willReturn(mockJournalResponse); + + URI stubUri = URI.create("/v1/leaves/1/journals/1"); + given(uriCreator.createUri_Test(Mockito.any(String.class), Mockito.anyLong())).willReturn(stubUri); + } + + private MockMultipartFile createMockMultipartFile(String name, String content, String contentType) { + return new MockMultipartFile(name, name + ".json", contentType, content.getBytes(StandardCharsets.UTF_8)); + } + + private MockMultipartFile createImageMockFile(String pathString) { + Path path = Paths.get(pathString); + byte[] content; + try { + content = Files.readAllBytes(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + return new MockMultipartFile("image", "testImage.jpg", "image/jpeg", content); + } + + @Test + void 이미지_있는_식물_일지_등록() throws Exception { + + //when + ResultActions actions = mockMvc.perform( + multipart(DEFAULT_URL + "/{leaf-id}/journals", leafId) + .file(leafAuthorPart) + .file(requestDtoPart) + .file(imagePart) + .contentType("multipart/form-data") + .accept(MediaType.APPLICATION_JSON) + .characterEncoding("UTF-8") + ); + + //then + URI expectedLocation = UriCreator.createUri(DEFAULT_URL + "/1/journals/", mockJournalResponse.getJournalId()); + actions + .andExpect(status().isCreated()) + .andExpect(header().string("Location", is(expectedLocation.toString()))) + .andDo(print()); + } + +// @Test +// void 이미지가_없는_식물_일지_등록() throws Exception { +// // given, imagePart를 null로 설정 +// MockMultipartFile imagePart = null; +// +// ResultActions actions = mockMvc.perform( +// multipart(DEFAULT_URL + "/{leaf-id}/journals", leafId) +// .file(leafAuthorPart) +// .file(requestDtoPart) +// // image part를 추가하지 않음 +// .file(imagePart) +// .contentType("multipart/form-data") +// .accept(MediaType.APPLICATION_JSON) +// .characterEncoding("UTF-8") +// ); +// +// // then +// actions +// .andExpect(status().isCreated()) // or isBadRequest(), depending on the expected outcome +// .andDo(print()); +// } + + @Test + void 이미지가_빈_식물_일지_등록() throws Exception { + // given, imagePart를 빈 내용으로 설정 + MockMultipartFile imagePart = new MockMultipartFile("image", "testImage.jpg", "image/jpeg", new byte[0]); + + // when + ResultActions actions = mockMvc.perform( + multipart(DEFAULT_URL + "/{leaf-id}/journals", leafId) + .file(leafAuthorPart) + .file(requestDtoPart) + .file(imagePart) // empty image part + .contentType("multipart/form-data") + .accept(MediaType.APPLICATION_JSON) + .characterEncoding("UTF-8") + ); + + // then + actions + .andExpect(status().isCreated()) // or isBadRequest(), depending on the expected outcome + .andDo(print()); + } + } + + } From 40b8726aacd86672b4fe06a2db6e564f7ec5c97b Mon Sep 17 00:00:00 2001 From: LST Date: Sun, 15 Oct 2023 09:34:41 +0900 Subject: [PATCH 008/129] =?UTF-8?q?:test=5Ftube:=20Test:=20BoardLikesRank?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C=20=EB=B0=A9=EC=8B=9D=20soft=20delete?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD,=20=EC=9D=B4=EB=A0=A5=20?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=9A=A9=EB=8F=84=20=ED=8F=90?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/controller/BoardController.java | 5 +- .../domain/board/service/BoardService.java | 9 +-- .../growstory/domain/rank/RankService.java | 2 +- .../board_likes/entity/BoardLikesRank.java | 5 +- .../service/BoardLikesRankService.java | 55 +++++++++------- .../rank/controller/RankController.java | 2 +- .../growstory/domain/rank/entity/Rank.java | 64 +++++++++++++------ ...ServiceTest.java => BoardRankingTest.java} | 19 +++--- 8 files changed, 94 insertions(+), 67 deletions(-) rename server/src/test/java/com/growstory/domain/board/service/{BoardRankingServiceTest.java => BoardRankingTest.java} (94%) diff --git a/server/src/main/java/com/growstory/domain/board/controller/BoardController.java b/server/src/main/java/com/growstory/domain/board/controller/BoardController.java index 101ca614..94ca0034 100644 --- a/server/src/main/java/com/growstory/domain/board/controller/BoardController.java +++ b/server/src/main/java/com/growstory/domain/board/controller/BoardController.java @@ -7,7 +7,6 @@ import com.growstory.domain.rank.board_likes.dto.BoardLikesRankDto; import com.growstory.domain.rank.board_likes.service.BoardLikesRankService; import com.growstory.global.constants.HttpStatusCode; -import com.growstory.global.response.MultiResponseDto; import com.growstory.global.response.MultiResponseDto2; import com.growstory.global.response.SingleResponseDto; import com.growstory.global.utils.UriCreator; @@ -69,7 +68,7 @@ public ResponseEntity> getBoard(@Positive @P public ResponseEntity> getBoards(@Positive @RequestParam(defaultValue = "1") int page, @Positive @RequestParam(defaultValue = "12") int size) { Page responseBoardDtos = boardService.findBoards(page - 1, size); - List responseBoardRankList = boardLikesRankService.findAllBoardLikesRanks(); + List responseBoardRankList = boardLikesRankService.findCurrentBoardLikesRanks(); return ResponseEntity.ok(MultiResponseDto2.builder() @@ -86,7 +85,7 @@ public ResponseEntity responseBoardDtos = boardService.findBoardsByKeyword(page - 1, size, keyword); - List responseBoardRankList = boardLikesRankService.findAllBoardLikesRanks(); + List responseBoardRankList = boardLikesRankService.findCurrentBoardLikesRanks(); return ResponseEntity.ok(MultiResponseDto2.builder() .status(HttpStatusCode.OK.getStatusCode()) diff --git a/server/src/main/java/com/growstory/domain/board/service/BoardService.java b/server/src/main/java/com/growstory/domain/board/service/BoardService.java index 9d83141a..575c92f8 100644 --- a/server/src/main/java/com/growstory/domain/board/service/BoardService.java +++ b/server/src/main/java/com/growstory/domain/board/service/BoardService.java @@ -269,7 +269,7 @@ public List findTop3LikedBoards() { return response; } - // 좋아요 기준 상위 3개의 게시글을 랭킹과 함께 반환 (🆘 추후 리팩토링) + // 좋아요 기준 상위 3개의 게시글을 랭킹과 함께 반환 public List findTop3LikedBoardRanks() { LocalDateTime sevenDaysAgo = LocalDateTime.now().minusDays(7); List topBoardsWithLikes = boardRepository.findTop3LikedBoards(sevenDaysAgo); @@ -297,7 +297,7 @@ public List findTop3LikedBoardRanks() { .board(board) .likeNum(likeCount) .build(); - boardLikesRank.updateRank(uniqueLikeCounts.size()); + boardLikesRank.updateRank(uniqueLikeCounts.size()); //차등 등수 업데이트 boardLikesRanks.add(boardLikesRank); }); @@ -308,9 +308,10 @@ public List findTop3LikedBoardRanks() { private boolean checkSameLikesCondition(List boardLikesRanks) { int boardSize = boardLikesRanks.size(); + //게시글이 4개 이상이고 마지막 두 게시글의 순위가 서로 다르면 마지막 요소를 제거하고 false 반환 if(boardSize>=4 && - (boardLikesRanks.get(boardSize-1).getRankStatus().getRank() != - boardLikesRanks.get(boardSize-2).getRankStatus().getRank())) { + (boardLikesRanks.get(boardSize-1).getRankOrders().getPosition() != + boardLikesRanks.get(boardSize-2).getRankOrders().getPosition())) { boardLikesRanks.remove(boardLikesRanks.get(boardSize-1)); return false; } diff --git a/server/src/main/java/com/growstory/domain/rank/RankService.java b/server/src/main/java/com/growstory/domain/rank/RankService.java index 4c533097..6cba0264 100644 --- a/server/src/main/java/com/growstory/domain/rank/RankService.java +++ b/server/src/main/java/com/growstory/domain/rank/RankService.java @@ -24,7 +24,7 @@ public void compensateWeeklyPoints(Rank rank) { // 주간 랭킹별 포인트 보상액 산정 private int calculateWeeklyPoints(Rank rank) { int score; - switch (rank.getRankStatus().getRank()) { + switch (rank.getRankOrders().getPosition()) { case 1: score = 3000; break; diff --git a/server/src/main/java/com/growstory/domain/rank/board_likes/entity/BoardLikesRank.java b/server/src/main/java/com/growstory/domain/rank/board_likes/entity/BoardLikesRank.java index 29cbbe46..cb3f3b86 100644 --- a/server/src/main/java/com/growstory/domain/rank/board_likes/entity/BoardLikesRank.java +++ b/server/src/main/java/com/growstory/domain/rank/board_likes/entity/BoardLikesRank.java @@ -4,8 +4,6 @@ import com.growstory.domain.board.entity.Board; import com.growstory.domain.rank.board_likes.dto.BoardLikesRankDto; import com.growstory.domain.rank.entity.Rank; -import com.growstory.global.exception.BusinessLogicException; -import com.growstory.global.exception.ExceptionCode; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -31,12 +29,13 @@ public BoardLikesRank(Board board, Long likeNum, Account account) { super(account); this.board = board; this.likeNum = likeNum; + super.updateRankStat(RankStat.CURRENT); } public BoardLikesRankDto.Response toResponseDto() { return BoardLikesRankDto.Response .builder() - .rank(this.getRankStatus().getRank()) //Rank 에서 상속 받은 rank, account 정보 활용 + .rank(this.getRankOrders().getPosition()) //Rank 에서 상속 받은 rank, account 정보 활용 .displayName(this.getAccount().getDisplayName()) .boardId(this.board.getBoardId()) .title(this.board.getTitle()) diff --git a/server/src/main/java/com/growstory/domain/rank/board_likes/service/BoardLikesRankService.java b/server/src/main/java/com/growstory/domain/rank/board_likes/service/BoardLikesRankService.java index 7e42f4b8..072a4ecb 100644 --- a/server/src/main/java/com/growstory/domain/rank/board_likes/service/BoardLikesRankService.java +++ b/server/src/main/java/com/growstory/domain/rank/board_likes/service/BoardLikesRankService.java @@ -4,9 +4,9 @@ import com.growstory.domain.rank.RankService; import com.growstory.domain.rank.board_likes.dto.BoardLikesRankDto; import com.growstory.domain.rank.board_likes.entity.BoardLikesRank; -import com.growstory.domain.rank.board_likes.history.entity.BoardLikesRankHistory; import com.growstory.domain.rank.board_likes.history.repository.BoardLikesRankHistoryRepository; import com.growstory.domain.rank.board_likes.repository.BoardLikesRankRepository; +import com.growstory.domain.rank.entity.Rank; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -32,48 +32,55 @@ public class BoardLikesRankService { @Value("${my.scheduled.cron}") private String cronExpression; - // 주간 랭킹 조회 - public List findAllBoardLikesRanks() { - List boardLikesRanks = repository.findAll(); - return boardLikesRanks.stream() + // 현재 주간 랭킹 조회 + public List findCurrentBoardLikesRanks() { + return repository.findAll().stream() + //이번 주의 유효한 랭킹만 조회 + .filter(boardLikesRank -> boardLikesRank.getRankStat()== Rank.RankStat.CURRENT) .map(BoardLikesRank::toResponseDto) .collect(Collectors.toList()); } + //TODO: 이력 테이블 삭제 + // 주 1회 랭킹 업데이트 및 이력 관리, 포인트 보상 @Scheduled(cron = "${my.scheduled.cron}") public void updateFindTop3LikedBoards() { log.info("# Scheduled task findTop3LikedBoards started at {}", LocalDateTime.now()); log.info("# and Cron expression is: {}", cronExpression); + // 기존 랭킹 데이터 상태 Previous로 전환 + repository.findAll() + .forEach(boardLikesRank -> boardLikesRank.updateRankStat(Rank.RankStat.PREVIOUS)); + // 좋아요 개수 상위 3등 까지의 게시글 조회 List boardLikesRanks = boardService.findTop3LikedBoardRanks(); - // 이전 주의 랭킹 이력 테이블에 저장 - saveHistories(boardLikesRanks); - - // 유저에게 보상 포인트 제공 + // 해당 게시글의 유저에게 보상 포인트 제공 boardLikesRanks .forEach(rankService::compensateWeeklyPoints); + // 이번 주 랭킹 저장 + repository.saveAll(boardLikesRanks); + // 이전 주의 랭킹 삭제 및 이번 주 랭킹 저장 - repository.deleteAll(); - List newBoardLikesRanks = boardService.findTop3LikedBoardRanks(); - repository.saveAll(newBoardLikesRanks); +// repository.deleteAll(); +// List newBoardLikesRanks = boardService.findTop3LikedBoardRanks(); +// repository.saveAll(newBoardLikesRanks); } // 이전 게시글 좋아요 랭킹을 이력 테이블로서 저장 - private void saveHistories(List boardLikesRanks) { - List histories - = boardLikesRanks.stream() - .map(rank -> { - return BoardLikesRankHistory.builder() - .accountId(rank.getAccount().getAccountId()) - .boardId(rank.getBoard().getBoardId()) - .likesNum(rank.getLikeNum()) - .build(); - }).collect(Collectors.toList()); - historyRepository.saveAll(histories); - } +// private void saveHistories(List boardLikesRanks) { +// List histories +// = boardLikesRanks.stream() +// .map(rank -> { +// return BoardLikesRankHistory.builder() +// .accountId(rank.getAccount().getAccountId()) +// .boardId(rank.getBoard().getBoardId()) +// .likesNum(rank.getLikeNum()) +// .build(); +// }).collect(Collectors.toList()); +// historyRepository.saveAll(histories); +// } } diff --git a/server/src/main/java/com/growstory/domain/rank/controller/RankController.java b/server/src/main/java/com/growstory/domain/rank/controller/RankController.java index 3c355d05..63071ddd 100644 --- a/server/src/main/java/com/growstory/domain/rank/controller/RankController.java +++ b/server/src/main/java/com/growstory/domain/rank/controller/RankController.java @@ -25,7 +25,7 @@ public class RankController { @GetMapping("/weekly-board-likes") public ResponseEntity getWeeklyBoardLikesRanks() { - List responseDto = boardLikesRankService.findAllBoardLikesRanks(); + List responseDto = boardLikesRankService.findCurrentBoardLikesRanks(); return ResponseEntity.ok(SingleResponseDto.builder() .data(responseDto) diff --git a/server/src/main/java/com/growstory/domain/rank/entity/Rank.java b/server/src/main/java/com/growstory/domain/rank/entity/Rank.java index 34b128cb..742cde25 100644 --- a/server/src/main/java/com/growstory/domain/rank/entity/Rank.java +++ b/server/src/main/java/com/growstory/domain/rank/entity/Rank.java @@ -25,52 +25,74 @@ public abstract class Rank extends BaseTimeEntity { private Account account; @Enumerated(EnumType.STRING) - private RankStatus rankStatus; + private RankOrders rankOrders; + + @Enumerated(EnumType.STRING) + private RankStat rankStat; public Rank(Account account) { this.account = account; } - protected void updateRank(RankStatus rankStatus) { - this.rankStatus = rankStatus; + protected void updateRank(RankOrders rankOrders) { + this.rankOrders = rankOrders; } @Getter - public enum RankStatus { - RANK_NO_1("rank_no_1", 1), - RANK_NO_2("rank_no_2", 2), - RANK_NO_3("rank_no_3", 3), - RANK_NO_4("rank_no_4", 4), - RANK_NO_5("rank_no_5", 5); - - private String status; - private int rank; - - RankStatus(String status, int rank) { - this.status = status; - this.rank = rank; + public enum RankOrders { + FIRST("rank_no_1", 1), + SECOND("rank_no_2", 2), + THIRD("rank_no_3", 3), + FOURTH("rank_no_4", 4), + FIFTH("rank_no_5", 5); + + private final String name; + private final int position; + + RankOrders(String name, int position) { + this.name = name; + this.position = position; } } + @Getter + public enum RankStat { + CURRENT("Current Record", 1), + PREVIOUS("Previous Record", 2); + + private final String recordLabel; + private final int typeCode; + + RankStat(String recordLabel, int typeCode) { + this.recordLabel = recordLabel; + this.typeCode = typeCode; + } + } + + public void updateRank(int rank) { switch (rank) { case 1 : - updateRank(RankStatus.RANK_NO_1); + updateRank(RankOrders.FIRST); break; case 2 : - updateRank(RankStatus.RANK_NO_2); + updateRank(RankOrders.SECOND); break; case 3 : - updateRank(RankStatus.RANK_NO_3); + updateRank(RankOrders.THIRD); break; case 4 : - updateRank(RankStatus.RANK_NO_4); + updateRank(RankOrders.FOURTH); break; case 5 : - updateRank(RankStatus.RANK_NO_5); + updateRank(RankOrders.FIFTH); break; default: throw new BusinessLogicException(ExceptionCode.RANK_NOT_FOUND); } } + + public void updateRankStat(RankStat rankStat) { + this.rankStat = rankStat; + } } diff --git a/server/src/test/java/com/growstory/domain/board/service/BoardRankingServiceTest.java b/server/src/test/java/com/growstory/domain/board/service/BoardRankingTest.java similarity index 94% rename from server/src/test/java/com/growstory/domain/board/service/BoardRankingServiceTest.java rename to server/src/test/java/com/growstory/domain/board/service/BoardRankingTest.java index 45a0be51..5c9a41d5 100644 --- a/server/src/test/java/com/growstory/domain/board/service/BoardRankingServiceTest.java +++ b/server/src/test/java/com/growstory/domain/board/service/BoardRankingTest.java @@ -2,7 +2,6 @@ import com.growstory.domain.board.repository.BoardHashTagRepository; import com.growstory.domain.board.repository.BoardRepository; -import com.growstory.domain.board.service.BoardService; import com.growstory.domain.comment.service.CommentService; import com.growstory.domain.hashTag.repository.HashTagRepository; import com.growstory.domain.hashTag.service.HashTagService; @@ -26,7 +25,7 @@ import static org.mockito.BDDMockito.given; @ExtendWith(MockitoExtension.class) -public class BoardRankingServiceTest { +public class BoardRankingTest { @InjectMocks private BoardService boardService; @@ -91,7 +90,7 @@ public void setUp() { //then assertThat(responses.get(0).getLikeNum(), is(3L)); assertThat(responses.get(2).getLikeNum(), is(1L)); - assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(3)); + assertThat(responses.get(responses.size()-1).getRankOrders().getPosition(), is(3)); assertThat(responses.size(), is(3)); } @@ -112,7 +111,7 @@ public void setUp() { //then assertThat(responses.get(0).getLikeNum(), is(3L)); assertThat(responses.get(2).getLikeNum(), is(2L)); - assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(2)); + assertThat(responses.get(responses.size()-1).getRankOrders().getPosition(), is(2)); assertThat(responses.size(), is(3)); } @@ -133,8 +132,8 @@ public void setUp() { //then assertThat(responses.get(0).getLikeNum(), is(3L)); assertThat(responses.get(2).getLikeNum(), is(3L)); - assertThat(responses.get(0).getRankStatus().getRank(), is(1)); - assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(1)); + assertThat(responses.get(0).getRankOrders().getPosition(), is(1)); + assertThat(responses.get(responses.size()-1).getRankOrders().getPosition(), is(1)); assertThat(responses.size(), is(3)); } @Test @@ -157,8 +156,8 @@ public void setUp() { //then assertThat(responses.get(0).getLikeNum(), is(3L)); assertThat(responses.get(responses.size()-1).getLikeNum(), is(3L)); - assertThat(responses.get(0).getRankStatus().getRank(), is(1)); - assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(1)); + assertThat(responses.get(0).getRankOrders().getPosition(), is(1)); + assertThat(responses.get(responses.size()-1).getRankOrders().getPosition(), is(1)); assertThat(responses.size(), is(5)); } @Test @@ -181,8 +180,8 @@ public void setUp() { //then assertThat(responses.get(0).getLikeNum(), is(3L)); assertThat(responses.get(responses.size()-1).getLikeNum(), is(2L)); - assertThat(responses.get(0).getRankStatus().getRank(), is(1)); - assertThat(responses.get(responses.size()-1).getRankStatus().getRank(), is(2)); + assertThat(responses.get(0).getRankOrders().getPosition(), is(1)); + assertThat(responses.get(responses.size()-1).getRankOrders().getPosition(), is(2)); assertThat(responses.size(), is(6)); } From 1c03f634bb6bda3498b8de9d789a8258ba2d6e2f Mon Sep 17 00:00:00 2001 From: LST Date: Sun, 15 Oct 2023 12:28:17 +0900 Subject: [PATCH 009/129] =?UTF-8?q?:recycle:=20Refactor:=20LeafAuthorDto?= =?UTF-8?q?=20=EA=B0=9D=EC=B2=B4=20long=20leafAuthorId=20=EB=A1=9C=20?= =?UTF-8?q?=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../journal/controller/JournalController.java | 14 ++++++-------- .../domain/journal/dto/JournalDto.java | 18 +++++++++++------- .../domain/journal/service/JournalService.java | 10 ++++------ .../domain/journal/JournalControllerTest.java | 12 ++++++------ 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/server/src/main/java/com/growstory/domain/journal/controller/JournalController.java b/server/src/main/java/com/growstory/domain/journal/controller/JournalController.java index 0a9c4800..edc91358 100644 --- a/server/src/main/java/com/growstory/domain/journal/controller/JournalController.java +++ b/server/src/main/java/com/growstory/domain/journal/controller/JournalController.java @@ -43,11 +43,10 @@ public ResponseEntity getJournals( // POST, 식물 일지를 등록 @PostMapping("/{leaf-id}/journals") - public ResponseEntity postJournal(@RequestPart(value = "leafAuthor") JournalDto.LeafAuthor leafAuthor, - @Positive @PathVariable("leaf-id") Long leafId, + public ResponseEntity postJournal(@Positive @PathVariable("leaf-id") Long leafId, @Valid @RequestPart(value = "postDto") JournalDto.Post postDto, @RequestPart(required = false, value = "image") MultipartFile image) { - JournalDto.Response journal = journalService.createJournal(leafAuthor.getAccountId(), leafId, postDto, image); + JournalDto.Response journal = journalService.createJournal(leafId, postDto, image); URI location = uriCreator.createUri_Test(DEFAULT_URL, journal.getJournalId()); @@ -56,12 +55,11 @@ public ResponseEntity postJournal(@RequestPart(value = "leafAuthor") // PATCH, 식물 일지를 수정 @PatchMapping("/journals/{journal-id}") - public ResponseEntity patchJournal(@RequestPart JournalDto.LeafAuthor leafAuthor, - @Positive @PathVariable("journal-id") Long journalId, + public ResponseEntity patchJournal(@Positive @PathVariable("journal-id") Long journalId, @Valid @RequestPart JournalDto.Patch patchDto, @RequestPart(required = false) MultipartFile image) { - journalService.updateJournal(leafAuthor.getAccountId(), journalId, patchDto, image); + journalService.updateJournal(journalId, patchDto, image); return ResponseEntity.noContent().build(); } @@ -69,9 +67,9 @@ public ResponseEntity patchJournal(@RequestPart JournalDto.LeafAutho // DELETE, 식물 일지를 삭제 @DeleteMapping("/journals/{journal-id}") public ResponseEntity deleteJournal( - @RequestBody JournalDto.LeafAuthor leafAuthor, + @RequestParam("leaf-author-id") long leafAuthorId, @Positive @PathVariable("journal-id") Long journalId) { - journalService.deleteJournal(leafAuthor.getAccountId(), journalId); + journalService.deleteJournal(leafAuthorId, journalId); return ResponseEntity.noContent().build(); } diff --git a/server/src/main/java/com/growstory/domain/journal/dto/JournalDto.java b/server/src/main/java/com/growstory/domain/journal/dto/JournalDto.java index 5ee14b8e..4a081054 100644 --- a/server/src/main/java/com/growstory/domain/journal/dto/JournalDto.java +++ b/server/src/main/java/com/growstory/domain/journal/dto/JournalDto.java @@ -20,6 +20,8 @@ public static class Post { String title; @NotBlank String content; + @NotNull + long leafAuthorId; } @Getter @@ -28,6 +30,8 @@ public static class Patch { String title; @Nullable String content; + @NotNull + long leafAuthorId; } @Getter @@ -40,11 +44,11 @@ public static class Response { LocalDateTime createdAt; } - @Getter - @Builder - @NoArgsConstructor - @AllArgsConstructor - public static class LeafAuthor { - long accountId; - } +// @Getter +// @Builder +// @NoArgsConstructor +// @AllArgsConstructor +// public static class LeafAuthor { +// long accountId; +// } } diff --git a/server/src/main/java/com/growstory/domain/journal/service/JournalService.java b/server/src/main/java/com/growstory/domain/journal/service/JournalService.java index f7c47dea..06dea81d 100644 --- a/server/src/main/java/com/growstory/domain/journal/service/JournalService.java +++ b/server/src/main/java/com/growstory/domain/journal/service/JournalService.java @@ -46,8 +46,8 @@ public List findAllJournals(Long accountId, Long leafId) { .collect(Collectors.toList()); } - public JournalDto.Response createJournal(Long accountId, Long leafId, JournalDto.Post postDto, MultipartFile image) { - accountService.isAuthIdMatching(accountId); + public JournalDto.Response createJournal(Long leafId, JournalDto.Post postDto, MultipartFile image) { + accountService.isAuthIdMatching(postDto.getLeafAuthorId()); Leaf findLeaf = leafService.findLeafEntityBy(leafId); Journal journal = createJournalWithNoImg(findLeaf, postDto); //image가 null이거나 비어있을 경우 ResponseDto로 변환하여 반환 @@ -72,9 +72,9 @@ private Journal createJournalWithNoImg(Leaf findLeaf, JournalDto.Post postDto) { .build()); } - public void updateJournal(Long accountId, Long journalId, JournalDto.Patch patchDto, MultipartFile image) { + public void updateJournal(Long journalId, JournalDto.Patch patchDto, MultipartFile image) { Journal findJournal = findVerifiedJournalBy(journalId); - accountService.isAuthIdMatching(accountId); + accountService.isAuthIdMatching(patchDto.getLeafAuthorId()); Optional.ofNullable(patchDto.getTitle()) .ifPresent(findJournal::updateTitle); @@ -84,8 +84,6 @@ public void updateJournal(Long accountId, Long journalId, JournalDto.Patch patch updateLoadImage(image, findJournal, JOURNAL_IMAGE_PROCESS_TYPE); } - //TODO: S3Uploader로 빼는 리팩토링 작업? (상위 클래스 Image를 이용한 형변환) - // 기존 DB와 S3에 저장된 이미지 정보를 업로드 이미지 여부에 따라 수정 private void updateLoadImage(MultipartFile image, Journal journal, String type) { JournalImage journalImage = journal.getJournalImage(); diff --git a/server/src/test/java/com/growstory/domain/journal/JournalControllerTest.java b/server/src/test/java/com/growstory/domain/journal/JournalControllerTest.java index 91d1cdc6..c4fd0a04 100644 --- a/server/src/test/java/com/growstory/domain/journal/JournalControllerTest.java +++ b/server/src/test/java/com/growstory/domain/journal/JournalControllerTest.java @@ -81,7 +81,7 @@ void getJournalsTest() throws Exception { class PostJournalTest { long leafId; - JournalDto.LeafAuthor leafAuthor; +// JournalDto.LeafAuthor leafAuthor; JournalDto.Post requestDto; MockMultipartFile leafAuthorPart; MockMultipartFile requestDtoPart; @@ -91,17 +91,17 @@ class PostJournalTest { @BeforeEach void init() { leafId = 1L; - leafAuthor = JournalDto.LeafAuthor.builder().accountId(1L).build(); +// leafAuthor = JournalDto.LeafAuthor.builder().accountId(1L).build(); requestDto = JournalDto.Post.builder().title("식물 일지 제목").content("내용").build(); // MockMultipartFile 객체화 - leafAuthorPart = createMockMultipartFile("leafAuthor", gson.toJson(leafAuthor), "application/json"); +// leafAuthorPart = createMockMultipartFile("leafAuthor", gson.toJson(leafAuthor), "application/json"); requestDtoPart = createMockMultipartFile("postDto", gson.toJson(requestDto), "application/json"); imagePart = createImageMockFile("src/test/resources/images/testImage.jpg"); //given mockJournalResponse = Stub.MockJournal.getStubJournalResponse1(); - given(journalService.createJournal(anyLong(), anyLong(), Mockito.any(JournalDto.Post.class), Mockito.any(MultipartFile.class))) + given(journalService.createJournal(anyLong(), Mockito.any(JournalDto.Post.class), Mockito.any(MultipartFile.class))) .willReturn(mockJournalResponse); URI stubUri = URI.create("/v1/leaves/1/journals/1"); @@ -129,7 +129,7 @@ private MockMultipartFile createImageMockFile(String pathString) { //when ResultActions actions = mockMvc.perform( multipart(DEFAULT_URL + "/{leaf-id}/journals", leafId) - .file(leafAuthorPart) +// .file(leafAuthorPart) .file(requestDtoPart) .file(imagePart) .contentType("multipart/form-data") @@ -175,7 +175,7 @@ private MockMultipartFile createImageMockFile(String pathString) { // when ResultActions actions = mockMvc.perform( multipart(DEFAULT_URL + "/{leaf-id}/journals", leafId) - .file(leafAuthorPart) +// .file(leafAuthorPart) .file(requestDtoPart) .file(imagePart) // empty image part .contentType("multipart/form-data") From 7f4eeb8284fe961c38b33cc65606b0b5a3e4a050 Mon Sep 17 00:00:00 2001 From: LST Date: Sun, 15 Oct 2023 18:43:27 +0900 Subject: [PATCH 010/129] :rocket: Deploy: CI/CD 231015 --- .../controller/PlantObjectControllerTest.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/server/src/test/java/com/growstory/domain/plant_object/controller/PlantObjectControllerTest.java b/server/src/test/java/com/growstory/domain/plant_object/controller/PlantObjectControllerTest.java index b6641070..c8a1d683 100644 --- a/server/src/test/java/com/growstory/domain/plant_object/controller/PlantObjectControllerTest.java +++ b/server/src/test/java/com/growstory/domain/plant_object/controller/PlantObjectControllerTest.java @@ -2,15 +2,12 @@ import com.google.gson.Gson; import com.growstory.domain.plant_object.dto.PlantObjDto; -import com.growstory.domain.plant_object.location.dto.LocationDto; import com.growstory.domain.plant_object.service.PlantObjService; -import com.growstory.domain.plant_object.service.PlantObjServiceTest; import com.growstory.domain.point.dto.PointDto; import com.growstory.domain.stubdata.Stub; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import org.mockito.stubbing.Answer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; @@ -18,15 +15,16 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import java.util.List; -import static org.hamcrest.Matchers.*; -import static org.mockito.BDDMockito.*; +import static org.hamcrest.Matchers.is; +import static org.mockito.BDDMockito.anyLong; +import static org.mockito.BDDMockito.given; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @AutoConfigureMockMvc From 97b5fafca5afff9a18c77ee1ec668d0449717a7d Mon Sep 17 00:00:00 2001 From: dohyoungK Date: Mon, 23 Oct 2023 16:59:21 +0900 Subject: [PATCH 011/129] =?UTF-8?q?[BE]=20=E2=99=BB=EF=B8=8F=20Refactor=20?= =?UTF-8?q?:=20Email,=20Account=20Status=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../account/controller/AccountController.java | 19 ++++--- .../domain/account/entity/Account.java | 33 ++++-------- .../account/service/AccountService.java | 39 +++++++------- .../leaf/controller/LeafController.java | 8 +-- .../domain/leaf/service/LeafService.java | 28 +++++----- .../growstory/global/auth/dto/LoginDto.java | 1 + .../auth/filter/JwtAuthenticationFilter.java | 1 + .../handler/OAuth2AccountSuccessHandler.java | 51 +++++++++---------- .../email/controller/EmailController.java | 12 +++-- .../global/email/service/EmailService.java | 10 ++-- 10 files changed, 99 insertions(+), 103 deletions(-) diff --git a/server/src/main/java/com/growstory/domain/account/controller/AccountController.java b/server/src/main/java/com/growstory/domain/account/controller/AccountController.java index da77b177..5115db80 100644 --- a/server/src/main/java/com/growstory/domain/account/controller/AccountController.java +++ b/server/src/main/java/com/growstory/domain/account/controller/AccountController.java @@ -7,7 +7,6 @@ import com.growstory.global.response.SingleResponseDto; import com.growstory.global.utils.UriCreator; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -35,9 +34,9 @@ public class AccountController { @Operation(summary = "회원가입", description = "사용자 정보를 입력받아 계정 생성") @PostMapping("/signup") - public ResponseEntity postAccount(@Valid @RequestBody AccountDto.Post accountPostDto) { - AccountDto.Response accountResponseDto = accountService.createAccount(accountPostDto); - URI location = UriCreator.createUri(ACCOUNT_DEFAULT_URL, accountResponseDto.getAccountId()); + public ResponseEntity postAccount(@Valid @RequestBody AccountDto.Post requestDto) { + AccountDto.Response responseDto = accountService.createAccount(requestDto); + URI location = UriCreator.createUri(ACCOUNT_DEFAULT_URL, responseDto.getAccountId()); return ResponseEntity.created(location).build(); } @@ -52,16 +51,16 @@ public ResponseEntity patchProfileImage(@RequestPart MultipartFile p @Operation(summary = "닉네임 수정", description = "입력받은 닉네임으로 정보 수정") @PatchMapping("/displayname") - public ResponseEntity patchDisplayName(@Valid @RequestBody AccountDto.DisplayNamePatch displayNamePatchDto) { - accountService.updateDisplayName(displayNamePatchDto); + public ResponseEntity patchDisplayName(@Valid @RequestBody AccountDto.DisplayNamePatch requestDto) { + accountService.updateDisplayName(requestDto); return ResponseEntity.noContent().build(); } @Operation(summary = "비밀번호 수정", description = "입력받은 비밀번호로 정보 수정") @PatchMapping("/password") - public ResponseEntity patchDisplayName(@Valid @RequestBody AccountDto.PasswordPatch passwordPatchDto) { - accountService.updatePassword(passwordPatchDto); + public ResponseEntity patchDisplayName(@Valid @RequestBody AccountDto.PasswordPatch requestDto) { + accountService.updatePassword(requestDto); return ResponseEntity.noContent().build(); } @@ -139,8 +138,8 @@ public ResponseEntity> getCommentWrit @Operation(summary = "비밀번호 검증", description = "회원탈퇴시 비밀번호 검증") @PostMapping("/password/verification") - public ResponseEntity> verifyPassword(@Valid @RequestBody AccountDto.PasswordVerify passwordVerifyDto) { - Boolean isMatched = accountService.verifyPassword(passwordVerifyDto); + public ResponseEntity> verifyPassword(@Valid @RequestBody AccountDto.PasswordVerify requestDto) { + Boolean isMatched = accountService.verifyPassword(requestDto); return ResponseEntity.ok(SingleResponseDto.builder() .status(HttpStatusCode.OK.getStatusCode()) diff --git a/server/src/main/java/com/growstory/domain/account/entity/Account.java b/server/src/main/java/com/growstory/domain/account/entity/Account.java index 5e342dca..cba96503 100644 --- a/server/src/main/java/com/growstory/domain/account/entity/Account.java +++ b/server/src/main/java/com/growstory/domain/account/entity/Account.java @@ -2,6 +2,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.growstory.domain.account.constants.AccountGrade; +import com.growstory.domain.account.constants.Status; import com.growstory.domain.board.entity.Board; import com.growstory.domain.comment.entity.Comment; import com.growstory.domain.leaf.entity.Leaf; @@ -41,7 +43,9 @@ public class Account extends BaseTimeEntity { @JsonManagedReference private List boards = new ArrayList<>(); - @OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true) + // cascade = 부모를 db에서 delete하면 자식도 지워진다. list에서 + // orphan = 부모를 db에서 delete하면 자식도 지워진다. + @OneToMany(mappedBy = "account", orphanRemoval = true) private List leaves = new ArrayList<>(); // 자신이 좋아요 누른 계정 리스트 @@ -73,26 +77,8 @@ public class Account extends BaseTimeEntity { @Enumerated(EnumType.STRING) private AccountGrade accountGrade = AccountGrade.GRADE_BRONZE; -// 식물카드개수에 의한 등급 제도 -// 50개 미만 - 브론즈 가드너 -// 50개 이상 - 실버 가드너 -// 100개 이상 - 골드 가드너 - public enum AccountGrade { - GRADE_BRONZE(1, "브론즈 가드너"), - GRADE_SILVER(2, "실버 가드너"), - GRADE_GOLD(3, "골드 가드너"); - - @Getter - private int stepNumber; - - @Getter - private String stepDescription; - - AccountGrade(int stepNumber, String stepDescription) { - this.stepNumber = stepNumber; - this.stepDescription = stepDescription; - } - } + @Enumerated(EnumType.STRING) + private Status status = Status.USER; public void addLeaf(Leaf leaf) { leaves.add(leaf); @@ -144,7 +130,7 @@ public Account(Long accountId, String email, String displayName, String password public Account(Long accountId, String email, String displayName, String password, String profileImageUrl, List boards, List leaves, List givingAccountLikes, List receivingAccountLikes, List boardLikes, List comments, - Point point, List plantObjs, List roles, AccountGrade accountGrade) { + Point point, List plantObjs, List roles, AccountGrade accountGrade, Status status) { this.accountId = accountId; this.email = email; this.displayName = displayName; @@ -160,5 +146,6 @@ public Account(Long accountId, String email, String displayName, String password this.plantObjs = plantObjs; this.roles = roles; this.accountGrade = accountGrade; + this.status = status; } -} +} \ No newline at end of file diff --git a/server/src/main/java/com/growstory/domain/account/service/AccountService.java b/server/src/main/java/com/growstory/domain/account/service/AccountService.java index 68008fa8..99af4d38 100644 --- a/server/src/main/java/com/growstory/domain/account/service/AccountService.java +++ b/server/src/main/java/com/growstory/domain/account/service/AccountService.java @@ -1,5 +1,6 @@ package com.growstory.domain.account.service; +import com.growstory.domain.account.constants.Status; import com.growstory.domain.account.dto.AccountDto; import com.growstory.domain.account.entity.Account; import com.growstory.domain.account.repository.AccountRepository; @@ -42,20 +43,24 @@ public class AccountService { private final S3Uploader s3Uploader; private final AuthUserUtils authUserUtils; - public AccountDto.Response createAccount(AccountDto.Post accountPostDto) { - verifyExistsEmail(accountPostDto.getEmail()); + public AccountDto.Response createAccount(AccountDto.Post requsetDto) { + verifyExistsEmail(requsetDto.getEmail()); - String encryptedPassword = passwordEncoder.encode(accountPostDto.getPassword()); - List roles = authorityUtils.createRoles(accountPostDto.getEmail()); - Point point = pointService.createPoint(accountPostDto.getEmail()); + Status status = Status.USER; + String encryptedPassword = passwordEncoder.encode(requsetDto.getPassword()); + List roles = authorityUtils.createRoles(requsetDto.getEmail()); + Point point = pointService.createPoint(requsetDto.getEmail()); + + //TODO: if admin@gmail.com 일때 status.admin 추가 + if (requsetDto.getEmail().equals("admin@gmail.com")) status = Status.ADMIN; Account savedAccount = accountRepository.save(Account.builder() - .displayName(accountPostDto.getDisplayName()) - .email(accountPostDto.getEmail()) + .displayName(requsetDto.getDisplayName()) + .email(requsetDto.getEmail()) .password(encryptedPassword) .point(point) .roles(roles) - .accountGrade(Account.AccountGrade.GRADE_BRONZE) + .status(status) .build()); point.updateAccount(savedAccount); @@ -76,20 +81,20 @@ public void updateProfileImage(MultipartFile profileImage) { .build()); } - public void updateDisplayName(AccountDto.DisplayNamePatch displayNamePatchDto) { + public void updateDisplayName(AccountDto.DisplayNamePatch requestDto) { Account findAccount = authUserUtils.getAuthUser(); accountRepository.save(findAccount.toBuilder() - .displayName(displayNamePatchDto.getDisplayName()) + .displayName(requestDto.getDisplayName()) .build()); } - public void updatePassword(AccountDto.PasswordPatch passwordPatchDto) { + public void updatePassword(AccountDto.PasswordPatch requestDto) { Account findAccount = authUserUtils.getAuthUser(); - String encryptedChangedPassword = passwordEncoder.encode(passwordPatchDto.getChangedPassword()); + String encryptedChangedPassword = passwordEncoder.encode(requestDto.getChangedPassword()); - if (!passwordEncoder.matches(passwordPatchDto.getPresentPassword(), findAccount.getPassword())) + if (!passwordEncoder.matches(requestDto.getPresentPassword(), findAccount.getPassword())) throw new BadCredentialsException("현재 비밀번호가 일치하지 않습니다."); if (findAccount.getPassword().equals(encryptedChangedPassword)) @@ -144,7 +149,7 @@ public Page getAccountCommentWrittenBoard(int page, in Account findAccount = findVerifiedAccount(accountId); List commentWrittenBoardList = findAccount.getComments().stream() .map(comment -> getBoardResponse(comment.getBoard())) - .distinct() + .distinct() // 같은 게시글 중복 제거 .collect(Collectors.toList()); int startIdx = page * size; @@ -161,13 +166,13 @@ public void deleteAccount() { accountRepository.delete(findAccount); } - public Boolean verifyPassword(AccountDto.PasswordVerify passwordVerifyDto) { + public Boolean verifyPassword(AccountDto.PasswordVerify requestDto) { Account findAccount = authUserUtils.getAuthUser(); - return passwordEncoder.matches(passwordVerifyDto.getPassword(), findAccount.getPassword()); + return passwordEncoder.matches(requestDto.getPassword(), findAccount.getPassword()); } - private void verifyExistsEmail(String email) { + public void verifyExistsEmail(String email) { Optional findAccount = accountRepository.findByEmail(email); if(findAccount.isPresent()) diff --git a/server/src/main/java/com/growstory/domain/leaf/controller/LeafController.java b/server/src/main/java/com/growstory/domain/leaf/controller/LeafController.java index c49d51d1..59e2099e 100644 --- a/server/src/main/java/com/growstory/domain/leaf/controller/LeafController.java +++ b/server/src/main/java/com/growstory/domain/leaf/controller/LeafController.java @@ -31,9 +31,9 @@ public class LeafController { @Operation(summary = "식물카드 생성", description = "식물 정보를 입력받아 식물카드 생성") @PostMapping - public ResponseEntity postLeaf(@Valid @RequestPart LeafDto.Post leafPostDto, + public ResponseEntity postLeaf(@Valid @RequestPart LeafDto.Post requestDto, @RequestPart MultipartFile leafImage) { - LeafDto.Response leafResponseDto = leafService.createLeaf(leafPostDto, leafImage); + LeafDto.Response leafResponseDto = leafService.createLeaf(requestDto, leafImage); URI location = UriCreator.createUri(LEAF_DEFAUTL_URL, leafResponseDto.getLeafId()); return ResponseEntity.created(location).build(); @@ -41,9 +41,9 @@ public ResponseEntity postLeaf(@Valid @RequestPart LeafDto.Post leaf @Operation(summary = "식물카드 수정", description = "입력받은 식물 정보로 식물카드 수정") @PatchMapping - public ResponseEntity patchLeaf(@Valid @RequestPart LeafDto.Patch leafPatchDto, + public ResponseEntity patchLeaf(@Valid @RequestPart LeafDto.Patch requestDto, @RequestPart(required = false) MultipartFile leafImage) { - leafService.updateLeaf(leafPatchDto, leafImage); + leafService.updateLeaf(requestDto, leafImage); return ResponseEntity.noContent().build(); } diff --git a/server/src/main/java/com/growstory/domain/leaf/service/LeafService.java b/server/src/main/java/com/growstory/domain/leaf/service/LeafService.java index 6bd4dc67..da9ad5c5 100644 --- a/server/src/main/java/com/growstory/domain/leaf/service/LeafService.java +++ b/server/src/main/java/com/growstory/domain/leaf/service/LeafService.java @@ -1,5 +1,6 @@ package com.growstory.domain.leaf.service; +import com.growstory.domain.account.constants.AccountGrade; import com.growstory.domain.account.entity.Account; import com.growstory.domain.account.service.AccountService; import com.growstory.domain.images.service.JournalImageService; @@ -33,11 +34,11 @@ public class LeafService { private final AuthUserUtils authUserUtils; private final JournalImageService journalImageService; - public LeafDto.Response createLeaf(LeafDto.Post leafPostDto, MultipartFile leafImage) { + public LeafDto.Response createLeaf(LeafDto.Post requestDto, MultipartFile leafImage) { Account findAccount = authUserUtils.getAuthUser(); Leaf leaf = Leaf.builder() - .leafName(leafPostDto.getLeafName()) - .content(leafPostDto.getContent()) + .leafName(requestDto.getLeafName()) + .content(requestDto.getContent()) .account(findAccount) .build(); @@ -54,21 +55,21 @@ public LeafDto.Response createLeaf(LeafDto.Post leafPostDto, MultipartFile leafI .build(); } - public void updateLeaf(LeafDto.Patch leafPatchDto, MultipartFile leafImage) { + public void updateLeaf(LeafDto.Patch requestDto, MultipartFile leafImage) { Account findAccount = authUserUtils.getAuthUser(); - Leaf findLeaf = findVerifiedLeafByAccount(findAccount.getAccountId(), leafPatchDto.getLeafId()); + Leaf findLeaf = findVerifiedLeafByAccount(findAccount.getAccountId(), requestDto.getLeafId()); String leafImageUrl = findLeaf.getLeafImageUrl(); - if (leafPatchDto.getIsImageUpdated()) + if (requestDto.getIsImageUpdated()) s3Uploader.deleteImageFromS3(leafImageUrl, LEAF_IMAGE_PROCESS_TYPE); if (Optional.ofNullable(leafImage).isPresent()) leafImageUrl = s3Uploader.uploadImageToS3(leafImage, LEAF_IMAGE_PROCESS_TYPE); leafRepository.save(findLeaf.toBuilder() - .leafName(Optional.ofNullable(leafPatchDto.getLeafName()).orElse(findLeaf.getLeafName())) + .leafName(Optional.ofNullable(requestDto.getLeafName()).orElse(findLeaf.getLeafName())) .leafImageUrl(leafImageUrl) - .content(Optional.ofNullable(leafPatchDto.getContent()).orElse(findLeaf.getContent())) + .content(Optional.ofNullable(requestDto.getContent()).orElse(findLeaf.getContent())) .build()); } @@ -96,7 +97,8 @@ public Leaf findLeafEntityBy(Long leafId) { } public void deleteLeaf(Long leafId) { - Account findAccount = authUserUtils.getAuthUser(); +// Account findAccount = authUserUtils.getAuthUser(); + Account findAccount = accountService.findVerifiedAccount(1L); Leaf findLeaf = findVerifiedLeafByAccount(findAccount.getAccountId(), leafId); s3Uploader.deleteImageFromS3(findLeaf.getLeafImageUrl(), LEAF_IMAGE_PROCESS_TYPE); @@ -124,14 +126,14 @@ private Leaf findVerifiedLeafByAccount(Long accountId, Long leafId) { else return findLeaf; } - public Account.AccountGrade updateAccountGrade(Account findAccount) { + public AccountGrade updateAccountGrade(Account findAccount) { int leavesNum = findAccount.getLeaves().size(); if (leavesNum < 50) { - return Account.AccountGrade.GRADE_BRONZE; + return AccountGrade.GRADE_BRONZE; } else if (leavesNum < 100) { - return Account.AccountGrade.GRADE_SILVER; + return AccountGrade.GRADE_SILVER; } else { - return Account.AccountGrade.GRADE_GOLD; + return AccountGrade.GRADE_GOLD; } } diff --git a/server/src/main/java/com/growstory/global/auth/dto/LoginDto.java b/server/src/main/java/com/growstory/global/auth/dto/LoginDto.java index c7767786..0c6b3cf0 100644 --- a/server/src/main/java/com/growstory/global/auth/dto/LoginDto.java +++ b/server/src/main/java/com/growstory/global/auth/dto/LoginDto.java @@ -17,5 +17,6 @@ public static class Response { private String email; private String displayName; private String profileImageUrl; + private String status; } } diff --git a/server/src/main/java/com/growstory/global/auth/filter/JwtAuthenticationFilter.java b/server/src/main/java/com/growstory/global/auth/filter/JwtAuthenticationFilter.java index d419b972..fbf80951 100644 --- a/server/src/main/java/com/growstory/global/auth/filter/JwtAuthenticationFilter.java +++ b/server/src/main/java/com/growstory/global/auth/filter/JwtAuthenticationFilter.java @@ -74,6 +74,7 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR .email(account.getEmail()) .displayName(URLEncoder.encode(account.getDisplayName(), "UTF-8")) .profileImageUrl(account.getProfileImageUrl()) + .status(account.getStatus().getStepDescription()) .build(); // response body에 유저 정보 저장 diff --git a/server/src/main/java/com/growstory/global/auth/handler/OAuth2AccountSuccessHandler.java b/server/src/main/java/com/growstory/global/auth/handler/OAuth2AccountSuccessHandler.java index 8150d719..34f6a7d5 100644 --- a/server/src/main/java/com/growstory/global/auth/handler/OAuth2AccountSuccessHandler.java +++ b/server/src/main/java/com/growstory/global/auth/handler/OAuth2AccountSuccessHandler.java @@ -1,5 +1,6 @@ package com.growstory.global.auth.handler; +import com.growstory.domain.account.constants.Status; import com.growstory.domain.account.entity.Account; import com.growstory.domain.account.repository.AccountRepository; import com.growstory.domain.point.entity.Point; @@ -9,21 +10,15 @@ import com.growstory.global.auth.utils.CustomAuthorityUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseCookie; -import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.util.UriComponentsBuilder; import org.yaml.snakeyaml.util.UriEncoder; import javax.servlet.ServletException; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.*; @@ -60,7 +55,8 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo .profileImageUrl(profileImageUrl) .point(point) .roles(authorities) - .accountGrade(Account.AccountGrade.GRADE_BRONZE) + //status social로 추가 + .status(Status.SOCIAL_USER) .build()); point.updateAccount(savedAccount); @@ -145,28 +141,29 @@ private Object createURI(String accessToken, String refreshToken, Account accoun .queryParam("accountId", account.getAccountId()) .queryParam("displayName", UriEncoder.encode(account.getDisplayName())) .queryParam("profileIamgeUrl", account.getProfileImageUrl()) + .queryParam("status", account.getStatus().getStepDescription()) .build() .toUri(); } - private HttpServletResponse addCookies(HttpServletResponse response, Account account, String accessToken, String refreshToken) { - response.addHeader("Set-Cookie", createCookie("access_token", accessToken).toString()); - response.addHeader("Set-Cookie", createCookie("refresh_token", refreshToken).toString()); - response.addHeader("Set-Cookie", createCookie("account_id", account.getAccountId().toString()).toString()); - response.addHeader("Set-Cookie", createCookie("displayName", UriEncoder.encode(account.getDisplayName())).toString()); - response.addHeader("Set-Cookie", createCookie("profileImageUrl", account.getProfileImageUrl()).toString()); - - return response; - } - - private ResponseCookie createCookie(String key, String value) { - ResponseCookie cookie = ResponseCookie.from(key, value) - .sameSite("") -// .domain("seb45-main-011.vercel.app") - .path("/") -// .secure(true) - .build(); - - return cookie; - } +// private HttpServletResponse addCookies(HttpServletResponse response, Account account, String accessToken, String refreshToken) { +// response.addHeader("Set-Cookie", createCookie("access_token", accessToken).toString()); +// response.addHeader("Set-Cookie", createCookie("refresh_token", refreshToken).toString()); +// response.addHeader("Set-Cookie", createCookie("account_id", account.getAccountId().toString()).toString()); +// response.addHeader("Set-Cookie", createCookie("displayName", UriEncoder.encode(account.getDisplayName())).toString()); +// response.addHeader("Set-Cookie", createCookie("profileImageUrl", account.getProfileImageUrl()).toString()); +// +// return response; +// } +// +// private ResponseCookie createCookie(String key, String value) { +// ResponseCookie cookie = ResponseCookie.from(key, value) +// .sameSite("") +//// .domain("seb45-main-011.vercel.app") +// .path("/") +//// .secure(true) +// .build(); +// +// return cookie; +// } } diff --git a/server/src/main/java/com/growstory/global/email/controller/EmailController.java b/server/src/main/java/com/growstory/global/email/controller/EmailController.java index 7cbfae90..477680f6 100644 --- a/server/src/main/java/com/growstory/global/email/controller/EmailController.java +++ b/server/src/main/java/com/growstory/global/email/controller/EmailController.java @@ -1,5 +1,6 @@ package com.growstory.global.email.controller; +import com.growstory.domain.account.service.AccountService; import com.growstory.global.constants.HttpStatusCode; import com.growstory.global.email.dto.EmailDto; import com.growstory.global.email.service.EmailService; @@ -22,11 +23,14 @@ @RequestMapping("/v1/emails") public class EmailController { private final EmailService emailService; + private final AccountService accountService; @Operation(summary = "회원가입 시 메일 인증", description = "회원가입 시 입력받은 이메일로 메일 전송") @PostMapping("/signup") - public ResponseEntity> postAuthCodeMail(@Valid @RequestBody EmailDto.Post emailPostDto) { - EmailDto.SignUpResponse responseDto = emailService.sendAuthCodeMail(emailPostDto); + public ResponseEntity> postAuthCodeMail(@Valid @RequestBody EmailDto.Post requestDto) { + // 인증시 메일 확인 하기 + accountService.verifyExistsEmail(requestDto.getEmail()); + EmailDto.SignUpResponse responseDto = emailService.sendAuthCodeMail(requestDto); return ResponseEntity.ok(SingleResponseDto.builder() .status(HttpStatusCode.OK.getStatusCode()) @@ -37,8 +41,8 @@ public ResponseEntity> postAuthCodeMa @Operation(summary = "비밀번호 찾기 시 임시 비밀번호 전송", description = "비밀번호 찾기 시 입력받은 이메일로 임시 비밀번호 전송") @PostMapping("/password") - public ResponseEntity> postPasswordMail(@Valid @RequestBody EmailDto.Post emailPostDto) { - EmailDto.PasswordResponse responseDto = emailService.sendPasswordMail(emailPostDto); + public ResponseEntity> postPasswordMail(@Valid @RequestBody EmailDto.Post requestDto) { + EmailDto.PasswordResponse responseDto = emailService.sendPasswordMail(requestDto); return ResponseEntity.ok(SingleResponseDto.builder() .status(HttpStatusCode.OK.getStatusCode()) diff --git a/server/src/main/java/com/growstory/global/email/service/EmailService.java b/server/src/main/java/com/growstory/global/email/service/EmailService.java index bc596a27..c35fa391 100644 --- a/server/src/main/java/com/growstory/global/email/service/EmailService.java +++ b/server/src/main/java/com/growstory/global/email/service/EmailService.java @@ -30,7 +30,7 @@ public class EmailService { private final PasswordEncoder passwordEncoder; // 부하 테스트 때 비동기 처리 - public EmailDto.SignUpResponse sendAuthCodeMail(EmailDto.Post emailPostDto) { + public EmailDto.SignUpResponse sendAuthCodeMail(EmailDto.Post requsetDto) { try { String authCode = getAuthCode(); @@ -40,7 +40,7 @@ public EmailDto.SignUpResponse sendAuthCodeMail(EmailDto.Post emailPostDto) { MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8"); - mimeMessageHelper.setTo(emailPostDto.getEmail()); // 수신 이메일 + mimeMessageHelper.setTo(requsetDto.getEmail()); // 수신 이메일 mimeMessageHelper.setSubject("[GrowStory] 회원가입 인증번호 안내"); // 이메일 제목 mimeMessageHelper.setText(finalText, true); // 이메일 본문 @@ -59,8 +59,8 @@ public EmailDto.SignUpResponse sendAuthCodeMail(EmailDto.Post emailPostDto) { } } - public EmailDto.PasswordResponse sendPasswordMail(EmailDto.Post emailPostDto) { - Optional optionalAccount = accountRepository.findByEmail(emailPostDto.getEmail()); + public EmailDto.PasswordResponse sendPasswordMail(EmailDto.Post requestDto) { + Optional optionalAccount = accountRepository.findByEmail(requestDto.getEmail()); if (optionalAccount.isEmpty()) { return EmailDto.PasswordResponse.builder() @@ -77,7 +77,7 @@ public EmailDto.PasswordResponse sendPasswordMail(EmailDto.Post emailPostDto) { MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8"); - mimeMessageHelper.setTo(emailPostDto.getEmail()); // 수신 이메일 + mimeMessageHelper.setTo(requestDto.getEmail()); // 수신 이메일 mimeMessageHelper.setSubject("[GrowStory] 임시 비밀번호 안내"); // 이메일 제목 mimeMessageHelper.setText(setContext(password, "password"), true); // 이메일 본문 mailSender.send(mimeMessage); From 013e9965db05728592ee70531970a76fa70a5e7b Mon Sep 17 00:00:00 2001 From: dohyoungK Date: Mon, 23 Oct 2023 16:59:33 +0900 Subject: [PATCH 012/129] =?UTF-8?q?[BE]=20=E2=99=BB=EF=B8=8F=20Refactor=20?= =?UTF-8?q?:=20Email,=20Account=20Status=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../account/constants/AccountGrade.java | 24 +++++++++++++++++++ .../domain/account/constants/Status.java | 21 ++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 server/src/main/java/com/growstory/domain/account/constants/AccountGrade.java create mode 100644 server/src/main/java/com/growstory/domain/account/constants/Status.java diff --git a/server/src/main/java/com/growstory/domain/account/constants/AccountGrade.java b/server/src/main/java/com/growstory/domain/account/constants/AccountGrade.java new file mode 100644 index 00000000..f312e33a --- /dev/null +++ b/server/src/main/java/com/growstory/domain/account/constants/AccountGrade.java @@ -0,0 +1,24 @@ +package com.growstory.domain.account.constants; + +import lombok.Getter; + +// 식물카드개수에 의한 등급 제도 +// 50개 미만 - 브론즈 가드너 +// 50개 이상 - 실버 가드너 +// 100개 이상 - 골드 가드너 +public enum AccountGrade { + GRADE_BRONZE(1, "브론즈 가드너"), + GRADE_SILVER(2, "실버 가드너"), + GRADE_GOLD(3, "골드 가드너"); + + @Getter + private int stepNumber; + + @Getter + private String stepDescription; + + AccountGrade(int stepNumber, String stepDescription) { + this.stepNumber = stepNumber; + this.stepDescription = stepDescription; + } +} diff --git a/server/src/main/java/com/growstory/domain/account/constants/Status.java b/server/src/main/java/com/growstory/domain/account/constants/Status.java new file mode 100644 index 00000000..d724df01 --- /dev/null +++ b/server/src/main/java/com/growstory/domain/account/constants/Status.java @@ -0,0 +1,21 @@ +package com.growstory.domain.account.constants; + +import lombok.Getter; + + public enum Status { + ADMIN(1, "관리자"), + USER(2, "일반 유저"), + SOCIAL_USER(3, "소셜 로그인 유저"), + BANNED_USER(4, "정지 유저"); + + @Getter + private int stepNumber; + + @Getter + private String stepDescription; + + Status(int stepNumber, String stepDescription) { + this.stepNumber = stepNumber; + this.stepDescription = stepDescription; + } +} From c93fad6255f4527d924f65d7fb9f567128c7f233 Mon Sep 17 00:00:00 2001 From: LST Date: Wed, 25 Oct 2023 08:50:32 +0900 Subject: [PATCH 013/129] =?UTF-8?q?:pencil2:=20Chore:=20Todo=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD,=20PlantObjServiceTest=20=EC=9D=BC=EB=B6=80=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A3=BC=EC=84=9D=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../account/service/AccountService.java | 1 + .../service/PlantObjServiceTest.java | 38 +++++++++---------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/server/src/main/java/com/growstory/domain/account/service/AccountService.java b/server/src/main/java/com/growstory/domain/account/service/AccountService.java index 68008fa8..80de3606 100644 --- a/server/src/main/java/com/growstory/domain/account/service/AccountService.java +++ b/server/src/main/java/com/growstory/domain/account/service/AccountService.java @@ -180,6 +180,7 @@ public Account findVerifiedAccount(Long accountId) { new BusinessLogicException(ExceptionCode.ACCOUNT_NOT_FOUND)); } + //TODO: 리팩토링 -> AuthUserUtil public void isAuthIdMatching(Long accountId) { Authentication authentication = null; Map claims = null; diff --git a/server/src/test/java/com/growstory/domain/plant_object/service/PlantObjServiceTest.java b/server/src/test/java/com/growstory/domain/plant_object/service/PlantObjServiceTest.java index 3e053d3f..dfa4085c 100644 --- a/server/src/test/java/com/growstory/domain/plant_object/service/PlantObjServiceTest.java +++ b/server/src/test/java/com/growstory/domain/plant_object/service/PlantObjServiceTest.java @@ -212,25 +212,25 @@ public void init() { assertThat(gardenInfo.getPlantObjs().get(0).getPlantObjId(), is(plantObjList.get(0).getPlantObjId())); } - @DisplayName("saveLocation Test : 프로덕트 id와 로케이션 id 불일치") - @Test - public void testSaveLocation_프로덕트id_로케이션id_불일치() { - //given - willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); - Long accountId = 1L; - List patchLocations = Stub.MockLocation.getStubPatchLocationResponses(); - patchLocations.add(PlantObjDto.PatchLocation.builder() - .plantObjId(3L) - .locationDto(LocationDto.Patch.builder() - .locationId(99L).x(0).y(0).isInstalled(false) - .build()) - .build()); - //when - BusinessLogicException exception = assertThrows(BusinessLogicException.class, - () -> plantObjService.saveLocation(accountId, patchLocations)); - //then - assertThat(exception.getExceptionCode(), is(ExceptionCode.LOCATION_NOT_ALLOW)); - } +// @DisplayName("saveLocation Test : 프로덕트 id와 로케이션 id 불일치") +// @Test +// public void testSaveLocation_프로덕트id_로케이션id_불일치() { +// //given +// willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); +// Long accountId = 1L; +// List patchLocations = Stub.MockLocation.getStubPatchLocationResponses(); +// patchLocations.add(PlantObjDto.PatchLocation.builder() +// .plantObjId(3L) +// .locationDto(LocationDto.Patch.builder() +// .locationId(99L).x(0).y(0).isInstalled(false) +// .build()) +// .build()); +// //when +// BusinessLogicException exception = assertThrows(BusinessLogicException.class, +// () -> plantObjService.saveLocation(accountId, patchLocations)); +// //then +// assertThat(exception.getExceptionCode(), is(ExceptionCode.LOCATION_NOT_ALLOW)); +// } @DisplayName("saveLocation Test : X축에 부적절한 위치 삽입1 (+좌표)") @Test From 21dfdef16add2633cff426603571e02250a33ffd Mon Sep 17 00:00:00 2001 From: dohyoungK Date: Wed, 25 Oct 2023 13:06:20 +0900 Subject: [PATCH 014/129] =?UTF-8?q?[BE]=20=E2=9C=A8=09feat=20:=20=EC=B6=9C?= =?UTF-8?q?=EC=84=9D=20=EC=B2=B4=ED=81=AC=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/account/entity/Account.java | 9 ++++++++- .../account/service/AccountService.java | 17 ++++++++++++++++ .../auth/config/PasswordEncoderConfig.java | 14 +++++++++++++ .../auth/config/SecurityConfiguration.java | 20 +++++++++++-------- .../auth/filter/JwtVerificationFilter.java | 5 ----- .../AccountAuthenticationSuccessHandler.java | 14 +++++++++++++ .../handler/OAuth2AccountSuccessHandler.java | 7 ++++--- 7 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 server/src/main/java/com/growstory/global/auth/config/PasswordEncoderConfig.java diff --git a/server/src/main/java/com/growstory/domain/account/entity/Account.java b/server/src/main/java/com/growstory/domain/account/entity/Account.java index cba96503..8f898978 100644 --- a/server/src/main/java/com/growstory/domain/account/entity/Account.java +++ b/server/src/main/java/com/growstory/domain/account/entity/Account.java @@ -43,7 +43,7 @@ public class Account extends BaseTimeEntity { @JsonManagedReference private List boards = new ArrayList<>(); - // cascade = 부모를 db에서 delete하면 자식도 지워진다. list에서 + // cascade = 부모를 db에서 delete하면 자식도 지워진다. // orphan = 부모를 db에서 delete하면 자식도 지워진다. @OneToMany(mappedBy = "account", orphanRemoval = true) private List leaves = new ArrayList<>(); @@ -80,6 +80,9 @@ public class Account extends BaseTimeEntity { @Enumerated(EnumType.STRING) private Status status = Status.USER; + // 출석 체크 + private Boolean attendance = false; + public void addLeaf(Leaf leaf) { leaves.add(leaf); } @@ -102,6 +105,10 @@ public void updatePoint(Point point) { point.updateAccount(this); } + public void updateAttendance(Boolean attendance) { + this.attendance = attendance; + } + public void addBoardLike(BoardLike boardLike) { boardLikes.add(boardLike); } diff --git a/server/src/main/java/com/growstory/domain/account/service/AccountService.java b/server/src/main/java/com/growstory/domain/account/service/AccountService.java index 99af4d38..45808fb5 100644 --- a/server/src/main/java/com/growstory/domain/account/service/AccountService.java +++ b/server/src/main/java/com/growstory/domain/account/service/AccountService.java @@ -17,6 +17,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -166,6 +167,22 @@ public void deleteAccount() { accountRepository.delete(findAccount); } + // 출석 체크 + public void attendanceCheck(Account account) { + if (!account.getAttendance()) { + account.updatePoint(pointService.updatePoint(account.getPoint(), "login")); + account.updateAttendance(true); + accountRepository.save(account); + } + } + + // 자정에 초기화 + @Scheduled(cron = "0 0 0 * * *") + public void attendanceReset() { + accountRepository.findAll() + .forEach(account -> account.updateAttendance(false)); + } + public Boolean verifyPassword(AccountDto.PasswordVerify requestDto) { Account findAccount = authUserUtils.getAuthUser(); diff --git a/server/src/main/java/com/growstory/global/auth/config/PasswordEncoderConfig.java b/server/src/main/java/com/growstory/global/auth/config/PasswordEncoderConfig.java new file mode 100644 index 00000000..a466bc54 --- /dev/null +++ b/server/src/main/java/com/growstory/global/auth/config/PasswordEncoderConfig.java @@ -0,0 +1,14 @@ +package com.growstory.global.auth.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class PasswordEncoderConfig { + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } +} diff --git a/server/src/main/java/com/growstory/global/auth/config/SecurityConfiguration.java b/server/src/main/java/com/growstory/global/auth/config/SecurityConfiguration.java index 805fcffa..78cc6fa1 100644 --- a/server/src/main/java/com/growstory/global/auth/config/SecurityConfiguration.java +++ b/server/src/main/java/com/growstory/global/auth/config/SecurityConfiguration.java @@ -1,6 +1,7 @@ package com.growstory.global.auth.config; import com.growstory.domain.account.repository.AccountRepository; +import com.growstory.domain.account.service.AccountService; import com.growstory.domain.point.repository.PointRepository; import com.growstory.domain.point.service.PointService; import com.growstory.global.auth.filter.JwtAuthenticationFilter; @@ -17,8 +18,6 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; import org.springframework.security.web.SecurityFilterChain; @@ -27,6 +26,7 @@ @RequiredArgsConstructor public class SecurityConfiguration { private final JwtTokenizer jwtTokenizer; + private final AccountService accountService; private final AccountRepository accountRepository; private final CustomAuthorityUtils authorityUtils; private final SecurityCorsConfig corsConfig; @@ -57,15 +57,19 @@ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Excepti .anyRequest().permitAll()) .oauth2Login(oauth2 -> { oauth2.failureHandler(new OAuth2AccountFailureHandler()); - oauth2.successHandler(new OAuth2AccountSuccessHandler(jwtTokenizer, authorityUtils, accountRepository, pointService, pointRepository)); + oauth2.successHandler(new OAuth2AccountSuccessHandler(jwtTokenizer, authorityUtils, accountService, accountRepository, pointService, pointRepository)); }) .build(); } - @Bean - public PasswordEncoder passwordEncoder() { - return PasswordEncoderFactories.createDelegatingPasswordEncoder(); - } + // securityConfiguration 내부에서 passwordEncoder를 Bean으로 등록하기 때문에 + // accountService와 순환참조 발생 + // 따라서 PasswordEncoderConfig파일을 따로 만들어 + // 외부에서 passwordEncoder를 Bean으로 등록 +// @Bean +// public PasswordEncoder passwordEncoder() { +// return PasswordEncoderFactories.createDelegatingPasswordEncoder(); +// } public class CustomFilterConfigurer extends AbstractHttpConfigurer { @Override @@ -76,7 +80,7 @@ public void configure(HttpSecurity builder) throws Exception { JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager, jwtTokenizer); // JwtAuthenticationFilter 객체 생성하며 DI하기 // AbstractAuthenticationProcessingFilter에서 상속받은 filterProcessurl을 설정 (설정하지 않으면 default 값인 /Login) jwtAuthenticationFilter.setFilterProcessesUrl("/v1/accounts/authentication"); - jwtAuthenticationFilter.setAuthenticationSuccessHandler(new AccountAuthenticationSuccessHandler()); + jwtAuthenticationFilter.setAuthenticationSuccessHandler(new AccountAuthenticationSuccessHandler(accountRepository, accountService)); jwtAuthenticationFilter.setAuthenticationFailureHandler(new AccountAuthenticationFailureHandler()); JwtVerificationFilter jwtVerificationFilter = new JwtVerificationFilter(jwtTokenizer, authorityUtils); diff --git a/server/src/main/java/com/growstory/global/auth/filter/JwtVerificationFilter.java b/server/src/main/java/com/growstory/global/auth/filter/JwtVerificationFilter.java index a1f10f90..e81b4cc7 100644 --- a/server/src/main/java/com/growstory/global/auth/filter/JwtVerificationFilter.java +++ b/server/src/main/java/com/growstory/global/auth/filter/JwtVerificationFilter.java @@ -2,9 +2,6 @@ import com.growstory.global.auth.jwt.JwtTokenizer; import com.growstory.global.auth.utils.CustomAuthorityUtils; -import com.growstory.global.exception.BusinessLogicException; -import com.growstory.global.exception.ExceptionCode; -import com.growstory.global.response.ErrorResponder; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.SignatureException; @@ -12,7 +9,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.json.BasicJsonParser; import org.springframework.boot.json.JsonParser; -import org.springframework.http.HttpStatus; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -26,7 +22,6 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Date; -import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/server/src/main/java/com/growstory/global/auth/handler/AccountAuthenticationSuccessHandler.java b/server/src/main/java/com/growstory/global/auth/handler/AccountAuthenticationSuccessHandler.java index 3cbfd1f4..e9512e5e 100644 --- a/server/src/main/java/com/growstory/global/auth/handler/AccountAuthenticationSuccessHandler.java +++ b/server/src/main/java/com/growstory/global/auth/handler/AccountAuthenticationSuccessHandler.java @@ -1,5 +1,10 @@ package com.growstory.global.auth.handler; +import com.growstory.domain.account.entity.Account; +import com.growstory.domain.account.repository.AccountRepository; +import com.growstory.domain.account.service.AccountService; +import com.growstory.global.exception.BusinessLogicException; +import com.growstory.global.exception.ExceptionCode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; @@ -9,13 +14,22 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.Map; @Slf4j @RequiredArgsConstructor public class AccountAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + private final AccountRepository accountRepository; + private final AccountService accountService; + @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { log.info("# Authenticated successfully !"); + + Account account = accountRepository.findById(((Account) authentication.getPrincipal()).getAccountId()).orElseThrow(() -> + new BusinessLogicException(ExceptionCode.ACCOUNT_NOT_FOUND)); + + accountService.attendanceCheck(account); } } diff --git a/server/src/main/java/com/growstory/global/auth/handler/OAuth2AccountSuccessHandler.java b/server/src/main/java/com/growstory/global/auth/handler/OAuth2AccountSuccessHandler.java index 34f6a7d5..3081eb64 100644 --- a/server/src/main/java/com/growstory/global/auth/handler/OAuth2AccountSuccessHandler.java +++ b/server/src/main/java/com/growstory/global/auth/handler/OAuth2AccountSuccessHandler.java @@ -3,6 +3,7 @@ import com.growstory.domain.account.constants.Status; import com.growstory.domain.account.entity.Account; import com.growstory.domain.account.repository.AccountRepository; +import com.growstory.domain.account.service.AccountService; import com.growstory.domain.point.entity.Point; import com.growstory.domain.point.repository.PointRepository; import com.growstory.domain.point.service.PointService; @@ -30,6 +31,7 @@ public class OAuth2AccountSuccessHandler extends SimpleUrlAuthenticationSuccessH private final JwtTokenizer jwtTokenizer; private final CustomAuthorityUtils authorityUtils; + private final AccountService accountService; private final AccountRepository accountRepository; private final PointService pointService; private final PointRepository pointRepository; @@ -61,6 +63,8 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo point.updateAccount(savedAccount); pointRepository.save(point); + } else { + accountService.attendanceCheck(optionalAccount.get()); } redirect(request, response, optionalAccount.orElse(savedAccount), authorities); @@ -131,9 +135,6 @@ private Object createURI(String accessToken, String refreshToken, Account accoun .newInstance() .scheme("https") .host("growstory.vercel.app") -// .port(3000) -// .host("growstory.s3-website.ap-northeast-2.amazonaws.com") -// .port(80) //S3는 80포트 .port(443) .path("/signin") .queryParam("access_token", accessToken) From 5e4658f1a798362af2e157b839a20141b3d84bb4 Mon Sep 17 00:00:00 2001 From: nalsae <101828759+nalsae@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:06:06 +0900 Subject: [PATCH 015/129] =?UTF-8?q?=F0=9F=93=9D=20Docs=20:=20README.md=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 09173024..1f74198f 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ ## 📜 서비스 매뉴얼 -#### [📥 서비스 매뉴얼 다운로드](https://github.com/nalsae/seb45_main_011/files/12662276/Grow.Story.pdf) +#### [📥 서비스 매뉴얼 다운로드](https://github.com/codestates-seb/seb45_main_011/files/12667869/Grow.Story.pdf)
From b9e662a8e1e2e2b1b071c56bfb0d22f2f85cecb5 Mon Sep 17 00:00:00 2001 From: hanbinchoi Date: Wed, 20 Sep 2023 10:14:38 +0900 Subject: [PATCH 016/129] =?UTF-8?q?[FE]=20=E2=99=BB=EF=B8=8F=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EC=83=81=EC=84=B8=20=EB=B9=84=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=A0=91=EA=B7=BC=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/app/post/[id]/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/app/post/[id]/page.tsx b/client/src/app/post/[id]/page.tsx index fb7a7cb8..02f444a1 100644 --- a/client/src/app/post/[id]/page.tsx +++ b/client/src/app/post/[id]/page.tsx @@ -47,8 +47,6 @@ export default function Post({ params }: PostProps) { const { userId } = useUserStore(); const { isOpen, type } = usePostModalStore(); - useEffectOnce(() => router.refresh()); - const { data: post, isLoading, @@ -65,6 +63,8 @@ export default function Post({ params }: PostProps) { useEffectOnce(() => { window.scrollTo(0, 0); + if (!userId) return router.push('/signin'); + return router.refresh(); }); useEffect(() => { From 9a2d95642e8f9e837c3712aaad8681c400450727 Mon Sep 17 00:00:00 2001 From: hanbinchoi Date: Wed, 20 Sep 2023 15:01:53 +0900 Subject: [PATCH 017/129] =?UTF-8?q?[FE]=20=E2=99=BB=EF=B8=8F=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EC=83=81=EC=84=B8=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EB=B9=84=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=B7=B0=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/app/post/[id]/page.tsx | 12 ++++-------- client/src/app/post/add/page.tsx | 13 ++++++++++++- client/src/components/board/RankBoard.tsx | 3 ++- client/src/components/common/PostCountInfo.tsx | 13 ++++++++++++- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/client/src/app/post/[id]/page.tsx b/client/src/app/post/[id]/page.tsx index 02f444a1..1401e484 100644 --- a/client/src/app/post/[id]/page.tsx +++ b/client/src/app/post/[id]/page.tsx @@ -51,19 +51,14 @@ export default function Post({ params }: PostProps) { data: post, isLoading, isError, - } = useQuery( - ['post', boardId], - () => getPostByBoardId(boardId), - { - enabled: !!userId, - }, + } = useQuery(['post', boardId], () => + getPostByBoardId(boardId), ); const isOwner = userId === String(post?.accountId); useEffectOnce(() => { window.scrollTo(0, 0); - if (!userId) return router.push('/signin'); return router.refresh(); }); @@ -128,7 +123,8 @@ export default function Post({ params }: PostProps) { boardId={String(post.boardId)} className="mb-3" /> - + {userId && } +