You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by da...@apache.org on 2025/01/16 14:19:37 UTC
(cloudstack) branch main updated: Create API to list Quota credits (#9590)
This is an automated email from the ASF dual-hosted git repository.
dahn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/main by this push:
new 449d3c7cb1d Create API to list Quota credits (#9590)
449d3c7cb1d is described below
commit 449d3c7cb1df75867a3e1a9c3648fc3675c6b39e
Author: Fabricio Duarte <fa...@gmail.com>
AuthorDate: Thu Jan 16 11:19:32 2025 -0300
Create API to list Quota credits (#9590)
Co-authored-by: Bernardo De Marco Gonçalves <be...@gmail.com>
---
.../org/apache/cloudstack/api/ApiConstants.java | 16 +-
.../main/java/com/cloud/domain/dao/DomainDao.java | 2 +
.../java/com/cloud/domain/dao/DomainDaoImpl.java | 10 ++
.../resources/META-INF/db/schema-42010to42100.sql | 7 +
.../cloudstack/quota/dao/QuotaCreditsDao.java | 2 +-
.../cloudstack/quota/dao/QuotaCreditsDaoImpl.java | 54 ++++---
.../apache/cloudstack/quota/vo/QuotaCreditsVO.java | 6 +
.../api/command/QuotaCreditsListCmd.java | 122 +++++++++++++++
.../api/response/QuotaBalanceResponse.java | 4 +-
.../api/response/QuotaCreditsResponse.java | 63 ++++----
.../api/response/QuotaResponseBuilder.java | 3 +
.../api/response/QuotaResponseBuilderImpl.java | 122 ++++++++++++---
.../apache/cloudstack/quota/QuotaServiceImpl.java | 2 +
.../api/command/QuotaCreditsListCmdTest.java | 79 ++++++++++
.../api/response/QuotaResponseBuilderImplTest.java | 165 ++++++++++++++++++++-
15 files changed, 572 insertions(+), 85 deletions(-)
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index cf03f1d2699..a406e2d7a72 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -1192,6 +1192,14 @@ public class ApiConstants {
"numeric value will be applied; if the result is neither a boolean nor a numeric value, the tariff will not be applied. If the rule is not informed, the tariff " +
"value will be applied.";
+ public static final String PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS = "The recommended format is \"yyyy-MM-dd'T'HH:mm:ssZ\" (e.g.: \"2023-01-01T12:00:00+0100\"); " +
+ "however, the following formats are also accepted: \"yyyy-MM-dd HH:mm:ss\" (e.g.: \"2023-01-01 12:00:00\") and \"yyyy-MM-dd\" (e.g.: \"2023-01-01\" - if the time is not " +
+ "added, it will be interpreted as \"00:00:00\"). If the recommended format is not used, the date will be considered in the server timezone.";
+
+ public static final String PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS = "The recommended format is \"yyyy-MM-dd'T'HH:mm:ssZ\" (e.g.: \"2023-01-01T12:00:00+0100\"); " +
+ "however, the following formats are also accepted: \"yyyy-MM-dd HH:mm:ss\" (e.g.: \"2023-01-01 12:00:00\") and \"yyyy-MM-dd\" (e.g.: \"2023-01-01\" - if the time is not " +
+ "added, it will be interpreted as \"23:59:59\"). If the recommended format is not used, the date will be considered in the server timezone.";
+
/**
* This enum specifies IO Drivers, each option controls specific policies on I/O.
* Qemu guests support "threads" and "native" options Since 0.8.8 ; "io_uring" is supported Since 6.3.0 (QEMU 5.0).
@@ -1214,14 +1222,6 @@ public class ApiConstants {
}
}
- public static final String PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS = "The recommended format is \"yyyy-MM-dd'T'HH:mm:ssZ\" (e.g.: \"2023-01-01T12:00:00+0100\"); " +
- "however, the following formats are also accepted: \"yyyy-MM-dd HH:mm:ss\" (e.g.: \"2023-01-01 12:00:00\") and \"yyyy-MM-dd\" (e.g.: \"2023-01-01\" - if the time is not " +
- "added, it will be interpreted as \"00:00:00\"). If the recommended format is not used, the date will be considered in the server timezone.";
-
- public static final String PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS = "The recommended format is \"yyyy-MM-dd'T'HH:mm:ssZ\" (e.g.: \"2023-01-01T12:00:00+0100\"); " +
- "however, the following formats are also accepted: \"yyyy-MM-dd HH:mm:ss\" (e.g.: \"2023-01-01 12:00:00\") and \"yyyy-MM-dd\" (e.g.: \"2023-01-01\" - if the time is not " +
- "added, it will be interpreted as \"23:59:59\"). If the recommended format is not used, the date will be considered in the server timezone.";
-
public enum BootType {
UEFI, BIOS;
diff --git a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDao.java b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDao.java
index 937d99f5052..3aee371da25 100644
--- a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDao.java
+++ b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDao.java
@@ -42,5 +42,7 @@ public interface DomainDao extends GenericDao<DomainVO, Long> {
List<Long> getDomainChildrenIds(String path);
+ List<Long> getDomainAndChildrenIds(long domainId);
+
boolean domainIdListContainsAccessibleDomain(String domainIdList, Account caller, Long domainId);
}
diff --git a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java
index 74f2932ca43..56d971bbe01 100644
--- a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java
@@ -19,6 +19,7 @@ package com.cloud.domain.dao;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -238,6 +239,15 @@ public class DomainDaoImpl extends GenericDaoBase<DomainVO, Long> implements Dom
return customSearch(sc, null);
}
+ @Override
+ public List<Long> getDomainAndChildrenIds(long domainId) {
+ DomainVO domain = findById(domainId);
+ if (domain != null) {
+ return getDomainChildrenIds(domain.getPath());
+ }
+ return new ArrayList<>();
+ }
+
@Override
public boolean isChildDomain(Long parentId, Long childId) {
if ((parentId == null) || (childId == null)) {
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql b/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql
index 91223bab798..47e7bebbee4 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql
@@ -24,3 +24,10 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.console_session', 'console_endpoint_
-- Add client_address column to cloud.console_session table
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.console_session', 'client_address', 'VARCHAR(45)');
+
+-- Allow default roles to use quotaCreditsList
+INSERT INTO `cloud`.`role_permissions` (uuid, role_id, rule, permission, sort_order)
+SELECT uuid(), role_id, 'quotaCreditsList', permission, sort_order
+FROM `cloud`.`role_permissions` rp
+WHERE rp.rule = 'quotaStatement'
+AND NOT EXISTS(SELECT 1 FROM cloud.role_permissions rp_ WHERE rp.role_id = rp_.role_id AND rp_.rule = 'quotaCreditsList');
diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaCreditsDao.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaCreditsDao.java
index f08d8f96ca8..da36bc0b98c 100644
--- a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaCreditsDao.java
+++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaCreditsDao.java
@@ -25,7 +25,7 @@ import com.cloud.utils.db.GenericDao;
public interface QuotaCreditsDao extends GenericDao<QuotaCreditsVO, Long> {
- List<QuotaCreditsVO> findCredits(long accountId, long domainId, Date startDate, Date endDate);
+ List<QuotaCreditsVO> findCredits(Long accountId, Long domainId, Date startDate, Date endDate, boolean recursive);
QuotaCreditsVO saveCredits(QuotaCreditsVO credits);
diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaCreditsDaoImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaCreditsDaoImpl.java
index 40e18aa282b..ce51177d0ae 100644
--- a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaCreditsDaoImpl.java
+++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaCreditsDaoImpl.java
@@ -16,19 +16,20 @@
//under the License.
package org.apache.cloudstack.quota.dao;
-import java.util.Collections;
import java.util.Date;
import java.util.List;
import javax.inject.Inject;
+import com.cloud.domain.dao.DomainDao;
+import com.cloud.utils.db.Filter;
+import com.cloud.utils.db.SearchBuilder;
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
import org.apache.cloudstack.quota.vo.QuotaCreditsVO;
+import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Component;
-import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
-import com.cloud.utils.db.QueryBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
@@ -39,25 +40,36 @@ import com.cloud.utils.db.TransactionStatus;
public class QuotaCreditsDaoImpl extends GenericDaoBase<QuotaCreditsVO, Long> implements QuotaCreditsDao {
@Inject
- QuotaBalanceDao _quotaBalanceDao;
+ DomainDao domainDao;
+ @Inject
+ QuotaBalanceDao quotaBalanceDao;
+
+ private SearchBuilder<QuotaCreditsVO> quotaCreditsVoSearch;
+
+ public QuotaCreditsDaoImpl() {
+ quotaCreditsVoSearch = createSearchBuilder();
+ quotaCreditsVoSearch.and("updatedOn", quotaCreditsVoSearch.entity().getUpdatedOn(), SearchCriteria.Op.BETWEEN);
+ quotaCreditsVoSearch.and("accountId", quotaCreditsVoSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
+ quotaCreditsVoSearch.and("domainId", quotaCreditsVoSearch.entity().getDomainId(), SearchCriteria.Op.IN);
+ quotaCreditsVoSearch.done();
+ }
@Override
- public List<QuotaCreditsVO> findCredits(final long accountId, final long domainId, final Date startDate, final Date endDate) {
- return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback<List<QuotaCreditsVO>>() {
- @Override
- public List<QuotaCreditsVO> doInTransaction(final TransactionStatus status) {
- if ((startDate != null) && (endDate != null) && startDate.before(endDate)) {
- Filter filter = new Filter(QuotaCreditsVO.class, "updatedOn", true, 0L, Long.MAX_VALUE);
- QueryBuilder<QuotaCreditsVO> qb = QueryBuilder.create(QuotaCreditsVO.class);
- qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId);
- qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId);
- qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.BETWEEN, startDate, endDate);
- return search(qb.create(), filter);
- } else {
- return Collections.<QuotaCreditsVO> emptyList();
- }
- }
- });
+ public List<QuotaCreditsVO> findCredits(Long accountId, Long domainId, Date startDate, Date endDate, boolean recursive) {
+ SearchCriteria<QuotaCreditsVO> sc = quotaCreditsVoSearch.create();
+ Filter filter = new Filter(QuotaCreditsVO.class, "updatedOn", true, 0L, Long.MAX_VALUE);
+
+ sc.setParametersIfNotNull("accountId", accountId);
+ if (domainId != null) {
+ List<Long> domainIds = recursive ? domainDao.getDomainAndChildrenIds(domainId) : List.of(domainId);
+ sc.setParameters("domainId", domainIds.toArray());
+ }
+
+ if (ObjectUtils.allNotNull(startDate, endDate)) {
+ sc.setParameters("updatedOn", startDate, endDate);
+ }
+
+ return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback<List<QuotaCreditsVO>>) status -> search(sc, filter));
}
@Override
@@ -68,7 +80,7 @@ public class QuotaCreditsDaoImpl extends GenericDaoBase<QuotaCreditsVO, Long> im
persist(credits);
// make an entry in the balance table
QuotaBalanceVO bal = new QuotaBalanceVO(credits);
- _quotaBalanceDao.persist(bal);
+ quotaBalanceDao.persist(bal);
return credits;
}
});
diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaCreditsVO.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaCreditsVO.java
index f9c7b45b8a4..5cf6b1f0e57 100644
--- a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaCreditsVO.java
+++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaCreditsVO.java
@@ -17,6 +17,7 @@
package org.apache.cloudstack.quota.vo;
import org.apache.cloudstack.api.InternalIdentity;
+import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import javax.persistence.Column;
import javax.persistence.Entity;
@@ -113,4 +114,9 @@ public class QuotaCreditsVO implements InternalIdentity {
public long getId() {
return this.id;
}
+
+ @Override
+ public String toString() {
+ return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "accountId", "domainId", "credit");
+ }
}
diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaCreditsListCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaCreditsListCmd.java
new file mode 100644
index 00000000000..48bb7ef79e7
--- /dev/null
+++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaCreditsListCmd.java
@@ -0,0 +1,122 @@
+//Licensed to the Apache Software Foundation (ASF) under one
+//or more contributor license agreements. See the NOTICE file
+//distributed with this work for additional information
+//regarding copyright ownership. The ASF licenses this file
+//to you under the Apache License, Version 2.0 (the
+//"License"); you may not use this file except in compliance
+//with the License. You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing,
+//software distributed under the License is distributed on an
+//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+//KIND, either express or implied. See the License for the
+//specific language governing permissions and limitations
+//under the License.
+package org.apache.cloudstack.api.command;
+
+import com.cloud.utils.Pair;
+
+import org.apache.cloudstack.api.ACL;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.AccountResponse;
+import org.apache.cloudstack.api.response.DomainResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.QuotaCreditsResponse;
+import org.apache.cloudstack.api.response.QuotaResponseBuilder;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.time.DateUtils;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import javax.inject.Inject;
+
+@APICommand(name = "quotaCreditsList", responseObject = QuotaCreditsResponse.class, description = "Lists quota credits of an account.", since = "4.21.0",
+ requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
+public class QuotaCreditsListCmd extends BaseCmd {
+
+ @Inject
+ QuotaResponseBuilder quotaResponseBuilder;
+
+ @ACL
+ @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "ID of the account for which the credit statement will be generated.")
+ private Long accountId;
+
+ @ACL
+ @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "ID of the domain for which credit statement will be generated. " +
+ "Available only for administrators.")
+ private Long domainId;
+
+ @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, description = "End date of the credit statement. If not provided, the current date will be " +
+ "considered as the end date. " + ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS)
+ private Date endDate;
+
+ @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "Start date of the credit statement. If not provided, the first day of the current month " +
+ "will be considered as the start date. " + ApiConstants.PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS)
+ private Date startDate;
+
+ @Parameter(name = ApiConstants.IS_RECURSIVE, type = CommandType.BOOLEAN, description = "Whether to generate the credit statement for the provided domain and its children. " +
+ "Defaults to false.")
+ private Boolean recursive = false;
+
+ public Long getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(Long accountId) {
+ this.accountId = accountId;
+ }
+
+ public Long getDomainId() {
+ return domainId;
+ }
+
+ public void setDomainId(Long domainId) {
+ this.domainId = domainId;
+ }
+
+ public Date getEndDate() {
+ return ObjectUtils.defaultIfNull(endDate, new Date());
+ }
+
+ public void setEndDate(Date endDate) {
+ this.endDate = endDate;
+ }
+
+ public Date getStartDate() {
+ return ObjectUtils.defaultIfNull(startDate, DateUtils.truncate(new Date(), Calendar.MONTH));
+ }
+
+ public void setStartDate(Date startDate) {
+ this.startDate = startDate;
+ }
+
+ public Boolean getRecursive() {
+ return recursive;
+ }
+
+ public void setRecursive(Boolean recursive) {
+ this.recursive = recursive;
+ }
+
+ @Override
+ public void execute() {
+ Pair<List<QuotaCreditsResponse>, Integer> responses = quotaResponseBuilder.createQuotaCreditsListResponse(this);
+ ListResponse<QuotaCreditsResponse> response = new ListResponse<>();
+ response.setResponses(responses.first(), responses.second());
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return -1;
+ }
+
+}
diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaBalanceResponse.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaBalanceResponse.java
index fca6b6cbb1a..4c6cc750d99 100644
--- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaBalanceResponse.java
+++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaBalanceResponse.java
@@ -122,8 +122,8 @@ public class QuotaBalanceResponse extends BaseResponse {
public void addCredits(QuotaBalanceVO credit) {
QuotaCreditsResponse cr = new QuotaCreditsResponse();
- cr.setCredits(credit.getCreditBalance());
- cr.setUpdatedOn(credit.getUpdatedOn() == null ? null : new Date(credit.getUpdatedOn().getTime()));
+ cr.setCredit(credit.getCreditBalance());
+ cr.setCreditedOn(credit.getUpdatedOn());
credits.add(0, cr);
}
diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaCreditsResponse.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaCreditsResponse.java
index 561937ddcca..c81f777268f 100644
--- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaCreditsResponse.java
+++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaCreditsResponse.java
@@ -20,65 +20,62 @@ import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
import org.apache.cloudstack.api.BaseResponse;
-import org.apache.cloudstack.quota.vo.QuotaCreditsVO;
import java.math.BigDecimal;
-import java.math.RoundingMode;
import java.util.Date;
public class QuotaCreditsResponse extends BaseResponse {
- @SerializedName("credits")
- @Param(description = "the credit deposited")
- private BigDecimal credits;
+ @SerializedName("credit")
+ @Param(description = "The credit deposited.")
+ private BigDecimal credit;
- @SerializedName("updated_by")
- @Param(description = "the user name of the admin who updated the credits")
- private String updatedBy;
+ @SerializedName("creditoruserid")
+ @Param(description = "ID of the creditor user.")
+ private String creditorUserId;
- @SerializedName("updated_on")
- @Param(description = "the account name of the admin who updated the credits")
- private Date updatedOn;
+ @SerializedName("creditorusername")
+ @Param(description = "Username of the creditor user.")
+ private String creditorUsername;
+
+ @SerializedName("creditedon")
+ @Param(description = "When the credit was added.")
+ private Date creditedOn;
@SerializedName("currency")
- @Param(description = "currency")
+ @Param(description = "Credit's currency.")
private String currency;
- public QuotaCreditsResponse() {
- super();
+ public BigDecimal getCredit() {
+ return credit;
}
- public QuotaCreditsResponse(QuotaCreditsVO result, String updatedBy) {
- super();
- if (result != null) {
- setCredits(result.getCredit());
- setUpdatedBy(updatedBy);
- setUpdatedOn(new Date());
- }
+ public void setCredit(BigDecimal credit) {
+ this.credit = credit;
}
- public BigDecimal getCredits() {
- return credits;
+ public String getCreditorUserId() {
+ return creditorUserId;
}
- public void setCredits(BigDecimal credits) {
- this.credits = credits.setScale(2, RoundingMode.HALF_EVEN);
+ public void setCreditorUserId(String creditorUserId) {
+ this.creditorUserId = creditorUserId;
}
- public String getUpdatedBy() {
- return updatedBy;
+ public String getCreditorUsername() {
+ return creditorUsername;
}
- public void setUpdatedBy(String updatedBy) {
- this.updatedBy = updatedBy;
+ public void setCreditorUsername(String creditorUsername) {
+ this.creditorUsername = creditorUsername;
}
- public Date getUpdatedOn() {
- return updatedOn;
+ public Date getCreditedOn() {
+ return creditedOn;
}
- public void setUpdatedOn(Date updatedOn) {
- this.updatedOn = updatedOn;
+ public void setCreditedOn(Date creditedOn) {
+ this.creditedOn = creditedOn;
}
public String getCurrency() {
diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java
index 56935a1360c..67f75ebf82f 100644
--- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java
+++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java
@@ -19,6 +19,7 @@ package org.apache.cloudstack.api.response;
import com.cloud.user.User;
import org.apache.cloudstack.api.command.QuotaBalanceCmd;
import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd;
+import org.apache.cloudstack.api.command.QuotaCreditsListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd;
import org.apache.cloudstack.api.command.QuotaPresetVariablesListCmd;
@@ -90,5 +91,7 @@ public interface QuotaResponseBuilder {
List<QuotaConfigureEmailResponse> listEmailConfiguration(long accountId);
+ Pair<List<QuotaCreditsResponse>, Integer> createQuotaCreditsListResponse(QuotaCreditsListCmd cmd);
+
QuotaValidateActivationRuleResponse validateActivationRule(QuotaValidateActivationRuleCmd cmd);
}
diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java
index 733f7792356..7a987df0a35 100644
--- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java
+++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java
@@ -42,12 +42,16 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
+import com.cloud.exception.PermissionDeniedException;
+import com.cloud.user.User;
+import com.cloud.user.UserVO;
import com.cloud.utils.DateUtil;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.QuotaBalanceCmd;
import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd;
+import org.apache.cloudstack.api.command.QuotaCreditsListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd;
import org.apache.cloudstack.api.command.QuotaPresetVariablesListCmd;
@@ -99,7 +103,6 @@ import com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO;
-import com.cloud.user.User;
import com.cloud.user.dao.AccountDao;
import com.cloud.user.dao.UserDao;
import com.cloud.utils.Pair;
@@ -116,7 +119,7 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
@Inject
private QuotaBalanceDao _quotaBalanceDao;
@Inject
- private QuotaCreditsDao _quotaCreditsDao;
+ private QuotaCreditsDao quotaCreditsDao;
@Inject
private QuotaUsageDao _quotaUsageDao;
@Inject
@@ -548,28 +551,32 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
@Override
public QuotaCreditsResponse addQuotaCredits(Long accountId, Long domainId, Double amount, Long updatedBy, Boolean enforce) {
- Date despositedOn = new Date();
- QuotaBalanceVO qb = _quotaBalanceDao.findLaterBalanceEntry(accountId, domainId, despositedOn);
+ Date depositedOn = new Date();
+ QuotaBalanceVO qb = _quotaBalanceDao.findLaterBalanceEntry(accountId, domainId, depositedOn);
if (qb != null) {
throw new InvalidParameterValueException(String.format("Incorrect deposit date [%s], as there are balance entries after this date.",
- despositedOn));
+ depositedOn));
}
QuotaCreditsVO credits = new QuotaCreditsVO(accountId, domainId, new BigDecimal(amount), updatedBy);
- credits.setUpdatedOn(despositedOn);
- QuotaCreditsVO result = _quotaCreditsDao.saveCredits(credits);
+ credits.setUpdatedOn(depositedOn);
+ QuotaCreditsVO result = quotaCreditsDao.saveCredits(credits);
+ if (result == null) {
+ logger.error("Unable to add credits to account ID [{}].", accountId);
+ throw new CloudRuntimeException("Unable to add credits to account.");
+ }
final AccountVO account = _accountDao.findById(accountId);
if (account == null) {
throw new InvalidParameterValueException("Account does not exist with account id " + accountId);
}
final boolean lockAccountEnforcement = "true".equalsIgnoreCase(QuotaConfig.QuotaEnableEnforcement.value());
- final BigDecimal currentAccountBalance = _quotaBalanceDao.lastQuotaBalance(accountId, domainId, startOfNextDay(new Date(despositedOn.getTime())));
+ final BigDecimal currentAccountBalance = _quotaBalanceDao.lastQuotaBalance(accountId, domainId, startOfNextDay(new Date(depositedOn.getTime())));
logger.debug("Depositing [{}] credits on adjusted date [{}]; current balance is [{}].", amount,
- DateUtil.displayDateInTimezone(QuotaManagerImpl.getUsageAggregationTimeZone(), despositedOn), currentAccountBalance);
+ DateUtil.displayDateInTimezone(QuotaManagerImpl.getUsageAggregationTimeZone(), depositedOn), currentAccountBalance);
// update quota account with the balance
- _quotaService.saveQuotaAccount(account, currentAccountBalance, despositedOn);
+ _quotaService.saveQuotaAccount(account, currentAccountBalance, depositedOn);
if (lockAccountEnforcement) {
if (currentAccountBalance.compareTo(new BigDecimal(0)) >= 0) {
if (account.getState() == Account.State.LOCKED) {
@@ -584,14 +591,8 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
}
}
- String creditor = String.valueOf(Account.ACCOUNT_ID_SYSTEM);
- User creditorUser = _userDao.getUser(updatedBy);
- if (creditorUser != null) {
- creditor = creditorUser.getUsername();
- }
- QuotaCreditsResponse response = new QuotaCreditsResponse(result, creditor);
- response.setCurrency(QuotaConfig.QuotaCurrencySymbol.value());
- return response;
+ UserVO creditor = getCreditorForQuotaCredits(result);
+ return createQuotaCreditsResponse(result, creditor);
}
private QuotaEmailTemplateResponse createQuotaEmailResponse(QuotaEmailTemplatesVO template) {
@@ -938,6 +939,91 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
return quotaConfigureEmailResponse;
}
+ @Override
+ public Pair<List<QuotaCreditsResponse>, Integer> createQuotaCreditsListResponse(QuotaCreditsListCmd cmd) {
+ List<QuotaCreditsVO> credits = getCreditsForQuotaCreditsList(cmd);
+
+ List<QuotaCreditsResponse> creditResponses = new ArrayList<>();
+ Map<Long, UserVO> userMap = new HashMap<>();
+
+ for (QuotaCreditsVO credit : credits) {
+ UserVO creditor = getCreditorForQuotaCreditsList(credit, userMap);
+ QuotaCreditsResponse response = createQuotaCreditsResponse(credit, creditor);
+ creditResponses.add(response);
+ }
+
+ return new Pair<>(creditResponses, creditResponses.size());
+ }
+
+ protected List<QuotaCreditsVO> getCreditsForQuotaCreditsList(QuotaCreditsListCmd cmd) {
+ Long accountId = cmd.getAccountId();
+ Long domainId = cmd.getDomainId();
+ Date startDate = cmd.getStartDate();
+ Date endDate = cmd.getEndDate();
+ boolean isRecursive = cmd.getRecursive();
+
+ if (ObjectUtils.allNull(accountId, domainId)) {
+ throw new InvalidParameterValueException("Please provide either account ID or domain ID.");
+ }
+
+ if (startDate.after(endDate)) {
+ throw new InvalidParameterValueException("The start date must be before the end date.");
+ }
+
+ Account caller = CallContext.current().getCallingAccount();
+ if (domainId != null && _accountMgr.isNormalUser(caller.getAccountId())) {
+ throw new PermissionDeniedException("Regular users are not allowed to generate domain statements.");
+ }
+
+ return quotaCreditsDao.findCredits(accountId, domainId, startDate, endDate, isRecursive);
+ }
+
+ /**
+ * Returns the creditor user of a <code>QuotaCreditsVO</code>. If <code>userMap</code> contains the user, returns the
+ * user from the map; otherwise, obtains the user from the database and adds it to the map.
+ */
+ protected UserVO getCreditorForQuotaCreditsList(QuotaCreditsVO credit, Map<Long, UserVO> userMap) {
+ Long creditorUserId = credit.getUpdatedBy();
+
+ UserVO userVo = userMap.get(creditorUserId);
+ if (userVo != null) {
+ return userVo;
+ }
+
+ userVo = getCreditorForQuotaCredits(credit);
+ userMap.put(creditorUserId, userVo);
+ return userVo;
+ }
+
+ /**
+ * Returns the creditor user of a <code>QuotaCreditsVO</code> by obtaining it from the database.
+ */
+ protected UserVO getCreditorForQuotaCredits(QuotaCreditsVO credit) {
+ Long creditorUserId = credit.getUpdatedBy();
+ UserVO userVo = _userDao.findByIdIncludingRemoved(creditorUserId);
+ if (userVo == null) {
+ logger.error("Could not find creditor user with ID [{}] for credit [{}].", creditorUserId, credit.toString());
+ throw new CloudRuntimeException("Could not find creditor user.");
+ }
+ return userVo;
+ }
+
+ protected QuotaCreditsResponse createQuotaCreditsResponse(QuotaCreditsVO credit, UserVO creditor) {
+ QuotaCreditsResponse response = new QuotaCreditsResponse();
+
+ if (credit != null) {
+ response.setCredit(credit.getCredit());
+ response.setCreditedOn(credit.getUpdatedOn());
+ response.setCurrency(QuotaConfig.QuotaCurrencySymbol.value());
+ }
+ if (creditor != null) {
+ response.setCreditorUserId(creditor.getUuid());
+ response.setCreditorUsername(creditor.getUsername());
+ }
+ response.setObjectName("credit");
+ return response;
+ }
+
@Override
public QuotaValidateActivationRuleResponse validateActivationRule(QuotaValidateActivationRuleCmd cmd) {
String message;
diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java
index 97d77b8aa22..e02f260c142 100644
--- a/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java
+++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java
@@ -29,6 +29,7 @@ import javax.naming.ConfigurationException;
import org.apache.cloudstack.api.command.QuotaBalanceCmd;
import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd;
import org.apache.cloudstack.api.command.QuotaCreditsCmd;
+import org.apache.cloudstack.api.command.QuotaCreditsListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd;
import org.apache.cloudstack.api.command.QuotaEnabledCmd;
@@ -115,6 +116,7 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi
cmdList.add(QuotaTariffListCmd.class);
cmdList.add(QuotaTariffUpdateCmd.class);
cmdList.add(QuotaCreditsCmd.class);
+ cmdList.add(QuotaCreditsListCmd.class);
cmdList.add(QuotaEmailTemplateListCmd.class);
cmdList.add(QuotaEmailTemplateUpdateCmd.class);
cmdList.add(QuotaTariffCreateCmd.class);
diff --git a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaCreditsListCmdTest.java b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaCreditsListCmdTest.java
new file mode 100644
index 00000000000..26621a52dbc
--- /dev/null
+++ b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaCreditsListCmdTest.java
@@ -0,0 +1,79 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.api.command;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import org.apache.commons.lang3.time.DateUtils;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockedConstruction;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class QuotaCreditsListCmdTest {
+
+ @Spy
+ QuotaCreditsListCmd quotaCreditsListCmdSpy;
+
+ @Test
+ public void getEndDateTestReturnsNewDateWhenEndDateIsNull() {
+ quotaCreditsListCmdSpy.setEndDate(null);
+
+ try (MockedConstruction<Date> dateMockedConstruction = Mockito.mockConstruction(Date.class)) {
+ Date result = quotaCreditsListCmdSpy.getEndDate();
+
+ Assert.assertEquals(1, dateMockedConstruction.constructed().size());
+ Assert.assertEquals(dateMockedConstruction.constructed().get(0), result);
+ }
+ }
+
+ @Test
+ public void getEndDateTestReturnsEndDateWhenItIsNotNull() {
+ Date expected = new Date();
+ quotaCreditsListCmdSpy.setEndDate(expected);
+
+ Date result = quotaCreditsListCmdSpy.getEndDate();
+
+ Assert.assertEquals(expected, result);
+ }
+
+ @Test
+ public void getStartDateTestReturnsFirstDayOfTheCurrentMonthWhenStartDateIsNull() {
+ quotaCreditsListCmdSpy.setStartDate(null);
+ Date expected = DateUtils.truncate(new Date(), Calendar.MONTH);
+
+ Date result = quotaCreditsListCmdSpy.getStartDate();
+
+ Assert.assertEquals(expected, result);
+ }
+
+ @Test
+ public void getStartDateTestReturnsStartDateWhenItIsNotNull() {
+ Date expected = new Date();
+ quotaCreditsListCmdSpy.setStartDate(expected);
+
+ Date result = quotaCreditsListCmdSpy.getStartDate();
+
+ Assert.assertEquals(expected, result);
+ }
+}
diff --git a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java
index a26e6c0476d..1f5480404e4 100644
--- a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java
+++ b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java
@@ -24,6 +24,7 @@ import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -32,12 +33,18 @@ import java.util.function.Consumer;
import com.cloud.domain.DomainVO;
import com.cloud.domain.dao.DomainDao;
+import com.cloud.exception.PermissionDeniedException;
+import com.cloud.user.AccountManager;
+import com.cloud.user.UserVO;
import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd;
+import org.apache.cloudstack.api.command.QuotaCreditsListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd;
import org.apache.cloudstack.api.command.QuotaValidateActivationRuleCmd;
+import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.discovery.ApiDiscoveryService;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.jsinterpreter.JsInterpreterHelper;
@@ -66,6 +73,7 @@ import org.apache.cloudstack.utils.jsinterpreter.JsInterpreter;
import org.apache.commons.lang3.time.DateUtils;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
@@ -81,6 +89,7 @@ import com.cloud.user.dao.UserDao;
import com.cloud.user.User;
import junit.framework.TestCase;
+import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
@@ -135,7 +144,8 @@ public class QuotaResponseBuilderImplTest extends TestCase {
QuotaEmailConfigurationDao quotaEmailConfigurationDaoMock;
@InjectMocks
- QuotaResponseBuilderImpl quotaResponseBuilderSpy = Mockito.spy(QuotaResponseBuilderImpl.class);
+ @Spy
+ QuotaResponseBuilderImpl quotaResponseBuilderSpy;
Date date = new Date();
@@ -154,6 +164,26 @@ public class QuotaResponseBuilderImplTest extends TestCase {
@Mock
QuotaEmailTemplatesVO quotaEmailTemplatesVoMock;
+ @Mock
+ QuotaCreditsVO quotaCreditsVoMock;
+
+ @Mock
+ UserVO userVoMock;
+
+ @Mock
+ AccountManager accountManagerMock;
+
+ @Mock
+ Account callerAccountMock;
+
+ @Mock
+ User callerUserMock;
+
+ @Before
+ public void setup() {
+ CallContext.register(callerUserMock, callerAccountMock);
+ }
+
private void overrideDefaultQuotaEnabledConfigValue(final Object value) throws IllegalAccessException, NoSuchFieldException {
Field f = ConfigKey.class.getDeclaredField("_defaultValue");
f.setAccessible(true);
@@ -221,13 +251,14 @@ public class QuotaResponseBuilderImplTest extends TestCase {
Mockito.when(quotaCreditsDaoMock.saveCredits(Mockito.any(QuotaCreditsVO.class))).thenReturn(credit);
Mockito.when(quotaBalanceDaoMock.lastQuotaBalance(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(Date.class))).thenReturn(new BigDecimal(111));
+ Mockito.doReturn(userVoMock).when(quotaResponseBuilderSpy).getCreditorForQuotaCredits(credit);
AccountVO account = new AccountVO();
account.setState(Account.State.LOCKED);
Mockito.when(accountDaoMock.findById(Mockito.anyLong())).thenReturn(account);
QuotaCreditsResponse resp = quotaResponseBuilderSpy.addQuotaCredits(accountId, domainId, amount, updatedBy, true);
- assertTrue(resp.getCredits().compareTo(credit.getCredit()) == 0);
+ assertTrue(resp.getCredit().compareTo(credit.getCredit()) == 0);
}
@Test
@@ -658,6 +689,136 @@ public class QuotaResponseBuilderImplTest extends TestCase {
assertFalse(quotaResponseBuilderSpy.isUserAllowedToSeeActivationRules(userMock));
}
+ @Test
+ public void createQuotaCreditsListResponseTestReturnsObject() {
+ List<QuotaCreditsVO> credits = new ArrayList<>();
+ credits.add(new QuotaCreditsVO());
+ QuotaCreditsResponse expectedQuotaCreditsResponse = new QuotaCreditsResponse();
+
+ Mockito.doReturn(credits).when(quotaResponseBuilderSpy).getCreditsForQuotaCreditsList(Mockito.any());
+ Mockito.doReturn(userVoMock).when(quotaResponseBuilderSpy).getCreditorForQuotaCreditsList(Mockito.any(), Mockito.any());
+ Mockito.doReturn(expectedQuotaCreditsResponse).when(quotaResponseBuilderSpy).createQuotaCreditsResponse(credits.get(0), userVoMock);
+
+ Pair<List<QuotaCreditsResponse>, Integer> result = quotaResponseBuilderSpy.createQuotaCreditsListResponse(createQuotaCreditsListCmdForTests());
+
+ Assert.assertEquals(expectedQuotaCreditsResponse, result.first().get(0));
+ Assert.assertEquals(1, (int) result.second());
+ }
+
+ private QuotaCreditsListCmd createQuotaCreditsListCmdForTests() {
+ Mockito.doReturn(false).when(accountManagerMock).isNormalUser(Mockito.anyLong());
+ QuotaCreditsListCmd cmd = new QuotaCreditsListCmd();
+ cmd.setAccountId(1L);
+ cmd.setDomainId(2L);
+ return cmd;
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void getCreditsForQuotaCreditsListTestThrowsInvalidParameterValueExceptionWhenBothAccountIdAndDomainIdAreNull() {
+ QuotaCreditsListCmd cmd = new QuotaCreditsListCmd();
+
+ quotaResponseBuilderSpy.getCreditsForQuotaCreditsList(cmd);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void getCreditsForQuotaCreditsListTestThrowsInvalidParameterValueExceptionWhenStartDateIsAfterEndDate() {
+ QuotaCreditsListCmd cmd = createQuotaCreditsListCmdForTests();
+ cmd.setStartDate(new Date());
+ cmd.setEndDate(DateUtils.addDays(new Date(), -1));
+
+ quotaResponseBuilderSpy.getCreditsForQuotaCreditsList(cmd);
+ }
+
+ @Test(expected = PermissionDeniedException.class)
+ public void getCreditsForQuotaCreditsListTestThrowsPermissionDeniedExceptionWhenDomainIdIsProvidedAndCallerIsNormalUser() {
+ QuotaCreditsListCmd cmd = createQuotaCreditsListCmdForTests();
+ Mockito.doReturn(true).when(accountManagerMock).isNormalUser(Mockito.anyLong());
+
+ quotaResponseBuilderSpy.getCreditsForQuotaCreditsList(cmd);
+ }
+
+ @Test
+ public void getCreditsForQuotaCreditsListTestReturnsData() {
+ QuotaCreditsListCmd cmd = createQuotaCreditsListCmdForTests();
+ List<QuotaCreditsVO> expected = new ArrayList<>();
+ expected.add(new QuotaCreditsVO());
+
+ Mockito.doReturn(expected).when(quotaCreditsDaoMock).findCredits(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.any(), Mockito.anyBoolean());
+
+ List<QuotaCreditsVO> result = quotaResponseBuilderSpy.getCreditsForQuotaCreditsList(cmd);
+
+ Assert.assertEquals(expected, result);
+ }
+
+ @Test
+ public void getCreditorForQuotaCreditsListTestReturnsUserFromMapWhenMapHasCreditor() {
+ Long creditorId = 1L;
+ Map<Long, UserVO> userMap = new HashMap<>();
+
+ userMap.put(creditorId, userVoMock);
+ Mockito.doReturn(creditorId).when(quotaCreditsVoMock).getUpdatedBy();
+
+ UserVO result = quotaResponseBuilderSpy.getCreditorForQuotaCreditsList(quotaCreditsVoMock, userMap);
+
+ Assert.assertEquals(userVoMock, result);
+ }
+
+ @Test
+ public void getCreditorForQuotaCreditsListTestGetsCreditorFromDatabaseAndAddsItToMapWhenMapDoesNotHaveCreditor() {
+ Long creditorId = 1L;
+ Map<Long, UserVO> userMap = new HashMap<>();
+
+ Mockito.doReturn(creditorId).when(quotaCreditsVoMock).getUpdatedBy();
+ Mockito.doReturn(userVoMock).when(userDaoMock).findByIdIncludingRemoved(creditorId);
+
+ UserVO result = quotaResponseBuilderSpy.getCreditorForQuotaCreditsList(quotaCreditsVoMock, userMap);
+
+ Assert.assertEquals(userVoMock, result);
+ Assert.assertEquals(userVoMock, userMap.get(creditorId));
+ }
+
+ @Test
+ public void getCreditorForQuotaCreditsTestReturnsCreditorWhenCreditorExists() {
+ Long creditorId = 1L;
+
+ Mockito.when(quotaCreditsVoMock.getUpdatedBy()).thenReturn(creditorId);
+ Mockito.doReturn(userVoMock).when(userDaoMock).findByIdIncludingRemoved(creditorId);
+
+ UserVO result = quotaResponseBuilderSpy.getCreditorForQuotaCredits(quotaCreditsVoMock);
+
+ Assert.assertEquals(userVoMock, result);
+ }
+
+ @Test(expected = CloudRuntimeException.class)
+ public void getCreditorForQuotaCreditsTestThrowsCloudRuntimeExceptionWhenCreditorDoesNotExist() {
+ quotaResponseBuilderSpy.getCreditorForQuotaCredits(quotaCreditsVoMock);
+ }
+
+ @Test
+ public void createQuotaCreditsResponseTestReturnsObject() {
+ QuotaCreditsResponse expected = new QuotaCreditsResponse();
+ expected.setCreditorUserId("test_uuid");
+ expected.setCreditorUsername("test_name");
+ expected.setCredit(new BigDecimal(41.5));
+ expected.setCreditedOn(new Date());
+ expected.setCurrency(QuotaConfig.QuotaCurrencySymbol.value());
+ expected.setObjectName("credit");
+
+ Mockito.when(userVoMock.getUuid()).thenReturn(expected.getCreditorUserId());
+ Mockito.when(userVoMock.getUsername()).thenReturn(expected.getCreditorUsername());
+ Mockito.when(quotaCreditsVoMock.getCredit()).thenReturn(expected.getCredit());
+ Mockito.when(quotaCreditsVoMock.getUpdatedOn()).thenReturn(expected.getCreditedOn());
+
+ QuotaCreditsResponse result = quotaResponseBuilderSpy.createQuotaCreditsResponse(quotaCreditsVoMock, userVoMock);
+
+ Assert.assertEquals(expected.getCreditorUserId(), result.getCreditorUserId());
+ Assert.assertEquals(expected.getCreditorUsername(), result.getCreditorUsername());
+ Assert.assertEquals(expected.getCredit(), result.getCredit());
+ Assert.assertEquals(expected.getCreditedOn(), result.getCreditedOn());
+ Assert.assertEquals(expected.getCurrency(), result.getCurrency());
+ Assert.assertEquals(expected.getObjectName(), result.getObjectName());
+ }
+
@Test
public void validateActivationRuleTestValidateActivationRuleReturnValidScriptResponse() {
Mockito.doReturn("if (account.name == 'test') { true } else { false }").when(quotaValidateActivationRuleCmdMock).getActivationRule();