diff --git a/src/main/java/org/gridsuite/useradmin/server/UserAdminApplicationProps.java b/src/main/java/org/gridsuite/useradmin/server/UserAdminApplicationProps.java index bde7378..51ed3e5 100644 --- a/src/main/java/org/gridsuite/useradmin/server/UserAdminApplicationProps.java +++ b/src/main/java/org/gridsuite/useradmin/server/UserAdminApplicationProps.java @@ -30,7 +30,7 @@ public class UserAdminApplicationProps { * Value returned by {@link UserAdminController#getCasesAlertThreshold()}. * Default: {@code 90} */ - private Integer casesAlertThreshold; + private Integer casesAlertThreshold = 90; /** * Default value of {@link org.gridsuite.useradmin.server.dto.UserInfos#maxAllowedBuilds()} if {@code null}. diff --git a/src/main/java/org/gridsuite/useradmin/server/controller/UserAdminController.java b/src/main/java/org/gridsuite/useradmin/server/controller/UserAdminController.java index 3589785..c91c17c 100644 --- a/src/main/java/org/gridsuite/useradmin/server/controller/UserAdminController.java +++ b/src/main/java/org/gridsuite/useradmin/server/controller/UserAdminController.java @@ -99,7 +99,7 @@ public ResponseEntity updateUser(@PathVariable("sub") String sub, @Operation(summary = "Record connection attempt") @ApiResponse(responseCode = "200", description = "Connection attempt recorded") public ResponseEntity recordConnectionAttempt(@PathVariable("sub") String sub, - @RequestParam(value = "isConnectionAccepted") boolean isConnectionAccepted) { + @RequestParam(value = "isConnectionAccepted") boolean isConnectionAccepted) { service.recordConnectionAttempt(sub, isConnectionAccepted); return ResponseEntity.ok().build(); } @@ -118,14 +118,6 @@ public ResponseEntity> getUserGroups(@PathVariable("sub") String return ResponseEntity.ok(service.getUserGroups(sub)); } - @GetMapping(value = "/users/{sub}/profile/max-cases") - @Operation(summary = "Get the user's max allowed cases") - @ApiResponse(responseCode = "200", description = "The user max allowed cases created") - @ApiResponse(responseCode = "404", description = "The user doesn't exist") - public ResponseEntity getUserProfileMaxStudies(@PathVariable("sub") String sub) { - return ResponseEntity.ok().body(service.getUserProfileMaxAllowedCases(sub)); - } - @GetMapping(value = "/cases-alert-threshold") @Operation(summary = "Get the cases alert threshold") @ApiResponse(responseCode = "200", description = "The cases alert threshold") @@ -133,14 +125,6 @@ public ResponseEntity getCasesAlertThreshold() { return ResponseEntity.ok().body(service.getCasesAlertThreshold()); } - @GetMapping(value = "/users/{sub}/profile/max-builds") - @Operation(summary = "Get the user's max allowed builds") - @ApiResponse(responseCode = "200", description = "The user max allowed builds") - @ApiResponse(responseCode = "404", description = "The user doesn't exist") - public ResponseEntity getUserProfileMaxAllowedBuilds(@PathVariable("sub") String sub) { - return ResponseEntity.ok().body(service.getUserProfileMaxAllowedBuilds(sub)); - } - @GetMapping(value = "/connections", produces = {MediaType.APPLICATION_JSON_VALUE}) @Operation(summary = "get the connections", description = "Access restricted to users of type: `admin`") @ApiResponse(responseCode = "200", description = "The connections list") @@ -148,92 +132,21 @@ public ResponseEntity> getConnections() { return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(service.getConnections()); } - @GetMapping(value = "/users/{sub}/profile/max-loadflow") - @Operation(summary = "Get the user's max allowed loadflows") - @ApiResponse(responseCode = "200", description = "The user max allowed loadflows") - @ApiResponse(responseCode = "404", description = "The user doesn't exist") - public ResponseEntity getUserProfileMaxAllowedLoadflows(@PathVariable String sub) { - return ResponseEntity.ok().body(service.getUserProfileMaxAllowedLoadflows(sub)); - } - - @GetMapping(value = "/users/{sub}/profile/max-security") - @Operation(summary = "Get the user's max allowed security analysis") - @ApiResponse(responseCode = "200", description = "The user max allowed security analysis") - @ApiResponse(responseCode = "404", description = "The user doesn't exist") - public ResponseEntity getUserProfileMaxAllowedSecurity(@PathVariable String sub) { - return ResponseEntity.ok().body(service.getUserProfileMaxAllowedSecurity(sub)); - } - - @GetMapping(value = "/users/{sub}/profile/max-sensitivity") - @Operation(summary = "Get the user's max allowed sensitivity analysis") - @ApiResponse(responseCode = "200", description = "The user max allowed sensitivity analysis") - @ApiResponse(responseCode = "404", description = "The user doesn't exist") - public ResponseEntity getUserProfileMaxAllowedSensitivity(@PathVariable String sub) { - return ResponseEntity.ok().body(service.getUserProfileMaxAllowedSensitivity(sub)); - } - - @GetMapping(value = "/users/{sub}/profile/max-shortcircuit") - @Operation(summary = "Get the user's max allowed short circuit analysis") - @ApiResponse(responseCode = "200", description = "The user max allowed short circuit analysis") - @ApiResponse(responseCode = "404", description = "The user doesn't exist") - public ResponseEntity getUserProfileMaxAllowedShortCircuit(@PathVariable String sub) { - return ResponseEntity.ok().body(service.getUserProfileMaxAllowedShortCircuit(sub)); - } - - @GetMapping(value = "/users/{sub}/profile/max-voltage-init") - @Operation(summary = "Get the user's max allowed voltage init analysis") - @ApiResponse(responseCode = "200", description = "The user max allowed voltage init analysis") - @ApiResponse(responseCode = "404", description = "The user doesn't exist") - public ResponseEntity getUserProfileMaxAllowedVoltageInit(@PathVariable String sub) { - return ResponseEntity.ok().body(service.getUserProfileMaxAllowedVoltageInit(sub)); - } - - @GetMapping(value = "/users/{sub}/profile/max-pcc-min") - @Operation(summary = "Get the user's max allowed pcc min analysis") - @ApiResponse(responseCode = "200", description = "The user max allowed pcc min analysis") - @ApiResponse(responseCode = "404", description = "The user doesn't exist") - public ResponseEntity getUserProfileMaxAllowedPccMin(@PathVariable String sub) { - return ResponseEntity.ok().body(service.getUserProfileMaxAllowedPccMin(sub)); - } - - @GetMapping(value = "/users/{sub}/profile/max-state-estimation") - @Operation(summary = "Get the user's max allowed state estimation analysis") - @ApiResponse(responseCode = "200", description = "The user max allowed state estimation analysis") - @ApiResponse(responseCode = "404", description = "The user doesn't exist") - public ResponseEntity getUserProfileMaxAllowedStateEstimation(@PathVariable String sub) { - return ResponseEntity.ok().body(service.getUserProfileMaxAllowedStateEstimation(sub)); - } - - @GetMapping(value = "/users/{sub}/profile/max-balance-adjustement") - @Operation(summary = "Get the user's max allowed balance adjustement analysis") - @ApiResponse(responseCode = "200", description = "The user max allowed balance adjustement analysis") - @ApiResponse(responseCode = "404", description = "The user doesn't exist") - public ResponseEntity getUserProfileMaxAllowedBalanceAdjustement(@PathVariable String sub) { - return ResponseEntity.ok().body(service.getUserProfileMaxAllowedBalanceAdjustement(sub)); - } - - @GetMapping(value = "/users/{sub}/profile/max-dynamic-simulation") - @Operation(summary = "Get the user's max allowed dynamic simulation analysis") - @ApiResponse(responseCode = "200", description = "The user max allowed dynamic simulation analysis") - @ApiResponse(responseCode = "404", description = "The user doesn't exist") - public ResponseEntity getUserProfileMaxAllowedDynamicSimulation(@PathVariable String sub) { - return ResponseEntity.ok().body(service.getUserProfileMaxAllowedDynamicSimulation(sub)); - } - - @GetMapping(value = "/users/{sub}/profile/max-dynamic-security") - @Operation(summary = "Get the user's max allowed dynamic security analysis") - @ApiResponse(responseCode = "200", description = "The user max allowed dynamic security analysis") + @Deprecated + @GetMapping(value = "/users/{sub}/profile/max-cases") + @Operation(summary = "Get the user's max allowed cases") + @ApiResponse(responseCode = "200", description = "The user max allowed cases created") @ApiResponse(responseCode = "404", description = "The user doesn't exist") - public ResponseEntity getUserProfileMaxAllowedDynamicSecurity(@PathVariable String sub) { - return ResponseEntity.ok().body(service.getUserProfileMaxAllowedDynamicSecurity(sub)); + public ResponseEntity getUserProfileMaxStudies(@PathVariable("sub") String sub) { + return ResponseEntity.ok().body(service.getUserProfileMaxAllowedCases(sub)); } - @GetMapping(value = "/users/{sub}/profile/max-dynamic-margin") - @Operation(summary = "Get the user's max allowed dynamic margin analysis") - @ApiResponse(responseCode = "200", description = "The user max allowed dynamic margin analysis") + @Deprecated + @GetMapping(value = "/users/{sub}/profile/max-builds") + @Operation(summary = "Get the user's max allowed builds") + @ApiResponse(responseCode = "200", description = "The user max allowed builds") @ApiResponse(responseCode = "404", description = "The user doesn't exist") - public ResponseEntity getUserProfileMaxAllowedDynamicMargin(@PathVariable String sub) { - return ResponseEntity.ok().body(service.getUserProfileMaxAllowedDynamicMargin(sub)); + public ResponseEntity getUserProfileMaxAllowedBuilds(@PathVariable("sub") String sub) { + return ResponseEntity.ok().body(service.getUserProfileMaxAllowedBuilds(sub)); } - } diff --git a/src/main/java/org/gridsuite/useradmin/server/controller/UserQuotaController.java b/src/main/java/org/gridsuite/useradmin/server/controller/UserQuotaController.java new file mode 100644 index 0000000..4c0ab9a --- /dev/null +++ b/src/main/java/org/gridsuite/useradmin/server/controller/UserQuotaController.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2026, 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.*; +import org.gridsuite.useradmin.server.service.UserQuotaService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; +import java.util.UUID; + +/** + * @author Ghiles Abdellah {@literal } + */ +@RestController +@RequestMapping(value = "/" + UserAdminApi.API_VERSION) +@Tag(name = "UserQuotaController") +@ApiResponse(responseCode = "403", description = "The current user does not have right to ask these data") +public class UserQuotaController { + private final UserQuotaService userQuotaService; + + public UserQuotaController(UserQuotaService userQuotaService) { + this.userQuotaService = userQuotaService; + } + + @GetMapping(value = "/users/{sub}/quota/max") + @Operation(summary = "Get the user's quota") + @ApiResponse(responseCode = "200", description = "The user max quota") + @ApiResponse(responseCode = "404", description = "The user doesn't exist") + public ResponseEntity> getUserProfileMaxQuota(@PathVariable("sub") String sub) { + Map userMaxQuota = userQuotaService.getUserMaxQuota(sub); + return ResponseEntity.ok().body(userMaxQuota); + } + + @GetMapping(value = "/users/{sub}/quota/current") + @Operation(summary = "Get the user's current quota usage") + @ApiResponse(responseCode = "200", description = "The user current quota usage") + @ApiResponse(responseCode = "404", description = "The user doesn't exist") + public ResponseEntity> getUserCurrentQuotaUsage(@PathVariable("sub") String sub) { + Map userCurrentQuotaUsage = userQuotaService.getUserCurrentQuotaUsage(sub); + return ResponseEntity.ok().body(userCurrentQuotaUsage); + } + + @PostMapping(value = "/users/{sub}/quota/reset") + @Operation(summary = "Reset the user's current quota usage", description = "Access restricted to users of type: `admin`") + @ApiResponse(responseCode = "200", description = "The user current quota usage has been reseted") + public ResponseEntity resetUserCurrentQuotaUsage(@PathVariable("sub") String sub) { + userQuotaService.resetUserCurrentQuotaUsage(sub); + return ResponseEntity.ok().build(); + } + + @PostMapping(value = "/users/{sub}/quota/{operation}/{operation_id}/start") + @Operation(summary = "Increase the user's current quota usage for a given operation") + @ApiResponse(responseCode = "200", description = "The user current quota usage has been updated") + public ResponseEntity startUserOperation(@PathVariable("sub") String sub, + @PathVariable("operation") OperationType operation, + @PathVariable("operation_id") UUID operationId) { + userQuotaService.startUserOperation(sub, operation, operationId); + return ResponseEntity.ok().build(); + } + + @PostMapping(value = "/users/{sub}/quota/{operation}/{operation_id}/end") + @Operation(summary = "Decrease the user's current quota usage for a given operation") + @ApiResponse(responseCode = "200", description = "The user current quota usage has been updated") + public ResponseEntity endUserOperation(@PathVariable("sub") String sub, + @PathVariable("operation") OperationType operation, + @PathVariable("operation_id") UUID operationId) { + userQuotaService.endUserOperation(sub, operation, operationId); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/org/gridsuite/useradmin/server/dto/OperationType.java b/src/main/java/org/gridsuite/useradmin/server/dto/OperationType.java new file mode 100644 index 0000000..a6aec70 --- /dev/null +++ b/src/main/java/org/gridsuite/useradmin/server/dto/OperationType.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2026, 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.dto; + +/** + * @author Ghiles Abdellah {@literal } + */ +public enum OperationType { + CASES, + BUILD, + LOAD_FLOW, + SECURITY, + SENSITIVITY, + SHORT_CIRCUIT, + VOLTAGE_INIT, + PCC_MIN, + STATE_ESTIMATION, + BALANCE_ADJUSTEMENT, + DYNAMIC_SIMULATION, + DYNAMIC_SECURITY, + DYNAMIC_MARGIN, +} diff --git a/src/main/java/org/gridsuite/useradmin/server/dto/UserProfile.java b/src/main/java/org/gridsuite/useradmin/server/dto/UserProfile.java index d86d1f0..ba2cdc3 100644 --- a/src/main/java/org/gridsuite/useradmin/server/dto/UserProfile.java +++ b/src/main/java/org/gridsuite/useradmin/server/dto/UserProfile.java @@ -21,19 +21,7 @@ @Builder public final class UserProfile { public static final String DEFAULT_PROFILE_NAME = "default profile"; - public static final String MAX_ALLOWED_CASES = "maxAllowedCases"; - public static final String MAX_ALLOWED_BUILD = "maxAllowedBuilds"; - public static final String MAX_ALLOWED_LOADFLOW = "maxAllowedLoadflow"; - public static final String MAX_ALLOWED_SECURITY = "maxAllowedSecurity"; - public static final String MAX_ALLOWED_SENSITIVITY = "maxAllowedSensitivity"; - public static final String MAX_ALLOWED_SHORT_CIRCUIT = "maxAllowedShortCircuit"; - public static final String MAX_ALLOWED_VOLTAGE_INIT = "maxAllowedVoltageInit"; - public static final String MAX_ALLOWED_PCC_MIN = "maxAllowedPccMin"; - public static final String MAX_ALLOWED_STATE_ESTIMATION = "maxAllowedStateEstimation"; - public static final String MAX_ALLOWED_BALANCE_ADJUSTEMENT = "maxAllowedBalanceAdjustement"; - public static final String MAX_ALLOWED_DYNAMIC_SIMULATION = "maxAllowedDynamicSimulation"; - public static final String MAX_ALLOWED_DYNAMIC_SECURITY = "maxAllowedDynamicSecurity"; - public static final String MAX_ALLOWED_DYNAMIC_MARGIN = "maxAllowedDynamicMargin"; + private final UUID id; private final String name; private final UUID loadFlowParameterId; @@ -43,10 +31,10 @@ public final class UserProfile { private final UUID pccMinParameterId; private final UUID voltageInitParameterId; private final Boolean allLinksValid; - private final Map maxAllowValuesMap; private final UUID spreadsheetConfigCollectionId; private final UUID networkVisualizationParameterId; private final UUID workspaceId; + private final Map maxOperationQuota; public UserProfile( UUID id, @@ -58,10 +46,10 @@ public UserProfile( UUID pccMinParameterId, UUID voltageInitParameterId, Boolean allLinksValid, - Map maxAllowValuesMap, UUID spreadsheetConfigCollectionId, UUID networkVisualizationParameterId, - UUID workspaceId + UUID workspaceId, + Map maxOperationQuota ) { this.id = id; this.name = name; @@ -72,18 +60,25 @@ public UserProfile( this.pccMinParameterId = pccMinParameterId; this.voltageInitParameterId = voltageInitParameterId; this.allLinksValid = allLinksValid; - this.maxAllowValuesMap = maxAllowValuesMap == null ? new HashMap<>() : new HashMap<>(maxAllowValuesMap); this.spreadsheetConfigCollectionId = spreadsheetConfigCollectionId; this.networkVisualizationParameterId = networkVisualizationParameterId; this.workspaceId = workspaceId; + this.maxOperationQuota = maxOperationQuota == null ? new HashMap<>() : new HashMap<>(maxOperationQuota); + } + public static UserProfile createDefaultProfile(Map maxAllowValues) { + return UserProfile.builder() + .name(DEFAULT_PROFILE_NAME) + .maxOperationQuota(maxAllowValues) + .build(); } - public Map getMaxAllowValuesMap() { - return Collections.unmodifiableMap(maxAllowValuesMap); + public Map getMaxOperationQuota() { + return Collections.unmodifiableMap(maxOperationQuota); } - public static UserProfile createDefaultProfile(Map maxAllowValues) { - return UserProfile.builder().name(DEFAULT_PROFILE_NAME).maxAllowValuesMap(maxAllowValues).build(); + public Integer getMaxOperationQuota(OperationType operationType) { + return maxOperationQuota.get(operationType); } + } diff --git a/src/main/java/org/gridsuite/useradmin/server/entity/UserInfosEntity.java b/src/main/java/org/gridsuite/useradmin/server/entity/UserInfosEntity.java index 83dbe0d..37d27c9 100644 --- a/src/main/java/org/gridsuite/useradmin/server/entity/UserInfosEntity.java +++ b/src/main/java/org/gridsuite/useradmin/server/entity/UserInfosEntity.java @@ -12,10 +12,10 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.gridsuite.useradmin.server.dto.OperationType; import org.gridsuite.useradmin.server.dto.UserInfos; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; /** @@ -29,25 +29,42 @@ @Table(name = "user_infos", indexes = {@Index(name = "user_infos_sub_index", columnList = "sub")}) public class UserInfosEntity { - public UserInfosEntity(String sub) { - this(UUID.randomUUID(), sub, null, null); - } - @Id @Column(name = "id") private UUID id; - // TODO rename to subject or userName @Column(name = "sub", nullable = false, unique = true) private String sub; - @ManyToOne @JoinColumn(name = "profile_id", foreignKey = @ForeignKey(name = "profile_id_fk_constraint")) private UserProfileEntity profile; - + @OneToMany(orphanRemoval = true, mappedBy = "userInfos", cascade = CascadeType.ALL) + private List userOperations = new ArrayList<>(); @ManyToMany(mappedBy = "users") private Set groups; + public UserInfosEntity(String sub) { + this(UUID.randomUUID(), sub, null, new ArrayList<>(), null); + } + + public static UserInfos toDto(@Nullable final UserInfosEntity entity) { + return entity == null ? null : entity.toUserInfos(null, null, null, + null, null, null, null, + null, null, null, null, + null, null, null); + } + + public static UserInfos toDtoWithDetail(@Nullable final UserInfosEntity entity, Integer maxAllowedCases, Integer numberCasesUsed, + Integer maxAllowedBuilds, Integer maxAllowedLoadflow, Integer maxAllowedSecurity, + Integer maxAllowedSensitivity, Integer maxAllowedShortCircuit, Integer maxAllowedVoltageInit, + Integer maxAllowedPccMin, Integer maxAllowedStateEstimation, Integer maxAllowedBalanceAdjustement, + Integer maxAllowedDynamicSimulation, Integer maxAllowedDynamicSecurity, Integer maxAllowedDynamicMargin) { + return entity == null ? null : entity.toUserInfos(maxAllowedCases, numberCasesUsed, maxAllowedBuilds, + maxAllowedLoadflow, maxAllowedSecurity, maxAllowedSensitivity, maxAllowedShortCircuit, maxAllowedVoltageInit, + maxAllowedPccMin, maxAllowedStateEstimation, maxAllowedBalanceAdjustement, maxAllowedDynamicSimulation, + maxAllowedDynamicSecurity, maxAllowedDynamicMargin); + } + private UserInfos toUserInfos(Integer maxAllowedCases, Integer numberCasesUsed, Integer maxAllowedBuilds, @@ -62,30 +79,17 @@ private UserInfos toUserInfos(Integer maxAllowedCases, Integer maxAllowedDynamicSimulation, Integer maxAllowedDynamicSecurity, Integer maxAllowedDynamicMargin - ) { + ) { String profileName = getProfile() == null ? null : getProfile().getName(); Set groupNames = getGroups() == null ? null : getGroups().stream().map(GroupInfosEntity::getName).collect(Collectors.toSet()); return new UserInfos(getSub(), null, null, profileName, maxAllowedCases, numberCasesUsed, maxAllowedBuilds, - maxAllowedLoadflow, maxAllowedSecurity, maxAllowedSensitivity, maxAllowedShortCircuit, maxAllowedVoltageInit, - maxAllowedPccMin, maxAllowedStateEstimation, maxAllowedBalanceAdjustement, maxAllowedDynamicSimulation, - maxAllowedDynamicSecurity, maxAllowedDynamicMargin, groupNames); + maxAllowedLoadflow, maxAllowedSecurity, maxAllowedSensitivity, maxAllowedShortCircuit, maxAllowedVoltageInit, + maxAllowedPccMin, maxAllowedStateEstimation, maxAllowedBalanceAdjustement, maxAllowedDynamicSimulation, + maxAllowedDynamicSecurity, maxAllowedDynamicMargin, groupNames); } - public static UserInfos toDto(@Nullable final UserInfosEntity entity) { - return entity == null ? null : entity.toUserInfos(null, null, null, - null, null, null, null, - null, null, null, null, - null, null, null); - } - - public static UserInfos toDtoWithDetail(@Nullable final UserInfosEntity entity, Integer maxAllowedCases, Integer numberCasesUsed, - Integer maxAllowedBuilds, Integer maxAllowedLoadflow, Integer maxAllowedSecurity, - Integer maxAllowedSensitivity, Integer maxAllowedShortCircuit, Integer maxAllowedVoltageInit, - Integer maxAllowedPccMin, Integer maxAllowedStateEstimation, Integer maxAllowedBalanceAdjustement, - Integer maxAllowedDynamicSimulation, Integer maxAllowedDynamicSecurity, Integer maxAllowedDynamicMargin) { - return entity == null ? null : entity.toUserInfos(maxAllowedCases, numberCasesUsed, maxAllowedBuilds, - maxAllowedLoadflow, maxAllowedSecurity, maxAllowedSensitivity, maxAllowedShortCircuit, maxAllowedVoltageInit, - maxAllowedPccMin, maxAllowedStateEstimation, maxAllowedBalanceAdjustement, maxAllowedDynamicSimulation, - maxAllowedDynamicSecurity, maxAllowedDynamicMargin); + public Map getQuotaUsageMap() { + return userOperations.stream() + .collect(Collectors.groupingBy(UserOperationEntity::getOperationType, Collectors.summingInt(e -> 1))); } } diff --git a/src/main/java/org/gridsuite/useradmin/server/entity/UserOperationEntity.java b/src/main/java/org/gridsuite/useradmin/server/entity/UserOperationEntity.java new file mode 100644 index 0000000..25ccf47 --- /dev/null +++ b/src/main/java/org/gridsuite/useradmin/server/entity/UserOperationEntity.java @@ -0,0 +1,44 @@ +package org.gridsuite.useradmin.server.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.gridsuite.useradmin.server.dto.OperationType; + +import java.util.UUID; + +@Getter +@Setter +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "user_quotas", + uniqueConstraints = @UniqueConstraint(columnNames = {"sub", "operation_id"})) +public class UserOperationEntity { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private UUID id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "sub", + referencedColumnName = "sub", nullable = false, + foreignKey = @ForeignKey(name = "userOperation_userInfo_sub_fk_constraint")) + private UserInfosEntity userInfos; + + @Column(name = "operation_id", nullable = false) + private UUID operationId; + + @Enumerated(EnumType.STRING) + @Column(name = "operation_type", nullable = false) + private OperationType operationType; + + public UserOperationEntity(UserInfosEntity userInfos, UUID operationId, OperationType operationType) { + this.userInfos = userInfos; + this.operationId = operationId; + this.operationType = operationType; + } +} diff --git a/src/main/java/org/gridsuite/useradmin/server/service/UserAdminService.java b/src/main/java/org/gridsuite/useradmin/server/service/UserAdminService.java index 94b7371..75c327f 100644 --- a/src/main/java/org/gridsuite/useradmin/server/service/UserAdminService.java +++ b/src/main/java/org/gridsuite/useradmin/server/service/UserAdminService.java @@ -7,11 +7,7 @@ package org.gridsuite.useradmin.server.service; import org.gridsuite.useradmin.server.UserAdminApplicationProps; -import org.gridsuite.useradmin.server.dto.UserConnection; -import org.gridsuite.useradmin.server.dto.UserGroup; -import org.gridsuite.useradmin.server.dto.UserIdentity; -import org.gridsuite.useradmin.server.dto.UserInfos; -import org.gridsuite.useradmin.server.dto.UserProfile; +import org.gridsuite.useradmin.server.dto.*; import org.gridsuite.useradmin.server.entity.UserInfosEntity; import org.gridsuite.useradmin.server.entity.UserProfileEntity; import org.gridsuite.useradmin.server.error.UserAdminException; @@ -24,8 +20,6 @@ import java.util.*; import java.util.stream.Collectors; -import static org.gridsuite.useradmin.server.dto.UserProfile.*; - /** * @author Etienne Homer */ @@ -38,8 +32,9 @@ public class UserAdminService { private final UserProfileService userProfileService; private final UserGroupService userGroupService; private final UserIdentityService userIdentityService; - + private final UserQuotaService userQuotaService; private final UserAdminApplicationProps applicationProps; + private final UserGroupRepository userGroupRepository; public UserAdminService(final UserInfosRepository userInfosRepository, @@ -49,6 +44,7 @@ public UserAdminService(final UserInfosRepository userInfosRepository, final UserProfileService userProfileService, final UserGroupService userGroupService, final UserIdentityService userIdentityService, + final UserQuotaService userQuotaService, final UserAdminApplicationProps applicationProps, final UserGroupRepository userGroupRepository) { this.userInfosRepository = Objects.requireNonNull(userInfosRepository); @@ -58,8 +54,15 @@ public UserAdminService(final UserInfosRepository userInfosRepository, this.userProfileService = Objects.requireNonNull(userProfileService); this.userGroupService = Objects.requireNonNull(userGroupService); this.userIdentityService = Objects.requireNonNull(userIdentityService); + this.userQuotaService = Objects.requireNonNull(userQuotaService); this.applicationProps = Objects.requireNonNull(applicationProps); - this.userGroupRepository = userGroupRepository; + this.userGroupRepository = Objects.requireNonNull(userGroupRepository); + } + + private static void removeUserFromGroups(UserInfosEntity entity) { + if (entity.getGroups() != null) { + entity.getGroups().forEach(group -> group.getUsers().removeIf(user -> user.getSub().equals(entity.getSub()))); + } } private UserInfos toDtoUserInfo(final UserInfosEntity entity) { @@ -91,12 +94,6 @@ public void createUser(String sub) { userInfosRepository.save(new UserInfosEntity(sub)); } - private static void removeUserFromGroups(UserInfosEntity entity) { - if (entity.getGroups() != null) { - entity.getGroups().forEach(group -> group.getUsers().removeIf(user -> user.getSub().equals(entity.getSub()))); - } - } - @Transactional public long delete(String sub) { adminRightService.assertIsAdmin(); @@ -128,10 +125,10 @@ public void updateUser(String sub, UserInfos userInfos) { // set all new groups for user user.setGroups(userInfos.groups() == null ? - null : - userInfos.groups().stream().map(userGroupRepository::findByName) - .filter(Optional::isPresent) - .map(Optional::get).collect(Collectors.toSet())); + null : + userInfos.groups().stream().map(userGroupRepository::findByName) + .filter(Optional::isPresent) + .map(Optional::get).collect(Collectors.toSet())); // add user to all groups if (user.getGroups() != null) { @@ -160,25 +157,7 @@ public Optional getUser(String sub) { @Transactional(readOnly = true) public UserProfile getUserProfile(String sub) { - return doGetUserProfile(sub); - } - - private UserProfile doGetUserProfile(String sub) { - // this method is not restricted to Admin because it is called by any user to retrieve its own profile - Optional userOpt = userInfosRepository.findBySub(sub); - - if (userOpt.isEmpty()) { - return createDefaultProfile(); - } - - UserInfosEntity user = userOpt.get(); - - if (user.getProfile() == null) { - return createDefaultProfile(); - } - - return userProfileService.getProfile(user.getProfile().getId()) - .orElseGet(this::createDefaultProfile); + return userProfileService.doGetUserProfile(sub); } @Transactional(readOnly = true) @@ -203,121 +182,20 @@ public List getUserGroups(String sub) { .toList(); } + @Deprecated @Transactional(readOnly = true) public Integer getUserProfileMaxAllowedCases(String sub) { - UserProfile profile = doGetUserProfile(sub); - return Optional.ofNullable(profile.getMaxAllowValuesMap().get(MAX_ALLOWED_CASES)) - .orElse(applicationProps.getDefaultMaxAllowedCases()); - } - - public Integer getCasesAlertThreshold() { - return Optional.ofNullable(applicationProps.getCasesAlertThreshold()).orElse(90); + return userQuotaService.getUserProfileMaxAllowedCases(sub); } + @Deprecated @Transactional(readOnly = true) public Integer getUserProfileMaxAllowedBuilds(String sub) { - UserProfile profile = doGetUserProfile(sub); - return Optional.ofNullable(profile.getMaxAllowValuesMap().get(MAX_ALLOWED_BUILD)) - .orElse(applicationProps.getDefaultMaxAllowedBuilds()); - } - - @Transactional(readOnly = true) - public Integer getUserProfileMaxAllowedLoadflows(String sub) { - UserProfile profile = doGetUserProfile(sub); - return Optional.ofNullable(profile.getMaxAllowValuesMap().get(MAX_ALLOWED_LOADFLOW)) - .orElse(applicationProps.getDefaultMaxAllowedLoadflow()); + return userQuotaService.getUserProfileMaxAllowedBuilds(sub); } - @Transactional(readOnly = true) - public Integer getUserProfileMaxAllowedSecurity(String sub) { - UserProfile profile = doGetUserProfile(sub); - return Optional.ofNullable(profile.getMaxAllowValuesMap().get(MAX_ALLOWED_SECURITY)) - .orElse(applicationProps.getDefaultMaxAllowedSecurity()); - } - - @Transactional(readOnly = true) - public Integer getUserProfileMaxAllowedSensitivity(String sub) { - UserProfile profile = doGetUserProfile(sub); - return Optional.ofNullable(profile.getMaxAllowValuesMap().get(MAX_ALLOWED_SENSITIVITY)) - .orElse(applicationProps.getDefaultMaxAllowedSensitivity()); - } - - @Transactional(readOnly = true) - public Integer getUserProfileMaxAllowedShortCircuit(String sub) { - UserProfile profile = doGetUserProfile(sub); - return Optional.ofNullable(profile.getMaxAllowValuesMap().get(MAX_ALLOWED_SHORT_CIRCUIT)) - .orElse(applicationProps.getDefaultMaxAllowedShortCircuit()); - } - - @Transactional(readOnly = true) - public Integer getUserProfileMaxAllowedVoltageInit(String sub) { - UserProfile profile = doGetUserProfile(sub); - return Optional.ofNullable(profile.getMaxAllowValuesMap().get(MAX_ALLOWED_VOLTAGE_INIT)) - .orElse(applicationProps.getDefaultMaxAllowedVoltageInit()); - } - - @Transactional(readOnly = true) - public Integer getUserProfileMaxAllowedPccMin(String sub) { - UserProfile profile = doGetUserProfile(sub); - return Optional.ofNullable(profile.getMaxAllowValuesMap().get(MAX_ALLOWED_PCC_MIN)) - .orElse(applicationProps.getDefaultMaxAllowedPccMin()); - } - - @Transactional(readOnly = true) - public Integer getUserProfileMaxAllowedStateEstimation(String sub) { - UserProfile profile = doGetUserProfile(sub); - return Optional.ofNullable(profile.getMaxAllowValuesMap().get(MAX_ALLOWED_STATE_ESTIMATION)) - .orElse(applicationProps.getDefaultMaxAllowedStateEstimation()); - } - - @Transactional(readOnly = true) - public Integer getUserProfileMaxAllowedBalanceAdjustement(String sub) { - UserProfile profile = doGetUserProfile(sub); - return Optional.ofNullable(profile.getMaxAllowValuesMap().get(MAX_ALLOWED_BALANCE_ADJUSTEMENT)) - .orElse(applicationProps.getDefaultMaxAllowedBalanceAdjustement()); - } - - @Transactional(readOnly = true) - public Integer getUserProfileMaxAllowedDynamicSimulation(String sub) { - UserProfile profile = doGetUserProfile(sub); - return Optional.ofNullable(profile.getMaxAllowValuesMap().get(MAX_ALLOWED_DYNAMIC_SIMULATION)) - .orElse(applicationProps.getDefaultMaxAllowedDynamicSimulation()); - } - - @Transactional(readOnly = true) - public Integer getUserProfileMaxAllowedDynamicSecurity(String sub) { - UserProfile profile = doGetUserProfile(sub); - return Optional.ofNullable(profile.getMaxAllowValuesMap().get(MAX_ALLOWED_DYNAMIC_SECURITY)) - .orElse(applicationProps.getDefaultMaxAllowedDynamicSecurity()); - } - - @Transactional(readOnly = true) - public Integer getUserProfileMaxAllowedDynamicMargin(String sub) { - UserProfile profile = doGetUserProfile(sub); - return Optional.ofNullable(profile.getMaxAllowValuesMap().get(MAX_ALLOWED_DYNAMIC_MARGIN)) - .orElse(applicationProps.getDefaultMaxAllowedDynamicMargin()); - } - - private Map getDefaultMaxAllowedValues() { - Map maxAllowedValuesMap = new HashMap<>(); - maxAllowedValuesMap.put(MAX_ALLOWED_CASES, applicationProps.getDefaultMaxAllowedCases()); - maxAllowedValuesMap.put(MAX_ALLOWED_BUILD, applicationProps.getDefaultMaxAllowedBuilds()); - maxAllowedValuesMap.put(MAX_ALLOWED_LOADFLOW, applicationProps.getDefaultMaxAllowedLoadflow()); - maxAllowedValuesMap.put(MAX_ALLOWED_SECURITY, applicationProps.getDefaultMaxAllowedSecurity()); - maxAllowedValuesMap.put(MAX_ALLOWED_SENSITIVITY, applicationProps.getDefaultMaxAllowedSensitivity()); - maxAllowedValuesMap.put(MAX_ALLOWED_SHORT_CIRCUIT, applicationProps.getDefaultMaxAllowedShortCircuit()); - maxAllowedValuesMap.put(MAX_ALLOWED_VOLTAGE_INIT, applicationProps.getDefaultMaxAllowedVoltageInit()); - maxAllowedValuesMap.put(MAX_ALLOWED_PCC_MIN, applicationProps.getDefaultMaxAllowedPccMin()); - maxAllowedValuesMap.put(MAX_ALLOWED_STATE_ESTIMATION, applicationProps.getDefaultMaxAllowedStateEstimation()); - maxAllowedValuesMap.put(MAX_ALLOWED_BALANCE_ADJUSTEMENT, applicationProps.getDefaultMaxAllowedBalanceAdjustement()); - maxAllowedValuesMap.put(MAX_ALLOWED_DYNAMIC_SIMULATION, applicationProps.getDefaultMaxAllowedDynamicSimulation()); - maxAllowedValuesMap.put(MAX_ALLOWED_DYNAMIC_SECURITY, applicationProps.getDefaultMaxAllowedDynamicSecurity()); - maxAllowedValuesMap.put(MAX_ALLOWED_DYNAMIC_MARGIN, applicationProps.getDefaultMaxAllowedDynamicMargin()); - return maxAllowedValuesMap; - } - - private UserProfile createDefaultProfile() { - return UserProfile.createDefaultProfile(getDefaultMaxAllowedValues()); + public Integer getCasesAlertThreshold() { + return applicationProps.getCasesAlertThreshold(); } /** diff --git a/src/main/java/org/gridsuite/useradmin/server/service/UserProfileService.java b/src/main/java/org/gridsuite/useradmin/server/service/UserProfileService.java index 53f0b77..b607a9c 100644 --- a/src/main/java/org/gridsuite/useradmin/server/service/UserProfileService.java +++ b/src/main/java/org/gridsuite/useradmin/server/service/UserProfileService.java @@ -9,38 +9,85 @@ import com.google.common.collect.Sets; import org.apache.commons.lang3.BooleanUtils; import org.gridsuite.useradmin.server.UserAdminApplicationProps; +import org.gridsuite.useradmin.server.dto.OperationType; import org.gridsuite.useradmin.server.dto.UserProfile; +import org.gridsuite.useradmin.server.entity.UserInfosEntity; import org.gridsuite.useradmin.server.entity.UserProfileEntity; import org.gridsuite.useradmin.server.error.UserAdminException; +import org.gridsuite.useradmin.server.repository.UserInfosRepository; import org.gridsuite.useradmin.server.repository.UserProfileRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; + import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.gridsuite.useradmin.server.dto.UserProfile.*; +import static org.gridsuite.useradmin.server.dto.OperationType.*; /** * @author David Braquart */ @Service public class UserProfileService { + private final UserInfosRepository userInfosRepository; private final UserProfileRepository userProfileRepository; private final DirectoryService directoryService; private final AdminRightService adminRightService; private final UserAdminApplicationProps applicationProps; - public UserProfileService(final UserProfileRepository userProfileRepository, + public UserProfileService(final UserInfosRepository userInfosRepository, + final UserProfileRepository userProfileRepository, final AdminRightService adminRightService, final DirectoryService directoryService, final UserAdminApplicationProps applicationProps) { + this.userInfosRepository = userInfosRepository; this.userProfileRepository = Objects.requireNonNull(userProfileRepository); this.adminRightService = Objects.requireNonNull(adminRightService); this.directoryService = Objects.requireNonNull(directoryService); this.applicationProps = Objects.requireNonNull(applicationProps); } + public UserProfile doGetUserProfile(String sub) { + // this method is not restricted to Admin because it is called by any user to retrieve its own profile + Optional userOpt = userInfosRepository.findBySub(sub); + + if (userOpt.isEmpty()) { + return createDefaultProfile(); + } + + UserInfosEntity user = userOpt.get(); + + if (user.getProfile() == null) { + return createDefaultProfile(); + } + + return getProfile(user.getProfile().getId()) + .orElseGet(this::createDefaultProfile); + } + + public UserProfile createDefaultProfile() { + return UserProfile.createDefaultProfile(getDefaultMaxAllowedValues()); + } + + public Map getDefaultMaxAllowedValues() { + Map maxAllowedValuesMap = new EnumMap<>(OperationType.class); + maxAllowedValuesMap.put(CASES, applicationProps.getDefaultMaxAllowedCases()); + maxAllowedValuesMap.put(BUILD, applicationProps.getDefaultMaxAllowedBuilds()); + maxAllowedValuesMap.put(LOAD_FLOW, applicationProps.getDefaultMaxAllowedLoadflow()); + maxAllowedValuesMap.put(SECURITY, applicationProps.getDefaultMaxAllowedSecurity()); + maxAllowedValuesMap.put(SENSITIVITY, applicationProps.getDefaultMaxAllowedSensitivity()); + maxAllowedValuesMap.put(SHORT_CIRCUIT, applicationProps.getDefaultMaxAllowedShortCircuit()); + maxAllowedValuesMap.put(VOLTAGE_INIT, applicationProps.getDefaultMaxAllowedVoltageInit()); + maxAllowedValuesMap.put(PCC_MIN, applicationProps.getDefaultMaxAllowedPccMin()); + maxAllowedValuesMap.put(STATE_ESTIMATION, applicationProps.getDefaultMaxAllowedStateEstimation()); + maxAllowedValuesMap.put(BALANCE_ADJUSTEMENT, applicationProps.getDefaultMaxAllowedBalanceAdjustement()); + maxAllowedValuesMap.put(DYNAMIC_SIMULATION, applicationProps.getDefaultMaxAllowedDynamicSimulation()); + maxAllowedValuesMap.put(DYNAMIC_SECURITY, applicationProps.getDefaultMaxAllowedDynamicSecurity()); + maxAllowedValuesMap.put(DYNAMIC_MARGIN, applicationProps.getDefaultMaxAllowedDynamicMargin()); + return maxAllowedValuesMap; + } + @Transactional(readOnly = true) @SuppressWarnings("checkstyle:LambdaBodyLength") public List getProfiles(String userId, boolean checkLinksValidity) { @@ -52,25 +99,25 @@ public List getProfiles(String userId, boolean checkLinksValidity) if (!checkLinksValidity) { return profiles - .stream() - .map(this::toDto) - .toList(); + .stream() + .map(this::toDto) + .toList(); } Set allUuidsInAllProfiles = profiles - .stream() - .flatMap(e -> Stream.of( - e.getLoadFlowParameterId(), - e.getSecurityAnalysisParameterId(), - e.getSensitivityAnalysisParameterId(), - e.getShortcircuitParameterId(), - e.getPccminParameterId(), - e.getVoltageInitParameterId(), - e.getSpreadsheetConfigCollectionId(), - e.getNetworkVisualizationParameterId(), - e.getWorkspaceId())) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); + .stream() + .flatMap(e -> Stream.of( + e.getLoadFlowParameterId(), + e.getSecurityAnalysisParameterId(), + e.getSensitivityAnalysisParameterId(), + e.getShortcircuitParameterId(), + e.getPccminParameterId(), + e.getVoltageInitParameterId(), + e.getSpreadsheetConfigCollectionId(), + e.getNetworkVisualizationParameterId(), + e.getWorkspaceId())) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); Set existingUuids = directoryService.getExistingElements(allUuidsInAllProfiles, userId); // relative complement will be used to check the elements validity (the missing set should be very small) Set missingUuids = Sets.difference(allUuidsInAllProfiles, existingUuids); @@ -121,7 +168,7 @@ public Optional getProfileIfAdmin(UUID profileUuid) { public void updateProfile(UUID profileUuid, UserProfile userProfile) { adminRightService.assertIsAdmin(); UserProfileEntity profile = userProfileRepository.findById(profileUuid) - .orElseThrow(() -> UserAdminException.profileNotFound(profileUuid)); + .orElseThrow(() -> UserAdminException.profileNotFound(profileUuid)); profile.setName(userProfile.getName()); profile.setLoadFlowParameterId(userProfile.getLoadFlowParameterId()); profile.setSecurityAnalysisParameterId(userProfile.getSecurityAnalysisParameterId()); @@ -129,19 +176,19 @@ public void updateProfile(UUID profileUuid, UserProfile userProfile) { profile.setShortcircuitParameterId(userProfile.getShortcircuitParameterId()); profile.setPccminParameterId(userProfile.getPccMinParameterId()); profile.setVoltageInitParameterId(userProfile.getVoltageInitParameterId()); - profile.setMaxAllowedCases(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_CASES)); - profile.setMaxAllowedBuilds(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_BUILD)); - profile.setMaxAllowedLoadflow(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_LOADFLOW)); - profile.setMaxAllowedSecurity(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_SECURITY)); - profile.setMaxAllowedSensitivity(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_SENSITIVITY)); - profile.setMaxAllowedShortCircuit(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_SHORT_CIRCUIT)); - profile.setMaxAllowedVoltageInit(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_VOLTAGE_INIT)); - profile.setMaxAllowedPccMin(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_PCC_MIN)); - profile.setMaxAllowedStateEstimation(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_STATE_ESTIMATION)); - profile.setMaxAllowedBalanceAdjustement(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_BALANCE_ADJUSTEMENT)); - profile.setMaxAllowedDynamicSimulation(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_DYNAMIC_SIMULATION)); - profile.setMaxAllowedDynamicSecurity(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_DYNAMIC_SECURITY)); - profile.setMaxAllowedDynamicMargin(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_DYNAMIC_MARGIN)); + profile.setMaxAllowedCases(userProfile.getMaxOperationQuota(OperationType.CASES)); + profile.setMaxAllowedBuilds(userProfile.getMaxOperationQuota(OperationType.BUILD)); + profile.setMaxAllowedLoadflow(userProfile.getMaxOperationQuota(OperationType.LOAD_FLOW)); + profile.setMaxAllowedSecurity(userProfile.getMaxOperationQuota(OperationType.SECURITY)); + profile.setMaxAllowedSensitivity(userProfile.getMaxOperationQuota(OperationType.SENSITIVITY)); + profile.setMaxAllowedShortCircuit(userProfile.getMaxOperationQuota(OperationType.SHORT_CIRCUIT)); + profile.setMaxAllowedVoltageInit(userProfile.getMaxOperationQuota(OperationType.VOLTAGE_INIT)); + profile.setMaxAllowedPccMin(userProfile.getMaxOperationQuota(OperationType.PCC_MIN)); + profile.setMaxAllowedStateEstimation(userProfile.getMaxOperationQuota(OperationType.STATE_ESTIMATION)); + profile.setMaxAllowedBalanceAdjustement(userProfile.getMaxOperationQuota(OperationType.BALANCE_ADJUSTEMENT)); + profile.setMaxAllowedDynamicSimulation(userProfile.getMaxOperationQuota(OperationType.DYNAMIC_SIMULATION)); + profile.setMaxAllowedDynamicSecurity(userProfile.getMaxOperationQuota(OperationType.DYNAMIC_SECURITY)); + profile.setMaxAllowedDynamicMargin(userProfile.getMaxOperationQuota(OperationType.DYNAMIC_MARGIN)); profile.setSpreadsheetConfigCollectionId(userProfile.getSpreadsheetConfigCollectionId()); profile.setNetworkVisualizationParameterId(userProfile.getNetworkVisualizationParameterId()); profile.setWorkspaceId(userProfile.getWorkspaceId()); @@ -175,20 +222,21 @@ private UserProfile toDto(final UserProfileEntity entity, Boolean allLinksValid) if (entity == null) { return null; } - Map maxAllowedValues = new HashMap<>(); - maxAllowedValues.put(MAX_ALLOWED_CASES, entity.getMaxAllowedCases()); - maxAllowedValues.put(MAX_ALLOWED_BUILD, entity.getMaxAllowedBuilds()); - maxAllowedValues.put(MAX_ALLOWED_LOADFLOW, entity.getMaxAllowedLoadflow()); - maxAllowedValues.put(MAX_ALLOWED_SECURITY, entity.getMaxAllowedSecurity()); - maxAllowedValues.put(MAX_ALLOWED_SENSITIVITY, entity.getMaxAllowedSensitivity()); - maxAllowedValues.put(MAX_ALLOWED_SHORT_CIRCUIT, entity.getMaxAllowedShortCircuit()); - maxAllowedValues.put(MAX_ALLOWED_VOLTAGE_INIT, entity.getMaxAllowedVoltageInit()); - maxAllowedValues.put(MAX_ALLOWED_PCC_MIN, entity.getMaxAllowedPccMin()); - maxAllowedValues.put(MAX_ALLOWED_STATE_ESTIMATION, entity.getMaxAllowedStateEstimation()); - maxAllowedValues.put(MAX_ALLOWED_BALANCE_ADJUSTEMENT, entity.getMaxAllowedBalanceAdjustement()); - maxAllowedValues.put(MAX_ALLOWED_DYNAMIC_SIMULATION, entity.getMaxAllowedDynamicSimulation()); - maxAllowedValues.put(MAX_ALLOWED_DYNAMIC_SECURITY, entity.getMaxAllowedDynamicSecurity()); - maxAllowedValues.put(MAX_ALLOWED_DYNAMIC_MARGIN, entity.getMaxAllowedDynamicMargin()); + Map maxAllowedValues = new EnumMap<>(OperationType.class); + maxAllowedValues.put(OperationType.CASES, entity.getMaxAllowedCases()); + maxAllowedValues.put(OperationType.BUILD, entity.getMaxAllowedBuilds()); + maxAllowedValues.put(OperationType.LOAD_FLOW, entity.getMaxAllowedLoadflow()); + maxAllowedValues.put(OperationType.SECURITY, entity.getMaxAllowedSecurity()); + maxAllowedValues.put(OperationType.SENSITIVITY, entity.getMaxAllowedSensitivity()); + maxAllowedValues.put(OperationType.SHORT_CIRCUIT, entity.getMaxAllowedShortCircuit()); + maxAllowedValues.put(OperationType.VOLTAGE_INIT, entity.getMaxAllowedVoltageInit()); + maxAllowedValues.put(OperationType.PCC_MIN, entity.getMaxAllowedPccMin()); + maxAllowedValues.put(OperationType.STATE_ESTIMATION, entity.getMaxAllowedStateEstimation()); + maxAllowedValues.put(OperationType.BALANCE_ADJUSTEMENT, entity.getMaxAllowedBalanceAdjustement()); + maxAllowedValues.put(OperationType.DYNAMIC_SIMULATION, entity.getMaxAllowedDynamicSimulation()); + maxAllowedValues.put(OperationType.DYNAMIC_SECURITY, entity.getMaxAllowedDynamicSecurity()); + maxAllowedValues.put(OperationType.DYNAMIC_MARGIN, entity.getMaxAllowedDynamicMargin()); + return UserProfile.builder() .id(entity.getId()) .name(entity.getName()) @@ -199,7 +247,7 @@ private UserProfile toDto(final UserProfileEntity entity, Boolean allLinksValid) .pccMinParameterId(entity.getPccminParameterId()) .voltageInitParameterId(entity.getVoltageInitParameterId()) .allLinksValid(allLinksValid) - .maxAllowValuesMap(maxAllowedValues) + .maxOperationQuota(maxAllowedValues) .spreadsheetConfigCollectionId(entity.getSpreadsheetConfigCollectionId()) .networkVisualizationParameterId(entity.getNetworkVisualizationParameterId()) .workspaceId(entity.getWorkspaceId()) @@ -209,30 +257,30 @@ private UserProfile toDto(final UserProfileEntity entity, Boolean allLinksValid) private UserProfileEntity toEntity(final UserProfile userProfile) { Objects.requireNonNull(userProfile); return new UserProfileEntity( - UUID.randomUUID(), - userProfile.getName(), - userProfile.getLoadFlowParameterId(), - userProfile.getSecurityAnalysisParameterId(), - userProfile.getSensitivityAnalysisParameterId(), - userProfile.getShortcircuitParameterId(), - userProfile.getPccMinParameterId(), - userProfile.getVoltageInitParameterId(), - Optional.ofNullable(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_CASES)).orElse(applicationProps.getDefaultMaxAllowedCases()), - Optional.ofNullable(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_BUILD)).orElse(applicationProps.getDefaultMaxAllowedBuilds()), - Optional.ofNullable(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_LOADFLOW)).orElse(applicationProps.getDefaultMaxAllowedLoadflow()), - Optional.ofNullable(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_SECURITY)).orElse(applicationProps.getDefaultMaxAllowedSecurity()), - Optional.ofNullable(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_SENSITIVITY)).orElse(applicationProps.getDefaultMaxAllowedSensitivity()), - Optional.ofNullable(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_SHORT_CIRCUIT)).orElse(applicationProps.getDefaultMaxAllowedShortCircuit()), - Optional.ofNullable(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_VOLTAGE_INIT)).orElse(applicationProps.getDefaultMaxAllowedVoltageInit()), - Optional.ofNullable(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_PCC_MIN)).orElse(applicationProps.getDefaultMaxAllowedPccMin()), - Optional.ofNullable(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_STATE_ESTIMATION)).orElse(applicationProps.getDefaultMaxAllowedStateEstimation()), - Optional.ofNullable(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_BALANCE_ADJUSTEMENT)).orElse(applicationProps.getDefaultMaxAllowedBalanceAdjustement()), - Optional.ofNullable(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_DYNAMIC_SIMULATION)).orElse(applicationProps.getDefaultMaxAllowedDynamicSimulation()), - Optional.ofNullable(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_DYNAMIC_SECURITY)).orElse(applicationProps.getDefaultMaxAllowedDynamicSecurity()), - Optional.ofNullable(userProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_DYNAMIC_MARGIN)).orElse(applicationProps.getDefaultMaxAllowedDynamicMargin()), - userProfile.getSpreadsheetConfigCollectionId(), - userProfile.getNetworkVisualizationParameterId(), - userProfile.getWorkspaceId() + UUID.randomUUID(), + userProfile.getName(), + userProfile.getLoadFlowParameterId(), + userProfile.getSecurityAnalysisParameterId(), + userProfile.getSensitivityAnalysisParameterId(), + userProfile.getShortcircuitParameterId(), + userProfile.getPccMinParameterId(), + userProfile.getVoltageInitParameterId(), + Optional.ofNullable(userProfile.getMaxOperationQuota(OperationType.CASES)).orElse(applicationProps.getDefaultMaxAllowedCases()), + Optional.ofNullable(userProfile.getMaxOperationQuota(OperationType.BUILD)).orElse(applicationProps.getDefaultMaxAllowedBuilds()), + Optional.ofNullable(userProfile.getMaxOperationQuota(OperationType.LOAD_FLOW)).orElse(applicationProps.getDefaultMaxAllowedLoadflow()), + Optional.ofNullable(userProfile.getMaxOperationQuota(OperationType.SECURITY)).orElse(applicationProps.getDefaultMaxAllowedSecurity()), + Optional.ofNullable(userProfile.getMaxOperationQuota(OperationType.SENSITIVITY)).orElse(applicationProps.getDefaultMaxAllowedSensitivity()), + Optional.ofNullable(userProfile.getMaxOperationQuota(OperationType.SHORT_CIRCUIT)).orElse(applicationProps.getDefaultMaxAllowedShortCircuit()), + Optional.ofNullable(userProfile.getMaxOperationQuota(OperationType.VOLTAGE_INIT)).orElse(applicationProps.getDefaultMaxAllowedVoltageInit()), + Optional.ofNullable(userProfile.getMaxOperationQuota(OperationType.PCC_MIN)).orElse(applicationProps.getDefaultMaxAllowedPccMin()), + Optional.ofNullable(userProfile.getMaxOperationQuota(OperationType.STATE_ESTIMATION)).orElse(applicationProps.getDefaultMaxAllowedStateEstimation()), + Optional.ofNullable(userProfile.getMaxOperationQuota(OperationType.BALANCE_ADJUSTEMENT)).orElse(applicationProps.getDefaultMaxAllowedBalanceAdjustement()), + Optional.ofNullable(userProfile.getMaxOperationQuota(OperationType.DYNAMIC_SIMULATION)).orElse(applicationProps.getDefaultMaxAllowedDynamicSimulation()), + Optional.ofNullable(userProfile.getMaxOperationQuota(OperationType.DYNAMIC_SECURITY)).orElse(applicationProps.getDefaultMaxAllowedDynamicSecurity()), + Optional.ofNullable(userProfile.getMaxOperationQuota(OperationType.DYNAMIC_MARGIN)).orElse(applicationProps.getDefaultMaxAllowedDynamicMargin()), + userProfile.getSpreadsheetConfigCollectionId(), + userProfile.getNetworkVisualizationParameterId(), + userProfile.getWorkspaceId() ); } } diff --git a/src/main/java/org/gridsuite/useradmin/server/service/UserQuotaService.java b/src/main/java/org/gridsuite/useradmin/server/service/UserQuotaService.java new file mode 100644 index 0000000..eec53a9 --- /dev/null +++ b/src/main/java/org/gridsuite/useradmin/server/service/UserQuotaService.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2026, 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.service; + +import org.gridsuite.useradmin.server.UserAdminApplicationProps; +import org.gridsuite.useradmin.server.dto.OperationType; +import org.gridsuite.useradmin.server.dto.UserProfile; +import org.gridsuite.useradmin.server.entity.UserInfosEntity; +import org.gridsuite.useradmin.server.entity.UserOperationEntity; +import org.gridsuite.useradmin.server.error.UserAdminException; +import org.gridsuite.useradmin.server.repository.UserInfosRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; + +import static org.gridsuite.useradmin.server.dto.OperationType.*; + +/** + * @author Ghiles Abdellah {@literal } + */ +@Service +public class UserQuotaService { + private final UserInfosRepository userInfosRepository; + private final AdminRightService adminRightService; + private final UserProfileService userProfileService; + + private final UserAdminApplicationProps applicationProps; + + public UserQuotaService(final UserInfosRepository userInfosRepository, + final AdminRightService adminRightService, + final UserProfileService userProfileService, + final UserAdminApplicationProps applicationProps) { + this.userInfosRepository = Objects.requireNonNull(userInfosRepository); + this.adminRightService = Objects.requireNonNull(adminRightService); + this.userProfileService = Objects.requireNonNull(userProfileService); + this.applicationProps = Objects.requireNonNull(applicationProps); + } + + @Transactional(readOnly = true) + public Map getUserMaxQuota(String sub) { + UserProfile profile = userProfileService.doGetUserProfile(sub); + return Optional.ofNullable(profile.getMaxOperationQuota()) + .orElse(userProfileService.getDefaultMaxAllowedValues()); + } + + @Transactional() + public void resetUserCurrentQuotaUsage(String sub) { + adminRightService.assertIsAdmin(); + Optional userOpt = userInfosRepository.findBySub(sub); + + UserInfosEntity userInfosEntity = userOpt.orElseThrow(() -> UserAdminException.userNotFound(sub)); + userInfosEntity.getUserOperations().clear(); + userInfosRepository.save(userInfosEntity); + } + + @Deprecated + @Transactional(readOnly = true) + public Integer getUserProfileMaxAllowedCases(String sub) { + return Optional.ofNullable(getUserMaxQuota(sub).get(CASES)) + .orElse(applicationProps.getDefaultMaxAllowedCases()); + } + + @Deprecated + @Transactional(readOnly = true) + public Integer getUserProfileMaxAllowedBuilds(String sub) { + return Optional.ofNullable(getUserMaxQuota(sub).get(BUILD)) + .orElse(applicationProps.getDefaultMaxAllowedBuilds()); + } + + @Transactional(readOnly = true) + public Map getUserCurrentQuotaUsage(String sub) { + Optional userOpt = userInfosRepository.findBySub(sub); + + UserInfosEntity userInfosEntity = userOpt.orElseThrow(() -> UserAdminException.userNotFound(sub)); + return userInfosEntity.getQuotaUsageMap(); + } + + @Transactional() + public void startUserOperation(String sub, OperationType operation, UUID operationId) { + Optional userOpt = userInfosRepository.findBySub(sub); + + UserInfosEntity userInfosEntity = userOpt.orElseThrow(() -> UserAdminException.userNotFound(sub)); + userInfosEntity.getUserOperations().add(new UserOperationEntity(userInfosEntity, operationId, operation)); + userInfosRepository.save(userInfosEntity); + } + + @Transactional() + public void endUserOperation(String sub, OperationType operation, UUID operationId) { + Optional userOpt = userInfosRepository.findBySub(sub); + + UserInfosEntity userInfosEntity = userOpt.orElseThrow(() -> UserAdminException.userNotFound(sub)); + userInfosEntity.getUserOperations().removeIf(userOperationEntity -> userOperationEntity.getOperationId().equals(operationId) && userOperationEntity.getOperationType().equals(operation)); + userInfosRepository.save(userInfosEntity); + } +} diff --git a/src/main/resources/db/changelog/changesets/changelog_20260707T085013Z.xml b/src/main/resources/db/changelog/changesets/changelog_20260707T085013Z.xml new file mode 100644 index 0000000..e055f66 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/changelog_20260707T085013Z.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 02bbed9..90f2f54 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -47,3 +47,6 @@ databaseChangeLog: - include: file: changesets/changelog_20260616T125854Z.xml relativeToChangelogFile: true + - include: + file: changesets/changelog_20260707T085013Z.xml + relativeToChangelogFile: true diff --git a/src/test/java/org/gridsuite/useradmin/server/DtoConverterTest.java b/src/test/java/org/gridsuite/useradmin/server/DtoConverterTest.java index 3a5d5e4..026d11b 100644 --- a/src/test/java/org/gridsuite/useradmin/server/DtoConverterTest.java +++ b/src/test/java/org/gridsuite/useradmin/server/DtoConverterTest.java @@ -22,7 +22,7 @@ void testConversionToDtoOfUserInfos() { final UUID uuid = UUID.randomUUID(); // no profile and no group - assertThat(UserInfosEntity.toDto(new UserInfosEntity(uuid, "sub_user", null, null))) + assertThat(UserInfosEntity.toDto(new UserInfosEntity(uuid, "sub_user", null, null, null))) .as("dto result") .isEqualTo(new UserInfos("sub_user", null, null, null, null, null, null, null, null, null, null, null, null, null, null, @@ -33,13 +33,13 @@ void testConversionToDtoOfUserInfos() { null, null, null, null, null, null, null, null, null); // Test mapping without quota - assertThat(UserInfosEntity.toDto(new UserInfosEntity(uuid, "sub_user", profile, null))) + assertThat(UserInfosEntity.toDto(new UserInfosEntity(uuid, "sub_user", profile, null, null))) .as("dto result") .isEqualTo(new UserInfos("sub_user", null, null, "a profile", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null)); // Test mapping with quota - assertThat(UserInfosEntity.toDtoWithDetail(new UserInfosEntity(uuid, "sub_user", profile, null), 5, 2, 6, null, null, null, + assertThat(UserInfosEntity.toDtoWithDetail(new UserInfosEntity(uuid, "sub_user", profile, null, null), 5, 2, 6, null, null, null, null, null, null, null, null, null, null, null)) .as("dto result") @@ -51,13 +51,13 @@ void testConversionToDtoOfUserInfos() { GroupInfosEntity group1 = new GroupInfosEntity(UUID.randomUUID(), "group1", Set.of()); GroupInfosEntity group2 = new GroupInfosEntity(UUID.randomUUID(), "group2", Set.of()); // Test mapping without quota - assertThat(UserInfosEntity.toDto(new UserInfosEntity(uuid, "sub_user", profile, Set.of(group1, group2)))) + assertThat(UserInfosEntity.toDto(new UserInfosEntity(uuid, "sub_user", profile, null, Set.of(group1, group2)))) .as("dto result") .isEqualTo(new UserInfos("sub_user", null, null, "a profile", null, null, null, null, null, null, null, null, null, null, null, null, null, null, Set.of("group1", "group2"))); // Test mapping with quota - assertThat(UserInfosEntity.toDtoWithDetail(new UserInfosEntity(uuid, "sub_user", profile, Set.of(group1, group2)), 5, 2, 6, null, null, null, + assertThat(UserInfosEntity.toDtoWithDetail(new UserInfosEntity(uuid, "sub_user", profile, null, Set.of(group1, group2)), 5, 2, 6, null, null, null, null, null, null, null, null, null, null, null)) .as("dto result") diff --git a/src/test/java/org/gridsuite/useradmin/server/NoQuotaTest.java b/src/test/java/org/gridsuite/useradmin/server/NoQuotaTest.java index d0bc3d2..4b8ab44 100644 --- a/src/test/java/org/gridsuite/useradmin/server/NoQuotaTest.java +++ b/src/test/java/org/gridsuite/useradmin/server/NoQuotaTest.java @@ -6,8 +6,10 @@ */ package org.gridsuite.useradmin.server; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; +import org.gridsuite.useradmin.server.dto.OperationType; import org.gridsuite.useradmin.server.dto.UserInfos; import org.gridsuite.useradmin.server.dto.UserProfile; import org.gridsuite.useradmin.server.entity.UserProfileEntity; @@ -24,12 +26,13 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; +import java.util.EnumMap; import java.util.HashMap; import java.util.Map; import java.util.Optional; import static org.gridsuite.useradmin.server.Utils.ROLES_HEADER; -import static org.gridsuite.useradmin.server.dto.UserProfile.*; +import static org.gridsuite.useradmin.server.dto.OperationType.*; import static org.gridsuite.useradmin.server.utils.TestConstants.*; import static org.junit.jupiter.api.Assertions.*; import static org.springframework.http.MediaType.APPLICATION_JSON; @@ -82,7 +85,7 @@ void cleanDB() { void testProfileCreation() throws Exception { createProfile(); // test with quotas - createProfile(PROFILE_TWO, createMaxAllowedCases()); + createProfile(PROFILE_TWO, getMaxAllowedCases()); } @Test @@ -90,7 +93,7 @@ void testUserCreationWithoutProfile() throws Exception { createUser(USER_SUB); assertTrue(getMaxAllowedBuilds(USER_SUB).isEmpty()); - assertTrue(createMaxAllowedCases(USER_SUB).isEmpty()); + assertTrue(getMaxAllowedCases(USER_SUB).isEmpty()); } @Test @@ -100,57 +103,56 @@ void testUserCreationWithProfile() throws Exception { createUser(USER_SUB); associateProfileToUser(USER_SUB, PROFILE_ONE); + // noquota profile sets all defaults to null, so every quota value is null assertTrue(getMaxAllowedBuilds(USER_SUB).isEmpty()); - assertTrue(createMaxAllowedCases(USER_SUB).isEmpty()); - assertTrue(getMaxAllowedLoadflow(USER_SUB).isEmpty()); - assertTrue(getMaxAllowedSecurity(USER_SUB).isEmpty()); - assertTrue(getMaxAllowedSensitivity(USER_SUB).isEmpty()); - assertTrue(getMaxAllowedShortCircuit(USER_SUB).isEmpty()); - assertTrue(getMaxAllowedVoltageInit(USER_SUB).isEmpty()); - assertTrue(getMaxAllowedPccMin(USER_SUB).isEmpty()); - assertTrue(getMaxAllowedStateEstimation(USER_SUB).isEmpty()); - assertTrue(getMaxAllowedBalanceAdjustement(USER_SUB).isEmpty()); - assertTrue(getMaxAllowedDynamicSimulation(USER_SUB).isEmpty()); - assertTrue(getMaxAllowedDynamicSecurity(USER_SUB).isEmpty()); - assertTrue(getMaxAllowedDynamicMargin(USER_SUB).isEmpty()); - - Map maxAllowedCases = createMaxAllowedCases(); + assertTrue(getMaxAllowedCases(USER_SUB).isEmpty()); + + // check all types via the new unified /quota/max endpoint + Map quota = getMaxQuotaMap(USER_SUB); + assertTrue(quota.values().stream().allMatch(v -> v == null)); + + Map maxAllowedCases = getMaxAllowedCases(); //profile with quotas createProfile(PROFILE_TWO, maxAllowedCases); createUser(USER_SUB_TWO); associateProfileToUser(USER_SUB_TWO, PROFILE_TWO); - assertEquals("10", createMaxAllowedCases(USER_SUB_TWO)); + assertEquals("10", getMaxAllowedCases(USER_SUB_TWO)); assertEquals("20", getMaxAllowedBuilds(USER_SUB_TWO)); - assertEquals("2", getMaxAllowedLoadflow(USER_SUB_TWO)); - assertEquals("2", getMaxAllowedSecurity(USER_SUB_TWO)); - assertEquals("1", getMaxAllowedSensitivity(USER_SUB_TWO)); - assertEquals("1", getMaxAllowedShortCircuit(USER_SUB_TWO)); - assertEquals("2", getMaxAllowedVoltageInit(USER_SUB_TWO)); - assertEquals("1", getMaxAllowedPccMin(USER_SUB_TWO)); - assertEquals("1", getMaxAllowedStateEstimation(USER_SUB_TWO)); - assertEquals("1", getMaxAllowedBalanceAdjustement(USER_SUB_TWO)); - assertEquals("1", getMaxAllowedDynamicSimulation(USER_SUB_TWO)); - assertEquals("1", getMaxAllowedDynamicSecurity(USER_SUB_TWO)); - assertEquals("1", getMaxAllowedDynamicMargin(USER_SUB_TWO)); + + // check all types via the new unified /quota/max endpoint + Map quotaTwo = getMaxQuotaMap(USER_SUB_TWO); + assertEquals(10, quotaTwo.get(CASES)); + assertEquals(20, quotaTwo.get(BUILD)); + assertEquals(2, quotaTwo.get(LOAD_FLOW)); + assertEquals(2, quotaTwo.get(SECURITY)); + assertEquals(1, quotaTwo.get(SENSITIVITY)); + assertEquals(1, quotaTwo.get(SHORT_CIRCUIT)); + assertEquals(2, quotaTwo.get(VOLTAGE_INIT)); + assertEquals(1, quotaTwo.get(PCC_MIN)); + assertEquals(1, quotaTwo.get(STATE_ESTIMATION)); + assertEquals(1, quotaTwo.get(BALANCE_ADJUSTEMENT)); + assertEquals(1, quotaTwo.get(DYNAMIC_SIMULATION)); + assertEquals(1, quotaTwo.get(DYNAMIC_SECURITY)); + assertEquals(1, quotaTwo.get(DYNAMIC_MARGIN)); } - private static @NonNull Map createMaxAllowedCases() { - Map maxAllowedCases = new HashMap<>(); - maxAllowedCases.put(MAX_ALLOWED_CASES, 10); - maxAllowedCases.put(MAX_ALLOWED_BUILD, 20); - maxAllowedCases.put(MAX_ALLOWED_LOADFLOW, 2); - maxAllowedCases.put(MAX_ALLOWED_SECURITY, 2); - maxAllowedCases.put(MAX_ALLOWED_SENSITIVITY, 1); - maxAllowedCases.put(MAX_ALLOWED_SHORT_CIRCUIT, 1); - maxAllowedCases.put(MAX_ALLOWED_VOLTAGE_INIT, 2); - maxAllowedCases.put(MAX_ALLOWED_PCC_MIN, 1); - maxAllowedCases.put(MAX_ALLOWED_STATE_ESTIMATION, 1); - maxAllowedCases.put(MAX_ALLOWED_BALANCE_ADJUSTEMENT, 1); - maxAllowedCases.put(MAX_ALLOWED_DYNAMIC_SIMULATION, 1); - maxAllowedCases.put(MAX_ALLOWED_DYNAMIC_SECURITY, 1); - maxAllowedCases.put(MAX_ALLOWED_DYNAMIC_MARGIN, 1); + private static @NonNull Map getMaxAllowedCases() { + Map maxAllowedCases = new EnumMap<>(OperationType.class); + maxAllowedCases.put(CASES, 10); + maxAllowedCases.put(BUILD, 20); + maxAllowedCases.put(LOAD_FLOW, 2); + maxAllowedCases.put(SECURITY, 2); + maxAllowedCases.put(SENSITIVITY, 1); + maxAllowedCases.put(SHORT_CIRCUIT, 1); + maxAllowedCases.put(VOLTAGE_INIT, 2); + maxAllowedCases.put(PCC_MIN, 1); + maxAllowedCases.put(STATE_ESTIMATION, 1); + maxAllowedCases.put(BALANCE_ADJUSTEMENT, 1); + maxAllowedCases.put(DYNAMIC_SIMULATION, 1); + maxAllowedCases.put(DYNAMIC_SECURITY, 1); + maxAllowedCases.put(DYNAMIC_MARGIN, 1); return maxAllowedCases; } @@ -158,8 +160,8 @@ private void createProfile() throws Exception { createProfile(NoQuotaTest.PROFILE_ONE, new HashMap<>()); } - private void createProfile(String profileName, Map maxAllowedValues) throws Exception { - UserProfile profileInfo = UserProfile.builder().name(profileName).allLinksValid(false).maxAllowValuesMap(maxAllowedValues).build(); + private void createProfile(String profileName, Map maxAllowedValues) throws Exception { + UserProfile profileInfo = UserProfile.builder().name(profileName).allLinksValid(false).maxOperationQuota(maxAllowedValues).build(); performPost(API_BASE_PATH + "/profiles", profileInfo); Optional createdProfile = userProfileRepository.findByName(profileName); @@ -170,19 +172,19 @@ private void createProfile(String profileName, Map maxAllowedVa assertNull(createdProfile.get().getShortcircuitParameterId()); assertNull(createdProfile.get().getPccminParameterId()); assertNull(createdProfile.get().getVoltageInitParameterId()); - assertEquals(maxAllowedValues.get(MAX_ALLOWED_BUILD), createdProfile.get().getMaxAllowedBuilds()); - assertEquals(maxAllowedValues.get(MAX_ALLOWED_CASES), createdProfile.get().getMaxAllowedCases()); - assertEquals(maxAllowedValues.get(MAX_ALLOWED_LOADFLOW), createdProfile.get().getMaxAllowedLoadflow()); - assertEquals(maxAllowedValues.get(MAX_ALLOWED_SECURITY), createdProfile.get().getMaxAllowedSecurity()); - assertEquals(maxAllowedValues.get(MAX_ALLOWED_SENSITIVITY), createdProfile.get().getMaxAllowedSensitivity()); - assertEquals(maxAllowedValues.get(MAX_ALLOWED_SHORT_CIRCUIT), createdProfile.get().getMaxAllowedShortCircuit()); - assertEquals(maxAllowedValues.get(MAX_ALLOWED_VOLTAGE_INIT), createdProfile.get().getMaxAllowedVoltageInit()); - assertEquals(maxAllowedValues.get(MAX_ALLOWED_PCC_MIN), createdProfile.get().getMaxAllowedPccMin()); - assertEquals(maxAllowedValues.get(MAX_ALLOWED_STATE_ESTIMATION), createdProfile.get().getMaxAllowedStateEstimation()); - assertEquals(maxAllowedValues.get(MAX_ALLOWED_BALANCE_ADJUSTEMENT), createdProfile.get().getMaxAllowedBalanceAdjustement()); - assertEquals(maxAllowedValues.get(MAX_ALLOWED_DYNAMIC_SIMULATION), createdProfile.get().getMaxAllowedDynamicSimulation()); - assertEquals(maxAllowedValues.get(MAX_ALLOWED_DYNAMIC_SECURITY), createdProfile.get().getMaxAllowedDynamicSecurity()); - assertEquals(maxAllowedValues.get(MAX_ALLOWED_DYNAMIC_MARGIN), createdProfile.get().getMaxAllowedDynamicMargin()); + assertEquals(maxAllowedValues.get(BUILD), createdProfile.get().getMaxAllowedBuilds()); + assertEquals(maxAllowedValues.get(CASES), createdProfile.get().getMaxAllowedCases()); + assertEquals(maxAllowedValues.get(LOAD_FLOW), createdProfile.get().getMaxAllowedLoadflow()); + assertEquals(maxAllowedValues.get(SECURITY), createdProfile.get().getMaxAllowedSecurity()); + assertEquals(maxAllowedValues.get(SENSITIVITY), createdProfile.get().getMaxAllowedSensitivity()); + assertEquals(maxAllowedValues.get(SHORT_CIRCUIT), createdProfile.get().getMaxAllowedShortCircuit()); + assertEquals(maxAllowedValues.get(VOLTAGE_INIT), createdProfile.get().getMaxAllowedVoltageInit()); + assertEquals(maxAllowedValues.get(PCC_MIN), createdProfile.get().getMaxAllowedPccMin()); + assertEquals(maxAllowedValues.get(STATE_ESTIMATION), createdProfile.get().getMaxAllowedStateEstimation()); + assertEquals(maxAllowedValues.get(BALANCE_ADJUSTEMENT), createdProfile.get().getMaxAllowedBalanceAdjustement()); + assertEquals(maxAllowedValues.get(DYNAMIC_SIMULATION), createdProfile.get().getMaxAllowedDynamicSimulation()); + assertEquals(maxAllowedValues.get(DYNAMIC_SECURITY), createdProfile.get().getMaxAllowedDynamicSecurity()); + assertEquals(maxAllowedValues.get(DYNAMIC_MARGIN), createdProfile.get().getMaxAllowedDynamicMargin()); assertNull(createdProfile.get().getSpreadsheetConfigCollectionId()); assertNull(createdProfile.get().getNetworkVisualizationParameterId()); } @@ -211,58 +213,19 @@ private void associateProfileToUser(String userSub, String profileName) throws E performPut(API_BASE_PATH + "/users/" + userSub, userInfos); } + private Map getMaxQuotaMap(String userSub) throws Exception { + MvcResult result = performGet(API_BASE_PATH + "/users/" + userSub + "/quota/max"); + return objectMapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() { }); + } + private String getMaxAllowedBuilds(String userSub) throws Exception { return getMaxAllowedElement(userSub, "max-builds"); } - private String createMaxAllowedCases(String userSub) throws Exception { + private String getMaxAllowedCases(String userSub) throws Exception { return getMaxAllowedElement(userSub, "max-cases"); } - private String getMaxAllowedLoadflow(String userSub) throws Exception { - return getMaxAllowedElement(userSub, "max-loadflow"); - } - - private String getMaxAllowedSecurity(String userSub) throws Exception { - return getMaxAllowedElement(userSub, "max-security"); - } - - private String getMaxAllowedSensitivity(String userSub) throws Exception { - return getMaxAllowedElement(userSub, "max-sensitivity"); - } - - private String getMaxAllowedShortCircuit(String userSub) throws Exception { - return getMaxAllowedElement(userSub, "max-shortcircuit"); - } - - private String getMaxAllowedVoltageInit(String userSub) throws Exception { - return getMaxAllowedElement(userSub, "max-voltage-init"); - } - - private String getMaxAllowedPccMin(String userSub) throws Exception { - return getMaxAllowedElement(userSub, "max-pcc-min"); - } - - private String getMaxAllowedStateEstimation(String userSub) throws Exception { - return getMaxAllowedElement(userSub, "max-state-estimation"); - } - - private String getMaxAllowedBalanceAdjustement(String userSub) throws Exception { - return getMaxAllowedElement(userSub, "max-balance-adjustement"); - } - - private String getMaxAllowedDynamicSimulation(String userSub) throws Exception { - return getMaxAllowedElement(userSub, "max-dynamic-simulation"); - } - - private String getMaxAllowedDynamicSecurity(String userSub) throws Exception { - return getMaxAllowedElement(userSub, "max-dynamic-security"); - } - - private String getMaxAllowedDynamicMargin(String userSub) throws Exception { - return getMaxAllowedElement(userSub, "max-dynamic-margin"); - } - private String getMaxAllowedElement(String userSub, String elementName) throws Exception { MvcResult result = performGet(API_BASE_PATH + "/users/" + userSub + "/profile/" + elementName); return result.getResponse().getContentAsString(); diff --git a/src/test/java/org/gridsuite/useradmin/server/UserAdminTest.java b/src/test/java/org/gridsuite/useradmin/server/UserAdminTest.java index f228253..bb39ef1 100644 --- a/src/test/java/org/gridsuite/useradmin/server/UserAdminTest.java +++ b/src/test/java/org/gridsuite/useradmin/server/UserAdminTest.java @@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import org.apache.commons.collections4.CollectionUtils; +import org.gridsuite.useradmin.server.dto.OperationType; import org.gridsuite.useradmin.server.dto.UserGroup; import org.gridsuite.useradmin.server.dto.UserInfos; import org.gridsuite.useradmin.server.dto.UserProfile; @@ -37,7 +38,6 @@ import java.util.Set; import static org.gridsuite.useradmin.server.Utils.ROLES_HEADER; -import static org.gridsuite.useradmin.server.dto.UserProfile.*; import static org.gridsuite.useradmin.server.utils.TestConstants.*; import static org.junit.jupiter.api.Assertions.*; import static org.springframework.http.MediaType.APPLICATION_JSON; @@ -273,8 +273,8 @@ void testGetUserProfileNotFound() throws Exception { assertNotNull(profile); assertEquals(UserProfile.DEFAULT_PROFILE_NAME, profile.getName()); assertNull(profile.getId()); - assertEquals(userAdminApplicationProps.getDefaultMaxAllowedCases(), profile.getMaxAllowValuesMap().get(MAX_ALLOWED_CASES)); - assertEquals(userAdminApplicationProps.getDefaultMaxAllowedBuilds(), profile.getMaxAllowValuesMap().get(MAX_ALLOWED_BUILD)); + assertEquals(userAdminApplicationProps.getDefaultMaxAllowedCases(), profile.getMaxOperationQuota().get(OperationType.CASES)); + assertEquals(userAdminApplicationProps.getDefaultMaxAllowedBuilds(), profile.getMaxOperationQuota().get(OperationType.BUILD)); assertNull(profile.getLoadFlowParameterId()); assertNull(profile.getSecurityAnalysisParameterId()); assertNull(profile.getSensitivityAnalysisParameterId()); @@ -387,7 +387,7 @@ private void createUser(String userName) throws Exception { private void createProfile() throws Exception { ObjectWriter objectWriter = objectMapper.writer().withDefaultPrettyPrinter(); - UserProfile profileInfo = UserProfile.builder().name(UserAdminTest.PROFILE_1).allLinksValid(false).maxAllowValuesMap(new HashMap<>()).build(); + UserProfile profileInfo = UserProfile.builder().name(UserAdminTest.PROFILE_1).allLinksValid(false).maxOperationQuota(new HashMap<>()).build(); mockMvc.perform(post("/" + UserAdminApi.API_VERSION + "/profiles") .content(objectWriter.writeValueAsString(profileInfo)) .contentType(MediaType.APPLICATION_JSON) diff --git a/src/test/java/org/gridsuite/useradmin/server/UserIdentityIntegrationTest.java b/src/test/java/org/gridsuite/useradmin/server/UserIdentityIntegrationTest.java index 279f4e1..12db535 100644 --- a/src/test/java/org/gridsuite/useradmin/server/UserIdentityIntegrationTest.java +++ b/src/test/java/org/gridsuite/useradmin/server/UserIdentityIntegrationTest.java @@ -103,8 +103,8 @@ void cleanDB() { @Test void testGetUsersWithIdentityEnrichment() throws Exception { // Create users in database - userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_SUB_1, null, null)); - userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_SUB_2, null, null)); + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_SUB_1, null, null, null)); + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_SUB_2, null, null, null)); // Mock user-identity-server response UserIdentity identity1 = new UserIdentity(USER_SUB_1, "John", "Doe"); @@ -152,7 +152,7 @@ void testGetUsersWithIdentityEnrichment() throws Exception { @Test void testGetUserWithIdentityEnrichment() throws Exception { // Create user in database - userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_SUB_1, null, null)); + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_SUB_1, null, null, null)); // Mock user-identity-server response for single user UserIdentity identity = new UserIdentity(USER_SUB_1, "John", "Doe"); @@ -181,7 +181,7 @@ void testGetUserWithIdentityEnrichment() throws Exception { @Test void testGetUsersWhenIdentityServiceFails() throws Exception { // Create user in database - userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_SUB_1, null, null)); + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_SUB_1, null, null, null)); // Mock user-identity-server to return 500 error wireMockServer.stubFor(WireMock.get(urlPathEqualTo("/v1/users/identities")) @@ -207,7 +207,7 @@ void testGetUsersWhenIdentityServiceFails() throws Exception { @Test void testGetUserWhenIdentityServiceReturns404() throws Exception { // Create user in database - userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_SUB_1, null, null)); + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_SUB_1, null, null, null)); // Mock user-identity-server to return 404 (user not found in identity service) wireMockServer.stubFor(WireMock.get(urlEqualTo("/v1/users/identities/" + USER_SUB_1)) diff --git a/src/test/java/org/gridsuite/useradmin/server/UserProfileTest.java b/src/test/java/org/gridsuite/useradmin/server/UserProfileTest.java index 782faa2..08bdeeb 100644 --- a/src/test/java/org/gridsuite/useradmin/server/UserProfileTest.java +++ b/src/test/java/org/gridsuite/useradmin/server/UserProfileTest.java @@ -13,6 +13,7 @@ import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.matching.StringValuePattern; import org.gridsuite.useradmin.server.dto.ElementAttributes; +import org.gridsuite.useradmin.server.dto.OperationType; import org.gridsuite.useradmin.server.dto.UserProfile; import org.gridsuite.useradmin.server.entity.UserInfosEntity; import org.gridsuite.useradmin.server.entity.UserProfileEntity; @@ -38,8 +39,7 @@ import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.gridsuite.useradmin.server.Utils.ROLES_HEADER; -import static org.gridsuite.useradmin.server.dto.UserProfile.MAX_ALLOWED_BUILD; -import static org.gridsuite.useradmin.server.dto.UserProfile.MAX_ALLOWED_CASES; +import static org.gridsuite.useradmin.server.dto.OperationType.*; import static org.gridsuite.useradmin.server.utils.TestConstants.USER_ADMIN_ROLE; import static org.junit.jupiter.api.Assertions.*; import static org.springframework.http.MediaType.APPLICATION_JSON; @@ -120,8 +120,8 @@ void testCreateProfile() throws Exception { assertNull(userProfiles.getFirst().getPccMinParameterId()); assertNull(userProfiles.getFirst().getVoltageInitParameterId()); assertNull(userProfiles.getFirst().getAllLinksValid()); - assertEquals(10, userProfiles.getFirst().getMaxAllowValuesMap().get(MAX_ALLOWED_CASES)); - assertEquals(15, userProfiles.getFirst().getMaxAllowValuesMap().get(MAX_ALLOWED_BUILD)); + assertEquals(10, userProfiles.getFirst().getMaxOperationQuota().get(CASES)); + assertEquals(15, userProfiles.getFirst().getMaxOperationQuota().get(BUILD)); assertNull(userProfiles.getFirst().getSpreadsheetConfigCollectionId()); assertNull(userProfiles.getFirst().getNetworkVisualizationParameterId()); @@ -177,7 +177,7 @@ void testGetProfileMaxAllowedCases() throws Exception { UserProfileEntity userProfileEntity = new UserProfileEntity(UUID.randomUUID(), "profileName", null, null, null, null, null, null, 15, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); - UserInfosEntity userInfosEntity = new UserInfosEntity(UUID.randomUUID(), ADMIN_USER, userProfileEntity, null); + UserInfosEntity userInfosEntity = new UserInfosEntity(UUID.randomUUID(), ADMIN_USER, userProfileEntity, null, null); userProfileRepository.save(userProfileEntity); userInfosRepository.save(userInfosEntity); @@ -197,7 +197,7 @@ void testGetProfileMaxAllowedBuilds() throws Exception { null, null, null, null, null, null, null, null, null, null, null, null, null, null); - UserInfosEntity userInfosEntity = new UserInfosEntity(UUID.randomUUID(), ADMIN_USER, userProfileEntity, null); + UserInfosEntity userInfosEntity = new UserInfosEntity(UUID.randomUUID(), ADMIN_USER, userProfileEntity, null, null); userProfileRepository.save(userProfileEntity); userInfosRepository.save(userInfosEntity); @@ -212,7 +212,7 @@ void testGetProfileMaxAllowedBuilds() throws Exception { @Test void testGetProfileMaxAllowedCasesWithNoProfileSet() throws Exception { - UserInfosEntity userInfosEntity = new UserInfosEntity(UUID.randomUUID(), ADMIN_USER, null, null); + UserInfosEntity userInfosEntity = new UserInfosEntity(UUID.randomUUID(), ADMIN_USER, null, null, null); userInfosRepository.save(userInfosEntity); MvcResult result = mockMvc.perform(get("/" + UserAdminApi.API_VERSION + "/users/{sub}/profile/max-cases", ADMIN_USER) @@ -227,7 +227,7 @@ void testGetProfileMaxAllowedCasesWithNoProfileSet() throws Exception { @Test void testGetProfileMaxAllowedBuildsWithNoProfileSet() throws Exception { - UserInfosEntity userInfosEntity = new UserInfosEntity(UUID.randomUUID(), ADMIN_USER, null, null); + UserInfosEntity userInfosEntity = new UserInfosEntity(UUID.randomUUID(), ADMIN_USER, null, null, null); userInfosRepository.save(userInfosEntity); MvcResult result = mockMvc.perform(get("/" + UserAdminApi.API_VERSION + "/users/{sub}/profile/max-builds", ADMIN_USER) @@ -275,9 +275,9 @@ private void updateProfile(boolean validParameters) throws Exception { UUID profileUuid = createProfile(PROFILE_1, ADMIN_USER, USER_ADMIN_ROLE, null, 0, HttpStatus.CREATED); // udpate the profile: change name and set its parameters, maxAllowedCases, maxAllowedBuilds and spreadsheet config collection - Map maxAllowedValues = new HashMap<>(); - maxAllowedValues.put(MAX_ALLOWED_CASES, 10); - maxAllowedValues.put(MAX_ALLOWED_BUILD, 11); + Map maxAllowedValues = new EnumMap<>(OperationType.class); + maxAllowedValues.put(CASES, 10); + maxAllowedValues.put(BUILD, 11); UserProfile userProfile = UserProfile.builder() .id(profileUuid).name(PROFILE_2) .loadFlowParameterId(loadFlowParametersUuid) @@ -286,7 +286,7 @@ private void updateProfile(boolean validParameters) throws Exception { .shortcircuitParameterId(shortcircuitParametersUuid) .pccMinParameterId(pccminParametersUuid) .voltageInitParameterId(voltageInitParametersUuid) - .maxAllowValuesMap(maxAllowedValues) + .maxOperationQuota(maxAllowedValues) .spreadsheetConfigCollectionId(spreadsheetConfigCollectionUuid) .networkVisualizationParameterId(networkVisualizationParametersUuid) .workspaceId(diagramConfigUuid) @@ -304,8 +304,8 @@ private void updateProfile(boolean validParameters) throws Exception { assertEquals(pccminParametersUuid, userProfiles.get(0).getPccMinParameterId()); assertEquals(voltageInitParametersUuid, userProfiles.get(0).getVoltageInitParameterId()); assertEquals(validParameters, userProfiles.get(0).getAllLinksValid()); - assertEquals(10, userProfiles.get(0).getMaxAllowValuesMap().get(MAX_ALLOWED_CASES)); - assertEquals(11, userProfiles.get(0).getMaxAllowValuesMap().get(MAX_ALLOWED_BUILD)); + assertEquals(10, userProfiles.get(0).getMaxOperationQuota().get(CASES)); + assertEquals(11, userProfiles.get(0).getMaxOperationQuota().get(BUILD)); assertEquals(spreadsheetConfigCollectionUuid, userProfiles.get(0).getSpreadsheetConfigCollectionId()); assertEquals(networkVisualizationParametersUuid, userProfiles.get(0).getNetworkVisualizationParameterId()); @@ -320,10 +320,10 @@ private static Map handleQueryParams(List para } private UUID createProfile(String profileName, String userName, String userRole, Integer maxAllowedCases, Integer maxAllowedBuilds, HttpStatusCode status) throws Exception { - Map maxAllowedValues = new HashMap<>(); - maxAllowedValues.put(MAX_ALLOWED_CASES, maxAllowedCases); - maxAllowedValues.put(MAX_ALLOWED_BUILD, maxAllowedBuilds); - UserProfile profileInfo = UserProfile.builder().name(profileName).maxAllowValuesMap(maxAllowedValues).build(); + Map maxAllowedValues = new EnumMap<>(OperationType.class); + maxAllowedValues.put(CASES, maxAllowedCases); + maxAllowedValues.put(BUILD, maxAllowedBuilds); + UserProfile profileInfo = UserProfile.builder().name(profileName).maxOperationQuota(maxAllowedValues).build(); mockMvc.perform(post("/" + UserAdminApi.API_VERSION + "/profiles") .content(objectWriter.writeValueAsString(profileInfo)) .contentType(MediaType.APPLICATION_JSON) @@ -405,7 +405,7 @@ private void updateProfile(UserProfile newData, String userName, String userRole assertEquals(newData.getShortcircuitParameterId(), updatedProfile.getShortcircuitParameterId()); assertEquals(newData.getPccMinParameterId(), updatedProfile.getPccMinParameterId()); assertEquals(newData.getVoltageInitParameterId(), updatedProfile.getVoltageInitParameterId()); - assertEquals(newData.getMaxAllowValuesMap().get(MAX_ALLOWED_BUILD), updatedProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_BUILD)); + assertEquals(newData.getMaxOperationQuota().get(BUILD), updatedProfile.getMaxOperationQuota().get(BUILD)); assertNull(updatedProfile.getAllLinksValid()); // validity not set in this case assertEquals(newData.getSpreadsheetConfigCollectionId(), updatedProfile.getSpreadsheetConfigCollectionId()); assertEquals(newData.getNetworkVisualizationParameterId(), updatedProfile.getNetworkVisualizationParameterId()); diff --git a/src/test/java/org/gridsuite/useradmin/server/controller/UserGroupControllerTest.java b/src/test/java/org/gridsuite/useradmin/server/controller/UserGroupControllerTest.java index bdebf93..7a471f7 100644 --- a/src/test/java/org/gridsuite/useradmin/server/controller/UserGroupControllerTest.java +++ b/src/test/java/org/gridsuite/useradmin/server/controller/UserGroupControllerTest.java @@ -164,9 +164,9 @@ void testGroups() throws Exception { when(directoryService.getCasesCount(USER_A)).thenReturn(5); // Create users USER_A, USER_B, USER_C in database - userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_A, null, null)); - userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_B, null, null)); - userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_C, null, null)); + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_A, null, null, null)); + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_B, null, null, null)); + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_C, null, null, null)); // create new group GROUP createGroup(GROUP); @@ -193,8 +193,8 @@ void testGroups() throws Exception { checkUserGroup(USER_C, GROUP); // update the group with new name and new users : USER_A, USER_D, USER_E - userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_D, null, null)); - userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_E, null, null)); + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_D, null, null, null)); + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_E, null, null, null)); updateGroup(group.id(), GROUP_NEW_NAME, Set.of(USER_A, USER_D, USER_E)); diff --git a/src/test/java/org/gridsuite/useradmin/server/controller/UserInfosControllerTest.java b/src/test/java/org/gridsuite/useradmin/server/controller/UserInfosControllerTest.java index b3eb701..d5e72ba 100644 --- a/src/test/java/org/gridsuite/useradmin/server/controller/UserInfosControllerTest.java +++ b/src/test/java/org/gridsuite/useradmin/server/controller/UserInfosControllerTest.java @@ -70,7 +70,7 @@ void getUserDetail() throws Exception { null, null, null, null, null); userProfileRepository.save(profileEntity); // Create a user - UserInfosEntity userInfosEntity = new UserInfosEntity(UUID.randomUUID(), USER_A, profileEntity, null); + UserInfosEntity userInfosEntity = new UserInfosEntity(UUID.randomUUID(), USER_A, profileEntity, null, null); userInfosRepository.save(userInfosEntity); // Mock the calls to the directory service and the database diff --git a/src/test/java/org/gridsuite/useradmin/server/controller/UserQuotaControllerTest.java b/src/test/java/org/gridsuite/useradmin/server/controller/UserQuotaControllerTest.java new file mode 100644 index 0000000..f1dac82 --- /dev/null +++ b/src/test/java/org/gridsuite/useradmin/server/controller/UserQuotaControllerTest.java @@ -0,0 +1,259 @@ +/** + * Copyright (c) 2026, 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.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.gridsuite.useradmin.server.dto.OperationType; +import org.gridsuite.useradmin.server.entity.UserInfosEntity; +import org.gridsuite.useradmin.server.repository.UserInfosRepository; +import org.gridsuite.useradmin.server.repository.UserProfileRepository; +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.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import java.util.Map; +import java.util.UUID; + +import static org.gridsuite.useradmin.server.Utils.ROLES_HEADER; +import static org.gridsuite.useradmin.server.dto.OperationType.*; +import static org.gridsuite.useradmin.server.utils.TestConstants.API_BASE_PATH; +import static org.gridsuite.useradmin.server.utils.TestConstants.USER_ADMIN_ROLE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Ghiles Abdellah {@literal } + */ +@AutoConfigureMockMvc +@SpringBootTest +class UserQuotaControllerTest { + + private static final String ADMIN_USER = "admin1"; + private static final String USER_A = "userAQuota"; + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private UserInfosRepository userInfosRepository; + + @Autowired + private UserProfileRepository userProfileRepository; + + @AfterEach + void cleanDB() { + userInfosRepository.deleteAll(); + userProfileRepository.deleteAll(); + } + + @Test + void getUserMaxQuotaReturnsDefaultQuotaWhenUserHasNoProfile() throws Exception { + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_A, null, null, null)); + + MvcResult result = mockMvc.perform(get(API_BASE_PATH + "/users/{sub}/quota/max", USER_A) + .header("userId", ADMIN_USER) + .header(ROLES_HEADER, USER_ADMIN_ROLE) + .contentType(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn(); + + Map quota = objectMapper.readValue( + result.getResponse().getContentAsString(), new TypeReference<>() { }); + + assertNotNull(quota); + // default values from application-default.yml + assertEquals(20, quota.get(CASES)); + assertEquals(10, quota.get(BUILD)); + assertEquals(2, quota.get(LOAD_FLOW)); + assertEquals(2, quota.get(SECURITY)); + assertEquals(1, quota.get(SENSITIVITY)); + assertEquals(1, quota.get(SHORT_CIRCUIT)); + assertEquals(2, quota.get(VOLTAGE_INIT)); + assertEquals(1, quota.get(PCC_MIN)); + assertEquals(1, quota.get(STATE_ESTIMATION)); + assertEquals(1, quota.get(BALANCE_ADJUSTEMENT)); + assertEquals(1, quota.get(DYNAMIC_SIMULATION)); + assertEquals(1, quota.get(DYNAMIC_SECURITY)); + assertEquals(1, quota.get(DYNAMIC_MARGIN)); + } + + @Test + void getUserCurrentQuotaUsageReturnsEmptyMapWhenNoOperationsRegistered() throws Exception { + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_A, null, null, null)); + + MvcResult result = mockMvc.perform(get(API_BASE_PATH + "/users/{sub}/quota/current", USER_A) + .header("userId", ADMIN_USER) + .header(ROLES_HEADER, USER_ADMIN_ROLE) + .contentType(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn(); + + Map usage = objectMapper.readValue( + result.getResponse().getContentAsString(), new TypeReference<>() { }); + + assertNotNull(usage); + assertTrue(usage.isEmpty()); + } + + @Test + void getUserCurrentQuotaUsageReturnsAggregatedCountsWhenOperationsExist() throws Exception { + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_A, null, null, null)); + + UUID buildOp1 = UUID.randomUUID(); + UUID buildOp2 = UUID.randomUUID(); + UUID casesOp1 = UUID.randomUUID(); + + startOperation(USER_A, BUILD, buildOp1); + startOperation(USER_A, BUILD, buildOp2); + startOperation(USER_A, CASES, casesOp1); + + MvcResult result = mockMvc.perform(get(API_BASE_PATH + "/users/{sub}/quota/current", USER_A) + .header("userId", ADMIN_USER) + .header(ROLES_HEADER, USER_ADMIN_ROLE) + .contentType(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn(); + + Map usage = objectMapper.readValue( + result.getResponse().getContentAsString(), new TypeReference<>() { }); + + assertEquals(2, usage.get(BUILD)); + assertEquals(1, usage.get(CASES)); + } + + @Test + void getUserCurrentQuotaUsageReturns404WhenUserDoesNotExist() throws Exception { + mockMvc.perform(get(API_BASE_PATH + "/users/{sub}/quota/current", "nonExistentUser") + .header("userId", ADMIN_USER) + .header(ROLES_HEADER, USER_ADMIN_ROLE) + .contentType(APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + void startUserOperationAddsOperationAndReturns200() throws Exception { + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_A, null, null, null)); + UUID operationId = UUID.randomUUID(); + + startOperation(USER_A, BUILD, operationId); + + // verify via the current-quota endpoint (avoids lazy-loading outside a session) + Map usage = getCurrentQuota(USER_A); + assertEquals(1, usage.get(BUILD)); + } + + @Test + void startUserOperationReturns404WhenUserDoesNotExist() throws Exception { + mockMvc.perform(post(API_BASE_PATH + "/users/{sub}/quota/{operation}/{id}/start", + "nonExistentUser", BUILD, UUID.randomUUID()) + .header("userId", ADMIN_USER) + .header(ROLES_HEADER, USER_ADMIN_ROLE) + .contentType(APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + void endUserOperationRemovesOperationAndReturns200() throws Exception { + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_A, null, null, null)); + UUID operationId = UUID.randomUUID(); + + startOperation(USER_A, BUILD, operationId); + assertEquals(1, getCurrentQuota(USER_A).get(BUILD)); + + endOperation(USER_A, BUILD, operationId); + assertTrue(getCurrentQuota(USER_A).isEmpty()); + } + + @Test + void endUserOperationDoesNotRemoveWhenTypeMismatch() throws Exception { + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_A, null, null, null)); + UUID operationId = UUID.randomUUID(); + + startOperation(USER_A, BUILD, operationId); + + // end with a different type — should be a no-op + endOperation(USER_A, CASES, operationId); + + assertEquals(1, getCurrentQuota(USER_A).get(BUILD)); + } + + @Test + void endUserOperationReturns404WhenUserDoesNotExist() throws Exception { + mockMvc.perform(post(API_BASE_PATH + "/users/{sub}/quota/{operation}/{id}/end", + "nonExistentUser", BUILD, UUID.randomUUID()) + .header("userId", ADMIN_USER) + .header(ROLES_HEADER, USER_ADMIN_ROLE) + .contentType(APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + void resetUserCurrentQuotaUsageClearsAllOperationsWhenAdmin() throws Exception { + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_A, null, null, null)); + startOperation(USER_A, BUILD, UUID.randomUUID()); + startOperation(USER_A, CASES, UUID.randomUUID()); + assertEquals(2, getCurrentQuota(USER_A).size()); + + mockMvc.perform(post(API_BASE_PATH + "/users/{sub}/quota/reset", USER_A) + .header("userId", ADMIN_USER) + .header(ROLES_HEADER, USER_ADMIN_ROLE) + .contentType(APPLICATION_JSON)) + .andExpect(status().isOk()); + + assertTrue(getCurrentQuota(USER_A).isEmpty()); + } + + @Test + void resetUserCurrentQuotaUsageReturns403WhenNotAdmin() throws Exception { + userInfosRepository.save(new UserInfosEntity(UUID.randomUUID(), USER_A, null, null, null)); + + mockMvc.perform(post(API_BASE_PATH + "/users/{sub}/quota/reset", USER_A) + .header("userId", "regularUser") + // no ADMIN role header + .contentType(APPLICATION_JSON)) + .andExpect(status().isForbidden()); + } + + private void startOperation(String sub, OperationType operation, UUID operationId) throws Exception { + mockMvc.perform(post(API_BASE_PATH + "/users/{sub}/quota/{operation}/{id}/start", sub, operation, operationId) + .header("userId", ADMIN_USER) + .header(ROLES_HEADER, USER_ADMIN_ROLE) + .contentType(APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + private void endOperation(String sub, OperationType operation, UUID operationId) throws Exception { + mockMvc.perform(post(API_BASE_PATH + "/users/{sub}/quota/{operation}/{id}/end", sub, operation, operationId) + .header("userId", ADMIN_USER) + .header(ROLES_HEADER, USER_ADMIN_ROLE) + .contentType(APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + private Map getCurrentQuota(String sub) throws Exception { + MvcResult result = mockMvc.perform(get(API_BASE_PATH + "/users/{sub}/quota/current", sub) + .header("userId", ADMIN_USER) + .header(ROLES_HEADER, USER_ADMIN_ROLE) + .contentType(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn(); + return objectMapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() { }); + } +} diff --git a/src/test/java/org/gridsuite/useradmin/server/service/UserGroupServiceTest.java b/src/test/java/org/gridsuite/useradmin/server/service/UserGroupServiceTest.java index 91708ec..2bf4262 100644 --- a/src/test/java/org/gridsuite/useradmin/server/service/UserGroupServiceTest.java +++ b/src/test/java/org/gridsuite/useradmin/server/service/UserGroupServiceTest.java @@ -51,7 +51,7 @@ class UserGroupServiceTest { void toDtoUserGroupTest() { // create user and group GroupInfosEntity group = new GroupInfosEntity("group_A"); - UserInfosEntity user = new UserInfosEntity(UUID.randomUUID(), "user_A", null, Set.of(group)); + UserInfosEntity user = new UserInfosEntity(UUID.randomUUID(), "user_A", null, null, Set.of(group)); group.setUsers(Set.of(user)); when(userGroupRepositoryMock.findByName("group_A")).thenReturn(Optional.of(group)); diff --git a/src/test/java/org/gridsuite/useradmin/server/service/UserInfosServiceTest.java b/src/test/java/org/gridsuite/useradmin/server/service/UserInfosServiceTest.java index 69ac6d4..a9874fa 100644 --- a/src/test/java/org/gridsuite/useradmin/server/service/UserInfosServiceTest.java +++ b/src/test/java/org/gridsuite/useradmin/server/service/UserInfosServiceTest.java @@ -55,7 +55,7 @@ void toDtoUserInfoTest() { null, null, null, null, null, null, null, null, null, null, null, null, null, null); - UserInfosEntity user = new UserInfosEntity(UUID.randomUUID(), "user_A", profile, null); + UserInfosEntity user = new UserInfosEntity(UUID.randomUUID(), "user_A", profile, null, null); when(userInfosRepositoryMock.findBySub("user_A")).thenReturn(Optional.of(user)); UserInfos userInfos = userInfosService.getUserInfo("user_A"); diff --git a/src/test/java/org/gridsuite/useradmin/server/service/UserQuotaServiceTest.java b/src/test/java/org/gridsuite/useradmin/server/service/UserQuotaServiceTest.java new file mode 100644 index 0000000..7cd6f1e --- /dev/null +++ b/src/test/java/org/gridsuite/useradmin/server/service/UserQuotaServiceTest.java @@ -0,0 +1,276 @@ +/** + * Copyright (c) 2026, 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.service; + +import org.gridsuite.useradmin.server.UserAdminApplication; +import org.gridsuite.useradmin.server.UserAdminApplicationProps; +import org.gridsuite.useradmin.server.dto.OperationType; +import org.gridsuite.useradmin.server.dto.UserProfile; +import org.gridsuite.useradmin.server.entity.UserInfosEntity; +import org.gridsuite.useradmin.server.entity.UserOperationEntity; +import org.gridsuite.useradmin.server.error.UserAdminException; +import org.gridsuite.useradmin.server.repository.UserInfosRepository; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import static org.gridsuite.useradmin.server.dto.OperationType.BUILD; +import static org.gridsuite.useradmin.server.dto.OperationType.CASES; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @author Ghiles Abdellah {@literal } + */ +@SpringBootTest(classes = {UserAdminApplication.class}) +class UserQuotaServiceTest { + + @Mock + private UserInfosRepository userInfosRepositoryMock; + + @Mock + private AdminRightService adminRightServiceMock; + + @Mock + private UserProfileService userProfileServiceMock; + + @Mock + private UserAdminApplicationProps applicationPropsMock; + + @InjectMocks + private UserQuotaService userQuotaService; + + @Test + void getUserMaxQuotaReturnsProfileQuotaWhenProfileHasQuota() { + Map profileQuota = new EnumMap<>(OperationType.class); + profileQuota.put(CASES, 10); + profileQuota.put(BUILD, 5); + UserProfile profile = UserProfile.builder() + .name("profile_A") + .maxOperationQuota(profileQuota) + .build(); + + when(userProfileServiceMock.doGetUserProfile("user_A")).thenReturn(profile); + + Map result = userQuotaService.getUserMaxQuota("user_A"); + + assertNotNull(result); + assertEquals(10, result.get(CASES)); + assertEquals(5, result.get(BUILD)); + } + + @Test + void getUserMaxQuotaReturnsDefaultQuotaWhenProfileHasNullQuota() { + Map defaultQuota = new EnumMap<>(OperationType.class); + defaultQuota.put(CASES, 20); + defaultQuota.put(BUILD, 10); + + UserProfile profileWithNullQuota = mock(UserProfile.class); + when(profileWithNullQuota.getMaxOperationQuota()).thenReturn(null); + + when(userProfileServiceMock.doGetUserProfile("user_B")).thenReturn(profileWithNullQuota); + when(userProfileServiceMock.getDefaultMaxAllowedValues()).thenReturn(defaultQuota); + + Map result = userQuotaService.getUserMaxQuota("user_B"); + + assertNotNull(result); + assertEquals(20, result.get(CASES)); + assertEquals(10, result.get(BUILD)); + } + + @Test + void getUserCurrentQuotaUsageReturnsEmptyMapWhenNoOperations() { + UserInfosEntity userInfosEntity = new UserInfosEntity(UUID.randomUUID(), "user_A", null, new ArrayList<>(), null); + when(userInfosRepositoryMock.findBySub("user_A")).thenReturn(Optional.of(userInfosEntity)); + + Map result = userQuotaService.getUserCurrentQuotaUsage("user_A"); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + void getUserCurrentQuotaUsageReturnsAggregatedCountsWhenOperationsExist() { + UserInfosEntity userInfosEntity = new UserInfosEntity(UUID.randomUUID(), "user_A", null, new ArrayList<>(), null); + UUID op1 = UUID.randomUUID(); + UUID op2 = UUID.randomUUID(); + UUID op3 = UUID.randomUUID(); + userInfosEntity.getUserOperations().add(new UserOperationEntity(userInfosEntity, op1, BUILD)); + userInfosEntity.getUserOperations().add(new UserOperationEntity(userInfosEntity, op2, BUILD)); + userInfosEntity.getUserOperations().add(new UserOperationEntity(userInfosEntity, op3, CASES)); + + when(userInfosRepositoryMock.findBySub("user_A")).thenReturn(Optional.of(userInfosEntity)); + + Map result = userQuotaService.getUserCurrentQuotaUsage("user_A"); + + assertEquals(2, result.get(BUILD)); + assertEquals(1, result.get(CASES)); + } + + @Test + void getUserCurrentQuotaUsageThrowsUserNotFoundWhenUserDoesNotExist() { + when(userInfosRepositoryMock.findBySub("unknown")).thenReturn(Optional.empty()); + + assertThrows(UserAdminException.class, () -> userQuotaService.getUserCurrentQuotaUsage("unknown")); + } + + @Test + void startUserOperationAddsOperationToUser() { + UserInfosEntity userInfosEntity = new UserInfosEntity(UUID.randomUUID(), "user_A", null, new ArrayList<>(), null); + when(userInfosRepositoryMock.findBySub("user_A")).thenReturn(Optional.of(userInfosEntity)); + when(userInfosRepositoryMock.save(any())).thenReturn(userInfosEntity); + + UUID operationId = UUID.randomUUID(); + userQuotaService.startUserOperation("user_A", BUILD, operationId); + + assertEquals(1, userInfosEntity.getUserOperations().size()); + assertEquals(BUILD, userInfosEntity.getUserOperations().getFirst().getOperationType()); + assertEquals(operationId, userInfosEntity.getUserOperations().getFirst().getOperationId()); + verify(userInfosRepositoryMock).save(userInfosEntity); + } + + @Test + void startUserOperationThrowsUserNotFoundWhenUserDoesNotExist() { + when(userInfosRepositoryMock.findBySub("unknown")).thenReturn(Optional.empty()); + + assertThrows(UserAdminException.class, + () -> userQuotaService.startUserOperation("unknown", BUILD, UUID.randomUUID())); + } + + @Test + void endUserOperationRemovesMatchingOperation() { + UserInfosEntity userInfosEntity = new UserInfosEntity(UUID.randomUUID(), "user_A", null, new ArrayList<>(), null); + UUID operationId = UUID.randomUUID(); + userInfosEntity.getUserOperations().add(new UserOperationEntity(userInfosEntity, operationId, BUILD)); + // add a different operation that must NOT be removed + userInfosEntity.getUserOperations().add(new UserOperationEntity(userInfosEntity, UUID.randomUUID(), CASES)); + + when(userInfosRepositoryMock.findBySub("user_A")).thenReturn(Optional.of(userInfosEntity)); + when(userInfosRepositoryMock.save(any())).thenReturn(userInfosEntity); + + userQuotaService.endUserOperation("user_A", BUILD, operationId); + + assertEquals(1, userInfosEntity.getUserOperations().size()); + assertEquals(CASES, userInfosEntity.getUserOperations().get(0).getOperationType()); + verify(userInfosRepositoryMock).save(userInfosEntity); + } + + @Test + void endUserOperationDoesNotRemoveWhenOperationIdMatchesButTypeDiffers() { + UserInfosEntity userInfosEntity = new UserInfosEntity(UUID.randomUUID(), "user_A", null, new ArrayList<>(), null); + UUID operationId = UUID.randomUUID(); + userInfosEntity.getUserOperations().add(new UserOperationEntity(userInfosEntity, operationId, BUILD)); + + when(userInfosRepositoryMock.findBySub("user_A")).thenReturn(Optional.of(userInfosEntity)); + when(userInfosRepositoryMock.save(any())).thenReturn(userInfosEntity); + + // end a CASES operation with the same UUID — should NOT remove the BUILD one + userQuotaService.endUserOperation("user_A", CASES, operationId); + + assertEquals(1, userInfosEntity.getUserOperations().size()); + } + + @Test + void endUserOperationThrowsUserNotFoundWhenUserDoesNotExist() { + when(userInfosRepositoryMock.findBySub("unknown")).thenReturn(Optional.empty()); + + assertThrows(UserAdminException.class, + () -> userQuotaService.endUserOperation("unknown", BUILD, UUID.randomUUID())); + } + + @Test + void resetUserCurrentQuotaUsageClearsOperationsWhenAdmin() { + UserInfosEntity userInfosEntity = new UserInfosEntity(UUID.randomUUID(), "user_A", null, new ArrayList<>(), null); + userInfosEntity.getUserOperations().add(new UserOperationEntity(userInfosEntity, UUID.randomUUID(), BUILD)); + when(userInfosRepositoryMock.findBySub("user_A")).thenReturn(Optional.of(userInfosEntity)); + when(userInfosRepositoryMock.save(any())).thenReturn(userInfosEntity); + doNothing().when(adminRightServiceMock).assertIsAdmin(); + + userQuotaService.resetUserCurrentQuotaUsage("user_A"); + + assertTrue(userInfosEntity.getUserOperations().isEmpty()); + verify(userInfosRepositoryMock).save(userInfosEntity); + } + + @Test + void resetUserCurrentQuotaUsageThrowsUserNotFoundWhenUserDoesNotExist() { + doNothing().when(adminRightServiceMock).assertIsAdmin(); + when(userInfosRepositoryMock.findBySub("unknown")).thenReturn(Optional.empty()); + + assertThrows(UserAdminException.class, () -> userQuotaService.resetUserCurrentQuotaUsage("unknown")); + } + + @Test + void resetUserCurrentQuotaUsageThrowsForbiddenWhenNotAdmin() { + doThrow(UserAdminException.forbidden()).when(adminRightServiceMock).assertIsAdmin(); + + assertThrows(UserAdminException.class, () -> userQuotaService.resetUserCurrentQuotaUsage("user_A")); + verify(userInfosRepositoryMock, never()).save(any()); + } + + @Test + void getUserProfileMaxAllowedCasesReturnsProfileValueWhenPresent() { + Map quota = new EnumMap<>(OperationType.class); + quota.put(CASES, 15); + UserProfile profile = UserProfile.builder().name("p").maxOperationQuota(quota).build(); + when(userProfileServiceMock.doGetUserProfile("user_A")).thenReturn(profile); + + Integer result = userQuotaService.getUserProfileMaxAllowedCases("user_A"); + + assertEquals(15, result); + } + + @Test + void getUserProfileMaxAllowedCasesReturnsDefaultWhenProfileHasNoValue() { + UserProfile profile = UserProfile.builder().name("p").maxOperationQuota(new EnumMap<>(OperationType.class)).build(); + when(userProfileServiceMock.doGetUserProfile("user_A")).thenReturn(profile); + when(applicationPropsMock.getDefaultMaxAllowedCases()).thenReturn(50); + + Integer result = userQuotaService.getUserProfileMaxAllowedCases("user_A"); + + assertEquals(50, result); + } + + @Test + void getUserProfileMaxAllowedBuildsReturnsProfileValueWhenPresent() { + Map quota = new EnumMap<>(OperationType.class); + quota.put(BUILD, 8); + UserProfile profile = UserProfile.builder().name("p").maxOperationQuota(quota).build(); + when(userProfileServiceMock.doGetUserProfile("user_A")).thenReturn(profile); + + Integer result = userQuotaService.getUserProfileMaxAllowedBuilds("user_A"); + + assertEquals(8, result); + } + + @Test + void getUserProfileMaxAllowedBuildsReturnsDefaultWhenProfileHasNoValue() { + UserProfile profile = UserProfile.builder().name("p").maxOperationQuota(new EnumMap<>(OperationType.class)).build(); + when(userProfileServiceMock.doGetUserProfile("user_A")).thenReturn(profile); + when(applicationPropsMock.getDefaultMaxAllowedBuilds()).thenReturn(25); + + Integer result = userQuotaService.getUserProfileMaxAllowedBuilds("user_A"); + + assertEquals(25, result); + } +}