From f0301e563bee0ab9d6d7b3d48c3814beb3f81dca Mon Sep 17 00:00:00 2001 From: David Date: Thu, 14 Aug 2025 01:31:42 +0200 Subject: [PATCH] fix crud --- .../api/v1/QuoteRestController.java | 59 ++++++++ .../api/v1/QuoteRestControllerTest.java | 39 +++++ .../api/v1/UserRestControllerTest.java | 39 +++++ .../configuration/SecurityConfiguration.java | 21 ++- .../src/main/resources/application-dev.yml | 42 +++--- .../src/main/resources/application-prod.yml | 23 ++- .../xpeditis/LeBlrApplicationTests.java | 14 -- .../xpeditis/XpeditisApplicationTests.java | 12 ++ .../CustomOAuth2UserServiceTest.java | 40 +++++ ...Auth2AuthenticationSuccessHandlerTest.java | 49 +++++++ .../com/dh7789dev/xpeditis/QuoteService.java | 13 ++ .../dh7789dev/xpeditis/QuoteServiceImpl.java | 33 +++++ .../dh7789dev/xpeditis/QuoteRepository.java | 13 ++ .../repository/QuoteJpaRepository.java | 84 +++++++++++ .../repository/QuoteJpaRepositoryTest.java | 120 ++++++++++++++- .../repository/UserJpaRepositoryTest.java | 138 +++++++++++++++++- 16 files changed, 681 insertions(+), 58 deletions(-) create mode 100644 application/src/main/java/com/dh7789dev/xpeditis/controller/api/v1/QuoteRestController.java create mode 100644 application/src/test/java/com/dh7789dev/xpeditis/controller/api/v1/QuoteRestControllerTest.java create mode 100644 application/src/test/java/com/dh7789dev/xpeditis/controller/api/v1/UserRestControllerTest.java delete mode 100755 bootstrap/src/test/java/com/dh7789dev/xpeditis/LeBlrApplicationTests.java create mode 100755 bootstrap/src/test/java/com/dh7789dev/xpeditis/XpeditisApplicationTests.java create mode 100644 bootstrap/src/test/java/com/dh7789dev/xpeditis/configuration/CustomOAuth2UserServiceTest.java create mode 100644 bootstrap/src/test/java/com/dh7789dev/xpeditis/configuration/OAuth2AuthenticationSuccessHandlerTest.java diff --git a/application/src/main/java/com/dh7789dev/xpeditis/controller/api/v1/QuoteRestController.java b/application/src/main/java/com/dh7789dev/xpeditis/controller/api/v1/QuoteRestController.java new file mode 100644 index 0000000..f2c86f2 --- /dev/null +++ b/application/src/main/java/com/dh7789dev/xpeditis/controller/api/v1/QuoteRestController.java @@ -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 create(@RequestBody Quote quote) { + return ResponseEntity.status(HttpStatus.CREATED).body(service.create(quote)); + } + + @Operation(summary = "Update a quote") + @PutMapping("/{id}") + public ResponseEntity 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 getById(@PathVariable Long id) { + return ResponseEntity.ok(service.getById(id)); + } + + @Operation(summary = "List quotes") + @GetMapping + public ResponseEntity> list() { + return ResponseEntity.ok(service.list()); + } + + @Operation(summary = "Delete a quote") + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable Long id) { + service.delete(id); + return ResponseEntity.noContent().build(); + } +} + + diff --git a/application/src/test/java/com/dh7789dev/xpeditis/controller/api/v1/QuoteRestControllerTest.java b/application/src/test/java/com/dh7789dev/xpeditis/controller/api/v1/QuoteRestControllerTest.java new file mode 100644 index 0000000..f308c6c --- /dev/null +++ b/application/src/test/java/com/dh7789dev/xpeditis/controller/api/v1/QuoteRestControllerTest.java @@ -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()); + } +} + + diff --git a/application/src/test/java/com/dh7789dev/xpeditis/controller/api/v1/UserRestControllerTest.java b/application/src/test/java/com/dh7789dev/xpeditis/controller/api/v1/UserRestControllerTest.java new file mode 100644 index 0000000..13705d5 --- /dev/null +++ b/application/src/test/java/com/dh7789dev/xpeditis/controller/api/v1/UserRestControllerTest.java @@ -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()); + } +} + + diff --git a/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/SecurityConfiguration.java b/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/SecurityConfiguration.java index de51415..032018b 100755 --- a/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/SecurityConfiguration.java +++ b/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/SecurityConfiguration.java @@ -21,6 +21,7 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 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.web.util.matcher.AntPathRequestMatcher.antMatcher; @@ -65,15 +66,18 @@ public class SecurityConfiguration { private final LogoutHandler logoutHandler; private final CustomOAuth2UserService customOAuth2UserService; private final OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler; + private final ClientRegistrationRepository clientRegistrationRepository; @Autowired public SecurityConfiguration(UserDetailsService userDetailsService, LogoutHandler logoutHandler, CustomOAuth2UserService customOAuth2UserService, - OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler) { + OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler, + @Autowired(required = false) ClientRegistrationRepository clientRegistrationRepository) { this.userDetailsService = userDetailsService; this.logoutHandler = logoutHandler; this.customOAuth2UserService = customOAuth2UserService; this.oAuth2AuthenticationSuccessHandler = oAuth2AuthenticationSuccessHandler; + this.clientRegistrationRepository = clientRegistrationRepository; } @Bean @@ -118,11 +122,16 @@ public class SecurityConfiguration { ) .sessionManagement(session -> session.sessionCreationPolicy(STATELESS)) .authenticationProvider(authenticationProvider()) - .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) - .oauth2Login(oauth -> oauth - .userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService)) - .successHandler(oAuth2AuthenticationSuccessHandler) - ) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); + + if (clientRegistrationRepository != null) { + http.oauth2Login(oauth -> oauth + .userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService)) + .successHandler(oAuth2AuthenticationSuccessHandler) + ); + } + + http .logout(logout -> logout .logoutUrl("/api/v1/auth/logout") .addLogoutHandler(logoutHandler) diff --git a/bootstrap/src/main/resources/application-dev.yml b/bootstrap/src/main/resources/application-dev.yml index 5bdbf4e..1bc14f2 100644 --- a/bootstrap/src/main/resources/application-dev.yml +++ b/bootstrap/src/main/resources/application-dev.yml @@ -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: h2: @@ -47,27 +68,6 @@ spring: timeout: 3000 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: email: diff --git a/bootstrap/src/main/resources/application-prod.yml b/bootstrap/src/main/resources/application-prod.yml index 085d01f..22e33c2 100644 --- a/bootstrap/src/main/resources/application-prod.yml +++ b/bootstrap/src/main/resources/application-prod.yml @@ -85,4 +85,25 @@ application: secret-key: 404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970 expiration: 86400000 # a day refresh-token: - expiration: 604800000 # 7 days \ No newline at end of file + 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 \ No newline at end of file diff --git a/bootstrap/src/test/java/com/dh7789dev/xpeditis/LeBlrApplicationTests.java b/bootstrap/src/test/java/com/dh7789dev/xpeditis/LeBlrApplicationTests.java deleted file mode 100755 index 0d8751c..0000000 --- a/bootstrap/src/test/java/com/dh7789dev/xpeditis/LeBlrApplicationTests.java +++ /dev/null @@ -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() { - } -} diff --git a/bootstrap/src/test/java/com/dh7789dev/xpeditis/XpeditisApplicationTests.java b/bootstrap/src/test/java/com/dh7789dev/xpeditis/XpeditisApplicationTests.java new file mode 100755 index 0000000..00af861 --- /dev/null +++ b/bootstrap/src/test/java/com/dh7789dev/xpeditis/XpeditisApplicationTests.java @@ -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); + } +} diff --git a/bootstrap/src/test/java/com/dh7789dev/xpeditis/configuration/CustomOAuth2UserServiceTest.java b/bootstrap/src/test/java/com/dh7789dev/xpeditis/configuration/CustomOAuth2UserServiceTest.java new file mode 100644 index 0000000..2567dc1 --- /dev/null +++ b/bootstrap/src/test/java/com/dh7789dev/xpeditis/configuration/CustomOAuth2UserServiceTest.java @@ -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"); + } +} + + diff --git a/bootstrap/src/test/java/com/dh7789dev/xpeditis/configuration/OAuth2AuthenticationSuccessHandlerTest.java b/bootstrap/src/test/java/com/dh7789dev/xpeditis/configuration/OAuth2AuthenticationSuccessHandlerTest.java new file mode 100644 index 0000000..9bb4de3 --- /dev/null +++ b/bootstrap/src/test/java/com/dh7789dev/xpeditis/configuration/OAuth2AuthenticationSuccessHandlerTest.java @@ -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 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\"}"); + } +} + + diff --git a/domain/api/src/main/java/com/dh7789dev/xpeditis/QuoteService.java b/domain/api/src/main/java/com/dh7789dev/xpeditis/QuoteService.java index cfdfa2d..57bea0f 100644 --- a/domain/api/src/main/java/com/dh7789dev/xpeditis/QuoteService.java +++ b/domain/api/src/main/java/com/dh7789dev/xpeditis/QuoteService.java @@ -1,4 +1,17 @@ package com.dh7789dev.xpeditis; +import com.dh7789dev.xpeditis.dto.app.Quote; + +import java.util.List; + public interface QuoteService { + Quote create(Quote quote); + + Quote update(Long id, Quote quote); + + Quote getById(Long id); + + List list(); + + void delete(Long id); } diff --git a/domain/service/src/main/java/com/dh7789dev/xpeditis/QuoteServiceImpl.java b/domain/service/src/main/java/com/dh7789dev/xpeditis/QuoteServiceImpl.java index b23b8da..452422e 100644 --- a/domain/service/src/main/java/com/dh7789dev/xpeditis/QuoteServiceImpl.java +++ b/domain/service/src/main/java/com/dh7789dev/xpeditis/QuoteServiceImpl.java @@ -1,7 +1,40 @@ package com.dh7789dev.xpeditis; +import com.dh7789dev.xpeditis.dto.app.Quote; import org.springframework.stereotype.Service; +import java.util.List; + @Service 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 list() { + return quoteRepository.list(); + } + + @Override + public void delete(Long id) { + quoteRepository.delete(id); + } } diff --git a/domain/spi/src/main/java/com/dh7789dev/xpeditis/QuoteRepository.java b/domain/spi/src/main/java/com/dh7789dev/xpeditis/QuoteRepository.java index 48a6673..38d3959 100644 --- a/domain/spi/src/main/java/com/dh7789dev/xpeditis/QuoteRepository.java +++ b/domain/spi/src/main/java/com/dh7789dev/xpeditis/QuoteRepository.java @@ -1,4 +1,17 @@ package com.dh7789dev.xpeditis; +import com.dh7789dev.xpeditis.dto.app.Quote; + +import java.util.List; + public interface QuoteRepository { + Quote create(Quote quote); + + Quote update(Long id, Quote quote); + + Quote getById(Long id); + + List list(); + + void delete(Long id); } diff --git a/infrastructure/src/main/java/com/dh7789dev/xpeditis/repository/QuoteJpaRepository.java b/infrastructure/src/main/java/com/dh7789dev/xpeditis/repository/QuoteJpaRepository.java index 0dde1c9..f190a80 100644 --- a/infrastructure/src/main/java/com/dh7789dev/xpeditis/repository/QuoteJpaRepository.java +++ b/infrastructure/src/main/java/com/dh7789dev/xpeditis/repository/QuoteJpaRepository.java @@ -1,10 +1,94 @@ package com.dh7789dev.xpeditis.repository; 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 org.springframework.stereotype.Repository; @Slf4j @Repository 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 list() { + return quoteDao.findAll().stream() + .map(quoteMapper::quoteEntityToQuote) + .collect(java.util.stream.Collectors.toList()); + } + + @Override + public void delete(Long id) { + quoteDao.deleteById(id); + } } diff --git a/infrastructure/src/test/java/com/dh7789dev/xpeditis/repository/QuoteJpaRepositoryTest.java b/infrastructure/src/test/java/com/dh7789dev/xpeditis/repository/QuoteJpaRepositoryTest.java index 2a1f4e1..454a72f 100644 --- a/infrastructure/src/test/java/com/dh7789dev/xpeditis/repository/QuoteJpaRepositoryTest.java +++ b/infrastructure/src/test/java/com/dh7789dev/xpeditis/repository/QuoteJpaRepositoryTest.java @@ -1,19 +1,123 @@ 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.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.MockitoAnnotations; -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 { + 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 - void test(){ - int test = 1 +1; - assertEquals(2,test); + void create_maps_and_saves_with_links() { + Quote dto = new Quote( + 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); } } diff --git a/infrastructure/src/test/java/com/dh7789dev/xpeditis/repository/UserJpaRepositoryTest.java b/infrastructure/src/test/java/com/dh7789dev/xpeditis/repository/UserJpaRepositoryTest.java index eeeff31..7f148e2 100644 --- a/infrastructure/src/test/java/com/dh7789dev/xpeditis/repository/UserJpaRepositoryTest.java +++ b/infrastructure/src/test/java/com/dh7789dev/xpeditis/repository/UserJpaRepositoryTest.java @@ -1,19 +1,141 @@ 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.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.ArgumentCaptor; +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 { + @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 - void test(){ - int test = 1 +1; - assertEquals(2,test); + void changePassword_success() { + UserEntity principalUser = new UserEntity(); + 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); } }