Skip to content

Commit

Permalink
Merge pull request #96 from kakao-tech-campus-2nd-step3/fix/95-jwt-auth
Browse files Browse the repository at this point in the history
fix: JWT ์ธ์ฆ ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐ ์ถ”๊ฐ€ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ
  • Loading branch information
peeerr authored Nov 7, 2024
2 parents b8cf90b + 69f9291 commit 4035549
Show file tree
Hide file tree
Showing 12 changed files with 830 additions and 23 deletions.
1 change: 1 addition & 0 deletions .github/workflows/master_weekly_cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ jobs:
aws.s3.bucket: ${{ secrets.AWS_S3_BUCKET }}
aws.s3.accessKey: ${{ secrets.AWS_S3_ACCESS_KEY }}
aws.s3.secretKey: ${{ secrets.AWS_S3_SECRET_KEY }}
jwt.secret: ${{ secrets.JWT_SECRET }}

- name: ๋นŒ๋“œ๋กœ ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰ ๋ฐ Jar ํŒŒ์ผ ์ƒ์„ฑ
run: |
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pr_weekly_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ jobs:
aws.s3.bucket: ${{ secrets.AWS_S3_BUCKET }}
aws.s3.accessKey: ${{ secrets.AWS_S3_ACCESS_KEY }}
aws.s3.secretKey: ${{ secrets.AWS_S3_SECRET_KEY }}
jwt.secret: ${{ secrets.JWT_SECRET }}

- name: ๋นŒ๋“œ ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰
run: |
Expand Down
15 changes: 14 additions & 1 deletion src/main/java/com/potatocake/everymoment/util/JwtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,33 @@

import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Base64;
import java.util.Date;
import java.util.Optional;
import javax.crypto.SecretKey;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;

