eaa14e7101
Pull config from consul to resolve permission mapping dynamically if consul is not available or the value is corrupted, use settings from local value in application.yaml ISSUES CLOSED: clevermicro/user-management#23
465 lines
22 KiB
Java
465 lines
22 KiB
Java
package com.cleverthis.authservice;
|
|
|
|
import static org.hamcrest.MatcherAssert.assertThat;
|
|
import static org.hamcrest.Matchers.equalTo;
|
|
import static org.hamcrest.Matchers.hasItems;
|
|
import static org.mockito.ArgumentMatchers.eq;
|
|
import static org.mockito.Mockito.when;
|
|
|
|
import com.cleverthis.authservice.config.PermissionMappingConfig;
|
|
import com.cleverthis.authservice.controller.AuthController;
|
|
import com.cleverthis.authservice.dto.LoginRequest;
|
|
import com.cleverthis.authservice.dto.LogoutRequest;
|
|
import com.cleverthis.authservice.dto.TokenResponse;
|
|
import com.cleverthis.authservice.dto.VerificationResponse;
|
|
import com.cleverthis.authservice.exception.AuthenticationException;
|
|
import com.cleverthis.authservice.exception.GlobalExceptionHandler;
|
|
import com.cleverthis.authservice.exception.LogoutException;
|
|
import com.cleverthis.authservice.exception.TokenVerificationException;
|
|
import com.cleverthis.authservice.service.IdentityManagerService;
|
|
import com.cleverthis.authservice.service.impl.FallbackPermissionMapLookupService;
|
|
import com.ecwid.consul.v1.ConsulClient;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.mockito.ArgumentCaptor;
|
|
import org.mockito.Captor;
|
|
import org.mockito.Mockito;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
|
|
import org.springframework.context.annotation.Import;
|
|
import org.springframework.http.HttpHeaders;
|
|
import org.springframework.http.MediaType;
|
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
|
import reactor.core.publisher.Mono;
|
|
|
|
/**
|
|
* Unit tests for the AuthController using WebFluxTest and Mockito. Focuses on testing the
|
|
* controller logic and request/response handling.
|
|
*/
|
|
@WebFluxTest(AuthController.class)
|
|
@Import({GlobalExceptionHandler.class,
|
|
PermissionMappingConfig.class,
|
|
FallbackPermissionMapLookupService.class})
|
|
class AuthControllerTest {
|
|
|
|
@Autowired
|
|
private WebTestClient webTestClient;
|
|
|
|
@MockitoBean
|
|
private IdentityManagerService identityManagerService;
|
|
|
|
@MockitoBean
|
|
private PermissionMappingConfig permissionMappingConfig;
|
|
|
|
@Captor
|
|
private ArgumentCaptor<List<String>> candidateRolesCaptor;
|
|
|
|
// required by the fallback permission mapping lookup service
|
|
// provide a mocked one
|
|
@MockitoBean
|
|
private ConsulClient consulClient;
|
|
|
|
private TokenResponse mockTokenResponse;
|
|
private VerificationResponse mockUserVerificationResponse;
|
|
|
|
// --- Constants for Header Names (mirroring controller) ---
|
|
private static final String HEADER_USER_ID = "X-User-Id";
|
|
private static final String HEADER_USER_NAME = "X-User-Name";
|
|
private static final String HEADER_USER_EMAIL = "X-User-Email";
|
|
private static final String HEADER_USER_GROUPS = "X-User-Groups";
|
|
|
|
// --- Traefik Forwarded Headers ---
|
|
private static final String HEADER_X_FORWARDED_METHOD = "X-Forwarded-Method";
|
|
private static final String HEADER_X_FORWARDED_URI = "X-Forwarded-Uri";
|
|
|
|
@BeforeEach
|
|
void setUp() {
|
|
this.mockTokenResponse = new TokenResponse("access-123", 300, 1800, "refresh-456", "Bearer",
|
|
"id-789", 0, "state", "openid");
|
|
this.mockUserVerificationResponse =
|
|
new VerificationResponse(true, "test-client", "testuser", "openid", "user-sub-123",
|
|
System.currentTimeMillis() / 1000 + 300, System.currentTimeMillis() / 1000,
|
|
System.currentTimeMillis() / 1000, List.of("aud1"), "iss", "Bearer",
|
|
"test@example.com", true, "testuser", List.of("groupA"));
|
|
|
|
// Setup mock PermissionMappingConfig - Default to having some rules
|
|
PermissionMappingConfig.Rule rule1 = new PermissionMappingConfig.Rule();
|
|
rule1.setPathPrefix("/serviceA/");
|
|
rule1.setKeycloakClientId("serviceA-client");
|
|
|
|
PermissionMappingConfig.Rule rule2 = new PermissionMappingConfig.Rule();
|
|
rule2.setPathPrefix("/serviceB/items/");
|
|
rule2.setKeycloakClientId("serviceB-client");
|
|
|
|
List<PermissionMappingConfig.Rule> rules = new ArrayList<>();
|
|
rules.add(rule1);
|
|
rules.add(rule2);
|
|
// Mock getRules() to return the list
|
|
when(this.permissionMappingConfig.getRules()).thenReturn(rules);
|
|
// Mock getDefaultKeycloakClientId() to return null by default, can be overridden in
|
|
// specific tests
|
|
when(this.permissionMappingConfig.getDefaultKeycloakClientId()).thenReturn(null);
|
|
}
|
|
|
|
// --- /login Tests ---
|
|
|
|
@Test
|
|
void login_Success_ReturnsTokenResponse() {
|
|
LoginRequest loginRequest = new LoginRequest("testuser", "password");
|
|
when(this.identityManagerService.login("testuser", "password"))
|
|
.thenReturn(Mono.just(this.mockTokenResponse));
|
|
|
|
this.webTestClient.post().uri("/login").contentType(MediaType.APPLICATION_JSON)
|
|
.bodyValue(loginRequest).exchange().expectStatus().isOk() // Expect 200
|
|
.expectBody(TokenResponse.class)
|
|
.value(TokenResponse::getAccessToken, equalTo("access-123"))
|
|
.value(TokenResponse::getRefreshToken, equalTo("refresh-456"));
|
|
}
|
|
|
|
@Test
|
|
void login_ServiceThrowsAuthException_ReturnsUnauthorized() {
|
|
LoginRequest loginRequest = new LoginRequest("wronguser", "wrongpass");
|
|
// Mock the service to throw the specific exception handled by GlobalExceptionHandler
|
|
when(this.identityManagerService.login("wronguser", "wrongpass"))
|
|
.thenReturn(Mono.error(new AuthenticationException("Invalid credentials")));
|
|
|
|
this.webTestClient.post().uri("/login").contentType(MediaType.APPLICATION_JSON)
|
|
.bodyValue(loginRequest).exchange().expectStatus().isUnauthorized();
|
|
}
|
|
|
|
@Test
|
|
void login_InvalidRequest_ReturnsBadRequest() {
|
|
LoginRequest loginRequest = new LoginRequest("", ""); // Invalid request
|
|
|
|
// No need to mock service for validation errors
|
|
// The framework and GlobalExceptionHandler (via parent) should handle this
|
|
|
|
this.webTestClient.post().uri("/login").contentType(MediaType.APPLICATION_JSON)
|
|
.bodyValue(loginRequest).exchange().expectStatus().isBadRequest(); // Expect 400
|
|
}
|
|
|
|
// --- /verify Tests ---
|
|
|
|
@Test
|
|
void verify_Success_ReturnsVerificationResponse() {
|
|
String validToken = "valid-token-123";
|
|
when(this.identityManagerService.verifyToken(validToken))
|
|
.thenReturn(Mono.just(this.mockUserVerificationResponse));
|
|
|
|
this.webTestClient.post().uri("/verify")
|
|
.header(HttpHeaders.AUTHORIZATION, "Bearer " + validToken).exchange().expectStatus()
|
|
.isOk() // Expect 200
|
|
.expectBody(VerificationResponse.class)
|
|
.value(VerificationResponse::isActive, equalTo(true))
|
|
.value(VerificationResponse::getUsername, equalTo("testuser"));
|
|
}
|
|
|
|
@Test
|
|
void verify_ServiceThrowsVerificationException_ReturnsUnauthorized() {
|
|
String invalidToken = "invalid-token-456";
|
|
// Mock the service to throw the specific exception
|
|
when(this.identityManagerService.verifyToken(invalidToken))
|
|
.thenReturn(Mono.error(new TokenVerificationException("Token expired")));
|
|
|
|
this.webTestClient.post().uri("/verify")
|
|
.header(HttpHeaders.AUTHORIZATION, "Bearer " + invalidToken).exchange()
|
|
.expectStatus().isUnauthorized(); // Expect 401 (handled by GlobalExceptionHandler)
|
|
}
|
|
|
|
@Test
|
|
void verify_MissingAuthHeader_ReturnsBadRequest() { // Renamed test slightly for clarity
|
|
// No service mocking needed, framework handles missing required header
|
|
this.webTestClient.post().uri("/verify").exchange().expectStatus().isBadRequest();
|
|
}
|
|
|
|
@Test
|
|
void verify_InvalidAuthHeaderFormat_ReturnsUnauthorized() {
|
|
// Controller logic handles this case specifically
|
|
this.webTestClient.post().uri("/verify")
|
|
.header(HttpHeaders.AUTHORIZATION, "Basic somecredentials") // Wrong scheme
|
|
.exchange().expectStatus().isUnauthorized(); // Expect 401 (handled by controller
|
|
}
|
|
|
|
// --- /auth (ForwardAuth) Tests ---
|
|
|
|
// --- NEW /auth (ForwardAuth) Tests with Candidate Role List Logic ---
|
|
|
|
@Test
|
|
void forwardAuth_PermissionGranted_ReturnsOkWithUserHeaders() {
|
|
String userToken = "user-token-granted";
|
|
String originalMethod = "GET";
|
|
String originalUri = "/serviceA/api/data"; // Matches rule1
|
|
|
|
when(this.identityManagerService.verifyToken(userToken))
|
|
.thenReturn(Mono.just(this.mockUserVerificationResponse));
|
|
when(this.identityManagerService.checkUserHasAnyClientRole(eq("user-sub-123"),
|
|
eq("serviceA-client"), this.candidateRolesCaptor.capture()))
|
|
.thenReturn(Mono.just(true));
|
|
|
|
this.webTestClient.post().uri("/auth")
|
|
.header(HttpHeaders.AUTHORIZATION, "Bearer " + userToken)
|
|
.header(HEADER_X_FORWARDED_METHOD, originalMethod)
|
|
.header(HEADER_X_FORWARDED_URI, originalUri).exchange().expectStatus().isOk()
|
|
.expectHeader().valueEquals(HEADER_USER_ID, "user-sub-123").expectHeader()
|
|
.valueEquals(HEADER_USER_NAME, "testuser").expectHeader()
|
|
.valueEquals(HEADER_USER_EMAIL, "test@example.com").expectHeader()
|
|
.valueEquals(HEADER_USER_GROUPS, "groupA").expectBody().isEmpty();
|
|
}
|
|
|
|
@Test
|
|
void forwardAuth_PermissionGranted_SpecificRoleMatch() {
|
|
String userToken = "user-token-granted";
|
|
String originalMethod = "GET";
|
|
// Will be normalized to /api/items/BY_ID
|
|
String originalUri = "/serviceA/api/items/123e4567-e89b-12d3-a456-426614174000";
|
|
|
|
when(this.identityManagerService.verifyToken(userToken))
|
|
.thenReturn(Mono.just(this.mockUserVerificationResponse));
|
|
// Expect checkUserHasAnyClientRole to be called with a list of candidates
|
|
// For GET /api/items/BY_ID, candidates would be [GET_API_ITEMS_BY_ID, ALL_API_ITEMS_BY_ID,
|
|
// GET_API_ITEMS_STAR, ..., ALL_STAR]
|
|
when(this.identityManagerService.checkUserHasAnyClientRole(eq("user-sub-123"),
|
|
eq("serviceA-client"), this.candidateRolesCaptor.capture()))
|
|
.thenReturn(Mono.just(true));
|
|
|
|
this.webTestClient.post().uri("/auth")
|
|
.header(HttpHeaders.AUTHORIZATION, "Bearer " + userToken)
|
|
.header(HEADER_X_FORWARDED_METHOD, originalMethod)
|
|
.header(HEADER_X_FORWARDED_URI, originalUri).exchange().expectStatus().isOk()
|
|
.expectHeader().valueEquals(HEADER_USER_ID, "user-sub-123").expectBody().isEmpty();
|
|
|
|
// Verify the captured candidate roles
|
|
List<String> capturedRoles = this.candidateRolesCaptor.getValue();
|
|
assertThat(capturedRoles,
|
|
hasItems("GET_API_ITEMS_BY_ID", "ALL_API_ITEMS_BY_ID", "GET_API_ITEMS_STAR",
|
|
"ALL_API_ITEMS_STAR", "GET_API_STAR", "ALL_API_STAR", "GET_STAR",
|
|
"ALL_STAR"));
|
|
// Check order if important, or just presence
|
|
}
|
|
|
|
@Test
|
|
void forwardAuth_Success_ReturnsOkWithHeaders_NoGroupsInToken() { // Renamed for clarity
|
|
String validToken = "valid-token-fw-nogroups";
|
|
VerificationResponse responseWithoutGroups = new VerificationResponse(true, "test-client",
|
|
"testuser", "openid", "user-sub-123", System.currentTimeMillis() / 1000 + 300,
|
|
System.currentTimeMillis() / 1000, System.currentTimeMillis() / 1000,
|
|
List.of("aud1"), "iss", "Bearer", "test@example.com", true, "testuser", null);
|
|
|
|
when(this.identityManagerService.verifyToken(validToken))
|
|
.thenReturn(Mono.just(responseWithoutGroups));
|
|
when(this.identityManagerService.checkUserHasAnyClientRole(eq("user-sub-123"),
|
|
eq("serviceA-client"), this.candidateRolesCaptor.capture()))
|
|
.thenReturn(Mono.just(true));
|
|
|
|
this.webTestClient.post().uri("/auth")
|
|
.header(HttpHeaders.AUTHORIZATION, "Bearer " + validToken)
|
|
.header(HEADER_X_FORWARDED_METHOD, "GET")
|
|
.header(HEADER_X_FORWARDED_URI, "/serviceA/api/nogroups").exchange().expectStatus()
|
|
.isOk().expectHeader().valueEquals(HEADER_USER_ID, "user-sub-123").expectHeader()
|
|
.valueEquals(HEADER_USER_NAME, "testuser").expectHeader()
|
|
.valueEquals(HEADER_USER_EMAIL, "test@example.com").expectHeader()
|
|
.doesNotExist(HEADER_USER_GROUPS).expectBody().isEmpty();
|
|
}
|
|
|
|
@Test
|
|
void forwardAuth_PermissionGranted_WildcardRoleMatch_All_Star() {
|
|
String userToken = "user-token-admin";
|
|
String originalMethod = "DELETE";
|
|
String originalUri = "/serviceA/admin/config"; // -> /admin/config
|
|
|
|
when(this.identityManagerService.verifyToken(userToken))
|
|
.thenReturn(Mono.just(this.mockUserVerificationResponse));
|
|
// User has ALL_STAR, so this should pass
|
|
when(this.identityManagerService.checkUserHasAnyClientRole(eq("user-sub-123"),
|
|
eq("serviceA-client"), anyStringList() // Match any list of strings
|
|
)).thenAnswer(invocation -> {
|
|
List<String> candidates = invocation.getArgument(2);
|
|
// Simulate Keycloak returning true if ALL_STAR is among candidates and user has it
|
|
// For this test, we assume the service method checkUserHasAnyClientRole correctly finds
|
|
// ALL_STAR
|
|
// if the user has it and it's in the candidate list.
|
|
// Here, we just return true because the test name implies it should be granted.
|
|
// A more robust mock would check if "ALL_STAR" is in `candidates` if the user had
|
|
// ALL_STAR.
|
|
return Mono.just(true);
|
|
});
|
|
|
|
this.webTestClient.post().uri("/auth")
|
|
.header(HttpHeaders.AUTHORIZATION, "Bearer " + userToken)
|
|
.header(HEADER_X_FORWARDED_METHOD, originalMethod)
|
|
.header(HEADER_X_FORWARDED_URI, originalUri).exchange().expectStatus().isOk()
|
|
.expectHeader().valueEquals(HEADER_USER_ID, "user-sub-123").expectBody().isEmpty();
|
|
}
|
|
|
|
@Test
|
|
void forwardAuth_PermissionDenied_NoMatchingRole() {
|
|
String userToken = "user-token-denied";
|
|
String originalMethod = "POST";
|
|
String originalUri = "/serviceA/restricted/action"; // -> /restricted/action
|
|
|
|
when(this.identityManagerService.verifyToken(userToken))
|
|
.thenReturn(Mono.just(this.mockUserVerificationResponse));
|
|
when(this.identityManagerService.checkUserHasAnyClientRole(eq("user-sub-123"),
|
|
eq("serviceA-client"), anyStringList() // Match any list of strings
|
|
)).thenReturn(Mono.just(false)); // Service reports no matching role found
|
|
|
|
this.webTestClient.post().uri("/auth")
|
|
.header(HttpHeaders.AUTHORIZATION, "Bearer " + userToken)
|
|
.header(HEADER_X_FORWARDED_METHOD, originalMethod)
|
|
.header(HEADER_X_FORWARDED_URI, originalUri).exchange().expectStatus().isForbidden()
|
|
.expectBody().isEmpty();
|
|
}
|
|
|
|
@Test
|
|
void forwardAuth_RootPath_PermissionGranted() {
|
|
String userToken = "user-token-root-access";
|
|
String originalMethod = "GET";
|
|
String originalUri = "/serviceA/"; // Root path for serviceA
|
|
|
|
when(this.identityManagerService.verifyToken(userToken))
|
|
.thenReturn(Mono.just(this.mockUserVerificationResponse));
|
|
when(this.identityManagerService.checkUserHasAnyClientRole(eq("user-sub-123"),
|
|
eq("serviceA-client"), this.candidateRolesCaptor.capture()))
|
|
.thenReturn(Mono.just(true));
|
|
|
|
this.webTestClient.post().uri("/auth")
|
|
.header(HttpHeaders.AUTHORIZATION, "Bearer " + userToken)
|
|
.header(HEADER_X_FORWARDED_METHOD, originalMethod)
|
|
.header(HEADER_X_FORWARDED_URI, originalUri).exchange().expectStatus().isOk();
|
|
|
|
List<String> capturedRoles = this.candidateRolesCaptor.getValue();
|
|
assertThat(capturedRoles, hasItems("GET_ROOT", "ALL_ROOT", "GET_STAR", "ALL_STAR"));
|
|
}
|
|
|
|
@Test
|
|
void forwardAuth_TokenInvalid_ReturnsUnauthorized() {
|
|
String invalidUserToken = "invalid-user-token";
|
|
String originalMethod = "GET";
|
|
String originalUri = "/serviceA/api/data";
|
|
|
|
when(this.identityManagerService.verifyToken(invalidUserToken))
|
|
.thenReturn(Mono.error(new TokenVerificationException("Token is expired")));
|
|
|
|
this.webTestClient.post().uri("/auth")
|
|
.header(HttpHeaders.AUTHORIZATION, "Bearer " + invalidUserToken)
|
|
.header(HEADER_X_FORWARDED_METHOD, originalMethod)
|
|
.header(HEADER_X_FORWARDED_URI, originalUri).exchange().expectStatus()
|
|
.isUnauthorized();
|
|
}
|
|
|
|
@Test
|
|
void forwardAuth_ServiceThrowsGenericExceptionDuringVerification_ReturnsUnauthorized() {
|
|
String token = "token-causing-error";
|
|
String originalMethod = "GET";
|
|
String originalUri = "/serviceA/api/data";
|
|
|
|
when(this.identityManagerService.verifyToken(token)).thenReturn(
|
|
Mono.error(new RuntimeException("Internal service error during verification")));
|
|
|
|
this.webTestClient.post().uri("/auth").header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
|
|
.header(HEADER_X_FORWARDED_METHOD, originalMethod)
|
|
.header(HEADER_X_FORWARDED_URI, originalUri).exchange().expectStatus()
|
|
.isUnauthorized();
|
|
}
|
|
|
|
@Test
|
|
void forwardAuth_NoMatchingPathRule_ReturnsForbidden() {
|
|
String userToken = "user-token-no-rule";
|
|
String originalMethod = "GET";
|
|
String originalUri = "/unknownService/api/data"; // No rule matches this prefix
|
|
|
|
// Mock getRules to return an empty list for this specific test case, or ensure default
|
|
// setup handles it
|
|
// For this test, let's ensure the default setup from setUp() (which has rules for
|
|
// /serviceA/)
|
|
// does not match "/unknownService/"
|
|
when(this.identityManagerService.verifyToken(userToken))
|
|
.thenReturn(Mono.just(this.mockUserVerificationResponse));
|
|
// No call to checkUserClientRolePermission should happen if no rule matches
|
|
|
|
this.webTestClient.post().uri("/auth")
|
|
.header(HttpHeaders.AUTHORIZATION, "Bearer " + userToken)
|
|
.header(HEADER_X_FORWARDED_METHOD, originalMethod)
|
|
.header(HEADER_X_FORWARDED_URI, originalUri).exchange().expectStatus()
|
|
.isForbidden(); // Controller logic for no rule match
|
|
}
|
|
|
|
@Test
|
|
void forwardAuth_MissingAuthHeader_ReturnsBadRequest() {
|
|
// This tests if @RequestHeader(HttpHeaders.AUTHORIZATION) is enforced
|
|
this.webTestClient.post().uri("/auth").header(HEADER_X_FORWARDED_METHOD, "GET")
|
|
.header(HEADER_X_FORWARDED_URI, "/serviceA/api/data")
|
|
// Missing Authorization header
|
|
.exchange().expectStatus().isBadRequest();
|
|
}
|
|
|
|
@Test
|
|
void forwardAuth_InvalidAuthHeaderFormat_ReturnsUnauthorized() {
|
|
// This tests the controller's specific check for "Bearer " prefix
|
|
this.webTestClient.post().uri("/auth")
|
|
.header(HttpHeaders.AUTHORIZATION, "Basic somecredentials")
|
|
.header(HEADER_X_FORWARDED_METHOD, "GET")
|
|
.header(HEADER_X_FORWARDED_URI, "/serviceA/api/data").exchange().expectStatus()
|
|
.isUnauthorized();
|
|
}
|
|
|
|
@Test
|
|
void forwardAuth_MissingXforwardedMethodHeader_ReturnsBadRequest() {
|
|
this.webTestClient.post().uri("/auth")
|
|
.header(HttpHeaders.AUTHORIZATION, "Bearer some-token")
|
|
.header(HEADER_X_FORWARDED_URI, "/serviceA/api/data")
|
|
// Missing X-Forwarded-Method
|
|
.exchange().expectStatus().isBadRequest();
|
|
}
|
|
|
|
@Test
|
|
void forwardAuth_MissingXforwardedUriHeader_ReturnsBadRequest() {
|
|
this.webTestClient.post().uri("/auth")
|
|
.header(HttpHeaders.AUTHORIZATION, "Bearer some-token")
|
|
.header(HEADER_X_FORWARDED_METHOD, "GET")
|
|
// Missing X-Forwarded-Uri
|
|
.exchange().expectStatus().isBadRequest();
|
|
}
|
|
|
|
// --- /logout Tests ---
|
|
|
|
@Test
|
|
void logout_Success_ReturnsNoContent() {
|
|
LogoutRequest logoutRequest = new LogoutRequest("refresh-token-to-revoke");
|
|
when(this.identityManagerService.logout(logoutRequest.getRefreshToken()))
|
|
.thenReturn(Mono.empty());
|
|
|
|
this.webTestClient.post().uri("/logout").contentType(MediaType.APPLICATION_JSON)
|
|
.bodyValue(logoutRequest).exchange().expectStatus().isNoContent(); // Expect 204
|
|
}
|
|
|
|
@Test
|
|
void logout_ServiceThrowsException_ReturnsInternalServerError() {
|
|
LogoutRequest logoutRequest = new LogoutRequest("refresh-token-error");
|
|
// Mock service to throw the specific exception
|
|
when(this.identityManagerService.logout(logoutRequest.getRefreshToken()))
|
|
.thenReturn(Mono.error(new LogoutException("Keycloak unavailable")));
|
|
|
|
this.webTestClient.post().uri("/logout").contentType(MediaType.APPLICATION_JSON)
|
|
.bodyValue(logoutRequest).exchange().expectStatus().is5xxServerError();
|
|
}
|
|
|
|
@Test
|
|
void logout_InvalidRequest_ReturnsBadRequest() {
|
|
LogoutRequest logoutRequest = new LogoutRequest(""); // Invalid request (blank token)
|
|
|
|
// No service mocking needed
|
|
|
|
this.webTestClient.post().uri("/logout").contentType(MediaType.APPLICATION_JSON)
|
|
.bodyValue(logoutRequest).exchange().expectStatus().isBadRequest(); // Expect 400
|
|
}
|
|
|
|
// Helper to match any List<String> in Mockito
|
|
private static List<String> anyStringList() {
|
|
return Mockito.anyList();
|
|
}
|
|
}
|