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 }