Skip to content

Commit

Permalink
Display user information (#43)
Browse files Browse the repository at this point in the history
* Display user information
* add get user information controller
* Address PR feedback
* improve getting profileName
---------

Signed-off-by: TOURI ANIS <[email protected]>
  • Loading branch information
anistouri authored Oct 11, 2024
1 parent 1da16df commit f512352
Show file tree
Hide file tree
Showing 11 changed files with 307 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Copyright (c) 2024, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

package org.gridsuite.useradmin.server.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.gridsuite.useradmin.server.UserAdminApi;
import org.gridsuite.useradmin.server.dto.UserInfos;
import org.gridsuite.useradmin.server.service.UserInfosService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

/**
* @author Anis TOURI <anis.touri at rte-france.com>
*/
@RestController
@RequestMapping(value = "/" + UserAdminApi.API_VERSION + "/users")
@Tag(name = "UserInfoController")
public class UserInfoController {

private final UserInfosService userInfosService;

public UserInfoController(UserInfosService userInfosService) {
this.userInfosService = userInfosService;
}

@GetMapping(value = "/{sub}/detail", produces = "application/json")
@Operation(summary = "get detailed user information")
@ApiResponse(responseCode = "200", description = "The user exist")
@ApiResponse(responseCode = "404", description = "The user doesn't exist")
public ResponseEntity<UserInfos> getUserDetail(@PathVariable("sub") String sub) {
return ResponseEntity.of(userInfosService.getUserInfo(sub));
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@
public record UserInfos(
String sub,
boolean isAdmin,
String profileName
String profileName,
Integer maxAllowedCases,
Integer numberCasesUsed,
Integer maxAllowedBuilds
) { }
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public UserInfosEntity(String sub) {
@Column(name = "id")
private UUID id;

// TODO rename to subject or userName
@Column(name = "sub", nullable = false, unique = true)
private String sub;

Expand All @@ -48,6 +49,15 @@ public static UserInfos toDto(@Nullable final UserInfosEntity entity, Predicate<
return null;
}
String profileName = entity.getProfile() == null ? null : entity.getProfile().getName();
return new UserInfos(entity.getSub(), isAdminFn.test(entity.getSub()), profileName);
return new UserInfos(entity.getSub(), isAdminFn.test(entity.getSub()), profileName, null, null, null);
}

public static UserInfos toDtoWithDetail(@Nullable final UserInfosEntity userInfosEntity, Predicate<String> isAdminFn, Integer maxAllowedCases, Integer numberCasesUsed, Integer maxAllowedBuilds) {
if (userInfosEntity == null) {
return null;
}
UserProfileEntity userProfileEntity = userInfosEntity.getProfile();
String profileName = userProfileEntity != null ? userProfileEntity.getName() : null;
return new UserInfos(userInfosEntity.getSub(), isAdminFn.test(userInfosEntity.getSub()), profileName, maxAllowedCases, numberCasesUsed, maxAllowedBuilds);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public class DirectoryService {
private static final String ELEMENTS_SERVER_ROOT_PATH = DELIMITER + DIRECTORY_SERVER_API_VERSION + DELIMITER
+ "elements";

private static final String USER_SERVER_ROOT_PATH = DELIMITER + DIRECTORY_SERVER_API_VERSION + DELIMITER
+ "users";

private final RestTemplate restTemplate = new RestTemplate();

private static String directoryServerBaseUri;
Expand All @@ -59,4 +62,9 @@ public Set<UUID> getExistingElements(Set<UUID> elementsUuids) {
}).getBody();
return existingElementList == null ? Set.of() : existingElementList.stream().map(ElementAttributes::getElementUuid).collect(Collectors.toSet());
}

public Integer getCasesCount(String userId) {
String path = UriComponentsBuilder.fromPath(USER_SERVER_ROOT_PATH + "/{userId}/cases/count").buildAndExpand(userId).toUriString();
return restTemplate.getForObject(directoryServerBaseUri + path, Integer.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.gridsuite.useradmin.server.service;

import org.gridsuite.useradmin.server.UserAdminApplicationProps;
import org.gridsuite.useradmin.server.dto.UserInfos;
import org.gridsuite.useradmin.server.entity.UserInfosEntity;
import org.gridsuite.useradmin.server.entity.UserProfileEntity;
import org.gridsuite.useradmin.server.repository.UserInfosRepository;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Objects;
import java.util.Optional;

@Service
public class UserInfosService {

private final UserInfosService self;
private final UserInfosRepository userInfosRepository;
private final DirectoryService directoryService;
private final UserAdminApplicationProps applicationProps;
private final AdminRightService adminRightService;

public UserInfosService(@Lazy final UserInfosService self,
final UserInfosRepository userInfosRepository,
final DirectoryService directoryService,
final UserAdminApplicationProps applicationProps,
final AdminRightService adminRightService) {
this.self = Objects.requireNonNull(self);
this.userInfosRepository = Objects.requireNonNull(userInfosRepository);
this.directoryService = Objects.requireNonNull(directoryService);
this.applicationProps = Objects.requireNonNull(applicationProps);
this.adminRightService = Objects.requireNonNull(adminRightService);
}

public UserInfos toDtoUserInfo(final UserInfosEntity userInfosEntity, Integer casesUsed) {
// get max allowed cases
Integer maxAllowedCases = Optional.ofNullable(userInfosEntity.getProfile())
.map(UserProfileEntity::getMaxAllowedCases)
.orElse(applicationProps.getDefaultMaxAllowedCases());
// get max allowed builds
Integer maxAllowedBuilds = Optional.ofNullable(userInfosEntity.getProfile())
.map(UserProfileEntity::getMaxAllowedBuilds)
.orElse(applicationProps.getDefaultMaxAllowedBuilds());
return UserInfosEntity.toDtoWithDetail(userInfosEntity, adminRightService::isAdmin, maxAllowedCases, casesUsed, maxAllowedBuilds);
}

public Optional<UserInfos> getUserInfo(String sub) {
Optional<UserInfosEntity> userInfosEntity = self.getUserInfosEntity(sub);
if (userInfosEntity.isPresent()) {
// get number of cases used
Integer casesUsed = directoryService.getCasesCount(userInfosEntity.get().getSub());

return Optional.of(toDtoUserInfo(userInfosEntity.get(), casesUsed));
}
return Optional.empty();
}

@Transactional(readOnly = true)
public Optional<UserInfosEntity> getUserInfosEntity(String sub) {
return userInfosRepository.findBySub(sub);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

Expand All @@ -33,7 +29,6 @@ public class UserProfileService {
private final UserProfileRepository userProfileRepository;
private final DirectoryService directoryService;
private final AdminRightService adminRightService;

private final UserAdminApplicationProps applicationProps;

public UserProfileService(final UserProfileRepository userProfileRepository,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,18 @@ void testConversionToDtoOfUserInfos() {
// no profile
assertThat(UserInfosEntity.toDto(new UserInfosEntity(uuid, "sub_user", null), sub -> true))
.as("dto result")
.isEqualTo(new UserInfos("sub_user", true, null));
.isEqualTo(new UserInfos("sub_user", true, null, null, null, null));
// with profile
UserProfileEntity profile = new UserProfileEntity(UUID.randomUUID(), "a profile", null, 5, 6);
// Test mapping without quota
assertThat(UserInfosEntity.toDto(new UserInfosEntity(uuid, "sub_user", profile), sub -> true))
.as("dto result")
.isEqualTo(new UserInfos("sub_user", true, "a profile"));
.isEqualTo(new UserInfos("sub_user", true, "a profile", null, null, null));

// Test mapping with quota
assertThat(UserInfosEntity.toDtoWithDetail(new UserInfosEntity(uuid, "sub_user", profile), sub -> true, 5, 2, 6))
.as("dto result")
.isEqualTo(new UserInfos("sub_user", true, "a profile", 5, 2, 6));
}

@Test
Expand All @@ -39,7 +45,7 @@ void testConversionToDtoOfUserInfosAdminPredicate() {
Mockito.when(isAdminTest.test(Mockito.anyString())).thenReturn(false);
assertThat(UserInfosEntity.toDto(new UserInfosEntity(uuid, "admin_user", null), isAdminTest))
.as("dto result")
.isEqualTo(new UserInfos("admin_user", false, null));
.isEqualTo(new UserInfos("admin_user", false, null, null, null, null));
ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);
Mockito.verify(isAdminTest, Mockito.times(1)).test(argument.capture());
assertThat(argument.getValue()).as("value predicate submitted").isEqualTo("admin_user");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ private UserInfos getUserInfos(String userSub) {

@SneakyThrows
private void associateProfileToUser(String userSub, String profileName) {
UserInfos userInfos = new UserInfos(userSub, false, profileName);
UserInfos userInfos = new UserInfos(userSub, false, profileName, null, null, null);
performPut(API_BASE_PATH + "/users/" + userSub, userInfos);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ void testUpdateUser() {
createProfile(PROFILE_1);

// udpate the user: change its name and link it to the profile
UserInfos userInfo = new UserInfos(USER_SUB2, false, PROFILE_1);
UserInfos userInfo = new UserInfos(USER_SUB2, false, PROFILE_1, null, null, null);
updateUser(USER_SUB, userInfo, HttpStatus.OK, ADMIN_USER);

// Get and check user profile
Expand All @@ -229,13 +229,13 @@ void testUpdateUser() {
@Test
@SneakyThrows
void testUpdateUserNotFound() {
updateUser("nofFound", new UserInfos("nofFound", false, "prof"), HttpStatus.NOT_FOUND, ADMIN_USER);
updateUser("nofFound", new UserInfos("nofFound", false, "prof", null, null, null), HttpStatus.NOT_FOUND, ADMIN_USER);
}

@Test
@SneakyThrows
void testUpdateUserForbidden() {
updateUser("dummy", new UserInfos("dummy", false, "prof"), HttpStatus.FORBIDDEN, NOT_ADMIN);
updateUser("dummy", new UserInfos("dummy", false, "prof", null, null, null), HttpStatus.FORBIDDEN, NOT_ADMIN);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* Copyright (c) 2024, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

package org.gridsuite.useradmin.server.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.gridsuite.useradmin.server.UserAdminApi;
import org.gridsuite.useradmin.server.dto.UserInfos;
import org.gridsuite.useradmin.server.entity.UserInfosEntity;
import org.gridsuite.useradmin.server.entity.UserProfileEntity;
import org.gridsuite.useradmin.server.repository.UserInfosRepository;
import org.gridsuite.useradmin.server.repository.UserProfileRepository;
import org.gridsuite.useradmin.server.service.DirectoryService;
import org.junit.jupiter.api.AfterEach;
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.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;

import java.util.UUID;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.head;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
* @author Anis Touri <anis.touri at rte-france.com>
*/
@AutoConfigureMockMvc
@SpringBootTest
class UserInfosControllerTest {

private static final String PROFILE_A = "profile_A";
private static final String USER_A = "user_A";

private static final String API_BASE_PATH = "/" + UserAdminApi.API_VERSION;

@Autowired
UserProfileRepository userProfileRepository;
@Autowired
private UserInfosRepository userInfosRepository;

@MockBean
private DirectoryService directoryService;
@Autowired
private MockMvc mockMvc;

@Autowired
private ObjectMapper objectMapper;

@AfterEach
public void cleanDB() {
userInfosRepository.deleteAll();
}

@Test
void getUserDetail() throws Exception {
// Create a profile
UserProfileEntity profileEntity = new UserProfileEntity(UUID.randomUUID(), PROFILE_A, null, 10, 20);
userProfileRepository.save(profileEntity);
// Create a user
UserInfosEntity userInfosEntity = new UserInfosEntity(UUID.randomUUID(), USER_A, profileEntity);
userInfosRepository.save(userInfosEntity);

// Mock the calls to the directory service and the database
when(directoryService.getCasesCount(USER_A)).thenReturn(5);

UserInfos userInfos = objectMapper.readValue(
mockMvc.perform(head(API_BASE_PATH + "/users/{sub}/detail", USER_A))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(), UserInfos.class);
assertNotNull(userInfos);
assertEquals(USER_A, userInfos.sub());
assertFalse(userInfos.isAdmin());
assertEquals(PROFILE_A, userInfos.profileName());
assertEquals(10, userInfos.maxAllowedCases());
assertEquals(5, userInfos.numberCasesUsed());
assertEquals(20, userInfos.maxAllowedBuilds());

}
}
Loading

0 comments on commit f512352

Please sign in to comment.