Skip to content

Commit 2300eea

Browse files
committed
fix: 실행목표편집 로직 수정 및 테스트 코드 작성
1 parent b665138 commit 2300eea

3 files changed

Lines changed: 169 additions & 30 deletions

File tree

layer-api/src/main/java/org/layer/domain/actionItem/controller/dto/request/ActionItemUpdateRequest.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,12 @@ public record ActionItemUpdateRequest(@NotNull
1111
@Schema(description = "실행 목표 리스트")
1212
List<ActionItemUpdateElementRequest> actionItems) {
1313

14-
@Getter
15-
public static class ActionItemUpdateElementRequest {
16-
@Schema(description = "실행 목표 id")
17-
@NotNull
18-
Long id;
14+
public record ActionItemUpdateElementRequest(
15+
@Schema(description = "실행 목표 id (신규 생성시 null)")
16+
Long id,
1917

2018
@Schema(description = "변경된 실행 목표 내용")
2119
@NotNull
22-
String content;
23-
}
20+
String content
21+
) {}
2422
}

layer-api/src/main/java/org/layer/domain/actionItem/service/ActionItemService.java

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -231,34 +231,51 @@ public MemberActionItemGetResponse getMemberActionItemList(Long currentMemberId)
231231
//== 실행 목표 수정 ==//
232232
@Transactional
233233
public void updateActionItems(Long memberId, Long retrospectId, ActionItemUpdateRequest updateDto) {
234-
// 실행 목표 가져오기
235-
List<ActionItem> actionItems = actionItemRepository.findAllByRetrospectId(retrospectId);
236-
237-
// 리더인지 검증
234+
// 1. 리더 및 권한 검증
238235
Retrospect retrospect = retrospectRepository.findByIdOrThrow(retrospectId);
239236
Space space = spaceRepository.findByIdOrThrow(retrospect.getSpaceId());
240237
space.isLeaderSpace(memberId);
241238

242-
// 요청 리스트와 DB에 저장된 실행 목표 개수가 다를 때
243-
if (updateDto.actionItems().size() != actionItems.size()) {
244-
throw new ActionItemException(INVALID_ACTION_ITEM_LIST);
245-
}
246-
247-
248-
// O(1) 접근을 위해서 map으로 변경
249-
Map<Long, ActionItem> actionItemMap = actionItems.stream().collect(Collectors.toMap(
250-
ActionItem::getId,
251-
actionItem -> actionItem
252-
));
253-
254-
AtomicInteger order = new AtomicInteger(1);
255-
for (ActionItemUpdateRequest.ActionItemUpdateElementRequest updateItem : updateDto.actionItems()) {
256-
ActionItem actionItem = actionItemMap.getOrDefault(updateItem.getId(), null);
257-
if (actionItem == null) {
258-
throw new ActionItemException(INVALID_ACTION_ITEM_ID);
239+
// 2. DB에 저장된 기존 실행 목표 가져오기
240+
List<ActionItem> dbActionItems = actionItemRepository.findAllByRetrospectId(retrospectId);
241+
242+
// 3. 요청 데이터에서 ID 추출 (Update 대상 식별용)
243+
Set<Long> requestIds = updateDto.actionItems().stream()
244+
.map(ActionItemUpdateRequest.ActionItemUpdateElementRequest::id)
245+
.filter(Objects::nonNull) // ID가 있는 것만 (신규 생성 제외)
246+
.collect(Collectors.toSet());
247+
248+
// 4. [DELETE] 요청 리스트에 없는 DB 항목 삭제
249+
// (DB에는 있는데 요청 ID 목록에는 포함되지 않은 것들을 찾아서 삭제)
250+
List<ActionItem> itemsToDelete = dbActionItems.stream()
251+
.filter(item -> !requestIds.contains(item.getId()))
252+
.toList();
253+
actionItemRepository.deleteAll(itemsToDelete);
254+
255+
// 5. [UPDATE & CREATE] 요청 리스트 순서대로 처리
256+
// 빠른 접근을 위해 DB 데이터를 Map으로 변환
257+
Map<Long, ActionItem> actionItemMap = dbActionItems.stream()
258+
.collect(Collectors.toMap(ActionItem::getId, item -> item));
259+
260+
int order = 1;
261+
262+
for (ActionItemUpdateRequest.ActionItemUpdateElementRequest requestItem : updateDto.actionItems()) {
263+
if (requestItem.id() != null && actionItemMap.containsKey(requestItem.id())) {
264+
// 5-1. [UPDATE] 기존 아이템 내용 및 순서 갱신
265+
ActionItem actionItem = actionItemMap.get(requestItem.id());
266+
actionItem.updateContent(requestItem.content());
267+
actionItem.updateActionItemOrder(order++);
268+
} else {
269+
// 5-2. [CREATE] ID가 없거나 DB에 없는 ID인 경우 신규 생성
270+
ActionItem newActionItem = ActionItem.builder()
271+
.retrospectId(retrospectId)
272+
.spaceId(space.getId())
273+
.memberId(memberId)
274+
.content(requestItem.content())
275+
.actionItemOrder(order++)
276+
.build();
277+
actionItemRepository.save(newActionItem);
259278
}
260-
actionItem.updateContent(updateItem.getContent());
261-
actionItem.updateActionItemOrder(order.getAndIncrement());
262279
}
263280
}
264281
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package org.layer.domain.actionItem.service;
2+
3+
import java.util.Comparator;
4+
import java.util.List;
5+
6+
import org.assertj.core.api.Assertions;
7+
import org.junit.jupiter.api.DisplayName;
8+
import org.junit.jupiter.api.Nested;
9+
import org.junit.jupiter.api.Test;
10+
import org.layer.domain.actionItem.controller.dto.request.ActionItemUpdateRequest;
11+
import org.layer.domain.actionItem.entity.ActionItem;
12+
import org.layer.domain.actionItem.repository.ActionItemRepository;
13+
import org.layer.domain.fixture.MemberFixture;
14+
import org.layer.domain.fixture.RetrospectFixture;
15+
import org.layer.domain.fixture.SpaceFixture;
16+
import org.layer.domain.member.entity.Member;
17+
import org.layer.domain.member.repository.MemberRepository;
18+
import org.layer.domain.retrospect.entity.AnalysisStatus;
19+
import org.layer.domain.retrospect.entity.Retrospect;
20+
import org.layer.domain.retrospect.entity.RetrospectStatus;
21+
import org.layer.domain.retrospect.repository.RetrospectRepository;
22+
import org.layer.domain.space.entity.MemberSpaceRelation;
23+
import org.layer.domain.space.entity.Space;
24+
import org.layer.domain.space.repository.MemberSpaceRelationRepository;
25+
import org.layer.domain.space.repository.SpaceRepository;
26+
import org.springframework.beans.factory.annotation.Autowired;
27+
import org.springframework.boot.test.context.SpringBootTest;
28+
import org.springframework.test.context.ActiveProfiles;
29+
import org.springframework.transaction.annotation.Transactional;
30+
31+
@SpringBootTest
32+
@ActiveProfiles("test")
33+
@Transactional
34+
public class ActionItemServiceTest {
35+
36+
@Autowired
37+
private ActionItemService actionItemService;
38+
39+
@Autowired
40+
private ActionItemRepository actionItemRepository;
41+
42+
@Autowired
43+
private RetrospectRepository retrospectRepository;
44+
45+
@Autowired
46+
private MemberRepository memberRepository;
47+
48+
@Autowired
49+
private SpaceRepository spaceRepository;
50+
51+
@Autowired
52+
private MemberSpaceRelationRepository memberSpaceRelationRepository;
53+
54+
@Nested
55+
class 실행_목표_수정 {
56+
57+
@Test
58+
@DisplayName("요청 리스트에 따라 기존 항목 삭제, 내용 수정, 신규 생성이 수행되고 순서가 재정렬된다.")
59+
void updateActionItems_Sync_Test() {
60+
// given
61+
// 1. 리더 멤버 및 스페이스 생성
62+
Member leader = memberRepository.save(MemberFixture.createFixture("social-leader"));
63+
Space space = spaceRepository.save(SpaceFixture.createFixture(leader.getId(), 1L));
64+
memberSpaceRelationRepository.save(new MemberSpaceRelation(leader.getId(), space));
65+
66+
// 2. 회고 생성
67+
Retrospect retrospect = retrospectRepository.save(RetrospectFixture.createFixture(
68+
space.getId(), RetrospectStatus.PROCEEDING, AnalysisStatus.NOT_STARTED, null
69+
));
70+
71+
// 3. [DB 상태] 기존 실행 목표 3개 저장 (A, B, C)
72+
ActionItem itemA = actionItemRepository.save(createActionItem(retrospect, space, leader, "목표 A", 1));
73+
ActionItem itemB = actionItemRepository.save(createActionItem(retrospect, space, leader, "목표 B", 2));
74+
ActionItem itemC = actionItemRepository.save(createActionItem(retrospect, space, leader, "목표 C", 3));
75+
76+
// 4. [요청 생성] A, C 삭제 / B 수정 / D 신규 생성
77+
// 기대 결과 순서: 1. B(수정됨) -> 2. D(신규)
78+
List<ActionItemUpdateRequest.ActionItemUpdateElementRequest> updateElements = List.of(
79+
// B 수정 (ID 유지)
80+
new ActionItemUpdateRequest.ActionItemUpdateElementRequest(itemB.getId(), "목표 B 수정"),
81+
// D 생성 (ID null)
82+
new ActionItemUpdateRequest.ActionItemUpdateElementRequest(null, "목표 D 신규")
83+
);
84+
ActionItemUpdateRequest request = new ActionItemUpdateRequest(updateElements);
85+
86+
// when
87+
// (서비스 메서드 호출)
88+
actionItemService.updateActionItems(leader.getId(), retrospect.getId(), request);
89+
90+
// then
91+
List<ActionItem> results = actionItemRepository.findAllByRetrospectId(retrospect.getId());
92+
93+
// 1. 개수 검증 (A, C 삭제, B 유지, D 생성 -> 총 2개)
94+
Assertions.assertThat(results).hasSize(2);
95+
96+
// 순서대로 정렬하여 검증
97+
results.sort(Comparator.comparingInt(ActionItem::getActionItemOrder));
98+
ActionItem firstItem = results.get(0);
99+
ActionItem secondItem = results.get(1);
100+
101+
// 2. 첫 번째 아이템 검증 (B 수정 확인)
102+
Assertions.assertThat(firstItem.getId()).isEqualTo(itemB.getId());
103+
Assertions.assertThat(firstItem.getContent()).isEqualTo("목표 B 수정");
104+
Assertions.assertThat(firstItem.getActionItemOrder()).isEqualTo(1);
105+
106+
// 3. 두 번째 아이템 검증 (D 생성 확인)
107+
Assertions.assertThat(secondItem.getId()).isNotEqualTo(itemA.getId()); // A가 아님
108+
Assertions.assertThat(secondItem.getId()).isNotEqualTo(itemC.getId()); // C가 아님
109+
Assertions.assertThat(secondItem.getContent()).isEqualTo("목표 D 신규");
110+
Assertions.assertThat(secondItem.getActionItemOrder()).isEqualTo(2);
111+
}
112+
113+
// 테스트용 헬퍼 메서드 (Fixture가 없다면 간편하게 사용)
114+
private ActionItem createActionItem(Retrospect retrospect, Space space, Member member, String content, int order) {
115+
return ActionItem.builder()
116+
.retrospectId(retrospect.getId())
117+
.spaceId(space.getId())
118+
.memberId(member.getId())
119+
.content(content)
120+
.actionItemOrder(order)
121+
.build();
122+
}
123+
}
124+
}

0 commit comments

Comments
 (0)