diff --git a/.coderabbit.yml b/.coderabbit.yml index 2f7e97c4..465f6147 100644 --- a/.coderabbit.yml +++ b/.coderabbit.yml @@ -8,6 +8,8 @@ reviews: auto_review: enabled: true drafts: false + base_branches: + - ".*" profile: "assertive" request_changes_workflow: false high_level_summary: true diff --git a/.gitignore b/.gitignore index 71cc691d..a311993f 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,5 @@ pinpoint-agent/tools/ # macOS artefacts .DS_Store + +docs/ \ No newline at end of file diff --git a/src/main/java/com/project/dorumdorum/domain/room/application/usecase/ApplyRoomUseCase.java b/src/main/java/com/project/dorumdorum/domain/room/application/usecase/ApplyRoomUseCase.java index 57566142..d29c7c86 100644 --- a/src/main/java/com/project/dorumdorum/domain/room/application/usecase/ApplyRoomUseCase.java +++ b/src/main/java/com/project/dorumdorum/domain/room/application/usecase/ApplyRoomUseCase.java @@ -7,6 +7,7 @@ import com.project.dorumdorum.domain.room.domain.service.RoomRequestService; import com.project.dorumdorum.domain.room.domain.service.RoomService; import com.project.dorumdorum.domain.roommate.domain.service.RoommateService; +import com.project.dorumdorum.domain.user.domain.entity.User; import com.project.dorumdorum.domain.user.domain.service.UserService; import com.project.dorumdorum.global.exception.RestApiException; import lombok.RequiredArgsConstructor; @@ -34,10 +35,12 @@ public class ApplyRoomUseCase { * - 지원 요청을 생성하고 1:1 채팅방 생성 이벤트를 발행 */ public String execute(String userNo, String roomNo, JoinRoomRequest request) { - userService.validateExistsById(userNo); - + User applicant = userService.findById(userNo); Room room = roomService.findById(roomNo); + if (!applicant.getGender().equals(room.getGender())) { + throw new RestApiException(GENDER_MISMATCH); + } if (roommateService.isUserRoommate(userNo, roomNo)) { throw new RestApiException(CANNOT_APPLY_TO_OWN_ROOM); } diff --git a/src/main/java/com/project/dorumdorum/domain/room/application/usecase/CreateRoomUseCase.java b/src/main/java/com/project/dorumdorum/domain/room/application/usecase/CreateRoomUseCase.java index d6c9ff3a..1273e16a 100644 --- a/src/main/java/com/project/dorumdorum/domain/room/application/usecase/CreateRoomUseCase.java +++ b/src/main/java/com/project/dorumdorum/domain/room/application/usecase/CreateRoomUseCase.java @@ -8,15 +8,21 @@ import com.project.dorumdorum.domain.room.domain.service.RoomService; import com.project.dorumdorum.domain.roommate.domain.entity.RoomRole; import com.project.dorumdorum.domain.roommate.domain.service.RoommateService; +import com.project.dorumdorum.domain.user.domain.entity.Gender; +import com.project.dorumdorum.domain.user.domain.service.UserService; +import com.project.dorumdorum.global.exception.RestApiException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import static com.project.dorumdorum.global.exception.code.status.RoomErrorStatus.ALREADY_JOINED_USER; + @Service @Transactional @RequiredArgsConstructor public class CreateRoomUseCase { + private final UserService userService; private final RoomService roomService; private final RoommateService roommateService; private final RoomRuleService roomRuleService; @@ -29,7 +35,11 @@ public class CreateRoomUseCase { * - 생성된 방 번호를 반환 */ public String execute(String userNo, RoomCreateRequest request) { - Room room = roomService.create(userNo, request); + if (roommateService.existsByUserNo(userNo)) { + throw new RestApiException(ALREADY_JOINED_USER); + } + Gender gender = userService.findById(userNo).getGender(); + Room room = roomService.create(userNo, gender, request); roommateService.create(userNo, room, RoomRole.HOST); RoomRule roomRule = roomRuleMapper.toRoomRule(room, request.rule()); diff --git a/src/main/java/com/project/dorumdorum/domain/room/application/usecase/FindRoomsUseCase.java b/src/main/java/com/project/dorumdorum/domain/room/application/usecase/FindRoomsUseCase.java index 155152a7..a2585433 100644 --- a/src/main/java/com/project/dorumdorum/domain/room/application/usecase/FindRoomsUseCase.java +++ b/src/main/java/com/project/dorumdorum/domain/room/application/usecase/FindRoomsUseCase.java @@ -3,6 +3,8 @@ import com.project.dorumdorum.domain.room.application.dto.request.ChecklistFilterRequest; import com.project.dorumdorum.domain.room.application.dto.response.FindRoomsResponse; import com.project.dorumdorum.domain.room.domain.service.RoomService; +import com.project.dorumdorum.domain.user.domain.entity.Gender; +import com.project.dorumdorum.domain.user.domain.service.UserService; import com.project.dorumdorum.global.pagination.CursorCodec; import com.project.dorumdorum.global.pagination.CursorPage; import com.project.dorumdorum.global.pagination.CursorQueryParams; @@ -18,6 +20,7 @@ @Transactional(readOnly = true) public class FindRoomsUseCase { + private final UserService userService; private final RoomService roomService; private static final int LIMIT = 50; @@ -26,10 +29,12 @@ public class FindRoomsUseCase { * - 필터와 커서 기준으로 방 목록을 조회 * - 다음 페이지 커서를 포함한 결과를 반환 */ - public CursorPage execute(ChecklistFilterRequest request) { + public CursorPage execute(String userNo, ChecklistFilterRequest request) { + Gender gender = userService.findById(userNo).getGender(); CursorQueryParams params = PaginationHelper.prepareCursorQuery(request.cursor(), LIMIT); List responses = roomService.searchByCursor( + gender, request, params.cursorCreatedAt(), params.cursorId(), diff --git a/src/main/java/com/project/dorumdorum/domain/room/domain/entity/Room.java b/src/main/java/com/project/dorumdorum/domain/room/domain/entity/Room.java index dcf50087..7596aeaf 100644 --- a/src/main/java/com/project/dorumdorum/domain/room/domain/entity/Room.java +++ b/src/main/java/com/project/dorumdorum/domain/room/domain/entity/Room.java @@ -1,5 +1,6 @@ package com.project.dorumdorum.domain.room.domain.entity; +import com.project.dorumdorum.domain.user.domain.entity.Gender; import com.project.dorumdorum.global.common.BaseEntity; import io.hypersistence.utils.hibernate.id.Tsid; import jakarta.persistence.*; @@ -12,8 +13,8 @@ @Builder @Table( indexes = { - @Index(name = "idx_room_status_created", columnList = "room_status, created_at, room_no"), - @Index(name = "idx_room_status_remaining_created", columnList = "room_status, remaining, created_at DESC, room_no DESC"), + @Index(name = "idx_room_status_gender_created", columnList = "room_status, gender, created_at DESC, room_no DESC"), + @Index(name = "idx_room_status_gender_remaining_created", columnList = "room_status, gender, remaining ASC, created_at DESC, room_no DESC"), @Index(name = "idx_room_residence_period", columnList = "residence_period"), @Index(name = "idx_room_host_user_no", columnList = "host_user_no") } @@ -49,6 +50,10 @@ public class Room extends BaseEntity { @Column(nullable = false) private String hostUserNo; + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Gender gender; + @Column(nullable = false) @Enumerated(EnumType.STRING) private ResidencePeriod residencePeriod; // 거주기간 (예: "학기(16주)", "반기(24주)", "계절학기") diff --git a/src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomQueryRepository.java b/src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomQueryRepository.java index eff78adb..a124acc1 100644 --- a/src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomQueryRepository.java +++ b/src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomQueryRepository.java @@ -2,6 +2,7 @@ import com.project.dorumdorum.domain.room.application.dto.request.ChecklistFilterRequest; import com.project.dorumdorum.domain.room.application.dto.response.FindRoomsResponse; +import com.project.dorumdorum.domain.user.domain.entity.Gender; import java.time.LocalDateTime; import java.util.List; @@ -10,6 +11,7 @@ public interface RoomQueryRepository { List findByCursor( + Gender gender, ChecklistFilterRequest request, LocalDateTime cursorCreatedAt, String cursorId, diff --git a/src/main/java/com/project/dorumdorum/domain/room/domain/service/RoomService.java b/src/main/java/com/project/dorumdorum/domain/room/domain/service/RoomService.java index 6a69f417..74a180ad 100644 --- a/src/main/java/com/project/dorumdorum/domain/room/domain/service/RoomService.java +++ b/src/main/java/com/project/dorumdorum/domain/room/domain/service/RoomService.java @@ -5,6 +5,7 @@ import com.project.dorumdorum.domain.room.application.dto.response.FindRoomsResponse; import com.project.dorumdorum.domain.room.domain.entity.Room; import com.project.dorumdorum.domain.room.domain.repository.RoomRepository; +import com.project.dorumdorum.domain.user.domain.entity.Gender; import com.project.dorumdorum.global.exception.RestApiException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -21,13 +22,14 @@ public class RoomService { private final RoomRepository roomRepository; - public Room create(String userNo, RoomCreateRequest request) { + public Room create(String userNo, Gender gender, RoomCreateRequest request) { Room entity = Room.builder() .capacity(request.capacity()) .roomType(request.roomType()) .residencePeriod(request.residencePeriod()) .title(request.title()) .hostUserNo(userNo) + .gender(gender) .build(); return roomRepository.save(entity); @@ -44,13 +46,14 @@ public Room findByIdForUpdate(String roomNo) { } public List searchByCursor( + Gender gender, ChecklistFilterRequest request, LocalDateTime cursorCreatedAt, String cursorId, Integer cursorRemaining, int limitPlusOne ) { - return roomRepository.findByCursor(request, cursorCreatedAt, cursorId, cursorRemaining, limitPlusOne); + return roomRepository.findByCursor(gender, request, cursorCreatedAt, cursorId, cursorRemaining, limitPlusOne); } public FindRoomsResponse findMyRoom(String userNo) { diff --git a/src/main/java/com/project/dorumdorum/domain/room/infra/repository/RoomRepositoryImpl.java b/src/main/java/com/project/dorumdorum/domain/room/infra/repository/RoomRepositoryImpl.java index 5f687fa2..6980f25d 100644 --- a/src/main/java/com/project/dorumdorum/domain/room/infra/repository/RoomRepositoryImpl.java +++ b/src/main/java/com/project/dorumdorum/domain/room/infra/repository/RoomRepositoryImpl.java @@ -6,6 +6,7 @@ import com.project.dorumdorum.domain.room.domain.entity.RoomType; import com.project.dorumdorum.domain.room.domain.entity.RoomStatus; import com.project.dorumdorum.domain.room.domain.repository.RoomQueryRepository; +import com.project.dorumdorum.domain.user.domain.entity.Gender; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQuery; @@ -38,6 +39,7 @@ public class RoomRepositoryImpl implements RoomQueryRepository { @Override public List findByCursor( + Gender gender, ChecklistFilterRequest request, LocalDateTime cursorCreatedAt, String cursorId, @@ -45,7 +47,7 @@ public List findByCursor( int limitPlusOne ) { if (hasChecklistFilters(request)) { - return findByCursorWithLateral(request, cursorCreatedAt, cursorId, cursorRemaining, limitPlusOne); + return findByCursorWithLateral(gender, request, cursorCreatedAt, cursorId, cursorRemaining, limitPlusOne); } JPAQuery q = query @@ -67,6 +69,7 @@ public List findByCursor( .leftJoin(user).on(user.userNo.eq(room.hostUserNo)) .where( room.roomStatus.eq(RoomStatus.CONFIRM_PENDING), + room.gender.eq(gender), eqRoomType(request), eqResidencePeriod(request), eqCapacity(request), @@ -85,6 +88,7 @@ public List findByCursor( } private List findByCursorWithLateral( + Gender gender, ChecklistFilterRequest request, LocalDateTime cursorCreatedAt, String cursorId, @@ -143,8 +147,10 @@ JOIN LATERAL ( LEFT JOIN users u ON u.user_no = r.host_user_no WHERE r.room_status = :roomStatus + AND r.gender = :gender """); params.put("roomStatus", RoomStatus.CONFIRM_PENDING.name()); + params.put("gender", gender.name()); appendCondition(sql, params, "r.room_type", "roomType", enumName(request.roomType())); appendCondition(sql, params, "r.residence_period", "residencePeriod", enumName(request.residencePeriod())); diff --git a/src/main/java/com/project/dorumdorum/domain/room/ui/FindRoomsController.java b/src/main/java/com/project/dorumdorum/domain/room/ui/FindRoomsController.java index 3a46002b..b9f352a7 100644 --- a/src/main/java/com/project/dorumdorum/domain/room/ui/FindRoomsController.java +++ b/src/main/java/com/project/dorumdorum/domain/room/ui/FindRoomsController.java @@ -4,6 +4,7 @@ import com.project.dorumdorum.domain.room.application.dto.response.FindRoomsResponse; import com.project.dorumdorum.domain.room.application.usecase.FindRoomsUseCase; import com.project.dorumdorum.domain.room.ui.spec.FindRoomsApiSpec; +import com.project.dorumdorum.global.annotation.CurrentUser; import org.springframework.http.ResponseEntity; import com.project.dorumdorum.global.pagination.CursorPage; import jakarta.validation.Valid; @@ -19,8 +20,9 @@ public class FindRoomsController implements FindRoomsApiSpec { @Override public ResponseEntity> loadAll( + @CurrentUser String userNo, @Valid @RequestBody ChecklistFilterRequest request ) { - return ResponseEntity.ok(findRoomsUseCase.execute(request)); + return ResponseEntity.ok(findRoomsUseCase.execute(userNo, request)); } } diff --git a/src/main/java/com/project/dorumdorum/domain/room/ui/spec/FindRoomsApiSpec.java b/src/main/java/com/project/dorumdorum/domain/room/ui/spec/FindRoomsApiSpec.java index a7b7514c..944bc087 100644 --- a/src/main/java/com/project/dorumdorum/domain/room/ui/spec/FindRoomsApiSpec.java +++ b/src/main/java/com/project/dorumdorum/domain/room/ui/spec/FindRoomsApiSpec.java @@ -16,10 +16,12 @@ public interface FindRoomsApiSpec { @Operation( summary = "방 목록 조회 API", description = "체크리스트 및 방 조건을 기준으로 방 목록을 조회합니다. " - + "커서 기반 페이지네이션을 지원합니다." + + "커서 기반 페이지네이션을 지원합니다. " + + "로그인한 사용자의 성별과 일치하는 방만 반환합니다." ) @PostMapping("/api/rooms/search") ResponseEntity> loadAll( + @Parameter(hidden = true) String userNo, @Parameter(description = "체크리스트 기반 방 검색 조건", required = true) @Valid @RequestBody ChecklistFilterRequest request ); diff --git a/src/main/java/com/project/dorumdorum/domain/user/domain/entity/User.java b/src/main/java/com/project/dorumdorum/domain/user/domain/entity/User.java index 77ed3687..7e790a59 100644 --- a/src/main/java/com/project/dorumdorum/domain/user/domain/entity/User.java +++ b/src/main/java/com/project/dorumdorum/domain/user/domain/entity/User.java @@ -51,6 +51,7 @@ public class User extends BaseEntity { private Integer age; + @Column(nullable = false) @Enumerated(EnumType.STRING) private Gender gender; diff --git a/src/main/java/com/project/dorumdorum/global/exception/code/status/RoomErrorStatus.java b/src/main/java/com/project/dorumdorum/global/exception/code/status/RoomErrorStatus.java index 6b027bbc..489445be 100644 --- a/src/main/java/com/project/dorumdorum/global/exception/code/status/RoomErrorStatus.java +++ b/src/main/java/com/project/dorumdorum/global/exception/code/status/RoomErrorStatus.java @@ -24,6 +24,7 @@ public enum RoomErrorStatus implements BaseCodeInterface { CANNOT_KICK_SELF(HttpStatus.BAD_REQUEST, "ROOM012", "자기 자신은 강퇴할 수 없습니다."), ROOM_FULL(HttpStatus.BAD_REQUEST, "ROOM014", "방 정원이 가득 찼습니다."), INVALID_ROOM_CAPACITY(HttpStatus.BAD_REQUEST, "ROOM015", "현재 인원보다 적은 정원으로 변경할 수 없습니다."), + GENDER_MISMATCH(HttpStatus.FORBIDDEN, "ROOM016", "성별이 다른 방에는 신청할 수 없습니다."), ; private final HttpStatus httpStatus; diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index a9d49e00..8cd6c848 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -16,10 +16,16 @@ CREATE UNIQUE INDEX IF NOT EXISTS uk_device_user_device CREATE UNIQUE INDEX IF NOT EXISTS uk_room_request_user_room_direction ON room_request (user_no, room_no, direction); +ALTER TABLE IF EXISTS users + ALTER COLUMN gender SET NOT NULL; + +DROP INDEX IF EXISTS idx_room_status_created; DROP INDEX IF EXISTS idx_room_status_remaining_created; -CREATE INDEX IF NOT EXISTS idx_room_status_remaining_created - ON room (room_status, remaining, created_at DESC, room_no DESC); +CREATE INDEX IF NOT EXISTS idx_room_status_gender_created + ON room (room_status, gender, created_at DESC, room_no DESC); +CREATE INDEX IF NOT EXISTS idx_room_status_gender_remaining_created + ON room (room_status, gender, remaining ASC, created_at DESC, room_no DESC); ALTER TABLE IF EXISTS chat_room DROP CONSTRAINT IF EXISTS uk_chat_room_direct; diff --git a/src/test/java/com/project/dorumdorum/domain/chat/integration/ChatTransactionAtomicityIntegrationTest.java b/src/test/java/com/project/dorumdorum/domain/chat/integration/ChatTransactionAtomicityIntegrationTest.java index 3f7d7c18..eb13a2f7 100644 --- a/src/test/java/com/project/dorumdorum/domain/chat/integration/ChatTransactionAtomicityIntegrationTest.java +++ b/src/test/java/com/project/dorumdorum/domain/chat/integration/ChatTransactionAtomicityIntegrationTest.java @@ -16,6 +16,7 @@ import com.project.dorumdorum.domain.roommate.domain.entity.RoomRole; import com.project.dorumdorum.domain.roommate.domain.entity.Roommate; import com.project.dorumdorum.domain.roommate.domain.repository.RoommateRepository; +import com.project.dorumdorum.domain.user.domain.entity.Gender; import com.project.dorumdorum.domain.user.domain.entity.Role; import com.project.dorumdorum.domain.user.domain.entity.User; import com.project.dorumdorum.domain.user.domain.service.UserService; @@ -117,6 +118,7 @@ void setUp() { .title("원자성 테스트 방") .hostUserNo(HOST_NO) .residencePeriod(ResidencePeriod.SEMESTER) + .gender(Gender.MALE) .build() ); diff --git a/src/test/java/com/project/dorumdorum/domain/room/unit/infra/repository/RoomRepositoryImplTest.java b/src/test/java/com/project/dorumdorum/domain/room/unit/infra/repository/RoomRepositoryImplTest.java index 55c3c9cc..9338808e 100644 --- a/src/test/java/com/project/dorumdorum/domain/room/unit/infra/repository/RoomRepositoryImplTest.java +++ b/src/test/java/com/project/dorumdorum/domain/room/unit/infra/repository/RoomRepositoryImplTest.java @@ -7,6 +7,7 @@ import com.project.dorumdorum.domain.room.domain.entity.RoomStatus; import com.project.dorumdorum.domain.room.domain.entity.RoomType; import com.project.dorumdorum.domain.room.infra.repository.RoomRepositoryImpl; +import com.project.dorumdorum.domain.user.domain.entity.Gender; import com.querydsl.core.types.Expression; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -35,6 +36,8 @@ class RoomRepositoryImplTest { @Mock private JPAQueryFactory queryFactory; @Mock private EntityManager entityManager; + private static final Gender DEFAULT_GENDER = Gender.MALE; + private ChecklistFilterRequest request(ChecklistFilterRequest.SortType sortType) { return new ChecklistFilterRequest( sortType, null, null, null, null, @@ -67,7 +70,7 @@ void findByCursor_WhenRecruiting_FetchesWithLimit() { ); List result = repository.findByCursor( - request, cursorCreatedAt, "r1", 1, 51 + DEFAULT_GENDER, request, cursorCreatedAt, "r1", 1, 51 ); assertThat(result).isEqualTo(expected); @@ -89,7 +92,7 @@ void findByCursor_WithCreatedAtSortCursor_Fetches() { ChecklistFilterRequest request = request(ChecklistFilterRequest.SortType.LATEST); List result = repository.findByCursor( - request, cursorCreatedAt, "r9", null, 11 + DEFAULT_GENDER, request, cursorCreatedAt, "r9", null, 11 ); assertThat(result).isEmpty(); @@ -108,7 +111,7 @@ void findByCursor_WithNullCursorAndEmptyFilters_Fetches() { ChecklistFilterRequest request = request(ChecklistFilterRequest.SortType.LATEST); List result = repository.findByCursor( - request, null, null, null, 7 + DEFAULT_GENDER, request, null, null, null, 7 ); assertThat(result).isEmpty(); @@ -130,7 +133,7 @@ void findByCursor_WithNullSort_UsesDefaultOrderBranch() { ChecklistFilterRequest request = request(null); List result = repository.findByCursor( - request, cursorCreatedAt, "r3", null, 5 + DEFAULT_GENDER, request, cursorCreatedAt, "r3", null, 5 ); assertThat(result).isEmpty(); @@ -145,34 +148,11 @@ void findByCursor_WithChecklistFilters_UsesNativeLateralQuery() { LocalDateTime createdAt = LocalDateTime.now(); ChecklistFilterRequest request = new ChecklistFilterRequest( ChecklistFilterRequest.SortType.REMAINING, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, + null, null, null, null, + null, null, null, null, null, null, null, null, null, null, + null, null, null, null, SmokingType.NON_SMOKER, - null, - null, - null, - null, - null, - null, - null, - null, - null + null, null, null, null, null, null, null, null, null ); Object[] row = { @@ -194,7 +174,7 @@ void findByCursor_WithChecklistFilters_UsesNativeLateralQuery() { rows.add(row); when(nativeQuery.getResultList()).thenReturn(rows); - List result = repository.findByCursor(request, createdAt, "r1", 1, 51); + List result = repository.findByCursor(DEFAULT_GENDER, request, createdAt, "r1", 1, 51); assertThat(result).containsExactly( new FindRoomsResponse("r1", RoomType.TYPE_1, 2, 1, createdAt, diff --git a/src/test/java/com/project/dorumdorum/domain/room/unit/service/RoomServiceTest.java b/src/test/java/com/project/dorumdorum/domain/room/unit/service/RoomServiceTest.java index 2759b33f..4aa5939b 100644 --- a/src/test/java/com/project/dorumdorum/domain/room/unit/service/RoomServiceTest.java +++ b/src/test/java/com/project/dorumdorum/domain/room/unit/service/RoomServiceTest.java @@ -9,6 +9,7 @@ import com.project.dorumdorum.domain.room.domain.entity.RoomType; import com.project.dorumdorum.domain.room.domain.repository.RoomRepository; import com.project.dorumdorum.domain.room.domain.service.RoomService; +import com.project.dorumdorum.domain.user.domain.entity.Gender; import com.project.dorumdorum.global.exception.RestApiException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -36,13 +37,13 @@ class RoomServiceTest { @InjectMocks private RoomService service; @Test - @DisplayName("Should create and save room from request") + @DisplayName("Should create and save room with gender from request") void create_SavesRoom() { RoomCreateRequest request = new RoomCreateRequest(RoomType.TYPE_1, 2, ResidencePeriod.SEMESTER, "title", null); - Room saved = Room.builder().roomNo("r1").hostUserNo("u1").build(); + Room saved = Room.builder().roomNo("r1").hostUserNo("u1").gender(Gender.MALE).build(); when(roomRepository.save(any(Room.class))).thenReturn(saved); - Room result = service.create("u1", request); + Room result = service.create("u1", Gender.MALE, request); assertThat(result).isEqualTo(saved); verify(roomRepository).save(any(Room.class)); @@ -56,7 +57,7 @@ void findById_WhenMissing_Throws() { } @Test - @DisplayName("Should delegate cursor search to repository") + @DisplayName("Should delegate cursor search to repository with gender filter") void searchByCursor_DelegatesToRepository() { List expected = List.of( new FindRoomsResponse("r1", RoomType.TYPE_1, 2, 1, LocalDateTime.now(), @@ -68,11 +69,11 @@ void searchByCursor_DelegatesToRepository() { null, null, null, null, null, null, null, null, null, null, null, null, null, null ); - when(roomRepository.findByCursor(any(), any(), any(), any(), anyInt())) + when(roomRepository.findByCursor(any(Gender.class), any(), any(), any(), any(), anyInt())) .thenReturn(expected); List result = service.searchByCursor( - request, null, null, null, 10 + Gender.MALE, request, null, null, null, 10 ); assertThat(result).isEqualTo(expected); diff --git a/src/test/java/com/project/dorumdorum/domain/room/unit/ui/FindRoomsControllerTest.java b/src/test/java/com/project/dorumdorum/domain/room/unit/ui/FindRoomsControllerTest.java index b2af2a1d..da15bfc8 100644 --- a/src/test/java/com/project/dorumdorum/domain/room/unit/ui/FindRoomsControllerTest.java +++ b/src/test/java/com/project/dorumdorum/domain/room/unit/ui/FindRoomsControllerTest.java @@ -26,6 +26,8 @@ class FindRoomsControllerTest { @Mock private FindRoomsUseCase useCase; @InjectMocks private FindRoomsController controller; + private static final String USER_NO = "u1"; + @Test void loadAll_ReturnsUseCaseResult() { ChecklistFilterRequest request = new ChecklistFilterRequest( @@ -35,12 +37,12 @@ void loadAll_ReturnsUseCaseResult() { null, null, null, null ); CursorPage page = new CursorPage<>(List.of(), null, false); - when(useCase.execute(request)).thenReturn(page); + when(useCase.execute(USER_NO, request)).thenReturn(page); ResponseEntity> response = - controller.loadAll(request); + controller.loadAll(USER_NO, request); - verify(useCase).execute(request); + verify(useCase).execute(USER_NO, request); assertThat(response.getBody()).isEqualTo(page); } } diff --git a/src/test/java/com/project/dorumdorum/domain/room/unit/usecase/ApplyRoomUseCaseTest.java b/src/test/java/com/project/dorumdorum/domain/room/unit/usecase/ApplyRoomUseCaseTest.java index dd547ec4..f0878898 100644 --- a/src/test/java/com/project/dorumdorum/domain/room/unit/usecase/ApplyRoomUseCaseTest.java +++ b/src/test/java/com/project/dorumdorum/domain/room/unit/usecase/ApplyRoomUseCaseTest.java @@ -8,6 +8,8 @@ import com.project.dorumdorum.domain.room.domain.service.RoomRequestService; import com.project.dorumdorum.domain.room.domain.service.RoomService; import com.project.dorumdorum.domain.roommate.domain.service.RoommateService; +import com.project.dorumdorum.domain.user.domain.entity.Gender; +import com.project.dorumdorum.domain.user.domain.entity.User; import com.project.dorumdorum.domain.user.domain.service.UserService; import com.project.dorumdorum.domain.room.application.event.RoomApplicationSubmittedEvent; import com.project.dorumdorum.global.exception.RestApiException; @@ -37,12 +39,21 @@ class ApplyRoomUseCaseTest { @InjectMocks private ApplyRoomUseCase useCase; + private User maleUser(String userNo) { + return User.builder().userNo(userNo).gender(Gender.MALE).build(); + } + + private Room maleRoom(String roomNo, String hostUserNo) { + return Room.builder().roomNo(roomNo).hostUserNo(hostUserNo).gender(Gender.MALE).build(); + } + @Test @DisplayName("Should create join request when validations pass") void execute_WithValidState_CreatesJoinRequest() { String userNo = "u1"; String roomNo = "r1"; - Room room = Room.builder().roomNo(roomNo).hostUserNo("host-1").build(); + User applicant = maleUser(userNo); + Room room = maleRoom(roomNo, "host-1"); JoinRoomRequest request = new JoinRoomRequest("intro", "msg"); RoomRequest createdRequest = RoomRequest.builder() .roomRequestNo("req-1") @@ -53,6 +64,7 @@ void execute_WithValidState_CreatesJoinRequest() { .additionalMessage(request.additionalMessage()) .build(); + when(userService.findById(userNo)).thenReturn(applicant); when(roomService.findById(roomNo)).thenReturn(room); when(roommateService.isUserRoommate(userNo, roomNo)).thenReturn(false); when(roommateService.existsByUserNo(userNo)).thenReturn(false); @@ -61,7 +73,7 @@ void execute_WithValidState_CreatesJoinRequest() { String requestNo = useCase.execute(userNo, roomNo, request); - verify(userService).validateExistsById(userNo); + verify(userService).findById(userNo); verify(roomRequestService).create(userNo, room, request, Direction.USER_TO_ROOM); assertThat(requestNo).isEqualTo("req-1"); @@ -74,12 +86,31 @@ void execute_WithValidState_CreatesJoinRequest() { assertThat(event.hostUserNo()).isEqualTo("host-1"); } + @Test + @DisplayName("Should throw GENDER_MISMATCH when applicant gender differs from room gender") + void execute_WhenGenderMismatch_Throws() { + String userNo = "u1"; + String roomNo = "r1"; + User femaleApplicant = User.builder().userNo(userNo).gender(Gender.FEMALE).build(); + Room maleRoom = Room.builder().roomNo(roomNo).gender(Gender.MALE).build(); + + when(userService.findById(userNo)).thenReturn(femaleApplicant); + when(roomService.findById(roomNo)).thenReturn(maleRoom); + + assertThatThrownBy(() -> useCase.execute(userNo, roomNo, new JoinRoomRequest("intro", null))) + .isInstanceOf(RestApiException.class); + + verify(roommateService, never()).isUserRoommate(anyString(), anyString()); + verify(roomRequestService, never()).create(anyString(), any(), any(), eq(Direction.USER_TO_ROOM)); + } + @Test @DisplayName("Should throw when user already belongs to the room") void execute_WhenAlreadyRoommate_Throws() { String userNo = "u1"; String roomNo = "r1"; - when(roomService.findById(roomNo)).thenReturn(Room.builder().roomNo(roomNo).build()); + when(userService.findById(userNo)).thenReturn(maleUser(userNo)); + when(roomService.findById(roomNo)).thenReturn(maleRoom(roomNo, "host-1")); when(roommateService.isUserRoommate(userNo, roomNo)).thenReturn(true); assertThatThrownBy(() -> useCase.execute(userNo, roomNo, new JoinRoomRequest("intro", null))) @@ -93,8 +124,8 @@ void execute_WhenAlreadyRoommate_Throws() { void execute_WhenAlreadyJoined_Throws() { String userNo = "u1"; String roomNo = "r1"; - Room room = Room.builder().roomNo(roomNo).build(); - when(roomService.findById(roomNo)).thenReturn(room); + when(userService.findById(userNo)).thenReturn(maleUser(userNo)); + when(roomService.findById(roomNo)).thenReturn(maleRoom(roomNo, "host-1")); when(roommateService.isUserRoommate(userNo, roomNo)).thenReturn(false); when(roommateService.existsByUserNo(userNo)).thenReturn(true); @@ -107,7 +138,8 @@ void execute_WhenAlreadyJoined_Throws() { void execute_WhenDuplicateRequest_Throws() { String userNo = "u1"; String roomNo = "r1"; - Room room = Room.builder().roomNo(roomNo).build(); + Room room = maleRoom(roomNo, "host-1"); + when(userService.findById(userNo)).thenReturn(maleUser(userNo)); when(roomService.findById(roomNo)).thenReturn(room); when(roommateService.isUserRoommate(userNo, roomNo)).thenReturn(false); when(roommateService.existsByUserNo(userNo)).thenReturn(false); @@ -122,7 +154,8 @@ void execute_WhenDuplicateRequest_Throws() { void execute_WhenValidationFails_NoEventPublished() { String userNo = "u1"; String roomNo = "r1"; - when(roomService.findById(roomNo)).thenReturn(Room.builder().roomNo(roomNo).build()); + when(userService.findById(userNo)).thenReturn(maleUser(userNo)); + when(roomService.findById(roomNo)).thenReturn(maleRoom(roomNo, "host-1")); when(roommateService.isUserRoommate(userNo, roomNo)).thenReturn(true); assertThatThrownBy(() -> useCase.execute(userNo, roomNo, new JoinRoomRequest("intro", null))) diff --git a/src/test/java/com/project/dorumdorum/domain/room/unit/usecase/CreateRoomUseCaseTest.java b/src/test/java/com/project/dorumdorum/domain/room/unit/usecase/CreateRoomUseCaseTest.java index d27be30d..9a414451 100644 --- a/src/test/java/com/project/dorumdorum/domain/room/unit/usecase/CreateRoomUseCaseTest.java +++ b/src/test/java/com/project/dorumdorum/domain/room/unit/usecase/CreateRoomUseCaseTest.java @@ -12,6 +12,9 @@ import com.project.dorumdorum.domain.room.domain.service.RoomService; import com.project.dorumdorum.domain.roommate.domain.entity.RoomRole; import com.project.dorumdorum.domain.roommate.domain.service.RoommateService; +import com.project.dorumdorum.domain.user.domain.entity.Gender; +import com.project.dorumdorum.domain.user.domain.entity.User; +import com.project.dorumdorum.domain.user.domain.service.UserService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -19,13 +22,16 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import com.project.dorumdorum.global.exception.RestApiException; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) @DisplayName("CreateRoomUseCase Unit Tests") class CreateRoomUseCaseTest { + @Mock private UserService userService; @Mock private RoomService roomService; @Mock private RoommateService roommateService; @Mock private RoomRuleService roomRuleService; @@ -33,20 +39,39 @@ class CreateRoomUseCaseTest { @InjectMocks private CreateRoomUseCase useCase; @Test - @DisplayName("Should create room, host roommate and room rule") + @DisplayName("Should throw when user is already in a room") + void execute_WhenUserAlreadyInRoom_Throws() { + String userNo = "u1"; + CreateRoomRuleRequest ruleRequest = mock(CreateRoomRuleRequest.class); + RoomCreateRequest request = new RoomCreateRequest(RoomType.TYPE_1, 2, ResidencePeriod.SEMESTER, "title", ruleRequest); + + when(roommateService.existsByUserNo(userNo)).thenReturn(true); + + assertThatThrownBy(() -> useCase.execute(userNo, request)) + .isInstanceOf(RestApiException.class); + + verify(roomService, never()).create(any(), any(), any()); + } + + @Test + @DisplayName("Should create room with host gender, host roommate and room rule") void execute_CreatesRoomAndHostAndRule() { String userNo = "u1"; - CreateRoomRuleRequest ruleRequest = org.mockito.Mockito.mock(CreateRoomRuleRequest.class); + User host = User.builder().userNo(userNo).gender(Gender.MALE).build(); + CreateRoomRuleRequest ruleRequest = mock(CreateRoomRuleRequest.class); RoomCreateRequest request = new RoomCreateRequest(RoomType.TYPE_1, 2, ResidencePeriod.SEMESTER, "title", ruleRequest); - Room room = Room.builder().roomNo("r1").build(); - RoomRule roomRule = org.mockito.Mockito.mock(RoomRule.class); + Room room = Room.builder().roomNo("r1").gender(Gender.MALE).build(); + RoomRule roomRule = mock(RoomRule.class); - when(roomService.create(userNo, request)).thenReturn(room); + when(roommateService.existsByUserNo(userNo)).thenReturn(false); + when(userService.findById(userNo)).thenReturn(host); + when(roomService.create(userNo, Gender.MALE, request)).thenReturn(room); when(roomRuleMapper.toRoomRule(room, ruleRequest)).thenReturn(roomRule); useCase.execute(userNo, request); - verify(roomService).create(userNo, request); + verify(userService).findById(userNo); + verify(roomService).create(userNo, Gender.MALE, request); verify(roommateService).create(userNo, room, RoomRole.HOST); verify(roomRuleMapper).toRoomRule(room, ruleRequest); verify(roomRuleService).save(roomRule); diff --git a/src/test/java/com/project/dorumdorum/domain/room/unit/usecase/FindRoomsUseCaseTest.java b/src/test/java/com/project/dorumdorum/domain/room/unit/usecase/FindRoomsUseCaseTest.java index 8a6225ec..043e5454 100644 --- a/src/test/java/com/project/dorumdorum/domain/room/unit/usecase/FindRoomsUseCaseTest.java +++ b/src/test/java/com/project/dorumdorum/domain/room/unit/usecase/FindRoomsUseCaseTest.java @@ -1,14 +1,17 @@ package com.project.dorumdorum.domain.room.unit.usecase; import com.project.dorumdorum.domain.room.application.dto.request.ChecklistFilterRequest; -import com.project.dorumdorum.domain.room.application.dto.request.RoomSort; import com.project.dorumdorum.domain.room.application.dto.response.FindRoomsResponse; import com.project.dorumdorum.domain.room.application.usecase.FindRoomsUseCase; import com.project.dorumdorum.domain.room.domain.entity.ResidencePeriod; import com.project.dorumdorum.domain.room.domain.entity.RoomStatus; import com.project.dorumdorum.domain.room.domain.entity.RoomType; import com.project.dorumdorum.domain.room.domain.service.RoomService; +import com.project.dorumdorum.domain.user.domain.entity.Gender; +import com.project.dorumdorum.domain.user.domain.entity.User; +import com.project.dorumdorum.domain.user.domain.service.UserService; import com.project.dorumdorum.global.pagination.CursorPage; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -29,9 +32,18 @@ @DisplayName("FindRoomsUseCase Unit Tests") class FindRoomsUseCaseTest { + @Mock private UserService userService; @Mock private RoomService roomService; @InjectMocks private FindRoomsUseCase useCase; + private static final String USER_NO = "u1"; + private static final User MALE_USER = User.builder().userNo(USER_NO).gender(Gender.MALE).build(); + + @BeforeEach + void setUp() { + when(userService.findById(USER_NO)).thenReturn(MALE_USER); + } + private FindRoomsResponse room(String roomNo, int capacity, int current, LocalDateTime createdAt) { return new FindRoomsResponse(roomNo, RoomType.TYPE_1, capacity, current, createdAt, "title", "host", RoomStatus.CONFIRM_PENDING, ResidencePeriod.SEMESTER.name(), capacity - current); @@ -54,15 +66,15 @@ void execute_WhenResponsesExceedLimit_ReturnsHasNextTrue() { .mapToObj(i -> room("r" + i, 2, 1, now.minusMinutes(i))) .toList(); ChecklistFilterRequest request = request(ChecklistFilterRequest.SortType.LATEST, null); - when(roomService.searchByCursor(any(), any(), any(), any(), anyInt())) + when(roomService.searchByCursor(any(), any(), any(), any(), any(), anyInt())) .thenReturn(responses); - CursorPage result = useCase.execute(request); + CursorPage result = useCase.execute(USER_NO, request); assertThat(result.items()).hasSize(50); assertThat(result.hasNext()).isTrue(); assertThat(result.nextCursor()).isNotBlank(); - verify(roomService).searchByCursor(any(), any(), any(), any(), anyInt()); + verify(roomService).searchByCursor(any(), any(), any(), any(), any(), anyInt()); } @Test @@ -70,10 +82,10 @@ void execute_WhenResponsesExceedLimit_ReturnsHasNextTrue() { void execute_WhenResponsesWithinLimit_ReturnsHasNextFalse() { List responses = List.of(room("r1", 2, 1, LocalDateTime.now())); ChecklistFilterRequest request = request(ChecklistFilterRequest.SortType.REMAINING, null); - when(roomService.searchByCursor(any(), any(), any(), any(), anyInt())) + when(roomService.searchByCursor(any(), any(), any(), any(), any(), anyInt())) .thenReturn(responses); - CursorPage result = useCase.execute(request); + CursorPage result = useCase.execute(USER_NO, request); assertThat(result.items()).hasSize(1); assertThat(result.hasNext()).isFalse(); @@ -84,10 +96,10 @@ void execute_WhenResponsesWithinLimit_ReturnsHasNextFalse() { @DisplayName("Should return null nextCursor when no response") void execute_WhenNoResponse_ReturnsNullCursor() { ChecklistFilterRequest request = request(ChecklistFilterRequest.SortType.LATEST, null); - when(roomService.searchByCursor(any(), any(), any(), any(), anyInt())) + when(roomService.searchByCursor(any(), any(), any(), any(), any(), anyInt())) .thenReturn(List.of()); - CursorPage result = useCase.execute(request); + CursorPage result = useCase.execute(USER_NO, request); assertThat(result.items()).isEmpty(); assertThat(result.nextCursor()).isNull(); @@ -100,12 +112,12 @@ void execute_WithCursor_UsesDecodedCursorPath() { List responses = List.of(room("r1", 2, 1, LocalDateTime.now())); String cursor = com.project.dorumdorum.global.pagination.CursorCodec.encode(LocalDateTime.now(), "r1"); ChecklistFilterRequest request = request(ChecklistFilterRequest.SortType.LATEST, cursor); - when(roomService.searchByCursor(any(), any(), any(), any(), anyInt())) + when(roomService.searchByCursor(any(), any(), any(), any(), any(), anyInt())) .thenReturn(responses); - CursorPage result = useCase.execute(request); + CursorPage result = useCase.execute(USER_NO, request); assertThat(result.items()).hasSize(1); - verify(roomService).searchByCursor(any(), any(), any(), any(), anyInt()); + verify(roomService).searchByCursor(any(), any(), any(), any(), any(), anyInt()); } }