Skip to content

Commit a45462c

Browse files
committed
refactor/OPS-360 : default 폴더 CRUD 로직 수정
1 parent 23ff02e commit a45462c

File tree

5 files changed

+103
-43
lines changed

5 files changed

+103
-43
lines changed

src/main/java/org/tuna/zoopzoop/backend/domain/archive/archive/entity/Archive.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class Archive extends BaseEntity {
2222
private ArchiveType archiveType;
2323

2424
//아카이브 삭제(아마도 계정 탈퇴) 시 폴더 일괄 삭제
25-
@OneToMany(mappedBy = "archive", cascade = CascadeType.REMOVE, orphanRemoval = true)
25+
@OneToMany(mappedBy = "archive", cascade = CascadeType.ALL, orphanRemoval = true)
2626
private List<Folder> folders = new ArrayList<>();
2727

2828
public Archive(ArchiveType archiveType) {

src/main/java/org/tuna/zoopzoop/backend/domain/archive/folder/controller/FolderController.java

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import org.tuna.zoopzoop.backend.global.rsData.RsData;
1717
import org.tuna.zoopzoop.backend.global.security.jwt.CustomUserDetails;
1818

19-
import java.util.HashMap;
2019
import java.util.List;
2120
import java.util.Map;
2221

@@ -54,20 +53,26 @@ public RsData<resBodyForCreateFolder> createFolder(
5453
* 내 PersonalArchive 안의 folder 삭제
5554
* @param folderId 삭제할 folderId
5655
*/
57-
@Operation(summary = "폴더 삭제", description = "내 PersonalArchive 안에 폴더를 삭제합니다.")
5856
@DeleteMapping("/{folderId}")
5957
public ResponseEntity<Map<String, Object>> deleteFolder(
6058
@PathVariable Integer folderId,
6159
@AuthenticationPrincipal CustomUserDetails userDetails
6260
) {
61+
if (folderId == 0) {
62+
var body = new java.util.HashMap<String, Object>();
63+
body.put("status", 400);
64+
body.put("msg", "default 폴더는 삭제할 수 없습니다.");
65+
body.put("data", null); // HashMap은 null 허용
66+
return ResponseEntity.badRequest().body(body);
67+
}
68+
6369
Member member = userDetails.getMember();
6470
String deletedFolderName = folderService.deleteFolder(member.getId(), folderId);
6571

66-
Map<String, Object> body = new HashMap<>();
72+
var body = new java.util.HashMap<String, Object>();
6773
body.put("status", 200);
6874
body.put("msg", deletedFolderName + " 폴더가 삭제됐습니다.");
69-
body.put("data", null);
70-
75+
body.put("data", null); // <- 여기도 Map.of 쓰면 NPE 납니다
7176
return ResponseEntity.ok(body);
7277
}
7378

@@ -76,23 +81,29 @@ public ResponseEntity<Map<String, Object>> deleteFolder(
7681
* @param folderId 수정할 폴더 Id
7782
* @param body 수정할 폴더 값
7883
*/
79-
@Operation(summary = "폴더 수정", description = "내 PersonalArchive 안에 폴더를 수정합니다.")
8084
@PatchMapping("/{folderId}")
8185
public ResponseEntity<Map<String, Object>> updateFolderName(
8286
@PathVariable Integer folderId,
8387
@RequestBody Map<String, String> body,
8488
@AuthenticationPrincipal CustomUserDetails userDetails
8589
) {
90+
if (folderId == 0) {
91+
var res = new java.util.HashMap<String, Object>();
92+
res.put("status", 400);
93+
res.put("msg", "default 폴더는 이름을 변경할 수 없습니다.");
94+
res.put("data", null);
95+
return ResponseEntity.badRequest().body(res);
96+
}
97+
8698
Member member = userDetails.getMember();
8799
String newName = body.get("folderName");
88100
String updatedName = folderService.updateFolderName(member.getId(), folderId, newName);
89101

90-
Map<String, Object> response = new HashMap<>();
91-
response.put("status", 200);
92-
response.put("msg", "폴더 이름이 " + updatedName + " 으로 변경됐습니다.");
93-
response.put("data", Map.of("folderName", updatedName));
94-
95-
return ResponseEntity.ok(response);
102+
return ResponseEntity.ok(java.util.Map.of(
103+
"status", 200,
104+
"msg", "폴더 이름이 " + updatedName + " 으로 변경됐습니다.",
105+
"data", java.util.Map.of("folderName", updatedName)
106+
));
96107
}
97108

98109
/**
@@ -117,30 +128,32 @@ public ResponseEntity<?> getFolders(
117128
}
118129

119130
/**
120-
* 폴더(내 PersonalArchive 소속) 안의 파일 목록 조회
131+
* 폴더 안의 파일 목록 조회
121132
*/
122-
@Operation(summary = "폴더 내 파일 목록 조회", description = "내 PersonalArchive의 폴더 속 파일 목록을 조회합니다.")
123133
@GetMapping("/{folderId}/files")
124134
public ResponseEntity<?> getFilesInFolder(
125135
@PathVariable Integer folderId,
126136
@AuthenticationPrincipal CustomUserDetails userDetails
127137
) {
128-
Member member = userDetails.getMember();
129-
FolderFilesDto rs = folderService.getFilesInFolderForPersonal(member.getId(), folderId);
130-
131-
return ResponseEntity.ok(
132-
Map.of(
133-
"status", 200,
134-
"msg", "해당 폴더의 파일 목록을 불러왔습니다.",
135-
"data", Map.of(
136-
"folder", Map.of(
137-
"folderId", rs.folderId(),
138-
"folderName", rs.folderName()
139-
),
140-
"files", rs.files()
141-
)
138+
int memberId = userDetails.getMember().getId();
139+
140+
Integer targetFolderId = (folderId == 0)
141+
? folderService.getDefaultFolderId(memberId)
142+
: folderId;
143+
144+
FolderFilesDto rs = folderService.getFilesInFolderForPersonal(memberId, targetFolderId);
145+
146+
return ResponseEntity.ok(Map.of(
147+
"status", 200,
148+
"msg", folderId == 0 ? "기본 폴더의 파일 목록을 불러왔습니다." : "해당 폴더의 파일 목록을 불러왔습니다.",
149+
"data", Map.of(
150+
"folder", Map.of(
151+
"folderId", rs.folderId(),
152+
"folderName", rs.folderName()
153+
),
154+
"files", rs.files()
142155
)
143-
);
156+
));
144157
}
145158

146159
}

src/main/java/org/tuna/zoopzoop/backend/domain/archive/folder/service/FolderService.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,14 @@ public FolderFilesDto getFilesInFolderForPersonal(Integer memberId, Integer fold
192192
return new FolderFilesDto(folder.getId(), folder.getName(), files);
193193
}
194194

195+
196+
public Integer getDefaultFolderId(int memberId) {
197+
Folder folder = folderRepository.findDefaultFolderByMemberId(memberId)
198+
.orElseThrow(() -> new NoResultException("default 폴더를 찾을 수 없습니다."));
199+
return folder.getId();
200+
201+
}
202+
195203
/**
196204
* 입력된 폴더명을 (폴더명, 숫자)로 분리하는 유틸 클래스
197205
* “폴더명” → (”폴더명”, null)

src/test/java/org/tuna/zoopzoop/backend/domain/archive/folder/controller/FolderControllerTest.java

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,17 @@
2222
import org.tuna.zoopzoop.backend.domain.member.enums.Provider;
2323
import org.tuna.zoopzoop.backend.domain.member.repository.MemberRepository;
2424
import org.tuna.zoopzoop.backend.domain.member.service.MemberService;
25+
import org.tuna.zoopzoop.backend.global.jpa.entity.BaseEntity;
2526

2627
import java.time.LocalDate;
2728
import java.util.List;
2829

2930
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
31+
import static org.hamcrest.Matchers.nullValue;
3032
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
3133
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
3234

33-
/**
34-
* FolderController 통합 테스트 (Given / When / Then 주석 유지)
35-
*
36-
* - @SpringBootTest + @AutoConfigureMockMvc 로 전체 컨텍스트에서 테스트
37-
* - @WithUserDetails 를 사용해 인증 principal 을 주입
38-
* - 테스트용 멤버는 BeforeAll에서 생성 (UserDetailsService 가 해당 username 으로 로드 가능해야 함)
39-
*/
35+
4036
@ActiveProfiles("test")
4137
@SpringBootTest
4238
@AutoConfigureMockMvc
@@ -61,14 +57,12 @@ class FolderControllerTest {
6157

6258
@BeforeAll
6359
void beforeAll() {
64-
// WithUserDetails가 SecurityContext 생성 시 DB에서 사용자를 조회하므로 미리 생성
6560
try {
6661
memberService.createMember("folderTester", "http://example.com/profile.png", TEST_PROVIDER_KEY, Provider.KAKAO);
6762
} catch (Exception ignored) {}
6863

69-
// 준비된 멤버 ID
7064
testMemberId = memberRepository.findByProviderAndProviderKey(Provider.KAKAO, TEST_PROVIDER_KEY)
71-
.map(m -> m.getId())
65+
.map(BaseEntity::getId)
7266
.orElseThrow();
7367

7468
// GIVEN: 테스트용 폴더 및 샘플 자료 준비 (docs 폴더 + 2개 자료)
@@ -77,7 +71,7 @@ void beforeAll() {
7771

7872
Folder docsFolder = folderRepository.findById(docsFolderId).orElseThrow();
7973

80-
// 자료 2건 생성 — **category는 NOT NULL enum** 이므로 반드시 설정
74+
// 자료 2건 생성
8175
DataSource d1 = new DataSource();
8276
d1.setFolder(docsFolder);
8377
d1.setTitle("spec.pdf");
@@ -143,6 +137,8 @@ void createFolder_missingName() throws Exception {
143137
.andExpect(status().isBadRequest());
144138
}
145139

140+
141+
// DeleteFile
146142
@Test
147143
@DisplayName("개인 아카이브 폴더 삭제 - 성공 시 200과 삭제 메시지 반환")
148144
@WithUserDetails("KAKAO:sc1111")
@@ -158,6 +154,17 @@ void deleteFolder_ok() throws Exception {
158154
.andExpect(jsonPath("$.msg").value("todelete 폴더가 삭제됐습니다."));
159155
}
160156

157+
@Test
158+
@DisplayName("개인 아카이브 폴더 삭제 실패- 기본 폴더면 400")
159+
@WithUserDetails("KAKAO:sc1111")
160+
void deleteDefaultFolder_badRequest() throws Exception {
161+
mockMvc.perform(delete("/api/v1/archive/folder/{folderId}", 0))
162+
.andExpect(status().isBadRequest())
163+
.andExpect(jsonPath("$.status").value(400))
164+
.andExpect(jsonPath("$.msg").value("default 폴더는 삭제할 수 없습니다."))
165+
.andExpect(jsonPath("$.data").value(nullValue()));
166+
}
167+
161168
@Test
162169
@DisplayName("개인 아카이브 폴더 삭제 - 존재하지 않으면 404")
163170
@WithUserDetails("KAKAO:sc1111")
@@ -169,6 +176,7 @@ void deleteFolder_notFound() throws Exception {
169176
.andExpect(jsonPath("$.msg").value("존재하지 않는 폴더입니다."));
170177
}
171178

179+
172180
// UpdateFile
173181
@Test
174182
@DisplayName("개인 아카이브 폴더 이름 변경 - 성공 시 200과 변경된 이름 반환")
@@ -191,6 +199,22 @@ void updateFolder_ok() throws Exception {
191199
.andExpect(jsonPath("$.data.folderName").value("회의록"));
192200
}
193201

202+
@Test
203+
@DisplayName("개인 아카이브 폴더 이름 변경 실패 - 기본 폴더면 400")
204+
@WithUserDetails("KAKAO:sc1111")
205+
void updateDefaultFolder_badRequest() throws Exception {
206+
var body = new java.util.HashMap<String,String>();
207+
body.put("folderName","무시됨");
208+
209+
mockMvc.perform(patch("/api/v1/archive/folder/{folderId}", 0)
210+
.contentType(MediaType.APPLICATION_JSON)
211+
.content(objectMapper.writeValueAsString(body)))
212+
.andExpect(status().isBadRequest())
213+
.andExpect(jsonPath("$.status").value(400))
214+
.andExpect(jsonPath("$.msg").value("default 폴더는 이름을 변경할 수 없습니다."))
215+
.andExpect(jsonPath("$.data").value(nullValue()));
216+
}
217+
194218
@Test
195219
@DisplayName("개인 아카이브 폴더 이름 변경 - 존재하지 않는 폴더면 404")
196220
@WithUserDetails("KAKAO:sc1111")
@@ -245,6 +269,21 @@ void getFilesInFolder_success() throws Exception {
245269
.andExpect(jsonPath("$.data.files[0].imageUrl").isString());
246270
}
247271

272+
@Test
273+
@DisplayName("폴더 파일 목록 조회 - default 폴더 성공")
274+
@WithUserDetails("KAKAO:sc1111")
275+
void getFilesInDefaultFolder_success() throws Exception {
276+
mockMvc.perform(get("/api/v1/archive/folder/{folderId}/files", 0)
277+
.accept(MediaType.APPLICATION_JSON))
278+
.andExpect(status().isOk())
279+
.andExpect(jsonPath("$.status").value(200))
280+
.andExpect(jsonPath("$.msg").value("기본 폴더의 파일 목록을 불러왔습니다."))
281+
.andExpect(jsonPath("$.data.folder.folderId").isNumber())
282+
.andExpect(jsonPath("$.data.folder.folderName").isString())
283+
.andExpect(jsonPath("$.data.files").isArray());
284+
}
285+
286+
248287
@Test
249288
@DisplayName("폴더 내 파일 목록 조회 - 폴더가 없으면 404")
250289
@WithUserDetails("KAKAO:sc1111")

src/test/java/org/tuna/zoopzoop/backend/domain/archive/folder/service/FolderServiceTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ void createFolder_memberNotFound() {
125125
when(memberRepository.findById(2)).thenReturn(Optional.empty());
126126

127127
// when & then
128-
assertThrows(IllegalArgumentException.class,
128+
assertThrows(NoResultException.class,
129129
() -> folderService.createFolderForPersonal(2, "보고서"));
130130
}
131131

@@ -292,7 +292,7 @@ void getFilesInFolderForPersonal_success() {
292292

293293
// then
294294
assertThat(dto.files()).hasSize(2);
295-
FileSummary f0 = dto.files().get(0);
295+
FileSummary f0 = dto.files().getFirst();
296296
assertThat(f0.dataSourceId()).isEqualTo(10);
297297
assertThat(f0.title()).isEqualTo("spec.pdf");
298298
assertThat(f0.summary()).isEqualTo("요약 A");

0 commit comments

Comments
 (0)