001    /**
002     * Copyright 2004-2012 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.setHours((new BigDecimal(numHours)).setScale(TkConstants.BIG_DECIMAL_SCALE, TkConstants.BIG_DECIMAL_SCALE_ROUNDING));
195                    cal.add(Calendar.HOUR, numHours);
196                    timeBlock.setEndTimestamp(new Timestamp(cal.getTimeInMillis()));
197                    timeBlock.setEndTimeDisplay(new DateTime(cal.getTimeInMillis()));
198    
199                    return timeBlock;
200            }
201    
202            public static List<Job> getJobs(Date calDate){
203                    return TkServiceLocator.getJobService().getJobs(TKContext.getPrincipalId(), calDate);
204            }
205            /**
206             *
207             * @param page: current html page
208             * @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
209             * @return HtmlPage resultPage
210             * @throws Exception
211             */
212            public static HtmlPage fillOutForm(HtmlPage page, Map<String, Object> criteria) throws Exception {
213                    HtmlForm lookupForm = HtmlUnitUtil.getDefaultForm(page);
214                    String formFieldPrefix = "";
215                    HtmlPage resultPage = null;
216                    HtmlSelect select = null;
217                    HtmlInput input = null;
218                    HtmlCheckBoxInput checkBox = null;
219                    HtmlTextArea textArea = null;
220    
221    
222                    // Common class of both HtmlInput and HtmlTextArea -- since either of these can appear
223                    // on a web-form.
224                    HtmlElement htmlBasicInput = null;
225    
226                    Set<Map.Entry<String, Object>> entries = criteria.entrySet();
227                    Iterator<Map.Entry<String, Object>> it = entries.iterator();
228    
229                    while (it.hasNext()) {
230                            Map.Entry<String,Object> entry = it.next();
231    
232                            // if the field type is not <input>
233                            if(entry.getValue() instanceof String[]) {
234                                    String key = Arrays.asList((String[])entry.getValue()).get(0).toString();
235                                    String value = Arrays.asList((String[])entry.getValue()).get(1).toString();
236    
237                                    // drop-down
238                                    if(key.equals(TkTestConstants.FormElementTypes.DROPDOWN)) {
239    
240                                            try {
241                                                    select = (HtmlSelect) lookupForm.getSelectByName(formFieldPrefix  + entry.getKey());
242                                            } catch (Exception e) {
243                                                    select = (HtmlSelect) lookupForm.getElementById(formFieldPrefix  + entry.getKey());
244                                            }
245    
246                                            resultPage = (HtmlPage) select.getOptionByValue((String)value).setSelected(true);
247                                    }
248                                    // check box
249                                    else if(key.equals(TkTestConstants.FormElementTypes.CHECKBOX)) {
250                                            try {
251                                              checkBox = page.getHtmlElementById(formFieldPrefix  + entry.getKey());
252                                            }
253                                            catch(Exception e) {
254                                                    checkBox = page.getElementByName(formFieldPrefix  + entry.getKey());
255                                            }
256                                            resultPage = (HtmlPage) checkBox.setChecked(Boolean.parseBoolean(value));
257                                    }
258                                    // text area
259                                    else if(key.equals(TkTestConstants.FormElementTypes.TEXTAREA)) {
260                                            try {
261                                               textArea = page.getHtmlElementById(formFieldPrefix  + entry.getKey());
262                                            } catch (Exception e){
263                                                    textArea = page.getElementByName(formFieldPrefix  + entry.getKey());
264                                            }
265                                            textArea.setText(Arrays.asList((String[])entry.getValue()).get(1).toString());
266                                    }
267                            } else {
268                                    try {
269                                            htmlBasicInput = page.getHtmlElementById(formFieldPrefix + entry.getKey());
270                                            if (htmlBasicInput instanceof HtmlTextArea) {
271                                                    textArea = (HtmlTextArea)htmlBasicInput;
272                                                    textArea.setText(entry.getValue().toString());
273                                                    resultPage = (HtmlPage) textArea.getPage();
274                                            } else if (htmlBasicInput instanceof HtmlInput) {
275                                                    input = (HtmlInput)htmlBasicInput;
276                                                    resultPage = (HtmlPage) input.setValueAttribute(entry.getValue().toString());
277                                            } else {
278                                                    LOG.error("Request to populate a non-input html form field.");
279                                            }
280                                    } catch (Exception e) {
281                                            htmlBasicInput = page.getElementByName(formFieldPrefix + entry.getKey());
282    
283                                            if (htmlBasicInput instanceof HtmlTextArea) {
284                                                    textArea = (HtmlTextArea)htmlBasicInput;
285                                                    textArea.setText(entry.getValue().toString());
286                                                    resultPage = (HtmlPage) textArea.getPage();
287                                            } else if (htmlBasicInput instanceof HtmlInput) {
288                                                    input = (HtmlInput)htmlBasicInput;
289                                                    resultPage = (HtmlPage) input.setValueAttribute(entry.getValue().toString());
290                                            } else {
291                                                    LOG.error("Request to populate a non-input html form field.");
292                                            }
293                                    }
294                            }
295                    }
296                    HtmlUnitUtil.createTempFile(resultPage);
297                    return resultPage;
298            }
299    
300            /**
301             *
302             * @param page: current html page //NOTE doesnt seem to work currently for js setting of form variables
303             * @param name: the button name
304             * @return
305             * @throws Exception
306             */
307            public static HtmlPage clickButton(HtmlPage page, String name) throws Exception {
308                    HtmlForm form = HtmlUnitUtil.getDefaultForm(page);
309                    HtmlSubmitInput input = form.getInputByName(name);
310                    return (HtmlPage) input.click();
311            }
312            
313            public static HtmlPage clickClockInOrOutButton(HtmlPage page) throws Exception {
314                    HtmlForm form = HtmlUnitUtil.getDefaultForm(page);
315                    
316                    HtmlSubmitInput input = form.getInputByName("clockAction");
317                    form.getInputByName("methodToCall").setValueAttribute("clockAction");
318                    return (HtmlPage) input.click();
319            }
320            
321            public static HtmlPage clickLunchInOrOutButton(HtmlPage page, String lunchAction) throws Exception {
322                    HtmlForm form = HtmlUnitUtil.getDefaultForm(page);
323                    
324                    HtmlSubmitInput input = form.getInputByName("clockAction");
325                    form.getInputByName("methodToCall").setValueAttribute("clockAction");
326                    form.getInputByName("currentClockAction").setValueAttribute(lunchAction);
327                    return (HtmlPage) input.click();
328            }
329    
330            @SuppressWarnings("serial")
331            public static void verifyAggregateHourSumsFlatList(String msg, final Map<String,BigDecimal> ecToHoursMap, TkTimeBlockAggregate aggregate) {
332                    // Initializes sum map to zeros, since we only care about the entires
333                    // that were passed in.
334                    Map<String,BigDecimal> ecToSumMap = new HashMap<String,BigDecimal>() {{ for (String ec : ecToHoursMap.keySet()) { put(ec, BigDecimal.ZERO); }}};
335    
336                    for (TimeBlock bl : aggregate.getFlattenedTimeBlockList())
337                            for (TimeHourDetail thd : bl.getTimeHourDetails())
338                                    if (ecToSumMap.containsKey(thd.getEarnCode()))
339                                            ecToSumMap.put(thd.getEarnCode(), ecToSumMap.get(thd.getEarnCode()).add(thd.getHours()));
340    
341                    // Assert that our values are correct.
342                    for (String key : ecToHoursMap.keySet())
343                            Assert.assertEquals(
344                                            msg + " >> ("+key+") Wrong number of hours expected: " + ecToHoursMap.get(key) + " found: " + ecToSumMap.get(key) + " :: ",
345                                            0,
346                                            ecToHoursMap.get(key).compareTo(ecToSumMap.get(key)));
347            }
348    
349            /**
350             * Helper method to verify that the aggregate contains the correct sums as
351             * indicated in the ecToHoursMapping, on a SINGLE given flsaWeek.
352             *
353             * Warning! Contains Assertions, use only with Test Cases.
354             *
355             * @param ecToHoursMap ex: { 'REG' => 40, 'OVT' => 10 }
356             * @param aggregate An aggregate object containing the time blocks
357             * @param flsaWeek 0 indexed start week (pulling from aggregate)
358             */
359            @SuppressWarnings("serial")
360            public static void verifyAggregateHourSums(String msg, final Map<String,BigDecimal> ecToHoursMap, TkTimeBlockAggregate aggregate, int flsaWeek) {
361                    // Initializes sum map to zeros, since we only care about the entires
362                    // that were passed in.
363                    Map<String,BigDecimal> ecToSumMap = new HashMap<String,BigDecimal>() {{ for (String ec : ecToHoursMap.keySet()) { put(ec, BigDecimal.ZERO); }}};
364    
365                    List<FlsaWeek> flsaWeeks = aggregate.getFlsaWeeks(DateTimeZone.forID(TkServiceLocator.getTimezoneService().getUserTimezone()));
366                    Assert.assertTrue(msg + " >> Not enough FLSA weeks to verify aggregate hours, max: " + (flsaWeeks.size() - 1), flsaWeeks.size() > flsaWeek);
367    
368                    // Build our Sum Map.
369                    FlsaWeek week = flsaWeeks.get(flsaWeek);
370                    List<FlsaDay> flsaDays = week.getFlsaDays();
371                    for (FlsaDay day : flsaDays)
372                            for (TimeBlock bl : day.getAppliedTimeBlocks())
373                                    for (TimeHourDetail thd : bl.getTimeHourDetails())
374                                            if (ecToSumMap.containsKey(thd.getEarnCode()))
375                                                    ecToSumMap.put(thd.getEarnCode(), ecToSumMap.get(thd.getEarnCode()).add(thd.getHours()));
376    
377                    // Assert that our values are correct.
378                    for (String key : ecToHoursMap.keySet())
379                            Assert.assertEquals(
380                                            msg + " >> ("+key+") Wrong number of hours expected: " + ecToHoursMap.get(key) + " found: " + ecToSumMap.get(key) + " :: ",
381                                            0,
382                                            ecToHoursMap.get(key).compareTo(ecToSumMap.get(key)));
383            }
384            public static void verifyAggregateHourSums(final Map<String,BigDecimal> ecToHoursMap, TkTimeBlockAggregate aggregate, int flsaWeek) {
385                    TkTestUtils.verifyAggregateHourSums("", ecToHoursMap, aggregate, flsaWeek);
386            }
387    
388    
389            /**
390             * Helper method to generate time blocks suitable for db persistence in
391             * unit tests.
392             */
393            public static List<TimeBlock> createUniformActualTimeBlocks(TimesheetDocument timesheetDocument, Assignment assignment, String earnCode, DateTime start, int days, BigDecimal hours, BigDecimal amount) {
394                    TimeBlockService service = TkServiceLocator.getTimeBlockService();
395                    List<TimeBlock> blocks = new ArrayList<TimeBlock>();
396    
397                    for (int i=0; i<days; i++) {
398                            DateTime ci = start.plusDays(i);
399                            DateTime co = ci.plusHours(hours.intValue());
400                            Timestamp tsin = new Timestamp(ci.getMillis());
401                            Timestamp tsout = new Timestamp(co.getMillis());
402    
403                            blocks.addAll(service.buildTimeBlocks(assignment, earnCode, timesheetDocument, tsin, tsout, hours, amount, false, false));
404                    }
405    
406                    return blocks;
407            }
408    
409            public static Map<Timestamp, BigDecimal> getDateToHoursMap(TimeBlock timeBlock, TimeHourDetail timeHourDetail) {
410                    Map<Timestamp, BigDecimal> dateToHoursMap = new HashMap<Timestamp, BigDecimal>();
411                    DateTime beginTime = new DateTime(timeBlock.getBeginTimestamp());
412                    DateTime endTime = new DateTime(timeBlock.getEndTimestamp());
413    
414                    Days d = Days.daysBetween(beginTime, endTime);
415                    int numberOfDays = d.getDays();
416                    if (numberOfDays < 1) {
417                            dateToHoursMap.put(timeBlock.getBeginTimestamp(), timeHourDetail.getHours());
418                            return dateToHoursMap;
419                    }
420                    DateTime currentTime = beginTime;
421                    for (int i = 0; i < numberOfDays; i++) {
422                            DateTime nextDayAtMidnight = new DateTime(currentTime.plusDays(1).getMillis());
423                            nextDayAtMidnight = nextDayAtMidnight.hourOfDay().setCopy(12);
424                            nextDayAtMidnight = nextDayAtMidnight.minuteOfDay().setCopy(0);
425                            nextDayAtMidnight = nextDayAtMidnight.secondOfDay().setCopy(0);
426                            nextDayAtMidnight = nextDayAtMidnight.millisOfSecond().setCopy(0);
427                            Duration dur = new Duration(currentTime, nextDayAtMidnight);
428                            long duration = dur.getStandardSeconds();
429                            BigDecimal hrs = new BigDecimal(duration / 3600, TkConstants.MATH_CONTEXT);
430                            dateToHoursMap.put(new Timestamp(currentTime.getMillis()), hrs);
431                            currentTime = nextDayAtMidnight;
432                    }
433                    Duration dur = new Duration(currentTime, endTime);
434                    long duration = dur.getStandardSeconds();
435                    BigDecimal hrs = new BigDecimal(duration / 3600, TkConstants.MATH_CONTEXT);
436                    dateToHoursMap.put(new Timestamp(currentTime.getMillis()), hrs);
437    
438                    return dateToHoursMap;
439            }
440    
441            public static Date createDate(int month, int day, int year, int hours, int minutes, int seconds){
442                    DateTime dt = new DateTime(year, month, day, hours, minutes, seconds, 0);
443                    return new Date(dt.getMillis());
444            }
445    
446    
447        /**
448         * Method to obtain the HREF onclick='' value from the button when
449         * the client side typically processes the request.
450         * @param button
451         */
452        public static String getOnClickHref(HtmlElement button) {
453            NamedNodeMap attributes = button.getAttributes();
454            Node node = attributes.getNamedItem("onclick");
455    
456            //location.href='TimesheetSubmit.do?action=R&documentId=2000&methodToCall=approveTimesheet'
457            String hrefRaw = node.getNodeValue();
458            int sidx = hrefRaw.indexOf("='");
459    
460            return hrefRaw.substring(sidx+2, hrefRaw.length() - 1);
461        }
462    
463    }