Skip to content

Commit 8b1229c

Browse files
committed
Merge branch 'refs/heads/develop' into feat/oauth
2 parents 97d318c + ad30807 commit 8b1229c

File tree

9 files changed

+142
-60
lines changed

9 files changed

+142
-60
lines changed

.github/workflows/CI-CD_Pipeline.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ jobs:
2222
- os: ubuntu-latest
2323
gradle_cmd: "./gradlew"
2424
report_path: "backend/build/reports/tests"
25-
domain_tasks: "testUser testExchange testTrade_log testWallet testCoin"
2625

2726
runs-on: ${{ matrix.os }}
2827
env:
@@ -90,8 +89,9 @@ jobs:
9089
# JPA 설정 (application-test.yml에서 참조)
9190
TEST_JPA_HIBERNATE_DDL_AUTO=create-drop
9291
93-
send_email_password=${{ secrets.SEND_EMAIL_PASSWORD }}
9492
send_email_address=${{ secrets.SEND_EMAIL_ADDRESS }}
93+
send_email_password=${{ secrets.SEND_EMAIL_PASSWORD }}
94+
9595
9696
# Redis 설정 (application-test.yml에서 참조, GitHub Actions 서비스 사용)
9797
TEST_REDIS_HOST=localhost
@@ -109,6 +109,13 @@ jobs:
109109
CUSTOM_JWT_SECRET_KEY=${{ secrets.JWT_SECRET_KEY }}
110110
CUSTOM_JWT_ACCESS_TOKEN_EXPIRATION_SECONDS=3600
111111
EOF
112+
- name: Export .env into runner env
113+
working-directory: backend
114+
run: |
115+
while IFS= read -r line; do
116+
[[ -z "$line" || "$line" =~ ^# ]] && continue
117+
echo "$line" >> $GITHUB_ENV
118+
done < .env
112119
113120
- name: Run unit, and domain tests
114121
run: ${{ matrix.gradle_cmd }} clean test

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) {

backend/src/test/java/com/ai/lawyer/domain/poll/service/PollAutoCloseTest.java

Lines changed: 71 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,49 @@
22

33
import com.ai.lawyer.domain.poll.dto.PollCreateDto;
44
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;
5+
import com.ai.lawyer.domain.poll.dto.PollOptionCreateDto;
86
import com.ai.lawyer.domain.poll.entity.Poll;
7+
import com.ai.lawyer.domain.poll.entity.PollOptions;
98
import com.ai.lawyer.domain.post.entity.Post;
109
import com.ai.lawyer.domain.post.repository.PostRepository;
1110
import com.ai.lawyer.domain.member.entity.Member;
1211
import com.ai.lawyer.domain.member.repositories.MemberRepository;
1312
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;
1415
import org.junit.jupiter.api.DisplayName;
1516
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;
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;
2021

21-
import java.util.List;
22+
import java.time.LocalDateTime;
2223

24+
import static java.util.Arrays.asList;
2325
import static org.assertj.core.api.Assertions.*;
26+
import static org.mockito.BDDMockito.*;
27+
import static org.mockito.Mockito.lenient;
2428

25-
@SpringBootTest
26-
@Transactional
29+
@ExtendWith(MockitoExtension.class)
2730
class PollAutoCloseTest {
28-
@Autowired
29-
private PollService pollService;
30-
31-
@Autowired
31+
@Mock
32+
private PollRepository pollRepository;
33+
@Mock
3234
private PostRepository postRepository;
33-
34-
@Autowired
35+
@Mock
3536
private MemberRepository memberRepository;
36-
37-
@Autowired
38-
private PollRepository pollRepository;
37+
@Mock
38+
private PollOptionsRepository pollOptionsRepository;
39+
@Mock
40+
private PollVoteRepository pollVoteRepository;
41+
@InjectMocks
42+
private PollServiceImpl pollService;
3943

4044
@Test
4145
@DisplayName("autoClose 예약 종료 자동 처리 기능(정책 우회)")
42-
void autoCloseTest() throws Exception {
43-
// 테스트용 member 생성
46+
void autoCloseTest() {
47+
// 필요한 Mock 객체 및 반환값 설정 예시
4448
Member member = Member.builder()
4549
.loginId("[email protected]")
4650
.password("pw")
@@ -49,42 +53,66 @@ void autoCloseTest() throws Exception {
4953
.role(Member.Role.USER)
5054
.name("테스트유저")
5155
.build();
52-
member = memberRepository.save(member);
56+
// memberId를 명확히 지정
57+
member.setMemberId(1L);
58+
lenient().when(memberRepository.save(any(Member.class))).thenReturn(member);
5359

54-
// 테스트용 post 생성
5560
Post post = new Post();
61+
post.setPostId(1L);
5662
post.setPostName("테스트용 게시글");
5763
post.setPostContent("테스트 내용");
5864
post.setCategory("테스트");
59-
post.setCreatedAt(java.time.LocalDateTime.now());
65+
post.setCreatedAt(LocalDateTime.now());
6066
post.setMember(member);
61-
post = postRepository.save(post);
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);
6286

6387
PollCreateDto createDto = new PollCreateDto();
64-
createDto.setPostId(post.getPostId());
88+
createDto.setPostId(1L);
6589
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();
90+
createDto.setReservedCloseAt(LocalDateTime.now().plusHours(1).plusSeconds(1));
91+
PollOptionCreateDto option1 = new PollOptionCreateDto();
6992
option1.setContent("찬성");
70-
var option2 = new com.ai.lawyer.domain.poll.dto.PollOptionCreateDto();
93+
PollOptionCreateDto option2 = new PollOptionCreateDto();
7194
option2.setContent("반대");
72-
createDto.setPollOptions(java.util.Arrays.asList(option1, option2));
73-
PollDto created = pollService.createPoll(createDto, member.getMemberId());
95+
createDto.setPollOptions(asList(option1, option2));
7496

75-
// 2. 생성 직후 상태는 ONGOING이어야 함
76-
PollDto ongoing = pollService.getPoll(created.getPollId());
77-
assertThat(ongoing.getStatus()).isEqualTo(PollDto.PollStatus.ONGOING);
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);
78107

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);
108+
// pollVoteRepository.countByPollId의 반환값 설정
109+
lenient().when(pollVoteRepository.countByPollId(anyLong())).thenReturn(0L);
85110

86-
// 4. getPoll 호출 시 자동 종료(CLOSED)로 바뀌는지 확인
87-
PollDto closed = pollService.getPoll(created.getPollId());
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);
88116
assertThat(closed.getStatus()).isEqualTo(PollDto.PollStatus.CLOSED);
89117
}
90118
}

0 commit comments

Comments
 (0)