Java‐based Identity Access module ‐ Test Files - Wiz-DevTech/prettygirllz GitHub Wiki

1. Main Application Test

// src/test/java/com/wizdevtech/identityaccess/IdentityAccessApplicationTests.java
package com.wizdevtech.identityaccess;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

@SpringBootTest
@ActiveProfiles("test")
class IdentityAccessApplicationTests {

    @Test
    void contextLoads() {
        // Verifies that the application context loads successfully
    }
}

2. Controller Tests

// src/test/java/com/wizdevtech/identityaccess/controller/AuthControllerTest.java
package com.wizdevtech.identityaccess.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wizdevtech.identityaccess.dto.AuthenticationRequest;
import com.wizdevtech.identityaccess.dto.AuthenticationResponse;
import com.wizdevtech.identityaccess.dto.RegistrationRequest;
import com.wizdevtech.identityaccess.model.User;
import com.wizdevtech.identityaccess.service.AuthenticationService;
import com.wizdevtech.identityaccess.service.JwtService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.test.web.servlet.MockMvc;

import java.util.Set;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(AuthController.class)
class AuthControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @MockBean
    private AuthenticationService authService;

    @MockBean
    private JwtService jwtService;

    private AuthenticationRequest validLoginRequest;
    private RegistrationRequest validRegistrationRequest;
    private AuthenticationResponse successResponse;
    private User createdUser;

    @BeforeEach
    void setUp() {
        validLoginRequest = new AuthenticationRequest("[email protected]", "password123");
        
        validRegistrationRequest = new RegistrationRequest();
        validRegistrationRequest.setEmail("[email protected]");
        validRegistrationRequest.setPassword("securepass");
        validRegistrationRequest.setRoles(Set.of("USER"));
        
        createdUser = new User();
        createdUser.setId(1L);
        createdUser.setEmail("[email protected]");
        createdUser.setRoles(Set.of("USER"));
        
        successResponse = AuthenticationResponse.builder()
                .token("jwt-token")
                .id(1L)
                .email("[email protected]")
                .roles(Set.of("USER"))
                .build();
    }

    @Test
    void login_withValidCredentials_returnsToken() throws Exception {
        // Arrange
        when(authService.authenticate(any(AuthenticationRequest.class))).thenReturn(successResponse);

        // Act & Assert
        mockMvc.perform(post("/api/auth/login")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(validLoginRequest)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.token").value("jwt-token"))
                .andExpect(jsonPath("$.id").value(1))
                .andExpect(jsonPath("$.email").value("[email protected]"));
        
        verify(authService, times(1)).authenticate(any(AuthenticationRequest.class));
    }

    @Test
    void login_withInvalidCredentials_returns401() throws Exception {
        // Arrange
        when(authService.authenticate(any(AuthenticationRequest.class)))
                .thenThrow(new BadCredentialsException("Invalid credentials"));

        // Act & Assert
        mockMvc.perform(post("/api/auth/login")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(validLoginRequest)))
                .andExpect(status().isUnauthorized());
        
        verify(authService, times(1)).authenticate(any(AuthenticationRequest.class));
    }

    @Test
    void register_withValidData_returnsUserAndToken() throws Exception {
        // Arrange
        when(authService.createUser(any(User.class), anyString())).thenReturn(createdUser);
        when(jwtService.generateToken(any(User.class))).thenReturn("jwt-token");

        // Act & Assert
        mockMvc.perform(post("/api/auth/register")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(validRegistrationRequest)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.token").exists())
                .andExpect(jsonPath("$.email").value("[email protected]"));
        
        verify(authService, times(1)).createUser(any(User.class), anyString());
        verify(jwtService, times(1)).generateToken(any(User.class));
    }

    @Test
    void validateToken_withValidToken_returnsTrue() throws Exception {
        // Arrange
        when(jwtService.extractAllClaims(anyString())).thenReturn(mock(Claims.class));

        // Act & Assert
        mockMvc.perform(post("/api/auth/validate")
                .param("token", "valid-token"))
                .andExpect(status().isOk())
                .andExpect(content().string("true"));
        
        verify(jwtService, times(1)).extractAllClaims(anyString());
    }

    @Test
    void validateToken_withInvalidToken_returnsFalse() throws Exception {
        // Arrange
        doThrow(new JwtException("Invalid token")).when(jwtService).extractAllClaims(anyString());

        // Act & Assert
        mockMvc.perform(post("/api/auth/validate")
                .param("token", "invalid-token"))
                .andExpect(status().isOk())
                .andExpect(content().string("false"));
        
        verify(jwtService, times(1)).extractAllClaims(anyString());
    }
}

3. Service Tests

3.1 Authentication Service Test

// src/test/java/com/wizdevtech/identityaccess/service/AuthenticationServiceTest.java
package com.wizdevtech.identityaccess.service;

