View Javadoc
1   /*
2    * The Kuali Financial System, a comprehensive financial management system for higher education.
3    * 
4    * Copyright 2005-2014 The Kuali Foundation
5    * 
6    * This program is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU Affero General Public License as
8    * published by the Free Software Foundation, either version 3 of the
9    * License, or (at your option) any later version.
10   * 
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU Affero General Public License for more details.
15   * 
16   * You should have received a copy of the GNU Affero General Public License
17   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  package org.kuali.kfs.gl.businessobject.lookup;
20  
21  import java.sql.Date;
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.Map;
27  
28  import org.apache.commons.lang.StringUtils;
29  import org.kuali.kfs.coa.businessobject.Account;
30  import org.kuali.kfs.gl.Constant;
31  import org.kuali.kfs.gl.businessobject.Balance;
32  import org.kuali.kfs.gl.businessobject.CurrentAccountBalance;
33  import org.kuali.kfs.gl.service.AccountBalanceService;
34  import org.kuali.kfs.sys.ConfigureContext;
35  import org.kuali.kfs.sys.KFSPropertyConstants;
36  import org.kuali.kfs.sys.ObjectUtil;
37  import org.kuali.kfs.sys.businessobject.lookup.LookupableSpringContext;
38  import org.kuali.kfs.sys.context.SpringContext;
39  import org.kuali.kfs.sys.fixture.AccountFixture;
40  import org.kuali.rice.kim.api.identity.Person;
41  import org.kuali.rice.kim.api.identity.PersonService;
42  import org.kuali.rice.kns.lookup.HtmlData.AnchorHtmlData;
43  import org.kuali.rice.krad.bo.PersistableBusinessObjectBase;
44  import org.kuali.rice.krad.datadictionary.DataDictionary;
45  import org.kuali.rice.krad.exception.ValidationException;
46  import org.kuali.rice.krad.service.BusinessObjectService;
47  import org.kuali.rice.krad.service.DataDictionaryService;
48  import org.kuali.rice.krad.util.GlobalVariables;
49  import org.kuali.rice.krad.util.ObjectUtils;
50  
51  @ConfigureContext
52  public class CurrentAccountBalanceLookupableHelperServiceTest extends AbstractGeneralLedgerLookupableHelperServiceTestBase {
53  
54      // Enum, for sake of readability in method calls
55      private enum ExpectException{YES, NO};
56  
57      // Class variables, service key, namely the class being tested
58      private static final String LOOKUP_HELPER_SERVICE_KEY = "glCurrentAccountBalanceLookupableHelperService";
59  
60      // Class variables, search parameter keys
61      private static final String UNIVERSITY_FISCAL_YEAR_KEY = KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR; // universityFiscalYear
62      private static final String UNIVERSITY_FISCAL_PERIOD_CODE_KEY =  KFSPropertyConstants.UNIVERSITY_FISCAL_PERIOD_CODE; // universityFiscalPeriodCode
63      private static final String CHART_OF_ACCOUNTS_CODE_KEY =  KFSPropertyConstants.ACCOUNT + "." + KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE; // chartOfAccountsCode
64      private static final String ACCOUNT_NUMBER_KEY =   KFSPropertyConstants.ACCOUNT + "." + KFSPropertyConstants.ACCOUNT_NUMBER; // account.accountNumber
65      private static final String FISCAL_OFFICER_PRINCIPAL_NAME_KEY = KFSPropertyConstants.ACCOUNT + "." + KFSPropertyConstants.ACCOUNT_FISCAL_OFFICER_USER + "." + KFSPropertyConstants.PERSON_USER_ID; // account.accountFiscalOfficerUser.principalName;
66      private static final String ACCOUNT_SUPERVISOR_PRINCIPAL_NAME_KEY = KFSPropertyConstants.ACCOUNT + "." + KFSPropertyConstants.ACCOUNT_SUPERVISORY_USER + "." + KFSPropertyConstants.PERSON_USER_ID; // account.accountSupervisoryUser.principalName
67      private static final String SUB_ACCOUNT_NUMBER_KEY = KFSPropertyConstants.SUB_ACCOUNT_NUMBER; // subAccountNumber
68      private static final String ORGINIZATION_CODE_KEY = KFSPropertyConstants.ACCOUNT + "." + KFSPropertyConstants.ORGANIZATION_CODE; // account.organizationCode
69  
70      // Class variables, magic strings; necessity since these values (mostly) weren't found in any Constants or Fixture classes
71      private static final String FISCAL_OFFICER_PRINCIPAL_NAME_VAL = "mhkozlow";  // Name in UserNameFixture.java, but wish to stay consistant
72      private static final String ACCOUNT_SUPERVISOR_PRINCIPAL_NAME_VAL = "jaraujo"; // Name not in UserNameFixture.java
73      private static final String ORGINIZATION_CODE_VAL = "PSY"; // Only found in DB bootstrap *.sql
74      private static final String ACCOUNT_EXPIRATION_DATE = "2101-09-30";
75      private static final Integer UNIVERSITY_FISCAL_YEAR = 2014;
76  
77      // Instance variables, required service classes
78      private AccountBalanceService accountBalanceService;
79      private PersonService personService;
80      private DataDictionary dataDictionary;
81  
82      // Instance variables, vanilla
83      private Map<String, String> fullFieldToValueMap;
84      private Map<String, String> requiredFieldToValueMap;
85      private Map<String, String> semiRequiredFieldToValueMap;
86      private Map<String, String> optionalFieldToValueMap;
87  
88  
89  
90  
91      /**
92       * This method:
93       * 1. Calls super.setUp()
94       * 2. Sets service spring beans
95       * 3. Initializes ancestor's testDataGenerator
96       * 4. Collects entries placed in local maps used for test data
97       * 5. Generates an Account and sets it in delegate of ancestor
98       *
99       * @see org.kuali.kfs.gl.businessobject.lookup.AbstractGeneralLedgerLookupableHelperServiceTestBase#setUp()
100      */
101     @Override
102     protected void setUp() throws Exception {
103         super.setUp();
104         accountBalanceService = SpringContext.getBean(AccountBalanceService.class);
105         personService = SpringContext.getBean(PersonService.class);
106         dataDictionary = SpringContext.getBean(DataDictionaryService.class).getDataDictionary();
107         lookupableHelperServiceImpl = LookupableSpringContext.getLookupableHelperService(LOOKUP_HELPER_SERVICE_KEY);
108         lookupableHelperServiceImpl.setBusinessObjectClass(CurrentAccountBalance.class);
109         testDataGenerator.generateTransactionData(pendingEntry);
110         initFieldToValueMaps(); // generateAccount() is dependant on this
111         pendingEntry.setAccount(generateAccount());
112     }
113 
114 
115     /*
116      * Helper method that sets up each type of search parameter map.
117      */
118     private void initFieldToValueMaps(){
119         initRequiredFieldToValueMap();
120         initSemiRequiredFieldToValueMap();
121         initOptionalFieldToValueMap();
122         initFullFieldToValueMap();
123     }
124 
125 
126     /*
127      * Initialize required search parameter key/value pairs.
128      */
129     private void initRequiredFieldToValueMap(){
130         requiredFieldToValueMap = new HashMap<String, String>();
131         requiredFieldToValueMap.put(UNIVERSITY_FISCAL_YEAR_KEY, UNIVERSITY_FISCAL_YEAR.toString());
132         requiredFieldToValueMap.put(UNIVERSITY_FISCAL_PERIOD_CODE_KEY, pendingEntry.getUniversityFiscalPeriodCode());
133         requiredFieldToValueMap.put(Constant.CONSOLIDATION_OPTION, Constant.CONSOLIDATION);
134     }
135 
136 
137     /*
138      * Initialize "at-least-one" search parameter key/value pairs.
139      */
140     private void initSemiRequiredFieldToValueMap(){
141         /*
142          * These are related to creating an Account object to set on the
143          * pendingEntry object, and a subsequent join on the Account table
144          * during DB searches.
145          */
146         semiRequiredFieldToValueMap = new HashMap<String, String>();
147         semiRequiredFieldToValueMap.put(ACCOUNT_NUMBER_KEY, pendingEntry.getAccountNumber());
148         semiRequiredFieldToValueMap.put(CHART_OF_ACCOUNTS_CODE_KEY, pendingEntry.getChartOfAccountsCode());
149         semiRequiredFieldToValueMap.put(FISCAL_OFFICER_PRINCIPAL_NAME_KEY, FISCAL_OFFICER_PRINCIPAL_NAME_VAL);
150         semiRequiredFieldToValueMap.put(ACCOUNT_SUPERVISOR_PRINCIPAL_NAME_KEY, ACCOUNT_SUPERVISOR_PRINCIPAL_NAME_VAL);
151     }
152 
153 
154     /*
155      * Initialize completely optional search parameter key/value pairs.
156      */
157     private void initOptionalFieldToValueMap(){
158         optionalFieldToValueMap = new HashMap<String, String>();
159         optionalFieldToValueMap.put(SUB_ACCOUNT_NUMBER_KEY, pendingEntry.getSubAccountNumber());
160 
161         // This correlates to the test accountNumber and is
162         // joined across the real Account table, so must be valid
163         // for this accoutNumber. Could not find any test fixtures
164         // or constants class to pull this from.
165         optionalFieldToValueMap.put(ORGINIZATION_CODE_KEY, ORGINIZATION_CODE_VAL);
166     }
167 
168 
169     /*
170      * Combine all search parameter key/value pairs into ine map.
171      */
172     private void initFullFieldToValueMap(){
173         fullFieldToValueMap = new HashMap<String, String>();
174         fullFieldToValueMap.putAll(requiredFieldToValueMap);
175         fullFieldToValueMap.putAll(semiRequiredFieldToValueMap);
176         fullFieldToValueMap.putAll(optionalFieldToValueMap);
177     }
178 
179 
180     /**
181      * This method tests the returned hyperlink for each of the search result's
182      * column values if the value's type has an inquiry page. For instance, the
183      * column "Fiscal Year" might have a value of "2014", and the "2014" value
184      * would be hyperlinked to perform an auto search and return the user to a
185      * "System Options Inquiry" results page with details about that
186      * "Fiscal Year" entry.
187      *
188      * In this specific test case, the "subAccountCode" hyperlink should not
189      * exist when the "Consolidation Option" of the search is selected to be
190      * "Consolidation". Selecting this option means that subAccountNumber is
191      * not displayed, and the "----" placeholder should not be used as a
192      * subAccountNumber inquiry hyperlink.
193      *
194      * @see org.kuali.kfs.gl.businessobject.lookup.AbstractGeneralLedgerLookupableHelperServiceTestBase#testGetInquiryUrl()
195      */
196     @Override
197     public void testGetInquiryUrl() {
198 
199         // Set sentinal that there is no subAccountNumber present
200         pendingEntry.setSubAccountNumber(Constant.CONSOLIDATED_SUB_ACCOUNT_NUMBER);
201 
202         Balance balance = new Balance(pendingEntry);
203         CurrentAccountBalance currentAccountBalance = generateCurrentAccountBalance(balance);
204 
205         List<String> inquiryFieldNames = new ArrayList<String>();
206         inquiryFieldNames.add(UNIVERSITY_FISCAL_YEAR_KEY);
207         inquiryFieldNames.add(UNIVERSITY_FISCAL_PERIOD_CODE_KEY);
208         inquiryFieldNames.add(CHART_OF_ACCOUNTS_CODE_KEY);
209         inquiryFieldNames.add(ORGINIZATION_CODE_KEY);
210         inquiryFieldNames.add(FISCAL_OFFICER_PRINCIPAL_NAME_KEY);
211         inquiryFieldNames.add(SUB_ACCOUNT_NUMBER_KEY);
212 
213         //List<String> inquiryFieldNames = new ArrayList<String>();
214         for(String fieldName : inquiryFieldNames){
215             AnchorHtmlData htmlData = (AnchorHtmlData)lookupableHelperServiceImpl.getInquiryUrl(currentAccountBalance, fieldName);
216             assertTrue("Null inquiryUrl for property: " + fieldName, ObjectUtils.isNotNull(htmlData));
217 
218             String href = htmlData.getHref();
219             if(SUB_ACCOUNT_NUMBER_KEY.equals(fieldName)){
220                 assertTrue("The href anchor for the property " + fieldName + "is not blank.", StringUtils.isBlank(href));
221             }else{
222                 assertTrue("The href anchor for the property " + fieldName + "is blank.", StringUtils.isNotBlank(href));
223             }
224         }
225 
226         // Revert back to original test value for other tests
227         pendingEntry.setSubAccountNumber(optionalFieldToValueMap.get(SUB_ACCOUNT_NUMBER_KEY));
228 
229     }
230 
231 
232     /**
233      * This test validates search paramaters specific to CurrentAccountBalanceLookupableHelperServiceImpl.
234      *
235      * Validation of actual values via services only occur for universityFiscalYear and
236      * universityFiscalPeriodCode. This means that test values must be present in the
237      * coresponding FS_OPTION_T and SH_ACCT_PERIOD_T DB tables.
238      *
239      * Validation for the remaining parameters consist of ensuring the parameters line up
240      * with public properties of the CurrentAccountBalance class, as defined in the
241      * CurrentAccountBalance.xml Data Dictionary(DD) configuration, and that the values of
242      * the properties are non-null if they are defined as such in the DD.
243      *
244      * Additionally, it should be noted that this method tests the various combinations
245      * of parameters, since one subset is always required, another subset requires only
246      * one from the set, and the final subset is entirely optional.
247      *
248      * Coverage for Override of {@link org.kuali.kfs.gl.businessobject.lookup.AbstractGeneralLedgerLookupableHelperServiceImpl#validateSearchParameters(java.util.Map)}
249      */
250     public void testValidateSearchParameters(){
251         /*
252          * Search Parameter Subsets:
253          *
254          * Both required:
255          * - universityFiscalYear
256          * - universityFiscalPeriodCode
257          *
258          * At least one required (aka, semi-required):
259          * - account.accountNumber
260          * - account.organizationCode
261          * - account.accountFiscalOfficerUser.principalName
262          * - account.accountSupervisoryUser.principalName
263          *
264          * Optional:
265          * - chartOfAccountsCode
266          * - subAccountNumber
267          */
268 
269         // Data structure to hold different combinations of parameters.
270         Map<String, String> testMap = new HashMap<String, String>();
271 
272         // Test with no parameters, should throw exception
273         // Validating parameters: []
274         validateMap(testMap, ExpectException.YES);
275 
276         // Test with required fields, should throw exception.
277         // Validating parameters: [universityFiscalYear, universityFiscalPeriodCode]
278         testMap.putAll(requiredFieldToValueMap);
279         validateMap(testMap, ExpectException.YES);
280 
281         // Test the "at least one required" params, should *not* throw exception.
282         // Validating parameters: [universityFiscalYear, universityFiscalPeriodCode, account.accountNumber,
283         //                         account.organizationCode, account.accountSupervisoryUser.principalName,
284         //                         account.accountFiscalOfficerUser.principalName, chartOfAccountsCode]
285         testMap.putAll(semiRequiredFieldToValueMap);
286         validateMap(testMap, ExpectException.NO);
287 
288 
289         // Test with "optional" params, should *not* throw exception.
290         // Validating parameters: [universityFiscalYear, universityFiscalPeriodCode, account.accountNumber,
291         //                         account.organizationCode, account.accountSupervisoryUser.principalName,
292         //                         account.accountFiscalOfficerUser.principalName, chartOfAccountsCode]
293         testMap.putAll(optionalFieldToValueMap);
294         validateMap(testMap, ExpectException.NO);
295 
296         // Test with all parameters present
297         validateMap(fullFieldToValueMap, ExpectException.NO);
298 
299     }
300 
301 
302     /*
303      * This method will:
304      * 1. Create a "failure" message containing the field parameters being vaildated
305      * 2. Perform validation against the input map, both positive and negative testing
306      * 3. Clear any global error messages since an error count will fail other tests
307      */
308     private void validateMap(Map<String, String> fieldToValueMap, ExpectException shouldExpectException){
309         String message = String.format("Validation failed for search parameters: %s", fieldToValueMap.keySet());
310         try{
311             if(shouldExpectException == ExpectException.YES){
312                 assertTrue(message, validateSearchParametersThrowsException(fieldToValueMap));
313             }else{
314                 assertFalse(message, validateSearchParametersThrowsException(fieldToValueMap));
315             }
316         } finally {
317             // Need to ensure this gets cleared -- negative tests will generate
318             // a ValidationException, which in turn creates an error message
319             GlobalVariables.getMessageMap().clearErrorMessages();
320         }
321     }
322 
323 
324     /*
325      * Note, this helper method will propagate any Throwable that is not of
326      * type ValidationException -- this is on purpose, as we want the test
327      * to blow up if an unexpected error manifests.
328      */
329     private boolean validateSearchParametersThrowsException(Map<String, String> fieldToValueMap) {
330         try{
331             lookupableHelperServiceImpl.validateSearchParameters(fieldToValueMap);
332         }catch(ValidationException e){
333             return true;
334         }
335         return false;
336     }
337 
338 
339     /**
340     * This method will run several searches with various combos
341     * of search parameter key/value pairs.
342     *
343     * @throw java.lang.Exception Thrown when any exception occurs that is not of type ValidationException
344     * @see org.kuali.kfs.gl.businessobject.lookup.AbstractGeneralLedgerLookupableHelperServiceTestBase#testGetSearchResults()
345     */
346    @Override
347    public void testGetSearchResults() throws Exception {
348 
349        // Used to persist generated record
350        Balance balance = new Balance(pendingEntry);
351        balance.setUniversityFiscalYear(UNIVERSITY_FISCAL_YEAR);
352 
353        // Used for testing lookup
354        CurrentAccountBalance currentAccountBalance = generateCurrentAccountBalance(balance);
355 
356        // Test without having inserted Balance
357        Map<String, String> fieldValues = getLookupFieldValues(currentAccountBalance, false);
358        List searchResults = lookupableHelperServiceImpl.getSearchResults(fieldValues);
359        assertTrue(testDataGenerator.getMessageValue("noSuchRecord"), !contains(searchResults, currentAccountBalance));
360 
361        // Add record to DB
362        insertNewRecord(balance);
363 
364        // Search with only the required params, should return 1 result
365        fieldValues = getLookupFieldValues(currentAccountBalance, false);
366        searchResults = lookupableHelperServiceImpl.getSearchResults(fieldValues);
367        int numOfFirstResult = searchResults.size();
368        assertTrue(testDataGenerator.getMessageValue("wrongRecordSize"), searchResults.size() >= 1);
369        assertTrue(testDataGenerator.getMessageValue("failToFindRecord"), contains(searchResults, currentAccountBalance));
370 
371        // Search with all available search params, should return 1 result
372        fieldValues = getLookupFieldValues(currentAccountBalance, true);
373        searchResults = lookupableHelperServiceImpl.getSearchResults(fieldValues);
374        assertTrue(testDataGenerator.getMessageValue("wrongRecordSize"), searchResults.size() >= numOfFirstResult);
375        assertTrue(testDataGenerator.getMessageValue("failToFindRecord"), contains(searchResults, currentAccountBalance));
376 
377    }
378 
379 
380    /**
381     * This method will return a map of entries suitable for building a record
382     * query.
383     *
384     * Need to override since TestDataGenerator only handles key/value pairs
385     * contained in the data.properties test file.
386     *
387     * If one places properties in the file that are not relevant to a Transaction objects,
388     * an exception is thrown.
389     *
390     * @param businessObject Moot, this is just to follow the parents method signature
391     * @param isExtended If true, will include all search kay/pair values, if false, will only return bare minimum kay/pair values
392     * @see org.kuali.kfs.gl.businessobject.lookup.AbstractGeneralLedgerLookupableHelperServiceTestBase#getLookupFieldValues(org.kuali.rice.krad.bo.PersistableBusinessObjectBase, boolean)
393     */
394    @Override
395    public Map<String, String> getLookupFieldValues(PersistableBusinessObjectBase businessObject, boolean isExtended) throws Exception{
396        if(isExtended){
397            return fullFieldToValueMap;
398        }else{
399            // Don't add in the optional fields
400            Map<String, String> results = new HashMap<String, String>();
401            results.putAll(requiredFieldToValueMap);
402            results.put(ACCOUNT_NUMBER_KEY, semiRequiredFieldToValueMap.get(ACCOUNT_NUMBER_KEY));
403            return results;
404        }
405    }
406 
407 
408    /*
409     * Build a CuurentAccountBalance from the given Balance
410     */
411    private CurrentAccountBalance generateCurrentAccountBalance(Balance balance){
412        CurrentAccountBalance currentAccountBalance = new CurrentAccountBalance();
413        ObjectUtil.buildObject(currentAccountBalance, balance);
414        currentAccountBalance.setUniversityFiscalYear(UNIVERSITY_FISCAL_YEAR);
415        currentAccountBalance.setAccount(generateAccount());
416        currentAccountBalance.setUniversityFiscalPeriodCode(requiredFieldToValueMap.get(UNIVERSITY_FISCAL_PERIOD_CODE_KEY));
417        return currentAccountBalance;
418    }
419 
420 
421    /*
422     * Create a minimal Account object, which will be set on
423     * super.pendingEntry, and used in generating test data.
424     */
425    private Account generateAccount(){
426        Account account = AccountFixture.ACCOUNT_PRESENCE_ACCOUNT.createAccount();
427        account.setAccountExpirationDate(Date.valueOf(ACCOUNT_EXPIRATION_DATE));
428        account.setActive(true);
429        account.setAccountNumber(testDataGenerator.getPropertyValue(KFSPropertyConstants.ACCOUNT_NUMBER));
430        account.setChartOfAccountsCode(pendingEntry.getChartOfAccountsCode());
431        account.setOrganizationCode(optionalFieldToValueMap.get(ORGINIZATION_CODE_KEY));
432 
433        Person accountFiscalOfficerUser = personService.getPersonByPrincipalName(semiRequiredFieldToValueMap.get(FISCAL_OFFICER_PRINCIPAL_NAME_KEY));
434        account.setAccountFiscalOfficerSystemIdentifier(accountFiscalOfficerUser.getPrincipalId());
435        account.setAccountFiscalOfficerUser(accountFiscalOfficerUser);
436 
437        Person accountSupervisoryUser = personService.getPersonByPrincipalName(semiRequiredFieldToValueMap.get(ACCOUNT_SUPERVISOR_PRINCIPAL_NAME_KEY));
438        account.setAccountFiscalOfficerSystemIdentifier(accountSupervisoryUser.getPrincipalId());
439        account.setAccountSupervisoryUser(accountSupervisoryUser);
440 
441        return account;
442    }
443 
444 
445    /*
446     * This method inserts a new Balance record into database.
447     *
448     * @param transaction the given transaction to persist.
449     */
450    private void insertNewRecord(Balance balance) {
451        SpringContext.getBean(BusinessObjectService.class).save(balance);
452    }
453 
454 
455     /**
456      * Returns the fields that are used for a Current Account Balance Lookup.
457      *
458      * @param isExtended true if extended attributes should be included for checking, false otherwise
459      * @return a List of field names to check
460      * @see org.kuali.kfs.gl.businessobject.lookup.AbstractGeneralLedgerLookupableHelperServiceTestBase#getLookupFields(boolean)
461      */
462     @Override
463     public List<String> getLookupFields(boolean isExtended) {
464         List<String> lookupFields = new LinkedList<String>();
465         lookupFields.add(UNIVERSITY_FISCAL_YEAR_KEY);
466         lookupFields.add(UNIVERSITY_FISCAL_PERIOD_CODE_KEY);
467         lookupFields.add(ACCOUNT_NUMBER_KEY);
468         if(isExtended){
469             lookupFields.add(ORGINIZATION_CODE_KEY);
470             lookupFields.add(FISCAL_OFFICER_PRINCIPAL_NAME_KEY);
471             lookupFields.add(ACCOUNT_SUPERVISOR_PRINCIPAL_NAME_KEY);
472             lookupFields.add(CHART_OF_ACCOUNTS_CODE_KEY);
473             lookupFields.add(SUB_ACCOUNT_NUMBER_KEY);
474         }
475         return lookupFields;
476     }
477 
478 
479 }