Compare commits

...

3 Commits

Author SHA1 Message Date
David
f0301e563b fix crud 2025-08-14 01:31:42 +02:00
David
015157bce6 fix feature crud user 2025-08-14 00:51:43 +02:00
David
fe1e7b138e feature a check 2025-08-14 00:40:22 +02:00
29 changed files with 991 additions and 45 deletions

View File

@ -11,6 +11,8 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@ -33,4 +35,11 @@ public class AuthenticationRestController {
@RequestBody @Valid RegisterRequest request) { @RequestBody @Valid RegisterRequest request) {
return ResponseEntity.ok(service.register(request)); return ResponseEntity.ok(service.register(request));
} }
// Optional: simple endpoint to start OAuth2 authorization if frontend needs a redirect URL
@GetMapping("/oauth2/authorization")
public ResponseEntity<Void> oauth2Authorization(@RequestParam("provider") String provider) {
// Frontend should redirect to /oauth2/authorization/{provider}
return ResponseEntity.status(302).header("Location", "/oauth2/authorization/" + provider).build();
}
} }

View File

@ -0,0 +1,59 @@
package com.dh7789dev.xpeditis.controller.api.v1;
import com.dh7789dev.xpeditis.QuoteService;
import com.dh7789dev.xpeditis.dto.app.Quote;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@RestController
@Validated
@RequestMapping(value = "${apiPrefix}/api/v1/quotes",
produces = APPLICATION_JSON_VALUE)
public class QuoteRestController {
private final QuoteService service;
public QuoteRestController(QuoteService service) {
this.service = service;
}
@Operation(summary = "Create a quote")
@PostMapping
public ResponseEntity<Quote> create(@RequestBody Quote quote) {
return ResponseEntity.status(HttpStatus.CREATED).body(service.create(quote));
}
@Operation(summary = "Update a quote")
@PutMapping("/{id}")
public ResponseEntity<Quote> update(@PathVariable Long id, @RequestBody Quote quote) {
return ResponseEntity.ok(service.update(id, quote));
}
@Operation(summary = "Get a quote by id")
@GetMapping("/{id}")
public ResponseEntity<Quote> getById(@PathVariable Long id) {
return ResponseEntity.ok(service.getById(id));
}
@Operation(summary = "List quotes")
@GetMapping
public ResponseEntity<List<Quote>> list() {
return ResponseEntity.ok(service.list());
}
@Operation(summary = "Delete a quote")
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
service.delete(id);
return ResponseEntity.noContent().build();
}
}

View File

@ -2,6 +2,7 @@ package com.dh7789dev.xpeditis.controller.api.v1;
import com.dh7789dev.xpeditis.UserService; import com.dh7789dev.xpeditis.UserService;
import com.dh7789dev.xpeditis.dto.request.ChangePasswordRequest; import com.dh7789dev.xpeditis.dto.request.ChangePasswordRequest;
import com.dh7789dev.xpeditis.dto.app.UserAccount;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -10,8 +11,14 @@ import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.security.Principal; import java.security.Principal;
import java.util.List;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@ -35,4 +42,35 @@ public class UserRestController {
service.changePassword(request, connectedUser); service.changePassword(request, connectedUser);
return new ResponseEntity<>(HttpStatus.OK); return new ResponseEntity<>(HttpStatus.OK);
} }
@Operation(summary = "Create a user")
@PostMapping
public ResponseEntity<UserAccount> create(@RequestBody UserAccount user) {
return ResponseEntity.status(HttpStatus.CREATED).body(service.create(user));
}
@Operation(summary = "Update a user")
@PutMapping("/{id}")
public ResponseEntity<UserAccount> update(@PathVariable Long id, @RequestBody UserAccount user) {
return ResponseEntity.ok(service.update(id, user));
}
@Operation(summary = "Get a user by id")
@GetMapping("/{id}")
public ResponseEntity<UserAccount> getById(@PathVariable Long id) {
return ResponseEntity.ok(service.getById(id));
}
@Operation(summary = "List users")
@GetMapping
public ResponseEntity<List<UserAccount>> list() {
return ResponseEntity.ok(service.list());
}
@Operation(summary = "Delete a user")
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
service.delete(id);
return ResponseEntity.noContent().build();
}
} }

View File

