test: implement extra unit test to read 85% goal
This commit is contained in:
-1
@@ -57,7 +57,6 @@ public class UserEndpointV1 extends AbstractEndpoint {
|
||||
}
|
||||
// this should be non-null
|
||||
final var id = argId.textValue();
|
||||
assert id != null;
|
||||
try {
|
||||
return this.keycloakAdminClient.getUserDetails(id).block();
|
||||
} catch (final NoSuchElementException ex) {
|
||||
|
||||
+7
-5
@@ -262,8 +262,7 @@ public class KeycloakIdentityManagerService implements IdentityManagerService {
|
||||
return domain;
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
newOrg.getDomains().clear();
|
||||
newOrg.getDomains().addAll(domainRepresentations);
|
||||
domainRepresentations.forEach(newOrg::addDomain);
|
||||
}
|
||||
return this.keycloakAdminClient.createOrganization(newOrg)
|
||||
.map(this::toOrganizationResponse);
|
||||
@@ -298,8 +297,10 @@ public class KeycloakIdentityManagerService implements IdentityManagerService {
|
||||
return domain;
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
if (existingOrg.getDomains() != null) {
|
||||
existingOrg.getDomains().clear();
|
||||
existingOrg.getDomains().addAll(domainRepresentations);
|
||||
}
|
||||
domainRepresentations.forEach(existingOrg::addDomain);
|
||||
}
|
||||
return this.keycloakAdminClient.updateOrganization(orgId, existingOrg);
|
||||
});
|
||||
@@ -337,8 +338,9 @@ public class KeycloakIdentityManagerService implements IdentityManagerService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GroupResponse> createSubGroup(final String parentGroupId,
|
||||
final CreateGroupRequest request) {
|
||||
public Mono<GroupResponse> createSubGroup(
|
||||
final String parentGroupId, final CreateGroupRequest request
|
||||
) {
|
||||
GroupRepresentation newGroup = new GroupRepresentation();
|
||||
newGroup.setName(request.getName());
|
||||
newGroup.setAttributes(request.getAttributes());
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package com.cleverthis.authservice.controller.rabbitmq.v1.user;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.cleverthis.authservice.controller.rabbitmq.exception.EndpointBadRequestException;
|
||||
import com.cleverthis.authservice.controller.rabbitmq.exception.EndpointException;
|
||||
import com.cleverthis.authservice.controller.rabbitmq.model.EndpointRequest;
|
||||
import com.cleverthis.authservice.controller.rabbitmq.model.EndpointResponses;
|
||||
import com.cleverthis.authservice.service.impl.KeycloakAdminReactiveClient;
|
||||
import com.cleverthis.clevermicro.client.amqp.CleverMicroAmqpClient;
|
||||
import com.cleverthis.clevermicro.client.amqp.handler.HandlerContext;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
class UserEndpointV1Test {
|
||||
private final CleverMicroAmqpClient client = mock();
|
||||
private final ObjectMapper objectMapper = spy(new ObjectMapper());
|
||||
private final KeycloakAdminReactiveClient keycloakAdminClient = mock();
|
||||
|
||||
private UserEndpointV1 userEndpointV1;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws IOException {
|
||||
when(this.client.getHandlerManager()).thenReturn(mock());
|
||||
this.userEndpointV1 = spy(new UserEndpointV1(
|
||||
this.client, this.objectMapper, this.keycloakAdminClient));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHandler_MethodNotFound() {
|
||||
final var req = mock(EndpointRequest.class);
|
||||
when(req.getMethod()).thenReturn("method that doesn't exist");
|
||||
|
||||
final var ex = assertThrows(EndpointBadRequestException.class,
|
||||
() -> this.userEndpointV1.handleRequest(mock(), req));
|
||||
|
||||
assertEquals("Method not found", ex.getMessage());
|
||||
}
|
||||
|
||||
private EndpointRequest createRequest(String method, Map<String, ?> args) {
|
||||
final String json;
|
||||
try {
|
||||
json = this.objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(args);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
final LinkedHashMap<String, JsonNode> convertedArgs;
|
||||
try {
|
||||
convertedArgs = this.objectMapper.readValue(json, new TypeReference<>() {
|
||||
});
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return EndpointRequest.builder().method(method).args(convertedArgs).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHandler_QueryUserById() throws Exception {
|
||||
final var handlerContext = mock(HandlerContext.class);
|
||||
|
||||
// normal path
|
||||
var req = createRequest("queryUserById", Map.of("id", "test-id"));
|
||||
final var returnedUser = new UserRepresentation();
|
||||
returnedUser.setId("test-id");
|
||||
returnedUser.setUsername("test-username");
|
||||
when(this.keycloakAdminClient.getUserDetails("test-id"))
|
||||
.thenReturn(Mono.just(returnedUser));
|
||||
|
||||
this.userEndpointV1.handleRequest(handlerContext, req);
|
||||
|
||||
verify(handlerContext).finish(
|
||||
this.objectMapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(
|
||||
EndpointResponses.ok(returnedUser)
|
||||
)
|
||||
);
|
||||
|
||||
// missing args
|
||||
req = createRequest("queryUserById", Map.of("not-id", "test-id"));
|
||||
{ // scope for final copy
|
||||
final var finalReq = req;
|
||||
assertThrows(EndpointBadRequestException.class,
|
||||
() -> this.userEndpointV1.handleRequest(handlerContext, finalReq));
|
||||
}
|
||||
|
||||
// invalid args
|
||||
req = createRequest("queryUserById", Map.of("id", 123));
|
||||
{ // scope for final copy
|
||||
final var finalReq = req;
|
||||
assertThrows(EndpointBadRequestException.class,
|
||||
() -> this.userEndpointV1.handleRequest(handlerContext, finalReq));
|
||||
}
|
||||
|
||||
// user not found
|
||||
when(this.keycloakAdminClient.getUserDetails("test-id"))
|
||||
.thenReturn(Mono.error(new NoSuchElementException("test exception")));
|
||||
req = createRequest("queryUserById", Map.of("id", "test-id"));
|
||||
{ // scope for final copy
|
||||
final var finalReq = req;
|
||||
final var ex = assertThrows(EndpointException.class,
|
||||
() -> this.userEndpointV1.handleRequest(handlerContext, finalReq));
|
||||
assertEquals("cleverthis.clevermicro.auth.user_not_found", ex.getEndpointException());
|
||||
}
|
||||
}
|
||||
}
|
||||
+312
@@ -1,23 +1,36 @@
|
||||
package com.cleverthis.authservice.service.impl;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
|
||||
import com.cleverthis.authservice.config.KeycloakClientConfigProperties;
|
||||
import com.cleverthis.authservice.dto.TokenResponse;
|
||||
import com.cleverthis.authservice.dto.VerificationResponse;
|
||||
import com.cleverthis.authservice.dto.group.CreateGroupRequest;
|
||||
import com.cleverthis.authservice.dto.keycloakadmin.KeycloakInvitationRequest;
|
||||
import com.cleverthis.authservice.dto.organization.CreateOrganizationRequest;
|
||||
import com.cleverthis.authservice.dto.organization.InviteUserRequest;
|
||||
import com.cleverthis.authservice.dto.organization.UpdateOrganizationRequest;
|
||||
import com.cleverthis.authservice.exception.AuthenticationException;
|
||||
import com.cleverthis.authservice.exception.TokenVerificationException;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.representations.idm.MemberRepresentation;
|
||||
import org.keycloak.representations.idm.OrganizationDomainRepresentation;
|
||||
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
class KeycloakIdentityManagerServiceTest {
|
||||
@@ -246,4 +259,303 @@ class KeycloakIdentityManagerServiceTest {
|
||||
.expectNext(false)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createOrganization_success() {
|
||||
// Arrange
|
||||
final var request = new CreateOrganizationRequest(
|
||||
"Test Organization", List.of("test.org"), "", Map.of()
|
||||
);
|
||||
final var mockResponse = new OrganizationRepresentation();
|
||||
mockResponse.setId("123");
|
||||
mockResponse.setName("Test Organization");
|
||||
mockResponse.addDomain(new OrganizationDomainRepresentation("test.org"));
|
||||
mockResponse.setDescription("");
|
||||
mockResponse.setEnabled(true);
|
||||
|
||||
Mockito.when(this.adminClient.createOrganization(any()))
|
||||
.thenReturn(Mono.just(mockResponse));
|
||||
|
||||
// Act
|
||||
final var result = this.service.createOrganization(request);
|
||||
|
||||
// Assert
|
||||
StepVerifier.create(result)
|
||||
.expectNextMatches(response -> "123".equals(response.getId()) &&
|
||||
"Test Organization".equals(response.getName()))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOrganizations_success() {
|
||||
// Arrange
|
||||
final var mockOrganizations = List.of(
|
||||
new OrganizationRepresentation() {{
|
||||
setId("org1");
|
||||
setName("Organization 1");
|
||||
addDomain(new OrganizationDomainRepresentation("domain1.org"));
|
||||
}},
|
||||
new OrganizationRepresentation() {{
|
||||
setId("org2");
|
||||
setName("Organization 2");
|
||||
addDomain(new OrganizationDomainRepresentation("domain2.org"));
|
||||
}}
|
||||
);
|
||||
|
||||
Mockito.when(this.adminClient.getOrganizations())
|
||||
.thenReturn(Mono.just(mockOrganizations));
|
||||
|
||||
// Act
|
||||
final var result = this.service.getOrganizations();
|
||||
|
||||
// Assert
|
||||
StepVerifier.create(result)
|
||||
.expectNextMatches(orgList -> orgList.size() == 2
|
||||
&& "Organization 1".equals(orgList.getFirst().getName())
|
||||
&& "domain1.org".equals(
|
||||
orgList.getFirst().getDomains().iterator().next().getName())
|
||||
&& "Organization 2".equals(orgList.get(1).getName()))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOrganizationById_success() {
|
||||
// Arrange
|
||||
final var orgId = "abc123";
|
||||
final var orgRepresentation = new OrganizationRepresentation();
|
||||
orgRepresentation.setId(orgId);
|
||||
orgRepresentation.setName("Test Organization");
|
||||
orgRepresentation.addDomain(new OrganizationDomainRepresentation("test.org"));
|
||||
orgRepresentation.setDescription("Test Description");
|
||||
orgRepresentation.setEnabled(true);
|
||||
|
||||
Mockito.when(this.adminClient.getOrganizationById(orgId))
|
||||
.thenReturn(Mono.just(orgRepresentation));
|
||||
|
||||
// Act
|
||||
final var result = this.service.getOrganizationById(orgId);
|
||||
|
||||
// Assert
|
||||
StepVerifier.create(result)
|
||||
.expectNextMatches(response -> "abc123".equals(response.getId())
|
||||
&& "Test Organization".equals(response.getName())
|
||||
&& "Test Description".equals(response.getDescription())
|
||||
&& Boolean.TRUE.equals(response.getEnabled())
|
||||
&& response.getDomains().iterator().next().getName().equals("test.org"))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateOrganization_success() {
|
||||
// Arrange
|
||||
final var orgId = "org123";
|
||||
final var updateRequest = new UpdateOrganizationRequest(
|
||||
List.of("updated.org"), "Updated Description", Map.of("key", List.of("value"))
|
||||
);
|
||||
final var existingOrg = new OrganizationRepresentation();
|
||||
existingOrg.setId(orgId);
|
||||
existingOrg.setDescription("Old Description");
|
||||
existingOrg.addDomain(new OrganizationDomainRepresentation("old.org"));
|
||||
|
||||
Mockito.when(this.adminClient.getOrganizationById(orgId))
|
||||
.thenReturn(Mono.just(existingOrg));
|
||||
Mockito.when(this.adminClient.updateOrganization(orgId, existingOrg))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
// Act
|
||||
final var result = this.service.updateOrganization(orgId, updateRequest);
|
||||
|
||||
// Assert
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
|
||||
Mockito.verify(this.adminClient).updateOrganization(orgId, existingOrg);
|
||||
assertEquals("Updated Description", existingOrg.getDescription());
|
||||
assertEquals("value", existingOrg.getAttributes().get("key").getFirst());
|
||||
assertEquals(1, existingOrg.getDomains().size());
|
||||
assertEquals("updated.org", existingOrg.getDomains().iterator().next().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteOrganization_success() {
|
||||
// Arrange
|
||||
final var orgId = "test-org-id";
|
||||
|
||||
Mockito.when(this.adminClient.deleteOrganization(orgId))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
// Act
|
||||
final var result = this.service.deleteOrganization(orgId);
|
||||
|
||||
// Assert
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
|
||||
Mockito.verify(this.adminClient).deleteOrganization(orgId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void inviteUserToOrganization_success() {
|
||||
// Arrange
|
||||
final var orgId = "test-org-id";
|
||||
final var request = new InviteUserRequest("test@example.com", "John", "Wick");
|
||||
final var invitationRequest = new KeycloakInvitationRequest();
|
||||
invitationRequest.setEmail(request.getEmail());
|
||||
invitationRequest.setFirstName(request.getFirstName());
|
||||
invitationRequest.setLastName(request.getLastName());
|
||||
|
||||
Mockito.when(this.adminClient.inviteUserToOrganization(any(), any()))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
// Act
|
||||
final var result = this.service.inviteUserToOrganization(orgId, request);
|
||||
|
||||
// Assert
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
|
||||
Mockito.verify(this.adminClient).inviteUserToOrganization(orgId, invitationRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOrganizationMembers_success() {
|
||||
// Arrange
|
||||
final var orgId = "test-org-id";
|
||||
final var mockMembers = List.of(
|
||||
// I asked Gemini, and this is an antipattern that no one mentions
|
||||
// when I learned Java. Basically, it will create a new anonymous
|
||||
// class for every instance. In this case, we have two.
|
||||
// Gemini suggests we use a method to create a user and then put it in the list,
|
||||
// but a dedicated method is not as compact as double brace initialization.
|
||||
// This test is generated by gemini 2.5 pro. I think it's ok to use here
|
||||
// because it's a unit test, and I blame keycloak to not offer a builder.
|
||||
// DO NOT use this pattern in the main source set.
|
||||
new MemberRepresentation() {{
|
||||
setId("user1");
|
||||
setUsername("user1Username");
|
||||
setFirstName("User1");
|
||||
setLastName("One");
|
||||
setEmail("user1@example.com");
|
||||
setEnabled(true);
|
||||
}},
|
||||
new MemberRepresentation() {{
|
||||
setId("user2");
|
||||
setUsername("user2Username");
|
||||
setFirstName("User2");
|
||||
setLastName("Two");
|
||||
setEmail("user2@example.com");
|
||||
setEnabled(true);
|
||||
}}
|
||||
);
|
||||
|
||||
Mockito.when(this.adminClient.getOrganizationMembers(orgId))
|
||||
.thenReturn(Mono.just(mockMembers));
|
||||
|
||||
// Act
|
||||
final var result = this.service.getOrganizationMembers(orgId);
|
||||
|
||||
// Assert
|
||||
StepVerifier.create(result)
|
||||
.expectNextMatches(members -> members.size() == 2
|
||||
&& "user1".equals(members.getFirst().getId())
|
||||
&& "user1@example.com".equals(members.getFirst().getEmail())
|
||||
&& "user2".equals(members.get(1).getId())
|
||||
&& "user2@example.com".equals(members.get(1).getEmail()))
|
||||
.verifyComplete();
|
||||
|
||||
Mockito.verify(this.adminClient).getOrganizationMembers(orgId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addMemberToOrganization_success() {
|
||||
// Arrange
|
||||
final var orgId = "test-org-id";
|
||||
final var userId = "test-user-id";
|
||||
|
||||
Mockito.when(this.adminClient.addMemberToOrganization(orgId, userId))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
// Act
|
||||
final var result = this.service.addMemberToOrganization(orgId, userId);
|
||||
|
||||
// Assert
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
|
||||
Mockito.verify(this.adminClient).addMemberToOrganization(orgId, userId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeMemberFromOrganization_success() {
|
||||
// Arrange
|
||||
final var orgId = "test-org-id";
|
||||
final var userId = "test-user-id";
|
||||
|
||||
Mockito.when(this.adminClient.removeMemberFromOrganization(orgId, userId))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
// Act
|
||||
final var result = this.service.removeMemberFromOrganization(orgId, userId);
|
||||
|
||||
// Assert
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
|
||||
Mockito.verify(this.adminClient).removeMemberFromOrganization(orgId, userId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSubGroup_success() {
|
||||
// Arrange
|
||||
final var parentGroupId = "parent-group-id";
|
||||
final var request = new CreateGroupRequest(
|
||||
"Test SubGroup", "", Map.of("key", List.of("value")));
|
||||
final var mockGroupRepresentation = new GroupRepresentation();
|
||||
mockGroupRepresentation.setId("sub-group-id");
|
||||
mockGroupRepresentation.setName(request.getName());
|
||||
mockGroupRepresentation.setAttributes(request.getAttributes());
|
||||
|
||||
Mockito.when(this.adminClient.createSubGroup(parentGroupId, mockGroupRepresentation))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
// Act
|
||||
final var result = this.service.createSubGroup(parentGroupId, request);
|
||||
|
||||
// Assert
|
||||
StepVerifier.create(result)
|
||||
.expectNextMatches(response -> "Test SubGroup".equals(response.getName()) &&
|
||||
response.getAttributes().get("key").contains("value"))
|
||||
.verifyComplete();
|
||||
|
||||
Mockito.verify(this.adminClient).createSubGroup(Mockito.eq(parentGroupId), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getGroupById_success() {
|
||||
// Arrange
|
||||
final var groupId = "test-group-id";
|
||||
final var mockGroup = new GroupRepresentation();
|
||||
mockGroup.setId(groupId);
|
||||
mockGroup.setName("Test Group");
|
||||
mockGroup.setPath("/test-group");
|
||||
mockGroup.setDescription("Test Group Description");
|
||||
mockGroup.setAttributes(Map.of("key", List.of("value1", "value2")));
|
||||
|
||||
Mockito.when(this.adminClient.getGroupById(groupId))
|
||||
.thenReturn(Mono.just(mockGroup));
|
||||
|
||||
// Act
|
||||
final var result = this.service.getGroupById(groupId);
|
||||
|
||||
// Assert
|
||||
StepVerifier.create(result)
|
||||
.expectNextMatches(response -> response.getId().equals(groupId) &&
|
||||
response.getName().equals("Test Group") &&
|
||||
response.getPath().equals("/test-group") &&
|
||||
response.getDescription().equals("Test Group Description") &&
|
||||
response.getAttributes().get("key").contains("value1"))
|
||||
.verifyComplete();
|
||||
|
||||
Mockito.verify(this.adminClient).getGroupById(groupId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user