From 6748a737a53dcec6564458cdd905d0f335208050 Mon Sep 17 00:00:00 2001 From: "DESKTOP-N5KD4EV\\litte" Date: Mon, 13 Oct 2025 12:25:06 +0900 Subject: [PATCH] =?UTF-8?q?refactor/OPS-397=20:=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../archive/folder/service/FolderService.java | 2 +- .../repository/DataSourceQRepositoryImpl.java | 4 +- .../repository/DataSourceRepository.java | 2 +- .../datasource/service/DataSourceService.java | 55 +++++- .../service/PersonalDataSourceService.java | 24 +-- .../service/SpaceDataSourceService.java | 160 ++++++++---------- .../clients/liveblocks/LiveblocksClient.java | 3 +- .../folder/service/FolderServiceTest.java | 2 +- .../service/DataSourceServiceTest.java | 123 ++++++++++++-- .../PersonalArchiveDataSourceServiceTest.java | 27 +-- 10 files changed, 263 insertions(+), 139 deletions(-) diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/archive/folder/service/FolderService.java b/src/main/java/org/tuna/zoopzoop/backend/domain/archive/folder/service/FolderService.java index b5a39118..213aceab 100644 --- a/src/main/java/org/tuna/zoopzoop/backend/domain/archive/folder/service/FolderService.java +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/archive/folder/service/FolderService.java @@ -119,7 +119,7 @@ public FolderFilesDto getFilesInFolder(Archive archive, Integer folderId) { Folder folder = folderRepository.findByIdAndArchiveId(folderId, archive.getId()) .orElseThrow(() -> new NoResultException("존재하지 않는 폴더입니다.")); - var files = dataSourceRepository.findAllByFolder(folder).stream() + var files = dataSourceRepository.findAllByFolderAndIsActiveTrue(folder).stream() .map(ds -> new FileSummary( ds.getId(), ds.getTitle(), diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/repository/DataSourceQRepositoryImpl.java b/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/repository/DataSourceQRepositoryImpl.java index 99617ace..a1478ff2 100644 --- a/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/repository/DataSourceQRepositoryImpl.java +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/repository/DataSourceQRepositoryImpl.java @@ -20,7 +20,7 @@ import org.tuna.zoopzoop.backend.domain.datasource.entity.QDataSource; import org.tuna.zoopzoop.backend.domain.datasource.entity.QTag; -import java.time.LocalDateTime; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -139,7 +139,7 @@ private List> toOrderSpecifiers(Sort sort) { switch (o.getProperty()) { case "title" -> specs.add(new OrderSpecifier<>(dir, root.getString("title"))); case "createdAt" -> specs.add( - new OrderSpecifier<>(dir, root.getDateTime("createDate", LocalDateTime.class)) + new OrderSpecifier<>(dir, root.getDate("dataCreatedDate", LocalDate.class)) ); default -> { /* 무시 */ } } diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/repository/DataSourceRepository.java b/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/repository/DataSourceRepository.java index 3ce68a26..829df578 100644 --- a/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/repository/DataSourceRepository.java +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/repository/DataSourceRepository.java @@ -72,7 +72,7 @@ where ds.folder.id in ( """) void deleteByArchiveId(@Param("archiveId") int archiveId); - List findAllByFolder(Folder folder); + List findAllByFolderAndIsActiveTrue(Folder folder); List findAllByFolderId(Integer folderId); diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/service/DataSourceService.java b/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/service/DataSourceService.java index f88361b9..34dfb9db 100644 --- a/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/service/DataSourceService.java +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/service/DataSourceService.java @@ -229,11 +229,62 @@ public int restoreMany(List ids) { } // search - @Transactional - public Page searchInArchive(Integer archiveId, DataSourceSearchCondition cond, Pageable pageable) { + /** + * Personal 검색: memberId 스코프 + */ + public Page searchByMember(int memberId, + DataSourceSearchCondition cond, + Pageable pageable) { + cond = normalizeFolder(memberId, null, cond); + return dataSourceQRepository.search(memberId, cond, pageable); + } + + /** + * Space(공유) 검색: archiveId 스코프 + */ + public Page searchByArchive(int archiveId, + DataSourceSearchCondition cond, + Pageable pageable) { + cond = normalizeFolder(null, archiveId, cond); return dataSourceQRepository.searchInArchive(archiveId, cond, pageable); } + /** + * folderId=0 -> 기본 폴더 치환 + * memberId가 있으면 Personal, archiveId가 있으면 Shared로 판단 + */ + private DataSourceSearchCondition normalizeFolder(Integer memberId, + Integer archiveId, + DataSourceSearchCondition cond) { + if (cond == null) return null; + + Integer resolvedFolderId = cond.getFolderId(); + if (resolvedFolderId != null && resolvedFolderId == 0) { + if (memberId != null) { + resolvedFolderId = folderRepository.findDefaultFolderByMemberId(memberId) + .orElseThrow(() -> new NoResultException("기본 폴더가 존재하지 않습니다.")) + .getId(); + } else if (archiveId != null) { + resolvedFolderId = folderRepository.findByArchiveIdAndIsDefaultTrue(archiveId) + .orElseThrow(() -> new NoResultException("공유 기본 폴더 없음")) + .getId(); + } else { + throw new IllegalStateException("memberId 또는 archiveId 중 하나는 필요합니다."); + } + } + + // cond를 보존하되 folderId만 치환한 새 객체로 반환 + return DataSourceSearchCondition.builder() + .title(cond.getTitle()) + .summary(cond.getSummary()) + .category(cond.getCategory()) + .keyword(cond.getKeyword()) + .folderName(cond.getFolderName()) + .isActive(cond.getIsActive()) // controller default=true, repo에서도 방어 + .folderId(resolvedFolderId) + .build(); + } + // ===== update: 공통 유틸 ===== // 이미지 유효성 검사 public void validateImage(MultipartFile image) { diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/service/PersonalDataSourceService.java b/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/service/PersonalDataSourceService.java index 30652aaf..e711257a 100644 --- a/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/service/PersonalDataSourceService.java +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/service/PersonalDataSourceService.java @@ -17,9 +17,7 @@ import org.tuna.zoopzoop.backend.domain.datasource.dto.DataSourceSearchItem; import org.tuna.zoopzoop.backend.domain.datasource.dto.UpdateOutcome; import org.tuna.zoopzoop.backend.domain.datasource.entity.Tag; -import org.tuna.zoopzoop.backend.domain.datasource.repository.DataSourceQRepository; import org.tuna.zoopzoop.backend.domain.datasource.repository.DataSourceRepository; -import org.tuna.zoopzoop.backend.global.aws.S3Service; import java.io.IOException; import java.util.*; @@ -30,13 +28,10 @@ public class PersonalDataSourceService { private final DataSourceService domain; private final DataSourceRepository dataSourceRepository; - private final DataSourceQRepository dataSourceQRepository; private final FolderRepository folderRepository; private final PersonalArchiveRepository personalArchiveRepository; private final DataProcessorService dataProcessorService; - private final S3Service s3Service; - private int getPersonalArchiveId(int memberId) { PersonalArchive pa = personalArchiveRepository.findByMemberId(memberId) .orElseThrow(() -> new NoResultException("개인 아카이브를 찾을 수 없습니다.")); @@ -191,20 +186,9 @@ public UpdateOutcome updateWithImage(int memberId, int dataSourceId, } // search - public Page search(int memberId, DataSourceSearchCondition cond, Pageable pageable) { - if (cond.getFolderId() != null && cond.getFolderId() == 0) { - int defaultFolderId = folderRepository.findDefaultFolderByMemberId(memberId) - .orElseThrow(() -> new NoResultException("기본 폴더가 존재하지 않습니다.")) - .getId(); - cond = DataSourceSearchCondition.builder() - .title(cond.getTitle()) - .summary(cond.getSummary()) - .category(cond.getCategory()) - .folderName(cond.getFolderName()) - .isActive(cond.getIsActive()) - .folderId(defaultFolderId) - .build(); - } - return dataSourceQRepository.search(memberId, cond, pageable); + public Page search(int memberId, + DataSourceSearchCondition cond, + Pageable pageable) { + return domain.searchByMember(memberId, cond, pageable); } } diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/space/archive/service/SpaceDataSourceService.java b/src/main/java/org/tuna/zoopzoop/backend/domain/space/archive/service/SpaceDataSourceService.java index 83038c12..d41b0404 100644 --- a/src/main/java/org/tuna/zoopzoop/backend/domain/space/archive/service/SpaceDataSourceService.java +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/space/archive/service/SpaceDataSourceService.java @@ -15,6 +15,7 @@ import org.tuna.zoopzoop.backend.domain.datasource.dto.DataSourceSearchItem; import org.tuna.zoopzoop.backend.domain.datasource.dto.UpdateOutcome; import org.tuna.zoopzoop.backend.domain.datasource.entity.DataSource; +import org.tuna.zoopzoop.backend.domain.datasource.entity.Tag; import org.tuna.zoopzoop.backend.domain.datasource.repository.DataSourceRepository; import org.tuna.zoopzoop.backend.domain.datasource.service.DataSourceService; import org.tuna.zoopzoop.backend.domain.space.membership.entity.Membership; @@ -22,7 +23,6 @@ import org.tuna.zoopzoop.backend.domain.space.membership.repository.MembershipRepository; import org.tuna.zoopzoop.backend.domain.space.space.entity.Space; import org.tuna.zoopzoop.backend.domain.space.space.repository.SpaceRepository; -import org.tuna.zoopzoop.backend.global.aws.S3Service; import java.util.*; @@ -36,58 +36,10 @@ public class SpaceDataSourceService { private final SpaceRepository spaceRepository; private final MembershipRepository membershipRepository; - private final S3Service s3Service; - - private Space getSpace(String raw) { - Integer spaceId; - try { - spaceId = Integer.valueOf(raw); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("유효하지 않은 spaceId 형식: " + raw); - } - return spaceRepository.findById(spaceId) - .orElseThrow(() -> new NoResultException("존재하지 않는 스페이스입니다.")); - } - - // 권한 검사 - private void assertReadable(int requesterMemberId, Space space) { - membershipRepository.findByMemberIdAndSpaceId(requesterMemberId, space.getId()) - .orElseThrow(() -> new NoResultException("스페이스 멤버가 아닙니다.")); - } - - private void assertWritable(int requesterMemberId, Space space) { - Membership ms = membershipRepository.findByMemberIdAndSpaceId(requesterMemberId, space.getId()) - .orElseThrow(() -> new NoResultException("스페이스 멤버가 아닙니다.")); - if (ms.getAuthority() == Authority.READ_ONLY) - throw new SecurityException("쓰기 권한 없음"); - } - - // 스페이스의 공유 아카이브 ID 조회 - private Integer getArchiveId(Space space) { - SharingArchive sa = space.getSharingArchive(); - if (sa == null || sa.getArchive() == null) - throw new NoResultException("공유 아카이브 미준비"); - return sa.getArchive().getId(); - } - - // 아카이브 내 폴더 ID 결정 (0 또는 null → 기본 폴더) - private int resolveTargetFolderIdByArchive(int archiveId, Integer folderIdOrZero) { - if (folderIdOrZero == null || Objects.equals(folderIdOrZero, 0)) { - return folderRepository.findByArchiveIdAndIsDefaultTrue(archiveId) - .orElseThrow(() -> new NoResultException("공유 기본 폴더 없음")) - .getId(); - } - Folder f = folderRepository.findById(folderIdOrZero) - .orElseThrow(() -> new NoResultException("존재하지 않는 폴더입니다.")); - if (!Objects.equals(f.getArchive().getId(), archiveId)) - throw new IllegalArgumentException("해당 스페이스 아카이브 소속 폴더가 아닙니다."); - return f.getId(); - } - // 불러오기(개인→공유) @Transactional - public int importFromPersonal(int requesterMemberId, String spaceIdRaw, int sourceDataSourceId, Integer targetFolderIdOrZero) { - Space space = getSpace(spaceIdRaw); + public int importFromPersonal(int requesterMemberId, String spaceId, int sourceDataSourceId, Integer targetFolderIdOrZero) { + Space space = getSpace(spaceId); assertWritable(requesterMemberId, space); Integer archiveId = getArchiveId(space); @@ -105,7 +57,7 @@ public int importFromPersonal(int requesterMemberId, String spaceIdRaw, int sour .source(source.getSource()) .category(source.getCategory()) .dataCreatedDate(source.getDataCreatedDate()) - .tags(source.getTags() == null ? null : source.getTags().stream().map(t -> t.getTagName()).toList()) + .tags(source.getTags() == null ? null : source.getTags().stream().map(Tag::getTagName).toList()) .build(); return domain.create(targetFolderId, cmd); @@ -113,11 +65,11 @@ public int importFromPersonal(int requesterMemberId, String spaceIdRaw, int sour // 불러오기(개인→공유) 다건 @Transactional - public List importManyFromPersonal(int requesterMemberId, String spaceIdRaw, List sourceIds, Integer targetFolderIdOrZero) { + public List importManyFromPersonal(int requesterMemberId, String spaceId, List sourceIds, Integer targetFolderIdOrZero) { if (sourceIds == null || sourceIds.isEmpty()) throw new IllegalArgumentException("불러올 자료 id 배열이 비어있습니다."); - Space space = getSpace(spaceIdRaw); + Space space = getSpace(spaceId); assertWritable(requesterMemberId, space); Integer archiveId = getArchiveId(space); int targetFolderId = resolveTargetFolderIdByArchive(archiveId, targetFolderIdOrZero); @@ -144,7 +96,7 @@ public List importManyFromPersonal(int requesterMemberId, String spaceI .source(src.getSource()) .category(src.getCategory()) .dataCreatedDate(src.getDataCreatedDate()) - .tags(src.getTags() == null ? null : src.getTags().stream().map(t -> t.getTagName()).toList()) + .tags(src.getTags() == null ? null : src.getTags().stream().map(Tag::getTagName).toList()) .build(); created.add(domain.create(targetFolderId, cmd)); } @@ -153,8 +105,8 @@ public List importManyFromPersonal(int requesterMemberId, String spaceI // 삭제 @Transactional - public int deleteOne(int requesterMemberId, String spaceIdRaw, int dataSourceId) { - Space space = getSpace(spaceIdRaw); + public int deleteOne(int requesterMemberId, String spaceId, int dataSourceId) { + Space space = getSpace(spaceId); assertWritable(requesterMemberId, space); Integer archiveId = getArchiveId(space); @@ -167,8 +119,8 @@ public int deleteOne(int requesterMemberId, String spaceIdRaw, int dataSourceId) // 다건 삭제 @Transactional - public void deleteMany(int requesterMemberId, String spaceIdRaw, List ids) { - Space space = getSpace(spaceIdRaw); + public void deleteMany(int requesterMemberId, String spaceId, List ids) { + Space space = getSpace(spaceId); assertWritable(requesterMemberId, space); Integer archiveId = getArchiveId(space); @@ -181,8 +133,8 @@ public void deleteMany(int requesterMemberId, String spaceIdRaw, List i // 임시 삭제 @Transactional - public int softDelete(int requesterMemberId, String spaceIdRaw, List ids) { - Space space = getSpace(spaceIdRaw); + public int softDelete(int requesterMemberId, String spaceId, List ids) { + Space space = getSpace(spaceId); assertWritable(requesterMemberId, space); Integer archiveId = getArchiveId(space); @@ -195,8 +147,8 @@ public int softDelete(int requesterMemberId, String spaceIdRaw, List id // 복원 @Transactional - public int restore(int requesterMemberId, String spaceIdRaw, List ids) { - Space space = getSpace(spaceIdRaw); + public int restore(int requesterMemberId, String spaceId, List ids) { + Space space = getSpace(spaceId); assertWritable(requesterMemberId, space); Integer archiveId = getArchiveId(space); @@ -209,8 +161,8 @@ public int restore(int requesterMemberId, String spaceIdRaw, List ids) // 이동 @Transactional - public DataSourceService.MoveResult moveOne(int requesterMemberId, String spaceIdRaw, int dataSourceId, Integer targetFolderIdOrZero) { - Space space = getSpace(spaceIdRaw); + public DataSourceService.MoveResult moveOne(int requesterMemberId, String spaceId, int dataSourceId, Integer targetFolderIdOrZero) { + Space space = getSpace(spaceId); assertWritable(requesterMemberId, space); Integer archiveId = getArchiveId(space); @@ -224,8 +176,8 @@ public DataSourceService.MoveResult moveOne(int requesterMemberId, String spaceI // 다건 이동 @Transactional - public void moveMany(int requesterMemberId, String spaceIdRaw, Integer targetFolderIdOrZero, List ids) { - Space space = getSpace(spaceIdRaw); + public void moveMany(int requesterMemberId, String spaceId, Integer targetFolderIdOrZero, List ids) { + Space space = getSpace(spaceId); assertWritable(requesterMemberId, space); Integer archiveId = getArchiveId(space); @@ -240,8 +192,8 @@ public void moveMany(int requesterMemberId, String spaceIdRaw, Integer targetFol // 수정 @Transactional - public int update(int requesterMemberId, String spaceIdRaw, int dataSourceId, DataSourceService.UpdateCmd cmd) { - Space space = getSpace(spaceIdRaw); + public int update(int requesterMemberId, String spaceId, int dataSourceId, DataSourceService.UpdateCmd cmd) { + Space space = getSpace(spaceId); assertWritable(requesterMemberId, space); Integer archiveId = getArchiveId(space); @@ -253,10 +205,10 @@ public int update(int requesterMemberId, String spaceIdRaw, int dataSourceId, Da // 이미지 포함 수정 @Transactional - public UpdateOutcome updateWithImage(int requesterMemberId, String spaceIdRaw, int dataSourceId, + public UpdateOutcome updateWithImage(int requesterMemberId, String spaceId, int dataSourceId, DataSourceService.UpdateCmd baseCmd, MultipartFile image) { - Space space = getSpace(spaceIdRaw); + Space space = getSpace(spaceId); assertWritable(requesterMemberId, space); Integer archiveId = getArchiveId(space); @@ -280,27 +232,61 @@ public UpdateOutcome updateWithImage(int requesterMemberId, String spaceIdRaw, i // 검색 - public Page search(int requesterMemberId, String spaceIdRaw, DataSourceSearchCondition cond, Pageable pageable) { - Space space = getSpace(spaceIdRaw); + public Page search(int requesterMemberId, + String spaceId, + DataSourceSearchCondition cond, + Pageable pageable) { + Space space = getSpace(spaceId); assertReadable(requesterMemberId, space); - Integer archiveId = getArchiveId(space); + int archiveId = getArchiveId(space); + return domain.searchByArchive(archiveId, cond, pageable); + } + + // ========== 내부 유틸리티 ========== + private Space getSpace(String raw) { + Integer spaceId; + try { + spaceId = Integer.valueOf(raw); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("유효하지 않은 spaceId 형식: " + raw); + } + return spaceRepository.findById(spaceId) + .orElseThrow(() -> new NoResultException("존재하지 않는 스페이스입니다.")); + } + + // 권한 검사 + private void assertReadable(int requesterMemberId, Space space) { + membershipRepository.findByMemberIdAndSpaceId(requesterMemberId, space.getId()) + .orElseThrow(() -> new NoResultException("스페이스 멤버가 아닙니다.")); + } + + private void assertWritable(int requesterMemberId, Space space) { + Membership ms = membershipRepository.findByMemberIdAndSpaceId(requesterMemberId, space.getId()) + .orElseThrow(() -> new NoResultException("스페이스 멤버가 아닙니다.")); + if (ms.getAuthority() == Authority.READ_ONLY) + throw new SecurityException("쓰기 권한 없음"); + } + + // 스페이스의 공유 아카이브 ID 조회 + private Integer getArchiveId(Space space) { + SharingArchive sa = space.getSharingArchive(); + if (sa == null || sa.getArchive() == null) + throw new NoResultException("공유 아카이브 미준비"); + return sa.getArchive().getId(); + } - // folderId=0 → default - if (cond.getFolderId() != null && cond.getFolderId() == 0) { - int defaultFolderId = folderRepository.findByArchiveIdAndIsDefaultTrue(archiveId) + // 아카이브 내 폴더 ID 결정 (0 또는 null → 기본 폴더) + private int resolveTargetFolderIdByArchive(int archiveId, Integer folderIdOrZero) { + if (folderIdOrZero == null || Objects.equals(folderIdOrZero, 0)) { + return folderRepository.findByArchiveIdAndIsDefaultTrue(archiveId) .orElseThrow(() -> new NoResultException("공유 기본 폴더 없음")) .getId(); - cond = DataSourceSearchCondition.builder() - .title(cond.getTitle()) - .summary(cond.getSummary()) - .category(cond.getCategory()) - .folderName(cond.getFolderName()) - .isActive(cond.getIsActive()) - .folderId(defaultFolderId) - .build(); } - - return domain.searchInArchive(archiveId, cond, pageable); + Folder f = folderRepository.findById(folderIdOrZero) + .orElseThrow(() -> new NoResultException("존재하지 않는 폴더입니다.")); + if (!Objects.equals(f.getArchive().getId(), archiveId)) + throw new IllegalArgumentException("해당 스페이스 아카이브 소속 폴더가 아닙니다."); + return f.getId(); } } diff --git a/src/main/java/org/tuna/zoopzoop/backend/global/clients/liveblocks/LiveblocksClient.java b/src/main/java/org/tuna/zoopzoop/backend/global/clients/liveblocks/LiveblocksClient.java index f5d1faf2..19666483 100644 --- a/src/main/java/org/tuna/zoopzoop/backend/global/clients/liveblocks/LiveblocksClient.java +++ b/src/main/java/org/tuna/zoopzoop/backend/global/clients/liveblocks/LiveblocksClient.java @@ -8,7 +8,6 @@ import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import org.tuna.zoopzoop.backend.domain.dashboard.dto.ReqBodyForLiveblocksAuth; -import org.tuna.zoopzoop.backend.domain.dashboard.dto.ResBodyForAuthToken; import java.util.Collections; import java.util.HashMap; @@ -24,7 +23,7 @@ public class LiveblocksClient { @Value("${liveblocks.secret-key}") private String secretKey; - private static final String LIVEBLOCKS_API_URL = "https://api.liveblocks.io/v2/rooms"; + private static final String LIVEBLOCKS_API_URL = "https://api.liveblocks.io/v2/rooms?idempotent"; private static final String AUTH_API_URL = "https://api.liveblocks.io/v2/authorize-user"; /** diff --git a/src/test/java/org/tuna/zoopzoop/backend/domain/archive/folder/service/FolderServiceTest.java b/src/test/java/org/tuna/zoopzoop/backend/domain/archive/folder/service/FolderServiceTest.java index 4504d04f..2ffadd48 100644 --- a/src/test/java/org/tuna/zoopzoop/backend/domain/archive/folder/service/FolderServiceTest.java +++ b/src/test/java/org/tuna/zoopzoop/backend/domain/archive/folder/service/FolderServiceTest.java @@ -204,7 +204,7 @@ void getFilesInFolder_success() { when(folderRepository.findByIdAndArchiveId(eq(400), eq(archive.getId()))) .thenReturn(Optional.of(folder)); - when(dataSourceRepository.findAllByFolder(folder)).thenReturn(List.of(d1)); + when(dataSourceRepository.findAllByFolderAndIsActiveTrue(folder)).thenReturn(List.of(d1)); FolderFilesDto dto = folderService.getFilesInFolder(archive, 400); diff --git a/src/test/java/org/tuna/zoopzoop/backend/domain/datasource/service/DataSourceServiceTest.java b/src/test/java/org/tuna/zoopzoop/backend/domain/datasource/service/DataSourceServiceTest.java index 692bf671..34add9c9 100644 --- a/src/test/java/org/tuna/zoopzoop/backend/domain/datasource/service/DataSourceServiceTest.java +++ b/src/test/java/org/tuna/zoopzoop/backend/domain/datasource/service/DataSourceServiceTest.java @@ -4,9 +4,13 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.*; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.openapitools.jackson.nullable.JsonNullable; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.test.util.ReflectionTestUtils; import org.tuna.zoopzoop.backend.domain.archive.folder.entity.Folder; @@ -18,16 +22,17 @@ import org.tuna.zoopzoop.backend.domain.datasource.entity.Tag; import org.tuna.zoopzoop.backend.domain.datasource.repository.DataSourceQRepository; import org.tuna.zoopzoop.backend.domain.datasource.repository.DataSourceRepository; -import org.openapitools.jackson.nullable.JsonNullable; import java.time.LocalDate; import java.util.List; import java.util.Optional; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class DataSourceServiceTest { @@ -189,12 +194,110 @@ void restoreMany_ok() { // ---------------------- Search In Archive ---------------------- @Test - @DisplayName("searchInArchive: QRepo 위임") - void searchInArchive_delegate() { - when(dataSourceQRepository.searchInArchive(eq(100), any(DataSourceSearchCondition.class), any(Pageable.class))) + @DisplayName("searchByMember: folderId=0 → 개인 기본 폴더로 치환 + keyword/isActive 보존 + QRepo 위임") + void searchByMember_folderZero_normalizeAndDelegate() { + // given + int memberId = 10; + when(folderRepository.findDefaultFolderByMemberId(memberId)) + .thenReturn(Optional.of(folder(111))); + + var condIn = DataSourceSearchCondition.builder() + .folderId(0) // normalize 대상 + .keyword("kw") // 보존되어야 함 + .isActive(null) // null이면 repo단에서 true로 처리(방어 로직) + .build(); + + // capture + ArgumentCaptor condCap = ArgumentCaptor.forClass(DataSourceSearchCondition.class); + when(dataSourceQRepository.search(eq(memberId), condCap.capture(), any(Pageable.class))) + .thenReturn(Page.empty()); + + // when + Page page = service.searchByMember(memberId, condIn, Pageable.ofSize(8)); + + // then + assertThat(page).isEmpty(); + DataSourceSearchCondition passed = condCap.getValue(); + assertThat(passed.getFolderId()).isEqualTo(111); // 기본 폴더로 치환됨 + assertThat(passed.getKeyword()).isEqualTo("kw"); // 보존됨 + assertThat(passed.getIsActive()).isNull(); // 그대로 전달(Repo에서 true로 방어) + verify(dataSourceQRepository).search(eq(memberId), any(), any()); + } + + @Test + @DisplayName("searchByMember: folderId 지정 시 치환 없이 그대로 전달") + void searchByMember_folderGiven_noNormalize() { + int memberId = 10; + + var condIn = DataSourceSearchCondition.builder() + .folderId(222) // 그대로 가야 함 + .isActive(Boolean.FALSE) // 비활성만 + .build(); + + ArgumentCaptor condCap = ArgumentCaptor.forClass(DataSourceSearchCondition.class); + when(dataSourceQRepository.search(eq(memberId), condCap.capture(), any(Pageable.class))) + .thenReturn(new PageImpl<>(List.of())); + + Page page = service.searchByMember(memberId, condIn, Pageable.ofSize(8)); + + assertThat(page.getContent()).isEmpty(); + DataSourceSearchCondition passed = condCap.getValue(); + assertThat(passed.getFolderId()).isEqualTo(222); // 치환 없음 + assertThat(passed.getIsActive()).isFalse(); // 보존 + } + + @Test + @DisplayName("searchByMember: 기본 폴더 없음 → NoResultException") + void searchByMember_defaultMissing_throws() { + int memberId = 10; + when(folderRepository.findDefaultFolderByMemberId(memberId)) + .thenReturn(Optional.empty()); + + var condIn = DataSourceSearchCondition.builder().folderId(0).build(); + + assertThrows(NoResultException.class, + () -> service.searchByMember(memberId, condIn, Pageable.ofSize(8))); + } + + // ---------------------- searchByArchive ---------------------- + + @Test + @DisplayName("searchByArchive: folderId=0 → 공유 기본 폴더로 치환 + 위임") + void searchByArchive_folderZero_normalizeAndDelegate() { + int archiveId = 999; + when(folderRepository.findByArchiveIdAndIsDefaultTrue(archiveId)) + .thenReturn(Optional.of(folder(333))); + + var condIn = DataSourceSearchCondition.builder() + .folderId(0) + .keyword("shared-kw") + .isActive(true) + .build(); + + ArgumentCaptor condCap = ArgumentCaptor.forClass(DataSourceSearchCondition.class); + when(dataSourceQRepository.searchInArchive(eq(archiveId), condCap.capture(), any(Pageable.class))) .thenReturn(Page.empty()); - Page page = service.searchInArchive(100, - DataSourceSearchCondition.builder().build(), Pageable.unpaged()); + + Page page = service.searchByArchive(archiveId, condIn, Pageable.ofSize(8)); + assertThat(page).isEmpty(); + DataSourceSearchCondition passed = condCap.getValue(); + assertThat(passed.getFolderId()).isEqualTo(333); // 기본 폴더로 치환됨 + assertThat(passed.getKeyword()).isEqualTo("shared-kw"); // 보존됨 + assertThat(passed.getIsActive()).isTrue(); // 보존됨 + verify(dataSourceQRepository).searchInArchive(eq(archiveId), any(), any()); + } + + @Test + @DisplayName("searchByArchive: 기본 폴더 없음 → NoResultException") + void searchByArchive_defaultMissing_throws() { + int archiveId = 999; + when(folderRepository.findByArchiveIdAndIsDefaultTrue(archiveId)) + .thenReturn(Optional.empty()); + + var condIn = DataSourceSearchCondition.builder().folderId(0).build(); + + assertThrows(NoResultException.class, + () -> service.searchByArchive(archiveId, condIn, Pageable.ofSize(8))); } } diff --git a/src/test/java/org/tuna/zoopzoop/backend/domain/datasource/service/PersonalArchiveDataSourceServiceTest.java b/src/test/java/org/tuna/zoopzoop/backend/domain/datasource/service/PersonalArchiveDataSourceServiceTest.java index 646a458d..c772912e 100644 --- a/src/test/java/org/tuna/zoopzoop/backend/domain/datasource/service/PersonalArchiveDataSourceServiceTest.java +++ b/src/test/java/org/tuna/zoopzoop/backend/domain/datasource/service/PersonalArchiveDataSourceServiceTest.java @@ -12,8 +12,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.test.util.ReflectionTestUtils; import org.tuna.zoopzoop.backend.domain.archive.archive.entity.Archive; -import org.tuna.zoopzoop.backend.domain.archive.archive.entity.PersonalArchive; -import org.tuna.zoopzoop.backend.domain.archive.archive.repository.PersonalArchiveRepository; import org.tuna.zoopzoop.backend.domain.archive.folder.entity.Folder; import org.tuna.zoopzoop.backend.domain.archive.folder.repository.FolderRepository; import org.tuna.zoopzoop.backend.domain.datasource.dataprocessor.service.DataProcessorService; @@ -22,7 +20,6 @@ import org.tuna.zoopzoop.backend.domain.datasource.dto.DataSourceSearchItem; import org.tuna.zoopzoop.backend.domain.datasource.entity.Category; import org.tuna.zoopzoop.backend.domain.datasource.entity.DataSource; -import org.tuna.zoopzoop.backend.domain.datasource.repository.DataSourceQRepository; import org.tuna.zoopzoop.backend.domain.datasource.repository.DataSourceRepository; import java.io.IOException; @@ -40,10 +37,8 @@ class PersonalDataSourceServiceTest { @Mock private DataSourceService domain; @Mock private DataSourceRepository dataSourceRepository; - @Mock private DataSourceQRepository dataSourceQRepository; @Mock private FolderRepository folderRepository; @Mock private DataProcessorService dataProcessorService; - @Mock private PersonalArchiveRepository personalArchiveRepository; @InjectMocks private PersonalDataSourceService app; @@ -52,7 +47,7 @@ private Folder folder(int id, Archive a, boolean def) { Folder f = new Folder(); ReflectionTestUtils.setField(f, "id", id); f.setArchive(a); f.setDefault(def); return f; } - private PersonalArchive pa(Archive a) { PersonalArchive p = new PersonalArchive(); p.setArchive(a); return p; } +// private PersonalArchive pa(Archive a) { PersonalArchive p = new PersonalArchive(); p.setArchive(a); return p; } // ---------------------- Create ---------------------- @Test @@ -170,19 +165,25 @@ void restore_ok() { // ---------------------- Search ---------------------- @Test @DisplayName("search: folderId=0 → default 치환 후 QRepo.search 호출") - void search_defaultFolderId() { +// @DisplayName("search: Personal 서비스는 도메인으로 위임한다") + void search_delegateToDomain() { int memberId = 7; - Archive a = archive(100); - Folder df = folder(55, a, true); - when(folderRepository.findDefaultFolderByMemberId(memberId)).thenReturn(Optional.of(df)); - when(dataSourceQRepository.search(eq(memberId), any(DataSourceSearchCondition.class), any(Pageable.class))) + // given: 위임 결과를 미리 스텁 + when(domain.searchByMember(eq(memberId), any(DataSourceSearchCondition.class), any(Pageable.class))) .thenReturn(Page.empty()); - var cond = DataSourceSearchCondition.builder().folderId(0).build(); + var cond = DataSourceSearchCondition.builder() + .folderId(0) + .keyword("kw") + .build(); + + // when Page page = app.search(memberId, cond, Pageable.unpaged()); + // then assertThat(page).isEmpty(); - verify(dataSourceQRepository).search(eq(memberId), argThat(c -> c.getFolderId() == 55), any(Pageable.class)); + verify(domain).searchByMember(eq(memberId), any(DataSourceSearchCondition.class), any(Pageable.class)); } + }