diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..6885c1a
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,14 @@
+version: "3.9"
+
+services:
+ mysql:
+ image: mysql:8.0
+ container_name: mysql-db
+ restart: always
+ environment:
+ MYSQL_ROOT_PASSWORD: root123
+ MYSQL_DATABASE: mydb
+ MYSQL_USER: user
+ MYSQL_PASSWORD: user123
+ ports:
+ - "3306:3306"
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 9eaea54..07efe5a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,107 +1,113 @@
- 4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 4.0.1
-
-
- pl.milosnicyIT
- code-warehouse-backend
- 0.0.1-SNAPSHOT
- code-warehouse-backend
- code-warehouse-backend
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 17
-
-
-
- org.springframework.boot
- spring-boot-starter-data-jpa
-
-
- org.springframework.boot
- spring-boot-starter-security
-
-
- org.springframework.boot
- spring-boot-starter-webmvc
-
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.1
+
+
+ pl.milosnicyIT
+ code-warehouse-backend
+ 0.0.1-SNAPSHOT
+ code-warehouse-backend
+ code-warehouse-backend
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 17
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-webmvc
+
-
- com.h2database
- h2
- runtime
-
-
- com.mysql
- mysql-connector-j
- runtime
-
-
- org.projectlombok
- lombok
- true
-
-
- org.springframework.boot
- spring-boot-starter-data-jpa-test
- test
-
-
- org.springframework.boot
- spring-boot-starter-security-test
- test
-
-
- org.springframework.boot
- spring-boot-starter-webmvc-test
- test
-
-
+
+ com.h2database
+ h2
+ runtime
+
+
+ com.mysql
+ mysql-connector-j
+ runtime
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-security-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-webmvc-test
+ test
+
+
+
+ io.jsonwebtoken
+ jjwt
+ 0.12.6
+
+
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
-
- org.projectlombok
- lombok
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
-
-
-
- org.projectlombok
- lombok
-
-
-
-
-
-
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
diff --git a/src/main/java/org/commons/login/Password.java b/src/main/java/org/commons/login/Password.java
new file mode 100644
index 0000000..7d34ee6
--- /dev/null
+++ b/src/main/java/org/commons/login/Password.java
@@ -0,0 +1,34 @@
+package org.commons.login;
+
+import lombok.NonNull;
+
+import java.util.Objects;
+
+public class Password {
+ private final String password;
+
+ public Password(@NonNull final String password) {
+ if (password.length() < 15) throw new IllegalArgumentException("Password must have at least 15 characters");
+ if (password.contains(" ")) throw new IllegalArgumentException("Password must not contain spaces");
+
+ this.password = password;
+ }
+
+ @Override
+ public String toString() {
+ return this.password;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Password password1 = (Password) o;
+ return Objects.equals(password, password1.password);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(password);
+ }
+}
diff --git a/src/main/java/org/commons/login/Username.java b/src/main/java/org/commons/login/Username.java
new file mode 100644
index 0000000..bf384ee
--- /dev/null
+++ b/src/main/java/org/commons/login/Username.java
@@ -0,0 +1,34 @@
+package org.commons.login;
+
+import lombok.NonNull;
+
+import java.util.Objects;
+
+public class Username {
+ private final String username;
+
+ public Username(@NonNull final String username) {
+ if (username.length() < 3) throw new IllegalArgumentException("Username must have at least 3 characters");
+ if (username.contains(" ")) throw new IllegalArgumentException("Username must not contain spaces");
+
+ this.username = username;
+ }
+
+ @Override
+ public String toString() {
+ return this.username;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Username username1 = (Username) o;
+ return Objects.equals(username, username1.username);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(username);
+ }
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/config/auth/JWTAuthFilter.java b/src/main/java/pl/milosnicyit/codewarehousebackend/config/auth/JWTAuthFilter.java
new file mode 100644
index 0000000..4235324
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/config/auth/JWTAuthFilter.java
@@ -0,0 +1,57 @@
+package pl.milosnicyit.codewarehousebackend.config.auth;
+
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+import pl.milosnicyit.codewarehousebackend.jwt.JWTService;
+
+import java.io.IOException;
+
+@Component
+public class JWTAuthFilter extends OncePerRequestFilter {
+ private final JWTService jwtService;
+ private final UserDetailsService userDetailsService;
+
+ @Autowired
+ public JWTAuthFilter(JWTService jwtService, UserDetailsService userDetailsService) {
+ this.jwtService = jwtService;
+ this.userDetailsService = userDetailsService;
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, ServletException, IOException {
+ String authorization = request.getHeader("Authorization");
+
+ if (authorization != null && authorization.startsWith("Bearer ")) {
+ String token = authorization.substring(7);
+ try {
+ String login = this.jwtService.extractLogin(token);
+ if (login != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+ UserDetails userDetails = this.userDetailsService.loadUserByUsername(login);
+
+ if (this.jwtService.validateToken(token)) {
+ UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
+ userDetails, null, userDetails.getAuthorities()
+ );
+
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ }
+ }
+
+ } catch (final Exception exception) {
+ exception.printStackTrace();
+ }
+ }
+
+ filterChain.doFilter(request, response);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/config/auth/WebSecurityConfig.java b/src/main/java/pl/milosnicyit/codewarehousebackend/config/auth/WebSecurityConfig.java
new file mode 100644
index 0000000..1285db6
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/config/auth/WebSecurityConfig.java
@@ -0,0 +1,55 @@
+package pl.milosnicyit.codewarehousebackend.config.auth;
+
+import jakarta.servlet.http.HttpSession;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import static pl.milosnicyit.codewarehousebackend.controllers.v1.RestConstant.PATH;
+
+@Configuration
+@EnableWebSecurity
+public class WebSecurityConfig {
+ private final JWTAuthFilter jwtAuthFilter;
+
+ public WebSecurityConfig(JWTAuthFilter jwtAuthFilter) {
+ this.jwtAuthFilter = jwtAuthFilter;
+ }
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http, HttpSession httpSession) throws Exception {
+ return http.cors(httpSecurityCorsConfigurer -> {
+ })
+ .csrf(AbstractHttpConfigurer::disable)
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers(PATH + "auth/**").permitAll()
+ .anyRequest().authenticated()
+ )
+ .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
+ .build();
+ }
+
+ //todo change this or delete
+ @Bean
+ public WebMvcConfigurer corsConfigurer() {
+ return new WebMvcConfigurer() {
+ @Override
+ public void addCorsMappings(CorsRegistry registry) {
+ registry.addMapping("/**")
+ .allowedOriginPatterns("*")
+ .allowedMethods("*")
+ .allowedHeaders("*")
+ .allowCredentials(true);
+ }
+ };
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/controllers/ControllersConstant.java b/src/main/java/pl/milosnicyit/codewarehousebackend/controllers/ControllersConstant.java
new file mode 100644
index 0000000..aaecd64
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/controllers/ControllersConstant.java
@@ -0,0 +1,5 @@
+package pl.milosnicyit.codewarehousebackend.controllers;
+
+public final class ControllersConstant {
+ public static final String API_PATH = "/api";
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/controllers/v1/RestConstant.java b/src/main/java/pl/milosnicyit/codewarehousebackend/controllers/v1/RestConstant.java
new file mode 100644
index 0000000..505d550
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/controllers/v1/RestConstant.java
@@ -0,0 +1,7 @@
+package pl.milosnicyit.codewarehousebackend.controllers.v1;
+
+import static pl.milosnicyit.codewarehousebackend.controllers.ControllersConstant.API_PATH;
+
+public final class RestConstant {
+ public static final String PATH = API_PATH + "/v1/";
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/controllers/v1/auth/AuthController.java b/src/main/java/pl/milosnicyit/codewarehousebackend/controllers/v1/auth/AuthController.java
new file mode 100644
index 0000000..4de5ffe
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/controllers/v1/auth/AuthController.java
@@ -0,0 +1,28 @@
+package pl.milosnicyit.codewarehousebackend.controllers.v1.auth;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import pl.milosnicyit.codewarehousebackend.users.UsersService;
+
+import static pl.milosnicyit.codewarehousebackend.controllers.v1.RestConstant.PATH;
+
+@RestController
+@RequestMapping(PATH + "auth")
+public class AuthController {
+ private final UsersService usersService;
+
+ @Autowired
+ public AuthController(UsersService usersService) {
+ this.usersService = usersService;
+ }
+
+ @PostMapping("/register")
+ public ResponseEntity register(@RequestBody UserRequest userRequest) {
+ final String token = this.usersService.registerUser(userRequest.getUsername(), userRequest.getPassword());
+ return ResponseEntity.ok(new TokenResponse(token));
+ }
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/controllers/v1/auth/TokenResponse.java b/src/main/java/pl/milosnicyit/codewarehousebackend/controllers/v1/auth/TokenResponse.java
new file mode 100644
index 0000000..5995e46
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/controllers/v1/auth/TokenResponse.java
@@ -0,0 +1,14 @@
+package pl.milosnicyit.codewarehousebackend.controllers.v1.auth;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@ToString
+public class TokenResponse {
+ private String token;
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/controllers/v1/auth/UserRequest.java b/src/main/java/pl/milosnicyit/codewarehousebackend/controllers/v1/auth/UserRequest.java
new file mode 100644
index 0000000..a6b8e5f
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/controllers/v1/auth/UserRequest.java
@@ -0,0 +1,15 @@
+package pl.milosnicyit.codewarehousebackend.controllers.v1.auth;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@ToString
+public class UserRequest {
+ private String username;
+ private String password;
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/handlers/GlobalExceptionHandler.java b/src/main/java/pl/milosnicyit/codewarehousebackend/handlers/GlobalExceptionHandler.java
new file mode 100644
index 0000000..719ee6f
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/handlers/GlobalExceptionHandler.java
@@ -0,0 +1,19 @@
+package pl.milosnicyit.codewarehousebackend.handlers;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+ @ExceptionHandler(IllegalArgumentException.class)
+ public ResponseEntity handleIllegalArgumentException(IllegalArgumentException exception) {
+ return ResponseEntity.badRequest().body(new HandlerDTO(exception.getMessage()));
+ }
+
+ @ExceptionHandler(UsernameNotFoundException.class)
+ public ResponseEntity handleUsernameNotFoundException(UsernameNotFoundException exception) {
+ return ResponseEntity.badRequest().body(new HandlerDTO(exception.getMessage()));
+ }
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/handlers/HandlerDTO.java b/src/main/java/pl/milosnicyit/codewarehousebackend/handlers/HandlerDTO.java
new file mode 100644
index 0000000..59073ad
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/handlers/HandlerDTO.java
@@ -0,0 +1,10 @@
+package pl.milosnicyit.codewarehousebackend.handlers;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+public class HandlerDTO {
+ private String error;
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/AppJwtService.java b/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/AppJwtService.java
new file mode 100644
index 0000000..4f67780
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/AppJwtService.java
@@ -0,0 +1,28 @@
+package pl.milosnicyit.codewarehousebackend.jwt;
+
+import lombok.NonNull;
+import org.commons.login.Username;
+import pl.milosnicyit.codewarehousebackend.jwt.secret.JWTSecretService;
+
+class AppJwtService implements JWTService {
+ private final JwtBasicService jwtBasicService;
+
+ public AppJwtService(@NonNull final JWTSecretService jwtSecretService) {
+ this.jwtBasicService = new JwtBasicService(jwtSecretService);
+ }
+
+ @Override
+ public String generateToken(@NonNull String username) {
+ return this.jwtBasicService.generateToken(new Username(username));
+ }
+
+ @Override
+ public String extractLogin(@NonNull String token) {
+ return this.jwtBasicService.extractLogin(token).toString();
+ }
+
+ @Override
+ public boolean validateToken(@NonNull String jwtToken) {
+ return this.jwtBasicService.validateToken(jwtToken);
+ }
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/JWTService.java b/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/JWTService.java
new file mode 100644
index 0000000..f86070d
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/JWTService.java
@@ -0,0 +1,11 @@
+package pl.milosnicyit.codewarehousebackend.jwt;
+
+import lombok.NonNull;
+
+public interface JWTService {
+ String generateToken(@NonNull final String username);
+
+ String extractLogin(@NonNull final String token);
+
+ boolean validateToken(@NonNull final String jwtToken);
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/JWTServiceConfiguration.java b/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/JWTServiceConfiguration.java
new file mode 100644
index 0000000..164c196
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/JWTServiceConfiguration.java
@@ -0,0 +1,21 @@
+package pl.milosnicyit.codewarehousebackend.jwt;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import pl.milosnicyit.codewarehousebackend.jwt.secret.JWTSecretService;
+
+@Configuration
+public class JWTServiceConfiguration {
+ private final JWTSecretService jwtSecretService;
+
+ @Autowired
+ public JWTServiceConfiguration(JWTSecretService jwtSecretService) {
+ this.jwtSecretService = jwtSecretService;
+ }
+
+ @Bean
+ public JWTService jwtService() {
+ return new AppJwtService(this.jwtSecretService);
+ }
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/JwtBasicService.java b/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/JwtBasicService.java
new file mode 100644
index 0000000..c584887
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/JwtBasicService.java
@@ -0,0 +1,57 @@
+package pl.milosnicyit.codewarehousebackend.jwt;
+
+import io.jsonwebtoken.JwtException;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.security.Keys;
+import lombok.NonNull;
+import org.commons.login.Username;
+import pl.milosnicyit.codewarehousebackend.jwt.secret.JWTSecretService;
+
+import javax.crypto.SecretKey;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+
+class JwtBasicService {
+ private final JWTSecretService jwtSecretService;
+
+ public JwtBasicService(JWTSecretService jwtSecretService) {
+ this.jwtSecretService = jwtSecretService;
+ }
+
+ public String generateToken(@NonNull final Username username) {
+ return Jwts.builder()
+ .subject(username.toString())
+ .issuedAt(new Date())
+ .expiration(new Date(System.currentTimeMillis() + 86400000)) // 1 day
+ .signWith(getSigningKey())
+ .compact();
+ }
+
+ public Username extractLogin(@NonNull final String token) {
+ final String subject = Jwts.parser()
+ .verifyWith(getSigningKey())
+ .build()
+ .parseSignedClaims(token)
+ .getPayload()
+ .getSubject();
+
+ return new Username(subject);
+ }
+
+ public boolean validateToken(@NonNull final String jwtToken) {
+ try {
+ Jwts.parser()
+ .verifyWith(getSigningKey())
+ .build()
+ .parseSignedClaims(jwtToken);
+ return true;
+ } catch (final JwtException | IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ private SecretKey getSigningKey() {
+ final byte[] secretBytes = this.jwtSecretService.getSecret().getBytes(StandardCharsets.UTF_8);
+ return Keys.hmacShaKeyFor(secretBytes);
+ }
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JWTDevService.java b/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JWTDevService.java
new file mode 100644
index 0000000..806b2bf
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JWTDevService.java
@@ -0,0 +1,22 @@
+package pl.milosnicyit.codewarehousebackend.jwt.secret;
+
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.io.Encoders;
+import io.jsonwebtoken.security.Keys;
+
+import java.security.Key;
+
+class JWTDevService implements JWTSecretService {
+ private final JWTSecret jwtSecret;
+
+ public JWTDevService() {
+ Key secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
+ String base64Key = Encoders.BASE64.encode(secretKey.getEncoded());
+ this.jwtSecret = new JWTSecret(base64Key);
+ }
+
+ @Override
+ public String getSecret() {
+ return this.jwtSecret.toString();
+ }
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JWTSecret.java b/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JWTSecret.java
new file mode 100644
index 0000000..d5b0b23
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JWTSecret.java
@@ -0,0 +1,34 @@
+package pl.milosnicyit.codewarehousebackend.jwt.secret;
+
+import lombok.NonNull;
+
+import java.util.Objects;
+
+class JWTSecret {
+ private final String secret;
+
+ public JWTSecret(@NonNull final String secret) {
+ if (secret.length() < 15) throw new IllegalArgumentException("Secret must be at least 15 characters");
+ if (secret.contains(" ")) throw new IllegalArgumentException("Secret must not contain spaces");
+
+ this.secret = secret;
+ }
+
+ @Override
+ public String toString() {
+ return this.secret;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ JWTSecret jwtSecret = (JWTSecret) o;
+ return Objects.equals(secret, jwtSecret.secret);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(secret);
+ }
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JWTSecretConfiguration.java b/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JWTSecretConfiguration.java
new file mode 100644
index 0000000..e2b1fbd
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JWTSecretConfiguration.java
@@ -0,0 +1,13 @@
+package pl.milosnicyit.codewarehousebackend.jwt.secret;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class JWTSecretConfiguration {
+
+ @Bean
+ public JWTSecretService jwtSecretService() {
+ return new JWTDevService();
+ }
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JWTSecretService.java b/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JWTSecretService.java
new file mode 100644
index 0000000..bdcb548
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JWTSecretService.java
@@ -0,0 +1,5 @@
+package pl.milosnicyit.codewarehousebackend.jwt.secret;
+
+public interface JWTSecretService {
+ String getSecret();
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/password/PasswordEncoderBasicService.java b/src/main/java/pl/milosnicyit/codewarehousebackend/password/PasswordEncoderBasicService.java
new file mode 100644
index 0000000..a4027dd
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/password/PasswordEncoderBasicService.java
@@ -0,0 +1,20 @@
+package pl.milosnicyit.codewarehousebackend.password;
+
+import org.commons.login.Password;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+class PasswordEncoderBasicService implements PasswordEncoderService {
+ private final PasswordEncoder passwordEncoder;
+
+ public PasswordEncoderBasicService(PasswordEncoder passwordEncoder) {
+ this.passwordEncoder = passwordEncoder;
+ }
+
+ public String encode(Password rawPassword) {
+ return this.passwordEncoder.encode(rawPassword.toString());
+ }
+
+ public boolean matches(Password rawPassword, String encodedPassword) {
+ return this.passwordEncoder.matches(rawPassword.toString(), encodedPassword);
+ }
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/password/PasswordEncoderConfiguration.java b/src/main/java/pl/milosnicyit/codewarehousebackend/password/PasswordEncoderConfiguration.java
new file mode 100644
index 0000000..5c85b82
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/password/PasswordEncoderConfiguration.java
@@ -0,0 +1,20 @@
+package pl.milosnicyit.codewarehousebackend.password;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+@Configuration
+public class PasswordEncoderConfiguration {
+ private final PasswordEncoder passwordEncoder;
+
+ public PasswordEncoderConfiguration() {
+ this.passwordEncoder = new BCryptPasswordEncoder();
+ }
+
+ @Bean
+ public PasswordEncoderService passwordEncoderService() {
+ return new PasswordEncoderBasicService(passwordEncoder);
+ }
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/password/PasswordEncoderService.java b/src/main/java/pl/milosnicyit/codewarehousebackend/password/PasswordEncoderService.java
new file mode 100644
index 0000000..1a002be
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/password/PasswordEncoderService.java
@@ -0,0 +1,9 @@
+package pl.milosnicyit.codewarehousebackend.password;
+
+import org.commons.login.Password;
+
+public interface PasswordEncoderService {
+ String encode(Password rawPassword);
+
+ boolean matches(Password rawPassword, String encodedPassword);
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/users/UserAppService.java b/src/main/java/pl/milosnicyit/codewarehousebackend/users/UserAppService.java
new file mode 100644
index 0000000..a1daaab
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/users/UserAppService.java
@@ -0,0 +1,36 @@
+package pl.milosnicyit.codewarehousebackend.users;
+
+import org.commons.login.Password;
+import pl.milosnicyit.codewarehousebackend.jwt.JWTService;
+import pl.milosnicyit.codewarehousebackend.password.PasswordEncoderService;
+import pl.milosnicyit.codewarehousebackend.users.database.wrapper.UserDTO;
+import pl.milosnicyit.codewarehousebackend.users.database.wrapper.UserRepositoryWrapper;
+
+class UserAppService implements UsersService {
+ private final UserRepositoryWrapper userRepositoryWrapper;
+ private final PasswordEncoderService passwordEncoderService;
+ private final JWTService jwtService;
+
+ public UserAppService(UserRepositoryWrapper userRepositoryWrapper, PasswordEncoderService passwordEncoderService,
+ JWTService jwtService) {
+ this.userRepositoryWrapper = userRepositoryWrapper;
+ this.passwordEncoderService = passwordEncoderService;
+ this.jwtService = jwtService;
+ }
+
+ public String registerUser(String username, String rawPassword) {
+ if (userRepositoryWrapper.existsByUsername(username)) {
+ return null;
+ }
+
+ final Password password = new Password(rawPassword);
+ final UserDTO userDTO = new UserDTO();
+ userDTO.setUsername(username);
+ userDTO.setPassword(this.passwordEncoderService.encode(password));
+
+ if (this.userRepositoryWrapper.save(userDTO)) {
+ return this.jwtService.generateToken(username);
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/users/UserServiceConfiguration.java b/src/main/java/pl/milosnicyit/codewarehousebackend/users/UserServiceConfiguration.java
new file mode 100644
index 0000000..516548c
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/users/UserServiceConfiguration.java
@@ -0,0 +1,28 @@
+package pl.milosnicyit.codewarehousebackend.users;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import pl.milosnicyit.codewarehousebackend.jwt.JWTService;
+import pl.milosnicyit.codewarehousebackend.password.PasswordEncoderService;
+import pl.milosnicyit.codewarehousebackend.users.database.wrapper.UserRepositoryWrapper;
+
+@Configuration
+public class UserServiceConfiguration {
+ private final UserRepositoryWrapper userRepositoryWrapper;
+ private final PasswordEncoderService passwordEncoderService;
+ private final JWTService jwtService;
+
+ @Autowired
+ public UserServiceConfiguration(UserRepositoryWrapper userRepositoryWrapper, PasswordEncoderService passwordEncoderService,
+ JWTService jwtService) {
+ this.userRepositoryWrapper = userRepositoryWrapper;
+ this.passwordEncoderService = passwordEncoderService;
+ this.jwtService = jwtService;
+ }
+
+ @Bean
+ public UsersService usersService() {
+ return new UserAppService(userRepositoryWrapper, passwordEncoderService, jwtService);
+ }
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/users/UsersService.java b/src/main/java/pl/milosnicyit/codewarehousebackend/users/UsersService.java
new file mode 100644
index 0000000..82fab57
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/users/UsersService.java
@@ -0,0 +1,5 @@
+package pl.milosnicyit.codewarehousebackend.users;
+
+public interface UsersService {
+ String registerUser(String username, String rawPassword);
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/UserEntity.java b/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/UserEntity.java
new file mode 100644
index 0000000..d647167
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/UserEntity.java
@@ -0,0 +1,21 @@
+package pl.milosnicyit.codewarehousebackend.users.database;
+
+import jakarta.persistence.*;
+import lombok.Data;
+
+@Data
+
+@Entity
+@Table(name = "users")
+public class UserEntity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "user_id")
+ private long id;
+
+ @Column(name = "username", nullable = false, unique = true)
+ private String username;
+
+ @Column(name = "password", nullable = false)
+ private String password;
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/UsersRepository.java b/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/UsersRepository.java
new file mode 100644
index 0000000..9b95e7c
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/UsersRepository.java
@@ -0,0 +1,9 @@
+package pl.milosnicyit.codewarehousebackend.users.database;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface UsersRepository extends JpaRepository {
+ Optional findByUsername(String username);
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/wrapper/UserDTO.java b/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/wrapper/UserDTO.java
new file mode 100644
index 0000000..88d7f42
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/wrapper/UserDTO.java
@@ -0,0 +1,14 @@
+package pl.milosnicyit.codewarehousebackend.users.database.wrapper;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class UserDTO {
+ private long id;
+ private String username;
+ private String password;
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/wrapper/UserRepositoryBasicWrapper.java b/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/wrapper/UserRepositoryBasicWrapper.java
new file mode 100644
index 0000000..a77b2d5
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/wrapper/UserRepositoryBasicWrapper.java
@@ -0,0 +1,45 @@
+package pl.milosnicyit.codewarehousebackend.users.database.wrapper;
+
+import lombok.NonNull;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import pl.milosnicyit.codewarehousebackend.users.database.UserEntity;
+import pl.milosnicyit.codewarehousebackend.users.database.UsersRepository;
+
+class UserRepositoryBasicWrapper implements UserRepositoryWrapper {
+ private final UsersRepository usersRepository;
+
+ public UserRepositoryBasicWrapper(UsersRepository usersRepository) {
+ this.usersRepository = usersRepository;
+ }
+
+ @Override
+ public boolean save(@NonNull UserDTO user) {
+ final UserEntity userEntity = new UserEntity();
+
+ userEntity.setUsername(user.getUsername());
+ userEntity.setPassword(user.getPassword());
+
+ this.usersRepository.save(userEntity);
+ return true;
+ }
+
+ @Override
+ public UserDTO findByUsername(@NonNull String username) {
+ final UserEntity userEntity = this.usersRepository.findByUsername(username).orElseThrow(() ->
+ new UsernameNotFoundException("Username " + username + " not found"));
+ return new UserDTO(userEntity.getId(), userEntity.getUsername(), userEntity.getPassword());
+ }
+
+ @Override
+ public UserDTO findById(@NonNull Long id) {
+ final UserEntity userEntity = this.usersRepository.findById(id).orElseThrow(() ->
+ new UsernameNotFoundException("Username not found"));
+ return new UserDTO(userEntity.getId(), userEntity.getUsername(), userEntity.getPassword());
+ }
+
+ @Override
+ public boolean existsByUsername(@NonNull String username) {
+ final UserEntity userEntity = this.usersRepository.findByUsername(username).orElse(null);
+ return userEntity != null;
+ }
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/wrapper/UserRepositoryConfiguration.java b/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/wrapper/UserRepositoryConfiguration.java
new file mode 100644
index 0000000..15870bb
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/wrapper/UserRepositoryConfiguration.java
@@ -0,0 +1,21 @@
+package pl.milosnicyit.codewarehousebackend.users.database.wrapper;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import pl.milosnicyit.codewarehousebackend.users.database.UsersRepository;
+
+@Configuration
+public class UserRepositoryConfiguration {
+ private final UsersRepository usersRepository;
+
+ @Autowired
+ public UserRepositoryConfiguration(UsersRepository usersRepository) {
+ this.usersRepository = usersRepository;
+ }
+
+ @Bean
+ public UserRepositoryWrapper userRepositoryWrapper() {
+ return new UserRepositoryBasicWrapper(this.usersRepository);
+ }
+}
diff --git a/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/wrapper/UserRepositoryWrapper.java b/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/wrapper/UserRepositoryWrapper.java
new file mode 100644
index 0000000..bc80d7a
--- /dev/null
+++ b/src/main/java/pl/milosnicyit/codewarehousebackend/users/database/wrapper/UserRepositoryWrapper.java
@@ -0,0 +1,13 @@
+package pl.milosnicyit.codewarehousebackend.users.database.wrapper;
+
+import lombok.NonNull;
+
+public interface UserRepositoryWrapper {
+ boolean save(@NonNull final UserDTO user);
+
+ UserDTO findByUsername(@NonNull final String username);
+
+ UserDTO findById(@NonNull final Long id);
+
+ boolean existsByUsername(@NonNull final String username);
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 8896c71..3b43a49 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,12 +1,12 @@
spring.application.name=code-warehouse-backend
server.port=8082
-spring.datasource.url=jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC
-spring.datasource.username=root
-spring.datasource.password=your_password
+spring.datasource.url=jdbc:mysql://localhost:3306/mydb
+spring.datasource.username=user
+spring.datasource.password=user123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
-spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
+spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
diff --git a/src/test/java/org/commons/login/PasswordTest.java b/src/test/java/org/commons/login/PasswordTest.java
new file mode 100644
index 0000000..25ba000
--- /dev/null
+++ b/src/test/java/org/commons/login/PasswordTest.java
@@ -0,0 +1,51 @@
+package org.commons.login;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class PasswordTest {
+ @Test
+ void createPasswordWhenValid() {
+ String validPassword = "BardzoSilneHaslo123!";
+
+ Password password = new Password(validPassword);
+
+ assertEquals(validPassword, password.toString());
+ }
+
+ @Test
+ void shouldCreatePasswordWithExactlyFifteenCharacters() {
+ String validPassword = "123456789012345";
+
+ Password password = new Password(validPassword);
+
+ assertEquals(validPassword, password.toString());
+ }
+
+ @Test
+ void shouldThrowExceptionWhenPasswordIsNull() {
+ assertThrows(NullPointerException.class, () -> new Password(null));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"", "haslo", "12345678901234"})
+ void shouldThrowExceptionWhenPasswordIsTooShort(String invalidPassword) {
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+ () -> new Password(invalidPassword));
+
+ assertEquals("Password must have at least 15 characters", exception.getMessage());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"BardzoSilne Haslo123", "1234567890 12345", " ", " spacjaNaPoczatku123"})
+ void shouldThrowExceptionWhenPasswordContainsSpaces(String invalidPassword) {
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+ () -> new Password(invalidPassword));
+
+ assertEquals("Password must not contain spaces", exception.getMessage());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/commons/login/UsernameTest.java b/src/test/java/org/commons/login/UsernameTest.java
new file mode 100644
index 0000000..fa803bd
--- /dev/null
+++ b/src/test/java/org/commons/login/UsernameTest.java
@@ -0,0 +1,50 @@
+package org.commons.login;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class UsernameTest {
+ @ParameterizedTest
+ @ValueSource(strings = {"test", "%^#$#S"})
+ void shouldCreateUsernameWhenValid(String validName) {
+ Username username = new Username(validName);
+
+ assertEquals(validName, username.toString());
+ }
+
+ @Test
+ void shouldCreateUsernameWithExactlyThreeCharacters() {
+ String validName = "jan";
+
+ Username username = new Username(validName);
+
+ assertEquals(validName, username.toString());
+ }
+
+ @Test
+ void shouldThrowExceptionWhenUsernameIsNull() {
+ assertThrows(NullPointerException.class, () -> new Username(null));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"", "a", "ab", "@"})
+ void shouldThrowExceptionWhenUsernameIsTooShort(String invalidName) {
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+ () -> new Username(invalidName));
+
+ assertEquals("Username must have at least 3 characters", exception.getMessage());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {" test", " &*( ", " test ", "test "})
+ void shouldThrowExceptionWhenUsernameContainsSpace(String invalidName) {
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+ () -> new Username(invalidName));
+
+ assertEquals("Username must not contain spaces", exception.getMessage());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/pl/milosnicyit/codewarehousebackend/controllers/v1/auth/AuthControllerE2ETest.java b/src/test/java/pl/milosnicyit/codewarehousebackend/controllers/v1/auth/AuthControllerE2ETest.java
new file mode 100644
index 0000000..c6fb160
--- /dev/null
+++ b/src/test/java/pl/milosnicyit/codewarehousebackend/controllers/v1/auth/AuthControllerE2ETest.java
@@ -0,0 +1,65 @@
+package pl.milosnicyit.codewarehousebackend.controllers.v1.auth;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static pl.milosnicyit.codewarehousebackend.controllers.v1.RestConstant.PATH;
+import static pl.milosnicyit.codewarehousebackend.helpers.JsonHelper.toJson;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@ActiveProfiles("test")
+class AuthControllerE2ETest {
+ private static final String REGISTER_ENDPOINT = PATH + "auth/register";
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Test
+ void shouldRegisterNewUserEndToEnd() throws Exception {
+ String uniqueUsername = "e2e_ddffgdfdfdf";
+ String password = "realSecretPassword123!";
+
+ mockMvc.perform(post(REGISTER_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(getUserRequestJson(uniqueUsername, password)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.token").isNotEmpty())
+ .andExpect(jsonPath("$.token").isString());
+ }
+
+ @Test
+ void shouldReturnEmptyTokenWhenUserAlreadyExistsEndToEnd() throws Exception {
+ String duplicateUsername = "e2e_duplicate_";
+ String password = "realSecretPassword123!";
+ String jsonRequest = getUserRequestJson(duplicateUsername, password);
+
+ mockMvc.perform(post(REGISTER_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonRequest))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.token").isNotEmpty());
+
+ mockMvc.perform(post(REGISTER_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonRequest))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.token").isEmpty());
+ }
+
+ private String getUserRequestJson(String username, String password) throws JsonProcessingException {
+ UserRequest userRequest = new UserRequest();
+ userRequest.setUsername(username);
+ userRequest.setPassword(password);
+ return toJson(userRequest);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/pl/milosnicyit/codewarehousebackend/controllers/v1/auth/AuthSpecificationTest.java b/src/test/java/pl/milosnicyit/codewarehousebackend/controllers/v1/auth/AuthSpecificationTest.java
new file mode 100644
index 0000000..ab84fa3
--- /dev/null
+++ b/src/test/java/pl/milosnicyit/codewarehousebackend/controllers/v1/auth/AuthSpecificationTest.java
@@ -0,0 +1,68 @@
+package pl.milosnicyit.codewarehousebackend.controllers.v1.auth;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
+import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+import org.springframework.test.web.servlet.MockMvc;
+import pl.milosnicyit.codewarehousebackend.jwt.JWTService;
+import pl.milosnicyit.codewarehousebackend.users.UsersService;
+
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static pl.milosnicyit.codewarehousebackend.controllers.v1.RestConstant.PATH;
+import static pl.milosnicyit.codewarehousebackend.helpers.JsonHelper.toJson;
+
+@WebMvcTest(AuthController.class)
+@AutoConfigureMockMvc(addFilters = false)
+class AuthSpecificationTest {
+ private static final String REGISTER_ENDPOINT = PATH + "auth/register";
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @MockitoBean
+ private UsersService usersService;
+
+ @MockitoBean
+ private JWTService jwtService;
+
+ @Test
+ void shouldRegisterUserAndReturnOkWithToken() throws Exception {
+ String username = "testUser";
+ String password = "testPassword";
+ String generatedToken = "mocked.jwt.token";
+
+ when(usersService.registerUser(username, password)).thenReturn(generatedToken);
+
+ mockMvc.perform(post(REGISTER_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(getUserRequestJson()))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.token").value(generatedToken));
+ }
+
+ @Test
+ void shouldReturnBadRequestWhenRegistrationFails() throws Exception {
+
+ when(usersService.registerUser("zajetyLogin", "testPassword")).thenReturn(null);
+
+ mockMvc.perform(post(REGISTER_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(getUserRequestJson()))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.token").isEmpty());
+ }
+
+ private String getUserRequestJson() throws JsonProcessingException {
+ UserRequest userRequest = new UserRequest();
+ userRequest.setUsername("testUser");
+ userRequest.setPassword("testPassword");
+ return toJson(userRequest);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/pl/milosnicyit/codewarehousebackend/handlers/GlobalExceptionHandlerTest.java b/src/test/java/pl/milosnicyit/codewarehousebackend/handlers/GlobalExceptionHandlerTest.java
new file mode 100644
index 0000000..e30e07c
--- /dev/null
+++ b/src/test/java/pl/milosnicyit/codewarehousebackend/handlers/GlobalExceptionHandlerTest.java
@@ -0,0 +1,28 @@
+package pl.milosnicyit.codewarehousebackend.handlers;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class GlobalExceptionHandlerTest {
+ private final GlobalExceptionHandler exceptionHandler = new GlobalExceptionHandler();
+
+ @Test
+ void shouldHandleIllegalArgumentExceptionAndReturnBadRequest() {
+ String expectedMessage = "Nieprawidłowy argument testowy";
+ IllegalArgumentException exception = new IllegalArgumentException(expectedMessage);
+
+ ResponseEntity response = exceptionHandler.handleIllegalArgumentException(exception);
+
+ assertNotNull(response, "Response entity should not be null");
+ assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode(), "HTTP status should be 400 BAD REQUEST");
+
+ HandlerDTO body = response.getBody();
+ assertNotNull(body, "Response body should not be null");
+
+ assertEquals(expectedMessage, body.getError(), "Exception message should be mapped to DTO");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/pl/milosnicyit/codewarehousebackend/helpers/JsonHelper.java b/src/test/java/pl/milosnicyit/codewarehousebackend/helpers/JsonHelper.java
new file mode 100644
index 0000000..e03e1cc
--- /dev/null
+++ b/src/test/java/pl/milosnicyit/codewarehousebackend/helpers/JsonHelper.java
@@ -0,0 +1,13 @@
+package pl.milosnicyit.codewarehousebackend.helpers;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.NonNull;
+
+public final class JsonHelper {
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ public static String toJson(@NonNull final Object object) throws JsonProcessingException {
+ return OBJECT_MAPPER.writeValueAsString(object);
+ }
+}
diff --git a/src/test/java/pl/milosnicyit/codewarehousebackend/jwt/AppJwtServiceTest.java b/src/test/java/pl/milosnicyit/codewarehousebackend/jwt/AppJwtServiceTest.java
new file mode 100644
index 0000000..801158b
--- /dev/null
+++ b/src/test/java/pl/milosnicyit/codewarehousebackend/jwt/AppJwtServiceTest.java
@@ -0,0 +1,74 @@
+package pl.milosnicyit.codewarehousebackend.jwt;
+
+import org.commons.login.Username;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.MockedConstruction;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import pl.milosnicyit.codewarehousebackend.jwt.secret.JWTSecretService;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class AppJwtServiceTest {
+
+ @Mock
+ private JWTSecretService jwtSecretService;
+
+ @Test
+ void shouldDelegateGenerateToken() {
+ try (MockedConstruction mockedConstruction = Mockito.mockConstruction(JwtBasicService.class,
+ (mock, context) -> {
+ when(mock.generateToken(any(Username.class))).thenReturn("mocked_token");
+ })) {
+
+ AppJwtService appJwtService = new AppJwtService(jwtSecretService);
+ String inputUsername = "testUser";
+
+ String result = appJwtService.generateToken(inputUsername);
+
+ assertEquals("mocked_token", result);
+
+ JwtBasicService createdMock = mockedConstruction.constructed().get(0);
+ verify(createdMock).generateToken(Mockito.argThat(arg -> arg.toString().equals(inputUsername)));
+ }
+ }
+
+ @Test
+ void shouldDelegateExtractLogin() {
+ // given
+ try (MockedConstruction mockedConstruction = Mockito.mockConstruction(JwtBasicService.class,
+ (mock, context) -> {
+ when(mock.extractLogin("some_token")).thenReturn(new Username("extractedUser"));
+ })) {
+
+ AppJwtService appJwtService = new AppJwtService(jwtSecretService);
+
+ String result = appJwtService.extractLogin("some_token");
+
+ assertEquals("extractedUser", result);
+ }
+ }
+
+ @Test
+ void shouldDelegateValidateToken() {
+ // given
+ try (MockedConstruction mockedConstruction = Mockito.mockConstruction(JwtBasicService.class,
+ (mock, context) -> {
+ when(mock.validateToken("valid_token")).thenReturn(true);
+ })) {
+
+ AppJwtService appJwtService = new AppJwtService(jwtSecretService);
+
+ boolean result = appJwtService.validateToken("valid_token");
+
+ assertTrue(result);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/pl/milosnicyit/codewarehousebackend/jwt/JWTServiceSpecificationTest.java b/src/test/java/pl/milosnicyit/codewarehousebackend/jwt/JWTServiceSpecificationTest.java
new file mode 100644
index 0000000..1e42ce8
--- /dev/null
+++ b/src/test/java/pl/milosnicyit/codewarehousebackend/jwt/JWTServiceSpecificationTest.java
@@ -0,0 +1,27 @@
+package pl.milosnicyit.codewarehousebackend.jwt;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import pl.milosnicyit.codewarehousebackend.jwt.secret.JWTSecretService;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@ExtendWith(MockitoExtension.class)
+class JWTServiceSpecificationTest {
+
+ @Mock
+ private JWTSecretService jwtSecretService;
+
+ @Test
+ void shouldCreateJwtServiceBean() {
+ JWTServiceConfiguration configuration = new JWTServiceConfiguration(jwtSecretService);
+
+ JWTService jwtService = configuration.jwtService();
+
+ assertNotNull(jwtService, "JWTService bean should not be null");
+ assertInstanceOf(AppJwtService.class, jwtService, "Bean should be an instance of AppJwtService");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/pl/milosnicyit/codewarehousebackend/jwt/JwtBasicServiceTest.java b/src/test/java/pl/milosnicyit/codewarehousebackend/jwt/JwtBasicServiceTest.java
new file mode 100644
index 0000000..6e29996
--- /dev/null
+++ b/src/test/java/pl/milosnicyit/codewarehousebackend/jwt/JwtBasicServiceTest.java
@@ -0,0 +1,98 @@
+package pl.milosnicyit.codewarehousebackend.jwt;
+
+import org.commons.login.Username;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import pl.milosnicyit.codewarehousebackend.jwt.secret.JWTSecretService;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class JwtBasicServiceTest {
+ private static final String MOCK_SECRET = "ToJestBardzoTajnyKluczTestowyKtoryMaMin32Znaki!!";
+ private static final String TEST_USERNAME = "testUser123";
+
+ @Mock
+ private JWTSecretService jwtSecretService;
+
+ private JwtBasicService jwtBasicService;
+
+ @BeforeEach
+ void setUp() {
+ when(jwtSecretService.getSecret()).thenReturn(MOCK_SECRET);
+ jwtBasicService = new JwtBasicService(jwtSecretService);
+ }
+
+ @Test
+ void shouldGenerateValidToken() {
+ Username username = mock(Username.class);
+ when(username.toString()).thenReturn(TEST_USERNAME);
+
+ String token = jwtBasicService.generateToken(username);
+
+ assertNotNull(token);
+ assertFalse(token.isEmpty());
+ assertEquals(3, token.split("\\.").length);
+ }
+
+ @Test
+ void shouldExtractLoginFromToken() {
+ Username mockUsername = mock(Username.class);
+ when(mockUsername.toString()).thenReturn(TEST_USERNAME);
+
+ String generatedToken = jwtBasicService.generateToken(mockUsername);
+
+ Username extractedUsername = jwtBasicService.extractLogin(generatedToken);
+
+ assertNotNull(extractedUsername);
+ assertEquals(TEST_USERNAME, extractedUsername.toString());
+ }
+
+ @Test
+ void shouldReturnTrueWhenTokenIsValid() {
+ Username mockUsername = mock(Username.class);
+ when(mockUsername.toString()).thenReturn(TEST_USERNAME);
+ String generatedToken = jwtBasicService.generateToken(mockUsername);
+
+ boolean isValid = jwtBasicService.validateToken(generatedToken);
+
+ assertTrue(isValid);
+ }
+
+ @Test
+ void shouldReturnFalseWhenTokenIsTampered() {
+ // given
+ Username mockUsername = mock(Username.class);
+ when(mockUsername.toString()).thenReturn(TEST_USERNAME);
+ String generatedToken = jwtBasicService.generateToken(mockUsername);
+
+ String tamperedToken = generatedToken.substring(0, generatedToken.length() - 3) + "BAD";
+
+ boolean isValid = jwtBasicService.validateToken(tamperedToken);
+
+ assertFalse(isValid);
+ }
+
+ @Test
+ void shouldReturnFalseForMalformedString() {
+ String invalidToken = "to.nie.jest.prawdziwy.token";
+
+ boolean isValid = jwtBasicService.validateToken(invalidToken);
+
+ assertFalse(isValid);
+ }
+
+ @Test
+ void shouldReturnFalseForEmptyToken() {
+ String emptyToken = "";
+
+ boolean isValid = jwtBasicService.validateToken(emptyToken);
+
+ assertFalse(isValid);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JETSecretSpecificationTest.java b/src/test/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JETSecretSpecificationTest.java
new file mode 100644
index 0000000..ed2228b
--- /dev/null
+++ b/src/test/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JETSecretSpecificationTest.java
@@ -0,0 +1,16 @@
+package pl.milosnicyit.codewarehousebackend.jwt.secret;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+class JETSecretSpecificationTest {
+ @Test
+ void shouldReturnJWTDevService() {
+ JWTSecretConfiguration configuration = new JWTSecretConfiguration();
+
+ JWTSecretService service = configuration.jwtSecretService();
+
+ assertInstanceOf(JWTDevService.class, service);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JWTSecretTest.java b/src/test/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JWTSecretTest.java
new file mode 100644
index 0000000..09d282d
--- /dev/null
+++ b/src/test/java/pl/milosnicyit/codewarehousebackend/jwt/secret/JWTSecretTest.java
@@ -0,0 +1,51 @@
+package pl.milosnicyit.codewarehousebackend.jwt.secret;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class JWTSecretTest {
+ @Test
+ void shouldCreateJWTSecretWhenValid() {
+ String validSecret = "BardzoTajnySekretJWT123!@#";
+
+ JWTSecret jwtSecret = new JWTSecret(validSecret);
+
+ assertEquals(validSecret, jwtSecret.toString());
+ }
+
+ @Test
+ void shouldCreateJWTSecretWithExactlyFifteenCharacters() {
+ String validSecret = "123456789012345";
+
+ JWTSecret jwtSecret = new JWTSecret(validSecret);
+
+ assertEquals(validSecret, jwtSecret.toString());
+ }
+
+ @Test
+ void shouldThrowExceptionWhenSecretIsNull() {
+ assertThrows(NullPointerException.class, () -> new JWTSecret(null));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"", "tajny", "12345678901234"})
+ void shouldThrowExceptionWhenSecretIsTooShort(String invalidSecret) {
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+ () -> new JWTSecret(invalidSecret));
+
+ assertEquals("Secret must be at least 15 characters", exception.getMessage());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"Bardzo TajnySekret", "1234567890 12345", " ", " spacjaNaPoczatku1"})
+ void shouldThrowExceptionWhenSecretContainsSpaces(String invalidSecret) {
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+ () -> new JWTSecret(invalidSecret));
+
+ assertEquals("Secret must not contain spaces", exception.getMessage());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/pl/milosnicyit/codewarehousebackend/password/PasswordEncoderBasicServiceTest.java b/src/test/java/pl/milosnicyit/codewarehousebackend/password/PasswordEncoderBasicServiceTest.java
new file mode 100644
index 0000000..2d6211c
--- /dev/null
+++ b/src/test/java/pl/milosnicyit/codewarehousebackend/password/PasswordEncoderBasicServiceTest.java
@@ -0,0 +1,82 @@
+package pl.milosnicyit.codewarehousebackend.password;
+
+import org.commons.login.Password;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class PasswordEncoderBasicServiceTest {
+
+ @Mock
+ private PasswordEncoder passwordEncoder;
+
+ private PasswordEncoderBasicService passwordEncoderService;
+
+ @BeforeEach
+ void setUp() {
+ passwordEncoderService = new PasswordEncoderBasicService(passwordEncoder);
+ }
+
+ @Test
+ void shouldEncodePassword() {
+ // given
+ String rawPasswordString = "mojeSuperHaslo123";
+ String encodedPassword = "$2a$10$wypVjTq...ZaszyfrowaneHaslo";
+
+ Password mockPassword = mock(Password.class);
+ when(mockPassword.toString()).thenReturn(rawPasswordString);
+
+ when(passwordEncoder.encode(rawPasswordString)).thenReturn(encodedPassword);
+
+ // when
+ String result = passwordEncoderService.encode(mockPassword);
+
+ // then
+ assertEquals(encodedPassword, result);
+ verify(passwordEncoder, times(1)).encode(rawPasswordString);
+ }
+
+ @Test
+ void shouldReturnTrueWhenPasswordsMatch() {
+ // given
+ String rawPasswordString = "mojeSuperHaslo123";
+ String encodedPasswordFromDb = "$2a$10$wypVjTq...ZaszyfrowaneHaslo";
+
+ Password mockPassword = mock(Password.class);
+ when(mockPassword.toString()).thenReturn(rawPasswordString);
+
+ when(passwordEncoder.matches(rawPasswordString, encodedPasswordFromDb)).thenReturn(true);
+
+ boolean isMatch = passwordEncoderService.matches(mockPassword, encodedPasswordFromDb);
+
+ // then
+ assertTrue(isMatch);
+ verify(passwordEncoder, times(1)).matches(rawPasswordString, encodedPasswordFromDb);
+ }
+
+ @Test
+ void shouldReturnFalseWhenPasswordsDoNotMatch() {
+ // given
+ String rawPasswordString = "zleHaslo";
+ String encodedPasswordFromDb = "$2a$10$wypVjTq...ZaszyfrowaneHaslo";
+
+ Password mockPassword = mock(Password.class);
+ when(mockPassword.toString()).thenReturn(rawPasswordString);
+
+ when(passwordEncoder.matches(rawPasswordString, encodedPasswordFromDb)).thenReturn(false);
+
+ // when
+ boolean isMatch = passwordEncoderService.matches(mockPassword, encodedPasswordFromDb);
+
+ // then
+ assertFalse(isMatch);
+ verify(passwordEncoder, times(1)).matches(rawPasswordString, encodedPasswordFromDb);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/pl/milosnicyit/codewarehousebackend/password/PasswordEncoderSpecificationTest.java b/src/test/java/pl/milosnicyit/codewarehousebackend/password/PasswordEncoderSpecificationTest.java
new file mode 100644
index 0000000..08e315a
--- /dev/null
+++ b/src/test/java/pl/milosnicyit/codewarehousebackend/password/PasswordEncoderSpecificationTest.java
@@ -0,0 +1,20 @@
+package pl.milosnicyit.codewarehousebackend.password;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class PasswordEncoderSpecificationTest {
+
+ @Test
+ void shouldCreatePasswordEncoderServiceBean() {
+ PasswordEncoderConfiguration configuration = new PasswordEncoderConfiguration();
+
+ PasswordEncoderService service = configuration.passwordEncoderService();
+
+
+ assertNotNull(service, "PasswordEncoderService bean should not be null");
+ assertInstanceOf(PasswordEncoderBasicService.class, service, "Bean should be an instance of PasswordEncoderBasicService");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/pl/milosnicyit/codewarehousebackend/users/UserAppServiceTest.java b/src/test/java/pl/milosnicyit/codewarehousebackend/users/UserAppServiceTest.java
new file mode 100644
index 0000000..e5da739
--- /dev/null
+++ b/src/test/java/pl/milosnicyit/codewarehousebackend/users/UserAppServiceTest.java
@@ -0,0 +1,91 @@
+package pl.milosnicyit.codewarehousebackend.users;
+
+import org.commons.login.Password;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import pl.milosnicyit.codewarehousebackend.jwt.JWTService;
+import pl.milosnicyit.codewarehousebackend.password.PasswordEncoderService;
+import pl.milosnicyit.codewarehousebackend.users.database.wrapper.UserDTO;
+import pl.milosnicyit.codewarehousebackend.users.database.wrapper.UserRepositoryWrapper;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class UserAppServiceTest {
+
+ @Mock
+ private UserRepositoryWrapper userRepositoryWrapper;
+
+ @Mock
+ private PasswordEncoderService passwordEncoderService;
+
+ @Mock
+ private JWTService jwtService;
+
+ @InjectMocks
+ private UserAppService userAppService;
+
+ @Test
+ void shouldReturnNullWhenUsernameIsAlreadyTaken() {
+ String username = "zajetyLogin";
+ String rawPassword = "secretPassworddddddddddddddddddddddddddddddddddddd";
+
+ when(userRepositoryWrapper.existsByUsername(username)).thenReturn(true);
+
+ String result = userAppService.registerUser(username, rawPassword);
+
+ assertNull(result, "Should return null if user already exists");
+
+ verify(userRepositoryWrapper, never()).save(any());
+ verify(jwtService, never()).generateToken(anyString());
+ }
+
+ @Test
+ void shouldRegisterUserAndReturnTokenWhenDataIsCorrect() {
+ String username = "nowyUser";
+ String rawPassword = "secretPasswodddddddddddddddddddddddd";
+ String encodedPassword = "encodedPassword123";
+ String expectedToken = "jwt.token.here";
+
+ when(userRepositoryWrapper.existsByUsername(username)).thenReturn(false);
+ when(passwordEncoderService.encode(any(Password.class))).thenReturn(encodedPassword);
+ when(userRepositoryWrapper.save(any(UserDTO.class))).thenReturn(true);
+ when(jwtService.generateToken(username)).thenReturn(expectedToken);
+
+ String result = userAppService.registerUser(username, rawPassword);
+
+ assertEquals(expectedToken, result, "Should return generated JWT token");
+
+ ArgumentCaptor userDtoCaptor = ArgumentCaptor.forClass(UserDTO.class);
+ verify(userRepositoryWrapper).save(userDtoCaptor.capture());
+
+ UserDTO capturedUser = userDtoCaptor.getValue();
+ assertEquals(username, capturedUser.getUsername(), "Saved user should have correct username");
+ assertEquals(encodedPassword, capturedUser.getPassword(), "Saved user should have ENCODED password");
+ }
+
+ @Test
+ void shouldReturnNullWhenDatabaseSaveFails() {
+ String username = "nowyUser";
+ String rawPassword = "secretPdddddddddddddddddddddddddddddassword";
+ String encodedPassword = "encodedPassword123";
+
+ when(userRepositoryWrapper.existsByUsername(username)).thenReturn(false);
+ when(passwordEncoderService.encode(any(Password.class))).thenReturn(encodedPassword);
+
+ when(userRepositoryWrapper.save(any(UserDTO.class))).thenReturn(false);
+
+ String result = userAppService.registerUser(username, rawPassword);
+
+ assertNull(result, "Should return null if saving to DB fails");
+
+ verify(jwtService, never()).generateToken(anyString());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/pl/milosnicyit/codewarehousebackend/users/UserServiceSpecificationTest.java b/src/test/java/pl/milosnicyit/codewarehousebackend/users/UserServiceSpecificationTest.java
new file mode 100644
index 0000000..0ce0d79
--- /dev/null
+++ b/src/test/java/pl/milosnicyit/codewarehousebackend/users/UserServiceSpecificationTest.java
@@ -0,0 +1,39 @@
+package pl.milosnicyit.codewarehousebackend.users;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import pl.milosnicyit.codewarehousebackend.jwt.JWTService;
+import pl.milosnicyit.codewarehousebackend.password.PasswordEncoderService;
+import pl.milosnicyit.codewarehousebackend.users.database.wrapper.UserRepositoryWrapper;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@ExtendWith(MockitoExtension.class)
+class UserServiceSpecificationTest {
+
+ @Mock
+ private UserRepositoryWrapper userRepositoryWrapper;
+
+ @Mock
+ private PasswordEncoderService passwordEncoderService;
+
+ @Mock
+ private JWTService jwtService;
+
+ @Test
+ void shouldCreateUsersServiceBean() {
+ UserServiceConfiguration configuration = new UserServiceConfiguration(
+ userRepositoryWrapper,
+ passwordEncoderService,
+ jwtService
+ );
+
+ UsersService usersService = configuration.usersService();
+
+ assertNotNull(usersService, "UsersService bean should not be null");
+ assertInstanceOf(UserAppService.class, usersService, "Bean should be an instance of UserAppService");
+ }
+}
\ No newline at end of file