View Javadoc

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