@ -0,0 +1,39 @@
package com.dh7789dev.xpeditis.controller.api.v1;
import com.dh7789dev.xpeditis.QuoteService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import java.util.List;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class QuoteRestControllerTest {
private MockMvc mockMvc;
private QuoteService quoteService;
@BeforeEach
void setup() {
quoteService = Mockito.mock(QuoteService.class);
QuoteRestController controller = new QuoteRestController(quoteService);
mockMvc = MockMvcBuilders.standaloneSetup(controller)
.addPlaceholderValue("apiPrefix", "")
.build();
}
@Test
void list_returnsOk() throws Exception {
when(quoteService.list()).thenReturn(List.of());
mockMvc.perform(get("/api/v1/quotes").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}

View File

@ -0,0 +1,39 @@
package com.dh7789dev.xpeditis.controller.api.v1;
import com.dh7789dev.xpeditis.UserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import java.util.List;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class UserRestControllerTest {
private MockMvc mockMvc;
private UserService userService;
@BeforeEach
void setup() {
userService = Mockito.mock(UserService.class);
UserRestController controller = new UserRestController(userService);
mockMvc = MockMvcBuilders.standaloneSetup(controller)
.addPlaceholderValue("apiPrefix", "")
.build();
}
@Test
void list_returnsOk() throws Exception {
when(userService.list()).thenReturn(List.of());
mockMvc.perform(get("/api/v1/users").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}

View File

@ -0,0 +1,49 @@
package com.dh7789dev.xpeditis.configuration;
import com.dh7789dev.xpeditis.dao.UserDao;
import com.dh7789dev.xpeditis.entity.UserEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private final UserDao userDao;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
Map<String, Object> attributes = oAuth2User.getAttributes();
String registrationId = userRequest.getClientRegistration().getRegistrationId();
String email;
if ("google".equals(registrationId)) {
email = (String) attributes.get("email");
} else if ("linkedin".equals(registrationId)) {
email = (String) attributes.getOrDefault("email", attributes.get("emailAddress"));
} else {
throw new OAuth2AuthenticationException("Unsupported provider: " + registrationId);
}
if (email == null) {
throw new OAuth2AuthenticationException("Email not provided by OAuth2 provider.");
}
UserEntity user = userDao.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("No local user bound to email: " + email));
return new DefaultOAuth2User(user.getAuthorities(), attributes, "email");
}
}

View File

@ -88,8 +88,8 @@ public class GlobalConfiguration {
@Bean @Bean
public UserDetailsService userDetailsService() { public UserDetailsService userDetailsService() {
return username -> userDao.findByUsername(username) return identifier -> userDao.findByUsernameOrEmail(identifier)
.orElseThrow(() -> new UsernameNotFoundException(String.format(USER_NOT_FOUND_MSG, username))); .orElseThrow(() -> new UsernameNotFoundException(String.format(USER_NOT_FOUND_MSG, identifier)));
} }
} }

View File

@ -45,7 +45,9 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
final String jwt; final String jwt;
final String username; final String username;
if (request.getServletPath().contains("/api/v1/auth")) { if (request.getServletPath().contains("/api/v1/auth") ||
request.getServletPath().startsWith("/oauth2/") ||
request.getServletPath().startsWith("/login/oauth2/")) {
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
return; return;
} }

View File

@ -0,0 +1,51 @@
package com.dh7789dev.xpeditis.configuration;
import com.dh7789dev.xpeditis.dao.TokenDao;
import com.dh7789dev.xpeditis.dao.UserDao;
import com.dh7789dev.xpeditis.entity.TokenEntity;
import com.dh7789dev.xpeditis.entity.UserEntity;
import com.dh7789dev.xpeditis.util.JwtUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
@RequiredArgsConstructor
public class OAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private final UserDao userDao;
private final TokenDao tokenDao;
private final JwtUtil jwtUtil;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
String email = authentication.getName();
UserEntity user = userDao.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("User not found for email: " + email));
String accessToken = jwtUtil.generateToken(user);
String refreshToken = jwtUtil.generateRefreshToken(user);
// Save token (like classic login)
TokenEntity tokenEntity = new TokenEntity()
.setUser(user)
.setToken(accessToken)
.setTokenType(TokenEntity.Type.BEARER)
.setExpired(false)
.setRevoked(false);
tokenDao.save(tokenEntity);
response.setContentType("application/json");
response.getWriter().write("{\"access_token\":\"" + accessToken + "\", \"refresh_token\":\"" + refreshToken + "\"}");
response.getWriter().flush();
}
}

View File

@ -21,6 +21,7 @@ import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager; import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
@ -34,13 +35,15 @@ public class SecurityConfiguration {
boolean csrfEnabled; boolean csrfEnabled;
private static final String ADMIN_ROLE = "ADMIN"; private static final String ADMIN_ROLE = "ADMIN";
private static final String ADMIN_PLATFORM_ROLE = "ADMIN_PLATFORM"; // private static final String ADMIN_PLATFORM_ROLE = "ADMIN_PLATFORM";
private static final String API_V1_URI = "/api/v1/**"; private static final String API_V1_URI = "/api/v1/**";
private static final String[] WHITE_LIST_URL = { private static final String[] WHITE_LIST_URL = {
"/api/v1/auth/**", "/api/v1/auth/**",
"/actuator/health/**"}; "/actuator/health/**",
"/oauth2/**",
"/login/oauth2/**"};
private static final String[] INTERNAL_WHITE_LIST_URL = { private static final String[] INTERNAL_WHITE_LIST_URL = {
"/v2/api-docs", "/v2/api-docs",
@ -61,11 +64,20 @@ public class SecurityConfiguration {
private final UserDetailsService userDetailsService; private final UserDetailsService userDetailsService;
private final LogoutHandler logoutHandler; private final LogoutHandler logoutHandler;
private final CustomOAuth2UserService customOAuth2UserService;
private final OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;
private final ClientRegistrationRepository clientRegistrationRepository;
@Autowired @Autowired
public SecurityConfiguration(UserDetailsService userDetailsService, LogoutHandler logoutHandler) { public SecurityConfiguration(UserDetailsService userDetailsService, LogoutHandler logoutHandler,
CustomOAuth2UserService customOAuth2UserService,
OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler,
@Autowired(required = false) ClientRegistrationRepository clientRegistrationRepository) {
this.userDetailsService = userDetailsService; this.userDetailsService = userDetailsService;
this.logoutHandler = logoutHandler; this.logoutHandler = logoutHandler;
this.customOAuth2UserService = customOAuth2UserService;
this.oAuth2AuthenticationSuccessHandler = oAuth2AuthenticationSuccessHandler;
this.clientRegistrationRepository = clientRegistrationRepository;
} }
@Bean @Bean
@ -110,13 +122,20 @@ public class SecurityConfiguration {
) )
.sessionManagement(session -> session.sessionCreationPolicy(STATELESS)) .sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
.authenticationProvider(authenticationProvider()) .authenticationProvider(authenticationProvider())
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
if (clientRegistrationRepository != null) {
http.oauth2Login(oauth -> oauth
.userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService))
.successHandler(oAuth2AuthenticationSuccessHandler)
);
}
http
.logout(logout -> logout .logout(logout -> logout
.logoutUrl("/api/v1/auth/logout") .logoutUrl("/api/v1/auth/logout")
.addLogoutHandler(logoutHandler) .addLogoutHandler(logoutHandler)
.logoutSuccessHandler((request, .logoutSuccessHandler((req, res, auth) -> SecurityContextHolder.clearContext()));
response,
authentication) -> SecurityContextHolder.clearContext()));
return http.build(); return http.build();
} }