import com.wizdevtech.identityaccess.dto.AuthenticationRequest;
import com.wizdevtech.identityaccess.dto.AuthenticationResponse;
import com.wizdevtech.identityaccess.model.User;
import com.wizdevtech.identityaccess.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.Optional;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class AuthenticationServiceTest {

    @Mock
    private UserRepository userRepository;

    @Mock
    private JwtService jwtService;

    @Mock
    private PasswordEncoder passwordEncoder;

    @Mock
    private EncryptionService encryptionService;

    @InjectMocks
    private AuthenticationService authService;

    private User testUser;
    private AuthenticationRequest loginRequest;

    @BeforeEach
    void setUp() {
        testUser = new User();
        testUser.setId(1L);
        testUser.setEmail("[email protected]");
        testUser.setPasswordHash("hashed_password");
        testUser.setRoles(Set.of("USER"));

        loginRequest = new AuthenticationRequest("[email protected]", "password123");
    }

    @Test
    void authenticate_withValidCredentials_returnsToken() {
        // Arrange
        when(userRepository.findByEmail(anyString())).thenReturn(Optional.of(testUser));
        when(passwordEncoder.matches(anyString(), anyString())).thenReturn(true);
        when(jwtService.generateToken(any(User.class))).thenReturn("jwt-token");

        // Act
        AuthenticationResponse response = authService.authenticate(loginRequest);

        // Assert
        assertNotNull(response);
        assertEquals("jwt-token", response.getToken());
        assertEquals(testUser.getId(), response.getId());
        assertEquals(testUser.getEmail(), response.getEmail());
        assertEquals(testUser.getRoles(), response.getRoles());

        verify(userRepository, times(1)).findByEmail("[email protected]");
        verify(passwordEncoder, times(1)).matches("password123", "hashed_password");
        verify(jwtService, times(1)).generateToken(testUser);
    }

    @Test
    void authenticate_withNonExistentUser_throwsUsernameNotFoundException() {
        // Arrange
        when(userRepository.findByEmail(anyString())).thenReturn(Optional.empty());

        // Act & Assert
        assertThrows(UsernameNotFoundException.class, () -> {
            authService.authenticate(loginRequest);
        });

        verify(userRepository, times(1)).findByEmail("[email protected]");
        verify(passwordEncoder, never()).matches(anyString(), anyString());
        verify(jwtService, never()).generateToken(any(User.class));
    }

    @Test
    void authenticate_withInvalidPassword_throwsBadCredentialsException() {
        // Arrange
        when(userRepository.findByEmail(anyString())).thenReturn(Optional.of(testUser));
        when(passwordEncoder.matches(anyString(), anyString())).thenReturn(false);

        // Act & Assert
        assertThrows(BadCredentialsException.class, () -> {
            authService.authenticate(loginRequest);
        });

        verify(userRepository, times(1)).findByEmail("[email protected]");
        verify(passwordEncoder, times(1)).matches("password123", "hashed_password");
        verify(jwtService, never()).generateToken(any(User.class));
    }

    @Test
    void createUser_withValidData_returnsCreatedUser() {
        // Arrange
        User newUser = new User();
        newUser.setEmail("[email protected]");
        newUser.setRoles(Set.of("USER"));
        
        String rawPassword = "securepass";
        String encryptedData = "encrypted_data";
        String hashedPassword = "hashed_password";
        
        when(userRepository.existsByEmail(anyString())).thenReturn(false);
        when(passwordEncoder.encode(anyString())).thenReturn(hashedPassword);
        when(encryptionService.encrypt(anyString())).thenReturn(encryptedData);
        when(userRepository.save(any(User.class))).thenAnswer(invocation -> {
            User savedUser = invocation.getArgument(0);
            savedUser.setId(1L);
            return savedUser;
        });

        // Set sensitive data
        newUser.setSensitiveData("sensitive_info");

        // Act
        User result = authService.createUser(newUser, rawPassword);

        // Assert
        assertNotNull(result);
        assertEquals(1L, result.getId());
        assertEquals("[email protected]", result.getEmail());
        assertEquals(hashedPassword, result.getPasswordHash());
        assertEquals(encryptedData, result.getSensitiveData());
        assertEquals(Set.of("USER"), result.getRoles());

        verify(userRepository, times(1)).existsByEmail("[email protected]");
        verify(passwordEncoder, times(1)).encode(rawPassword);
        verify(encryptionService, times(1)).encrypt("sensitive_info");
        verify(userRepository, times(1)).save(newUser);
    }

    @Test
    void createUser_withExistingEmail_throwsIllegalArgumentException() {
        // Arrange
        User newUser = new User();
        newUser.setEmail("[email protected]");
        
        when(userRepository.existsByEmail("[email protected]")).thenReturn(true);

        // Act & Assert
        assertThrows(IllegalArgumentException.class, () -> {
            authService.createUser(newUser, "password");
        });

        verify(userRepository, times(1)).existsByEmail("[email protected]");
        verify(passwordEncoder, never()).encode(anyString());
        verify(userRepository, never()).save(any(User.class));
    }
}

3.2 JWT Service Test

// src/test/java/com/wizdevtech/identityaccess/service/JwtServiceTest.java
package com.wizdevtech.identityaccess.service;

