Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

기술 스택 및 회원 닉네임 검색 api 리팩토링 및 테스트 #52

Merged
merged 12 commits into from
Feb 19, 2024
Merged
2 changes: 1 addition & 1 deletion sidepeek_backend_secret
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package sixgaezzang.sidepeek.skill.controller;

import static sixgaezzang.sidepeek.skill.domain.Skill.MAX_SKILL_NAME_LENGTH;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.validation.constraints.Size;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import sixgaezzang.sidepeek.skill.dto.SkillSearchResponse;
import sixgaezzang.sidepeek.skill.dto.response.SkillSearchResponse;
import sixgaezzang.sidepeek.skill.serivce.SkillService;

@RestController
Expand All @@ -18,9 +23,12 @@ public class SkillController {
private final SkillService skillService;

@GetMapping
@Operation(summary = "기술 스택 검색")
@ApiResponse(responseCode = "200", description = "기술 스택 검색 성공")
@Parameter(name = "keyword", description = "검색어", example = "spring")
public ResponseEntity<SkillSearchResponse> searchByName(
@RequestParam(required = false)
@Size(max = 50, message = "최대 50자의 키워드로 검색할 수 있습니다.")
@Size(max = MAX_SKILL_NAME_LENGTH, message = "최대 " + MAX_SKILL_NAME_LENGTH + "자의 키워드로 검색할 수 있습니다.")
String keyword
) {
return ResponseEntity.ok()
Expand Down
18 changes: 17 additions & 1 deletion src/main/java/sixgaezzang/sidepeek/skill/domain/Skill.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package sixgaezzang.sidepeek.skill.domain;

import static sixgaezzang.sidepeek.common.util.ValidationUtils.validateMaxLength;
import static sixgaezzang.sidepeek.common.util.ValidationUtils.validateURI;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

Expand All @@ -16,14 +20,26 @@
@Getter
public class Skill {

public static final int MAX_SKILL_NAME_LENGTH = 50;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "name", nullable = false, length = 50)
@Column(name = "name", nullable = false, length = MAX_SKILL_NAME_LENGTH)
private String name;

@Column(name = "icon_image_url", nullable = false, columnDefinition = "TEXT")
private String iconImageUrl;

@Builder
public Skill(String name, String iconImageUrl) {
validateMaxLength(name, MAX_SKILL_NAME_LENGTH,
"최대 " + MAX_SKILL_NAME_LENGTH + "자의 이름으로 생성할 수 있습니다.");
Sehee-Lee-01 marked this conversation as resolved.
Show resolved Hide resolved
validateURI(iconImageUrl, "이미지 url 형식이 올바르지 않습니다.");

this.name = name;
this.iconImageUrl = iconImageUrl;
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package sixgaezzang.sidepeek.skill.dto;
package sixgaezzang.sidepeek.skill.dto.response;

import lombok.Builder;
import sixgaezzang.sidepeek.skill.domain.Skill;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package sixgaezzang.sidepeek.skill.dto;
package sixgaezzang.sidepeek.skill.dto.response;

import java.util.List;
import sixgaezzang.sidepeek.skill.domain.Skill;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
package sixgaezzang.sidepeek.skill.serivce;

import static sixgaezzang.sidepeek.common.ValidationUtils.validateMaxLength;
import static sixgaezzang.sidepeek.common.util.ValidationUtils.validateMaxLength;
import static sixgaezzang.sidepeek.skill.domain.Skill.MAX_SKILL_NAME_LENGTH;

import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import sixgaezzang.sidepeek.skill.dto.SkillSearchResponse;
import sixgaezzang.sidepeek.skill.dto.response.SkillSearchResponse;
import sixgaezzang.sidepeek.skill.repository.SkillRepository;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class SkillService {

public static final int KEYWORD_MAX_LENGTH = 50;

private final SkillRepository skillRepository;

public SkillSearchResponse searchByName(String keyword) {
if (Objects.isNull(keyword) || keyword.isBlank()) {
return SkillSearchResponse.from(skillRepository.findAll());
}

validateMaxLength(keyword, KEYWORD_MAX_LENGTH,
"최대 " + KEYWORD_MAX_LENGTH + "자의 키워드로 검색할 수 있습니다.");
validateMaxLength(keyword, MAX_SKILL_NAME_LENGTH,
"최대 " + MAX_SKILL_NAME_LENGTH + "자의 키워드로 검색할 수 있습니다.");

return SkillSearchResponse.from(skillRepository.findAllByNameContaining(keyword));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package sixgaezzang.sidepeek.users.controller;

import static sixgaezzang.sidepeek.users.domain.User.MAX_NICKNAME_LENGTH;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
Expand All @@ -18,8 +20,8 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import sixgaezzang.sidepeek.users.domain.Provider;
import sixgaezzang.sidepeek.users.dto.SignUpRequest;
import sixgaezzang.sidepeek.users.dto.UserSearchResponse;
import sixgaezzang.sidepeek.users.dto.request.SignUpRequest;
import sixgaezzang.sidepeek.users.dto.response.UserSearchResponse;
import sixgaezzang.sidepeek.users.service.UserService;

@RestController
Expand Down Expand Up @@ -49,9 +51,12 @@ public ResponseEntity<Void> signUp(@RequestBody @Valid SignUpRequest request) {
}

@GetMapping
@Operation(summary = "회원 검색")
@ApiResponse(responseCode = "200", description = "회원 검색 성공")
@Parameter(name = "keyword", description = "검색어", example = "sixgaezzang6")
public ResponseEntity<UserSearchResponse> searchByNickname(
@RequestParam(required = false)
@Size(max = 20, message = "최대 20자의 키워드로 검색할 수 있습니다.")
@Size(max = MAX_NICKNAME_LENGTH, message = "최대 " + MAX_NICKNAME_LENGTH + "자의 키워드로 검색할 수 있습니다.")
String keyword
) {
return ResponseEntity.ok()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User extends BaseTimeEntity {

private static final int MAX_NICKNAME_LENGTH = 20;
public static final int MAX_NICKNAME_LENGTH = 20;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package sixgaezzang.sidepeek.users.dto;
package sixgaezzang.sidepeek.users.dto.request;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package sixgaezzang.sidepeek.users.dto;
package sixgaezzang.sidepeek.users.dto.response;

import java.util.List;
import sixgaezzang.sidepeek.users.domain.User;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package sixgaezzang.sidepeek.users.dto;
package sixgaezzang.sidepeek.users.dto.response;

import lombok.Builder;
import sixgaezzang.sidepeek.users.domain.User;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sixgaezzang.sidepeek.users.service;

import static sixgaezzang.sidepeek.common.ValidationUtils.validateMaxLength;
import static sixgaezzang.sidepeek.common.util.ValidationUtils.validateMaxLength;
import static sixgaezzang.sidepeek.users.domain.User.MAX_NICKNAME_LENGTH;

import jakarta.persistence.EntityExistsException;
import java.util.Objects;
Expand All @@ -11,17 +12,15 @@
import sixgaezzang.sidepeek.users.domain.Password;
import sixgaezzang.sidepeek.users.domain.Provider;
import sixgaezzang.sidepeek.users.domain.User;
import sixgaezzang.sidepeek.users.dto.SignUpRequest;
import sixgaezzang.sidepeek.users.dto.UserSearchResponse;
import sixgaezzang.sidepeek.users.dto.request.SignUpRequest;
import sixgaezzang.sidepeek.users.dto.response.UserSearchResponse;
import sixgaezzang.sidepeek.users.repository.UserRepository;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class UserService {

private static final int KEYWORD_MAX_LENGTH = 20;

private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;

Expand Down Expand Up @@ -51,8 +50,8 @@ public UserSearchResponse searchByNickname(String keyword) {
return UserSearchResponse.from(userRepository.findAll());
}

validateMaxLength(keyword, KEYWORD_MAX_LENGTH,
"최대 " + KEYWORD_MAX_LENGTH + "자의 키워드로 검색할 수 있습니다.");
validateMaxLength(keyword, MAX_NICKNAME_LENGTH,
"최대 " + MAX_NICKNAME_LENGTH + "자의 키워드로 검색할 수 있습니다.");

return UserSearchResponse.from(userRepository.findAllByNicknameContaining(keyword));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package sixgaezzang.sidepeek.skill.serivce;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
import static sixgaezzang.sidepeek.skill.domain.Skill.MAX_SKILL_NAME_LENGTH;

import java.util.List;
import org.assertj.core.api.ThrowableAssert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import sixgaezzang.sidepeek.skill.domain.Skill;
import sixgaezzang.sidepeek.skill.dto.response.SkillResponse;
import sixgaezzang.sidepeek.skill.repository.SkillRepository;

@SpringBootTest
@Transactional
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class SkillServiceTest {

@Autowired
SkillService skillService;
@Autowired
SkillRepository skillRepository;

@Nested
class 기술_스택_검색_테스트 {

static final int SKILL_COUNT = 5;
static final String[] skills = {"spring", "spring boot", "spring web", "react", "react query"};

@BeforeEach
void setUp() {
String tempImageUrl = "https://google.com/image.png";
for (int i = 0; i < SKILL_COUNT; i++) {
createSkill(skills[i], tempImageUrl);
}
}

@ParameterizedTest(name = "[{index}] {0}으로 검색할 때 " + SKILL_COUNT + "개의 모든 기술 스택이 나온다.")
@NullAndEmptySource
void 검색어_없이_전체_기술_스택_검색에_성공한다(String keyword) {
// given, when
List<SkillResponse> skills = skillService.searchByName(keyword)
.skills();

// then
assertThat(skills.size()).isEqualTo(SKILL_COUNT);
}

@ParameterizedTest(name = "[{index}] {0}으로 검색할 때 {1}개의 기술 스택이 나온다.")
@CsvSource(value = {"spring:3", "react:2"}, delimiter = ':')
void 검색어로_기술_스택_검색에_성공한다(String keyword, int count) {
// given, when
List<SkillResponse> skills = skillService.searchByName(keyword)
.skills();

// then
assertThat(skills.size()).isEqualTo(count);
}

@Test
void 검색어_최대_글자_수가_넘어_기술_스택_검색에_실패한다() {
// given
int keywordLength = MAX_SKILL_NAME_LENGTH + 1;
String keyword = "a".repeat(keywordLength);

// when
ThrowableAssert.ThrowingCallable search = () -> skillService.searchByName(keyword);

// then
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(search)
.withMessage("최대 " + MAX_SKILL_NAME_LENGTH + "자의 키워드로 검색할 수 있습니다.");
}

}

private Skill createSkill(String name, String iconImageUrl) {
Skill skill = Skill.builder()
.name(name)
.iconImageUrl(iconImageUrl)
.build();
return skillRepository.save(skill);
}

}
Loading
Loading