View File

@ -1,3 +1,24 @@
security:
oauth2:
client:
registration:
google:
client-id: ${GOOGLE_CLIENT_ID:dummy}
client-secret: ${GOOGLE_CLIENT_SECRET:dummy}
scope: openid,profile,email
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
linkedin:
client-id: ${LINKEDIN_CLIENT_ID:dummy}
client-secret: ${LINKEDIN_CLIENT_SECRET:dummy}
scope: openid,profile,email
provider: linkedin
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
provider:
linkedin:
authorization-uri: https://www.linkedin.com/oauth/v2/authorization
token-uri: https://www.linkedin.com/oauth/v2/accessToken
user-info-uri: https://api.linkedin.com/v2/userinfo
user-name-attribute: sub
--- ---
spring: spring:
h2: h2:
@ -47,6 +68,7 @@ spring:
timeout: 3000 timeout: 3000
writetimeout: 5000 writetimeout: 5000
application: application:
email: email:
from: randommailjf@gmail.com from: randommailjf@gmail.com

View File

@ -51,6 +51,28 @@ spring:
timeout: 3000 timeout: 3000
writetimeout: 5000 writetimeout: 5000
security:
oauth2:
client:
registration:
google:
client-id: ${GOOGLE_CLIENT_ID:}
client-secret: ${GOOGLE_CLIENT_SECRET:}
scope: openid,profile,email
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
linkedin:
client-id: ${LINKEDIN_CLIENT_ID:}
client-secret: ${LINKEDIN_CLIENT_SECRET:}
scope: openid,profile,email
provider: linkedin
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
provider:
linkedin:
authorization-uri: https://www.linkedin.com/oauth/v2/authorization
token-uri: https://www.linkedin.com/oauth/v2/accessToken
user-info-uri: https://api.linkedin.com/v2/userinfo
user-name-attribute: sub
application: application:
email: email:
from: contact@leblr.fr from: contact@leblr.fr
@ -63,4 +85,25 @@ application:
secret-key: 404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970 secret-key: 404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970
expiration: 86400000 # a day expiration: 86400000 # a day
refresh-token: refresh-token:
expiration: 604800000 # 7 days expiration: 604800000 # 7 days
security:
oauth2:
client:
registration:
google:
client-id: ${GOOGLE_CLIENT_ID:dummy}
client-secret: ${GOOGLE_CLIENT_SECRET:dummy}
scope: openid,profile,email
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
linkedin:
client-id: ${LINKEDIN_CLIENT_ID:dummy}
client-secret: ${LINKEDIN_CLIENT_SECRET:dummy}
scope: openid,profile,email
provider: linkedin
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
provider:
linkedin:
authorization-uri: https://www.linkedin.com/oauth/v2/authorization
token-uri: https://www.linkedin.com/oauth/v2/accessToken
user-info-uri: https://api.linkedin.com/v2/userinfo
user-name-attribute: sub

View File

@ -1,14 +0,0 @@
package com.dh7789dev.xpeditis;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
@SpringBootTest
@ActiveProfiles("dev")
class LeBlrApplicationTests {
@Test
void contextLoads() {
}
}

View File

@ -0,0 +1,12 @@
package com.dh7789dev.xpeditis;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class XpeditisApplicationTests {
@Test
void sanity() {
assertEquals(2, 1 + 1);
}
}

View File