import com.wizdevtech.identityaccess.model.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class JwtServiceTest {

    @InjectMocks
    private JwtService jwtService;

    private User testUser;
    private String validToken;

    @BeforeEach
    void setUp() {
        // Set properties via reflection
        ReflectionTestUtils.setField(jwtService, "jwtSecret", "testSecret01234567890123456789012345678901");
        ReflectionTestUtils.setField(jwtService, "jwtExpiration", 3600000L); // 1 hour
        ReflectionTestUtils.setField(jwtService, "issuer", "test-issuer");

        // Create a test user
        testUser = new User();
        testUser.setId(1L);
        testUser.setEmail("[email protected]");
        testUser.setRoles(Set.of("USER"));

        // Generate a valid token
        validToken = jwtService.generateToken(testUser);
    }

    @Test
    void generateToken_withValidUser_returnsJwtToken() {
        // Act
        String token = jwtService.generateToken(testUser);

        // Assert
        assertNotNull(token);
        assertTrue(token.length() > 0);

        // Verify token claims
        Claims claims = jwtService.extractAllClaims(token);
        assertEquals("1", claims.getSubject());
        assertEquals("[email protected]", claims.get("email", String.class));
        assertEquals("test-issuer", claims.getIssuer());
        assertEquals(Set.of("USER"), claims.get("roles", Set.class));
        
        // Verify expiration
        assertTrue(claims.getExpiration().after(new Date()));
    }

    @Test
    void validateToken_withValidTokenAndUser_returnsTrue() {
        // Arrange
        UserDetails userDetails = mock(UserDetails.class);
        when(userDetails.getUsername()).thenReturn("1"); // User ID

        // Act
        boolean result = jwtService.validateToken(validToken, userDetails);

        // Assert
        assertTrue(result);
    }

    @Test
    void validateToken_withExpiredToken_returnsFalse() {
        // Arrange
        // Create a token with very short expiration
        ReflectionTestUtils.setField(jwtService, "jwtExpiration", 1L); // 1 millisecond
        String expiredToken = jwtService.generateToken(testUser);
        
        // Wait for token to expire
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            fail("Test interrupted");
        }
        
        UserDetails userDetails = mock(UserDetails.class);
        when(userDetails.getUsername()).thenReturn("1");

        // Act
        boolean result = jwtService.validateToken(expiredToken, userDetails);

        // Assert
        assertFalse(result);
    }

    @Test
    void validateToken_withInvalidSignature_throwsJwtException() {
        // Arrange
        String invalidToken = validToken.substring(0, validToken.length() - 5) + "12345";
        UserDetails userDetails = mock(UserDetails.class);

        // Act & Assert
        assertThrows(JwtException.class, () -> {
            jwtService.extractAllClaims(invalidToken);
        });
    }

    @Test
    void extractUsername_withValidToken_returnsSubject() {
        // Act
        String subject = jwtService.extractUsername(validToken);

        // Assert
        assertEquals("1", subject);
    }

    @Test
    void extractExpiration_withValidToken_returnsExpirationDate() {
        // Act
        Date expiration = jwtService.extractExpiration(validToken);

        // Assert
        assertNotNull(expiration);
        assertTrue(expiration.after(new Date()));
    }

    @Test
    void extractClaim_withValidTokenAndClaimResolver_returnsClaimValue() {
        // Act
        String issuer = jwtService.extractClaim(validToken, Claims::getIssuer);

        // Assert
        assertEquals("test-issuer", issuer);
    }
}

3.3 Encryption Service Test

// src/test/java/com/wizdevtech/identityaccess/service/EncryptionServiceTest.java
package com.wizdevtech.identityaccess.service;

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 static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
class EncryptionServiceTest {

    private EncryptionService encryptionService;
    private final String validKey = "12345678901234567890123456789012"; // 32 chars

    @BeforeEach
    void setUp() {
        encryptionService = new EncryptionService(validKey);
    }

    @Test
    void constructor_withInvalidKeyLength_throwsIllegalArgumentException() {
        // Arrange
        String shortKey = "tooshort";

        // Act & Assert
        assertThrows(IllegalArgumentException.class, () -> {
            new EncryptionService(shortKey);
        });
    }

    @Test
    void encrypt_withValidData_returnsEncryptedString() {
        // Arrange
        String plainText = "This is a secret message";

        // Act
        String encrypted = encryptionService.encrypt(plainText);

        // Assert
        assertNotNull(encrypted);
        assertNotEquals(plainText, encrypted);
        assertTrue(encrypted.contains(":"));
    }

    @Test
    void encrypt_withNullData_returnsNull() {
        // Act
        String encrypted = encryptionService.encrypt(null);

        // Assert
        assertNull(encrypted);
    }

    @Test
    void decrypt_withValidEncryptedData_returnsOriginalText() {
        // Arrange
        String plainText = "This is a secret message";
        String encrypted = encryptionService.encrypt(plainText);

        // Act
        String decrypted = encryptionService.decrypt(encrypted);

        // Assert
        assertEquals(plainText, decrypted);
    }

