Skip to content
Merged
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,7 @@ out/
### dev ###
application-dev.yml

# local/dev only
.dev/
docker-compose.yml
init-user.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;

@Getter
Expand All @@ -15,8 +16,6 @@
@Schema(name = "ExecutiveRequestDto", description = "임원진 요청 DTO")
public class ExecutiveRequestDto {

private Long id;

@NotBlank(message = "임원진 이름은 필수 입력 사항입니다.")
@Size(max = 50, message = "임원진 이름은 최대 50자입니다.")
@Schema(description = "임원진 이름", example = "김다솜")
Expand Down Expand Up @@ -45,7 +44,7 @@ public ExecutiveEntity toEntity() {
.role(this.role)
.githubUsername(this.github_username)
.team(this.team)
.sortOrder(this.sortOrder)
.sortOrder(sortOrder != null ? sortOrder : 9999)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ public void update(ExecutiveUpdateRequestDto dto) {
if (dto.getSortOrder() != null) this.sortOrder = dto.getSortOrder();
}

@PrePersist
public void prePersist() {
if (sortOrder == null) sortOrder = 9999;
}
@PreUpdate
public void preUpdate() {
if (sortOrder == null) sortOrder = 9999;
}

// 엔티티 -> DTO 변환 책임
public ExecutiveResponseDto toResponseDto() {
return ExecutiveResponseDto.builder()
Expand All @@ -67,6 +76,7 @@ public ExecutiveResponseDto toResponseDto() {
.role(this.role)
.github_username(this.githubUsername)
.team(this.team)
.sortOrder(this.sortOrder)
.build();
}

Expand All @@ -79,6 +89,7 @@ public ExecutiveListResponseDto toListResponseDto() {
.role(this.role)
.github_username(this.githubUsername)
.team(this.team)
.sortOrder(this.sortOrder)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ public List<ExecutiveListResponseDto> getAllExecutives() {

// 임원진 멤버 생성
public ExecutiveCreationResponseDto createExecutive(ExecutiveRequestDto requestDto) {
return new ExecutiveCreationResponseDto(executiveRepository.save(requestDto.toEntity()).getId());
ExecutiveEntity saved = executiveRepository.save(requestDto.toEntity());
return new ExecutiveCreationResponseDto(saved.getId());
}

// 임원진 멤버 삭제
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.URL;

@Getter
@Setter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requestDto는 객체 생성 후 변하면 안되어서 @Setter 제거 후 내부 필드 전부 final로 수정해주시면 감사하겠습니다. 그리고 추가로 @AllArgsConstructor 추가해서 빌더 패턴 유지해주시면 될 것 같습니다. 이 내용은 불변성 관련 내용이라서 DTO 불변성 이나 불변 객체 DTO 이런식으로 검색하시면 자료가 많을겁니다. (umc 워크북에서도 하셨던 내용이라 보시면 바로 이해하실거에요)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 리뷰 감사합니다! 수정 완료했습니다!

Expand Down Expand Up @@ -41,4 +42,14 @@ public class SomParticipantRequestDto {
@Email(message = "올바른 이메일 형식이 아닙니다.")
@Schema(description = "이메일 주소", example = "[email protected]", required = true)
private String email; // 이메일

@NotBlank(message = "GitHub 주소는 필수 입력 값입니다.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Github 주소는 필수입력값이 아니라 있으면 넣는걸로 바꿔주세요

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수정 완료했습니다!

@URL(protocol = "https", host = "github.com", message = "GitHub URL이 올바르지 않습니다.")
@Schema(description = "GitHub 주소", example = "https://github.com/username", required = true)
private String githubLink; // 깃허브 주소

@NotBlank(message = "포트폴리오 주소는 필수 입력 값입니다.")
@URL(protocol = "https", message = "포트폴리오 URL이 올바르지 않습니다.")
@Schema(description = "포트폴리오 주소", example = "https://portfolio.com/username", required = true)
private String portfolioLink; // 포트폴리오 주소
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,10 @@ public class SomParticipantResponseDto {

@Schema(description = "이메일 주소", example = "[email protected]", required = true)
private String email; // 이메일
}

@Schema(description = "깃허브 주소", example = "https://github.com/username", required = true)
private String githubLink; // 깃허브 주소

@Schema(description = "포트폴리오 주소", example = "https://portfolio.com/username", required = true)
private String portfolioLink; // 포트폴리오 주소
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SomParticipantService.toResponseDto() 로직을 여기 하단에 추가해주심 됩니다. 엔티티 캡슐화나 책임 분리? 라는 내용으로 찾아보심 될 것 같아요

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 리뷰 감사합니다! 수정 완료했습니다!

Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,20 @@ public class SomParticipant extends BaseEntity {
@Column(nullable = false)
private String email; // 이메일

@Column(nullable = false)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nullable 제약조건 없애주세요

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수정 완료했습니다!

private String githubLink; // 깃허브 링크

@Column(nullable = false)
private String portfolioLink; // 포트폴리오 링크

public void update(SomParticipantRequestDto requestDto) {
this.participantName = requestDto.getParticipantName();
this.studentId = requestDto.getStudentId();
this.department = requestDto.getDepartment();
this.grade = requestDto.getGrade();
this.contact = requestDto.getContact();
this.email = requestDto.getEmail();
this.githubLink = requestDto.getGithubLink();
this.portfolioLink = requestDto.getPortfolioLink();
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/**
 * Entity → Response DTO 변환 메서드
 */
private SomParticipantResponseDto toResponseDto(SomParticipant participant) {
    return SomParticipantResponseDto.builder()
            .id(participant.getId())
            .participantName(participant.getParticipantName())
            .studentId(participant.getStudentId())
            .department(participant.getDepartment())
            .grade(participant.getGrade())
            .contact(participant.getContact())
            .email(participant.getEmail())
            .githubLink(participant.getGithubLink())
            .portfolioLink(participant.getPortfolioLink())
            .build();

이 부분을 서비스단에서 처리하는게 아니라 엔티티 단계에서 처리하도록 수정해주세요!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 리뷰 감사합니다! 수정 완료했습니다!

Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public SomParticipantResponseDto createParticipant(SomParticipantRequestDto requ
.grade(requestDto.getGrade())
.contact(requestDto.getContact())
.email(requestDto.getEmail())
.githubLink(requestDto.getGithubLink())
.portfolioLink(requestDto.getPortfolioLink())
.build();

SomParticipant saved = somParticipantRepository.save(participant);
Expand Down Expand Up @@ -83,6 +85,8 @@ private SomParticipantResponseDto toResponseDto(SomParticipant participant) {
.grade(participant.getGrade())
.contact(participant.getContact())
.email(participant.getEmail())
.githubLink(participant.getGithubLink())
.portfolioLink(participant.getPortfolioLink())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ void createExecutive_success() {
// given
Long id = 1L;
ExecutiveRequestDto dto = new ExecutiveRequestDto(
id,
"김다솜",
"회장",
"동아리 운영 총괄",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package dmu.dasom.api.domain.somkathon;

import dmu.dasom.api.domain.common.exception.CustomException;
import dmu.dasom.api.domain.common.exception.ErrorCode;
import dmu.dasom.api.domain.somkathon.dto.SomParticipantRequestDto;
import dmu.dasom.api.domain.somkathon.dto.SomParticipantResponseDto;
import dmu.dasom.api.domain.somkathon.entity.SomParticipant;
import dmu.dasom.api.domain.somkathon.repository.SomParticipantRepository;
import dmu.dasom.api.domain.somkathon.service.SomParticipantService;
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.junit.jupiter.MockitoExtension;

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

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class SomParticipantServiceTest {

@Mock
private SomParticipantRepository repository;

@InjectMocks
private SomParticipantService service;

// =======================
// Create Tests
// =======================
@Test
@DisplayName("참가자 생성 - 성공")
void createParticipant_success() {
SomParticipantRequestDto request = new SomParticipantRequestDto();
request.setParticipantName("홍길동");
request.setStudentId("20250001");
request.setDepartment("컴퓨터공학과");
request.setGrade("3");
request.setContact("010-1234-5678");
request.setEmail("[email protected]");
request.setGithubLink("https://github.com/username");
request.setPortfolioLink("https://drive.google.com/file");

when(repository.findByStudentId("20250001")).thenReturn(Optional.empty());
when(repository.save(any(SomParticipant.class))).thenAnswer(invocation -> invocation.getArgument(0));

SomParticipantResponseDto response = service.createParticipant(request);

assertNotNull(response);
assertEquals("홍길동", response.getParticipantName());
assertEquals("20250001", response.getStudentId());
assertEquals("컴퓨터공학과", response.getDepartment());
assertEquals("3", response.getGrade());
assertEquals("010-1234-5678", response.getContact());
assertEquals("[email protected]", response.getEmail());
assertEquals("https://github.com/username", response.getGithubLink());
assertEquals("https://drive.google.com/file", response.getPortfolioLink());

verify(repository, times(1)).findByStudentId("20250001");
verify(repository, times(1)).save(any(SomParticipant.class));
}

@Test
@DisplayName("참가자 생성 - 실패 (학생ID 중복)")
void createParticipant_fail_duplicateStudentId() {
SomParticipantRequestDto request = new SomParticipantRequestDto();
request.setStudentId("20250001");

when(repository.findByStudentId("20250001")).thenReturn(Optional.of(mock(SomParticipant.class)));

CustomException exception = assertThrows(CustomException.class, () -> service.createParticipant(request));
assertEquals(ErrorCode.DUPLICATED_STUDENT_NO, exception.getErrorCode());

verify(repository, times(1)).findByStudentId("20250001");
verify(repository, never()).save(any());
}

// =======================
// Read Tests
// =======================
@Test
@DisplayName("모든 참가자 조회")
void getAllParticipants_success() {
SomParticipant p1 = SomParticipant.builder()
.participantName("홍길동")
.studentId("20250001")
.department("컴퓨터공학과")
.grade("3")
.contact("010-1234-5678")
.email("[email protected]")
.githubLink("https://github.com/hong")
.portfolioLink("https://drive.google.com/file")
.build();
SomParticipant p2 = SomParticipant.builder()
.participantName("김철수")
.studentId("20250002")
.department("소프트웨어공학과")
.grade("2")
.contact("010-9876-5432")
.email("[email protected]")
.githubLink("https://github.com/kim")
.portfolioLink("https://notion.site")
.build();

when(repository.findAll()).thenReturn(List.of(p1, p2));

List<SomParticipantResponseDto> list = service.getAllParticipants();

assertEquals(2, list.size());

assertEquals("홍길동", list.get(0).getParticipantName());
assertEquals("김철수", list.get(1).getParticipantName());

assertEquals("https://github.com/hong", list.get(0).getGithubLink());
assertEquals("https://notion.site", list.get(1).getPortfolioLink());

verify(repository, times(1)).findAll();
}

@Test
@DisplayName("특정 참가자 조회 - 성공")
void getParticipant_success() {
SomParticipant participant = SomParticipant.builder()
.participantName("홍길동")
.studentId("20250001")
.department("컴퓨터공학과")
.grade("3")
.contact("010-1234-5678")
.email("[email protected]")
.githubLink("https://github.com/username")
.portfolioLink("https://drive.google.com/file")
.build();

when(repository.findById(1L)).thenReturn(Optional.of(participant));

SomParticipantResponseDto response = service.getParticipant(1L);

assertEquals("홍길동", response.getParticipantName());
assertEquals("20250001", response.getStudentId());

verify(repository, times(1)).findById(1L);
}

@Test
@DisplayName("특정 참가자 조회 - 실패 (없음)")
void getParticipant_fail_notFound() {
when(repository.findById(1L)).thenReturn(Optional.empty());

CustomException exception = assertThrows(CustomException.class, () -> service.getParticipant(1L));
assertEquals(ErrorCode.NOT_FOUND_PARTICIPANT, exception.getErrorCode());

verify(repository, times(1)).findById(1L);
}

// =======================
// Delete Tests
// =======================
@Test
@DisplayName("참가자 삭제 - 성공")
void deleteParticipant_success() {
SomParticipant participant = SomParticipant.builder().build();
when(repository.findById(1L)).thenReturn(Optional.of(participant));

assertDoesNotThrow(() -> service.deleteParticipant(1L));
verify(repository, times(1)).deleteById(1L);
}

@Test
@DisplayName("참가자 삭제 - 실패 (없음)")
void deleteParticipant_fail_notFound() {
when(repository.findById(1L)).thenReturn(Optional.empty());

CustomException exception = assertThrows(CustomException.class, () -> service.deleteParticipant(1L));
assertEquals(ErrorCode.NOT_FOUND_PARTICIPANT, exception.getErrorCode());

verify(repository, never()).deleteById(any());
}
}
Loading