From e36adef16a0fd2094836b0c8ed948dd5dc2a36a5 Mon Sep 17 00:00:00 2001 From: Desktop Date: Thu, 30 Apr 2026 11:26:16 +0900 Subject: [PATCH 1/5] feat(workplace): add isActive filter to workplace list API --- .../api/employer/WorkplaceController.java | 6 +- .../workplace/service/WorkplaceService.java | 10 +- .../api/employer/WorkplaceControllerTest.java | 130 ++++++++++++++++++ .../service/WorkplaceServiceTest.java | 24 +++- 4 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 src/test/java/com/example/paycheck/api/employer/WorkplaceControllerTest.java diff --git a/src/main/java/com/example/paycheck/api/employer/WorkplaceController.java b/src/main/java/com/example/paycheck/api/employer/WorkplaceController.java index 902f775a..15f0db4a 100644 --- a/src/main/java/com/example/paycheck/api/employer/WorkplaceController.java +++ b/src/main/java/com/example/paycheck/api/employer/WorkplaceController.java @@ -36,8 +36,10 @@ public ApiResponse createWorkplace( @Operation(summary = "내 사업장 목록 조회", description = "로그인한 고용주의 사업장 목록을 조회합니다.") @GetMapping public ApiResponse> getWorkplaces( - @AuthenticationPrincipal User user) { - return ApiResponse.success(workplaceService.getWorkplacesByUserId(user.getId())); + @AuthenticationPrincipal User user, + @Parameter(description = "활성화 여부 필터 (true: 활성, false: 비활성, 미전송: 전체)") + @RequestParam(required = false) Boolean isActive) { + return ApiResponse.success(workplaceService.getWorkplacesByUserId(user.getId(), isActive)); } @Operation(summary = "사업장 상세 조회", description = "특정 사업장의 상세 정보를 조회합니다.") diff --git a/src/main/java/com/example/paycheck/domain/workplace/service/WorkplaceService.java b/src/main/java/com/example/paycheck/domain/workplace/service/WorkplaceService.java index 6a8c9420..8bd13210 100644 --- a/src/main/java/com/example/paycheck/domain/workplace/service/WorkplaceService.java +++ b/src/main/java/com/example/paycheck/domain/workplace/service/WorkplaceService.java @@ -44,10 +44,16 @@ public WorkplaceDto.Response createWorkplace(Long userId, WorkplaceDto.CreateReq return WorkplaceDto.Response.from(saved); } - public List getWorkplacesByUserId(Long userId) { + public List getWorkplacesByUserId(Long userId, Boolean isActive) { Employer employer = employerService.getEmployerByUserId(userId); - List workplaces = workplaceRepository.findByEmployerIdAndIsActive(employer.getId(), true); + List workplaces; + if (isActive != null) { + workplaces = workplaceRepository.findByEmployerIdAndIsActive(employer.getId(), isActive); + } else { + // 하위 호환성을 위해 기본적으로 활성 사업장만 조회 + workplaces = workplaceRepository.findByEmployerIdAndIsActive(employer.getId(), true); + } return workplaces.stream() .map(workplace -> { diff --git a/src/test/java/com/example/paycheck/api/employer/WorkplaceControllerTest.java b/src/test/java/com/example/paycheck/api/employer/WorkplaceControllerTest.java new file mode 100644 index 00000000..f209ae6f --- /dev/null +++ b/src/test/java/com/example/paycheck/api/employer/WorkplaceControllerTest.java @@ -0,0 +1,130 @@ +package com.example.paycheck.api.employer; + +import com.example.paycheck.domain.user.entity.User; +import com.example.paycheck.domain.workplace.dto.WorkplaceDto; +import com.example.paycheck.domain.workplace.service.WorkplaceService; +import com.example.paycheck.global.security.JwtAuthenticationFilter; +import com.example.paycheck.global.security.JwtTokenProvider; +import com.example.paycheck.global.security.permission.CustomPermissionEvaluator; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(WorkplaceController.class) +@AutoConfigureMockMvc(addFilters = false) +@DisplayName("WorkplaceController 테스트") +class WorkplaceControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private WorkplaceService workplaceService; + + @MockitoBean + private CustomPermissionEvaluator permissionEvaluator; + + @MockitoBean + private JwtTokenProvider jwtTokenProvider; + + @MockitoBean + private JwtAuthenticationFilter jwtAuthenticationFilter; + + @Autowired + private ObjectMapper objectMapper; + + private User testUser; + private UsernamePasswordAuthenticationToken authentication; + + @BeforeEach + void setUp() { + testUser = User.builder() + .id(1L) + .name("테스트 고용주") + .build(); + authentication = new UsernamePasswordAuthenticationToken(testUser, null, List.of()); + + given(permissionEvaluator.isEmployer()).willReturn(true); + } + + @Nested + @DisplayName("GET /api/employer/workplaces") + class GetWorkplaces { + + @Test + @DisplayName("성공 - isActive 파라미터 없이 모든 사업장을 조회한다") + void getWorkplaces_NoParam_Success() throws Exception { + // given + List response = List.of( + WorkplaceDto.ListResponse.builder().id(1L).name("사업장1").isActive(true).build(), + WorkplaceDto.ListResponse.builder().id(2L).name("사업장2").isActive(false).build() + ); + given(workplaceService.getWorkplacesByUserId(eq(1L), eq(null))) + .willReturn(response); + + // when & then + mockMvc.perform(get("/api/employer/workplaces") + .principal(authentication)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data.length()").value(2)); + } + + @Test + @DisplayName("성공 - isActive=true 파라미터로 활성 사업장만 조회한다") + void getWorkplaces_ActiveTrue_Success() throws Exception { + // given + List response = List.of( + WorkplaceDto.ListResponse.builder().id(1L).name("사업장1").isActive(true).build() + ); + given(workplaceService.getWorkplacesByUserId(eq(1L), eq(true))) + .willReturn(response); + + // when & then + mockMvc.perform(get("/api/employer/workplaces") + .param("isActive", "true") + .principal(authentication)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data.length()").value(1)) + .andExpect(jsonPath("$.data[0].isActive").value(true)); + } + + @Test + @DisplayName("성공 - isActive=false 파라미터로 비활성 사업장만 조회한다") + void getWorkplaces_ActiveFalse_Success() throws Exception { + // given + List response = List.of( + WorkplaceDto.ListResponse.builder().id(2L).name("사업장2").isActive(false).build() + ); + given(workplaceService.getWorkplacesByUserId(eq(1L), eq(false))) + .willReturn(response); + + // when & then + mockMvc.perform(get("/api/employer/workplaces") + .param("isActive", "false") + .principal(authentication)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data.length()").value(1)) + .andExpect(jsonPath("$.data[0].isActive").value(false)); + } + } +} diff --git a/src/test/java/com/example/paycheck/domain/workplace/service/WorkplaceServiceTest.java b/src/test/java/com/example/paycheck/domain/workplace/service/WorkplaceServiceTest.java index b48cb290..29a9d532 100644 --- a/src/test/java/com/example/paycheck/domain/workplace/service/WorkplaceServiceTest.java +++ b/src/test/java/com/example/paycheck/domain/workplace/service/WorkplaceServiceTest.java @@ -140,8 +140,8 @@ void getWorkplaceById_NotFound() { } @Test - @DisplayName("사용자 ID로 사업장 목록 조회 성공") - void getWorkplacesByUserId_Success() { + @DisplayName("사용자 ID로 사업장 목록 조회 성공 - 활성 사업장만 조회") + void getWorkplacesByUserId_ActiveOnly_Success() { // given when(employerService.getEmployerByUserId(1L)).thenReturn(testEmployer); when(workplaceRepository.findByEmployerIdAndIsActive(1L, true)) @@ -149,7 +149,7 @@ void getWorkplacesByUserId_Success() { when(workerContractRepository.countByWorkplaceIdAndIsActive(1L, true)).thenReturn(5); // when - List result = workplaceService.getWorkplacesByUserId(1L); + List result = workplaceService.getWorkplacesByUserId(1L, true); // then assertThat(result).isNotEmpty(); @@ -157,6 +157,24 @@ void getWorkplacesByUserId_Success() { verify(workplaceRepository).findByEmployerIdAndIsActive(1L, true); } + @Test + @DisplayName("사용자 ID로 사업장 목록 조회 성공 - 전체 조회") + void getWorkplacesByUserId_All_Success() { + // given + when(employerService.getEmployerByUserId(1L)).thenReturn(testEmployer); + when(workplaceRepository.findByEmployerId(1L)) + .thenReturn(Collections.singletonList(testWorkplace)); + when(workerContractRepository.countByWorkplaceIdAndIsActive(1L, true)).thenReturn(5); + + // when + List result = workplaceService.getWorkplacesByUserId(1L, null); + + // then + assertThat(result).isNotEmpty(); + verify(employerService).getEmployerByUserId(1L); + verify(workplaceRepository).findByEmployerId(1L); + } + @Test @DisplayName("사업장 정보 업데이트 성공") void updateWorkplace_Success() { From f945dc99da67035c7ad13532554d8cf13ca9e74d Mon Sep 17 00:00:00 2001 From: Desktop Date: Sun, 3 May 2026 22:34:32 +0900 Subject: [PATCH 2/5] fix(workplace): align test with isActive=null behavior - Update test to expect active-only workplaces when isActive is null - Change mock and verify to use findByEmployerIdAndIsActive instead of findByEmployerId - Fixes code-test mismatch from feat/159 review --- .../domain/workplace/service/WorkplaceServiceTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/example/paycheck/domain/workplace/service/WorkplaceServiceTest.java b/src/test/java/com/example/paycheck/domain/workplace/service/WorkplaceServiceTest.java index 29a9d532..38ce8cc6 100644 --- a/src/test/java/com/example/paycheck/domain/workplace/service/WorkplaceServiceTest.java +++ b/src/test/java/com/example/paycheck/domain/workplace/service/WorkplaceServiceTest.java @@ -158,11 +158,11 @@ void getWorkplacesByUserId_ActiveOnly_Success() { } @Test - @DisplayName("사용자 ID로 사업장 목록 조회 성공 - 전체 조회") - void getWorkplacesByUserId_All_Success() { + @DisplayName("사용자 ID로 사업장 목록 조회 성공 - isActive=null이면 활성 사업장만 조회") + void getWorkplacesByUserId_ActiveOnly_Success() { // given when(employerService.getEmployerByUserId(1L)).thenReturn(testEmployer); - when(workplaceRepository.findByEmployerId(1L)) + when(workplaceRepository.findByEmployerIdAndIsActive(1L, true)) .thenReturn(Collections.singletonList(testWorkplace)); when(workerContractRepository.countByWorkplaceIdAndIsActive(1L, true)).thenReturn(5); @@ -172,7 +172,7 @@ void getWorkplacesByUserId_All_Success() { // then assertThat(result).isNotEmpty(); verify(employerService).getEmployerByUserId(1L); - verify(workplaceRepository).findByEmployerId(1L); + verify(workplaceRepository).findByEmployerIdAndIsActive(1L, true); } @Test From dfa898935778820c962be6e1812ac029bc949671 Mon Sep 17 00:00:00 2001 From: Desktop Date: Sun, 3 May 2026 23:56:47 +0900 Subject: [PATCH 3/5] fix(notice): add workplace selection to notice creation and fix test duplication --- .../example/paycheck/api/notice/NoticeController.java | 9 ++++----- .../example/paycheck/domain/notice/dto/NoticeDto.java | 3 +++ .../paycheck/domain/notice/service/NoticeService.java | 6 +++--- .../domain/workplace/service/WorkplaceServiceTest.java | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/example/paycheck/api/notice/NoticeController.java b/src/main/java/com/example/paycheck/api/notice/NoticeController.java index 99f16651..a04f71dd 100644 --- a/src/main/java/com/example/paycheck/api/notice/NoticeController.java +++ b/src/main/java/com/example/paycheck/api/notice/NoticeController.java @@ -25,14 +25,13 @@ public class NoticeController { private final NoticeService noticeService; - @Operation(summary = "공지사항 작성", description = "사업장에 새로운 공지사항을 작성합니다.") - @PreAuthorize("@permissionEvaluator.canAccessWorkplaceAsMember(#workplaceId)") - @PostMapping("/workplaces/{workplaceId}/notices") + @Operation(summary = "공지사항 작성", description = "지정한 사업장에 공지사항을 작성합니다.") + @PreAuthorize("@permissionEvaluator.canAccessWorkplaceAsMember(#request.workplaceId)") + @PostMapping("/notices") public ResponseEntity> createNotice( - @Parameter(description = "사업장 ID", required = true) @PathVariable Long workplaceId, @AuthenticationPrincipal User user, @Valid @RequestBody NoticeDto.CreateRequest request) { - NoticeDto.Response response = noticeService.createNotice(workplaceId, user, request); + NoticeDto.Response response = noticeService.createNotice(user, request); return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(response)); } diff --git a/src/main/java/com/example/paycheck/domain/notice/dto/NoticeDto.java b/src/main/java/com/example/paycheck/domain/notice/dto/NoticeDto.java index a332448b..e7e7e1e7 100644 --- a/src/main/java/com/example/paycheck/domain/notice/dto/NoticeDto.java +++ b/src/main/java/com/example/paycheck/domain/notice/dto/NoticeDto.java @@ -21,6 +21,9 @@ public class NoticeDto { @AllArgsConstructor @Schema(name = "NoticeCreateRequest") public static class CreateRequest { + @NotNull(message = "사업장 ID는 필수입니다.") + private Long workplaceId; + @NotNull(message = "카테고리는 필수입니다.") private NoticeCategory category; diff --git a/src/main/java/com/example/paycheck/domain/notice/service/NoticeService.java b/src/main/java/com/example/paycheck/domain/notice/service/NoticeService.java index 258b7a2a..5f70c657 100644 --- a/src/main/java/com/example/paycheck/domain/notice/service/NoticeService.java +++ b/src/main/java/com/example/paycheck/domain/notice/service/NoticeService.java @@ -43,12 +43,12 @@ public class NoticeService { private final ObjectMapper objectMapper; @Transactional - public NoticeDto.Response createNotice(Long workplaceId, User author, NoticeDto.CreateRequest request) { + public NoticeDto.Response createNotice(User author, NoticeDto.CreateRequest request) { if (!request.getExpiresAt().isAfter(LocalDateTime.now())) { throw new BadRequestException(ErrorCode.INVALID_INPUT_VALUE, "만료 일시는 현재 시간 이후여야 합니다."); } - Workplace workplace = workplaceRepository.findById(workplaceId) + Workplace workplace = workplaceRepository.findById(request.getWorkplaceId()) .orElseThrow(() -> new NotFoundException(ErrorCode.WORKPLACE_NOT_FOUND, "사업장을 찾을 수 없습니다.")); Notice notice = Notice.builder() @@ -65,7 +65,7 @@ public NoticeDto.Response createNotice(Long workplaceId, User author, NoticeDto. publishNoticeCreatedNotification(saved); } catch (Exception e) { log.error("공지사항 생성은 성공했지만 알림 발행에 실패했습니다. noticeId={}, workplaceId={}", - saved.getId(), workplaceId, e); + saved.getId(), workplace.getId(), e); } return NoticeDto.Response.from(saved); } diff --git a/src/test/java/com/example/paycheck/domain/workplace/service/WorkplaceServiceTest.java b/src/test/java/com/example/paycheck/domain/workplace/service/WorkplaceServiceTest.java index 38ce8cc6..feb38d3c 100644 --- a/src/test/java/com/example/paycheck/domain/workplace/service/WorkplaceServiceTest.java +++ b/src/test/java/com/example/paycheck/domain/workplace/service/WorkplaceServiceTest.java @@ -159,7 +159,7 @@ void getWorkplacesByUserId_ActiveOnly_Success() { @Test @DisplayName("사용자 ID로 사업장 목록 조회 성공 - isActive=null이면 활성 사업장만 조회") - void getWorkplacesByUserId_ActiveOnly_Success() { + void getWorkplacesByUserId_NullParam_Success() { // given when(employerService.getEmployerByUserId(1L)).thenReturn(testEmployer); when(workplaceRepository.findByEmployerIdAndIsActive(1L, true)) From e6ae5d39722dafd50f9d08b4f429e3bd8d8209e5 Mon Sep 17 00:00:00 2001 From: Desktop Date: Mon, 4 May 2026 00:07:23 +0900 Subject: [PATCH 4/5] fix(test): update NoticeServiceTest to match new createNotice signature --- .../domain/notice/service/NoticeServiceTest.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/example/paycheck/domain/notice/service/NoticeServiceTest.java b/src/test/java/com/example/paycheck/domain/notice/service/NoticeServiceTest.java index d42a3095..5735958a 100644 --- a/src/test/java/com/example/paycheck/domain/notice/service/NoticeServiceTest.java +++ b/src/test/java/com/example/paycheck/domain/notice/service/NoticeServiceTest.java @@ -131,6 +131,7 @@ void setUp() { void createNotice_Success() { // given NoticeDto.CreateRequest request = NoticeDto.CreateRequest.builder() + .workplaceId(1L) .category(NoticeCategory.URGENT) .title("긴급 공지") .content("위생사항 엄수") @@ -143,7 +144,7 @@ void createNotice_Success() { .thenReturn(List.of(activeContract)); // when - NoticeDto.Response result = noticeService.createNotice(1L, authorUser, request); + NoticeDto.Response result = noticeService.createNotice(authorUser, request); // then assertThat(result).isNotNull(); @@ -170,6 +171,7 @@ void createNotice_Success() { void createNotice_Fail_WorkplaceNotFound() { // given NoticeDto.CreateRequest request = NoticeDto.CreateRequest.builder() + .workplaceId(999L) .category(NoticeCategory.ETC) .title("공지") .content("내용") @@ -179,7 +181,7 @@ void createNotice_Fail_WorkplaceNotFound() { when(workplaceRepository.findById(999L)).thenReturn(Optional.empty()); // when & then - assertThatThrownBy(() -> noticeService.createNotice(999L, authorUser, request)) + assertThatThrownBy(() -> noticeService.createNotice(authorUser, request)) .isInstanceOf(NotFoundException.class) .hasMessageContaining("사업장을 찾을 수 없습니다"); @@ -191,6 +193,7 @@ void createNotice_Fail_WorkplaceNotFound() { void createNotice_Success_WhenNotificationPublishFails() { // given NoticeDto.CreateRequest request = NoticeDto.CreateRequest.builder() + .workplaceId(1L) .category(NoticeCategory.URGENT) .title("긴급 공지") .content("위생사항 엄수") @@ -205,7 +208,7 @@ void createNotice_Success_WhenNotificationPublishFails() { .when(eventPublisher).publishEvent(any(NotificationEvent.class)); // when - NoticeDto.Response result = noticeService.createNotice(1L, authorUser, request); + NoticeDto.Response result = noticeService.createNotice(authorUser, request); // then assertThat(result).isNotNull(); @@ -245,6 +248,7 @@ void createNotice_Success_WhenEmployerIsNull() { .build(); NoticeDto.CreateRequest request = NoticeDto.CreateRequest.builder() + .workplaceId(1L) .category(NoticeCategory.URGENT) .title("긴급 공지") .content("위생사항 엄수") @@ -257,7 +261,7 @@ void createNotice_Success_WhenEmployerIsNull() { .thenReturn(List.of(contractInWorkplaceWithoutEmployer)); // when - NoticeDto.Response result = noticeService.createNotice(1L, authorUser, request); + NoticeDto.Response result = noticeService.createNotice(authorUser, request); // then assertThat(result).isNotNull(); @@ -290,6 +294,7 @@ void createNotice_Success_WhenOneRecipientPublishFails() { .build(); NoticeDto.CreateRequest request = NoticeDto.CreateRequest.builder() + .workplaceId(1L) .category(NoticeCategory.URGENT) .title("긴급 공지") .content("위생사항 엄수") @@ -310,7 +315,7 @@ void createNotice_Success_WhenOneRecipientPublishFails() { }).when(eventPublisher).publishEvent(any(NotificationEvent.class)); // when - NoticeDto.Response result = noticeService.createNotice(1L, authorUser, request); + NoticeDto.Response result = noticeService.createNotice(authorUser, request); // then assertThat(result).isNotNull(); From 9d98973295cceb32094d2a25906525d90d0232f4 Mon Sep 17 00:00:00 2001 From: Desktop Date: Mon, 4 May 2026 00:41:38 +0900 Subject: [PATCH 5/5] refactor(notice): revert notice API to RESTful path variable and remove unrelated isActive filter - Restore POST /api/workplaces/{workplaceId}/notices endpoint (RESTful design) - Restore createNotice(Long workplaceId, User, Request) method signature - Remove workplaceId field from NoticeDto.CreateRequest - Revert WorkplaceService.getWorkplacesByUserId to single-param signature - Remove isActive query param from WorkplaceController (unrelated to issue #159) - Remove WorkplaceControllerTest added for isActive feature - Update NoticeServiceTest and WorkplaceServiceTest accordingly --- .../api/employer/WorkplaceController.java | 6 +- .../paycheck/api/notice/NoticeController.java | 9 +- .../paycheck/domain/notice/dto/NoticeDto.java | 3 - .../domain/notice/service/NoticeService.java | 4 +- .../workplace/service/WorkplaceService.java | 10 +- .../api/employer/WorkplaceControllerTest.java | 130 ------------------ .../notice/service/NoticeServiceTest.java | 15 +- .../service/WorkplaceServiceTest.java | 22 +-- 8 files changed, 18 insertions(+), 181 deletions(-) delete mode 100644 src/test/java/com/example/paycheck/api/employer/WorkplaceControllerTest.java diff --git a/src/main/java/com/example/paycheck/api/employer/WorkplaceController.java b/src/main/java/com/example/paycheck/api/employer/WorkplaceController.java index 15f0db4a..902f775a 100644 --- a/src/main/java/com/example/paycheck/api/employer/WorkplaceController.java +++ b/src/main/java/com/example/paycheck/api/employer/WorkplaceController.java @@ -36,10 +36,8 @@ public ApiResponse createWorkplace( @Operation(summary = "내 사업장 목록 조회", description = "로그인한 고용주의 사업장 목록을 조회합니다.") @GetMapping public ApiResponse> getWorkplaces( - @AuthenticationPrincipal User user, - @Parameter(description = "활성화 여부 필터 (true: 활성, false: 비활성, 미전송: 전체)") - @RequestParam(required = false) Boolean isActive) { - return ApiResponse.success(workplaceService.getWorkplacesByUserId(user.getId(), isActive)); + @AuthenticationPrincipal User user) { + return ApiResponse.success(workplaceService.getWorkplacesByUserId(user.getId())); } @Operation(summary = "사업장 상세 조회", description = "특정 사업장의 상세 정보를 조회합니다.") diff --git a/src/main/java/com/example/paycheck/api/notice/NoticeController.java b/src/main/java/com/example/paycheck/api/notice/NoticeController.java index a04f71dd..99f16651 100644 --- a/src/main/java/com/example/paycheck/api/notice/NoticeController.java +++ b/src/main/java/com/example/paycheck/api/notice/NoticeController.java @@ -25,13 +25,14 @@ public class NoticeController { private final NoticeService noticeService; - @Operation(summary = "공지사항 작성", description = "지정한 사업장에 공지사항을 작성합니다.") - @PreAuthorize("@permissionEvaluator.canAccessWorkplaceAsMember(#request.workplaceId)") - @PostMapping("/notices") + @Operation(summary = "공지사항 작성", description = "사업장에 새로운 공지사항을 작성합니다.") + @PreAuthorize("@permissionEvaluator.canAccessWorkplaceAsMember(#workplaceId)") + @PostMapping("/workplaces/{workplaceId}/notices") public ResponseEntity> createNotice( + @Parameter(description = "사업장 ID", required = true) @PathVariable Long workplaceId, @AuthenticationPrincipal User user, @Valid @RequestBody NoticeDto.CreateRequest request) { - NoticeDto.Response response = noticeService.createNotice(user, request); + NoticeDto.Response response = noticeService.createNotice(workplaceId, user, request); return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(response)); } diff --git a/src/main/java/com/example/paycheck/domain/notice/dto/NoticeDto.java b/src/main/java/com/example/paycheck/domain/notice/dto/NoticeDto.java index e7e7e1e7..a332448b 100644 --- a/src/main/java/com/example/paycheck/domain/notice/dto/NoticeDto.java +++ b/src/main/java/com/example/paycheck/domain/notice/dto/NoticeDto.java @@ -21,9 +21,6 @@ public class NoticeDto { @AllArgsConstructor @Schema(name = "NoticeCreateRequest") public static class CreateRequest { - @NotNull(message = "사업장 ID는 필수입니다.") - private Long workplaceId; - @NotNull(message = "카테고리는 필수입니다.") private NoticeCategory category; diff --git a/src/main/java/com/example/paycheck/domain/notice/service/NoticeService.java b/src/main/java/com/example/paycheck/domain/notice/service/NoticeService.java index 5f70c657..6f18390e 100644 --- a/src/main/java/com/example/paycheck/domain/notice/service/NoticeService.java +++ b/src/main/java/com/example/paycheck/domain/notice/service/NoticeService.java @@ -43,12 +43,12 @@ public class NoticeService { private final ObjectMapper objectMapper; @Transactional - public NoticeDto.Response createNotice(User author, NoticeDto.CreateRequest request) { + public NoticeDto.Response createNotice(Long workplaceId, User author, NoticeDto.CreateRequest request) { if (!request.getExpiresAt().isAfter(LocalDateTime.now())) { throw new BadRequestException(ErrorCode.INVALID_INPUT_VALUE, "만료 일시는 현재 시간 이후여야 합니다."); } - Workplace workplace = workplaceRepository.findById(request.getWorkplaceId()) + Workplace workplace = workplaceRepository.findById(workplaceId) .orElseThrow(() -> new NotFoundException(ErrorCode.WORKPLACE_NOT_FOUND, "사업장을 찾을 수 없습니다.")); Notice notice = Notice.builder() diff --git a/src/main/java/com/example/paycheck/domain/workplace/service/WorkplaceService.java b/src/main/java/com/example/paycheck/domain/workplace/service/WorkplaceService.java index 8bd13210..6a8c9420 100644 --- a/src/main/java/com/example/paycheck/domain/workplace/service/WorkplaceService.java +++ b/src/main/java/com/example/paycheck/domain/workplace/service/WorkplaceService.java @@ -44,16 +44,10 @@ public WorkplaceDto.Response createWorkplace(Long userId, WorkplaceDto.CreateReq return WorkplaceDto.Response.from(saved); } - public List getWorkplacesByUserId(Long userId, Boolean isActive) { + public List getWorkplacesByUserId(Long userId) { Employer employer = employerService.getEmployerByUserId(userId); - List workplaces; - if (isActive != null) { - workplaces = workplaceRepository.findByEmployerIdAndIsActive(employer.getId(), isActive); - } else { - // 하위 호환성을 위해 기본적으로 활성 사업장만 조회 - workplaces = workplaceRepository.findByEmployerIdAndIsActive(employer.getId(), true); - } + List workplaces = workplaceRepository.findByEmployerIdAndIsActive(employer.getId(), true); return workplaces.stream() .map(workplace -> { diff --git a/src/test/java/com/example/paycheck/api/employer/WorkplaceControllerTest.java b/src/test/java/com/example/paycheck/api/employer/WorkplaceControllerTest.java deleted file mode 100644 index f209ae6f..00000000 --- a/src/test/java/com/example/paycheck/api/employer/WorkplaceControllerTest.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.example.paycheck.api.employer; - -import com.example.paycheck.domain.user.entity.User; -import com.example.paycheck.domain.workplace.dto.WorkplaceDto; -import com.example.paycheck.domain.workplace.service.WorkplaceService; -import com.example.paycheck.global.security.JwtAuthenticationFilter; -import com.example.paycheck.global.security.JwtTokenProvider; -import com.example.paycheck.global.security.permission.CustomPermissionEvaluator; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.http.MediaType; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.test.context.bean.override.mockito.MockitoBean; -import org.springframework.test.web.servlet.MockMvc; - -import java.util.List; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@WebMvcTest(WorkplaceController.class) -@AutoConfigureMockMvc(addFilters = false) -@DisplayName("WorkplaceController 테스트") -class WorkplaceControllerTest { - - @Autowired - private MockMvc mockMvc; - - @MockitoBean - private WorkplaceService workplaceService; - - @MockitoBean - private CustomPermissionEvaluator permissionEvaluator; - - @MockitoBean - private JwtTokenProvider jwtTokenProvider; - - @MockitoBean - private JwtAuthenticationFilter jwtAuthenticationFilter; - - @Autowired - private ObjectMapper objectMapper; - - private User testUser; - private UsernamePasswordAuthenticationToken authentication; - - @BeforeEach - void setUp() { - testUser = User.builder() - .id(1L) - .name("테스트 고용주") - .build(); - authentication = new UsernamePasswordAuthenticationToken(testUser, null, List.of()); - - given(permissionEvaluator.isEmployer()).willReturn(true); - } - - @Nested - @DisplayName("GET /api/employer/workplaces") - class GetWorkplaces { - - @Test - @DisplayName("성공 - isActive 파라미터 없이 모든 사업장을 조회한다") - void getWorkplaces_NoParam_Success() throws Exception { - // given - List response = List.of( - WorkplaceDto.ListResponse.builder().id(1L).name("사업장1").isActive(true).build(), - WorkplaceDto.ListResponse.builder().id(2L).name("사업장2").isActive(false).build() - ); - given(workplaceService.getWorkplacesByUserId(eq(1L), eq(null))) - .willReturn(response); - - // when & then - mockMvc.perform(get("/api/employer/workplaces") - .principal(authentication)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.success").value(true)) - .andExpect(jsonPath("$.data.length()").value(2)); - } - - @Test - @DisplayName("성공 - isActive=true 파라미터로 활성 사업장만 조회한다") - void getWorkplaces_ActiveTrue_Success() throws Exception { - // given - List response = List.of( - WorkplaceDto.ListResponse.builder().id(1L).name("사업장1").isActive(true).build() - ); - given(workplaceService.getWorkplacesByUserId(eq(1L), eq(true))) - .willReturn(response); - - // when & then - mockMvc.perform(get("/api/employer/workplaces") - .param("isActive", "true") - .principal(authentication)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.success").value(true)) - .andExpect(jsonPath("$.data.length()").value(1)) - .andExpect(jsonPath("$.data[0].isActive").value(true)); - } - - @Test - @DisplayName("성공 - isActive=false 파라미터로 비활성 사업장만 조회한다") - void getWorkplaces_ActiveFalse_Success() throws Exception { - // given - List response = List.of( - WorkplaceDto.ListResponse.builder().id(2L).name("사업장2").isActive(false).build() - ); - given(workplaceService.getWorkplacesByUserId(eq(1L), eq(false))) - .willReturn(response); - - // when & then - mockMvc.perform(get("/api/employer/workplaces") - .param("isActive", "false") - .principal(authentication)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.success").value(true)) - .andExpect(jsonPath("$.data.length()").value(1)) - .andExpect(jsonPath("$.data[0].isActive").value(false)); - } - } -} diff --git a/src/test/java/com/example/paycheck/domain/notice/service/NoticeServiceTest.java b/src/test/java/com/example/paycheck/domain/notice/service/NoticeServiceTest.java index 5735958a..d42a3095 100644 --- a/src/test/java/com/example/paycheck/domain/notice/service/NoticeServiceTest.java +++ b/src/test/java/com/example/paycheck/domain/notice/service/NoticeServiceTest.java @@ -131,7 +131,6 @@ void setUp() { void createNotice_Success() { // given NoticeDto.CreateRequest request = NoticeDto.CreateRequest.builder() - .workplaceId(1L) .category(NoticeCategory.URGENT) .title("긴급 공지") .content("위생사항 엄수") @@ -144,7 +143,7 @@ void createNotice_Success() { .thenReturn(List.of(activeContract)); // when - NoticeDto.Response result = noticeService.createNotice(authorUser, request); + NoticeDto.Response result = noticeService.createNotice(1L, authorUser, request); // then assertThat(result).isNotNull(); @@ -171,7 +170,6 @@ void createNotice_Success() { void createNotice_Fail_WorkplaceNotFound() { // given NoticeDto.CreateRequest request = NoticeDto.CreateRequest.builder() - .workplaceId(999L) .category(NoticeCategory.ETC) .title("공지") .content("내용") @@ -181,7 +179,7 @@ void createNotice_Fail_WorkplaceNotFound() { when(workplaceRepository.findById(999L)).thenReturn(Optional.empty()); // when & then - assertThatThrownBy(() -> noticeService.createNotice(authorUser, request)) + assertThatThrownBy(() -> noticeService.createNotice(999L, authorUser, request)) .isInstanceOf(NotFoundException.class) .hasMessageContaining("사업장을 찾을 수 없습니다"); @@ -193,7 +191,6 @@ void createNotice_Fail_WorkplaceNotFound() { void createNotice_Success_WhenNotificationPublishFails() { // given NoticeDto.CreateRequest request = NoticeDto.CreateRequest.builder() - .workplaceId(1L) .category(NoticeCategory.URGENT) .title("긴급 공지") .content("위생사항 엄수") @@ -208,7 +205,7 @@ void createNotice_Success_WhenNotificationPublishFails() { .when(eventPublisher).publishEvent(any(NotificationEvent.class)); // when - NoticeDto.Response result = noticeService.createNotice(authorUser, request); + NoticeDto.Response result = noticeService.createNotice(1L, authorUser, request); // then assertThat(result).isNotNull(); @@ -248,7 +245,6 @@ void createNotice_Success_WhenEmployerIsNull() { .build(); NoticeDto.CreateRequest request = NoticeDto.CreateRequest.builder() - .workplaceId(1L) .category(NoticeCategory.URGENT) .title("긴급 공지") .content("위생사항 엄수") @@ -261,7 +257,7 @@ void createNotice_Success_WhenEmployerIsNull() { .thenReturn(List.of(contractInWorkplaceWithoutEmployer)); // when - NoticeDto.Response result = noticeService.createNotice(authorUser, request); + NoticeDto.Response result = noticeService.createNotice(1L, authorUser, request); // then assertThat(result).isNotNull(); @@ -294,7 +290,6 @@ void createNotice_Success_WhenOneRecipientPublishFails() { .build(); NoticeDto.CreateRequest request = NoticeDto.CreateRequest.builder() - .workplaceId(1L) .category(NoticeCategory.URGENT) .title("긴급 공지") .content("위생사항 엄수") @@ -315,7 +310,7 @@ void createNotice_Success_WhenOneRecipientPublishFails() { }).when(eventPublisher).publishEvent(any(NotificationEvent.class)); // when - NoticeDto.Response result = noticeService.createNotice(authorUser, request); + NoticeDto.Response result = noticeService.createNotice(1L, authorUser, request); // then assertThat(result).isNotNull(); diff --git a/src/test/java/com/example/paycheck/domain/workplace/service/WorkplaceServiceTest.java b/src/test/java/com/example/paycheck/domain/workplace/service/WorkplaceServiceTest.java index feb38d3c..e7cf9def 100644 --- a/src/test/java/com/example/paycheck/domain/workplace/service/WorkplaceServiceTest.java +++ b/src/test/java/com/example/paycheck/domain/workplace/service/WorkplaceServiceTest.java @@ -141,7 +141,7 @@ void getWorkplaceById_NotFound() { @Test @DisplayName("사용자 ID로 사업장 목록 조회 성공 - 활성 사업장만 조회") - void getWorkplacesByUserId_ActiveOnly_Success() { + void getWorkplacesByUserId_Success() { // given when(employerService.getEmployerByUserId(1L)).thenReturn(testEmployer); when(workplaceRepository.findByEmployerIdAndIsActive(1L, true)) @@ -149,25 +149,7 @@ void getWorkplacesByUserId_ActiveOnly_Success() { when(workerContractRepository.countByWorkplaceIdAndIsActive(1L, true)).thenReturn(5); // when - List result = workplaceService.getWorkplacesByUserId(1L, true); - - // then - assertThat(result).isNotEmpty(); - verify(employerService).getEmployerByUserId(1L); - verify(workplaceRepository).findByEmployerIdAndIsActive(1L, true); - } - - @Test - @DisplayName("사용자 ID로 사업장 목록 조회 성공 - isActive=null이면 활성 사업장만 조회") - void getWorkplacesByUserId_NullParam_Success() { - // given - when(employerService.getEmployerByUserId(1L)).thenReturn(testEmployer); - when(workplaceRepository.findByEmployerIdAndIsActive(1L, true)) - .thenReturn(Collections.singletonList(testWorkplace)); - when(workerContractRepository.countByWorkplaceIdAndIsActive(1L, true)).thenReturn(5); - - // when - List result = workplaceService.getWorkplacesByUserId(1L, null); + List result = workplaceService.getWorkplacesByUserId(1L); // then assertThat(result).isNotEmpty();