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 }