001    /**
002     * Copyright 2004-2013 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.hr.time.test;
017    
018    import java.math.BigDecimal;
019    import java.sql.Date;
020    import java.sql.Timestamp;
021    import java.util.ArrayList;
022    import java.util.Arrays;
023    import java.util.Calendar;
024    import java.util.GregorianCalendar;
025    import java.util.HashMap;
026    import java.util.Iterator;
027    import java.util.LinkedList;
028    import java.util.List;
029    import java.util.Map;
030    import java.util.Set;
031    
032    import org.apache.commons.lang3.StringUtils;
033    import org.apache.log4j.Logger;
034    import org.joda.time.DateTime;
035    import org.joda.time.DateTimeZone;
036    import org.joda.time.Days;
037    import org.joda.time.Duration;
038    import org.junit.Assert;
039    import org.kuali.hr.job.Job;
040    import org.kuali.hr.lm.accrual.AccrualCategory;
041    import org.kuali.hr.lm.accrual.service.AccrualService;
042    import org.kuali.hr.lm.leaveblock.LeaveBlock;
043    import org.kuali.hr.lm.leaveblock.service.LeaveBlockService;
044    import org.kuali.hr.lm.leavecalendar.LeaveCalendarDocument;
045    import org.kuali.hr.time.assignment.Assignment;
046    import org.kuali.hr.time.flsa.FlsaDay;
047    import org.kuali.hr.time.flsa.FlsaWeek;
048    import org.kuali.hr.time.service.base.TkServiceLocator;
049    import org.kuali.hr.time.timeblock.TimeBlock;
050    import org.kuali.hr.time.timeblock.TimeHourDetail;
051    import org.kuali.hr.time.timeblock.service.TimeBlockService;
052    import org.kuali.hr.time.timesheet.TimesheetDocument;
053    import org.kuali.hr.time.util.*;
054    import org.kuali.rice.kew.api.exception.WorkflowException;
055    import org.w3c.dom.NamedNodeMap;
056    import org.w3c.dom.Node;
057    
058    import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput;
059    import com.gargoylesoftware.htmlunit.html.HtmlElement;
060    import com.gargoylesoftware.htmlunit.html.HtmlForm;
061    import com.gargoylesoftware.htmlunit.html.HtmlInput;
062    import com.gargoylesoftware.htmlunit.html.HtmlPage;
063    import com.gargoylesoftware.htmlunit.html.HtmlSelect;
064    import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
065    import com.gargoylesoftware.htmlunit.html.HtmlTextArea;
066    
067    public class TkTestUtils {
068    
069            private static final Logger LOG = Logger.getLogger(TkTestUtils.class);
070    
071            public static TimesheetDocument populateBlankTimesheetDocument(Date calDate) {
072                    try {
073                            TimesheetDocument timesheet = TkServiceLocator.getTimesheetService().openTimesheetDocument(TKUser.getCurrentTargetPersonId(),
074                                                            TkServiceLocator.getCalendarService().getCurrentCalendarDates(TKUser.getCurrentTargetPersonId(),
075                                        calDate));
076                            for(TimeBlock timeBlock : timesheet.getTimeBlocks()){
077                                    TkServiceLocator.getTimeBlockService().deleteTimeBlock(timeBlock);
078                            }
079    
080                            return timesheet;
081                    } catch (WorkflowException e) {
082                            throw new RuntimeException("Problem fetching document");
083                    }
084            }
085    
086            public static TimesheetDocument populateTimesheetDocument(Date calDate) {
087                    try {
088                            TimesheetDocument timesheet = TkServiceLocator.getTimesheetService().openTimesheetDocument(TKUser.getCurrentTargetPersonId(),
089                                                            TkServiceLocator.getCalendarService().getCurrentCalendarDates(TKUser.getCurrentTargetPersonId(),
090                                        calDate));
091                            for(TimeBlock timeBlock : timesheet.getTimeBlocks()){
092                                    TkServiceLocator.getTimeBlockService().deleteTimeBlock(timeBlock);
093                            }
094    
095                            //refetch clean document
096                            timesheet = TkServiceLocator.getTimesheetService().openTimesheetDocument(TKUser.getCurrentTargetPersonId(),
097                                            TkServiceLocator.getCalendarService().getCurrentCalendarDates(TKUser.getCurrentTargetPersonId(),
098                                calDate));
099                            List<TimeBlock> timeBlocks = new LinkedList<TimeBlock>();
100                            for(int i = 0;i<5;i++){
101                                    TimeBlock timeBlock = createTimeBlock(timesheet, i+1, 10);
102                                    timeBlocks.add(timeBlock);
103                                    timesheet.setTimeBlocks(timeBlocks);
104                            }
105                            
106                            //Add a TEX accrual earn code
107                            TimeBlock timeBlock = createTimeBlock(timesheet, 1, 4,"TEX");
108                            timeBlocks.add(timeBlock);
109                            timesheet.setTimeBlocks(timeBlocks);
110                            return timesheet;
111    
112                    } catch (WorkflowException e) {
113                            throw new RuntimeException("Problem fetching document");
114                    }
115            }
116    
117            //TODO populate this
118            public static void createEarnGroup(String earnGroup, List<String> earnCodes, Date asOfDate){
119    
120            }
121    
122            /**
123             * Helper method to create regular time blocks for use in testing.
124             *
125             * @param start
126             * @param days
127             * @param hours
128             * @param earnCode
129             * @param jobNumber
130             * @param workArea
131             * @return
132             */
133            public static List<TimeBlock> createUniformTimeBlocks(DateTime start, int days, BigDecimal hours, String earnCode, Long jobNumber, Long workArea) {
134                    return TkTestUtils.createUniformTimeBlocks(start, days, hours, earnCode, jobNumber, workArea, null);
135            }
136    
137            public static List<TimeBlock> createUniformTimeBlocks(DateTime start, int days, BigDecimal hours, String earnCode, Long jobNumber, Long workArea, Long task) {
138                    List<TimeBlock> blocks = new ArrayList<TimeBlock>();
139    
140                    for (int i=0; i<days; i++) {
141                            DateTime ci = start.plusDays(i);
142                            DateTime co = ci.plusHours(hours.intValue());
143                            TimeBlock block = TkTestUtils.createDummyTimeBlock(ci, co, hours, earnCode, jobNumber, workArea);
144                            block.setTask(task);
145                            blocks.add(block);
146                    }
147    
148                    return blocks;
149            }
150    
151            public static TimeBlock createDummyTimeBlock(DateTime clockIn, DateTime clockOut, BigDecimal hours, String earnCode) {
152                    return TkTestUtils.createDummyTimeBlock(clockIn, clockOut, hours, earnCode, -1L, -1L);
153            }
154    
155            public static TimeBlock createDummyTimeBlock(DateTime clockIn, DateTime clockOut, BigDecimal hours, String earnCode, Long jobNumber, Long workArea) {
156                    TimeBlock block = new TimeBlock();
157                    Timestamp ci = new Timestamp(clockIn.getMillis());
158                    Timestamp co = new Timestamp(clockOut.getMillis());
159                    block.setBeginTimestamp(ci);
160                    block.setEndTimestamp(co);
161                    block.setHours(hours);
162            block.setBeginTimeDisplay(new DateTime(ci.getTime()));
163            block.setEndTimeDisplay(new DateTime(co.getTime()));
164    
165            block.setEarnCode(earnCode);
166                    block.setJobNumber(jobNumber);
167                    block.setWorkArea(workArea);
168    
169                    TimeHourDetail thd = new TimeHourDetail();
170                    thd.setHours(hours);
171                    thd.setEarnCode(earnCode);
172                    List<TimeHourDetail> timeHourDetails = new ArrayList<TimeHourDetail>();
173                    timeHourDetails.add(thd);
174                    block.setTimeHourDetails(timeHourDetails);
175    
176                    return block;
177            }
178    
179            public static TimeBlock createTimeBlock(TimesheetDocument timesheetDocument, int dayInPeriod, int numHours){
180                    return createTimeBlock(timesheetDocument, dayInPeriod, numHours,"RGN");
181            }
182            public static TimeBlock createTimeBlock(TimesheetDocument timesheetDocument, int dayInPeriod, int numHours, String earnCode){
183                    TimeBlock timeBlock = new TimeBlock();
184                    Calendar cal = GregorianCalendar.getInstance();
185                    cal.setTimeInMillis(timesheetDocument.getCalendarEntry().getBeginPeriodDateTime().getTime());
186                    for(int i = 1; i< dayInPeriod;i++){
187                            cal.add(Calendar.DAY_OF_MONTH, 1);
188                    }
189                    cal.set(Calendar.HOUR, 8);
190                    cal.set(Calendar.MINUTE, 0);
191    
192                    timeBlock.setDocumentId(timesheetDocument.getDocumentId());
193                    timeBlock.setBeginTimeDisplay(new DateTime(cal.getTimeInMillis()));
194                    
195                    timeBlock.setBeginTimestamp(new Timestamp(cal.getTimeInMillis()));
196                    timeBlock.setBeginTimestampTimezone("EST");
197                    timeBlock.setEarnCode(earnCode);
198                    timeBlock.setJobNumber(1L);
199                    timeBlock.setWorkArea(1234L);
200                    timeBlock.setTask(1L);
201                    timeBlock.setHours((new BigDecimal(numHours)).setScale(TkConstants.BIG_DECIMAL_SCALE, TkConstants.BIG_DECIMAL_SCALE_ROUNDING));
202                    cal.add(Calendar.HOUR, numHours);
203                    timeBlock.setEndTimestamp(new Timestamp(cal.getTimeInMillis()));
204                    timeBlock.setEndTimeDisplay(new DateTime(cal.getTimeInMillis()));
205    
206                    return timeBlock;
207            }
208    
209            public static List<Job> getJobs(Date calDate){
210                    return TkServiceLocator.getJobService().getJobs(TKContext.getPrincipalId(), calDate);
211            }
212            /**
213             *
214             * @param page: current html page
215             * @param criteria: The key is the field name and the value is a string array which contains the field value and the field type which can be chosen from TkTestConstants
216             * @return HtmlPage resultPage
217             * @throws Exception
218             */
219            public static HtmlPage fillOutForm(HtmlPage page, Map<String, Object> criteria) throws Exception {
220                    HtmlForm lookupForm = HtmlUnitUtil.getDefaultForm(page);
221                    String formFieldPrefix = "";
222                    HtmlPage resultPage = null;
223                    HtmlSelect select = null;
224                    HtmlInput input = null;
225                    HtmlCheckBoxInput checkBox = null;
226                    HtmlTextArea textArea = null;
227    
228    
229                    // Common class of both HtmlInput and HtmlTextArea -- since either of these can appear
230                    // on a web-form.
231                    HtmlElement htmlBasicInput = null;
232    
233                    Set<Map.Entry<String, Object>> entries = criteria.entrySet();
234                    Iterator<Map.Entry<String, Object>> it = entries.iterator();
235    
236                    while (it.hasNext()) {
237                            Map.Entry<String,Object> entry = it.next();
238    
239                            // if the field type is not <input>
240                            if(entry.getValue() instanceof String[]) {
241                                    String key = Arrays.asList((String[])entry.getValue()).get(0).toString();
242                                    String value = Arrays.asList((String[])entry.getValue()).get(1).toString();
243    
244                                    // drop-down
245                                    if(key.equals(TkTestConstants.FormElementTypes.DROPDOWN)) {
246    
247                                            try {
248                                                    select = (HtmlSelect) lookupForm.getSelectByName(formFieldPrefix  + entry.getKey());
249                                            } catch (Exception e) {
250                                                    select = (HtmlSelect) lookupForm.getElementById(formFieldPrefix  + entry.getKey());
251                                            }
252                        //  try to get a useful option, other than the typical blank default, by getting the last option
253                        //  if the size of the options list is zero, then there is a problem. might as well error here with an array out of bounds.
254                        resultPage = (HtmlPage) select.getOption(select.getOptionSize()-1).setSelected(true);
255                        HtmlUnitUtil.createTempFile(resultPage);
256                    }
257                                    // check box
258                                    else if(key.equals(TkTestConstants.FormElementTypes.CHECKBOX)) {
259                                            try {
260                                              checkBox = page.getHtmlElementById(formFieldPrefix  + entry.getKey());
261                                            }
262                                            catch(Exception e) {
263                                                    checkBox = page.getElementByName(formFieldPrefix  + entry.getKey());
264                                            }
265                                            resultPage = (HtmlPage) checkBox.setChecked(Boolean.parseBoolean(value));
266                                    }
267                                    // text area
268                                    else if(key.equals(TkTestConstants.FormElementTypes.TEXTAREA)) {
269                                            try {
270                                               textArea = page.getHtmlElementById(formFieldPrefix  + entry.getKey());
271                                            } catch (Exception e){
272                                                    textArea = page.getElementByName(formFieldPrefix  + entry.getKey());
273                                            }
274                                            textArea.setText(Arrays.asList((String[])entry.getValue()).get(1).toString());
275                                    }
276                            } else {
277                                    try {
278                                            htmlBasicInput = page.getHtmlElementById(formFieldPrefix + entry.getKey());
279                                            if (htmlBasicInput instanceof HtmlTextArea) {
280                                                    textArea = (HtmlTextArea)htmlBasicInput;
281                                                    textArea.setText(entry.getValue().toString());
282                                                    resultPage = (HtmlPage) textArea.getPage();
283                                            } else if (htmlBasicInput instanceof HtmlInput) {
284                                                    input = (HtmlInput)htmlBasicInput;
285                                                    resultPage = (HtmlPage) input.setValueAttribute(entry.getValue().toString());
286                                            } else {
287                                                    LOG.error("Request to populate a non-input html form field.");
288                                            }
289                                    } catch (Exception e) {
290                                            htmlBasicInput = page.getElementByName(formFieldPrefix + entry.getKey());
291    
292                                            if (htmlBasicInput instanceof HtmlTextArea) {
293                                                    textArea = (HtmlTextArea)htmlBasicInput;
294                                                    textArea.setText(entry.getValue().toString());
295                                                    resultPage = (HtmlPage) textArea.getPage();
296                                            } else if (htmlBasicInput instanceof HtmlInput) {
297                                                    input = (HtmlInput)htmlBasicInput;
298                                                    resultPage = (HtmlPage) input.setValueAttribute(entry.getValue().toString());
299                                            } else {
300                                                    LOG.error("Request to populate a non-input html form field.");
301                                            }
302                                    }
303                            }
304                    }
305                    HtmlUnitUtil.createTempFile(resultPage);
306                    return resultPage;
307            }
308    
309            /**
310             *
311             * @param page: current html page //NOTE doesnt seem to work currently for js setting of form variables
312             * @param name: the button name
313             * @return
314             * @throws Exception
315             */
316            public static HtmlPage clickButton(HtmlPage page, String name) throws Exception {
317                    HtmlForm form = HtmlUnitUtil.getDefaultForm(page);
318                    HtmlSubmitInput input = form.getInputByName(name);
319                    return (HtmlPage) input.click();
320            }
321            
322            public static HtmlPage clickClockInOrOutButton(HtmlPage page) throws Exception {
323                    HtmlForm form = HtmlUnitUtil.getDefaultForm(page);
324                    
325                    HtmlSubmitInput input = form.getInputByName("clockAction");
326                    form.getInputByName("methodToCall").setValueAttribute("clockAction");
327                    return (HtmlPage) input.click();
328            }
329            
330            public static HtmlPage clickLunchInOrOutButton(HtmlPage page, String lunchAction) throws Exception {
331                    HtmlForm form = HtmlUnitUtil.getDefaultForm(page);
332                    
333                    HtmlSubmitInput input = form.getInputByName("clockAction");
334                    form.getInputByName("methodToCall").setValueAttribute("clockAction");
335                    form.getInputByName("currentClockAction").setValueAttribute(lunchAction);
336                    return (HtmlPage) input.click();
337            }
338    
339            @SuppressWarnings("serial")
340            public static void verifyAggregateHourSumsFlatList(String msg, final Map<String,BigDecimal> ecToHoursMap, TkTimeBlockAggregate aggregate) {
341                    // Initializes sum map to zeros, since we only care about the entires
342                    // that were passed in.
343                    Map<String,BigDecimal> ecToSumMap = new HashMap<String,BigDecimal>() {{ for (String ec : ecToHoursMap.keySet()) { put(ec, BigDecimal.ZERO); }}};
344    
345                    for (TimeBlock bl : aggregate.getFlattenedTimeBlockList()) {
346                            for (TimeHourDetail thd : bl.getTimeHourDetails()) {
347                                    if (ecToSumMap.containsKey(thd.getEarnCode())) {
348                                            ecToSumMap.put(thd.getEarnCode(), ecToSumMap.get(thd.getEarnCode()).add(thd.getHours()));
349                    }
350                }
351            }
352    
353                    // Assert that our values are correct.
354                    for (String key : ecToHoursMap.keySet()) {
355                            Assert.assertEquals(
356                                            msg + " >> ("+key+") Wrong number of hours expected: " + ecToHoursMap.get(key) + " found: " + ecToSumMap.get(key) + " :: ",
357                                            0,
358                                            ecToHoursMap.get(key).compareTo(ecToSumMap.get(key)));
359            }
360            }
361    
362            /**
363             * Helper method to verify that the aggregate contains the correct sums as
364             * indicated in the ecToHoursMapping, on a SINGLE given flsaWeek.
365             *
366             * Warning! Contains Assertions, use only with Test Cases.
367             *
368             * @param ecToHoursMap ex: { 'REG' => 40, 'OVT' => 10 }
369             * @param aggregate An aggregate object containing the time blocks
370             * @param flsaWeek 0 indexed start week (pulling from aggregate)
371             */
372            @SuppressWarnings("serial")
373            public static void verifyAggregateHourSums(String msg, final Map<String,BigDecimal> ecToHoursMap, TkTimeBlockAggregate aggregate, int flsaWeek) {
374                    // Initializes sum map to zeros, since we only care about the entires
375                    // that were passed in.
376                    Map<String,BigDecimal> ecToSumMap = new HashMap<String,BigDecimal>() {{ for (String ec : ecToHoursMap.keySet()) { put(ec, BigDecimal.ZERO); }}};
377    
378                    List<FlsaWeek> flsaWeeks = aggregate.getFlsaWeeks(DateTimeZone.forID(TKUtils.getSystemTimeZone()));
379                    Assert.assertTrue(msg + " >> Not enough FLSA weeks to verify aggregate hours, max: " + (flsaWeeks.size() - 1), flsaWeeks.size() > flsaWeek);
380    
381                    // Build our Sum Map.
382                    FlsaWeek week = flsaWeeks.get(flsaWeek);
383                    List<FlsaDay> flsaDays = week.getFlsaDays();
384                    for (FlsaDay day : flsaDays) {
385                            for (TimeBlock bl : day.getAppliedTimeBlocks()) {
386                                    for (TimeHourDetail thd : bl.getTimeHourDetails()) {
387                                            if (ecToSumMap.containsKey(thd.getEarnCode())) {
388                                                    ecToSumMap.put(thd.getEarnCode(), ecToSumMap.get(thd.getEarnCode()).add(thd.getHours()));
389                        }
390                    }
391                }
392            }
393    
394    
395                    // Assert that our values are correct.
396                    for (String key : ecToHoursMap.keySet()) {
397                            Assert.assertEquals(
398                                            msg + " >> ("+key+") Wrong number of hours expected: " + ecToHoursMap.get(key) + " found: " + ecToSumMap.get(key) + " :: ",
399                                            0,
400                                            ecToHoursMap.get(key).compareTo(ecToSumMap.get(key)));
401            }
402            }
403    
404    
405            public static void verifyAggregateHourSums(final Map<String,BigDecimal> ecToHoursMap, TkTimeBlockAggregate aggregate, int flsaWeek) {
406                    TkTestUtils.verifyAggregateHourSums("", ecToHoursMap, aggregate, flsaWeek);
407            }
408    
409    
410            /**
411             * Helper method to generate time blocks suitable for db persistence in
412             * unit tests.
413             */
414            public static List<TimeBlock> createUniformActualTimeBlocks(TimesheetDocument timesheetDocument, Assignment assignment, String earnCode, DateTime start, int days, BigDecimal hours, BigDecimal amount) {
415                    TimeBlockService service = TkServiceLocator.getTimeBlockService();
416                    List<TimeBlock> blocks = new ArrayList<TimeBlock>();
417    
418                    for (int i=0; i<days; i++) {
419                            DateTime ci = start.plusDays(i);
420                            DateTime co = ci.plusHours(hours.intValue());
421                            Timestamp tsin = new Timestamp(ci.getMillis());
422                            Timestamp tsout = new Timestamp(co.getMillis());
423    
424                            blocks.addAll(service.buildTimeBlocks(assignment, earnCode, timesheetDocument, tsin, tsout, hours, amount, false, false, TKContext.getPrincipalId()));
425                    }
426    
427                    return blocks;
428            }
429    
430            public static Map<Timestamp, BigDecimal> getDateToHoursMap(TimeBlock timeBlock, TimeHourDetail timeHourDetail) {
431                    Map<Timestamp, BigDecimal> dateToHoursMap = new HashMap<Timestamp, BigDecimal>();
432                    DateTime beginTime = new DateTime(timeBlock.getBeginTimestamp());
433                    DateTime endTime = new DateTime(timeBlock.getEndTimestamp());
434    
435                    Days d = Days.daysBetween(beginTime, endTime);
436                    int numberOfDays = d.getDays();
437                    if (numberOfDays < 1) {
438                            dateToHoursMap.put(timeBlock.getBeginTimestamp(), timeHourDetail.getHours());
439                            return dateToHoursMap;
440                    }
441                    DateTime currentTime = beginTime;
442                    for (int i = 0; i < numberOfDays; i++) {
443                            DateTime nextDayAtMidnight = new DateTime(currentTime.plusDays(1).getMillis());
444                            nextDayAtMidnight = nextDayAtMidnight.hourOfDay().setCopy(12);
445                            nextDayAtMidnight = nextDayAtMidnight.minuteOfDay().setCopy(0);
446                            nextDayAtMidnight = nextDayAtMidnight.secondOfDay().setCopy(0);
447                            nextDayAtMidnight = nextDayAtMidnight.millisOfSecond().setCopy(0);
448                            Duration dur = new Duration(currentTime, nextDayAtMidnight);
449                            long duration = dur.getStandardSeconds();
450                            BigDecimal hrs = new BigDecimal(duration / 3600, TkConstants.MATH_CONTEXT);
451                            dateToHoursMap.put(new Timestamp(currentTime.getMillis()), hrs);
452                            currentTime = nextDayAtMidnight;
453                    }
454                    Duration dur = new Duration(currentTime, endTime);
455                    long duration = dur.getStandardSeconds();
456                    BigDecimal hrs = new BigDecimal(duration / 3600, TkConstants.MATH_CONTEXT);
457                    dateToHoursMap.put(new Timestamp(currentTime.getMillis()), hrs);
458    
459                    return dateToHoursMap;
460            }
461    
462            public static Date createDate(int month, int day, int year, int hours, int minutes, int seconds){
463                    DateTime dt = new DateTime(year, month, day, hours, minutes, seconds, 0);
464                    return new Date(dt.getMillis());
465            }
466    
467    
468        /**
469         * Method to obtain the HREF onclick='' value from the button when
470         * the client side typically processes the request.
471         * @param button
472         */
473        public static String getOnClickHref(HtmlElement button) {
474            NamedNodeMap attributes = button.getAttributes();
475            Node node = attributes.getNamedItem("onclick");
476    
477            //location.href='TimesheetSubmit.do?action=R&documentId=2000&methodToCall=approveTimesheet'
478            String hrefRaw = node.getNodeValue();
479            int sidx = hrefRaw.indexOf("='");
480    
481            return hrefRaw.substring(sidx+2, hrefRaw.length() - 1);
482        }
483    
484    }