    @Test
    void decrypt_withNullData_returnsNull() {
        // Act
        String decrypted = encryptionService.decrypt(null);

        // Assert
        assertNull(decrypted);
    }

    @Test
    void decrypt_withInvalidFormat_throwsRuntimeException() {
        // Arrange
        String invalidEncrypted = "not-valid-format";

        // Act & Assert
        assertThrows(RuntimeException.class, () -> {
            encryptionService.decrypt(invalidEncrypted);
        });
    }

    @Test
    void encryptAndDecrypt_withLongText_worksCorrectly() {
        // Arrange
        StringBuilder longTextBuilder = new StringBuilder();
        for (int i = 0; i < 1000; i++) {
            longTextBuilder.append("This is a very long text that needs to be encrypted. ");
        }
        String longText = longTextBuilder.toString();

        // Act
        String encrypted = encryptionService.encrypt(longText);
        String decrypted = encryptionService.decrypt(encrypted);

        // Assert
        assertEquals(longText, decrypted);
    }
}

4. Repository Tests

// src/test/java/com/wizdevtech/identityaccess/repository/UserRepositoryTest.java
package com.wizdevtech.identityaccess.repository;

import com.wizdevtech.identityaccess.model.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.util.Optional;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.*;

@DataJpaTest
@ActiveProfiles("test")
@Testcontainers
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserRepositoryTest {

    @Container
    static PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<>("postgres:14")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");

    @Autowired
    private UserRepository userRepository;

    @Test
    void findByEmail_withExistingEmail_returnsUser() {
        // Arrange
        User user = new User();
        user.setEmail("[email protected]");
        user.setPasswordHash("hashedpassword");
        user.setRoles(Set.of("USER"));
        userRepository.save(user);

        // Act
        Optional<User> foundUser = userRepository.findByEmail("[email protected]");

        // Assert
        assertTrue(foundUser.isPresent());
        assertEquals("[email protected]", foundUser.get().getEmail());
    }

    @Test
    void findByEmail_withNonExistentEmail_returnsEmptyOptional() {
        // Act
        Optional<User> foundUser = userRepository.findByEmail("[email protected]");

        // Assert
        assertTrue(foundUser.isEmpty());
    }

    @Test
    void existsByEmail_withExistingEmail_returnsTrue() {
        // Arrange
        User user = new User();
        user.setEmail("[email protected]");
        user.setPasswordHash("hashedpassword");
        user.setRoles(Set.of("USER"));
        userRepository.save(user);

        // Act
        boolean exists = userRepository.existsByEmail("[email protected]");

        // Assert
        assertTrue(exists);
    }

    @Test
    void existsByEmail_withNonExistentEmail_returnsFalse() {
        // Act
        boolean exists = userRepository.existsByEmail("[email protected]");

        // Assert
        assertFalse(exists);
    }

    @Test
    void save_withValidUser_persistsUserWithGeneratedId() {
        // Arrange
        User user = new User();
        user.setEmail("[email protected]");
        user.setPasswordHash("hashedpassword");
        user.setSensitiveData("sensitive information");
        user.setRoles(Set.of("USER", "ADMIN"));

        // Act
        User savedUser = userRepository.save(user);

        // Assert
        assertNotNull(savedUser.getId());
        assertEquals("[email protected]", savedUser.getEmail());
        assertEquals("hashedpassword", savedUser.getPasswordHash());
        assertEquals("sensitive information", savedUser.getSensitiveData());
        assertEquals(Set.of("USER", "ADMIN"), savedUser.getRoles());
        assertNotNull(savedUser.getCreatedAt());
        assertNotNull(savedUser.getUpdatedAt());
    }
}

5. gRPC Service Tests

// src/test/java/com/wizdevtech/identityaccess/grpc/AuthServiceImplTest.java
package com.wizdevtech.identityaccess.grpc;

import com.wizdevtech.identityaccess.dto.AuthenticationRequest;
import com.wizdevtech.identityaccess.dto.AuthenticationResponse;
import com.wizdevtech.identityaccess.service.AuthenticationService;
import com.wizdevtech.identityaccess.service.JwtService;
import io.grpc.internal.testing.StreamRecorder;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.authentication.BadCredentialsException;