@Component
public class JwtUtil {

private final SecretKey SECRET_KEY = Jwts.SIG.HS256.key().build();
@Value("${jwt.secret}")
private String secret;

private SecretKey SECRET_KEY;
private final Long EXPIRE = 1000L * 60 * 60 * 48;
public final String PREFIX = "Bearer ";

@PostConstruct
public void init() {
byte[] keyBytes = Base64.getDecoder().decode(secret);
this.SECRET_KEY = Keys.hmacShaKeyFor(keyBytes);
}

public Long getId(String token) {
return Jwts.parser().verifyWith(SECRET_KEY).build()
.parseSignedClaims(token)
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ aws:
bucket: ${AWS_S3_BUCKET}
accessKey: ${AWS_S3_ACCESS_KEY}
secretKey: ${AWS_S3_SECRET_KEY}

jwt:
secret: ${JWT_SECRET}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.potatocake.everymoment.controller;

import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.BDDMockito.willDoNothing;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.potatocake.everymoment.dto.response.NotificationListResponse;
import com.potatocake.everymoment.entity.Member;
import com.potatocake.everymoment.security.MemberDetails;
import com.potatocake.everymoment.service.NotificationService;
import java.time.LocalDateTime;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;

@WebMvcTest(NotificationController.class)
class NotificationControllerTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private NotificationService notificationService;

@Test
@DisplayName("์•Œ๋ฆผ ๋ชฉ๋ก์ด ์„ฑ๊ณต์ ์œผ๋กœ ์กฐํšŒ๋œ๋‹ค.")
void should_GetNotifications_When_ValidRequest() throws Exception {
// given
Long memberId = 1L;
Member member = Member.builder()
.id(memberId)
.number(1234L)
.nickname("testUser")
.build();
MemberDetails memberDetails = new MemberDetails(member);

List<NotificationListResponse> responses = List.of(
NotificationListResponse.builder()
.id(1L)
.content("Notification 1")
.type("TEST1")
.targetId(1L)
.isRead(false)
.createdAt(LocalDateTime.now())
.build(),
NotificationListResponse.builder()
.id(2L)
.content("Notification 2")
.type("TEST2")
.targetId(2L)
.isRead(true)
.createdAt(LocalDateTime.now())
.build()
);

given(notificationService.getNotifications(memberId)).willReturn(responses);

// when
ResultActions result = mockMvc.perform(get("/api/notifications")
.with(user(memberDetails)));

// then
result.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.message").value("success"))
.andExpect(jsonPath("$.info").isArray())
.andExpect(jsonPath("$.info[0].content").value("Notification 1"))
.andExpect(jsonPath("$.info[0].read").value(false))
.andExpect(jsonPath("$.info[1].content").value("Notification 2"))
.andExpect(jsonPath("$.info[1].read").value(true));

then(notificationService).should().getNotifications(memberId);
}

@Test
@DisplayName("์•Œ๋ฆผ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ฝ์Œ ์ฒ˜๋ฆฌ๋œ๋‹ค.")
void should_UpdateNotification_When_ValidId() throws Exception {
// given
Long memberId = 1L;
Long notificationId = 1L;
Member member = Member.builder()
.id(memberId)
.number(1234L)
.nickname("testUser")
.build();
MemberDetails memberDetails = new MemberDetails(member);

willDoNothing().given(notificationService).updateNotification(memberId, notificationId);

// when
ResultActions result = mockMvc.perform(patch("/api/notifications/{notificationId}", notificationId)
.with(user(memberDetails))
.with(csrf()));

// then
result.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.message").value("success"));

then(notificationService).should().updateNotification(eq(memberId), eq(notificationId));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.potatocake.everymoment.entity;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class NotificationTest {

@Test
@DisplayName("์•Œ๋ฆผ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋œ๋‹ค.")
void should_CreateNotification_When_ValidInput() {
// given
Member member = Member.builder()
.id(1L)
.build();

// when
Notification notification = Notification.builder()
.member(member)
.content("Test notification")
.type("TEST")
.targetId(1L)
.isRead(false)
.build();

// then
assertThat(notification.getMember()).isEqualTo(member);
assertThat(notification.getContent()).isEqualTo("Test notification");
assertThat(notification.getType()).isEqualTo("TEST");
assertThat(notification.getTargetId()).isEqualTo(1L);
assertThat(notification.isRead()).isFalse();
}

@Test
@DisplayName("์•Œ๋ฆผ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ฝ์Œ ์ฒ˜๋ฆฌ๋œ๋‹ค.")
void should_MarkAsRead_When_UpdateIsRead() {
// given
Notification notification = Notification.builder()
.content("Test notification")
.isRead(false)
.build();

// when
notification.updateIsRead();

// then
assertThat(notification.isRead()).isTrue();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.potatocake.everymoment.repository;

import static org.assertj.core.api.Assertions.assertThat;

import com.potatocake.everymoment.entity.Member;
import com.potatocake.everymoment.entity.Notification;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.TestPropertySource;

@TestPropertySource(properties = "spring.jpa.hibernate.ddl-auto=create-drop")
@DataJpaTest
class NotificationRepositoryTest {

@Autowired
private NotificationRepository notificationRepository;

@Autowired
private MemberRepository memberRepository;

@Test
@DisplayName("์•Œ๋ฆผ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ €์žฅ๋œ๋‹ค.")
void should_SaveNotification_When_ValidEntity() {
// given
Member member = Member.builder()
.number(1234L)
.nickname("testUser")
.profileImageUrl("https://example.com/profile.jpg")
.build();
Member savedMember = memberRepository.save(member);

Notification notification = Notification.builder()
.member(savedMember)
.content("Test notification")
.type("TEST")
.targetId(1L)
.isRead(false)
.build();

// when
Notification savedNotification = notificationRepository.save(notification);

// then
assertThat(savedNotification.getId()).isNotNull();
assertThat(savedNotification.getContent()).isEqualTo("Test notification");
assertThat(savedNotification.getMember()).isEqualTo(savedMember);
}

@Test
@DisplayName("ํšŒ์›์˜ ์•Œ๋ฆผ ๋ชฉ๋ก์ด ์„ฑ๊ณต์ ์œผ๋กœ ์กฐํšŒ๋œ๋‹ค.")
void should_FindNotifications_When_FilteringByMemberId() {
// given
Member member = Member.builder()
.number(1234L)
.nickname("testUser")
.profileImageUrl("https://example.com/profile.jpg")
.build();
Member savedMember = memberRepository.save(member);

List<Notification> notifications = List.of(
Notification.builder()
.member(savedMember)
.content("Notification 1")
.type("TEST1")
.targetId(1L)
.build(),
Notification.builder()
.member(savedMember)
.content("Notification 2")
.type("TEST2")
.targetId(2L)
.build()
);

notificationRepository.saveAll(notifications);

// when
List<Notification> foundNotifications = notificationRepository
.findAllByMemberId(savedMember.getId());

// then
assertThat(foundNotifications).hasSize(2);
assertThat(foundNotifications)
.extracting("content")
.containsExactlyInAnyOrder("Notification 1", "Notification 2");
}

@Test
@DisplayName("์•Œ๋ฆผ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋œ๋‹ค.")
void should_DeleteNotification_When_ValidEntity() {
// given
Member member = Member.builder()
.number(1234L)
.nickname("testUser")
.profileImageUrl("https://example.com/profile.jpg")
.build();
Member savedMember = memberRepository.save(member);

Notification notification = Notification.builder()
.member(savedMember)
.content("Test notification")
.type("TEST")
.targetId(1L)
.build();

Notification savedNotification = notificationRepository.save(notification);

// when
notificationRepository.delete(savedNotification);

// then
List<Notification> remainingNotifications = notificationRepository
.findAllByMemberId(savedMember.getId());
assertThat(remainingNotifications).isEmpty();
}

}
Loading

0 comments on commit 4035549

Please sign in to comment.