Skip to content

Commit ad30807

Browse files
authored
Merge pull request #237 from prgrms-web-devcourse-final-project/feat/22-post
Feat[post]: 진행중, 마감 페이징 추가
2 parents 3c6185f + c70179f commit ad30807

File tree

8 files changed

+180
-105
lines changed

8 files changed

+180
-105
lines changed

backend/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ dependencies {
8585
// Testing (테스트)
8686
testImplementation 'org.springframework.boot:spring-boot-starter-test'
8787
testImplementation 'org.springframework.security:spring-security-test'
88+
testImplementation 'org.mockito:mockito-inline:5.2.0'
8889
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
8990
implementation("it.ozimov:embedded-redis:0.7.3") {
9091
exclude group: "org.slf4j", module: "slf4j-simple"

backend/src/main/java/com/ai/lawyer/domain/poll/controller/PollController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ public ResponseEntity<ApiResponse<List<PollDto>>> getTopOngoingPolls(@RequestPar
149149
return ResponseEntity.ok(new ApiResponse<>(200, message, polls));
150150
}
151151

152-
@Operation(summary = "index(순번)로 투표하기 - Swagger 편의용")
153-
@PostMapping("/{pollId}/vote-by-index")
152+
@Operation(summary = "index(순번)로 투표하기")
153+
@PostMapping("/{pollId}/voting")
154154
public ResponseEntity<ApiResponse<PollVoteDto>> voteByIndex(@PathVariable Long pollId, @RequestParam int index) {
155155
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
156156
Long memberId = Long.parseLong(authentication.getName());

backend/src/main/java/com/ai/lawyer/domain/post/controller/PostController.java

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,12 @@
22

33
import com.ai.lawyer.domain.post.dto.*;
44
import com.ai.lawyer.domain.post.service.PostService;
5-
import com.ai.lawyer.domain.member.entity.Member;
65
import com.ai.lawyer.domain.member.repositories.MemberRepository;
76
import com.ai.lawyer.global.jwt.TokenProvider;
87
import com.ai.lawyer.global.response.ApiResponse;
98
import io.swagger.v3.oas.annotations.Operation;
109
import io.swagger.v3.oas.annotations.tags.Tag;
11-
import jakarta.servlet.http.HttpServletRequest;
12-
import lombok.AllArgsConstructor;
13-
import lombok.Data;
1410
import lombok.RequiredArgsConstructor;
15-
import org.springframework.beans.factory.annotation.Autowired;
1611
import org.springframework.http.ResponseEntity;
1712
import org.springframework.web.bind.annotation.*;
1813
import org.springframework.web.server.ResponseStatusException;
@@ -66,7 +61,7 @@ public ResponseEntity<ApiResponse<List<PostDetailDto>>> getAllPosts() {
6661
}
6762

6863
@Operation(summary = "게시글 간편 전체 조회")
69-
@GetMapping("/simple")
64+
@GetMapping("/simplePost")
7065
public ResponseEntity<ApiResponse<List<PostSimpleDto>>> getAllSimplePosts() {
7166
List<PostSimpleDto> posts = postService.getAllSimplePosts();
7267
return ResponseEntity.ok(new ApiResponse<>(200, "게시글 간편 전체 조회 성공", posts));
@@ -153,7 +148,7 @@ public ResponseEntity<ApiResponse<List<PostDto>>> getMyPosts() {
153148
}
154149

155150
@Operation(summary = "게시글+투표 동시 등록")
156-
@PostMapping("/with-poll")
151+
@PostMapping("/createPost")
157152
public ResponseEntity<ApiResponse<PostDetailDto>> createPostWithPoll(@RequestBody PostWithPollCreateDto dto) {
158153
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
159154
Object principal = authentication.getPrincipal();
@@ -171,7 +166,7 @@ public ResponseEntity<ApiResponse<PostDetailDto>> createPostWithPoll(@RequestBod
171166

172167
@Operation(summary = "게시글 페이징 조회")
173168
@GetMapping("/paged")
174-
public ResponseEntity<ApiResponse<PostPageDTO>> getPostsPaged(
169+
public ResponseEntity<ApiResponse<PostPageDto>> getPostsPaged(
175170
@RequestParam(defaultValue = "0") int page,
176171
@RequestParam(defaultValue = "10") int size
177172
) {
@@ -180,7 +175,37 @@ public ResponseEntity<ApiResponse<PostPageDTO>> getPostsPaged(
180175
if (posts == null) {
181176
posts = new org.springframework.data.domain.PageImpl<>(java.util.Collections.emptyList(), pageable, 0);
182177
}
183-
PostPageDTO response = new PostPageDTO(posts);
178+
PostPageDto response = new PostPageDto(posts);
184179
return ResponseEntity.ok(new ApiResponse<>(200, "페이징 게시글 조회 성공", response));
185180
}
181+
182+
@Operation(summary = "진행중 투표 게시글 페이징 조회")
183+
@GetMapping("/ongoingPaged")
184+
public ResponseEntity<ApiResponse<PostPageDto>> getOngoingPostsPaged(
185+
@RequestParam(defaultValue = "0") int page,
186+
@RequestParam(defaultValue = "10") int size
187+
) {
188+
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
189+
Page<PostDto> posts = postService.getOngoingPostsPaged(pageable);
190+
if (posts == null) {
191+
posts = new org.springframework.data.domain.PageImpl<>(java.util.Collections.emptyList(), pageable, 0);
192+
}
193+
PostPageDto response = new PostPageDto(posts);
194+
return ResponseEntity.ok(new ApiResponse<>(200, "진행중 투표 게시글 페이징 조회 성공", response));
195+
}
196+
197+
@Operation(summary = "마감 투표 게시글 페이징 조회")
198+
@GetMapping("/closedPaged")
199+
public ResponseEntity<ApiResponse<PostPageDto>> getClosedPostsPaged(
200+
@RequestParam(defaultValue = "0") int page,
201+
@RequestParam(defaultValue = "10") int size
202+
) {
203+
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
204+
Page<PostDto> posts = postService.getClosedPostsPaged(pageable);
205+
if (posts == null) {
206+
posts = new org.springframework.data.domain.PageImpl<>(java.util.Collections.emptyList(), pageable, 0);
207+
}
208+
PostPageDto response = new PostPageDto(posts);
209+
return ResponseEntity.ok(new ApiResponse<>(200, "마감된 투표 게시글 페이징 조회 성공", response));
210+
}
186211
}

backend/src/main/java/com/ai/lawyer/domain/post/dto/PostPageDTO.java renamed to backend/src/main/java/com/ai/lawyer/domain/post/dto/PostPageDto.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@
1010
@Getter
1111
@Setter
1212
@NoArgsConstructor
13-
public class PostPageDTO {
13+
public class PostPageDto {
1414
private List<PostDto> content;
1515
private int page;
1616
private int size;
1717
private int totalPages;
1818
private long totalElements;
1919

20-
public PostPageDTO(Page<PostDto> page) {
20+
public PostPageDto(Page<PostDto> page) {
2121
this.content = page.getContent();
2222
this.page = page.getNumber();
2323
this.size = page.getSize();
2424
this.totalPages = page.getTotalPages();
2525
this.totalElements = page.getTotalElements();
2626
}
27-
}
27+
}

backend/src/main/java/com/ai/lawyer/domain/post/entity/Post.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class Post {
2222
private Long postId;
2323

2424
@ManyToOne(fetch = FetchType.LAZY)
25-
@JoinColumn(name = "member_id", nullable = false, foreignKey = @ForeignKey(name = "FK_POST_MEMBER"))
25+
@JoinColumn(name = "member_id", nullable = true, foreignKey = @ForeignKey(name = "FK_POST_MEMBER"))
2626
private Member member;
2727

2828
@Column(name = "post_name", length = 100, nullable = false)

backend/src/main/java/com/ai/lawyer/domain/post/service/PostService.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,6 @@ public interface PostService {
3333

3434
// ===== 페이징 관련 =====
3535
Page<PostDto> getPostsPaged(Pageable pageable);
36+
Page<PostDto> getOngoingPostsPaged(Pageable pageable);
37+
Page<PostDto> getClosedPostsPaged(Pageable pageable);
3638
}

backend/src/main/java/com/ai/lawyer/domain/post/service/PostServiceImpl.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.ai.lawyer.domain.poll.service.PollService;
2222
import org.springframework.data.domain.Page;
2323
import org.springframework.data.domain.Pageable;
24+
import org.springframework.data.domain.PageImpl;
2425
import org.springframework.http.HttpStatus;
2526
import org.springframework.stereotype.Service;
2627
import org.springframework.transaction.annotation.Transactional;
@@ -254,6 +255,24 @@ public Page<PostDto> getPostsPaged(Pageable pageable) {
254255
return postRepository.findAll(pageable).map(this::convertToDto);
255256
}
256257

258+
@Override
259+
public Page<PostDto> getOngoingPostsPaged(Pageable pageable) {
260+
Page<PostDto> allPosts = postRepository.findAll(pageable).map(this::convertToDto);
261+
List<PostDto> ongoing = allPosts.stream()
262+
.filter(dto -> dto.getPoll() != null && dto.getPoll().getStatus() == PollDto.PollStatus.ONGOING)
263+
.collect(Collectors.toList());
264+
return new PageImpl<>(ongoing, pageable, ongoing.size());
265+
}
266+
267+
@Override
268+
public Page<PostDto> getClosedPostsPaged(Pageable pageable) {
269+
Page<PostDto> allPosts = postRepository.findAll(pageable).map(this::convertToDto);
270+
List<PostDto> closed = allPosts.stream()
271+
.filter(dto -> dto.getPoll() != null && dto.getPoll().getStatus() == PollDto.PollStatus.CLOSED)
272+
.collect(Collectors.toList());
273+
return new PageImpl<>(closed, pageable, closed.size());
274+
}
275+
257276
private PostDto convertToDto(Post entity) {
258277
Long memberId = null;
259278
if (entity.getMember() != null) {
Lines changed: 118 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,118 @@
1-
//package com.ai.lawyer.domain.poll.service;
2-
//
3-
//import com.ai.lawyer.domain.poll.dto.PollCreateDto;
4-
//import com.ai.lawyer.domain.poll.dto.PollDto;
5-
//import com.ai.lawyer.domain.poll.dto.PollUpdateDto;
6-
//import com.ai.lawyer.domain.poll.dto.PollVoteDto;
7-
//import com.ai.lawyer.domain.poll.dto.PollStaticsResponseDto;
8-
//import com.ai.lawyer.domain.poll.entity.Poll;
9-
//import com.ai.lawyer.domain.post.entity.Post;
10-
//import com.ai.lawyer.domain.post.repository.PostRepository;
11-
//import com.ai.lawyer.domain.member.entity.Member;
12-
//import com.ai.lawyer.domain.member.repositories.MemberRepository;
13-
//import com.ai.lawyer.domain.poll.repository.PollRepository;
14-
//import org.junit.jupiter.api.DisplayName;
15-
//import org.junit.jupiter.api.Test;
16-
//import org.springframework.beans.factory.annotation.Autowired;
17-
//import org.springframework.boot.test.context.SpringBootTest;
18-
//import org.springframework.transaction.annotation.Transactional;
19-
//import org.springframework.web.server.ResponseStatusException;
20-
//
21-
//import java.util.List;
22-
//
23-
//import static org.assertj.core.api.Assertions.*;
24-
//
25-
//@SpringBootTest
26-
//@Transactional
27-
//class PollAutoCloseTest {
28-
// @Autowired
29-
// private PollService pollService;
30-
//
31-
// @Autowired
32-
// private PostRepository postRepository;
33-
//
34-
// @Autowired
35-
// private MemberRepository memberRepository;
36-
//
37-
// @Autowired
38-
// private PollRepository pollRepository;
39-
//
40-
// @Test
41-
// @DisplayName("autoClose 예약 종료 자동 처리 기능(정책 우회)")
42-
// void autoCloseTest() throws Exception {
43-
// // 테스트용 member 생성
44-
// Member member = Member.builder()
45-
// .loginId("[email protected]")
46-
// .password("pw")
47-
// .age(20)
48-
// .gender(Member.Gender.MALE)
49-
// .role(Member.Role.USER)
50-
// .name("테스트유저")
51-
// .build();
52-
// member = memberRepository.save(member);
53-
//
54-
// // 테스트용 post 생성
55-
// Post post = new Post();
56-
// post.setPostName("테스트용 게시글");
57-
// post.setPostContent("테스트 내용");
58-
// post.setCategory("테스트");
59-
// post.setCreatedAt(java.time.LocalDateTime.now());
60-
// post.setMember(member);
61-
// post = postRepository.save(post);
62-
//
63-
// PollCreateDto createDto = new PollCreateDto();
64-
// createDto.setPostId(post.getPostId());
65-
// createDto.setVoteTitle("autoClose 테스트");
66-
// createDto.setReservedCloseAt(java.time.LocalDateTime.now().plusHours(1).plusSeconds(1));
67-
// // 투표 항목 2개 추가
68-
// var option1 = new com.ai.lawyer.domain.poll.dto.PollOptionCreateDto();
69-
// option1.setContent("찬성");
70-
// var option2 = new com.ai.lawyer.domain.poll.dto.PollOptionCreateDto();
71-
// option2.setContent("반대");
72-
// createDto.setPollOptions(java.util.Arrays.asList(option1, option2));
73-
// PollDto created = pollService.createPoll(createDto, member.getMemberId());
74-
//
75-
// // 2. 생성 직후 상태는 ONGOING이어야 함
76-
// PollDto ongoing = pollService.getPoll(created.getPollId());
77-
// assertThat(ongoing.getStatus()).isEqualTo(PollDto.PollStatus.ONGOING);
78-
//
79-
// // 3. reservedCloseAt을 DB에서 과거로 강제 변경
80-
// var poll = pollRepository.findById(created.getPollId()).get();
81-
// var reservedCloseAtField = poll.getClass().getDeclaredField("reservedCloseAt");
82-
// reservedCloseAtField.setAccessible(true);
83-
// reservedCloseAtField.set(poll, java.time.LocalDateTime.now().minusSeconds(1));
84-
// pollRepository.save(poll);
85-
//
86-
// // 4. getPoll 호출 시 자동 종료(CLOSED)로 바뀌는지 확인
87-
// PollDto closed = pollService.getPoll(created.getPollId());
88-
// assertThat(closed.getStatus()).isEqualTo(PollDto.PollStatus.CLOSED);
89-
// }
90-
//}
1+
package com.ai.lawyer.domain.poll.service;
2+
3+
import com.ai.lawyer.domain.poll.dto.PollCreateDto;
4+
import com.ai.lawyer.domain.poll.dto.PollDto;
5+
import com.ai.lawyer.domain.poll.dto.PollOptionCreateDto;
6+
import com.ai.lawyer.domain.poll.entity.Poll;
7+
import com.ai.lawyer.domain.poll.entity.PollOptions;
8+
import com.ai.lawyer.domain.post.entity.Post;
9+
import com.ai.lawyer.domain.post.repository.PostRepository;
10+
import com.ai.lawyer.domain.member.entity.Member;
11+
import com.ai.lawyer.domain.member.repositories.MemberRepository;
12+
import com.ai.lawyer.domain.poll.repository.PollRepository;
13+
import com.ai.lawyer.domain.poll.repository.PollOptionsRepository;
14+
import com.ai.lawyer.domain.poll.repository.PollVoteRepository;
15+
import org.junit.jupiter.api.DisplayName;
16+
import org.junit.jupiter.api.Test;
17+
import org.mockito.InjectMocks;
18+
import org.mockito.Mock;
19+
import org.mockito.junit.jupiter.MockitoExtension;
20+
import org.junit.jupiter.api.extension.ExtendWith;
21+
22+
import java.time.LocalDateTime;
23+
24+
import static java.util.Arrays.asList;
25+
import static org.assertj.core.api.Assertions.*;
26+
import static org.mockito.BDDMockito.*;
27+
import static org.mockito.Mockito.lenient;
28+
29+
@ExtendWith(MockitoExtension.class)
30+
class PollAutoCloseTest {
31+
@Mock
32+
private PollRepository pollRepository;
33+
@Mock
34+
private PostRepository postRepository;
35+
@Mock
36+
private MemberRepository memberRepository;
37+
@Mock
38+
private PollOptionsRepository pollOptionsRepository;
39+
@Mock
40+
private PollVoteRepository pollVoteRepository;
41+
@InjectMocks
42+
private PollServiceImpl pollService;
43+
44+
@Test
45+
@DisplayName("autoClose 예약 종료 자동 처리 기능(정책 우회)")
46+
void autoCloseTest() {
47+
// 필요한 Mock 객체 및 반환값 설정 예시
48+
Member member = Member.builder()
49+
.loginId("[email protected]")
50+
.password("pw")
51+
.age(20)
52+
.gender(Member.Gender.MALE)
53+
.role(Member.Role.USER)
54+
.name("테스트유저")
55+
.build();
56+
// memberId를 명확히 지정
57+
member.setMemberId(1L);
58+
lenient().when(memberRepository.save(any(Member.class))).thenReturn(member);
59+
60+
Post post = new Post();
61+
post.setPostId(1L);
62+
post.setPostName("테스트용 게시글");
63+
post.setPostContent("테스트 내용");
64+
post.setCategory("테스트");
65+
post.setCreatedAt(LocalDateTime.now());
66+
post.setMember(member);
67+
post.setPoll(null);
68+
lenient().when(postRepository.save(any(Post.class))).thenReturn(post);
69+
70+
Poll poll = new Poll();
71+
poll.setPollId(1L);
72+
poll.setReservedCloseAt(LocalDateTime.now().plusHours(1).plusSeconds(1));
73+
poll.setStatus(Poll.PollStatus.ONGOING);
74+
lenient().when(pollRepository.save(any(Poll.class))).thenReturn(poll);
75+
76+
// postRepository.save(post) 반환값에 poll이 반영된 post 객체 설정
77+
Post postWithPoll = new Post();
78+
postWithPoll.setPostId(1L);
79+
postWithPoll.setPostName("테스트용 게시글");
80+
postWithPoll.setPostContent("테스트 내용");
81+
postWithPoll.setCategory("테스트");
82+
postWithPoll.setCreatedAt(post.getCreatedAt());
83+
postWithPoll.setMember(member);
84+
postWithPoll.setPoll(poll);
85+
lenient().when(postRepository.save(argThat(p -> p.getPoll() != null))).thenReturn(postWithPoll);
86+
87+
PollCreateDto createDto = new PollCreateDto();
88+
createDto.setPostId(1L);
89+
createDto.setVoteTitle("autoClose 테스트");
90+
createDto.setReservedCloseAt(LocalDateTime.now().plusHours(1).plusSeconds(1));
91+
PollOptionCreateDto option1 = new PollOptionCreateDto();
92+
option1.setContent("찬성");
93+
PollOptionCreateDto option2 = new PollOptionCreateDto();
94+
option2.setContent("반대");
95+
createDto.setPollOptions(asList(option1, option2));
96+
97+
// PollOptions 저장에 대한 Mock 동작 추가 (여러 번 호출될 수 있으므로 각각 반환)
98+
PollOptions pollOptions1 = PollOptions.builder()
99+
.poll(poll)
100+
.option("찬성")
101+
.build();
102+
PollOptions pollOptions2 = PollOptions.builder()
103+
.poll(poll)
104+
.option("반대")
105+
.build();
106+
lenient().when(pollOptionsRepository.save(any(PollOptions.class))).thenReturn(pollOptions1, pollOptions2);
107+
108+
// pollVoteRepository.countByPollId의 반환값 설정
109+
lenient().when(pollVoteRepository.countByPollId(anyLong())).thenReturn(0L);
110+
111+
// reservedCloseAt을 과거로 변경하여 자동 종료 테스트
112+
poll.setReservedCloseAt(LocalDateTime.now().minusSeconds(1));
113+
poll.setStatus(Poll.PollStatus.CLOSED);
114+
given(pollRepository.findById(eq(1L))).willReturn(java.util.Optional.of(poll));
115+
PollDto closed = pollService.getPoll(1L);
116+
assertThat(closed.getStatus()).isEqualTo(PollDto.PollStatus.CLOSED);
117+
}
118+
}

0 commit comments

Comments
 (0)