///////////////////////////////////////////////////////////////////////////// // // Project ProjectForge Community Edition // www.projectforge.org // // Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de) // // ProjectForge is dual-licensed. // // This community edition 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; version 3 of the License. // // This community edition 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, see http://www.gnu.org/licenses/. // ///////////////////////////////////////////////////////////////////////////// package org.projectforge.task; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.Serializable; import java.util.Calendar; import java.util.Date; import java.util.List; import org.junit.Test; import org.projectforge.access.AccessException; import org.projectforge.access.AccessType; import org.projectforge.access.OperationType; import org.projectforge.common.DateHolder; import org.projectforge.core.UserException; import org.projectforge.fibu.ProjektDO; import org.projectforge.fibu.ProjektDao; import org.projectforge.fibu.kost.Kost2ArtDO; import org.projectforge.fibu.kost.Kost2ArtDao; import org.projectforge.fibu.kost.Kost2DO; import org.projectforge.fibu.kost.Kost2Dao; import org.projectforge.test.TestBase; import org.projectforge.timesheet.TimesheetDO; import org.projectforge.timesheet.TimesheetDao; import org.projectforge.user.GroupDO; public class TaskTest extends TestBase { // private static final Logger log = Logger.getLogger(TaskTest.class); private TaskDao taskDao; private ProjektDao projektDao; private Kost2Dao kost2Dao; private Kost2ArtDao kost2ArtDao; private TaskTree taskTree; private TimesheetDao timesheetDao; public void setTaskDao(final TaskDao taskDao) { this.taskDao = taskDao; } public void setProjektDao(final ProjektDao projektDao) { this.projektDao = projektDao; } public void setKost2Dao(final Kost2Dao kost2Dao) { this.kost2Dao = kost2Dao; } public void setKost2ArtDao(final Kost2ArtDao kost2ArtDao) { this.kost2ArtDao = kost2ArtDao; } public void setTaskTree(final TaskTree taskTree) { this.taskTree = taskTree; } public void setTimesheetDao(final TimesheetDao timesheetDao) { this.timesheetDao = timesheetDao; } @Test public void testTaskDO() { final List<TaskDO> list = taskDao.internalLoadAll(); for (final TaskDO task : list) { if ("root".equals(task.getTitle()) == true) { assertNull("Only root node has no parent task.", task.getParentTaskId()); } else { assertNotNull("Only root node has no parent task.", task.getParentTaskId()); } } final TaskDO task = super.getTask("1.1"); logon(ADMIN); final TaskDO dbTask = taskDao.getById(task.getId()); assertEquals(task.getId(), dbTask.getId()); assertEquals(task.getTitle(), dbTask.getTitle()); } @Test public void testTaskTree() { final TaskTree taskTree = taskDao.getTaskTree(); final TaskNode root = taskTree.getRootTaskNode(); assertNull(root.getParent()); assertEquals("root", root.getTask().getTitle()); assertNotNull("root node must have childs", root.getChilds()); assertTrue("root node must have childs", root.getChilds().size() > 0); final TaskNode node1_1 = taskTree.getTaskNodeById(getTask("1.1").getId()); assertEquals(getTask("1.1").getTitle(), node1_1.getTask().getTitle()); assertEquals(getTask("1.1").getParentTaskId(), node1_1.getParent().getId()); final TaskNode node1 = taskTree.getTaskNodeById(getTask("1").getId()); final List<TaskNode> list = node1.getChilds(); assertEquals("Childs of 1 are 1.1 and 1.2", 2, list.size()); final TaskNode task1_1_1 = taskTree.getTaskNodeById(getTask("1.1.1").getId()); final List<TaskNode> path = task1_1_1.getPathToRoot(); assertEquals("Node has 2 ancestors plus itself.", 3, path.size()); assertEquals("Top task in path should be '1'", getTask("1").getId(), path.get(0).getId()); assertEquals("Second task in path sould be '1.1'", getTask("1.1").getId(), path.get(1).getId()); assertEquals("Third task in path is the node itself: '1.1'", getTask("1.1.1").getId(), path.get(2).getId()); } @Test public void testTraversingTaskTree() { final TaskTree taskTree = taskDao.getTaskTree(); final TaskNode root = taskTree.getRootTaskNode(); logStart("Traversing TaskTree"); traverseTaskTree(root); logEnd(); } @Test public void testCyclicTasks() { final TaskTree tree = taskDao.getTaskTree(); initTestDB.addTask("cyclictest", "root"); initTestDB.addTask("c", "cyclictest"); initTestDB.addTask("c.1", "c"); initTestDB.addTask("c.1.1", "c.1"); final TaskNode c = tree.getTaskNodeById(getTask("c").getId()); final TaskNode c_1_1 = tree.getTaskNodeById(getTask("c.1.1").getId()); try { c.setParent(c_1_1); fail("Cyclic reference not detected."); } catch (final UserException ex) { assertEquals(TaskDao.I18N_KEY_ERROR_CYCLIC_REFERENCE, ex.getI18nKey()); } try { c.setParent(c); fail("Cyclic reference not detected."); } catch (final UserException ex) { assertEquals(TaskDao.I18N_KEY_ERROR_CYCLIC_REFERENCE, ex.getI18nKey()); } } @Test public void testTaskDescendants() { final TaskTree tree = taskDao.getTaskTree(); initTestDB.addTask("descendanttest", "root"); initTestDB.addTask("d", "descendanttest"); initTestDB.addTask("d.1", "d"); initTestDB.addTask("d.1.1", "d.1"); initTestDB.addTask("d.1.2", "d.1"); initTestDB.addTask("d.1.2.1", "d.1.2"); initTestDB.addTask("d.2", "d"); final TaskNode d = tree.getTaskNodeById(getTask("d").getId()); final List<Integer> ids = d.getDescendantIds(); assertEquals(5, ids.size()); assertTrue(ids.contains(getTask("d.1").getId())); assertTrue(ids.contains(getTask("d.1.1").getId())); assertTrue(ids.contains(getTask("d.1.2").getId())); assertTrue(ids.contains(getTask("d.1.2.1").getId())); assertTrue(ids.contains(getTask("d.2").getId())); assertFalse(ids.contains(getTask("d").getId())); } @Test public void testTaskTreeUpdate() { final TaskTree tree = taskDao.getTaskTree(); initTestDB.addTask("taskTreeUpdateTest", "root"); initTestDB.addTask("u", "taskTreeUpdateTest"); final TaskNode u = tree.getTaskNodeById(getTask("u").getId()); final TaskNode parent = tree.getTaskNodeById(getTask("taskTreeUpdateTest").getId()); assertEquals("Should have no childs", false, u.hasChilds()); assertEquals(u.getParent().getId(), parent.getId()); initTestDB.addTask("u.1", "u"); assertEquals("Should have childs", true, u.hasChilds()); assertEquals("Should have exact 1 child", 1, u.getChilds().size()); initTestDB.addTask("u.2", "u"); assertEquals("Should have exact 2 childs", 2, u.getChilds().size()); initTestDB.addTask("u.2.1", "u.2"); initTestDB.addTask("u.2.2", "u.2"); initTestDB.addTask("u.2.3", "u.2"); final TaskNode u1 = tree.getTaskNodeById(getTask("u.1").getId()); final TaskNode u2 = tree.getTaskNodeById(getTask("u.2").getId()); assertEquals("Should have exact 3 childs", 3, u2.getChilds().size()); // Now we move u.2.3 to u.1.1: final TaskDO tu_2_3 = taskDao.internalGetById(getTask("u.2.3").getId()); tu_2_3.setTitle("u.1.1"); taskDao.setParentTask(tu_2_3, getTask("u.1").getId()); taskDao.internalUpdate(tu_2_3); assertEquals("Should have exact 2 childs", 2, u2.getChilds().size()); assertEquals("Should have exact 1 child", 1, u1.getChilds().size()); final TaskDO tu_1_1 = taskDao.internalGetById(getTask("u.2.3").getId()); assertEquals("u.1.1", tu_1_1.getTitle()); assertEquals(getTask("u.1").getId(), tu_1_1.getParentTaskId()); final TaskNode u_1_1 = tree.getTaskNodeById(tu_1_1.getId()); assertEquals("u.1.1", u_1_1.getTask().getTitle()); assertEquals(getTask("u.1").getId(), u_1_1.getParent().getId()); } /** * Checks task movements: Does the user has access to delete the task in the old hierarchy and the access to insert the task in the new * hierarchy? */ @Test public void checkTaskAccess() { initTestDB.addTask("accesstest", "root"); initTestDB.addTask("a", "accesstest"); initTestDB.addTask("a.1", "a"); initTestDB.addTask("a.1.1", "a.1"); initTestDB.addTask("a.1.2", "a.1"); initTestDB.addTask("a.2", "a"); initTestDB.addTask("a.2.1", "a.2"); initTestDB.addTask("a.2.2", "a.2"); initTestDB.addUser("taskTest1"); logon("taskTest1"); try { taskDao.getById(getTask("a.1").getId()); fail("User has no access to select task a.1"); } catch (final AccessException ex) { assertAccessException(ex, getTask("a.1").getId(), AccessType.TASKS, OperationType.SELECT); } initTestDB.addGroup("taskTest1", new String[] { "taskTest1"}); initTestDB.createGroupTaskAccess(getGroup("taskTest1"), getTask("a.1"), AccessType.TASKS, true, true, true, true); TaskDO task = taskDao.getById(getTask("a.1").getId()); assertEquals("Now readable.", "a.1", task.getTitle()); task = taskDao.getById(getTask("a.1.1").getId()); assertEquals("Also child tasks are now readable.", "a.1.1", task.getTitle()); taskDao.setParentTask(task, getTask("a.2").getId()); try { taskDao.update(task); fail("User has no access to insert task as child of a.2"); } catch (final AccessException ex) { assertAccessException(ex, getTask("a.2").getId(), AccessType.TASKS, OperationType.INSERT); } initTestDB.createGroupTaskAccess(getGroup("taskTest1"), getTask("a.2"), AccessType.TASKS, true, false, false, false); task = taskDao.getById(getTask("a.2.1").getId()); task.setTitle("a.2.1test"); try { taskDao.update(task); fail("User has no access to update child task of a.2"); } catch (final AccessException ex) { assertAccessException(ex, getTask("a.2.1").getId(), AccessType.TASKS, OperationType.UPDATE); } initTestDB.addUser("taskTest2"); logon("taskTest2"); initTestDB.addGroup("taskTest2", new String[] { "taskTest2"}); initTestDB.createGroupTaskAccess(getGroup("taskTest2"), getTask("a.1"), AccessType.TASKS, true, true, true, true); initTestDB.createGroupTaskAccess(getGroup("taskTest2"), getTask("a.2"), AccessType.TASKS, true, true, true, false); task = taskDao.getById(getTask("a.2.1").getId()); taskDao.setParentTask(task, getTask("a.1").getId()); try { taskDao.update(task); fail("User has no access to delete child task from a.2"); } catch (final AccessException ex) { assertAccessException(ex, getTask("a.2").getId(), AccessType.TASKS, OperationType.DELETE); } } @Test public void checkAccess() { logon(TEST_ADMIN_USER); final TaskDO task = initTestDB.addTask("checkAccessTestTask", "root"); initTestDB.addGroup("checkAccessTestGroup", new String[] { TEST_USER}); initTestDB.createGroupTaskAccess(getGroup("checkAccessTestGroup"), getTask("checkAccessTestTask"), AccessType.TASKS, true, true, true, true); logon(TEST_FINANCE_USER); final Kost2ArtDO kost2Art = new Kost2ArtDO(); kost2Art.setId(42); kost2Art.setName("Test"); kost2ArtDao.save(kost2Art); final Kost2DO kost2 = new Kost2DO(); kost2.setNummernkreis(3); kost2.setBereich(0); kost2.setTeilbereich(42); kost2.setKost2Art(kost2Art); kost2Dao.save(kost2); final ProjektDO projekt = new ProjektDO(); projekt.setInternKost2_4(123); projekt.setName("Testprojekt"); projektDao.save(projekt); checkAccess(TEST_ADMIN_USER, task.getId(), projekt, kost2); checkAccess(TEST_USER, task.getId(), projekt, kost2); } @Test public void checkKost2AndTimesheetBookingStatusAccess() { logon(TEST_FINANCE_USER); final TaskDO task = initTestDB.addTask("checkKost2AndTimesheetStatusAccessTask", "root"); final String groupName = "checkKost2AndTimesheetBookingStatusAccessGroup"; // Please note: TEST_USER is no project manager or assistant! final GroupDO projectManagers = initTestDB.addGroup(groupName, new String[] { TEST_PROJECT_MANAGER_USER, TEST_PROJECT_ASSISTANT_USER, TEST_USER}); initTestDB.createGroupTaskAccess(projectManagers, task, AccessType.TASKS, true, true, true, true); // All rights. final ProjektDO projekt = new ProjektDO().setName("checkKost2AndTimesheetBookingStatusAccess").setInternKost2_4(764).setNummer(1) .setProjektManagerGroup(projectManagers).setTask(task); projektDao.save(projekt); logon(TEST_USER); TaskDO task1 = new TaskDO().setParentTask(task).setTitle("Task 1").setKost2BlackWhiteList("Hurzel"); try { taskDao.save(task1); fail("AccessException expected."); } catch (final AccessException ex) { assertEquals("task.error.kost2Readonly", ex.getI18nKey()); // OK } try { task1.setKost2BlackWhiteList(null); task1.setKost2IsBlackList(true); taskDao.save(task1); fail("AccessException expected."); } catch (final AccessException ex) { assertEquals("task.error.kost2Readonly", ex.getI18nKey()); // OK } try { task1.setKost2IsBlackList(false); task1.setTimesheetBookingStatus(TimesheetBookingStatus.ONLY_LEAFS); taskDao.save(task1); fail("AccessException expected."); } catch (final AccessException ex) { assertEquals("task.error.timesheetBookingStatus2Readonly", ex.getI18nKey()); // OK } logon(TEST_PROJECT_MANAGER_USER); task1.setKost2IsBlackList(true); task1.setTimesheetBookingStatus(TimesheetBookingStatus.ONLY_LEAFS); task1 = taskDao.getById(taskDao.save(task1)); logon(TEST_USER); task1.setKost2BlackWhiteList("123456"); try { taskDao.update(task1); fail("AccessException expected."); } catch (final AccessException ex) { assertEquals("task.error.kost2Readonly", ex.getI18nKey()); // OK } try { task1.setKost2BlackWhiteList(null); task1.setKost2IsBlackList(false); taskDao.update(task1); fail("AccessException expected."); } catch (final AccessException ex) { assertEquals("task.error.kost2Readonly", ex.getI18nKey()); // OK } try { task1.setKost2IsBlackList(true); task1.setTimesheetBookingStatus(TimesheetBookingStatus.INHERIT); taskDao.update(task1); fail("AccessException expected."); } catch (final AccessException ex) { assertEquals("task.error.timesheetBookingStatus2Readonly", ex.getI18nKey()); // OK } logon(TEST_PROJECT_MANAGER_USER); task1.setKost2BlackWhiteList("123456"); task1.setKost2IsBlackList(false); task1.setTimesheetBookingStatus(TimesheetBookingStatus.INHERIT); taskDao.update(task1); } private void checkAccess(final String user, final Serializable id, final ProjektDO projekt, final Kost2DO kost2) { logon(user); TaskDO task = taskDao.getById(id); task.setProtectTimesheetsUntil(new Date()); try { taskDao.update(task); fail("AccessException expected."); } catch (final AccessException ex) { // OK assertEquals("task.error.protectTimesheetsUntilReadonly", ex.getI18nKey()); } task.setProtectTimesheetsUntil(null); task.setProtectionOfPrivacy(true); try { taskDao.update(task); fail("AccessException expected."); } catch (final AccessException ex) { // OK assertEquals("task.error.protectionOfPrivacyReadonly", ex.getI18nKey()); } task = taskDao.getById(id); task = new TaskDO(); task.setParentTask(getTask("checkAccessTestTask")); task.setProtectTimesheetsUntil(new Date()); try { taskDao.save(task); fail("AccessException expected."); } catch (final AccessException ex) { // OK assertEquals("task.error.protectTimesheetsUntilReadonly", ex.getI18nKey()); } task.setProtectTimesheetsUntil(null); task.setProtectionOfPrivacy(true); try { taskDao.save(task); fail("AccessException expected."); } catch (final AccessException ex) { // OK assertEquals("task.error.protectionOfPrivacyReadonly", ex.getI18nKey()); } task = taskDao.getById(id); } /** * Sister tasks should have different names. */ @Test public void testDuplicateTaskNames() { initTestDB.addTask("duplicateTaskNamesTest", "root"); initTestDB.addTask("dT.1", "duplicateTaskNamesTest"); initTestDB.addTask("dT.2", "duplicateTaskNamesTest"); initTestDB.addTask("dT.1.1", "dT.1"); try { // Try to insert sister task with same name: initTestDB.addTask("dT.1.1", "dT.1"); fail("Duplicate task was not detected."); } catch (final UserException ex) { assertEquals(TaskDao.I18N_KEY_ERROR_DUPLICATE_CHILD_TASKS, ex.getI18nKey()); } TaskDO task = initTestDB.addTask("dT.1.2", "dT.1"); task.setTitle("dT.1.1"); try { // Try to rename task to same name as a sister task: taskDao.internalUpdate(task); fail("Duplicate task was not detected."); } catch (final UserException ex) { assertEquals(TaskDao.I18N_KEY_ERROR_DUPLICATE_CHILD_TASKS, ex.getI18nKey()); } task = initTestDB.addTask("dT.1.1", "dT.2"); task.setParentTask(initTestDB.getTask("dT.1")); try { // Try to move task from dT.1.2 to dT.1.1 where already a task with the same name exists. taskDao.internalUpdate(task); fail("Duplicate task was not detected."); } catch (final UserException ex) { assertEquals(TaskDao.I18N_KEY_ERROR_DUPLICATE_CHILD_TASKS, ex.getI18nKey()); } task.setParentTask(initTestDB.getTask("dT.2")); taskDao.internalUpdate(task); } @Test public void readTotalDuration() { logon(getUser(TEST_ADMIN_USER)); final TaskDO task = initTestDB.addTask("totalDurationTask", "root"); final TaskDO subTask1 = initTestDB.addTask("totalDurationTask.subtask1", "totalDurationTask"); final TaskDO subTask2 = initTestDB.addTask("totalDurationTask.subtask2", "totalDurationTask"); assertEquals(0, taskDao.readTotalDuration(task.getId())); final DateHolder dh = new DateHolder(); dh.setDate(2010, Calendar.APRIL, 20, 8, 0); TimesheetDO ts = new TimesheetDO().setUser(getUser(TEST_USER)).setStartDate(dh.getDate()).setStopTime( dh.add(Calendar.HOUR_OF_DAY, 4).getTimestamp()).setTask(task); timesheetDao.save(ts); assertEquals(4 * 3600, taskDao.readTotalDuration(task.getId())); assertEquals(4 * 3600, getTotalDuration(task.getId())); ts = new TimesheetDO().setUser(getUser(TEST_USER)).setStartDate(dh.add(Calendar.HOUR_OF_DAY, 1).getDate()).setStopTime( dh.add(Calendar.HOUR_OF_DAY, 4).getTimestamp()).setTask(task); timesheetDao.save(ts); assertEquals(8 * 3600, taskDao.readTotalDuration(task.getId())); assertEquals(8 * 3600, getTotalDuration(task.getId())); ts = new TimesheetDO().setUser(getUser(TEST_USER)).setStartDate(dh.add(Calendar.HOUR_OF_DAY, 1).getDate()).setStopTime( dh.add(Calendar.HOUR_OF_DAY, 4).getTimestamp()).setTask(subTask1); timesheetDao.save(ts); final List<Object[]> list = taskDao.readTotalDurations(); boolean taskFound = false; boolean subtask1Found = false; for (final Object[] oa : list) { final Integer taskId = (Integer) oa[1]; if (taskId == task.getId()) { assertFalse("Entry should only exist once.", taskFound); assertFalse("Entry not first.", subtask1Found); taskFound = true; assertEquals(new Long(8 * 3600), oa[0]); } else if (taskId == subTask1.getId()) { assertFalse("Entry should only exist once.", subtask1Found); assertTrue("Entry not second.", taskFound); subtask1Found = true; assertEquals(new Long(4 * 3600), oa[0]); } else if (taskId == subTask2.getId()) { fail("Entry not not expected."); } } assertEquals(12 * 3600, getTotalDuration(task.getId())); assertEquals(8 * 3600, getDuration(task.getId())); assertEquals(4 * 3600, getTotalDuration(subTask1.getId())); assertEquals(4 * 3600, getDuration(subTask1.getId())); assertEquals(0, getTotalDuration(subTask2.getId())); assertEquals(0, getDuration(subTask2.getId())); taskTree.refresh(); // Should be same after refresh (there was an error). assertEquals(12 * 3600, getTotalDuration(task.getId())); assertEquals(8 * 3600, getDuration(task.getId())); assertEquals(4 * 3600, getTotalDuration(subTask1.getId())); assertEquals(4 * 3600, getDuration(subTask1.getId())); assertEquals(0, getTotalDuration(subTask2.getId())); assertEquals(0, getDuration(subTask2.getId())); } private long getTotalDuration(final Integer taskId) { return taskTree.getTaskNodeById(taskId).getDuration(taskTree, true); } private long getDuration(final Integer taskId) { return taskTree.getTaskNodeById(taskId).getDuration(taskTree, false); } private void traverseTaskTree(final TaskNode node) { logDot(); final List<TaskNode> childs = node.getChilds(); if (childs != null) { for (final TaskNode child : childs) { assertEquals("Child should have parent id of current node.", node.getId(), child.getParentId()); traverseTaskTree(child); } } } }