Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,7 @@ public class PostingListFilterDto {

@Parameter(description = "급여순 정렬 여부")
private Boolean payAmountSort;

@Parameter(description = "검색 키워드 (공고 제목 또는 업장명, 대소문자 무시, 앞뒤 공백 제거; 생략하거나 공백만 있으면 전체 조회)")
private String searchKeyword;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class PostingMapListFilterDto {
@Parameter(description = "지도 우측 하단 좌표")
private CoordinateDto coordinate2;

@Parameter(description = "검색 키워드 (공고 제목 또는 업장명)")
@Parameter(description = "검색 키워드 (공고 제목 또는 업장명, 대소문자 무시, 앞뒤 공백 제거; 생략하거나 공백만 있으면 전체 조회)")
private String searchKeyword;

@NotNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Repository;

import java.math.BigDecimal;
Expand Down Expand Up @@ -82,7 +83,8 @@ public long getCountOfPostings(PostingListFilterDto filter) {
ltePayAmount(qPosting, filter.getMaxPayAmount()),
gteStartTime(qPostingSchedule, filter.getStartTime()),
lteEndTime(qPostingSchedule, filter.getEndTime()),
workingDaysIn(qPostingSchedule, filter.getWorkingDays())
workingDaysIn(qPostingSchedule, filter.getWorkingDays()),
containsSearchKeyword(qPosting, qWorkspace, filter.getSearchKeyword())
)
.fetchOne();

Expand Down Expand Up @@ -132,7 +134,8 @@ public List<PostingListResponse> getPostingsWithCursor(CursorPageRequest<CursorD
ltePayAmount(qPosting, filter.getMaxPayAmount()),
gteStartTime(qPostingSchedule, filter.getStartTime()),
lteEndTime(qPostingSchedule, filter.getEndTime()),
workingDaysIn(qPostingSchedule, filter.getWorkingDays())
workingDaysIn(qPostingSchedule, filter.getWorkingDays()),
containsSearchKeyword(qPosting, qWorkspace, filter.getSearchKeyword())
)
.groupBy(qPosting.id, qPosting.payAmount, qPosting.createdAt)
.orderBy(getOrderSpecifiers(qPosting, filter.getPayAmountSort()))
Expand Down Expand Up @@ -563,11 +566,12 @@ private BooleanExpression withinBounds(QWorkspace qWorkspace, CoordinateDto coor
}

private BooleanExpression containsSearchKeyword(QPosting qPosting, QWorkspace qWorkspace, String searchKeyword) {
if (ObjectUtils.isEmpty(searchKeyword) || searchKeyword.isBlank()) {
String keyword = StringUtils.stripToNull(searchKeyword);
if (keyword == null) {
return null;
}
return qPosting.title.containsIgnoreCase(searchKeyword)
.or(qWorkspace.businessName.containsIgnoreCase(searchKeyword));
return qPosting.title.containsIgnoreCase(keyword)
.or(qWorkspace.businessName.containsIgnoreCase(keyword));
}

private OrderSpecifier<?>[] getOrderSpecifiersForMapList(QPosting qPosting, PostingSortType sortType) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package com.dreamteam.alter.application.posting.usecase;

import com.dreamteam.alter.adapter.inbound.common.dto.CursorDto;
import com.dreamteam.alter.adapter.inbound.common.dto.CursorPageRequest;
import com.dreamteam.alter.adapter.inbound.common.dto.CursorPageRequestDto;
import com.dreamteam.alter.adapter.inbound.general.posting.dto.PostingListFilterDto;
import com.dreamteam.alter.adapter.outbound.posting.persistence.readonly.PostingListResponse;
import com.dreamteam.alter.common.util.CursorUtil;
import com.dreamteam.alter.domain.posting.port.outbound.PostingQueryRepository;
import com.dreamteam.alter.domain.user.context.AppActor;
import com.dreamteam.alter.domain.user.entity.User;
import com.dreamteam.alter.domain.workspace.entity.BusinessType;
import com.dreamteam.alter.domain.workspace.entity.Workspace;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;

import java.time.LocalDateTime;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;

@ExtendWith(MockitoExtension.class)
@DisplayName("GetPostingsWithCursor - 일반 공고 목록 커서 조회")
class GetPostingsWithCursorTests {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
@Mock PostingQueryRepository postingQueryRepository;
@Spy ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();
@InjectMocks GetPostingsWithCursor useCase;

private PostingListResponse row(Long id) {
PostingListResponse row = mock(PostingListResponse.class);
Workspace workspace = mock(Workspace.class);
lenient().when(workspace.getBusinessType()).thenReturn(BusinessType.create("카페", "설명"));
lenient().when(row.getWorkspace()).thenReturn(workspace);
lenient().when(row.getSchedules()).thenReturn(List.of());
lenient().when(row.getId()).thenReturn(id);
lenient().when(row.getCreatedAt()).thenReturn(LocalDateTime.of(2026, 10, 3, 12, 0).minusDays(id));
return row;
}

private AppActor actor() {
AppActor actor = mock(AppActor.class);
lenient().when(actor.getUser()).thenReturn(mock(User.class));
return actor;
}

@Test
@DisplayName("다음 페이지 유무와 관계없이 마지막으로 반환한 행을 가리키는 커서를 내려준다")
void cursorPointsAtLastReturnedRowRegardlessOfNextPage() {
// given
given(postingQueryRepository.getCountOfPostings(any())).willReturn(2L);
List<PostingListResponse> rows = List.of(row(1L), row(2L));
given(postingQueryRepository.getPostingsWithCursor(any(), any(), any()))
.willReturn(rows);

// when
var result = useCase.execute(new CursorPageRequestDto(null, 10), new PostingListFilterDto(), actor());

// then
assertThat(result.data()).extracting(dto -> dto.getId()).containsExactly(1L, 2L);
assertThat(result.page().pageSize()).isEqualTo(10);
assertThat(result.page().totalCount()).isEqualTo(2);
CursorDto cursor = CursorUtil.decodeCursor(result.page().cursor(), CursorDto.class, objectMapper);
assertThat(cursor.getId()).isEqualTo(2L);
assertThat(cursor.getCreatedAt()).isEqualTo(rows.get(1).getCreatedAt());
}

@Test
@DisplayName("요청한 페이지 크기 그대로 조회한다")
void repositoryIsAskedForRequestedPageSize() {
// given
given(postingQueryRepository.getCountOfPostings(any())).willReturn(1L);
List<PostingListResponse> rows = List.of(row(1L));
given(postingQueryRepository.getPostingsWithCursor(any(), any(), any()))
.willReturn(rows);

// when
useCase.execute(new CursorPageRequestDto(null, 5), new PostingListFilterDto(), actor());

// then
@SuppressWarnings("unchecked")
ArgumentCaptor<CursorPageRequest<CursorDto>> captor = ArgumentCaptor.forClass(CursorPageRequest.class);
then(postingQueryRepository).should().getPostingsWithCursor(captor.capture(), any(), any());
assertThat(captor.getValue().pageSize()).isEqualTo(5);
}

@Test
@DisplayName("조건에 맞는 공고가 없으면 커서 없이 빈 목록을 반환한다")
void noMatchingPostingsReturnsEmptyWithoutCursor() {
// given
given(postingQueryRepository.getCountOfPostings(any())).willReturn(0L);

// when
var result = useCase.execute(new CursorPageRequestDto(null, 10), new PostingListFilterDto(), actor());

// then
assertThat(result.page().cursor()).isNull();
assertThat(result.page().totalCount()).isZero();
assertThat(result.data()).isEmpty();
}

@Test
@DisplayName("커서 이후 남은 공고가 없으면 커서 없이 빈 목록을 반환한다")
void noRowsAfterCursorReturnsEmptyWithoutCursor() {
// given
given(postingQueryRepository.getCountOfPostings(any())).willReturn(2L);
given(postingQueryRepository.getPostingsWithCursor(any(), any(), any())).willReturn(List.of());

// when
var result = useCase.execute(new CursorPageRequestDto(null, 10), new PostingListFilterDto(), actor());

// then
assertThat(result.page().cursor()).isNull();
assertThat(result.page().totalCount()).isEqualTo(2);
assertThat(result.data()).isEmpty();
}
}
Loading