/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package net.rrm.ehour.timesheet.service; import com.google.common.collect.Lists; import net.rrm.ehour.data.DateRange; import net.rrm.ehour.domain.*; import net.rrm.ehour.exception.OverBudgetException; import net.rrm.ehour.mail.service.ProjectManagerNotifierService; import net.rrm.ehour.persistence.timesheet.dao.TimesheetCommentDao; import net.rrm.ehour.persistence.timesheet.dao.TimesheetDao; import net.rrm.ehour.project.status.ProjectAssignmentStatus; import net.rrm.ehour.project.status.ProjectAssignmentStatus.Status; import net.rrm.ehour.project.status.ProjectAssignmentStatusService; import net.rrm.ehour.report.reports.element.AssignmentAggregateReportElement; import net.rrm.ehour.util.EhourConstants; import org.joda.time.DateTime; import org.joda.time.Interval; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.context.ApplicationContext; import scala.collection.JavaConversions; import scala.collection.mutable.Buffer; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class TimesheetPersistenceTest { private TimesheetPersistence persister; @Mock private TimesheetDao timesheetDAO; @Mock private ProjectManagerNotifierService projectManagerNotifierService; @Mock private ProjectAssignmentStatusService statusService; @Mock private TimesheetLockService timesheetLockService; @Mock private TimesheetCommentDao commentDao; @Mock private ApplicationContext context; private ProjectAssignment assignment; private List<TimesheetEntry> newEntries; private List<TimesheetEntry> existingEntries; private DateTime baseDate = DateTime.now().withTimeAtStartOfDay(); @Before public void setUp() { persister = new TimesheetPersistence(timesheetDAO, commentDao, statusService, projectManagerNotifierService, timesheetLockService, context); initData(); } @SuppressWarnings("deprecation") //new dates private void initData() { assignment = ProjectAssignmentObjectMother.createProjectAssignment(1); assignment.getProject().setProjectManager(UserObjectMother.createUser()); assignment.setNotifyPm(true); assignment.setAssignmentType(new ProjectAssignmentType(EhourConstants.ASSIGNMENT_TIME_ALLOTTED_FLEX)); newEntries = new ArrayList<>(); Date dateA = baseDate.toDate(); Date dateB = baseDate.plusDays(1).toDate(); { TimesheetEntry entry = new TimesheetEntry(); TimesheetEntryId id = new TimesheetEntryId(); id.setProjectAssignment(assignment); id.setEntryDate(dateA); entry.setEntryId(id); entry.setHours(8f); newEntries.add(entry); } { TimesheetEntry entryDel = new TimesheetEntry(); TimesheetEntryId idDel = new TimesheetEntryId(); idDel.setProjectAssignment(assignment); idDel.setEntryDate(dateB); entryDel.setEntryId(idDel); entryDel.setHours(null); newEntries.add(entryDel); } existingEntries = new ArrayList<>(); { TimesheetEntry entry = new TimesheetEntry(); TimesheetEntryId id = new TimesheetEntryId(); id.setProjectAssignment(assignment); id.setEntryDate(dateA); entry.setEntryId(id); entry.setHours(5f); existingEntries.add(entry); } { TimesheetEntry entryDel = new TimesheetEntry(); TimesheetEntryId idDel = new TimesheetEntryId(); idDel.setProjectAssignment(assignment); idDel.setEntryDate(dateB); entryDel.setEntryId(idDel); entryDel.setHours(5f); existingEntries.add(entryDel); } } @Test public void should_persist_new_timesheet() throws OverBudgetException { DateRange dateRange = new DateRange(); okStatus(); persister.validateAndPersist(assignment, newEntries, dateRange, Lists.<Date>newArrayList()); verify(statusService, times(2)).getAssignmentStatus(assignment); verify(timesheetDAO).persist(any(TimesheetEntry.class)); verify(timesheetDAO).getTimesheetEntriesInRange(assignment, dateRange); } @Test public void should_update_existing_timesheet() throws OverBudgetException { DateRange dateRange = new DateRange(); withExistingEntries(dateRange); okStatus(); persister.validateAndPersist(assignment, newEntries, dateRange, Lists.<Date>newArrayList()); verify(statusService, times(2)).getAssignmentStatus(assignment); verify(timesheetDAO).delete(any(TimesheetEntry.class)); verify(timesheetDAO).merge(any(TimesheetEntry.class)); } @Test public void should_not_persist_an_timesheet_that_went_overbudget() { DateRange dateRange = new DateRange(); withExistingEntries(dateRange); // before persist ProjectAssignmentStatus validStatus = new ProjectAssignmentStatus(); when(statusService.getAssignmentStatus(assignment)).thenReturn(validStatus); // after persist ProjectAssignmentStatus invalidStatus = new ProjectAssignmentStatus(); invalidStatus.addStatus(Status.OVER_OVERRUN); invalidStatus.setValid(false); when(statusService.getAssignmentStatus(assignment)).thenReturn(validStatus, invalidStatus); try { persister.validateAndPersist(assignment, newEntries, dateRange, Lists.<Date>newArrayList()); fail(); } catch (OverBudgetException e) { verify(timesheetDAO).merge(any(TimesheetEntry.class)); verify(timesheetDAO).delete(any(TimesheetEntry.class)); } } private void withExistingEntries(DateRange dateRange) { when(timesheetDAO.getTimesheetEntriesInRange(any(ProjectAssignment.class), eq(dateRange))).thenReturn(existingEntries); } @SuppressWarnings("deprecation") @Test public void should_allow_to_decrease_existing_hours_even_when_project_is_over_budget() throws OverBudgetException { Date dateC = new Date(2008 - 1900, Calendar.APRIL, 3); newEntries.clear(); existingEntries.clear(); { TimesheetEntry entryDel = new TimesheetEntry(); TimesheetEntryId idDel = new TimesheetEntryId(); idDel.setProjectAssignment(assignment); idDel.setEntryDate(dateC); entryDel.setEntryId(idDel); entryDel.setHours(7f); newEntries.add(entryDel); } { TimesheetEntry entryDel = new TimesheetEntry(); TimesheetEntryId idDel = new TimesheetEntryId(); idDel.setProjectAssignment(assignment); idDel.setEntryDate(dateC); entryDel.setEntryId(idDel); entryDel.setHours(8f); existingEntries.add(entryDel); } when(timesheetDAO.getTimesheetEntriesInRange(any(ProjectAssignment.class), any(DateRange.class))).thenReturn(existingEntries); ProjectAssignmentStatus beforeStatus = new ProjectAssignmentStatus(); beforeStatus.addStatus(Status.OVER_OVERRUN); beforeStatus.setValid(false); ProjectAssignmentStatus afterStatus = new ProjectAssignmentStatus(); afterStatus.addStatus(Status.OVER_OVERRUN); afterStatus.setValid(false); when(statusService.getAssignmentStatus(assignment)).thenReturn(beforeStatus, afterStatus); persister.validateAndPersist(assignment, newEntries, new DateRange(), Lists.<Date>newArrayList()); verify(timesheetDAO).merge(any(TimesheetEntry.class)); } @Test public void should_not_allow_to_book_more_hours_when_the_project_is_overbudget() { when(timesheetDAO.getTimesheetEntriesInRange(any(ProjectAssignment.class), any(DateRange.class))).thenReturn(existingEntries); ProjectAssignmentStatus beforeStatus = new ProjectAssignmentStatus(); beforeStatus.setValid(false); ProjectAssignmentStatus afterStatus = new ProjectAssignmentStatus(); afterStatus.addStatus(Status.OVER_OVERRUN); afterStatus.setValid(false); when(statusService.getAssignmentStatus(assignment)).thenReturn(beforeStatus, afterStatus); try { persister.validateAndPersist(assignment, newEntries, new DateRange(), Lists.<Date>newArrayList()); fail(); } catch (OverBudgetException ignored) { } } @Test public void should_mail_pm_when_status_of_project_changes() throws OverBudgetException { when(timesheetDAO.getLatestTimesheetEntryForAssignment(assignment.getAssignmentId())).thenReturn(newEntries.get(0)); when(timesheetDAO.getTimesheetEntriesInRange(any(ProjectAssignment.class), any(DateRange.class))).thenReturn(existingEntries); ProjectAssignmentStatus beforeStatus = new ProjectAssignmentStatus(); beforeStatus.addStatus(Status.IN_ALLOTTED); beforeStatus.setValid(true); ProjectAssignmentStatus afterStatus = new ProjectAssignmentStatus(); afterStatus.addStatus(Status.IN_OVERRUN); afterStatus.setValid(true); afterStatus.setAggregate(new AssignmentAggregateReportElement()); when(statusService.getAssignmentStatus(assignment)).thenReturn(beforeStatus); when(statusService.getAssignmentStatus(assignment)).thenReturn(beforeStatus, afterStatus); persister.validateAndPersist(assignment, newEntries, new DateRange(), Lists.<Date>newArrayList()); verify(timesheetDAO).delete(any(TimesheetEntry.class)); verify(timesheetDAO).merge(any(TimesheetEntry.class)); verify(projectManagerNotifierService).mailPMFlexAllottedReached(any(AssignmentAggregateReportElement.class), any(Date.class), eq(assignment.getProject().getProjectManager())); } @Test public void should_persist_individual_timesheet_entries_for_a_week() { TimesheetCommentId commentId = new TimesheetCommentId(1, new Date()); TimesheetComment comment = new TimesheetComment(commentId, "comment"); when(context.getBean(IPersistTimesheet.class)).thenReturn(persister); // through Spring for new TX per entr=y okStatus(); noLocks(); persister.persistTimesheetWeek(newEntries, comment, new DateRange(), UserObjectMother.createUser()); verify(commentDao).persist(comment); } @Test public void should_not_persist_when_whole_week_is_locked() throws OverBudgetException { okStatus(); TimesheetCommentId commentId = new TimesheetCommentId(1, new Date()); TimesheetComment comment = new TimesheetComment(commentId, "comment"); Date s = baseDate.toDate(); DateTime end = baseDate.plusWeeks(1); Date e = end.toDate(); User user = UserObjectMother.createUser(); when(context.getBean(IPersistTimesheet.class)).thenReturn(persister); withLock(new Interval(baseDate, end)); persister.persistTimesheetWeek(newEntries, comment, new DateRange(s, e), user); verify(timesheetDAO, never()).persist((any(TimesheetEntry.class))); } @Test public void should_not_persist_for_locked_day() throws OverBudgetException { okStatus(); TimesheetCommentId commentId = new TimesheetCommentId(1, new Date()); TimesheetComment comment = new TimesheetComment(commentId, "comment"); Date s = baseDate.toDate(); DateTime end = baseDate.plusWeeks(1); Date e = end.toDate(); User user = UserObjectMother.createUser(); when(context.getBean(IPersistTimesheet.class)).thenReturn(persister); TimesheetEntry entry = new TimesheetEntry(); TimesheetEntryId id = new TimesheetEntryId(); id.setProjectAssignment(assignment); id.setEntryDate(baseDate.plusDays(2).toDate()); entry.setEntryId(id); entry.setHours(5f); newEntries.add(entry); withLock(new Interval(baseDate.plusDays(2), end));// cancelling out the just created entry persister.persistTimesheetWeek(newEntries, comment, new DateRange(s, e), user); verify(timesheetDAO, times(1)).persist((any(TimesheetEntry.class))); verify(timesheetDAO, never()).delete((any(TimesheetEntry.class))); verify(commentDao, times(1)).persist(any(TimesheetComment.class)); } @Test public void should_not_persist_comment_when_whole_week_is_locked() throws OverBudgetException { okStatus(); TimesheetCommentId commentId = new TimesheetCommentId(1, new Date()); TimesheetComment comment = new TimesheetComment(commentId, "comment"); Date s = baseDate.toDate(); DateTime end = baseDate.plusWeeks(1); Date e = end.toDate(); User user = UserObjectMother.createUser(); when(context.getBean(IPersistTimesheet.class)).thenReturn(persister); withLock(new Interval(baseDate, end)); persister.persistTimesheetWeek(newEntries, comment, new DateRange(s, e), user); verify(timesheetDAO, never()).persist((any(TimesheetEntry.class))); verify(commentDao, never()).persist(any(TimesheetComment.class)); } @Test public void should_delete_entries_that_are_not_resubmitted() throws OverBudgetException { DateRange dateRange = new DateRange(); withExistingEntries(dateRange); okStatus(); persister.validateAndPersist(assignment, Lists.newArrayList(newEntries.get(0)), dateRange, Lists.<Date>newArrayList()); verify(statusService, times(2)).getAssignmentStatus(assignment); verify(timesheetDAO).delete(existingEntries.get(1)); verify(timesheetDAO).merge(existingEntries.get(0)); } private void okStatus() { when(statusService.getAssignmentStatus(assignment)).thenReturn(new ProjectAssignmentStatus()); } private void withLock(Interval... lockedRange) { Buffer<Interval> scalaBuffer = JavaConversions.asScalaBuffer(Lists.newArrayList(lockedRange)); when(timesheetLockService.findLockedDatesInRange(any(Date.class), any(Date.class), any(User.class))).thenReturn(scalaBuffer.toList()); } private void noLocks() { Buffer<Interval> scalaBuffer = JavaConversions.asScalaBuffer(Lists.<Interval>newArrayList()); scala.collection.immutable.List<Interval> intervals = scalaBuffer.toList(); when(timesheetLockService.findLockedDatesInRange(any(Date.class), any(Date.class), any(User.class))).thenReturn(intervals); } }