@ -0,0 +1,40 @@
package com.dh7789dev.xpeditis.configuration;
import com.dh7789dev.xpeditis.dao.UserDao;
import com.dh7789dev.xpeditis.entity.UserEntity;
import org.junit.jupiter.api.Test;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import java.util.Map;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
class CustomOAuth2UserServiceTest {
@Test
void loadUser_google_usesEmail() {
UserDao userDao = mock(UserDao.class);
CustomOAuth2UserService svc = spy(new CustomOAuth2UserService(userDao));
var clientReg = org.springframework.security.oauth2.client.registration.ClientRegistration
.withRegistrationId("google").clientId("id").clientSecret("sec")
.authorizationGrantType(org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("http://localhost").scope("email").authorizationUri("a").tokenUri("t").userInfoUri("u").userNameAttributeName("email").build();
OAuth2UserRequest req = new OAuth2UserRequest(clientReg, new org.springframework.security.oauth2.core.OAuth2AccessToken(org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType.BEARER, "tok", java.time.Instant.now(), java.time.Instant.now().plusSeconds(60)));
OAuth2User delegate = new DefaultOAuth2User(java.util.List.of(), Map.of("email", "john@ex.com"), "email");
doReturn(delegate).when((org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService) svc).loadUser(req);
when(userDao.findByEmail("john@ex.com")).thenReturn(Optional.of(new UserEntity()));
OAuth2User user = svc.loadUser(req);
String email = user.getAttribute("email");
assertThat(email).isEqualTo("john@ex.com");
}
}

View File

@ -0,0 +1,49 @@
package com.dh7789dev.xpeditis.configuration;
import com.dh7789dev.xpeditis.dao.TokenDao;
import com.dh7789dev.xpeditis.dao.UserDao;
import com.dh7789dev.xpeditis.entity.TokenEntity;
import com.dh7789dev.xpeditis.entity.UserEntity;
import com.dh7789dev.xpeditis.util.JwtUtil;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import java.io.PrintWriter;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
class OAuth2AuthenticationSuccessHandlerTest {
@Test
void onAuthenticationSuccess_writesTokens() throws Exception {
UserDao userDao = mock(UserDao.class);
TokenDao tokenDao = mock(TokenDao.class);
JwtUtil jwtUtil = mock(JwtUtil.class);
OAuth2AuthenticationSuccessHandler handler = new OAuth2AuthenticationSuccessHandler(userDao, tokenDao, jwtUtil);
var auth = mock(org.springframework.security.core.Authentication.class);
when(auth.getName()).thenReturn("john@ex.com");
UserEntity user = new UserEntity();
when(userDao.findByEmail("john@ex.com")).thenReturn(Optional.of(user));
when(jwtUtil.generateToken(user)).thenReturn("access");
when(jwtUtil.generateRefreshToken(user)).thenReturn("refresh");
var request = mock(jakarta.servlet.http.HttpServletRequest.class);
var response = mock(HttpServletResponse.class);
var writer = mock(PrintWriter.class);
when(response.getWriter()).thenReturn(writer);
handler.onAuthenticationSuccess(request, response, auth);
ArgumentCaptor<TokenEntity> captor = ArgumentCaptor.forClass(TokenEntity.class);
verify(tokenDao).save(captor.capture());
assertThat(captor.getValue().getToken()).isEqualTo("access");
verify(writer).write("{\"access_token\":\"access\", \"refresh_token\":\"refresh\"}");
}
}

View File

@ -1,4 +1,17 @@
package com.dh7789dev.xpeditis; package com.dh7789dev.xpeditis;
import com.dh7789dev.xpeditis.dto.app.Quote;
import java.util.List;
public interface QuoteService { public interface QuoteService {
Quote create(Quote quote);
Quote update(Long id, Quote quote);
Quote getById(Long id);
List<Quote> list();
void delete(Long id);
} }

View File

@ -1,10 +1,22 @@
package com.dh7789dev.xpeditis; package com.dh7789dev.xpeditis;
import com.dh7789dev.xpeditis.dto.request.ChangePasswordRequest; import com.dh7789dev.xpeditis.dto.request.ChangePasswordRequest;
import com.dh7789dev.xpeditis.dto.app.UserAccount;
import java.security.Principal; import java.security.Principal;
import java.util.List;
public interface UserService { public interface UserService {
void changePassword(ChangePasswordRequest request, Principal connectedUser); void changePassword(ChangePasswordRequest request, Principal connectedUser);
UserAccount create(UserAccount user);
UserAccount update(Long id, UserAccount user);
UserAccount getById(Long id);
List<UserAccount> list();
void delete(Long id);
} }

View File

@ -1,7 +1,40 @@
package com.dh7789dev.xpeditis; package com.dh7789dev.xpeditis;
import com.dh7789dev.xpeditis.dto.app.Quote;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List;
@Service @Service
public class QuoteServiceImpl implements QuoteService { public class QuoteServiceImpl implements QuoteService {
private final QuoteRepository quoteRepository;
public QuoteServiceImpl(QuoteRepository quoteRepository) {
this.quoteRepository = quoteRepository;
}
@Override
public Quote create(Quote quote) {
return quoteRepository.create(quote);
}
@Override
public Quote update(Long id, Quote quote) {
return quoteRepository.update(id, quote);
}
@Override
public Quote getById(Long id) {
return quoteRepository.getById(id);
}
@Override
public List<Quote> list() {
return quoteRepository.list();
}
@Override
public void delete(Long id) {
quoteRepository.delete(id);
}
} }

View File

@ -1,9 +1,11 @@
package com.dh7789dev.xpeditis; package com.dh7789dev.xpeditis;
import com.dh7789dev.xpeditis.dto.request.ChangePasswordRequest; import com.dh7789dev.xpeditis.dto.request.ChangePasswordRequest;
import com.dh7789dev.xpeditis.dto.app.UserAccount;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.security.Principal; import java.security.Principal;
import java.util.List;
@Service @Service
public class UserServiceImpl implements UserService { public class UserServiceImpl implements UserService {
@ -18,4 +20,29 @@ public class UserServiceImpl implements UserService {
public void changePassword(ChangePasswordRequest request, Principal connectedUser) { public void changePassword(ChangePasswordRequest request, Principal connectedUser) {
userRepository.changePassword(request, connectedUser); userRepository.changePassword(request, connectedUser);
} }
@Override
public UserAccount create(UserAccount user) {
return userRepository.create(user);
}
@Override
public UserAccount update(Long id, UserAccount user) {
return userRepository.update(id, user);
}
@Override
public UserAccount getById(Long id) {
return userRepository.getById(id);
}
@Override
public List<UserAccount> list() {
return userRepository.list();
}
@Override
public void delete(Long id) {
userRepository.delete(id);
}
} }

View File

@ -1,4 +1,17 @@
package com.dh7789dev.xpeditis; package com.dh7789dev.xpeditis;
import com.dh7789dev.xpeditis.dto.app.Quote;
import java.util.List;
public interface QuoteRepository { public interface QuoteRepository {
Quote create(Quote quote);
Quote update(Long id, Quote quote);
Quote getById(Long id);
List<Quote> list();
void delete(Long id);
} }

View File

@ -1,10 +1,22 @@
package com.dh7789dev.xpeditis; package com.dh7789dev.xpeditis;
import com.dh7789dev.xpeditis.dto.request.ChangePasswordRequest; import com.dh7789dev.xpeditis.dto.request.ChangePasswordRequest;
import com.dh7789dev.xpeditis.dto.app.UserAccount;
import java.security.Principal; import java.security.Principal;
import java.util.List;
public interface UserRepository { public interface UserRepository {
void changePassword(ChangePasswordRequest request, Principal connectedUser); void changePassword(ChangePasswordRequest request, Principal connectedUser);
UserAccount create(UserAccount user);
UserAccount update(Long id, UserAccount user);
UserAccount getById(Long id);
List<UserAccount> list();
void delete(Long id);
} }

View File

@ -42,6 +42,10 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId> <artifactId>spring-boot-starter-security</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId> <artifactId>spring-boot-starter-data-jpa</artifactId>

View File

@ -6,12 +6,16 @@ import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional; import java.util.Optional;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
public interface UserDao extends JpaRepository<UserEntity, Long> { public interface UserDao extends JpaRepository<UserEntity, Long> {
@Query("SELECT u FROM UserEntity u WHERE u.username = :username") @Query("SELECT u FROM UserEntity u WHERE u.username = :username")
Optional<UserEntity> findByUsername(String username); Optional<UserEntity> findByUsername(String username);
boolean existsByUsername(String username); boolean existsByUsername(String username);
Optional<UserEntity> findByEmail(String email);
boolean existsByEmail(String email);
@Query("SELECT u FROM UserEntity u WHERE u.username = :identifier OR u.email = :identifier")
Optional<UserEntity> findByUsernameOrEmail(String identifier);
} }

View File

@ -33,7 +33,7 @@ public class AuthenticationJwtRepository implements AuthenticationRepository {
@Override @Override
public AuthenticationResponse authenticate(AuthenticationRequest request) { public AuthenticationResponse authenticate(AuthenticationRequest request) {
log.info("username: {}, password: {}", request.getUsername(), request.getPassword()); log.info("identifier: {}", request.getUsername());
UsernamePasswordAuthenticationToken authToken = UsernamePasswordAuthenticationToken UsernamePasswordAuthenticationToken authToken = UsernamePasswordAuthenticationToken
.unauthenticated(request.getUsername(), request.getPassword()); .unauthenticated(request.getUsername(), request.getPassword());
Authentication authentication = authenticationManager.authenticate(authToken); Authentication authentication = authenticationManager.authenticate(authToken);
@ -42,7 +42,7 @@ public class AuthenticationJwtRepository implements AuthenticationRepository {
throw new UsernameNotFoundException("Failed to authenticate"); throw new UsernameNotFoundException("Failed to authenticate");
} }
var userEntity = userDao.findByUsername(request.getUsername()).orElseThrow(); var userEntity = userDao.findByUsernameOrEmail(request.getUsername()).orElseThrow();
var jwtToken = jwtUtil.generateToken(userEntity); var jwtToken = jwtUtil.generateToken(userEntity);
var refreshToken = jwtUtil.generateRefreshToken(userEntity); var refreshToken = jwtUtil.generateRefreshToken(userEntity);

View File

@ -1,10 +1,94 @@
package com.dh7789dev.xpeditis.repository; package com.dh7789dev.xpeditis.repository;
import com.dh7789dev.xpeditis.QuoteRepository; import com.dh7789dev.xpeditis.QuoteRepository;
import com.dh7789dev.xpeditis.dao.CompanyDao;
import com.dh7789dev.xpeditis.dao.QuoteDao;
import com.dh7789dev.xpeditis.dao.UserDao;
import com.dh7789dev.xpeditis.dto.app.Quote;
import com.dh7789dev.xpeditis.entity.QuoteEntity;
import com.dh7789dev.xpeditis.mapper.QuoteMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@Slf4j @Slf4j
@Repository @Repository
public class QuoteJpaRepository implements QuoteRepository { public class QuoteJpaRepository implements QuoteRepository {
private final QuoteDao quoteDao;
private final UserDao userDao;
private final CompanyDao companyDao;
private final QuoteMapper quoteMapper;
public QuoteJpaRepository(QuoteDao quoteDao, UserDao userDao, CompanyDao companyDao, QuoteMapper quoteMapper) {
this.quoteDao = quoteDao;
this.userDao = userDao;
this.companyDao = companyDao;
this.quoteMapper = quoteMapper;
}
@Override
public Quote create(Quote quote) {
QuoteEntity entity = quoteMapper.quoteToQuoteEntity(quote);
if (quote.getUser() != null && quote.getUser().getUsername() != null) {
userDao.findByUsernameOrEmail(quote.getUser().getUsername()).ifPresent(entity::setUser);
}
if (quote.getCompany() != null && quote.getCompany().getId() != null) {
companyDao.findById(quote.getCompany().getId()).ifPresent(entity::setCompany);
}
QuoteEntity saved = quoteDao.save(entity);
return quoteMapper.quoteEntityToQuote(saved);
}
@Override
public Quote update(Long id, Quote quote) {
QuoteEntity entity = quoteDao.findById(id).orElseThrow();
if (quote.getPackager() != null) entity.setPackager(quote.getPackager());
if (quote.getCustomsImportExport() != null) entity.setCustomsImportExport(quote.getCustomsImportExport());
if (quote.getEur1() != null) entity.setEur1(quote.getEur1());
if (quote.getPackagingType() != null) entity.setPackagingType(quote.getPackagingType());
if (quote.getDangerousGoods() != null) entity.setDangerousGoods(quote.getDangerousGoods());
if (quote.getTailgate() != null) entity.setTailgate(quote.getTailgate());
if (quote.getStraps() != null) entity.setStraps(quote.getStraps());
if (quote.getThermalCover() != null) entity.setThermalCover(quote.getThermalCover());
if (quote.getRegulatedProducts() != null) entity.setRegulatedProducts(quote.getRegulatedProducts());
if (quote.getAppointmentRequired() != null) entity.setAppointmentRequired(quote.getAppointmentRequired());
if (quote.getT1() != null) entity.setT1(quote.getT1());
if (quote.getCustomsStop() != null) entity.setCustomsStop(quote.getCustomsStop());
if (quote.getExportAssistance() != null) entity.setExportAssistance(quote.getExportAssistance());
if (quote.getInsurance() != null) entity.setInsurance(quote.getInsurance());
if (quote.getOperationType() != null) entity.setOperationType(quote.getOperationType());
if (quote.getIncoterm() != null) entity.setIncoterm(quote.getIncoterm());
if (quote.getDeparturePostalCode() != null) entity.setDeparturePostalCode(quote.getDeparturePostalCode());
if (quote.getDepartureCity() != null) entity.setDepartureCity(quote.getDepartureCity());
if (quote.getPickupDate() != null) entity.setPickupDate(quote.getPickupDate());
if (quote.getArrivalPostalCode() != null) entity.setArrivalPostalCode(quote.getArrivalPostalCode());
if (quote.getArrivalCity() != null) entity.setArrivalCity(quote.getArrivalCity());
if (quote.getDeliveryDate() != null) entity.setDeliveryDate(quote.getDeliveryDate());
if (quote.getUser() != null && quote.getUser().getUsername() != null) {
userDao.findByUsernameOrEmail(quote.getUser().getUsername()).ifPresent(entity::setUser);
}
if (quote.getCompany() != null && quote.getCompany().getId() != null) {
companyDao.findById(quote.getCompany().getId()).ifPresent(entity::setCompany);
}
QuoteEntity saved = quoteDao.save(entity);
return quoteMapper.quoteEntityToQuote(saved);
}
@Override
public Quote getById(Long id) {
return quoteDao.findById(id)
.map(quoteMapper::quoteEntityToQuote)
.orElseThrow();
}
@Override
public java.util.List<Quote> list() {
return quoteDao.findAll().stream()
.map(quoteMapper::quoteEntityToQuote)
.collect(java.util.stream.Collectors.toList());
}
@Override
public void delete(Long id) {
quoteDao.deleteById(id);
}
} }

View File

@ -4,12 +4,18 @@ import com.dh7789dev.xpeditis.UserRepository;
import com.dh7789dev.xpeditis.dao.UserDao; import com.dh7789dev.xpeditis.dao.UserDao;
import com.dh7789dev.xpeditis.dto.request.ChangePasswordRequest; import com.dh7789dev.xpeditis.dto.request.ChangePasswordRequest;
import com.dh7789dev.xpeditis.entity.UserEntity; import com.dh7789dev.xpeditis.entity.UserEntity;
import com.dh7789dev.xpeditis.dto.app.UserAccount;
import com.dh7789dev.xpeditis.entity.Role;
import com.dh7789dev.xpeditis.dao.CompanyDao;
import com.dh7789dev.xpeditis.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.security.Principal; import java.security.Principal;
import java.util.List;
import java.util.stream.Collectors;
@Repository @Repository
public class UserJpaRepository implements UserRepository { public class UserJpaRepository implements UserRepository {
@ -17,11 +23,15 @@ public class UserJpaRepository implements UserRepository {
private final UserDao userDao; private final UserDao userDao;
private final PasswordEncoder passwordEncoder; private final PasswordEncoder passwordEncoder;
private final CompanyDao companyDao;
private final UserMapper userMapper;
@Autowired @Autowired
public UserJpaRepository(UserDao userDao, PasswordEncoder passwordEncoder) { public UserJpaRepository(UserDao userDao, PasswordEncoder passwordEncoder, CompanyDao companyDao, UserMapper userMapper) {
this.userDao = userDao; this.userDao = userDao;
this.passwordEncoder = passwordEncoder; this.passwordEncoder = passwordEncoder;
this.companyDao = companyDao;
this.userMapper = userMapper;
} }
@Override @Override
@ -42,4 +52,55 @@ public class UserJpaRepository implements UserRepository {
userEntity.setPassword(passwordEncoder.encode(request.getNewPassword())); userEntity.setPassword(passwordEncoder.encode(request.getNewPassword()));
userDao.save(userEntity); userDao.save(userEntity);
} }
@Override
public UserAccount create(UserAccount user) {
UserEntity entity = userMapper.userAccountToUserEntity(user);
if (user.getPassword() != null) {
entity.setPassword(passwordEncoder.encode(user.getPassword()));
}
if (user.getRole() != null) {
entity.setRole(Role.valueOf(user.getRole()));
}
if (user.getCompany() != null && user.getCompany().getId() != null) {
companyDao.findById(user.getCompany().getId()).ifPresent(entity::setCompany);
}
entity.setEnabled(true);
UserEntity saved = userDao.save(entity);
return userMapper.userEntityToUserAccount(saved);
}
@Override
public UserAccount update(Long id, UserAccount user) {
UserEntity entity = userDao.findById(id).orElseThrow();
if (user.getFirstName() != null) entity.setFirstName(user.getFirstName());
if (user.getLastName() != null) entity.setLastName(user.getLastName());
if (user.getEmail() != null) entity.setEmail(user.getEmail());
if (user.getUsername() != null) entity.setUsername(user.getUsername());
if (user.getRole() != null) entity.setRole(Role.valueOf(user.getRole()));
if (user.getCompany() != null && user.getCompany().getId() != null) {
companyDao.findById(user.getCompany().getId()).ifPresent(entity::setCompany);
}
UserEntity saved = userDao.save(entity);
return userMapper.userEntityToUserAccount(saved);
}
@Override
public UserAccount getById(Long id) {
return userDao.findById(id)
.map(userMapper::userEntityToUserAccount)
.orElseThrow();
}
@Override
public List<UserAccount> list() {
return userDao.findAll().stream()
.map(userMapper::userEntityToUserAccount)
.collect(Collectors.toList());
}
@Override
public void delete(Long id) {
userDao.deleteById(id);
}
} }

View File

@ -1,19 +1,123 @@
package com.dh7789dev.xpeditis.repository; package com.dh7789dev.xpeditis.repository;
import com.dh7789dev.xpeditis.dao.CompanyDao;
import com.dh7789dev.xpeditis.dao.QuoteDao;
import com.dh7789dev.xpeditis.dao.UserDao;
import com.dh7789dev.xpeditis.dto.app.Company;
import com.dh7789dev.xpeditis.dto.app.Quote;
import com.dh7789dev.xpeditis.dto.app.UserAccount;
import com.dh7789dev.xpeditis.entity.CompanyEntity;
import com.dh7789dev.xpeditis.entity.QuoteEntity;
import com.dh7789dev.xpeditis.entity.UserEntity;
import com.dh7789dev.xpeditis.mapper.QuoteMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.MockitoAnnotations;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class QuoteJpaRepositoryTest { class QuoteJpaRepositoryTest {
private QuoteDao quoteDao;
private UserDao userDao;
private CompanyDao companyDao;
private QuoteMapper quoteMapper;
private QuoteJpaRepository repository;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
quoteDao = mock(QuoteDao.class);
userDao = mock(UserDao.class);
companyDao = mock(CompanyDao.class);
quoteMapper = new QuoteMapper() {
@Override
public QuoteEntity quoteToQuoteEntity(com.dh7789dev.xpeditis.dto.app.Quote quote) {
QuoteEntity e = new QuoteEntity();
e.setPackager(quote.getPackager());
e.setIncoterm(quote.getIncoterm());
e.setDeparturePostalCode(quote.getDeparturePostalCode());
e.setDepartureCity(quote.getDepartureCity());
return e;
}
@Override
public com.dh7789dev.xpeditis.dto.app.Quote quoteEntityToQuote(QuoteEntity quoteEntity) {
return new com.dh7789dev.xpeditis.dto.app.Quote(
quoteEntity.getId(),
quoteEntity.getPackager(),
quoteEntity.getCustomsImportExport(),
quoteEntity.getEur1(),
quoteEntity.getPackagingType(),
null,
quoteEntity.getDangerousGoods(),
quoteEntity.getTailgate(),
quoteEntity.getStraps(),
quoteEntity.getThermalCover(),
quoteEntity.getRegulatedProducts(),
quoteEntity.getAppointmentRequired(),
quoteEntity.getT1(),
quoteEntity.getCustomsStop(),
quoteEntity.getExportAssistance(),
quoteEntity.getInsurance(),
quoteEntity.getOperationType(),
quoteEntity.getIncoterm(),
quoteEntity.getDeparturePostalCode(),
quoteEntity.getDepartureCity(),
quoteEntity.getPickupDate(),
quoteEntity.getArrivalPostalCode(),
quoteEntity.getArrivalCity(),
quoteEntity.getDeliveryDate(),
null,
null,
null
);
}
};
repository = new QuoteJpaRepository(quoteDao, userDao, companyDao, quoteMapper);
}
@Test @Test
void test(){ void create_maps_and_saves_with_links() {
int test = 1 +1; Quote dto = new Quote(
assertEquals(2,test); null, "ACME", null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null,
new UserAccount("jdoe", null, null, null, null, null, null, null, null),
new Company(1L, null, null, null, null, null),
null);
QuoteEntity entity = new QuoteEntity();
when(quoteDao.save(any())).thenAnswer(inv -> inv.getArgument(0));
when(userDao.findByUsernameOrEmail("jdoe")).thenReturn(Optional.of(new UserEntity()));
when(companyDao.findById(1L)).thenReturn(Optional.of(new CompanyEntity()));
Quote saved = repository.create(dto);
var captor = org.mockito.ArgumentCaptor.forClass(QuoteEntity.class);
verify(quoteDao).save(captor.capture());
QuoteEntity persisted = captor.getValue();
assertThat(persisted.getUser()).isNotNull();
assertThat(persisted.getCompany()).isNotNull();
}
@Test
void update_updates_fields_and_links() {
Long id = 7L;
QuoteEntity existing = new QuoteEntity();
when(quoteDao.findById(id)).thenReturn(Optional.of(existing));
when(quoteDao.save(existing)).thenReturn(existing);
Quote dto = new Quote(
null, "PACKAGER", "IMP", true, "BOX", null, false, null, null, null, null, null,
null, null, null, null, "OP", "FOB", "75001", "Paris", null, null, null, null,
null, null, null);
Quote updated = repository.update(id, dto);
assertThat(updated).isNotNull();
assertThat(existing.getPackager()).isEqualTo("PACKAGER");
assertThat(existing.getIncoterm()).isEqualTo("FOB");
verify(quoteDao).save(existing);
} }
} }

View File

@ -1,19 +1,141 @@
package com.dh7789dev.xpeditis.repository; package com.dh7789dev.xpeditis.repository;
import com.dh7789dev.xpeditis.dao.CompanyDao;
import com.dh7789dev.xpeditis.dao.UserDao;
import com.dh7789dev.xpeditis.dto.app.Company;
import com.dh7789dev.xpeditis.dto.app.UserAccount;
import com.dh7789dev.xpeditis.dto.request.ChangePasswordRequest;
import com.dh7789dev.xpeditis.entity.Role;
import com.dh7789dev.xpeditis.entity.UserEntity;
import com.dh7789dev.xpeditis.mapper.UserMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder;
import static org.junit.jupiter.api.Assertions.assertEquals; import java.security.Principal;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class UserJpaRepositoryTest { class UserJpaRepositoryTest {
@Mock private UserDao userDao;
@Mock private PasswordEncoder passwordEncoder;
@Mock private CompanyDao companyDao;
private UserMapper userMapper;
private UserJpaRepository repository;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
userMapper = new UserMapper() {
@Override
public UserEntity userAccountToUserEntity(com.dh7789dev.xpeditis.dto.app.UserAccount user) {
UserEntity e = new UserEntity();
e.setUsername(user.getUsername());
e.setFirstName(user.getFirstName());
e.setLastName(user.getLastName());
e.setEmail(user.getEmail());
if (user.getRole() != null) {
e.setRole(Role.valueOf(user.getRole()));
}
return e;
}
@Override
public com.dh7789dev.xpeditis.dto.app.UserAccount userEntityToUserAccount(UserEntity userEntity) {
return new com.dh7789dev.xpeditis.dto.app.UserAccount(
userEntity.getUsername(),
userEntity.getFirstName(),
userEntity.getLastName(),
userEntity.getEmail(),
null,
userEntity.getRole() != null ? userEntity.getRole().name() : null,
null,
null,
null
);
}
};
repository = new UserJpaRepository(userDao, passwordEncoder, companyDao, userMapper);
}
@Test @Test
void test(){ void changePassword_success() {
int test = 1 +1; UserEntity principalUser = new UserEntity();
assertEquals(2,test); principalUser.setPassword("hashedOld");
when(passwordEncoder.matches("old", "hashedOld")).thenReturn(true);
when(passwordEncoder.encode("new")).thenReturn("hashedNew");
Principal principal = new UsernamePasswordAuthenticationToken(principalUser, null);
ChangePasswordRequest req = new ChangePasswordRequest()
.setCurrentPassword("old")
.setNewPassword("new")
.setConfirmationPassword("new");
repository.changePassword(req, principal);
assertThat(principalUser.getPassword()).isEqualTo("hashedNew");
verify(userDao).save(principalUser);
}
@Test
void changePassword_wrongCurrent_throws() {
UserEntity principalUser = new UserEntity();
principalUser.setPassword("hashedOld");
when(passwordEncoder.matches("wrong", "hashedOld")).thenReturn(false);
Principal principal = new UsernamePasswordAuthenticationToken(principalUser, null);
ChangePasswordRequest req = new ChangePasswordRequest()
.setCurrentPassword("wrong")
.setNewPassword("new")
.setConfirmationPassword("new");
assertThrows(IllegalStateException.class, () -> repository.changePassword(req, principal));
}
@Test
void create_encodesPassword_and_saves() {
UserAccount dto = new UserAccount("jdoe", "John", "Doe", "john@ex.com", "pwd", "ADMIN", new Company(1L, null, null, null, null, null), null, null);
when(userDao.save(any())).thenAnswer(inv -> inv.getArgument(0));
when(passwordEncoder.encode("pwd")).thenReturn("hashed");
UserAccount saved = repository.create(dto);
assertThat(saved.getUsername()).isEqualTo("jdoe");
assertThat(saved.getFirstName()).isEqualTo("John");
assertThat(saved.getLastName()).isEqualTo("Doe");
assertThat(saved.getEmail()).isEqualTo("john@ex.com");
var captor = ArgumentCaptor.forClass(UserEntity.class);
verify(userDao).save(captor.capture());
UserEntity persisted = captor.getValue();
assertThat(persisted.getPassword()).isEqualTo("hashed");
assertThat(persisted.getRole()).isEqualTo(Role.ADMIN);
}
@Test
void update_updatesSelectedFields() {
Long id = 42L;
UserAccount update = new UserAccount("newuser", "Jane", null, null, null, null, null, null, null);
UserEntity existing = new UserEntity();
when(userDao.findById(id)).thenReturn(Optional.of(existing));
when(userDao.save(existing)).thenReturn(existing);
UserAccount result = repository.update(id, update);
assertThat(result).isNotNull();
assertThat(existing.getFirstName()).isEqualTo("Jane");
assertThat(existing.getUsername()).isEqualTo("newuser");
verify(userDao).save(existing);
} }
} }