import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class AuthServiceImplTest {

    @Mock
    private AuthenticationService authService;

    @Mock
    private JwtService jwtService;

    @InjectMocks
    private AuthServiceImpl grpcAuthService;

    private AuthenticationResponse authResponse;
    private Claims mockClaims;

    @BeforeEach
    void setUp() {
        authResponse = AuthenticationResponse.builder()
                .token("test-token")
                .id(1L)
                .email("[email protected]")
                .roles(Set.of("USER"))
                .build();

        mockClaims = mock(Claims.class);
    }

    @Test
    void login_withValidCredentials_returnsSuccessResponse() throws Exception {
        // Arrange
        LoginRequest request = LoginRequest.newBuilder()
                .setEmail("[email protected]")
                .setPassword("password123")
                .build();

        StreamRecorder<LoginResponse> responseObserver = StreamRecorder.create();
        
        when(authService.authenticate(any(AuthenticationRequest.class))).thenReturn(authResponse);

        // Act
        grpcAuthService.login(request, responseObserver);

        // Assert
        if (!responseObserver.awaitCompletion(5, TimeUnit.SECONDS)) {
            fail("The call did not terminate in time");
        }

        assertNull(responseObserver.getError());
        List<LoginResponse> results = responseObserver.getValues();
        assertEquals(1, results.size());
        
        LoginResponse response = results.get(0);
        assertTrue(response.getSuccess());
        assertEquals("test-token", response.getToken());
        assertEquals(1, response.getUser().getId());
        assertEquals("[email protected]", response.getUser().getEmail());
        assertEquals(1, response.getUser().getRolesCount());
        assertEquals("USER", response.getUser().getRoles(0));
        
        verify(authService, times(1)).authenticate(any(AuthenticationRequest.class));
    }

    @Test
    void login_withInvalidCredentials_returnsFailureResponse() throws Exception {
        // Arrange
        LoginRequest request = LoginRequest.newBuilder()
                .setEmail("[email protected]")
                .setPassword("wrongpass")
                .build();

        StreamRecorder<LoginResponse> responseObserver = StreamRecorder.create();
        
        when(authService.authenticate(any(AuthenticationRequest.class)))
                .thenThrow(new BadCredentialsException("Invalid credentials"));

        // Act
        grpcAuthService.login(request, responseObserver);

        // Assert
        if (!responseObserver.awaitCompletion(5, TimeUnit.SECONDS)) {
            fail("The call did not terminate in time");
        }

        assertNull(responseObserver.getError());
        List<LoginResponse> results = responseObserver.getValues();
        assertEquals(1, results.size());
        
        LoginResponse response = results.get(0);
        assertFalse(response.getSuccess());
        assertEquals("Invalid credentials", response.getMessage());
        assertFalse(response.hasUser());
        
        verify(authService, times(1)).authenticate(any(AuthenticationRequest.class));
    }

    @Test
    void validateToken_withValidToken_returnsValidResponse() throws Exception {
        // Arrange
        ValidateTokenRequest request = ValidateTokenRequest.newBuilder()
                .setToken("valid-token")
                .build();

        StreamRecorder<ValidateTokenResponse> responseObserver = StreamRecorder.create();
        
        when(jwtService.extractAllClaims(anyString())).thenReturn(mockClaims);
        when(jwtService.isTokenExpired(anyString())).thenReturn(false);
        when(jwtService.extractUsername(anyString())).thenReturn("1");
        when(mockClaims.get("email", String.class)).thenReturn("[email protected]");
        when(mockClaims.get(eq("roles"), any())).thenReturn(List.of("USER"));

        // Act
        grpcAuthService.validateToken(request, responseObserver);

        // Assert
        if (!responseObserver.awaitCompletion(5, TimeUnit.SECONDS)) {
            fail("The call did not terminate in time");
        }

        assertNull(responseObserver.getError());
        List<ValidateTokenResponse> results = responseObserver.getValues();
        assertEquals(1, results.size());
        
        ValidateTokenResponse response = results.get(0);
        assertTrue(response.getIsValid());
        assertTrue(response.hasUser());
        assertEquals(1, response.getUser().getId());
        assertEquals("[email protected]", response.getUser().getEmail());
        assertEquals(1, response.getUser().getRolesCount());
        
        verify(jwtService, times(1)).extractAllClaims("valid-token");
        verify(jwtService, times(1)).isTokenExpired("valid-token

## 5. gRPC Service Tests (continued)

```java
// src/test/java/com/wizdevtech/identityaccess/grpc/AuthServiceImplTest.java (continued)
        verify(jwtService, times(1)).isTokenExpired("valid-token");
        verify(jwtService, times(1)).extractUsername("valid-token");
    }

    @Test
    void validateToken_withExpiredToken_returnsInvalidResponse() throws Exception {
        // Arrange
        ValidateTokenRequest request = ValidateTokenRequest.newBuilder()
                .setToken("expired-token")
                .build();

        StreamRecorder<ValidateTokenResponse> responseObserver = StreamRecorder.create();
        
        when(jwtService.extractAllClaims(anyString())).thenReturn(mockClaims);
        when(jwtService.isTokenExpired(anyString())).thenReturn(true);

        // Act
        grpcAuthService.validateToken(request, responseObserver);

        // Assert
        if (!responseObserver.awaitCompletion(5, TimeUnit.SECONDS)) {
            fail("The call did not terminate in time");
        }

        assertNull(responseObserver.getError());
        List<ValidateTokenResponse> results = responseObserver.getValues();
        assertEquals(1, results.size());
        
        ValidateTokenResponse response = results.get(0);
        assertFalse(response.getIsValid());
        assertTrue(response.hasUser()); // Default empty user is present
        assertEquals(0, response.getUser().getId()); // Default value
        
        verify(jwtService, times(1)).extractAllClaims("expired-token");
        verify(jwtService, times(1)).isTokenExpired("expired-token");
        verify(jwtService, never()).extractUsername(anyString());
    }

    @Test
    void validateToken_withInvalidToken_returnsInvalidResponse() throws Exception {
        // Arrange
        ValidateTokenRequest request = ValidateTokenRequest.newBuilder()
                .setToken("invalid-token")
                .build();

        StreamRecorder<ValidateTokenResponse> responseObserver = StreamRecorder.create();
        
        when(jwtService.extractAllClaims(anyString())).thenThrow(new JwtException("Invalid token"));

        // Act
        grpcAuthService.validateToken(request, responseObserver);

        // Assert
        if (!responseObserver.awaitCompletion(5, TimeUnit.SECONDS)) {
            fail("The call did not terminate in time");
        }

        assertNull(responseObserver.getError());
        List<ValidateTokenResponse> results = responseObserver.getValues();
        assertEquals(1, results.size());
        
        ValidateTokenResponse response = results.get(0);
        assertFalse(response.getIsValid());
        assertTrue(response.hasUser()); // Default empty user is present
        
        verify(jwtService, times(1)).extractAllClaims("invalid-token");
        verify(jwtService, never()).isTokenExpired(anyString());
    }
}

6. Security Component Tests

// src/test/java/com/wizdevtech/identityaccess/security/JwtAuthenticationFilterTest.java
package com.wizdevtech.identityaccess.security;

import com.wizdevtech.identityaccess.service.JwtService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.core.context.SecurityContextHolder;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class JwtAuthenticationFilterTest {

    @Mock
    private JwtService jwtService;

    @Mock
    private HttpServletRequest request;

    @Mock
    private HttpServletResponse response;

    @Mock
    private FilterChain filterChain;

    @InjectMocks
    private JwtAuthenticationFilter jwtAuthFilter;

    @BeforeEach
    void setUp() {
        SecurityContextHolder.clearContext();
    }

    @Test
    void doFilterInternal_withNoAuthHeader_continuesFilterChain() throws ServletException, IOException {
        // Arrange
        when(request.getHeader("Authorization")).thenReturn(null);

        // Act
        jwtAuthFilter.doFilterInternal(request, response, filterChain);

        // Assert
        verify(filterChain, times(1)).doFilter(request, response);
        verify(jwtService, never()).extractAllClaims(anyString());
        assertNull(SecurityContextHolder.getContext().getAuthentication());
    }

    @Test
    void doFilterInternal_withNonBearerAuthHeader_continuesFilterChain() throws ServletException, IOException {
        // Arrange
        when(request.getHeader("Authorization")).thenReturn("Basic abc123");

        // Act
        jwtAuthFilter.doFilterInternal(request, response, filterChain);

        // Assert
        verify(filterChain, times(1)).doFilter(request, response);
        verify(jwtService, never()).extractAllClaims(anyString());
        assertNull(SecurityContextHolder.getContext().getAuthentication());
    }

    @Test
    void doFilterInternal_withValidToken_setsAuthentication() throws ServletException, IOException {
        // Arrange
        String token = "valid.jwt.token";
        when(request.getHeader("Authorization")).thenReturn("Bearer " + token);
        
        Map<String, Object> claims = new HashMap<>();
        claims.put("roles", List.of("USER", "ADMIN"));
        
        when(jwtService.extractAllClaims(token)).thenReturn(claims);
        when(jwtService.isTokenExpired(token)).thenReturn(false);
        when(jwtService.extractUsername(token)).thenReturn("1");

        // Act
        jwtAuthFilter.doFilterInternal(request, response, filterChain);

        // Assert
        verify(filterChain, times(1)).doFilter(request, response);
        verify(jwtService, times(1)).extractAllClaims(token);
        verify(jwtService, times(1)).isTokenExpired(token);
        verify(jwtService, times(1)).extractUsername(token);
        
        assertNotNull(SecurityContextHolder.getContext().getAuthentication());
        assertEquals("1", SecurityContextHolder.getContext().getAuthentication().getPrincipal());
        assertEquals(2, SecurityContextHolder.getContext().getAuthentication().getAuthorities().size());
    }

    @Test
    void doFilterInternal_withExpiredToken_continuesFilterChainWithoutAuth() throws ServletException, IOException {
        // Arrange
        String token = "expired.jwt.token";
        when(request.getHeader("Authorization")).thenReturn("Bearer " + token);
        
        Map<String, Object> claims = new HashMap<>();
        claims.put("roles", List.of("USER"));
        
        when(jwtService.extractAllClaims(token)).thenReturn(claims);
        when(jwtService.isTokenExpired(token)).thenReturn(true);

        // Act
        jwtAuthFilter.doFilterInternal(request, response, filterChain);

        // Assert
        verify(filterChain, times(1)).doFilter(request, response);
        verify(jwtService, times(1)).extractAllClaims(token);
        verify(jwtService, times(1)).isTokenExpired(token);
        verify(jwtService, never()).extractUsername(anyString());
        
        assertNull(SecurityContextHolder.getContext().getAuthentication());
    }

    @Test
    void doFilterInternal_withInvalidToken_continuesFilterChainWithoutAuth() throws ServletException, IOException {
        // Arrange
        String token = "invalid.jwt.token";
        when(request.getHeader("Authorization")).thenReturn("Bearer " + token);
        
        when(jwtService.extractAllClaims(token)).thenThrow(new RuntimeException("Invalid token"));

        // Act
        jwtAuthFilter.doFilterInternal(request, response, filterChain);

        // Assert
        verify(filterChain, times(1)).doFilter(request, response);
        verify(jwtService, times(1)).extractAllClaims(token);
        verify(jwtService, never()).isTokenExpired(anyString());
        verify(jwtService, never()).extractUsername(anyString());
        
        assertNull(SecurityContextHolder.getContext().getAuthentication());
    }
}

7. DTO Tests

// src/test/java/com/wizdevtech/identityaccess/dto/AuthenticationRequestTest.java
package com.wizdevtech.identityaccess.dto;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.Set;

import static org.junit.jupiter.api.Assertions.*;

class AuthenticationRequestTest {

    private Validator validator;

    @BeforeEach
    void setUp() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }

    @Test
    void validate_withValidData_noViolations() {
        // Arrange
        AuthenticationRequest request = new AuthenticationRequest("[email protected]", "password123");

        // Act
        Set<ConstraintViolation<AuthenticationRequest>> violations = validator.validate(request);

        // Assert
        assertTrue(violations.isEmpty());
    }

    @Test
    void validate_withEmptyEmail_hasViolation() {
        // Arrange
        AuthenticationRequest request = new AuthenticationRequest("", "password123");

        // Act
        Set<ConstraintViolation<AuthenticationRequest>> violations = validator.validate(request);

        // Assert
        assertFalse(violations.isEmpty());
        assertEquals(1, violations.size());
        assertEquals("email", violations.iterator().next().getPropertyPath().toString());
    }

    @Test
    void validate_withInvalidEmail_hasViolation() {
        // Arrange
        AuthenticationRequest request = new AuthenticationRequest("not-an-email", "password123");

        // Act
        Set<ConstraintViolation<AuthenticationRequest>> violations = validator.validate(request);

        // Assert
        assertFalse(violations.isEmpty());
        assertEquals(1, violations.size());
        assertEquals("email", violations.iterator().next().getPropertyPath().toString());
    }

    @Test
    void validate_withEmptyPassword_hasViolation() {
        // Arrange
        AuthenticationRequest request = new AuthenticationRequest("[email protected]", "");

        // Act
        Set<ConstraintViolation<AuthenticationRequest>> violations = validator.validate(request);

        // Assert
        assertFalse(violations.isEmpty());
        assertEquals(1, violations.size());
        assertEquals("password", violations.iterator().next().getPropertyPath().toString());
    }

    @Test
    void validate_withNullFields_hasViolations() {
        // Arrange
        AuthenticationRequest request = new AuthenticationRequest(null, null);

        // Act
        Set<ConstraintViolation<AuthenticationRequest>> violations = validator.validate(request);

        // Assert
        assertFalse(violations.isEmpty());
        assertEquals(2, violations.size());
    }
}
// src/test/java/com/wizdevtech/identityaccess/dto/RegistrationRequestTest.java
package com.wizdevtech.identityaccess.dto;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.Set;

import static org.junit.jupiter.api.Assertions.*;

class RegistrationRequestTest {

    private Validator validator;

    @BeforeEach
    void setUp() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }

    @Test
    void validate_withValidData_noViolations() {
        // Arrange
        RegistrationRequest request = new RegistrationRequest();
        request.setEmail("[email protected]");
        request.setPassword("securePassword123");
        request.setRoles(Set.of("USER"));

        // Act
        Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request);

        // Assert
        assertTrue(violations.isEmpty());
    }

    @Test
    void validate_withEmptyEmail_hasViolation() {
        // Arrange
        RegistrationRequest request = new RegistrationRequest();
        request.setEmail("");
        request.setPassword("securePassword123");
        request.setRoles(Set.of("USER"));

        // Act
        Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request);

        // Assert
        assertFalse(violations.isEmpty());
        assertEquals(1, violations.size());
        assertEquals("email", violations.iterator().next().getPropertyPath().toString());
    }

    @Test
    void validate_withInvalidEmail_hasViolation() {
        // Arrange
        RegistrationRequest request = new RegistrationRequest();
        request.setEmail("not-an-email");
        request.setPassword("securePassword123");
        request.setRoles(Set.of("USER"));

        // Act
        Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request);

        // Assert
        assertFalse(violations.isEmpty());
        assertEquals(1, violations.size());
        assertEquals("email", violations.iterator().next().getPropertyPath().toString());
    }

    @Test
    void validate_withShortPassword_hasViolation() {
        // Arrange
        RegistrationRequest request = new RegistrationRequest();
        request.setEmail("[email protected]");
        request.setPassword("short");
        request.setRoles(Set.of("USER"));

        // Act
        Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request);

        // Assert
        assertFalse(violations.isEmpty());
        assertEquals(1, violations.size());
        assertEquals("password", violations.iterator().next().getPropertyPath().toString());
    }

    @Test
    void validate_withNullFields_hasViolations() {
        // Arrange
        RegistrationRequest request = new RegistrationRequest();

        // Act
        Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request);

        // Assert
        assertFalse(violations.isEmpty());
        assertEquals(2, violations.size()); // email and password are required
    }

    @Test
    void validate_withEmptyRoles_isValid() {
        // Arrange
        RegistrationRequest request = new RegistrationRequest();
        request.setEmail("[email protected]");
        request.setPassword("securePassword123");
        request.setRoles(Set.of()); // Empty set is valid

        // Act
        Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request);

        // Assert
        assertTrue(violations.isEmpty());
    }
}

8. Integration Tests

// src/test/java/com/wizdevtech/identityaccess/integration/AuthenticationFlowIntegrationTest.java
package com.wizdevtech.identityaccess.integration;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wizdevtech.identityaccess.dto.AuthenticationRequest;
import com.wizdevtech.identityaccess.dto.RegistrationRequest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.util.Set;

import static org.hamcrest.Matchers.notNullValue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
@Testcontainers
class AuthenticationFlowIntegrationTest {

    @Container
    static PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<>("postgres:14")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");

    @DynamicPropertySource
    static void registerPgProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", 
            () -> String.format("jdbc:postgresql://localhost:%d/testdb", 
                postgreSQLContainer.getFirstMappedPort()));
        registry.add("spring.datasource.username", () -> "test");
        registry.add("spring.datasource.password", () -> "test");
    }

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void fullAuthenticationFlow_registerAndLogin() throws Exception {
        // 1. Register a new user
        RegistrationRequest registrationRequest = new RegistrationRequest();
        registrationRequest.setEmail("[email protected]");
        registrationRequest.setPassword("SecurePassword123");
        registrationRequest.setRoles(Set.of("USER"));

        MvcResult registrationResult = mockMvc.perform(post("/api/auth/register")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(registrationRequest)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.token", notNullValue()))
                .andExpect(jsonPath("$.email").value("[email protected]"))
                .andExpect(jsonPath("$.roles[0]").value("USER"))
                .andReturn();

        // Extract token from registration response
        String responseJson = registrationResult.getResponse().getContentAsString();
        String token = objectMapper.readTree(responseJson).get("token").asText();

        // 2. Validate the token
        mockMvc.perform(post("/api/auth/validate")
                .param("token", token))
                .andExpect(status().isOk())
                .andExpect(content().string("true"));

        // 3. Log in with the created user
        AuthenticationRequest loginRequest = new AuthenticationRequest(
                "[email protected]", "SecurePassword123");

        mockMvc.perform(post("/api/auth/login")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(loginRequest)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.token", notNullValue()))
                .andExpect(jsonPath("$.email").value("[email protected]"));

        // 4. Try invalid login
        AuthenticationRequest invalidLoginRequest = new AuthenticationRequest(
                "[email protected]", "WrongPassword");

        mockMvc.perform(post("/api/auth/login")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(invalidLoginRequest)))
                .andExpect(status().isUnauthorized());
    }

    @Test
    void validateToken_withInvalidToken_returnsFalse() throws Exception {
        mockMvc.perform(post("/api/auth/validate")
                .param("token", "invalid.token.format"))
                .andExpect(status().isOk())
                .andExpect(content().string("false"));
    }
}

9. Custom Test Configuration

// src/test/resources/application-test.yml
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/testdb
    username: test
    password: test
    driver-class-name: org.postgresql.Driver
  jpa:
    hibernate:
      ddl-auto: create-drop
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
        format_sql: true
    show-sql: true

server:
  port: 8081
  
grpc:
  port: 9091
  
jwt:
  secret: test_secret_key_for_testing_purposes_only_32
  expiration: 3600000
  issuer: test-identity-access
  
encryption:
  key: test_encryption_key_for_tests_only_32

logging:
  level:
    org.springframework.security: DEBUG
    com.wizdevtech: DEBUG

These comprehensive test files cover all the major components of the Java-based Identity Access module:

  1. Unit Tests: Testing individual components in isolation

    • Controller tests verify proper HTTP endpoint behavior
    • Service tests ensure business logic works correctly
    • Repository tests confirm data persistence operations
    • DTO validation tests verify input validation
  2. Integration Tests: Testing complete application flows

    • End-to-end authentication flow (register, login, validate)
    • Database interactions with TestContainers for PostgreSQL
  3. Special Configuration:

    • Test-specific application properties
    • PostgreSQL test containers for real database testing

The tests follow established best practices:

  • Proper use of mocks where appropriate
  • Clear arrange-act-assert patterns
  • Comprehensive assertions
  • Proper test isolation
  • Coverage of both success and failure scenarios

These tests will ensure that your Identity Access module is robust and reliable before deploying to production. They can also be integrated into your CI/CD pipeline to maintain quality as the codebase evolves.

⚠️ **GitHub.com Fallback** ⚠️