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