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.log4j.Logger;
33  import org.joda.time.DateTime;
34  import org.joda.time.DateTimeZone;
35  import org.joda.time.Days;
36  import org.joda.time.Duration;
37  import org.junit.Assert;
38  import org.kuali.hr.job.Job;
39  import org.kuali.hr.time.assignment.Assignment;
40  import org.kuali.hr.time.flsa.FlsaDay;
41  import org.kuali.hr.time.flsa.FlsaWeek;
42  import org.kuali.hr.time.service.base.TkServiceLocator;
43  import org.kuali.hr.time.timeblock.TimeBlock;
44  import org.kuali.hr.time.timeblock.TimeHourDetail;
45  import org.kuali.hr.time.timeblock.service.TimeBlockService;
46  import org.kuali.hr.time.timesheet.TimesheetDocument;
47  import org.kuali.hr.time.util.TKContext;
48  import org.kuali.hr.time.util.TKUser;
49  import org.kuali.hr.time.util.TkConstants;
50  import org.kuali.hr.time.util.TkTimeBlockAggregate;
51  import org.kuali.rice.kew.api.exception.WorkflowException;
52  import org.w3c.dom.NamedNodeMap;
53  import org.w3c.dom.Node;
54  
55  import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput;
56  import com.gargoylesoftware.htmlunit.html.HtmlElement;
57  import com.gargoylesoftware.htmlunit.html.HtmlForm;
58  import com.gargoylesoftware.htmlunit.html.HtmlInput;
59  import com.gargoylesoftware.htmlunit.html.HtmlPage;
60  import com.gargoylesoftware.htmlunit.html.HtmlSelect;
61  import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
62  import com.gargoylesoftware.htmlunit.html.HtmlTextArea;
63  
64  public class TkTestUtils {
65  
66  	private static final Logger LOG = Logger.getLogger(TkTestUtils.class);
67  
68  	public static TimesheetDocument populateBlankTimesheetDocument(Date calDate) {
69  		try {
70  			TimesheetDocument timesheet = TkServiceLocator.getTimesheetService().openTimesheetDocument(TKUser.getCurrentTargetPerson().getPrincipalId(),
71  							TkServiceLocator.getCalendarService().getCurrentCalendarDates(TKUser.getCurrentTargetPerson().getPrincipalId(),
72                                      calDate));
73  			for(TimeBlock timeBlock : timesheet.getTimeBlocks()){
74  				TkServiceLocator.getTimeBlockService().deleteTimeBlock(timeBlock);
75  			}
76  
77  			return timesheet;
78  		} catch (WorkflowException e) {
79  			throw new RuntimeException("Problem fetching document");
80  		}
81  	}
82  
83  	public static TimesheetDocument populateTimesheetDocument(Date calDate) {
84  		try {
85  			TimesheetDocument timesheet = TkServiceLocator.getTimesheetService().openTimesheetDocument(TKUser.getCurrentTargetPerson().getPrincipalId(),
86  							TkServiceLocator.getCalendarService().getCurrentCalendarDates(TKUser.getCurrentTargetPerson().getPrincipalId(),
87                                      calDate));
88  			for(TimeBlock timeBlock : timesheet.getTimeBlocks()){
89  				TkServiceLocator.getTimeBlockService().deleteTimeBlock(timeBlock);
90  			}
91  
92  			//refetch clean document
93  			timesheet = TkServiceLocator.getTimesheetService().openTimesheetDocument(TKUser.getCurrentTargetPerson().getPrincipalId(),
94  					TkServiceLocator.getCalendarService().getCurrentCalendarDates(TKUser.getCurrentTargetPerson().getPrincipalId(),
95                              calDate));
96  			List<TimeBlock> timeBlocks = new LinkedList<TimeBlock>();
97  			for(int i = 0;i<5;i++){
98  				TimeBlock timeBlock = createTimeBlock(timesheet, i+1, 10);
99  				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 }