View Javadoc

1   /**
2    * Copyright 2004-2012 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.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 }