Skip to content

Commit

Permalink
Add endpoint to get user names for a given directory element (#98)
Browse files Browse the repository at this point in the history
Signed-off-by: Jon HARPER <>
Co-authored-by: sBouzols <sylvain.bouzols@gmail.com>
  • Loading branch information
jonenst and sBouzols authored Nov 12, 2024
1 parent 86c1a51 commit 0a49c44
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 3 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -172,5 +172,10 @@
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock-jetty12</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -389,4 +389,13 @@ public ResponseEntity<Void> moveElementsDirectory(
return ResponseEntity.ok().build();
}

@GetMapping(value = "/explore/elements/users-identities", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "get users identities from the elements ids given as parameters")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "The users identities"),
})
public ResponseEntity<String> getUsersIdentities(@RequestParam("ids") List<UUID> ids) {
String usersIdentities = exploreService.getUsersIdentities(ids);
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(usersIdentities);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,21 @@ public class ElementAttributes {

private String description;

private String lastModifiedBy;

private Map<String, Object> specificMetadata = new HashMap<>();

// TODO do we need to pass lastModifiedBy like other attributes and remove null from this constructor, and
// change all places calling the constructor to explicitly pass lastModifiedBy ? or is it like specificMetadata ?
// and remove the other constructor because it becomes the AllArgsConstructor
public ElementAttributes(UUID elementUuid, String elementName, String type, String owner, long subdirectoriesCount, String description) {
this(elementUuid, elementName, type, owner, subdirectoriesCount, description, null);
this(elementUuid, elementName, type, owner, subdirectoriesCount, description, null, null);
}

public ElementAttributes(UUID elementUuid, String elementName, String type, String owner, long subdirectoriesCount, String description, Map<String, Object> specificMetadata) {
this(elementUuid, elementName, type, owner, subdirectoriesCount, description, null, specificMetadata);
}

// DTO in directory-server has others properties : creationDate, lastModificationDate
// TODO clean this DTO duplication problem
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ public class DirectoryService implements IDirectoryElementsService {

public DirectoryService(
FilterService filterService, ContingencyListService contingencyListService, StudyService studyService, NetworkModificationService networkModificationService,
CaseService caseService, SpreadsheetConfigService spreadsheetConfigService, ParametersService parametersService, RestTemplate restTemplate, RemoteServicesProperties remoteServicesProperties) {
CaseService caseService, SpreadsheetConfigService spreadsheetConfigService, ParametersService parametersService, RestTemplate restTemplate,
RemoteServicesProperties remoteServicesProperties) {
this.directoryServerBaseUri = remoteServicesProperties.getServiceUri("directory-server");
this.restTemplate = restTemplate;
this.genericServices = Map.ofEntries(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Stream;

import static org.gridsuite.explore.server.ExploreException.Type.*;

Expand Down Expand Up @@ -46,6 +48,7 @@ public class ExploreService {
private final CaseService caseService;
private final ParametersService parametersService;
private final SpreadsheetConfigService spreadsheetConfigService;
private final UserIdentityService userIdentityService;

private static final Logger LOGGER = LoggerFactory.getLogger(ExploreService.class);
private final UserAdminService userAdminService;
Expand All @@ -58,7 +61,8 @@ public ExploreService(
NetworkModificationService networkModificationService,
CaseService caseService,
ParametersService parametersService, UserAdminService userAdminService,
SpreadsheetConfigService spreadsheetConfigService) {
SpreadsheetConfigService spreadsheetConfigService,
UserIdentityService userIdentityService) {

this.directoryService = directoryService;
this.studyService = studyService;
Expand All @@ -69,6 +73,7 @@ public ExploreService(
this.parametersService = parametersService;
this.userAdminService = userAdminService;
this.spreadsheetConfigService = spreadsheetConfigService;
this.userIdentityService = userIdentityService;
}

public void createStudy(String studyName, CaseInfo caseInfo, String description, String userId, UUID parentDirectoryUuid, Map<String, Object> importParams, Boolean duplicateCase) {
Expand Down Expand Up @@ -326,4 +331,11 @@ private void notifyStudyUpdate(ElementAttributes element, String userId) {
}
}

public String getUsersIdentities(List<UUID> elementsUuids) {
// this returns names for owner and lastmodifiedby,
// if we need it in the future, we can do separate requests.
List<String> subs = directoryService.getElementsInfos(elementsUuids, null).stream()
.flatMap(x -> Stream.of(x.getOwner(), x.getLastModifiedBy())).distinct().filter(Objects::nonNull).toList();
return userIdentityService.getUsersIdentities(subs);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* 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.explore.server.services;

import static org.gridsuite.explore.server.utils.ExploreUtils.wrapRemoteError;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import lombok.Setter;

/**
* @author Jon Schuhmacher <jon.harper at rte-france.com>
*/
@Service
public class UserIdentityService {

private static final String USER_IDENTITY_API_VERSION = "v1";
private static final String USERS_IDENTITY_PATH = "/users/identities?subs={subs}";

private static final String DELIMITER = "/";
private final RestTemplate restTemplate;

@Setter
private String userIdentityServerBaseUri;

@Autowired
public UserIdentityService(RestTemplate restTemplate, RemoteServicesProperties remoteServicesProperties) {
this.userIdentityServerBaseUri = remoteServicesProperties.getServiceUri("user-identity-server");
this.restTemplate = restTemplate;
}

public String getUsersIdentities(List<String> subs) {
String path = UriComponentsBuilder.fromPath(DELIMITER + USER_IDENTITY_API_VERSION + USERS_IDENTITY_PATH)
.buildAndExpand(String.join(",", subs)).toUriString();
try {
return restTemplate.getForObject(userIdentityServerBaseUri + path, String.class);
} catch (HttpStatusCodeException e) {
throw wrapRemoteError(e.getMessage(), e.getStatusCode());
}
}

}
155 changes: 155 additions & 0 deletions src/test/java/org/gridsuite/explore/server/UserIdentityTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/**
* 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.explore.server;

import org.gridsuite.explore.server.dto.ElementAttributes;
import org.gridsuite.explore.server.services.*;
import org.gridsuite.explore.server.utils.WireMockUtils;
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.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.matching.StringValuePattern;

import java.util.*;
import java.util.stream.Collectors;

import static org.gridsuite.explore.server.ExploreException.Type.NOT_FOUND;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;

/**
* @author Sylvain BOUZOLS <sylvain.bouzols at rte-france.com>
*/
@SpringBootTest
@AutoConfigureMockMvc
class UserIdentityTest {

@Autowired
protected MockMvc mockMvc;

private WireMockServer wireMockServer;

protected WireMockUtils wireMockUtils;

@Autowired
private UserIdentityService userIdentityService;

@MockBean
private DirectoryService directoryService;

private static final String BASE_URL = "/v1/explore/elements/users-identities";
private static final String USER_IDENTITY_SERVER_BASE_URL = "/v1/users";
private static final String SUB = "user01";
private static final String UNKNOWN_SUB = "user02";
private static final String EXCEPTION_SUB = "user03";

private static final UUID ELEMENT_UUID = UUID.randomUUID();
private static final String ELEMENT_NAME = "Test Element";

private static final UUID ELEMENT_NOT_FOUND_UUID = UUID.randomUUID();
private static final UUID ELEMENT_UNKNOWN_SUB_UUID = UUID.randomUUID();
private static final UUID ELEMENT_EXCEPTION_SUB_UUID = UUID.randomUUID();
private static final String ELEMENT_UNKNOWN_SUB_NAME = "Test Element Unknown sub";

@BeforeEach
public void setUp() {
wireMockServer = new WireMockServer(wireMockConfig().dynamicPort());
wireMockUtils = new WireMockUtils(wireMockServer);
wireMockServer.start();
userIdentityService.setUserIdentityServerBaseUri(wireMockServer.baseUrl());

when(directoryService.getElementsInfos(List.of(ELEMENT_UUID), null)).thenReturn(List.of(new ElementAttributes(
ELEMENT_UUID,
ELEMENT_NAME,
"SOME TYPE",
SUB,
0L,
null
)));
when(directoryService.getElementsInfos(List.of(ELEMENT_UNKNOWN_SUB_UUID), null)).thenReturn(List.of(new ElementAttributes(
ELEMENT_UNKNOWN_SUB_UUID,
ELEMENT_UNKNOWN_SUB_NAME,
"SOME TYPE",
UNKNOWN_SUB,
0L,
null
)));
when(directoryService.getElementsInfos(List.of(ELEMENT_EXCEPTION_SUB_UUID), null)).thenReturn(List.of(new ElementAttributes(
ELEMENT_EXCEPTION_SUB_UUID,
"exception",
"SOME TYPE",
EXCEPTION_SUB,
0L,
null
)));
when(directoryService.getElementsInfos(List.of(ELEMENT_NOT_FOUND_UUID), null)).thenThrow(new ExploreException(NOT_FOUND));
}

protected Map<String, StringValuePattern> handleQueryParams(List<String> subs) {
return Map.of("subs", WireMock.matching(subs.stream().map(sub -> ".+").collect(Collectors.joining(","))));
}

@Test
void testGetSubIdentity() throws Exception {
UUID stubId = wireMockServer.stubFor(WireMock.get(WireMock.urlMatching(USER_IDENTITY_SERVER_BASE_URL + "/identities\\?subs=" + SUB))
.willReturn(WireMock.ok()
.withBody("{sub: " + SUB + ", firstName: \"userFirstName\", lastName: \"userLastName\"}")
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE))).getId();

MvcResult mvcResult;
String usersInfos;
mvcResult = mockMvc.perform(get(BASE_URL)
.param("ids", ELEMENT_UUID.toString()))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
usersInfos = mvcResult.getResponse().getContentAsString();
assertTrue(usersInfos.contains("userFirstName"));
assertTrue(usersInfos.contains("userLastName"));

verify(directoryService, times(1)).getElementsInfos(List.of(ELEMENT_UUID), null);
wireMockUtils.verifyGetRequest(stubId, USER_IDENTITY_SERVER_BASE_URL + "/identities", handleQueryParams(List.of(SUB)), false);
}

@Test
void testGetSubIdentityNotFoundElement() throws Exception {
mockMvc.perform(get(BASE_URL)
.param("ids", ELEMENT_NOT_FOUND_UUID.toString()))
.andExpect(status().isNotFound());

verify(directoryService, times(1)).getElementsInfos(List.of(ELEMENT_NOT_FOUND_UUID), null);
}

@Test
void testGetSubIdentityException() throws Exception {
UUID stubId = wireMockServer.stubFor(WireMock.get(WireMock.urlMatching(USER_IDENTITY_SERVER_BASE_URL + "/identities\\?subs=" + EXCEPTION_SUB))
.willReturn(WireMock.serverError())).getId();

mockMvc.perform(get(BASE_URL)
.param("ids", ELEMENT_EXCEPTION_SUB_UUID.toString()))
.andExpect(status().isBadRequest())
.andExpect(result -> assertInstanceOf(ExploreException.class, result.getResolvedException()));

verify(directoryService, times(1)).getElementsInfos(List.of(ELEMENT_EXCEPTION_SUB_UUID), null);
wireMockUtils.verifyGetRequest(stubId, USER_IDENTITY_SERVER_BASE_URL + "/identities", handleQueryParams(List.of(EXCEPTION_SUB)), false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* 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.explore.server.utils;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.admin.model.ServeEventQuery;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
import com.github.tomakehurst.wiremock.matching.StringValuePattern;
import com.github.tomakehurst.wiremock.stubbing.ServeEvent;

import java.util.List;
import java.util.Map;
import java.util.UUID;

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

public class WireMockUtils {

private final WireMockServer wireMockServer;

public WireMockUtils(WireMockServer wireMockServer) {
this.wireMockServer = wireMockServer;
}

public void verifyGetRequest(UUID stubId, String urlPath, Map<String, StringValuePattern> queryParams, boolean regexMatching) {
RequestPatternBuilder requestBuilder = regexMatching ? WireMock.getRequestedFor(WireMock.urlPathMatching(urlPath)) : WireMock.getRequestedFor(WireMock.urlPathEqualTo(urlPath));
queryParams.forEach(requestBuilder::withQueryParam);
wireMockServer.verify(1, requestBuilder);
removeRequestForStub(stubId, 1);
}

private void removeRequestForStub(UUID stubId, int nbRequests) {
List<ServeEvent> serveEvents = wireMockServer.getServeEvents(ServeEventQuery.forStubMapping(stubId)).getServeEvents();
assertEquals(nbRequests, serveEvents.size());
for (ServeEvent serveEvent : serveEvents) {
wireMockServer.removeServeEvent(serveEvent.getId());
}
}
}

0 comments on commit 0a49c44

Please sign in to comment.