From 3ad7f105cb491e6bae3edd4d7a8b1eaaefda0534 Mon Sep 17 00:00:00 2001 From: Jeonghoon Park <39729721+shb03323@users.noreply.github.com> Date: Fri, 11 Aug 2023 14:29:53 +0900 Subject: [PATCH] =?UTF-8?q?=EC=84=9C=ED=8F=AC=ED=84=B0=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EB=B3=80=EA=B2=BD=20API=20(#315)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 로그인 한 사용자 조회 API 구현 * test: 인수 테스트 성공 케이스 작성 * refactor: restdocsConfig 변경 * test: 로그인된 사용자 조회 API mock mvc 테스트 진행 * refactor: 코드 리뷰 반영 * feat: request dto 생성 * test: 인수 테스트 작성 * test: 인수 테스트 수정 * feat: technical tag 이름으로 technical tag 찾는 기능 추가 * feat: supporter 와 technicalTag 로 supporterTechnicalTag 찾는 기능 구현 * refactor: supporter 와 technicalTag 로 supporterTechnicalTag 찾는 기능 제거 * feat: API 구현 * test: restdocs 테스트 구현 * refactor: submodule 변경 * refactor: submodule 수정 * refactor: Location 반환하도록 변경 * refactor: static 으로 된 메서드들 non-static 으로 변경 * feat: 서포터로 SupporterTechnicalTag 제거 기능 구현 * refactor: RunnerTechnicalTag nullable 하게 변경 * refactor: Batch delete 방식으로 데이터 삭제하도록 변경 * refactor: uri 매핑 형식 변경 * test: 공백 제거 * refactor: batch delete 시에 flush & clear * test: 오타 수정 --- backend/baton/secret | 2 +- .../asciidoc/MemberLoginProfileReadApi.adoc | 2 +- .../asciidoc/SupporterProfileUpdateApi.adoc | 30 ++++++ .../common/exception/ClientErrorCode.java | 7 +- .../domain/common/response/ErrorResponse.java | 12 +-- .../touch/baton/domain/member/Member.java | 51 +++++++--- .../touch/baton/domain/runner/Runner.java | 8 +- .../baton/domain/supporter/Supporter.java | 32 ++++++- .../SupporterProfileController.java | 16 ++++ .../supporter/service/SupporterService.java | 40 ++++++++ .../service/dto/SupporterUpdateRequest.java | 16 ++++ .../SupporterTechnicalTagRepository.java | 15 +++ .../repository/TechnicalTagRepository.java | 5 + .../baton/src/main/resources/application.yml | 2 +- .../baton/assure/common/AssuredSupport.java | 13 +++ .../SupporterProfileAssuredSupport.java | 34 +++++++ .../SupporterProfileAssuredUpdateTest.java | 94 ++++++++++++++++++ .../touch/baton/config/ServiceTestConfig.java | 10 +- .../update/SupporterProfileUpdateApiTest.java | 96 +++++++++++++++++++ .../touch/baton/domain/member/MemberTest.java | 57 ++++++++++- .../touch/baton/domain/runner/RunnerTest.java | 6 +- .../baton/domain/supporter/SupporterTest.java | 95 ++++++++++++------ .../service/SupporterServiceTest.java | 21 +++- .../SupporterTechnicalTagsTest.java | 42 ++++++++ .../SupporterTechnicalTagRepositoryTest.java | 57 +++++++++++ .../TechnicalTagRepositoryTest.java | 52 ++++++++++ 26 files changed, 741 insertions(+), 74 deletions(-) create mode 100644 backend/baton/src/docs/asciidoc/SupporterProfileUpdateApi.adoc create mode 100644 backend/baton/src/main/java/touch/baton/domain/supporter/service/dto/SupporterUpdateRequest.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/SupporterTechnicalTagRepository.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/supporter/SupporterProfileAssuredUpdateTest.java create mode 100644 backend/baton/src/test/java/touch/baton/document/profile/supporter/update/SupporterProfileUpdateApiTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/technicaltag/SupporterTechnicalTagsTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/SupporterTechnicalTagRepositoryTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/TechnicalTagRepositoryTest.java diff --git a/backend/baton/secret b/backend/baton/secret index b8a3860ca..c4577ebb1 160000 --- a/backend/baton/secret +++ b/backend/baton/secret @@ -1 +1 @@ -Subproject commit b8a3860ca41dbbffe0eed41298da6b391d13ba55 +Subproject commit c4577ebb1bd14b1152d980133bc5a413ed5aa4e9 diff --git a/backend/baton/src/docs/asciidoc/MemberLoginProfileReadApi.adoc b/backend/baton/src/docs/asciidoc/MemberLoginProfileReadApi.adoc index 574f38271..bac5cbeee 100644 --- a/backend/baton/src/docs/asciidoc/MemberLoginProfileReadApi.adoc +++ b/backend/baton/src/docs/asciidoc/MemberLoginProfileReadApi.adoc @@ -12,7 +12,7 @@ endif::[] == *로그인된 사용자 프로필 조회* -=== *로그인된 사용자 프로필 조회 조회 API* +=== *로그인된 사용자 프로필 조회 API* ==== *Http Request* include::{snippets}/../../build/generated-snippets/member-login-profile-read-api-test/read-login-member-by-access-token/http-request.adoc[] diff --git a/backend/baton/src/docs/asciidoc/SupporterProfileUpdateApi.adoc b/backend/baton/src/docs/asciidoc/SupporterProfileUpdateApi.adoc new file mode 100644 index 000000000..2615456b7 --- /dev/null +++ b/backend/baton/src/docs/asciidoc/SupporterProfileUpdateApi.adoc @@ -0,0 +1,30 @@ +ifndef::snippets[] +:snippets: ../../../build/generated-snippets +endif::[] +:doctype: investment +:icons: font +:source-highlighter: highlight.js +:toc: left +:toclevels: 2 +:sectlinks: +:operation-http-request-title: Example Request +:operation-http-response-title: Example Response + +== *서포터 프로필 수정* + +=== *서포터 프로필 수정 API* + +==== *Http Request* +include::{snippets}/../../build/generated-snippets/supporter-profile-update-api-test/update-supporter-profile/http-request.adoc[] + +==== *Http Request Headers* +include::{snippets}/../../build/generated-snippets/supporter-profile-update-api-test/update-supporter-profile/request-headers.adoc[] + +==== *Http Request Body* +include::{snippets}/../../build/generated-snippets/supporter-profile-update-api-test/update-supporter-profile/request-body.adoc[] + +==== *Http Response* +include::{snippets}/../../build/generated-snippets/supporter-profile-update-api-test/update-supporter-profile/http-response.adoc[] + +==== *Http Response Headers* +include::{snippets}/../../build/generated-snippets/supporter-profile-update-api-test/update-supporter-profile/response-headers.adoc[] diff --git a/backend/baton/src/main/java/touch/baton/domain/common/exception/ClientErrorCode.java b/backend/baton/src/main/java/touch/baton/domain/common/exception/ClientErrorCode.java index 9fa776907..fe01a8bb2 100644 --- a/backend/baton/src/main/java/touch/baton/domain/common/exception/ClientErrorCode.java +++ b/backend/baton/src/main/java/touch/baton/domain/common/exception/ClientErrorCode.java @@ -17,11 +17,16 @@ public enum ClientErrorCode { SUPPORTER_ID_IS_NULL(HttpStatus.BAD_REQUEST, "FB002", "서포터 식별자를 입력해주세요."), RUNNER_ID_IS_NULL(HttpStatus.BAD_REQUEST, "FB003", "러너 식별자를 입력해주세요."), - COMPANY_IS_NULL(HttpStatus.BAD_REQUEST, "OM001", "사용자의 회사 정보를 입력해주세요."), + NAME_IS_NULL(HttpStatus.BAD_REQUEST, "MB001", "사용자의 이름을 입력해주세요."), + COMPANY_IS_NULL(HttpStatus.BAD_REQUEST, "MB002", "사용자의 회사 정보를 입력해주세요."), + SUPPORTER_TECHNICAL_TAGS_ARE_NULL(HttpStatus.BAD_REQUEST, "MB003", "서포터 기술 태그 목록을 빈 값이라도 입력해주세요."), + RUNNER_TECHNICAL_TAGS_ARE_NULL(HttpStatus.BAD_REQUEST, "MB004", "러너 기술 태그 목록을 빈 값이라도 입력해주세요."), + OAUTH_REQUEST_URL_PROVIDER_IS_WRONG(HttpStatus.UNAUTHORIZED, "OA001", "redirect 할 url 이 조회되지 않는 잘못된 소셜 타입입니다."), OAUTH_INFORMATION_CLIENT_IS_WRONG(HttpStatus.UNAUTHORIZED, "OA002", " 소셜 계정 정보를 조회할 수 없는 잘못된 소셜 타입입니다."), OAUTH_AUTHORIZATION_VALUE_IS_NULL(HttpStatus.UNAUTHORIZED, "OA003", "Authorization 값을 입력해주세요."), OAUTH_AUTHORIZATION_BEARER_TYPE_NOT_FOUND(HttpStatus.UNAUTHORIZED, "OA004", "Authorization 값을 Bearer 타입으로 입력해주세요."), + JWT_SIGNATURE_IS_WRONG(HttpStatus.UNAUTHORIZED, "JW001", "시그니처가 다른 잘못된 JWT 입니다."), JWT_FORM_IS_WRONG(HttpStatus.UNAUTHORIZED, "JW002", "잘못 생성된 JWT 로 디코딩 할 수 없습니다."), JWT_CLAIM_IS_WRONG(HttpStatus.UNAUTHORIZED, "JW003", "JWT 에 기대한 정보를 모두 포함하고 있지 않습니다."), diff --git a/backend/baton/src/main/java/touch/baton/domain/common/response/ErrorResponse.java b/backend/baton/src/main/java/touch/baton/domain/common/response/ErrorResponse.java index fcf1f08a8..d2ad39ad5 100644 --- a/backend/baton/src/main/java/touch/baton/domain/common/response/ErrorResponse.java +++ b/backend/baton/src/main/java/touch/baton/domain/common/response/ErrorResponse.java @@ -1,18 +1,8 @@ package touch.baton.domain.common.response; -import lombok.Getter; import touch.baton.domain.common.exception.ClientRequestException; -@Getter -public class ErrorResponse { - - private final String errorCode; - private final String message; - - private ErrorResponse(final String errorCode, final String message) { - this.errorCode = errorCode; - this.message = message; - } +public record ErrorResponse(String errorCode, String message) { public static ErrorResponse from(final ClientRequestException e) { return new ErrorResponse(e.getErrorCode().getErrorCode(), e.getMessage()); diff --git a/backend/baton/src/main/java/touch/baton/domain/member/Member.java b/backend/baton/src/main/java/touch/baton/domain/member/Member.java index e5b69cab9..61925a91b 100644 --- a/backend/baton/src/main/java/touch/baton/domain/member/Member.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/Member.java @@ -84,28 +84,57 @@ private void validateNotNull(final MemberName memberName, final Company company, final ImageUrl imageUrl ) { - if (Objects.isNull(memberName)) { - throw new MemberDomainException("Member 의 name 은 null 일 수 없습니다."); - } + validateMemberNameNotNull(memberName); + validateSocialIdNotNull(socialId); + validateOauthIdNotNull(oauthId); + validateGithubUrlNotNull(githubUrl); + validateCompanyNotNull(company); + validateImageUrlNotNull(imageUrl); + } - if (Objects.isNull(socialId)) { - throw new MemberDomainException("Member 의 socialId 은 null 일 수 없습니다."); + private void validateImageUrlNotNull(final ImageUrl imageUrl) { + if (Objects.isNull(imageUrl)) { + throw new MemberDomainException("Member 의 imageUrl 은 null 일 수 없습니다."); } + } - if (Objects.isNull(oauthId)) { - throw new MemberDomainException("Member 의 oauthId 는 null 일 수 없습니다."); + private void validateCompanyNotNull(final Company company) { + if (Objects.isNull(company)) { + throw new MemberDomainException("Member 의 company 는 null 일 수 없습니다."); } + } + private void validateGithubUrlNotNull(final GithubUrl githubUrl) { if (Objects.isNull(githubUrl)) { throw new MemberDomainException("Member 의 githubUrl 은 null 일 수 없습니다."); } + } - if (Objects.isNull(company)) { - throw new MemberDomainException("Member 의 company 는 null 일 수 없습니다."); + private void validateOauthIdNotNull(final OauthId oauthId) { + if (Objects.isNull(oauthId)) { + throw new MemberDomainException("Member 의 oauthId 는 null 일 수 없습니다."); } + } - if (Objects.isNull(imageUrl)) { - throw new MemberDomainException("Member 의 imageUrl 은 null 일 수 없습니다."); + private void validateSocialIdNotNull(final SocialId socialId) { + if (Objects.isNull(socialId)) { + throw new MemberDomainException("Member 의 socialId 은 null 일 수 없습니다."); } } + + private void validateMemberNameNotNull(final MemberName memberName) { + if (Objects.isNull(memberName)) { + throw new MemberDomainException("Member 의 name 은 null 일 수 없습니다."); + } + } + + public void updateMemberName(final MemberName memberName) { + validateMemberNameNotNull(memberName); + this.memberName = memberName; + } + + public void updateCompany(final Company company) { + validateCompanyNotNull(company); + this.company = company; + } } diff --git a/backend/baton/src/main/java/touch/baton/domain/runner/Runner.java b/backend/baton/src/main/java/touch/baton/domain/runner/Runner.java index a0425e79a..627af7a9b 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runner/Runner.java +++ b/backend/baton/src/main/java/touch/baton/domain/runner/Runner.java @@ -56,21 +56,17 @@ private Runner(final Long id, final Member member, final RunnerTechnicalTags runnerTechnicalTags ) { - validateNotNull(member, runnerTechnicalTags); + validateMemberNotNull(member); this.id = id; this.introduction = introduction; this.member = member; this.runnerTechnicalTags = runnerTechnicalTags; } - private void validateNotNull(final Member member, final RunnerTechnicalTags runnerTechnicalTags) { + private void validateMemberNotNull(final Member member) { if (Objects.isNull(member)) { throw new RunnerDomainException("Runner 의 member 는 null 일 수 없습니다."); } - - if (Objects.isNull(runnerTechnicalTags)) { - throw new RunnerDomainException("Runner 의 runnerTechnicalTags 는 null 일 수 없습니다."); - } } public void addAllRunnerTechnicalTags(final List runnerTechnicalTags) { diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/Supporter.java b/backend/baton/src/main/java/touch/baton/domain/supporter/Supporter.java index 78c1a4fc7..46534dd97 100644 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/Supporter.java +++ b/backend/baton/src/main/java/touch/baton/domain/supporter/Supporter.java @@ -13,6 +13,8 @@ import touch.baton.domain.common.BaseEntity; import touch.baton.domain.common.vo.Introduction; import touch.baton.domain.member.Member; +import touch.baton.domain.member.vo.Company; +import touch.baton.domain.member.vo.MemberName; import touch.baton.domain.supporter.exception.SupporterDomainException; import touch.baton.domain.supporter.vo.ReviewCount; import touch.baton.domain.technicaltag.SupporterTechnicalTag; @@ -74,16 +76,26 @@ private void validateNotNull(final ReviewCount reviewCount, final Member member, final SupporterTechnicalTags supporterTechnicalTags ) { - if (Objects.isNull(reviewCount)) { - throw new SupporterDomainException("Supporter 의 reviewCount 는 null 일 수 없습니다."); + validateReviewCountNotNull(reviewCount); + validateMemberNotNull(member); + validateSupporterTechnicalTagsNotNull(supporterTechnicalTags); + } + + private void validateSupporterTechnicalTagsNotNull(final SupporterTechnicalTags supporterTechnicalTags) { + if (Objects.isNull(supporterTechnicalTags)) { + throw new SupporterDomainException("Supporter 의 supporterTechnicalTags 는 null 일 수 없습니다."); } + } + private void validateMemberNotNull(final Member member) { if (Objects.isNull(member)) { throw new SupporterDomainException("Supporter 의 member 는 null 일 수 없습니다."); } + } - if (Objects.isNull(supporterTechnicalTags)) { - throw new SupporterDomainException("Supporter 의 supporterTechnicalTags 는 null 일 수 없습니다."); + private void validateReviewCountNotNull(final ReviewCount reviewCount) { + if (Objects.isNull(reviewCount)) { + throw new SupporterDomainException("Supporter 의 reviewCount 는 null 일 수 없습니다."); } } @@ -91,6 +103,18 @@ public void addAllSupporterTechnicalTags(final List suppo this.supporterTechnicalTags.addAll(supporterTechnicalTags); } + public void updateMemberName(final MemberName memberName) { + this.member.updateMemberName(memberName); + } + + public void updateCompany(final Company company) { + this.member.updateCompany(company); + } + + public void updateIntroduction(final Introduction introduction) { + this.introduction = introduction; + } + @Override public boolean equals(final Object o) { if (this == o) return true; diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/controller/SupporterProfileController.java b/backend/baton/src/main/java/touch/baton/domain/supporter/controller/SupporterProfileController.java index 49fb54b40..989029507 100644 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/controller/SupporterProfileController.java +++ b/backend/baton/src/main/java/touch/baton/domain/supporter/controller/SupporterProfileController.java @@ -1,14 +1,22 @@ package touch.baton.domain.supporter.controller; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.UriComponentsBuilder; +import touch.baton.domain.oauth.controller.resolver.AuthSupporterPrincipal; import touch.baton.domain.supporter.Supporter; import touch.baton.domain.supporter.controller.response.SupporterResponse; import touch.baton.domain.supporter.service.SupporterService; +import touch.baton.domain.supporter.service.dto.SupporterUpdateRequest; + +import java.net.URI; @RequiredArgsConstructor @RequestMapping("/api/v1/profile/supporter") @@ -24,4 +32,12 @@ public ResponseEntity readProfileBySupporterId(@PathV return ResponseEntity.ok(response); } + + @PatchMapping("/me") + public ResponseEntity updateProfile(@AuthSupporterPrincipal final Supporter supporter, + @RequestBody @Valid final SupporterUpdateRequest request) { + supporterService.updateSupporter(supporter, request); + final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/profile/supporter/me").build().toUri(); + return ResponseEntity.noContent().location(redirectUri).build(); + } } diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/service/SupporterService.java b/backend/baton/src/main/java/touch/baton/domain/supporter/service/SupporterService.java index d093c7803..935e6b53b 100644 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/service/SupporterService.java +++ b/backend/baton/src/main/java/touch/baton/domain/supporter/service/SupporterService.java @@ -3,11 +3,21 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import touch.baton.domain.common.vo.Introduction; +import touch.baton.domain.common.vo.TagName; +import touch.baton.domain.member.vo.Company; +import touch.baton.domain.member.vo.MemberName; import touch.baton.domain.supporter.Supporter; import touch.baton.domain.supporter.exception.SupporterBusinessException; import touch.baton.domain.supporter.repository.SupporterRepository; +import touch.baton.domain.supporter.service.dto.SupporterUpdateRequest; +import touch.baton.domain.technicaltag.SupporterTechnicalTag; +import touch.baton.domain.technicaltag.TechnicalTag; +import touch.baton.domain.technicaltag.repository.SupporterTechnicalTagRepository; +import touch.baton.domain.technicaltag.repository.TechnicalTagRepository; import java.util.List; +import java.util.Optional; @RequiredArgsConstructor @Transactional(readOnly = true) @@ -15,6 +25,8 @@ public class SupporterService { private final SupporterRepository supporterRepository; + private final TechnicalTagRepository technicalTagRepository; + private final SupporterTechnicalTagRepository supporterTechnicalTagRepository; public List readAllSupporters() { return supporterRepository.findAll(); @@ -24,4 +36,32 @@ public Supporter readBySupporterId(final Long supporterId) { return supporterRepository.joinMemberBySupporterId(supporterId) .orElseThrow(() -> new SupporterBusinessException("존재하지 않는 서포터 식별자값으로 조회할 수 없습니다.")); } + + @Transactional + public void updateSupporter(final Supporter supporter, final SupporterUpdateRequest supporterUpdateRequest) { + supporter.updateMemberName(new MemberName(supporterUpdateRequest.name())); + supporter.updateCompany(new Company(supporterUpdateRequest.company())); + supporter.updateIntroduction(new Introduction(supporterUpdateRequest.introduction())); + supporterTechnicalTagRepository.deleteBySupporter(supporter); + supporterUpdateRequest.technicalTags() + .forEach(tagName -> createSupporterTechnicalTag(supporter, new TagName(tagName))); + } + + private SupporterTechnicalTag createSupporterTechnicalTag(final Supporter supporter, final TagName tagName) { + final TechnicalTag technicalTag = findTechnicalTagIfExistElseCreate(tagName); + return supporterTechnicalTagRepository.save(SupporterTechnicalTag.builder() + .supporter(supporter) + .technicalTag(technicalTag) + .build() + ); + } + + private TechnicalTag findTechnicalTagIfExistElseCreate(final TagName tagName) { + final Optional maybeTechnicalTag = technicalTagRepository.findByTagName(tagName); + return maybeTechnicalTag.orElseGet(() -> + technicalTagRepository.save(TechnicalTag.builder() + .tagName(tagName) + .build() + )); + } } diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/service/dto/SupporterUpdateRequest.java b/backend/baton/src/main/java/touch/baton/domain/supporter/service/dto/SupporterUpdateRequest.java new file mode 100644 index 000000000..5ced4dd94 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/supporter/service/dto/SupporterUpdateRequest.java @@ -0,0 +1,16 @@ +package touch.baton.domain.supporter.service.dto; + +import touch.baton.domain.common.exception.validator.ValidNotNull; + +import java.util.List; + +import static touch.baton.domain.common.exception.ClientErrorCode.*; + +public record SupporterUpdateRequest(@ValidNotNull(clientErrorCode = NAME_IS_NULL) + String name, + @ValidNotNull(clientErrorCode = COMPANY_IS_NULL) + String company, + String introduction, + @ValidNotNull(clientErrorCode = SUPPORTER_TECHNICAL_TAGS_ARE_NULL) + List technicalTags) { +} diff --git a/backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/SupporterTechnicalTagRepository.java b/backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/SupporterTechnicalTagRepository.java new file mode 100644 index 000000000..ace8e6c7f --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/SupporterTechnicalTagRepository.java @@ -0,0 +1,15 @@ +package touch.baton.domain.technicaltag.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.technicaltag.SupporterTechnicalTag; + +public interface SupporterTechnicalTagRepository extends JpaRepository { + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("DELETE FROM SupporterTechnicalTag st WHERE st.supporter = :supporter") + int deleteBySupporter(@Param("supporter") final Supporter supporter); +} diff --git a/backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/TechnicalTagRepository.java b/backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/TechnicalTagRepository.java index 478c6ff1d..681c35827 100644 --- a/backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/TechnicalTagRepository.java +++ b/backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/TechnicalTagRepository.java @@ -1,7 +1,12 @@ package touch.baton.domain.technicaltag.repository; import org.springframework.data.jpa.repository.JpaRepository; +import touch.baton.domain.common.vo.TagName; import touch.baton.domain.technicaltag.TechnicalTag; +import java.util.Optional; + public interface TechnicalTagRepository extends JpaRepository { + + Optional findByTagName(final TagName tagName); } diff --git a/backend/baton/src/main/resources/application.yml b/backend/baton/src/main/resources/application.yml index d93c8f75a..a124e2ba3 100644 --- a/backend/baton/src/main/resources/application.yml +++ b/backend/baton/src/main/resources/application.yml @@ -20,7 +20,7 @@ oauth: scope: ${OAUTH_GITHUB_SCOPE} cors: - allowed-origin: http://localhost:8080 + allowed-origin: http://localhost:3000 jwt: token: diff --git a/backend/baton/src/test/java/touch/baton/assure/common/AssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/common/AssuredSupport.java index 7e0efa533..87cc92c58 100644 --- a/backend/baton/src/test/java/touch/baton/assure/common/AssuredSupport.java +++ b/backend/baton/src/test/java/touch/baton/assure/common/AssuredSupport.java @@ -54,6 +54,19 @@ public static ExtractableResponse get(final String uri, final String a .extract(); } + + public static ExtractableResponse patch(final String uri, final String accessToken, final Object params) { + return RestAssured + .given().log().ifValidationFails() + .auth().preemptive().oauth2(accessToken) + .contentType(APPLICATION_JSON_VALUE) + .body(params) + .when().log().ifValidationFails() + .patch(uri) + .then().log().ifError() + .extract(); + } + public static ExtractableResponse delete(final String uri, final String pathParamName, final Long id) { return RestAssured .given().log().ifValidationFails() diff --git a/backend/baton/src/test/java/touch/baton/assure/supporter/SupporterProfileAssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/supporter/SupporterProfileAssuredSupport.java index 034a2898a..c488e8f59 100644 --- a/backend/baton/src/test/java/touch/baton/assure/supporter/SupporterProfileAssuredSupport.java +++ b/backend/baton/src/test/java/touch/baton/assure/supporter/SupporterProfileAssuredSupport.java @@ -2,12 +2,17 @@ import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import org.springframework.http.HttpStatus; import touch.baton.assure.common.AssuredSupport; +import touch.baton.domain.common.exception.ClientErrorCode; +import touch.baton.domain.common.response.ErrorResponse; import touch.baton.domain.supporter.Supporter; import touch.baton.domain.supporter.controller.response.SupporterResponse; +import touch.baton.domain.supporter.service.dto.SupporterUpdateRequest; import static org.assertj.core.api.SoftAssertions.assertSoftly; +@SuppressWarnings("NonAsciiCharacters") public class SupporterProfileAssuredSupport { private SupporterProfileAssuredSupport() { @@ -25,11 +30,23 @@ public static class SupporterClientRequestBuilder { private ExtractableResponse response; + private String accessToken; + + public SupporterClientRequestBuilder 로그인_한다(final String 토큰) { + accessToken = 토큰; + return this; + } + public SupporterClientRequestBuilder 서포터_프로필을_서포터_식별자값으로_조회한다(final Long 서포터_식별자값) { response = AssuredSupport.get("/api/v1/profile/supporter/{supporterId}", "supporterId", 서포터_식별자값); return this; } + public SupporterClientRequestBuilder 서포터_본인_프로필을_수정한다(final SupporterUpdateRequest request) { + response = AssuredSupport.patch("/api/v1/profile/supporter/me", accessToken, request); + return this; + } + public SupporterServerResponseBuilder 서버_응답() { return new SupporterServerResponseBuilder(response); } @@ -57,5 +74,22 @@ public SupporterServerResponseBuilder(final ExtractableResponse respon } ); } + + public void 서포터_본인_프로필_수정_성공을_검증한다(final HttpStatus HTTP_STATUS) { + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(HTTP_STATUS.value()); + softly.assertThat(response.header("Location")).isNotNull(); + }); + + } + + public void 서포터_본인_프로필_수정_실패를_검증한다(final ClientErrorCode clientErrorCode) { + final ErrorResponse actual = this.response.as(ErrorResponse.class); + + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(clientErrorCode.getHttpStatus().value()); + softly.assertThat(actual.errorCode()).isEqualTo(clientErrorCode.getErrorCode()); + }); + } } } diff --git a/backend/baton/src/test/java/touch/baton/assure/supporter/SupporterProfileAssuredUpdateTest.java b/backend/baton/src/test/java/touch/baton/assure/supporter/SupporterProfileAssuredUpdateTest.java new file mode 100644 index 000000000..c4a183395 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/supporter/SupporterProfileAssuredUpdateTest.java @@ -0,0 +1,94 @@ +package touch.baton.assure.supporter; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import touch.baton.config.AssuredTestConfig; +import touch.baton.domain.member.Member; +import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.supporter.service.dto.SupporterUpdateRequest; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.SupporterFixture; + +import java.util.List; + +import static org.springframework.http.HttpStatus.NO_CONTENT; +import static touch.baton.domain.common.exception.ClientErrorCode.*; + +@SuppressWarnings("NonAsciiCharacters") +public class SupporterProfileAssuredUpdateTest extends AssuredTestConfig { + + private String 디투_액세스_토큰; + + @BeforeEach + void setUp() { + final String 디투_소셜_id = "ditooSocialId"; + final Member 사용자_디투 = memberRepository.save(MemberFixture.createWithSocialId(디투_소셜_id)); + final Supporter 서포터_디투 = supporterRepository.save(SupporterFixture.create(사용자_디투)); + 디투_액세스_토큰 = login(디투_소셜_id); + } + + @Test + void 서포터_정보를_수정한다() { + final SupporterUpdateRequest supporterUpdateRequest = new SupporterUpdateRequest("디투랜드", "우아한테크코스", "안녕하세요.", List.of("java", "spring")); + + SupporterProfileAssuredSupport + .클라이언트_요청() + .로그인_한다(디투_액세스_토큰) + .서포터_본인_프로필을_수정한다(supporterUpdateRequest) + + .서버_응답() + .서포터_본인_프로필_수정_성공을_검증한다(NO_CONTENT); + } + + @Test + void 서포터_정보_수정_시에_이름이_없으면_예외가_발생한다() { + final SupporterUpdateRequest supporterUpdateRequest = new SupporterUpdateRequest(null, "우아한테크코스", "안녕하세요.", List.of("java", "spring")); + + SupporterProfileAssuredSupport + .클라이언트_요청() + .로그인_한다(디투_액세스_토큰) + .서포터_본인_프로필을_수정한다(supporterUpdateRequest) + + .서버_응답() + .서포터_본인_프로필_수정_실패를_검증한다(NAME_IS_NULL); + } + + @Test + void 서포터_정보_수정_시에_소속이_없으면_예외가_발생한다() { + final SupporterUpdateRequest supporterUpdateRequest = new SupporterUpdateRequest("디투랜드", null, "안녕하세요.", List.of("java", "spring")); + + SupporterProfileAssuredSupport + .클라이언트_요청() + .로그인_한다(디투_액세스_토큰) + .서포터_본인_프로필을_수정한다(supporterUpdateRequest) + + .서버_응답() + .서포터_본인_프로필_수정_실패를_검증한다(COMPANY_IS_NULL); + } + + @Test + void 서포터_정보_수정_시에_소개글이_없어도_된다() { + final SupporterUpdateRequest supporterUpdateRequest = new SupporterUpdateRequest("디투랜드", "배달의민족", null, List.of("java", "spring")); + + SupporterProfileAssuredSupport + .클라이언트_요청() + .로그인_한다(디투_액세스_토큰) + .서포터_본인_프로필을_수정한다(supporterUpdateRequest) + + .서버_응답() + .서포터_본인_프로필_수정_성공을_검증한다(NO_CONTENT); + } + + @Test + void 서포터_정보_수정_시에_기술_태그가_없으면_예외가_발생한다() { + final SupporterUpdateRequest supporterUpdateRequest = new SupporterUpdateRequest("디투랜드", "배달의민족", "배달왕이 될거에요.", null); + + SupporterProfileAssuredSupport + .클라이언트_요청() + .로그인_한다(디투_액세스_토큰) + .서포터_본인_프로필을_수정한다(supporterUpdateRequest) + + .서버_응답() + .서포터_본인_프로필_수정_실패를_검증한다(SUPPORTER_TECHNICAL_TAGS_ARE_NULL); + } +} diff --git a/backend/baton/src/test/java/touch/baton/config/ServiceTestConfig.java b/backend/baton/src/test/java/touch/baton/config/ServiceTestConfig.java index ea6b8a346..7cde80cde 100644 --- a/backend/baton/src/test/java/touch/baton/config/ServiceTestConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/ServiceTestConfig.java @@ -8,6 +8,7 @@ import touch.baton.domain.supporter.repository.SupporterRepository; import touch.baton.domain.tag.repository.RunnerPostTagRepository; import touch.baton.domain.tag.repository.TagRepository; +import touch.baton.domain.technicaltag.repository.SupporterTechnicalTagRepository; import touch.baton.domain.technicaltag.repository.TechnicalTagRepository; public abstract class ServiceTestConfig extends RepositoryTestConfig { @@ -18,6 +19,9 @@ public abstract class ServiceTestConfig extends RepositoryTestConfig { @Autowired protected RunnerRepository runnerRepository; + @Autowired + protected SupporterRepository supporterRepository; + @Autowired protected RunnerPostRepository runnerPostRepository; @@ -27,12 +31,12 @@ public abstract class ServiceTestConfig extends RepositoryTestConfig { @Autowired protected TagRepository tagRepository; - @Autowired - protected SupporterRepository supporterRepository; - @Autowired protected SupporterFeedbackRepository supporterFeedbackRepository; @Autowired protected TechnicalTagRepository technicalTagRepository; + + @Autowired + protected SupporterTechnicalTagRepository supporterTechnicalTagRepository; } diff --git a/backend/baton/src/test/java/touch/baton/document/profile/supporter/update/SupporterProfileUpdateApiTest.java b/backend/baton/src/test/java/touch/baton/document/profile/supporter/update/SupporterProfileUpdateApiTest.java new file mode 100644 index 000000000..ebe4a3c4a --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/document/profile/supporter/update/SupporterProfileUpdateApiTest.java @@ -0,0 +1,96 @@ +package touch.baton.document.profile.supporter.update; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import touch.baton.config.RestdocsConfig; +import touch.baton.domain.member.Member; +import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.supporter.controller.SupporterProfileController; +import touch.baton.domain.supporter.service.SupporterService; +import touch.baton.domain.supporter.service.dto.SupporterUpdateRequest; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.SupporterFixture; + +import java.util.List; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.http.HttpHeaders.*; +import static org.springframework.restdocs.headers.HeaderDocumentation.*; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; +import static org.springframework.restdocs.payload.JsonFieldType.ARRAY; +import static org.springframework.restdocs.payload.JsonFieldType.STRING; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +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; +import static touch.baton.fixture.vo.CompanyFixture.company; +import static touch.baton.fixture.vo.GithubUrlFixture.githubUrl; +import static touch.baton.fixture.vo.ImageUrlFixture.imageUrl; +import static touch.baton.fixture.vo.MemberNameFixture.memberName; +import static touch.baton.fixture.vo.OauthIdFixture.oauthId; +import static touch.baton.fixture.vo.SocialIdFixture.socialId; + +@WebMvcTest(SupporterProfileController.class) +public class SupporterProfileUpdateApiTest extends RestdocsConfig { + + @MockBean + private SupporterService supporterService; + + @BeforeEach + void setUp() { + final SupporterProfileController supporterProfileController = new SupporterProfileController(supporterService); + restdocsSetUp(supporterProfileController); + } + + @DisplayName("서포터 프로필 수정 API") + @Test + void updateSupporterProfile() throws Exception { + // given + final SupporterUpdateRequest request = new SupporterUpdateRequest("디투랜드", "우아한테크코스", "안녕하세요. 디투입니다.", List.of("java", "python")); + final String requestBody = objectMapper.writeValueAsString(request); + final String socialId = "ditooSocialId"; + final Member member = MemberFixture.create( + memberName("디투"), + socialId(socialId), + oauthId("abcd"), + githubUrl("naver.com"), + company("우아한테크코스"), + imageUrl("profile.jpg") + ); + final Supporter supporter = SupporterFixture.create(member); + final String token = getAccessTokenBySocialId(socialId); + + // when + when(oauthSupporterRepository.joinByMemberSocialId(any())) + .thenReturn(Optional.ofNullable(supporter)); + + // then + mockMvc.perform(patch("/api/v1/profile/supporter/me") + .header(AUTHORIZATION, "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .content(requestBody)) + .andExpect(status().isNoContent()) + .andExpect(header().string("Location", "/api/v1/profile/supporter/me")) + .andDo(restDocs.document( + requestHeaders( + headerWithName(AUTHORIZATION).description("Bearer JWT"), + headerWithName(CONTENT_TYPE).description("application/json") + ), + requestFields( + fieldWithPath("name").type(STRING).description("변경할 이름"), + fieldWithPath("company").type(STRING).description("변경할 소속"), + fieldWithPath("introduction").type(STRING).description("변경할 소개글"), + fieldWithPath("technicalTags.[]").type(ARRAY).description("변경할 기술 태그 목록") + ), + responseHeaders(headerWithName(LOCATION).description("redirect uri")) + )) + .andDo(print()); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/member/MemberTest.java b/backend/baton/src/test/java/touch/baton/domain/member/MemberTest.java index b45b8a8e2..9d2fb4bfb 100644 --- a/backend/baton/src/test/java/touch/baton/domain/member/MemberTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/member/MemberTest.java @@ -1,5 +1,6 @@ package touch.baton.domain.member; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -10,9 +11,9 @@ import touch.baton.domain.member.vo.MemberName; import touch.baton.domain.member.vo.OauthId; import touch.baton.domain.member.vo.SocialId; +import touch.baton.fixture.domain.MemberFixture; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.*; class MemberTest { @@ -124,4 +125,56 @@ void fail_if_imageUrl_is_null() { .hasMessage("Member 의 imageUrl 은 null 일 수 없습니다."); } } + + @DisplayName("수정 테스트") + @Nested + class Update { + + private Member member; + + @BeforeEach + void setUp() { + member = MemberFixture.createDitoo(); + } + + @DisplayName("이름 수정에 성공한다.") + @Test + void name_success() { + // given + final MemberName updatedName = new MemberName("디투랜드"); + + // when + member.updateMemberName(updatedName); + + // then + assertThat(member.getMemberName()).isEqualTo(updatedName); + } + + @DisplayName("이름이 null 이면 이름 수정에 실패한다.") + @Test + void name_fail_if_null() { + assertThatThrownBy(() -> member.updateMemberName(null)) + .isInstanceOf(MemberDomainException.class); + } + + @DisplayName("소속 수정에 성공한다.") + @Test + void company_success() { + // given + final Company updatedCompany = new Company("넥슨"); + + // when + member.updateCompany(updatedCompany); + + // then + assertThat(member.getCompany()).isEqualTo(updatedCompany); + } + + @DisplayName("소속이 null 이면 소속 수정에 실패한다.") + @Test + void company_fail_if_null() { + assertThatThrownBy(() -> member.updateCompany(null)) + .isInstanceOf(MemberDomainException.class); + } + } } diff --git a/backend/baton/src/test/java/touch/baton/domain/runner/RunnerTest.java b/backend/baton/src/test/java/touch/baton/domain/runner/RunnerTest.java index 662f1a182..9fc0f7cb8 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runner/RunnerTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runner/RunnerTest.java @@ -57,15 +57,15 @@ void fail_if_member_is_null() { .hasMessage("Runner 의 member 는 null 일 수 없습니다."); } - @DisplayName("runnerTechnicalTags 가 null 이 들어갈 경우 예외가 발생한다.") + @DisplayName("runnerTechnicalTags 가 null 이 들어갈 경우 예외가 발생하지 않는다.") @Test void fail_if_runnerTechnicalTags_is_null() { - assertThatThrownBy(() -> Runner.builder() + assertThatCode(() -> Runner.builder() .introduction(new Introduction("안녕하세요. 헤에디주입니다.")) .member(member) .runnerTechnicalTags(null) .build() - ).isInstanceOf(RunnerDomainException.class); + ).doesNotThrowAnyException(); } } } diff --git a/backend/baton/src/test/java/touch/baton/domain/supporter/SupporterTest.java b/backend/baton/src/test/java/touch/baton/domain/supporter/SupporterTest.java index cd65f86a9..46bca1f90 100644 --- a/backend/baton/src/test/java/touch/baton/domain/supporter/SupporterTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/supporter/SupporterTest.java @@ -1,9 +1,10 @@ package touch.baton.domain.supporter; +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 touch.baton.domain.common.vo.TagName; +import touch.baton.domain.common.vo.Introduction; import touch.baton.domain.member.Member; import touch.baton.domain.member.vo.Company; import touch.baton.domain.member.vo.GithubUrl; @@ -16,15 +17,15 @@ import touch.baton.domain.technicaltag.SupporterTechnicalTag; import touch.baton.domain.technicaltag.SupporterTechnicalTags; import touch.baton.domain.technicaltag.TechnicalTag; +import touch.baton.fixture.domain.SupporterFixture; import touch.baton.fixture.domain.SupporterTechnicalTagFixture; +import touch.baton.fixture.domain.TechnicalTagFixture; import java.util.ArrayList; import java.util.List; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.SoftAssertions.assertSoftly; class SupporterTest { @@ -64,7 +65,7 @@ void fail_if_member_is_null() { .hasMessage("Supporter 의 member 는 null 일 수 없습니다."); } - @DisplayName("technicalTags 가 null 이 들어갈 경우 예외가 발생한다.") + @DisplayName("supporterTechnicalTags 가 null 이 들어갈 경우 예외가 발생한다.") @Test void fail_if_supporterTechnicalTags_is_null() { assertThatThrownBy(() -> Supporter.builder() @@ -84,33 +85,71 @@ void read_supporterTechnicalTags() { final Supporter supporter = Supporter.builder() .reviewCount(new ReviewCount(10)) .member(member) - .supporterTechnicalTags(new SupporterTechnicalTags( - new ArrayList<>())) + .supporterTechnicalTags(new SupporterTechnicalTags(new ArrayList<>())) .build(); - final String expectedFirstTag = "java"; - final SupporterTechnicalTag javaTechnicalTag = SupporterTechnicalTagFixture.create(supporter, - TechnicalTag.builder() - .tagName(new TagName(expectedFirstTag)) - .build()); - - final String expectedSecondTag = "spring"; - final SupporterTechnicalTag springTechnicalTag = SupporterTechnicalTagFixture.create(supporter, - TechnicalTag.builder() - .tagName(new TagName(expectedSecondTag)) - .build()); - - final List expected = List.of(javaTechnicalTag, springTechnicalTag); - supporter.addAllSupporterTechnicalTags(expected); + final TechnicalTag technicalTag = TechnicalTagFixture.createJava(); + final SupporterTechnicalTag supporterTechnicalTag = SupporterTechnicalTagFixture.create(supporter, technicalTag); + supporter.addAllSupporterTechnicalTags(List.of(supporterTechnicalTag)); // when - final List actualTags = supporter.getSupporterTechnicalTags().getSupporterTechnicalTags(); + final List actual = supporter.getSupporterTechnicalTags().getSupporterTechnicalTags(); // then - assertAll( - () -> assertEquals(actualTags.size(), 2), - () -> assertEquals(expectedFirstTag, actualTags.get(0).getTechnicalTag().getTagName().getValue()), - () -> assertEquals(expectedSecondTag, actualTags.get(1).getTechnicalTag().getTagName().getValue()) - ); + assertSoftly(softly -> { + softly.assertThat(actual).hasSize(1); + softly.assertThat(actual.get(0)).isEqualTo(supporterTechnicalTag); + }); + } + + @DisplayName("수정 테스트") + @Nested + class Update { + + private Supporter supporter; + + @BeforeEach + void setUp() { + supporter = SupporterFixture.create(member); + } + + @DisplayName("이름 수정에 성공한다.") + @Test + void name_success() { + // given + final MemberName UpdatedName = new MemberName("디투"); + + // when + supporter.updateMemberName(UpdatedName); + + // then + assertThat(supporter.getMember().getMemberName()).isEqualTo(UpdatedName); + } + + @DisplayName("소속 수정에 성공한다.") + @Test + void company_success() { + // given + final Company updatedCompany = new Company("넥슨"); + + // when + supporter.updateCompany(updatedCompany); + + // then + assertThat(supporter.getMember().getCompany()).isEqualTo(updatedCompany); + } + + @DisplayName("소개글 수정에 성공한다.") + @Test + void introduction_success() { + // given + final Introduction updatedIntroduction = new Introduction("디투"); + + // when + supporter.updateIntroduction(updatedIntroduction); + + // then + assertThat(supporter.getIntroduction()).isEqualTo(updatedIntroduction); + } } } diff --git a/backend/baton/src/test/java/touch/baton/domain/supporter/service/SupporterServiceTest.java b/backend/baton/src/test/java/touch/baton/domain/supporter/service/SupporterServiceTest.java index a5c9285c5..0c22dbed7 100644 --- a/backend/baton/src/test/java/touch/baton/domain/supporter/service/SupporterServiceTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/supporter/service/SupporterServiceTest.java @@ -6,10 +6,14 @@ import touch.baton.config.ServiceTestConfig; import touch.baton.domain.member.Member; import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.supporter.service.dto.SupporterUpdateRequest; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.SupporterFixture; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; @@ -19,10 +23,10 @@ class SupporterServiceTest extends ServiceTestConfig { @BeforeEach void setUp() { - supporterService = new SupporterService(supporterRepository); + supporterService = new SupporterService(supporterRepository, technicalTagRepository, supporterTechnicalTagRepository); } - @DisplayName("Supporter 식별자 값으로 Member 패치 조인하여 Supporte 를 조회한다.") + @DisplayName("Supporter 식별자 값으로 Member 패치 조인하여 Supporter 를 조회한다.") @Test void readBySupporterId() { // given @@ -41,4 +45,17 @@ void readBySupporterId() { () -> assertThat(foundSupporter.getMember()).isEqualTo(savedMember) ); } + + @DisplayName("Supporter 정보를 수정한다.") + @Test + void updateSupporter() { + // given + final Member savedMember = memberRepository.save(MemberFixture.createDitoo()); + final Supporter savedSupporter = supporterRepository.save(SupporterFixture.create(savedMember)); + final SupporterUpdateRequest request = new SupporterUpdateRequest("디투랜드", "두나무", "소개글입니다.", List.of("golang", "rust")); + + // when & then + assertThatCode(() -> supporterService.updateSupporter(savedSupporter, request)) + .doesNotThrowAnyException(); + } } diff --git a/backend/baton/src/test/java/touch/baton/domain/technicaltag/SupporterTechnicalTagsTest.java b/backend/baton/src/test/java/touch/baton/domain/technicaltag/SupporterTechnicalTagsTest.java new file mode 100644 index 000000000..cedbf2dc4 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/technicaltag/SupporterTechnicalTagsTest.java @@ -0,0 +1,42 @@ +package touch.baton.domain.technicaltag; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.domain.member.Member; +import touch.baton.domain.supporter.Supporter; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.SupporterFixture; +import touch.baton.fixture.domain.SupporterTechnicalTagFixture; +import touch.baton.fixture.domain.TechnicalTagFixture; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class SupporterTechnicalTagsTest { + + private Supporter supporter; + private SupporterTechnicalTag supporterTechnicalTag; + + @BeforeEach + void setUp() { + final Member member = MemberFixture.createDitoo(); + supporter = SupporterFixture.create(member); + final TechnicalTag technicalTag = TechnicalTagFixture.createJava(); + supporterTechnicalTag = SupporterTechnicalTagFixture.create(supporter, technicalTag); + } + + @DisplayName("다중 덧셈 테스트") + @Test + void addAll() { + // given + final SupporterTechnicalTags supporterTechnicalTags = new SupporterTechnicalTags(); + + // when + supporterTechnicalTags.addAll(List.of(supporterTechnicalTag)); + + // then + assertThat(supporterTechnicalTags.getSupporterTechnicalTags()).containsExactly(supporterTechnicalTag); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/SupporterTechnicalTagRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/SupporterTechnicalTagRepositoryTest.java new file mode 100644 index 000000000..d76612dbb --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/SupporterTechnicalTagRepositoryTest.java @@ -0,0 +1,57 @@ +package touch.baton.domain.technicaltag.repository; + +import jakarta.persistence.EntityManager; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import touch.baton.config.RepositoryTestConfig; +import touch.baton.domain.member.Member; +import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.technicaltag.SupporterTechnicalTag; +import touch.baton.domain.technicaltag.TechnicalTag; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.SupporterFixture; +import touch.baton.fixture.domain.SupporterTechnicalTagFixture; +import touch.baton.fixture.domain.TechnicalTagFixture; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class SupporterTechnicalTagRepositoryTest extends RepositoryTestConfig { + + @Autowired + private SupporterTechnicalTagRepository supporterTechnicalTagRepository; + + @Autowired + private EntityManager entityManager; + + @DisplayName("batch 로 supporter 의 모든 SupporterTechnicalTag 를 삭제한다.") + @Test + void deleteBySupporter() { + // given + final Member member = MemberFixture.createDitoo(); + entityManager.persist(member); + final Supporter supporter = SupporterFixture.create(member); + entityManager.persist(supporter); + final TechnicalTag technicalTag1 = TechnicalTagFixture.createReact(); + final TechnicalTag technicalTag2 = TechnicalTagFixture.createSpring(); + final TechnicalTag technicalTag3 = TechnicalTagFixture.createJava(); + entityManager.persist(technicalTag1); + entityManager.persist(technicalTag2); + entityManager.persist(technicalTag3); + final SupporterTechnicalTag supporterTechnicalTag1 = SupporterTechnicalTagFixture.create(supporter, technicalTag1); + final SupporterTechnicalTag supporterTechnicalTag2 = SupporterTechnicalTagFixture.create(supporter, technicalTag2); + final SupporterTechnicalTag supporterTechnicalTag3 = SupporterTechnicalTagFixture.create(supporter, technicalTag3); + final List savedSupporterTechnicalTags = List.of(supporterTechnicalTag1, supporterTechnicalTag2, supporterTechnicalTag3); + supporterTechnicalTagRepository.saveAll(savedSupporterTechnicalTags); + entityManager.flush(); + + // when + final int expected = savedSupporterTechnicalTags.size(); + final int actual = supporterTechnicalTagRepository.deleteBySupporter(supporter); + + // then + assertThat(expected).isEqualTo(actual); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/TechnicalTagRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/TechnicalTagRepositoryTest.java new file mode 100644 index 000000000..a9a9960cc --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/TechnicalTagRepositoryTest.java @@ -0,0 +1,52 @@ +package touch.baton.domain.technicaltag.repository; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import touch.baton.config.RepositoryTestConfig; +import touch.baton.domain.common.vo.TagName; +import touch.baton.domain.technicaltag.TechnicalTag; +import touch.baton.fixture.domain.TechnicalTagFixture; +import touch.baton.fixture.vo.TagNameFixture; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +class TechnicalTagRepositoryTest extends RepositoryTestConfig { + + @Autowired + private TechnicalTagRepository technicalTagRepository; + + @DisplayName("TagName 으로 TechnicalTag 를 검색할 때 TechnicalTag 가 존재하면 검색된다.") + @Test + void findByName_ifPresent() { + // given + final TagName tagName = TagNameFixture.tagName("java"); + final TechnicalTag expected = technicalTagRepository.save(TechnicalTagFixture.create(tagName)); + technicalTagRepository.flush(); + + // when + final Optional actual = technicalTagRepository.findByTagName(tagName); + + // then + assertSoftly(softly -> { + softly.assertThat(actual).isPresent(); + softly.assertThat(expected).isEqualTo(actual.get()); + }); + } + + @DisplayName("TagName 으로 TechnicalTag 를 검색할 때 TechnicalTag 가 존재하지 않으면 검색되지 않는다.") + @Test + void findByName_ifNotPresent() { + // given + final TagName tagName = new TagName("java"); + + // when + final Optional actual = technicalTagRepository.findByTagName(tagName); + + // then + assertThat(actual).isNotPresent(); + } +}