diff --git a/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java b/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java index 8910414..2d1cef6 100644 --- a/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java +++ b/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java @@ -36,7 +36,8 @@ public enum ErrorCode { SLOT_NOT_ACTIVE(400, "C027", "해당 슬롯이 비활성화 되었습니다."), FILE_ENCODE_FAIL(400, "C028", "파일 인코딩에 실패하였습니다."), RECRUITMENT_NOT_ACTIVE(400, "C029", "모집 기간이 아닙니다."), - NOT_FOUND_PARTICIPANT(400, "C030", "참가자를 찾을 수 없습니다.") + NOT_FOUND_PARTICIPANT(400, "C030", "참가자를 찾을 수 없습니다."), + EXECUTIVE_NOT_FOUND(400, "C031", "임원진을 찾을 수 없습니다."), ; private final int status; diff --git a/src/main/java/dmu/dasom/api/domain/executive/controller/ExecutiveController.java b/src/main/java/dmu/dasom/api/domain/executive/controller/ExecutiveController.java new file mode 100644 index 0000000..ec62b72 --- /dev/null +++ b/src/main/java/dmu/dasom/api/domain/executive/controller/ExecutiveController.java @@ -0,0 +1,60 @@ +package dmu.dasom.api.domain.executive.controller; + +import dmu.dasom.api.domain.executive.dto.*; +import dmu.dasom.api.domain.executive.service.ExecutiveServiceImpl; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "EXECUTIVE API", description = "임원진 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/executives") +public class ExecutiveController { + + private final ExecutiveServiceImpl executiveService; + + @Operation(summary = "임원진 조회") + @GetMapping("/{id}") + public ResponseEntity getExecutiveById(@PathVariable @Min(1) Long id) { + return ResponseEntity.ok(executiveService.getExecutiveById(id)); + } + + @Operation(summary = "임원진 전체 조회") + @GetMapping + public ResponseEntity> getAllExecutives() { + return ResponseEntity.ok(executiveService.getAllExecutives()); + } + + @Operation(summary = "임원진 생성") + @PreAuthorize("hasRole('ADMIN')") + @PostMapping + public ResponseEntity createExecutive(@Valid @RequestBody ExecutiveRequestDto requestDto) { + return ResponseEntity.status(201).body(executiveService.createExecutive(requestDto)); + } + + @Operation(summary = "임원진 삭제") + @PreAuthorize("hasRole('ADMIN')") + @DeleteMapping("/{id}") + // Void 사용 이유? + // DELETE 요청 같이 성공/실패만 확인하면 되는 경우 사용 + public ResponseEntity deleteExecutive(@PathVariable @Min(1) Long id) { + executiveService.deleteExective(id); + return ResponseEntity.ok().build(); + } + + @Operation(summary = "임원진 수정") + @PreAuthorize("hasRole('ADMIN')") + @PutMapping("/{id}") + public ResponseEntity updateExecutive(@PathVariable @Min(1) Long id, + @Valid @RequestBody ExecutiveUpdateRequestDto requestDto) { + return ResponseEntity.ok(executiveService.updateExecutive(id, requestDto)); + } +} diff --git a/src/main/java/dmu/dasom/api/domain/executive/dto/ExecutiveCreationResponseDto.java b/src/main/java/dmu/dasom/api/domain/executive/dto/ExecutiveCreationResponseDto.java new file mode 100644 index 0000000..72affb2 --- /dev/null +++ b/src/main/java/dmu/dasom/api/domain/executive/dto/ExecutiveCreationResponseDto.java @@ -0,0 +1,19 @@ +package dmu.dasom.api.domain.executive.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Schema(name = "ExecutiveCreationResponseDto", description = "회장단 생성 응답 DTO") +public class ExecutiveCreationResponseDto { + + @NotNull + @Schema(description = "회장단 ID", example = "1") + private Long id; + +} diff --git a/src/main/java/dmu/dasom/api/domain/executive/dto/ExecutiveListResponseDto.java b/src/main/java/dmu/dasom/api/domain/executive/dto/ExecutiveListResponseDto.java new file mode 100644 index 0000000..7ae845d --- /dev/null +++ b/src/main/java/dmu/dasom/api/domain/executive/dto/ExecutiveListResponseDto.java @@ -0,0 +1,28 @@ +package dmu.dasom.api.domain.executive.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(name = "ExecutiveListResponseDto", description = "임원진 목록 응답 Dto") +public class ExecutiveListResponseDto { + + @Schema(description = "임원진 ID", example = "1") + private Long id; + + @Schema(description = "임원진 이름", example = "김다솜") + private String name; + + @Schema(description = "임원진 직책", example = "회장") + private String position; + + @Schema(description = "임원진 깃허브 주소", example = "https://github.com/dasom") + private String githubUrl; + +} diff --git a/src/main/java/dmu/dasom/api/domain/executive/dto/ExecutiveRequestDto.java b/src/main/java/dmu/dasom/api/domain/executive/dto/ExecutiveRequestDto.java new file mode 100644 index 0000000..2ba32e8 --- /dev/null +++ b/src/main/java/dmu/dasom/api/domain/executive/dto/ExecutiveRequestDto.java @@ -0,0 +1,37 @@ +package dmu.dasom.api.domain.executive.dto; + +import dmu.dasom.api.domain.executive.entity.ExecutiveEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Schema(name = "ExecutiveRequestDto", description = "임원진 요청 DTO") +public class ExecutiveRequestDto { + + private Long id; + + @NotBlank(message = "임원진 이름은 필수 입력 사항입니다.") + @Size(max = 50, message = "임원진 이름은 최대 50자입니다.") + @Schema(description = "임원진 이름", example = "김다솜") + private String name; + + @NotBlank(message = "임원진 역할은 필수 입력 사항입니다.") + @Schema(description = "임원진 역할", example = "회장") + private String position; + + private String githubUrl; + + public ExecutiveEntity toEntity() { + return ExecutiveEntity.builder() + .name(this.name) + .position(this.position) + .githubUrl(this.githubUrl) + .build(); + } +} diff --git a/src/main/java/dmu/dasom/api/domain/executive/dto/ExecutiveResponseDto.java b/src/main/java/dmu/dasom/api/domain/executive/dto/ExecutiveResponseDto.java new file mode 100644 index 0000000..a9007ff --- /dev/null +++ b/src/main/java/dmu/dasom/api/domain/executive/dto/ExecutiveResponseDto.java @@ -0,0 +1,27 @@ +package dmu.dasom.api.domain.executive.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(name = "ExecutiveResponseDto", description = "임원진 응답 DTO") +public class ExecutiveResponseDto { + + @Schema(description = "임원진 ID", example = "1") + private Long id; + + @Schema(description = "임원진 이름", example = "김다솜") + private String name; + + @Schema(description = "임원진 직책", example = "회장") + private String position; + + @Schema(description = "임원진 깃허브", example = "https://github.com/dasom") + private String githubUrl; +} diff --git a/src/main/java/dmu/dasom/api/domain/executive/dto/ExecutiveUpdateRequestDto.java b/src/main/java/dmu/dasom/api/domain/executive/dto/ExecutiveUpdateRequestDto.java new file mode 100644 index 0000000..e66f2e9 --- /dev/null +++ b/src/main/java/dmu/dasom/api/domain/executive/dto/ExecutiveUpdateRequestDto.java @@ -0,0 +1,24 @@ +package dmu.dasom.api.domain.executive.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Schema(name = "ExecutiveUpdateRequestDto", description = "임원진 멤버 수정 요청 DTO") +public class ExecutiveUpdateRequestDto { + + @Size(max = 50, message = "임원진 이름은 최대 50자입니다.") + @Schema(description = "수정할 임원진 이름", example = "김다솜", nullable = true) + private String name; + + @Schema(description = "수정할 임원진 직책", example = "회장", nullable = true) + private String position; + + @Schema(description = "수정할 임원진 깃허브 주소", example = "https://github.com/dasom", nullable = true) + private String githubUrl; +} diff --git a/src/main/java/dmu/dasom/api/domain/executive/entity/ExecutiveEntity.java b/src/main/java/dmu/dasom/api/domain/executive/entity/ExecutiveEntity.java new file mode 100644 index 0000000..849406f --- /dev/null +++ b/src/main/java/dmu/dasom/api/domain/executive/entity/ExecutiveEntity.java @@ -0,0 +1,61 @@ +package dmu.dasom.api.domain.executive.entity; + +import dmu.dasom.api.domain.common.BaseEntity; // BaseEntity 상속 받음 +import dmu.dasom.api.domain.executive.dto.ExecutiveListResponseDto; +import dmu.dasom.api.domain.executive.dto.ExecutiveResponseDto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.persistence.*; // JPA 어노테이션 패키지 ( DB 매핑 관련 ) +import lombok.*; // 보일러플레이트 코드 자동 생성 라이브러리 + +@Getter +@Entity +@Table(name = "executive") +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "조직도 엔티티") +public class ExecutiveEntity extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // 이름 + @Column(nullable = false, length = 50) + private String name; + + // 직책 + @Column(nullable=false, length = 50) + private String position; + + // 깃허브 주소 + @Column(nullable=false, length = 255) + private String githubUrl; + + // 엔티티 업데이트 메소드 + public void update(String name, String position, String githubUrl) { + this.name = name; + this.position = position; + this.githubUrl = githubUrl; + } + + // 엔티티 -> DTO 변환 책임 + public ExecutiveResponseDto toResponseDto() { + return ExecutiveResponseDto.builder() + .id(this.id) + .name(this.name) + .position(this.position) + .githubUrl(this.githubUrl) + .build(); + } + + // 임원진 전체 목록 조회 + public ExecutiveListResponseDto toListResponseDto() { + return ExecutiveListResponseDto.builder() + .id(this.id) + .name(this.name) + .position(this.position) + .githubUrl(this.githubUrl) + .build(); + } +} diff --git a/src/main/java/dmu/dasom/api/domain/executive/enums/Role.java b/src/main/java/dmu/dasom/api/domain/executive/enums/Role.java new file mode 100644 index 0000000..e736734 --- /dev/null +++ b/src/main/java/dmu/dasom/api/domain/executive/enums/Role.java @@ -0,0 +1,24 @@ +package dmu.dasom.api.domain.executive.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +// enum + 문자열 매핑 +@AllArgsConstructor +@Getter +public enum Role { + + ROLE_PRESIDENT("PRESIDENT"), // 회장 + ROLE_VICE_PRESIDENT("VICE_PRESIDENT"), // 부회장 + ROLE_TECHNICAL_MANAGER("TECHNICAL_MANAGER"), // 기술팀장 + ROLE_ACADEMIC_MANAGER("ACADEMIC_MANAGER"), // 학술팀장 + ROLE_ACADEMIC_SENIOR("ACADEMIC_SENIOR"), // 학술차장 + ROLE_PUBLIC_RELATIONS_MANAGER("PUBLIC_RELATIONS_MANAGER"), // 홍보팀장 + ROLE_CLERK("CLERK"), // 서기 + ROLE_MANAGER("MANAGER"), // 총무 + ROLE_SUB_MANAGER("SUB_MANAGER"), // 부총무 + ; + + private String name; + +} diff --git a/src/main/java/dmu/dasom/api/domain/executive/repository/ExecutiveRepository.java b/src/main/java/dmu/dasom/api/domain/executive/repository/ExecutiveRepository.java new file mode 100644 index 0000000..461f3fa --- /dev/null +++ b/src/main/java/dmu/dasom/api/domain/executive/repository/ExecutiveRepository.java @@ -0,0 +1,12 @@ +package dmu.dasom.api.domain.executive.repository; + +import dmu.dasom.api.domain.executive.entity.ExecutiveEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ExecutiveRepository extends JpaRepository { + + // 회장단 레포지토리 + +} diff --git a/src/main/java/dmu/dasom/api/domain/executive/service/ExecutiveService.java b/src/main/java/dmu/dasom/api/domain/executive/service/ExecutiveService.java new file mode 100644 index 0000000..8c3d483 --- /dev/null +++ b/src/main/java/dmu/dasom/api/domain/executive/service/ExecutiveService.java @@ -0,0 +1,18 @@ +package dmu.dasom.api.domain.executive.service; + +import dmu.dasom.api.domain.executive.dto.*; + +import java.util.List; + +public interface ExecutiveService { + + ExecutiveResponseDto getExecutiveById(Long id); + + List getAllExecutives(); + + ExecutiveCreationResponseDto createExecutive(ExecutiveRequestDto requestDto); + + void deleteExective(Long id); + + ExecutiveResponseDto updateExecutive(Long id, ExecutiveUpdateRequestDto requestDto); +} diff --git a/src/main/java/dmu/dasom/api/domain/executive/service/ExecutiveServiceImpl.java b/src/main/java/dmu/dasom/api/domain/executive/service/ExecutiveServiceImpl.java new file mode 100644 index 0000000..7d039b9 --- /dev/null +++ b/src/main/java/dmu/dasom/api/domain/executive/service/ExecutiveServiceImpl.java @@ -0,0 +1,69 @@ +package dmu.dasom.api.domain.executive.service; + +import dmu.dasom.api.domain.common.exception.CustomException; +import dmu.dasom.api.domain.common.exception.ErrorCode; +import dmu.dasom.api.domain.executive.dto.*; +import dmu.dasom.api.domain.executive.entity.ExecutiveEntity; +import dmu.dasom.api.domain.executive.repository.ExecutiveRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) // 기본값 : 읽기 전용 +public class ExecutiveServiceImpl implements ExecutiveService { + + private final ExecutiveRepository executiveRepository; + + // 임원진 멤버 조회 + // 이름, 직책, 깃허브 주소 검색 + public ExecutiveResponseDto getExecutiveById(Long id) { + ExecutiveEntity executive = executiveRepository.findById(id) + .orElseThrow(() -> new CustomException(ErrorCode.EXECUTIVE_NOT_FOUND)); + + return executive.toResponseDto(); + } + + // 임원진 전체 조회 + // 이름, 직책, 깃허브 주소 출력 + public List getAllExecutives() { + List executives = executiveRepository.findAll(); + + List executiveIds = executives.stream() + .map(ExecutiveEntity::getId) + .toList(); + + return executives.stream() + .map(executiveEntity -> executiveEntity.toListResponseDto()) + .toList(); + } + + // 임원진 멤버 생성 + public ExecutiveCreationResponseDto createExecutive(ExecutiveRequestDto requestDto) { + return new ExecutiveCreationResponseDto(executiveRepository.save(requestDto.toEntity()).getId()); + } + + // 임원진 멤버 삭제 + @Transactional + public void deleteExective(Long id) { + ExecutiveEntity executive = executiveRepository.findById(id) + .orElseThrow(() -> new CustomException(ErrorCode.EXECUTIVE_NOT_FOUND)); + + executiveRepository.delete(executive); + } + + // 임원진 멤버 수정 + @Transactional + public ExecutiveResponseDto updateExecutive(Long id, ExecutiveUpdateRequestDto requestDto) { + ExecutiveEntity executive = executiveRepository.findById(id) + .orElseThrow(() -> new CustomException(ErrorCode.EXECUTIVE_NOT_FOUND)); + + executive.update(requestDto.getName(), requestDto.getPosition(), requestDto.getGithubUrl()); + + return executive.toResponseDto(); + } +} diff --git a/src/main/java/dmu/dasom/api/global/auth/config/SecurityConfig.java b/src/main/java/dmu/dasom/api/global/auth/config/SecurityConfig.java index 3f3e2e6..cb7f8f3 100644 --- a/src/main/java/dmu/dasom/api/global/auth/config/SecurityConfig.java +++ b/src/main/java/dmu/dasom/api/global/auth/config/SecurityConfig.java @@ -12,6 +12,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; @@ -26,6 +27,7 @@ @Configuration @EnableWebSecurity +@EnableMethodSecurity(prePostEnabled = true) // 메소드 보안 활성화 (@PreAuthorize 사용) @RequiredArgsConstructor public class SecurityConfig { diff --git a/src/test/java/dmu/dasom/api/domain/executive/service/ExecutiveServiceTest.java b/src/test/java/dmu/dasom/api/domain/executive/service/ExecutiveServiceTest.java new file mode 100644 index 0000000..4b8a492 --- /dev/null +++ b/src/test/java/dmu/dasom/api/domain/executive/service/ExecutiveServiceTest.java @@ -0,0 +1,179 @@ +package dmu.dasom.api.domain.executive.service; + +import dmu.dasom.api.domain.common.exception.CustomException; +import dmu.dasom.api.domain.common.exception.ErrorCode; +import dmu.dasom.api.domain.executive.dto.ExecutiveCreationResponseDto; +import dmu.dasom.api.domain.executive.dto.ExecutiveRequestDto; +import dmu.dasom.api.domain.executive.dto.ExecutiveResponseDto; +import dmu.dasom.api.domain.executive.dto.ExecutiveUpdateRequestDto; +import dmu.dasom.api.domain.executive.entity.ExecutiveEntity; +import dmu.dasom.api.domain.executive.repository.ExecutiveRepository; +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 org.springframework.security.test.context.support.WithMockUser; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ExecutiveServiceTest { + + @Mock + private ExecutiveRepository executiveRepository; + + // 생성자 주입 + @InjectMocks + private ExecutiveServiceImpl executiveService; + + @Test + @DisplayName("임원진 멤버 조회 - 성공") + void getExecutiveById_success() { + // given + Long id = 1L; + ExecutiveEntity entity = ExecutiveEntity.builder() + .id(1L) + .name("김다솜") + .position("회장") + .githubUrl("https://github.com/dasom") + .build(); + + when(executiveRepository.findById(id)).thenReturn(Optional.of(entity)); + + // when + ExecutiveResponseDto responseDto = executiveService.getExecutiveById(id); + + // then + assertThat(responseDto.getId()).isEqualTo(id); + assertThat(responseDto.getName()).isEqualTo("김다솜"); + assertThat(responseDto.getPosition()).isEqualTo("회장"); + assertThat(responseDto.getGithubUrl()).isEqualTo("https://github.com/dasom"); + + // verify ( 호출 검증 ) + verify(executiveRepository, times(1)).findById(id); // 메소드를 정확히 한 번만 호출했는지? + } + + @Test + @DisplayName("임원진 멤버 조회 - 실패") + void getExecutiveById_fail() { + // given + Long id = 1L; + when(executiveRepository.findById(1L)).thenReturn(Optional.empty()); + + // when + CustomException exception = assertThrows(CustomException.class, () -> { + executiveService.getExecutiveById(1L); + }); + + // then + assertEquals(ErrorCode.EXECUTIVE_NOT_FOUND, exception.getErrorCode()); + } + + @Test + @DisplayName("임원진 멤버 생성 - 성공") + void createExecutive_success() { + // given + Long id = 1L; + ExecutiveRequestDto dto = new ExecutiveRequestDto( + id, "김다솜", "회장", "https://github.com/dasom" + ); + + ExecutiveEntity entity = ExecutiveEntity.builder() + .id(1L) + .name("김다솜") + .position("회장") + .githubUrl("https://github.com/dasom") + .build(); + + when(executiveRepository.save(any(ExecutiveEntity.class))).thenReturn(entity); + + // when + ExecutiveCreationResponseDto responseDto = executiveService.createExecutive(dto); + + // then + assertThat(responseDto.getId()).isEqualTo(id); + } + + @Test + @DisplayName("임원진 멤버 생성 - 실패_권한 없음") + @WithMockUser(roles = "MEMBER") + public void createExecutive_fail_authority() { + + } + + @Test + @DisplayName("임원진 멤버 삭제 - 성공") + void deleteExecutive_success() { + // given + Long id = 1L; + + ExecutiveEntity entity = ExecutiveEntity.builder() + .id(1L) + .name("김다솜") + .position("회장") + .githubUrl("https://github.com/dasom") + .build(); + + when(executiveRepository.findById(id)).thenReturn(Optional.of(entity)); + doNothing().when(executiveRepository).delete(entity); + + // when + executiveService.deleteExective(id); + + // then + verify(executiveRepository, times(1)).findById(id); + verify(executiveRepository, times(1)).delete(entity); + } + + @Test + @DisplayName("임원진 멤버 삭제 - 실패") + void deleteExecutive_fail() { + // given + Long id = 999L; + when(executiveRepository.findById(id)).thenReturn(Optional.empty()); + + // when + CustomException exception = assertThrows(CustomException.class, () -> {executiveService.deleteExective(id);}); + + // then + assertEquals(ErrorCode.EXECUTIVE_NOT_FOUND, exception.getErrorCode()); + } + + @Test + @DisplayName("임원진 멤버 수정 - 성공") + void updateExecutive_success() { + + //given + Long id = 1L; + + ExecutiveEntity entity = ExecutiveEntity.builder() + .id(1L) + .name("김다솜") + .position("회장") + .githubUrl("https://github.com/dasom") + .build(); + + ExecutiveUpdateRequestDto updateEntity = new ExecutiveUpdateRequestDto("김솜다", "부회장", "https://github.com/dasom"); + + when(executiveRepository.findById(id)).thenReturn(Optional.of(entity)); + + //when + ExecutiveResponseDto updateExecutive = executiveService.updateExecutive(id, updateEntity); + + //then + assertThat(updateExecutive.getName()).isEqualTo("김솜다"); + assertThat(updateExecutive.getPosition()).isEqualTo("부회장"); + assertThat(updateExecutive.getGithubUrl()).isEqualTo("https://github.com/dasom"); + + // verify ( 호출 검증 ) + verify(executiveRepository, times(1)).findById(id); // 메소드를 정확히 한 번만 호출했는지? + } +} \ No newline at end of file