/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.gl.businessobject.lookup;
import java.sql.Date;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.gl.Constant;
import org.kuali.kfs.gl.businessobject.Balance;
import org.kuali.kfs.gl.businessobject.CurrentAccountBalance;
import org.kuali.kfs.gl.service.AccountBalanceService;
import org.kuali.kfs.sys.ConfigureContext;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.ObjectUtil;
import org.kuali.kfs.sys.businessobject.lookup.LookupableSpringContext;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.fixture.AccountFixture;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.PersonService;
import org.kuali.rice.kns.lookup.HtmlData.AnchorHtmlData;
import org.kuali.rice.krad.bo.PersistableBusinessObjectBase;
import org.kuali.rice.krad.datadictionary.DataDictionary;
import org.kuali.rice.krad.exception.ValidationException;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.service.DataDictionaryService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.ObjectUtils;
@ConfigureContext
public class CurrentAccountBalanceLookupableHelperServiceTest extends AbstractGeneralLedgerLookupableHelperServiceTestBase {
// Enum, for sake of readability in method calls
private enum ExpectException{YES, NO};
// Class variables, service key, namely the class being tested
private static final String LOOKUP_HELPER_SERVICE_KEY = "glCurrentAccountBalanceLookupableHelperService";
// Class variables, search parameter keys
private static final String UNIVERSITY_FISCAL_YEAR_KEY = KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR; // universityFiscalYear
private static final String UNIVERSITY_FISCAL_PERIOD_CODE_KEY = KFSPropertyConstants.UNIVERSITY_FISCAL_PERIOD_CODE; // universityFiscalPeriodCode
private static final String CHART_OF_ACCOUNTS_CODE_KEY = KFSPropertyConstants.ACCOUNT + "." + KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE; // chartOfAccountsCode
private static final String ACCOUNT_NUMBER_KEY = KFSPropertyConstants.ACCOUNT + "." + KFSPropertyConstants.ACCOUNT_NUMBER; // account.accountNumber
private static final String FISCAL_OFFICER_PRINCIPAL_NAME_KEY = KFSPropertyConstants.ACCOUNT + "." + KFSPropertyConstants.ACCOUNT_FISCAL_OFFICER_USER + "." + KFSPropertyConstants.PERSON_USER_ID; // account.accountFiscalOfficerUser.principalName;
private static final String ACCOUNT_SUPERVISOR_PRINCIPAL_NAME_KEY = KFSPropertyConstants.ACCOUNT + "." + KFSPropertyConstants.ACCOUNT_SUPERVISORY_USER + "." + KFSPropertyConstants.PERSON_USER_ID; // account.accountSupervisoryUser.principalName
private static final String SUB_ACCOUNT_NUMBER_KEY = KFSPropertyConstants.SUB_ACCOUNT_NUMBER; // subAccountNumber
private static final String ORGINIZATION_CODE_KEY = KFSPropertyConstants.ACCOUNT + "." + KFSPropertyConstants.ORGANIZATION_CODE; // account.organizationCode
// Class variables, magic strings; necessity since these values (mostly) weren't found in any Constants or Fixture classes
private static final String FISCAL_OFFICER_PRINCIPAL_NAME_VAL = "mhkozlow"; // Name in UserNameFixture.java, but wish to stay consistant
private static final String ACCOUNT_SUPERVISOR_PRINCIPAL_NAME_VAL = "jaraujo"; // Name not in UserNameFixture.java
private static final String ORGINIZATION_CODE_VAL = "PSY"; // Only found in DB bootstrap *.sql
private static final String ACCOUNT_EXPIRATION_DATE = "2101-09-30";
private static final Integer UNIVERSITY_FISCAL_YEAR = 2014;
// Instance variables, required service classes
private AccountBalanceService accountBalanceService;
private PersonService personService;
private DataDictionary dataDictionary;
// Instance variables, vanilla
private Map<String, String> fullFieldToValueMap;
private Map<String, String> requiredFieldToValueMap;
private Map<String, String> semiRequiredFieldToValueMap;
private Map<String, String> optionalFieldToValueMap;
/**
* This method:
* 1. Calls super.setUp()
* 2. Sets service spring beans
* 3. Initializes ancestor's testDataGenerator
* 4. Collects entries placed in local maps used for test data
* 5. Generates an Account and sets it in delegate of ancestor
*
* @see org.kuali.kfs.gl.businessobject.lookup.AbstractGeneralLedgerLookupableHelperServiceTestBase#setUp()
*/
@Override
protected void setUp() throws Exception {
super.setUp();
accountBalanceService = SpringContext.getBean(AccountBalanceService.class);
personService = SpringContext.getBean(PersonService.class);
dataDictionary = SpringContext.getBean(DataDictionaryService.class).getDataDictionary();
lookupableHelperServiceImpl = LookupableSpringContext.getLookupableHelperService(LOOKUP_HELPER_SERVICE_KEY);
lookupableHelperServiceImpl.setBusinessObjectClass(CurrentAccountBalance.class);
testDataGenerator.generateTransactionData(pendingEntry);
initFieldToValueMaps(); // generateAccount() is dependant on this
pendingEntry.setAccount(generateAccount());
}
/*
* Helper method that sets up each type of search parameter map.
*/
private void initFieldToValueMaps(){
initRequiredFieldToValueMap();
initSemiRequiredFieldToValueMap();
initOptionalFieldToValueMap();
initFullFieldToValueMap();
}
/*
* Initialize required search parameter key/value pairs.
*/
private void initRequiredFieldToValueMap(){
requiredFieldToValueMap = new HashMap<String, String>();
requiredFieldToValueMap.put(UNIVERSITY_FISCAL_YEAR_KEY, UNIVERSITY_FISCAL_YEAR.toString());
requiredFieldToValueMap.put(UNIVERSITY_FISCAL_PERIOD_CODE_KEY, pendingEntry.getUniversityFiscalPeriodCode());
requiredFieldToValueMap.put(Constant.CONSOLIDATION_OPTION, Constant.CONSOLIDATION);
}
/*
* Initialize "at-least-one" search parameter key/value pairs.
*/
private void initSemiRequiredFieldToValueMap(){
/*
* These are related to creating an Account object to set on the
* pendingEntry object, and a subsequent join on the Account table
* during DB searches.
*/
semiRequiredFieldToValueMap = new HashMap<String, String>();
semiRequiredFieldToValueMap.put(ACCOUNT_NUMBER_KEY, pendingEntry.getAccountNumber());
semiRequiredFieldToValueMap.put(CHART_OF_ACCOUNTS_CODE_KEY, pendingEntry.getChartOfAccountsCode());
semiRequiredFieldToValueMap.put(FISCAL_OFFICER_PRINCIPAL_NAME_KEY, FISCAL_OFFICER_PRINCIPAL_NAME_VAL);
semiRequiredFieldToValueMap.put(ACCOUNT_SUPERVISOR_PRINCIPAL_NAME_KEY, ACCOUNT_SUPERVISOR_PRINCIPAL_NAME_VAL);
}
/*
* Initialize completely optional search parameter key/value pairs.
*/
private void initOptionalFieldToValueMap(){
optionalFieldToValueMap = new HashMap<String, String>();
optionalFieldToValueMap.put(SUB_ACCOUNT_NUMBER_KEY, pendingEntry.getSubAccountNumber());
// This correlates to the test accountNumber and is
// joined across the real Account table, so must be valid
// for this accoutNumber. Could not find any test fixtures
// or constants class to pull this from.
optionalFieldToValueMap.put(ORGINIZATION_CODE_KEY, ORGINIZATION_CODE_VAL);
}
/*
* Combine all search parameter key/value pairs into ine map.
*/
private void initFullFieldToValueMap(){
fullFieldToValueMap = new HashMap<String, String>();
fullFieldToValueMap.putAll(requiredFieldToValueMap);
fullFieldToValueMap.putAll(semiRequiredFieldToValueMap);
fullFieldToValueMap.putAll(optionalFieldToValueMap);
}
/**
* This method tests the returned hyperlink for each of the search result's
* column values if the value's type has an inquiry page. For instance, the
* column "Fiscal Year" might have a value of "2014", and the "2014" value
* would be hyperlinked to perform an auto search and return the user to a
* "System Options Inquiry" results page with details about that
* "Fiscal Year" entry.
*
* In this specific test case, the "subAccountCode" hyperlink should not
* exist when the "Consolidation Option" of the search is selected to be
* "Consolidation". Selecting this option means that subAccountNumber is
* not displayed, and the "----" placeholder should not be used as a
* subAccountNumber inquiry hyperlink.
*
* @see org.kuali.kfs.gl.businessobject.lookup.AbstractGeneralLedgerLookupableHelperServiceTestBase#testGetInquiryUrl()
*/
@Override
public void testGetInquiryUrl() {
// Set sentinal that there is no subAccountNumber present
pendingEntry.setSubAccountNumber(Constant.CONSOLIDATED_SUB_ACCOUNT_NUMBER);
Balance balance = new Balance(pendingEntry);
CurrentAccountBalance currentAccountBalance = generateCurrentAccountBalance(balance);
List<String> inquiryFieldNames = new ArrayList<String>();
inquiryFieldNames.add(UNIVERSITY_FISCAL_YEAR_KEY);
inquiryFieldNames.add(UNIVERSITY_FISCAL_PERIOD_CODE_KEY);
inquiryFieldNames.add(CHART_OF_ACCOUNTS_CODE_KEY);
inquiryFieldNames.add(ORGINIZATION_CODE_KEY);
inquiryFieldNames.add(FISCAL_OFFICER_PRINCIPAL_NAME_KEY);
inquiryFieldNames.add(SUB_ACCOUNT_NUMBER_KEY);
//List<String> inquiryFieldNames = new ArrayList<String>();
for(String fieldName : inquiryFieldNames){
AnchorHtmlData htmlData = (AnchorHtmlData)lookupableHelperServiceImpl.getInquiryUrl(currentAccountBalance, fieldName);
assertTrue("Null inquiryUrl for property: " + fieldName, ObjectUtils.isNotNull(htmlData));
String href = htmlData.getHref();
if(SUB_ACCOUNT_NUMBER_KEY.equals(fieldName)){
assertTrue("The href anchor for the property " + fieldName + "is not blank.", StringUtils.isBlank(href));
}else{
assertTrue("The href anchor for the property " + fieldName + "is blank.", StringUtils.isNotBlank(href));
}
}
// Revert back to original test value for other tests
pendingEntry.setSubAccountNumber(optionalFieldToValueMap.get(SUB_ACCOUNT_NUMBER_KEY));
}
/**
* This test validates search paramaters specific to CurrentAccountBalanceLookupableHelperServiceImpl.
*
* Validation of actual values via services only occur for universityFiscalYear and
* universityFiscalPeriodCode. This means that test values must be present in the
* coresponding FS_OPTION_T and SH_ACCT_PERIOD_T DB tables.
*
* Validation for the remaining parameters consist of ensuring the parameters line up
* with public properties of the CurrentAccountBalance class, as defined in the
* CurrentAccountBalance.xml Data Dictionary(DD) configuration, and that the values of
* the properties are non-null if they are defined as such in the DD.
*
* Additionally, it should be noted that this method tests the various combinations
* of parameters, since one subset is always required, another subset requires only
* one from the set, and the final subset is entirely optional.
*
* Coverage for Override of {@link org.kuali.kfs.gl.businessobject.lookup.AbstractGeneralLedgerLookupableHelperServiceImpl#validateSearchParameters(java.util.Map)}
*/
public void testValidateSearchParameters(){
/*
* Search Parameter Subsets:
*
* Both required:
* - universityFiscalYear
* - universityFiscalPeriodCode
*
* At least one required (aka, semi-required):
* - account.accountNumber
* - account.organizationCode
* - account.accountFiscalOfficerUser.principalName
* - account.accountSupervisoryUser.principalName
*
* Optional:
* - chartOfAccountsCode
* - subAccountNumber
*/
// Data structure to hold different combinations of parameters.
Map<String, String> testMap = new HashMap<String, String>();
// Test with no parameters, should throw exception
// Validating parameters: []
validateMap(testMap, ExpectException.YES);
// Test with required fields, should throw exception.
// Validating parameters: [universityFiscalYear, universityFiscalPeriodCode]
testMap.putAll(requiredFieldToValueMap);
validateMap(testMap, ExpectException.YES);
// Test the "at least one required" params, should *not* throw exception.
// Validating parameters: [universityFiscalYear, universityFiscalPeriodCode, account.accountNumber,
// account.organizationCode, account.accountSupervisoryUser.principalName,
// account.accountFiscalOfficerUser.principalName, chartOfAccountsCode]
testMap.putAll(semiRequiredFieldToValueMap);
validateMap(testMap, ExpectException.NO);
// Test with "optional" params, should *not* throw exception.
// Validating parameters: [universityFiscalYear, universityFiscalPeriodCode, account.accountNumber,
// account.organizationCode, account.accountSupervisoryUser.principalName,
// account.accountFiscalOfficerUser.principalName, chartOfAccountsCode]
testMap.putAll(optionalFieldToValueMap);
validateMap(testMap, ExpectException.NO);
// Test with all parameters present
validateMap(fullFieldToValueMap, ExpectException.NO);
}
/*
* This method will:
* 1. Create a "failure" message containing the field parameters being vaildated
* 2. Perform validation against the input map, both positive and negative testing
* 3. Clear any global error messages since an error count will fail other tests
*/
private void validateMap(Map<String, String> fieldToValueMap, ExpectException shouldExpectException){
String message = String.format("Validation failed for search parameters: %s", fieldToValueMap.keySet());
try{
if(shouldExpectException == ExpectException.YES){
assertTrue(message, validateSearchParametersThrowsException(fieldToValueMap));
}else{
assertFalse(message, validateSearchParametersThrowsException(fieldToValueMap));
}
} finally {
// Need to ensure this gets cleared -- negative tests will generate
// a ValidationException, which in turn creates an error message
GlobalVariables.getMessageMap().clearErrorMessages();
}
}
/*
* Note, this helper method will propagate any Throwable that is not of
* type ValidationException -- this is on purpose, as we want the test
* to blow up if an unexpected error manifests.
*/
private boolean validateSearchParametersThrowsException(Map<String, String> fieldToValueMap) {
try{
lookupableHelperServiceImpl.validateSearchParameters(fieldToValueMap);
}catch(ValidationException e){
return true;
}
return false;
}
/**
* This method will run several searches with various combos
* of search parameter key/value pairs.
*
* @throw java.lang.Exception Thrown when any exception occurs that is not of type ValidationException
* @see org.kuali.kfs.gl.businessobject.lookup.AbstractGeneralLedgerLookupableHelperServiceTestBase#testGetSearchResults()
*/
@Override
public void testGetSearchResults() throws Exception {
// Used to persist generated record
Balance balance = new Balance(pendingEntry);
balance.setUniversityFiscalYear(UNIVERSITY_FISCAL_YEAR);
// Used for testing lookup
CurrentAccountBalance currentAccountBalance = generateCurrentAccountBalance(balance);
// Test without having inserted Balance
Map<String, String> fieldValues = getLookupFieldValues(currentAccountBalance, false);
List searchResults = lookupableHelperServiceImpl.getSearchResults(fieldValues);
assertTrue(testDataGenerator.getMessageValue("noSuchRecord"), !contains(searchResults, currentAccountBalance));
// Add record to DB
insertNewRecord(balance);
// Search with only the required params, should return 1 result
fieldValues = getLookupFieldValues(currentAccountBalance, false);
searchResults = lookupableHelperServiceImpl.getSearchResults(fieldValues);
int numOfFirstResult = searchResults.size();
assertTrue(testDataGenerator.getMessageValue("wrongRecordSize"), searchResults.size() >= 1);
assertTrue(testDataGenerator.getMessageValue("failToFindRecord"), contains(searchResults, currentAccountBalance));
// Search with all available search params, should return 1 result
fieldValues = getLookupFieldValues(currentAccountBalance, true);
searchResults = lookupableHelperServiceImpl.getSearchResults(fieldValues);
assertTrue(testDataGenerator.getMessageValue("wrongRecordSize"), searchResults.size() >= numOfFirstResult);
assertTrue(testDataGenerator.getMessageValue("failToFindRecord"), contains(searchResults, currentAccountBalance));
}
/**
* This method will return a map of entries suitable for building a record
* query.
*
* Need to override since TestDataGenerator only handles key/value pairs
* contained in the data.properties test file.
*
* If one places properties in the file that are not relevant to a Transaction objects,
* an exception is thrown.
*
* @param businessObject Moot, this is just to follow the parents method signature
* @param isExtended If true, will include all search kay/pair values, if false, will only return bare minimum kay/pair values
* @see org.kuali.kfs.gl.businessobject.lookup.AbstractGeneralLedgerLookupableHelperServiceTestBase#getLookupFieldValues(org.kuali.rice.krad.bo.PersistableBusinessObjectBase, boolean)
*/
@Override
public Map<String, String> getLookupFieldValues(PersistableBusinessObjectBase businessObject, boolean isExtended) throws Exception{
if(isExtended){
return fullFieldToValueMap;
}else{
// Don't add in the optional fields
Map<String, String> results = new HashMap<String, String>();
results.putAll(requiredFieldToValueMap);
results.put(ACCOUNT_NUMBER_KEY, semiRequiredFieldToValueMap.get(ACCOUNT_NUMBER_KEY));
return results;
}
}
/*
* Build a CuurentAccountBalance from the given Balance
*/
private CurrentAccountBalance generateCurrentAccountBalance(Balance balance){
CurrentAccountBalance currentAccountBalance = new CurrentAccountBalance();
ObjectUtil.buildObject(currentAccountBalance, balance);
currentAccountBalance.setUniversityFiscalYear(UNIVERSITY_FISCAL_YEAR);
currentAccountBalance.setAccount(generateAccount());
currentAccountBalance.setUniversityFiscalPeriodCode(requiredFieldToValueMap.get(UNIVERSITY_FISCAL_PERIOD_CODE_KEY));
return currentAccountBalance;
}
/*
* Create a minimal Account object, which will be set on
* super.pendingEntry, and used in generating test data.
*/
private Account generateAccount(){
Account account = AccountFixture.ACCOUNT_PRESENCE_ACCOUNT.createAccount();
account.setAccountExpirationDate(Date.valueOf(ACCOUNT_EXPIRATION_DATE));
account.setActive(true);
account.setAccountNumber(testDataGenerator.getPropertyValue(KFSPropertyConstants.ACCOUNT_NUMBER));
account.setChartOfAccountsCode(pendingEntry.getChartOfAccountsCode());
account.setOrganizationCode(optionalFieldToValueMap.get(ORGINIZATION_CODE_KEY));
Person accountFiscalOfficerUser = personService.getPersonByPrincipalName(semiRequiredFieldToValueMap.get(FISCAL_OFFICER_PRINCIPAL_NAME_KEY));
account.setAccountFiscalOfficerSystemIdentifier(accountFiscalOfficerUser.getPrincipalId());
account.setAccountFiscalOfficerUser(accountFiscalOfficerUser);
Person accountSupervisoryUser = personService.getPersonByPrincipalName(semiRequiredFieldToValueMap.get(ACCOUNT_SUPERVISOR_PRINCIPAL_NAME_KEY));
account.setAccountFiscalOfficerSystemIdentifier(accountSupervisoryUser.getPrincipalId());
account.setAccountSupervisoryUser(accountSupervisoryUser);
return account;
}
/*
* This method inserts a new Balance record into database.
*
* @param transaction the given transaction to persist.
*/
private void insertNewRecord(Balance balance) {
SpringContext.getBean(BusinessObjectService.class).save(balance);
}
/**
* Returns the fields that are used for a Current Account Balance Lookup.
*
* @param isExtended true if extended attributes should be included for checking, false otherwise
* @return a List of field names to check
* @see org.kuali.kfs.gl.businessobject.lookup.AbstractGeneralLedgerLookupableHelperServiceTestBase#getLookupFields(boolean)
*/
@Override
public List<String> getLookupFields(boolean isExtended) {
List<String> lookupFields = new LinkedList<String>();
lookupFields.add(UNIVERSITY_FISCAL_YEAR_KEY);
lookupFields.add(UNIVERSITY_FISCAL_PERIOD_CODE_KEY);
lookupFields.add(ACCOUNT_NUMBER_KEY);
if(isExtended){
lookupFields.add(ORGINIZATION_CODE_KEY);
lookupFields.add(FISCAL_OFFICER_PRINCIPAL_NAME_KEY);
lookupFields.add(ACCOUNT_SUPERVISOR_PRINCIPAL_NAME_KEY);
lookupFields.add(CHART_OF_ACCOUNTS_CODE_KEY);
lookupFields.add(SUB_ACCOUNT_NUMBER_KEY);
}
return lookupFields;
}
}