This commit is contained in:
@@ -17,7 +17,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
cache: gradle
|
|
||||||
|
|
||||||
- name: Make Gradle wrapper executable
|
- name: Make Gradle wrapper executable
|
||||||
run: chmod +x ./gradlew
|
run: chmod +x ./gradlew
|
||||||
|
|||||||
@@ -42,6 +42,15 @@ dependencies {
|
|||||||
implementation("org.projectlombok:lombok:1.18.42")
|
implementation("org.projectlombok:lombok:1.18.42")
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.42'
|
annotationProcessor 'org.projectlombok:lombok:1.18.42'
|
||||||
implementation 'com.google.firebase:firebase-admin:9.9.0'
|
implementation 'com.google.firebase:firebase-admin:9.9.0'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
testImplementation("org.testcontainers:postgresql:1.21.3")
|
||||||
|
testImplementation("org.testcontainers:junit-jupiter:1.21.4")
|
||||||
|
testImplementation("org.mockito:mockito-inline:5.2.0")
|
||||||
|
testImplementation 'org.mockito:mockito-core'
|
||||||
}
|
}
|
||||||
|
|
||||||
generateJava {
|
generateJava {
|
||||||
@@ -52,4 +61,8 @@ generateJava {
|
|||||||
|
|
||||||
tasks.named('test') {
|
tasks.named('test') {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
|
|
||||||
|
jvmArgs += [
|
||||||
|
"-javaagent:${configurations.testRuntimeClasspath.find { it.name.contains('byte-buddy-agent') }}"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package dev.ksan.etfoglasiserver.config;
|
|||||||
import com.google.auth.oauth2.GoogleCredentials;
|
import com.google.auth.oauth2.GoogleCredentials;
|
||||||
import com.google.firebase.FirebaseApp;
|
import com.google.firebase.FirebaseApp;
|
||||||
import com.google.firebase.FirebaseOptions;
|
import com.google.firebase.FirebaseOptions;
|
||||||
|
import com.google.firebase.messaging.FirebaseMessaging;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
@@ -28,4 +29,9 @@ public class FirebaseConfig {
|
|||||||
}
|
}
|
||||||
return FirebaseApp.getInstance();
|
return FirebaseApp.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public FirebaseMessaging firebaseMessaging(FirebaseApp firebaseApp) {
|
||||||
|
return FirebaseMessaging.getInstance(firebaseApp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -33,6 +33,7 @@ public class JwtFilter extends OncePerRequestFilter {
|
|||||||
String token = null;
|
String token = null;
|
||||||
String username = null;
|
String username = null;
|
||||||
|
|
||||||
|
|
||||||
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
||||||
token = authHeader.substring(7);
|
token = authHeader.substring(7);
|
||||||
username = jwtService.extractEmail(token);
|
username = jwtService.extractEmail(token);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
|
|||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
@@ -61,4 +62,10 @@ public class SecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+6
-18
@@ -1,12 +1,8 @@
|
|||||||
package dev.ksan.etfoglasiserver.controller;
|
package dev.ksan.etfoglasiserver.controller;
|
||||||
|
|
||||||
import dev.ksan.etfoglasiserver.model.DeviceToken;
|
|
||||||
import dev.ksan.etfoglasiserver.model.RegisterTokenRequest;
|
import dev.ksan.etfoglasiserver.model.RegisterTokenRequest;
|
||||||
import dev.ksan.etfoglasiserver.model.User;
|
import dev.ksan.etfoglasiserver.service.DeviceTokenService;
|
||||||
import dev.ksan.etfoglasiserver.repository.DeviceTokenRepo;
|
|
||||||
import dev.ksan.etfoglasiserver.repository.UserRepo;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
@@ -18,24 +14,16 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class DeviceTokenController {
|
public class DeviceTokenController {
|
||||||
|
|
||||||
private final DeviceTokenRepo deviceTokenRepo;
|
private final DeviceTokenService deviceTokenService;
|
||||||
private final UserRepo userRepo;
|
|
||||||
|
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
public ResponseEntity<?> register(
|
public ResponseEntity<Void> register(
|
||||||
@RequestBody RegisterTokenRequest request,
|
@RequestBody RegisterTokenRequest request,
|
||||||
@AuthenticationPrincipal UserDetails principal) {
|
@AuthenticationPrincipal UserDetails principal) {
|
||||||
|
|
||||||
User user = userRepo.findByEmail(principal.getUsername())
|
deviceTokenService.registerToken(
|
||||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
principal.getUsername(),
|
||||||
|
request.getToken());
|
||||||
DeviceToken deviceToken = deviceTokenRepo
|
|
||||||
.findByFcmToken(request.getToken())
|
|
||||||
.orElse(new DeviceToken());
|
|
||||||
|
|
||||||
deviceToken.setUser(user);
|
|
||||||
deviceToken.setFcmToken(request.getToken());
|
|
||||||
deviceTokenRepo.save(deviceToken);
|
|
||||||
|
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public class UserController {
|
|||||||
@Autowired UserService service;
|
@Autowired UserService service;
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("users/me")
|
@GetMapping("/users/me")
|
||||||
public ResponseEntity<UserDTO> getUserById(Authentication authentication) {
|
public ResponseEntity<UserDTO> getUserById(Authentication authentication) {
|
||||||
String email = authentication.getName();
|
String email = authentication.getName();
|
||||||
User user = service.getAccountByEmail(email)
|
User user = service.getAccountByEmail(email)
|
||||||
@@ -46,7 +46,7 @@ public class UserController {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@PostMapping("users/me/notification-type")
|
@PostMapping("/users/me/notification-type")
|
||||||
public ResponseEntity<?> updateNotificationType(
|
public ResponseEntity<?> updateNotificationType(
|
||||||
@RequestBody Map<String, String> request,
|
@RequestBody Map<String, String> request,
|
||||||
Authentication authentication) {
|
Authentication authentication) {
|
||||||
@@ -82,19 +82,32 @@ public class UserController {
|
|||||||
return ResponseEntity.ok(updatedUser);
|
return ResponseEntity.ok(updatedUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// not the best, should fix this later
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public ResponseEntity<JwtResponseDTO> login(@RequestBody UserLoginDTO user) {
|
public ResponseEntity<JwtResponseDTO> login(@RequestBody UserLoginDTO user) {
|
||||||
|
try {
|
||||||
JwtResponseDTO userVer = service.verify(user);
|
JwtResponseDTO userVer = service.verify(user);
|
||||||
|
|
||||||
if (userVer != null) return new ResponseEntity<>(userVer, HttpStatus.OK);
|
if (userVer != null) return new ResponseEntity<>(userVer, HttpStatus.OK);
|
||||||
|
}catch (Exception e) {
|
||||||
|
|
||||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
// not the best, should fix this later too
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
public ResponseEntity<UserDTO> register(@RequestBody UserCreationDTO user) {
|
public ResponseEntity<UserDTO> register(@RequestBody UserCreationDTO user) {
|
||||||
|
|
||||||
|
try {
|
||||||
UserDTO newUser = service.register(user);
|
UserDTO newUser = service.register(user);
|
||||||
if (newUser != null) return new ResponseEntity<>(newUser, HttpStatus.CREATED);
|
if (newUser != null) return new ResponseEntity<>(newUser, HttpStatus.CREATED);
|
||||||
|
}catch (Exception e) {
|
||||||
|
|
||||||
|
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ public class UserCreationDTO {
|
|||||||
private Set<Subject> subjectSet;
|
private Set<Subject> subjectSet;
|
||||||
private String notification;
|
private String notification;
|
||||||
|
|
||||||
|
public UserCreationDTO(String mail, String password) {
|
||||||
|
this.email = mail;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
public String getEmail() {
|
public String getEmail() {
|
||||||
return email;
|
return email;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package dev.ksan.etfoglasiserver.dto;
|
package dev.ksan.etfoglasiserver.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
public class UserLoginDTO {
|
public class UserLoginDTO {
|
||||||
|
|
||||||
private String email;
|
private String email;
|
||||||
|
|||||||
@@ -1,4 +1,31 @@
|
|||||||
package dev.ksan.etfoglasiserver.service;
|
package dev.ksan.etfoglasiserver.service;
|
||||||
|
|
||||||
|
import dev.ksan.etfoglasiserver.model.DeviceToken;
|
||||||
|
import dev.ksan.etfoglasiserver.model.User;
|
||||||
|
import dev.ksan.etfoglasiserver.repository.DeviceTokenRepo;
|
||||||
|
import dev.ksan.etfoglasiserver.repository.UserRepo;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class DeviceTokenService {
|
public class DeviceTokenService {
|
||||||
|
|
||||||
|
private final DeviceTokenRepo deviceTokenRepo;
|
||||||
|
private final UserRepo userRepo;
|
||||||
|
|
||||||
|
public void registerToken(String email, String token) {
|
||||||
|
|
||||||
|
User user = userRepo.findByEmail(email)
|
||||||
|
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||||
|
|
||||||
|
DeviceToken deviceToken = deviceTokenRepo
|
||||||
|
.findByFcmToken(token)
|
||||||
|
.orElse(new DeviceToken());
|
||||||
|
|
||||||
|
deviceToken.setUser(user);
|
||||||
|
deviceToken.setFcmToken(token);
|
||||||
|
|
||||||
|
deviceTokenRepo.save(deviceToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
package dev.ksan.etfoglasiserver.service;
|
package dev.ksan.etfoglasiserver.service;
|
||||||
|
|
||||||
import com.google.firebase.messaging.FirebaseMessaging;
|
import com.google.firebase.messaging.*;
|
||||||
import com.google.firebase.messaging.FirebaseMessagingException;
|
|
||||||
import com.google.firebase.messaging.Message;
|
|
||||||
import com.google.firebase.messaging.Notification;
|
|
||||||
import dev.ksan.etfoglasiserver.model.DeviceToken;
|
import dev.ksan.etfoglasiserver.model.DeviceToken;
|
||||||
import dev.ksan.etfoglasiserver.model.User;
|
import dev.ksan.etfoglasiserver.model.User;
|
||||||
import dev.ksan.etfoglasiserver.repository.DeviceTokenRepo;
|
import dev.ksan.etfoglasiserver.repository.DeviceTokenRepo;
|
||||||
@@ -16,45 +13,32 @@ import java.util.List;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class NotificationService {
|
public class NotificationService {
|
||||||
|
|
||||||
private final DeviceTokenRepo deviceTokenRepository;
|
private final DeviceTokenRepo deviceTokenRepo;
|
||||||
|
private final FirebaseMessaging firebaseMessaging;
|
||||||
|
|
||||||
|
public void sendToUser(User user, String title, String body)
|
||||||
public void sendToUser(User user, String title, String body) {
|
throws FirebaseMessagingException {
|
||||||
|
|
||||||
List<DeviceToken> tokens =
|
List<DeviceToken> tokens =
|
||||||
deviceTokenRepository.findByUserId(user.getId());
|
deviceTokenRepo.findByUserId(user.getId());
|
||||||
|
|
||||||
for (DeviceToken token : tokens) {
|
for (DeviceToken token : tokens) {
|
||||||
|
|
||||||
try {
|
|
||||||
Message message = Message.builder()
|
Message message = Message.builder()
|
||||||
.setToken(token.getFcmToken())
|
.setToken(token.getFcmToken())
|
||||||
.setNotification(
|
.putData("title", title)
|
||||||
Notification.builder()
|
.putData("body", body)
|
||||||
.setTitle(title)
|
|
||||||
.setBody(body)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
FirebaseMessaging.getInstance().send(message);
|
try {
|
||||||
|
firebaseMessaging.send(message);
|
||||||
|
} catch (FirebaseMessagingException ex) {
|
||||||
|
|
||||||
} catch (FirebaseMessagingException e) {
|
if (ex.getMessagingErrorCode()
|
||||||
|
== MessagingErrorCode.UNREGISTERED) {
|
||||||
String errorCode = e.getMessagingErrorCode() != null
|
deviceTokenRepo.delete(token);
|
||||||
? e.getMessagingErrorCode().name()
|
|
||||||
: "";
|
|
||||||
|
|
||||||
System.out.println("Failed token: " + token.getFcmToken());
|
|
||||||
|
|
||||||
if (errorCode.equals("UNREGISTERED")
|
|
||||||
|| errorCode.equals("INVALID_ARGUMENT")) {
|
|
||||||
|
|
||||||
deviceTokenRepository.delete(token);
|
|
||||||
System.out.println("Deleted dead token");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package dev.ksan.etfoglasiserver.service;
|
package dev.ksan.etfoglasiserver.service;
|
||||||
|
|
||||||
|
import com.google.firebase.messaging.FirebaseMessagingException;
|
||||||
import dev.ksan.etfoglasiserver.model.Entry;
|
import dev.ksan.etfoglasiserver.model.Entry;
|
||||||
import dev.ksan.etfoglasiserver.model.NotificationMethod;
|
import dev.ksan.etfoglasiserver.model.NotificationMethod;
|
||||||
import dev.ksan.etfoglasiserver.model.Subject;
|
import dev.ksan.etfoglasiserver.model.Subject;
|
||||||
@@ -83,7 +84,12 @@ public class SubjectService {
|
|||||||
if (user.getNotificationMethod() != NotificationMethod.PUSH_NOTIFICATION)
|
if (user.getNotificationMethod() != NotificationMethod.PUSH_NOTIFICATION)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
notificationService.sendToUser(user, entry.getTitle(), entry.getParagraph());
|
notificationService.sendToUser(user, entry.getTitle(), entry.getParagraph());
|
||||||
|
}catch (FirebaseMessagingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -149,19 +150,18 @@ public class UserService {
|
|||||||
|
|
||||||
public JwtResponseDTO verify(UserLoginDTO user) {
|
public JwtResponseDTO verify(UserLoginDTO user) {
|
||||||
|
|
||||||
Authentication authentication = authManager.authenticate(
|
authManager.authenticate(
|
||||||
new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword())
|
new UsernamePasswordAuthenticationToken(
|
||||||
|
user.getEmail(),
|
||||||
|
user.getPassword()
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!authentication.isAuthenticated()) {
|
User foundUser = userRepo.findByEmail(user.getEmail())
|
||||||
throw new BadCredentialsException("Invalid username or password");
|
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||||
}
|
|
||||||
|
|
||||||
String token = jwtService.generateToken(user.getEmail());
|
String token = jwtService.generateToken(user.getEmail());
|
||||||
|
|
||||||
User foundUser = userRepo.findByEmail(user.getEmail())
|
|
||||||
.orElseThrow(() -> new RuntimeException("User not found after authentication"));
|
|
||||||
|
|
||||||
return new JwtResponseDTO(
|
return new JwtResponseDTO(
|
||||||
token,
|
token,
|
||||||
foundUser.getEmail(),
|
foundUser.getEmail(),
|
||||||
|
|||||||
+41
-1
@@ -1,4 +1,44 @@
|
|||||||
package dev.ksan.etfoglasiserver.controller;
|
package dev.ksan.etfoglasiserver.controller;
|
||||||
|
|
||||||
public class DeviceTokenControllerTest {
|
import dev.ksan.etfoglasiserver.model.RegisterTokenRequest;
|
||||||
|
import dev.ksan.etfoglasiserver.service.DeviceTokenService;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class DeviceTokenControllerTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
DeviceTokenService deviceTokenService;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
DeviceTokenController controller;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void register_savesNewToken() {
|
||||||
|
|
||||||
|
UserDetails principal = mock(UserDetails.class);
|
||||||
|
when(principal.getUsername()).thenReturn("test@test.com");
|
||||||
|
|
||||||
|
RegisterTokenRequest req = new RegisterTokenRequest();
|
||||||
|
req.setToken("fcm-123");
|
||||||
|
|
||||||
|
ResponseEntity<Void> res = controller.register(req, principal);
|
||||||
|
|
||||||
|
assertEquals(204, res.getStatusCode().value());
|
||||||
|
|
||||||
|
verify(deviceTokenService).registerToken(
|
||||||
|
"test@test.com",
|
||||||
|
"fcm-123"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+224
-1
@@ -1,4 +1,227 @@
|
|||||||
package dev.ksan.etfoglasiserver.controller;
|
package dev.ksan.etfoglasiserver.controller;
|
||||||
|
|
||||||
public class UserControllerIntegrationTest {
|
import dev.ksan.etfoglasiserver.dto.JwtResponseDTO;
|
||||||
|
import dev.ksan.etfoglasiserver.dto.UserCreationDTO;
|
||||||
|
import dev.ksan.etfoglasiserver.dto.UserDTO;
|
||||||
|
import dev.ksan.etfoglasiserver.dto.UserLoginDTO;
|
||||||
|
import dev.ksan.etfoglasiserver.model.NotificationMethod;
|
||||||
|
import dev.ksan.etfoglasiserver.model.User;
|
||||||
|
import dev.ksan.etfoglasiserver.repository.UserRepo;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.http.*;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
|
@Import(UserController.class)
|
||||||
|
class UserControllerIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired TestRestTemplate restTemplate;
|
||||||
|
@Autowired UserRepo userRepo;
|
||||||
|
@Autowired
|
||||||
|
PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
private String token;
|
||||||
|
private User testUser;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
userRepo.deleteAll();
|
||||||
|
|
||||||
|
testUser = new User();
|
||||||
|
testUser.setEmail("test@test.com");
|
||||||
|
testUser.setPassword(passwordEncoder.encode("password"));
|
||||||
|
testUser.setNotificationMethod(NotificationMethod.PUSH_NOTIFICATION);
|
||||||
|
userRepo.save(testUser);
|
||||||
|
|
||||||
|
ResponseEntity<JwtResponseDTO> res = restTemplate.postForEntity(
|
||||||
|
"/api/login",
|
||||||
|
new UserLoginDTO("test@test.com", "password"),
|
||||||
|
JwtResponseDTO.class
|
||||||
|
);
|
||||||
|
token = res.getBody().getToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void login_returnsToken_withValidCredentials() {
|
||||||
|
ResponseEntity<JwtResponseDTO> res = restTemplate.postForEntity(
|
||||||
|
"/api/login",
|
||||||
|
new UserLoginDTO("test@test.com", "password"),
|
||||||
|
JwtResponseDTO.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(200, res.getStatusCode().value());
|
||||||
|
assertNotNull(res.getBody().getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void login_returns400_withWrongPassword() {
|
||||||
|
ResponseEntity<?> res = restTemplate.postForEntity(
|
||||||
|
"/api/login",
|
||||||
|
new UserLoginDTO("test@test.com", "wrongpassword"),
|
||||||
|
Object.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(400, res.getStatusCode().value());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void register_createsUser_andReturns201() {
|
||||||
|
ResponseEntity<UserDTO> res = restTemplate.postForEntity(
|
||||||
|
"/api/register",
|
||||||
|
new UserCreationDTO("new@test.com", "password123"),
|
||||||
|
UserDTO.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(201, res.getStatusCode().value());
|
||||||
|
assertNotNull(res.getBody());
|
||||||
|
assertEquals("new@test.com", res.getBody().getEmail());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void register_returns400_withInvalidData() {
|
||||||
|
ResponseEntity<?> res = restTemplate.postForEntity(
|
||||||
|
"/api/register",
|
||||||
|
new UserCreationDTO("", ""),
|
||||||
|
Object.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(400, res.getStatusCode().value());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getMe_returnsUser_withValidToken() {
|
||||||
|
ResponseEntity<UserDTO> res = restTemplate.exchange(
|
||||||
|
"/api/users/me",
|
||||||
|
HttpMethod.GET,
|
||||||
|
withAuth(null),
|
||||||
|
UserDTO.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(200, res.getStatusCode().value());
|
||||||
|
assertEquals("test@test.com", res.getBody().getEmail());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getMe_returns401_withoutToken() {
|
||||||
|
ResponseEntity<?> res = restTemplate.getForEntity("/api/users/me", Object.class);
|
||||||
|
assertEquals(401, res.getStatusCode().value());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateUser_updatesEmail() {
|
||||||
|
UserCreationDTO update = new UserCreationDTO("updated@test.com", null);
|
||||||
|
|
||||||
|
ResponseEntity<UserDTO> res = restTemplate.exchange(
|
||||||
|
"/api/users/me",
|
||||||
|
HttpMethod.PUT,
|
||||||
|
withAuth(update),
|
||||||
|
UserDTO.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(200, res.getStatusCode().value());
|
||||||
|
assertEquals("updated@test.com", res.getBody().getEmail());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateUser_returns401_withoutToken() {
|
||||||
|
ResponseEntity<?> res = restTemplate.exchange(
|
||||||
|
"/api/users/me",
|
||||||
|
HttpMethod.PUT,
|
||||||
|
new HttpEntity<>(new UserCreationDTO("x@x.com", null)),
|
||||||
|
Object.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(401, res.getStatusCode().value());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateNotificationType_setsNoNotification() {
|
||||||
|
ResponseEntity<?> res = restTemplate.exchange(
|
||||||
|
"/api/users/me/notification-type",
|
||||||
|
HttpMethod.POST,
|
||||||
|
withAuth(Map.of("notificationType", "NO_NOTIFICATION")),
|
||||||
|
Object.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(200, res.getStatusCode().value());
|
||||||
|
|
||||||
|
User updated = userRepo.findByEmail("test@test.com").get();
|
||||||
|
assertEquals(NotificationMethod.NO_NOTIFICATION, updated.getNotificationMethod());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateNotificationType_setsPushNotification() {
|
||||||
|
// set to push first
|
||||||
|
testUser.setNotificationMethod(NotificationMethod.PUSH_NOTIFICATION);
|
||||||
|
userRepo.save(testUser);
|
||||||
|
|
||||||
|
restTemplate.exchange(
|
||||||
|
"/api/users/me/notification-type",
|
||||||
|
HttpMethod.POST,
|
||||||
|
withAuth(Map.of("notificationType", "PUSH_NOTIFICATION")),
|
||||||
|
Object.class
|
||||||
|
);
|
||||||
|
|
||||||
|
User updated = userRepo.findByEmail("test@test.com").get();
|
||||||
|
assertEquals(NotificationMethod.PUSH_NOTIFICATION, updated.getNotificationMethod());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateNotificationType_returns401_withoutToken() {
|
||||||
|
ResponseEntity<?> res = restTemplate.postForEntity(
|
||||||
|
"/api/users/me/notification-type",
|
||||||
|
Map.of("notificationType", "NO_NOTIFICATION"),
|
||||||
|
Object.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(401, res.getStatusCode().value());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deleteUser_returns204_andRemovesUser() {
|
||||||
|
ResponseEntity<Void> res = restTemplate.exchange(
|
||||||
|
"/api/users/me",
|
||||||
|
HttpMethod.DELETE,
|
||||||
|
withAuth(null),
|
||||||
|
Void.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(204, res.getStatusCode().value());
|
||||||
|
assertTrue(userRepo.findByEmail("test@test.com").isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deleteUser_returns401_withoutToken() {
|
||||||
|
ResponseEntity<?> res = restTemplate.exchange(
|
||||||
|
"/api/users/me",
|
||||||
|
HttpMethod.DELETE,
|
||||||
|
HttpEntity.EMPTY,
|
||||||
|
Object.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(401, res.getStatusCode().value());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private <T> HttpEntity<T> withAuth(T body) {
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setBearerAuth(token);
|
||||||
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
return new HttpEntity<>(body, headers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+70
-1
@@ -1,4 +1,73 @@
|
|||||||
package dev.ksan.etfoglasiserver.service;
|
package dev.ksan.etfoglasiserver.service;
|
||||||
|
|
||||||
public class NotificationServiceTest {
|
import com.google.firebase.messaging.FirebaseMessaging;
|
||||||
|
import com.google.firebase.messaging.FirebaseMessagingException;
|
||||||
|
import com.google.firebase.messaging.Message;
|
||||||
|
import com.google.firebase.messaging.MessagingErrorCode;
|
||||||
|
import dev.ksan.etfoglasiserver.model.DeviceToken;
|
||||||
|
import dev.ksan.etfoglasiserver.model.User;
|
||||||
|
import dev.ksan.etfoglasiserver.repository.DeviceTokenRepo;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class NotificationServiceTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
DeviceTokenRepo deviceTokenRepo;
|
||||||
|
@Mock
|
||||||
|
FirebaseMessaging firebaseMessaging;
|
||||||
|
@InjectMocks
|
||||||
|
NotificationService notificationService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sendToUser_sendsMessageForEachToken() throws FirebaseMessagingException {
|
||||||
|
User user = new User();
|
||||||
|
DeviceToken t1 = new DeviceToken(); t1.setFcmToken("token-1");
|
||||||
|
DeviceToken t2 = new DeviceToken(); t2.setFcmToken("token-2");
|
||||||
|
|
||||||
|
when(deviceTokenRepo.findByUserId(user.getId()))
|
||||||
|
.thenReturn(List.of(t1, t2));
|
||||||
|
when(firebaseMessaging.send(any())).thenReturn("msg-id");
|
||||||
|
|
||||||
|
notificationService.sendToUser(user, "Hello", "World");
|
||||||
|
|
||||||
|
verify(firebaseMessaging, times(2)).send(any(Message.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sendToUser_deletesStaleToken_whenUnregistered() throws FirebaseMessagingException {
|
||||||
|
User user = new User();
|
||||||
|
DeviceToken token = new DeviceToken();
|
||||||
|
token.setFcmToken("stale-token");
|
||||||
|
|
||||||
|
when(deviceTokenRepo.findByUserId(user.getId()))
|
||||||
|
.thenReturn(List.of(token));
|
||||||
|
|
||||||
|
FirebaseMessagingException ex = mock(FirebaseMessagingException.class);
|
||||||
|
when(ex.getMessagingErrorCode()).thenReturn(MessagingErrorCode.UNREGISTERED);
|
||||||
|
when(firebaseMessaging.send(any())).thenThrow(ex);
|
||||||
|
|
||||||
|
notificationService.sendToUser(user, "Hello", "World");
|
||||||
|
|
||||||
|
verify(deviceTokenRepo).delete(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sendToUser_doesNothing_whenUserHasNoTokens() throws FirebaseMessagingException {
|
||||||
|
User user = new User();
|
||||||
|
when(deviceTokenRepo.findByUserId(user.getId())).thenReturn(List.of());
|
||||||
|
|
||||||
|
notificationService.sendToUser(user, "Hello", "World");
|
||||||
|
|
||||||
|
verify(firebaseMessaging, never()).send(any());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user