blob: d5f29969ab8fb452025f608ac0a024c56d076b76 [file] [log] [blame]
/*
* 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 io.mifos.portfolio;
import com.google.gson.Gson;
import io.mifos.individuallending.api.v1.domain.product.ProductParameters;
import io.mifos.individuallending.api.v1.domain.workflow.Action;
import io.mifos.portfolio.api.v1.client.ProductAlreadyExistsException;
import io.mifos.portfolio.api.v1.client.ProductDefinitionIncomplete;
import io.mifos.portfolio.api.v1.client.ProductInUseException;
import io.mifos.portfolio.api.v1.domain.AccountAssignment;
import io.mifos.portfolio.api.v1.domain.BalanceRange;
import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
import io.mifos.portfolio.api.v1.domain.InterestBasis;
import io.mifos.portfolio.api.v1.domain.InterestRange;
import io.mifos.portfolio.api.v1.domain.Product;
import io.mifos.portfolio.api.v1.domain.ProductPage;
import io.mifos.portfolio.api.v1.domain.TermRange;
import io.mifos.portfolio.api.v1.events.ChargeDefinitionEvent;
import io.mifos.portfolio.api.v1.events.EventConstants;
import java.math.BigDecimal;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.apache.fineract.cn.api.util.NotFoundException;
import org.apache.fineract.cn.test.domain.TimeStampChecker;
import org.junit.Assert;
import org.junit.Test;
/**
* @author Myrle Krantz
*/
public class TestProducts extends AbstractPortfolioTest {
@Test
public void shouldCreateAndEnableProduct() throws InterruptedException {
final Product product = createAdjustedProduct(x -> {});
final Product productAsSaved = portfolioManager.getProduct(product.getIdentifier());
Assert.assertEquals(product, productAsSaved);
Assert.assertFalse(productAsSaved.isEnabled());
Assert.assertFalse(portfolioManager.getProductEnabled(product.getIdentifier()));
{
final ProductPage productsPage = portfolioManager.getProducts(true, null, 0, 100, null, null);
Assert.assertTrue(productsPage.getElements().contains(productAsSaved));
}
{
final ProductPage productsPage = portfolioManager.getProducts(true, product.getIdentifier().substring(2, 5), 0, 100, null, null);
Assert.assertTrue(productsPage.getElements().contains(productAsSaved));
}
{
final ProductPage productsPage = portfolioManager.getProducts(false, null, 0, 100, null, null);
Assert.assertFalse(productsPage.getElements().contains(productAsSaved));
}
{
final ProductPage productsPage = portfolioManager.getProducts(false, product.getIdentifier().substring(2, 5), 0, 100, null, null);
Assert.assertFalse(productsPage.getElements().contains(productAsSaved));
}
portfolioManager.enableProduct(product.getIdentifier(), true);
Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_PRODUCT_ENABLE, product.getIdentifier()));
final Product productAfterEnable = portfolioManager.getProduct(product.getIdentifier());
Assert.assertTrue(productAfterEnable.isEnabled());
Assert.assertTrue(portfolioManager.getProductEnabled(product.getIdentifier()));
{
final ProductPage productsPage = portfolioManager.getProducts(false, null, 0, 100, null, null);
Assert.assertTrue(productsPage.getElements().contains(productAsSaved));
}
{
final ProductPage productsPage = portfolioManager.getProducts(false, product.getIdentifier().substring(2, 5), 0, 100, null, null);
Assert.assertTrue(productsPage.getElements().contains(productAsSaved));
}
}
@Test
public void shouldCorrectlyOrderProducts() throws InterruptedException {
final Product productA = createAdjustedProduct(x -> {
x.setIdentifier("aaaaaaaa");
x.setName("ZZZZZZZ");
});
final Product productZ = createAdjustedProduct(x -> {
x.setIdentifier("zzzzzzzz");
x.setName("AAAAAAA");
});
final Product productASaved = portfolioManager.getProduct(productA.getIdentifier());
final Product productZSaved = portfolioManager.getProduct(productZ.getIdentifier());
{
final ProductPage productsPage = portfolioManager.getProducts(true, null, 0, 100, null, null);
Assert.assertEquals(productZSaved, productsPage.getElements().get(0)); //Modified by ordering.
}
{
final ProductPage productsPage = portfolioManager.getProducts(true, null, 0, 100, "identifier", "ASC");
Assert.assertEquals(productASaved, productsPage.getElements().get(0)); //Alphabetic ordering by identifier
}
{
final ProductPage productsPage = portfolioManager.getProducts(true, null, 0, 100, "name", "DESC");
Assert.assertEquals(productASaved, productsPage.getElements().get(0)); //Alphabetic ordering by name. Descending.
}
}
@Test
public void badArgumentsToSortOrderAndDirectionShouldThrow() throws InterruptedException {
try {
portfolioManager.getProducts(true, null, 0, 100, null, "asc");
Assert.fail("Should've thrown");
}
catch (final IllegalArgumentException ignored) { }
try {
portfolioManager.getProducts(true, null, 0, 100, null, "ACS");
Assert.fail("Should've thrown");
}
catch (final IllegalArgumentException ignored) { }
try {
portfolioManager.getProducts(true, null, 0, 100, "non-existent-column", null);
Assert.fail("Should've thrown");
}
catch (final IllegalArgumentException ignored) { }
try {
portfolioManager.getProducts(true, null, 0, 100, "", null);
Assert.fail("Should've thrown");
}
catch (final IllegalArgumentException ignored) { }
}
@Test
public void shouldCreateProductWithMaximumLengthEverything() throws InterruptedException {
final Product product = getTestProductWithMaximumLengthEverything();
portfolioManager.createProduct(product);
Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_PRODUCT, product.getIdentifier()));
final Product productAsSaved = portfolioManager.getProduct(product.getIdentifier());
Assert.assertEquals(product, productAsSaved);
}
@Test
public void shouldChangeProductAccountAssignments() throws InterruptedException {
final Product product = createAdjustedProduct(x -> x.setAccountAssignments(Collections.emptySet()));
//Add account assignments
final Set<AccountAssignment> incompleteAccountAssignments = portfolioManager.getIncompleteAccountAssignments(product.getIdentifier());
final Set<AccountAssignment> accountAssignments = incompleteAccountAssignments.stream()
.map(x -> new AccountAssignment(x.getDesignator(), testEnvironment.generateUniqueIdentifer("account")))
.collect(Collectors.toSet());
product.setAccountAssignments(accountAssignments);
TimeUnit.SECONDS.sleep(3);
final TimeStampChecker timeStampChecker = TimeStampChecker.roughlyNow();
portfolioManager.changeProduct(product.getIdentifier(), product);
Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_PRODUCT, product.getIdentifier()));
{
final Product productAsSaved = portfolioManager.getProduct(product.getIdentifier());
Assert.assertEquals(product, productAsSaved);
Assert.assertEquals(TEST_USER, productAsSaved.getLastModifiedBy());
timeStampChecker.assertCorrect(productAsSaved.getLastModifiedOn());
}
//Remove account assignments
product.setAccountAssignments(Collections.emptySet());
portfolioManager.changeProduct(product.getIdentifier(), product);
Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_PRODUCT, product.getIdentifier()));
{
final Product productAsSaved = portfolioManager.getProduct(product.getIdentifier());
Assert.assertEquals(product, productAsSaved);
}
}
@Test
public void shouldRemoveProductAccountAssignments() throws InterruptedException {
final Product product = createAdjustedProduct(x -> {});
final Set<AccountAssignment> incompleteAccountAssignments = portfolioManager.getIncompleteAccountAssignments(product.getIdentifier());
Assert.assertTrue(incompleteAccountAssignments.isEmpty());
product.setAccountAssignments(Collections.emptySet());
portfolioManager.changeProduct(product.getIdentifier(), product);
Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_PRODUCT, product.getIdentifier()));
final Product productAsSaved = portfolioManager.getProduct(product.getIdentifier());
Assert.assertTrue("Account assignments should be empty, but contain: " + productAsSaved.getAccountAssignments(),
productAsSaved.getAccountAssignments().isEmpty());
Assert.assertEquals(product, productAsSaved);
final Set<AccountAssignment> incompleteAccountAssignmentsAfterChange
= portfolioManager.getIncompleteAccountAssignments(product.getIdentifier());
Assert.assertFalse("Incomplete account assignments should not be empty, but is. (Beware the double negative.)",
incompleteAccountAssignmentsAfterChange.isEmpty());
}
@Test
public void incompleteProductDefinitionCantBeActivated() throws InterruptedException {
final Product product = Fixture.createAdjustedProduct(x -> {});
portfolioManager.createProduct(product);
final ChargeDefinition chargeDefinitionContainingIncompleteAccountAssignment = new ChargeDefinition();
chargeDefinitionContainingIncompleteAccountAssignment.setIdentifier("rando123456");
chargeDefinitionContainingIncompleteAccountAssignment.setName("ditto12356");
chargeDefinitionContainingIncompleteAccountAssignment.setFromAccountDesignator("not-there");
chargeDefinitionContainingIncompleteAccountAssignment.setToAccountDesignator("not-here");
chargeDefinitionContainingIncompleteAccountAssignment.setAmount(BigDecimal.ONE);
chargeDefinitionContainingIncompleteAccountAssignment.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
chargeDefinitionContainingIncompleteAccountAssignment.setChargeAction(Action.OPEN.name());
chargeDefinitionContainingIncompleteAccountAssignment.setDescription("who cares what the description is?");
portfolioManager.createChargeDefinition(product.getIdentifier(), chargeDefinitionContainingIncompleteAccountAssignment);
Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_CHARGE_DEFINITION,
new ChargeDefinitionEvent(product.getIdentifier(), chargeDefinitionContainingIncompleteAccountAssignment.getIdentifier())));
try {
portfolioManager.enableProduct(product.getIdentifier(), true);
Assert.fail("Enable should fail if product definition contains an non-existant account assignment.");
}
catch (ProductDefinitionIncomplete ignored) {
}
}
@Test(expected = IllegalArgumentException.class)
public void nonExistentPatternPackageShouldThrow() throws InterruptedException {
createAdjustedProduct(product -> product.setPatternPackage("be.bop.do.wap"));
}
@Test(expected = ProductAlreadyExistsException.class)
public void duplicateProductIdentifierShouldThrow() throws InterruptedException {
createAdjustedProduct(product -> product.setIdentifier("ditto"));
createAdjustedProduct(product -> product.setIdentifier("ditto"));
}
@Test
public void shouldFailToChangeProductAfterCaseHasBeenCreated() throws InterruptedException {
final Product product = createAndEnableProduct();
createCase(product.getIdentifier());
final Product slightlyChangedProduct = Fixture.createAdjustedProduct(x -> x.setDescription("changed description."));
slightlyChangedProduct.setIdentifier(product.getIdentifier());
try {
portfolioManager.changeProduct(product.getIdentifier(), slightlyChangedProduct);
Assert.fail("This should throw a ProductInUseException.");
}
catch (final ProductInUseException ignore) {
}
final Product productAsSaved = portfolioManager.getProduct(product.getIdentifier());
Assert.assertEquals(product, productAsSaved);
Assert.assertNotEquals(slightlyChangedProduct, productAsSaved);
Assert.assertEquals(TEST_USER, productAsSaved.getLastModifiedBy());
portfolioManager.enableProduct(product.getIdentifier(), false);
Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_PRODUCT_ENABLE, product.getIdentifier()));
Assert.assertFalse(portfolioManager.getProductEnabled(product.getIdentifier()));
}
@Test(expected = ProductInUseException.class)
public void shouldFailToDeleteProductAfterCaseHasBeenCreated() throws InterruptedException {
final Product product = createAndEnableProduct();
createCase(product.getIdentifier());
portfolioManager.deleteProduct(product.getIdentifier());
}
@Test(expected = NotFoundException.class)
public void shouldFailToDeleteNonExistentProduct() throws InterruptedException {
portfolioManager.deleteProduct("habberdash");
}
@Test(expected = IllegalArgumentException.class)
public void shouldFailToCreateProductDirectlyAsEnabled() throws InterruptedException {
final Product product = Fixture.createAdjustedProduct(x -> {});
product.setEnabled(true);
portfolioManager.createProduct(product);
}
@Test
public void shouldDeleteProduct() throws InterruptedException {
final Product product = createAndEnableProduct();
try {
portfolioManager.deleteProduct(product.getIdentifier());
Assert.fail("Product is enabled. It shouldn't be possible to delete it.");
}
catch (final ProductInUseException ignored) {}
portfolioManager.enableProduct(product.getIdentifier(), false);
Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_PRODUCT_ENABLE, product.getIdentifier()));
portfolioManager.deleteProduct(product.getIdentifier());
Assert.assertTrue(this.eventRecorder.wait(EventConstants.DELETE_PRODUCT, product.getIdentifier()));
try {
portfolioManager.getProduct(product.getIdentifier());
Assert.fail("product should not be found after delete.");
}
catch (final NotFoundException ignored) {}
}
private Product getTestProductWithMaximumLengthEverything()
{
final Product product = new Product();
product.setPatternPackage("io.mifos.individuallending.api.v1");
product.setIdentifier(StringUtils.repeat("x", 8));
product.setName(StringUtils.repeat("x", 256));
product.setDescription(StringUtils.repeat("x", 4096));
product.setTermRange(new TermRange(ChronoUnit.MONTHS, 12));
product.setBalanceRange(new BalanceRange(BigDecimal.ZERO.setScale(4, BigDecimal.ROUND_UNNECESSARY), new BigDecimal(10000).setScale(4, BigDecimal.ROUND_UNNECESSARY)));
product.setInterestRange(new InterestRange(new BigDecimal("999.98"), new BigDecimal("999.99")));
product.setInterestBasis(InterestBasis.CURRENT_BALANCE);
product.setCurrencyCode("XTS");
product.setMinorCurrencyUnitDigits(4);
final Set<AccountAssignment> accountAssignments = new HashSet<>();
accountAssignments.add(new AccountAssignment(StringUtils.repeat("x", 32), StringUtils.repeat("x", 8)));
product.setAccountAssignments(accountAssignments);
final ProductParameters productParameters = new ProductParameters();
productParameters.setMoratoriums(Collections.emptyList());
productParameters.setMaximumDispersalCount(5);
final Gson gson = new Gson();
product.setParameters(gson.toJson(productParameters));
return product;
}
}