/******************************************************************************* * Copyright (c) 2006, 2012 Steffen Pingel and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Steffen Pingel - initial API and implementation * Benjamin Muskalla - bug 386920 *******************************************************************************/ package org.eclipse.mylyn.trac.tests.core; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; import junit.framework.TestCase; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.mylyn.commons.net.AuthenticationCredentials; import org.eclipse.mylyn.commons.net.AuthenticationType; import org.eclipse.mylyn.internal.tasks.core.TaskTask; import org.eclipse.mylyn.internal.tasks.core.data.TextTaskAttachmentSource; import org.eclipse.mylyn.internal.tasks.core.sync.SynchronizationSession; import org.eclipse.mylyn.internal.trac.core.TracAttribute; import org.eclipse.mylyn.internal.trac.core.TracAttributeMapper; import org.eclipse.mylyn.internal.trac.core.TracCorePlugin; import org.eclipse.mylyn.internal.trac.core.TracRepositoryConnector; import org.eclipse.mylyn.internal.trac.core.TracTaskDataHandler; import org.eclipse.mylyn.internal.trac.core.TracTaskMapper; import org.eclipse.mylyn.internal.trac.core.client.ITracClient; import org.eclipse.mylyn.internal.trac.core.model.TracTicket; import org.eclipse.mylyn.internal.trac.core.model.TracTicket.Key; import org.eclipse.mylyn.internal.trac.core.util.TracUtil; import org.eclipse.mylyn.tasks.core.ITask; import org.eclipse.mylyn.tasks.core.ITaskAttachment; import org.eclipse.mylyn.tasks.core.RepositoryStatus; import org.eclipse.mylyn.tasks.core.TaskMapping; import org.eclipse.mylyn.tasks.core.TaskRepository; import org.eclipse.mylyn.tasks.core.data.AbstractTaskAttachmentHandler; import org.eclipse.mylyn.tasks.core.data.TaskAttribute; import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper; import org.eclipse.mylyn.tasks.core.data.TaskData; import org.eclipse.mylyn.tasks.core.data.TaskMapper; import org.eclipse.mylyn.tasks.core.data.TaskOperation; import org.eclipse.mylyn.tasks.core.data.TaskRelation; import org.eclipse.mylyn.trac.tests.support.TracFixture; import org.eclipse.mylyn.trac.tests.support.TracHarness; import org.eclipse.mylyn.trac.tests.support.TracTestUtil; /** * @author Steffen Pingel * @author Benjamin Muskalla */ public class TracTaskDataHandlerXmlRpcTest extends TestCase { private TracRepositoryConnector connector; private TaskRepository repository; private TracTaskDataHandler taskDataHandler; private ITracClient client; private TracHarness harness; public TracTaskDataHandlerXmlRpcTest() { } @Override protected void setUp() throws Exception { harness = TracFixture.current().createHarness(); connector = harness.connector(); taskDataHandler = connector.getTaskDataHandler(); repository = harness.repository(); client = connector.getClientManager().getTracClient(repository); } @Override protected void tearDown() throws Exception { harness.dispose(); } private SynchronizationSession createSession(ITask... tasks) { SynchronizationSession session = new SynchronizationSession(); session.setNeedsPerformQueries(true); session.setTaskRepository(repository); session.setFullSynchronization(true); session.setTasks(new HashSet<ITask>(Arrays.asList(tasks))); return session; } public void testMarkStaleTasks() throws Exception { SynchronizationSession session; // sleep for one second to ensure that the created ticket has a unique time stamp Thread.sleep(1000); TracTicket ticket = harness.createTicket("markStaleTasks"); ITask task = harness.getTask(ticket); long lastModified = TracUtil.toTracTime(task.getModificationDate()); // an empty set should not cause contact to the repository repository.setSynchronizationTimeStamp(null); session = createSession(task); connector.preSynchronization(session, null); assertTrue(session.needsPerformQueries()); assertNull(repository.getSynchronizationTimeStamp()); repository.setSynchronizationTimeStamp(null); session = createSession(task); connector.preSynchronization(session, null); assertTrue(session.needsPerformQueries()); assertEquals(Collections.singleton(task), session.getStaleTasks()); // always returns the ticket because time comparison mode is >= repository.setSynchronizationTimeStamp(lastModified + ""); session = createSession(task); connector.preSynchronization(session, null); // false since query that check for changed tasks only returns a single task assertFalse(session.needsPerformQueries()); assertEquals(Collections.emptySet(), session.getStaleTasks()); // nothing has changed, should not detect a change repository.setSynchronizationTimeStamp((lastModified + 1) + ""); session = createSession(task); connector.preSynchronization(session, null); assertFalse(session.needsPerformQueries()); assertEquals(Collections.emptySet(), session.getStaleTasks()); long mostRecentlyModified = 0; // try changing ticket 3x to make sure it gets a new change time for (int i = 0; i < 3; i++) { ticket = client.getTicket(ticket.getId(), null); ticket.putBuiltinValue(Key.DESCRIPTION, lastModified + ""); client.updateTicket(ticket, "comment", null); mostRecentlyModified = TracUtil.toTracTime(ticket.getLastChanged()); // needs to be at least one second ahead of repository time stamp if (mostRecentlyModified >= lastModified + 1) { break; } else if (i == 2) { fail("Failed to update ticket modification time: ticket id=" + ticket.getId() + ", lastModified=" + lastModified + ", mostRectentlyModified=" + mostRecentlyModified); } Thread.sleep(1500); } // should now detect a change repository.setSynchronizationTimeStamp((lastModified + 1) + ""); session = createSession(task); connector.preSynchronization(session, null); assertTrue("Expected change: ticket id=" + ticket.getId() + ", lastModified=" + lastModified + ", mostRectentlyModified=" + mostRecentlyModified, session.needsPerformQueries()); assertEquals(Collections.singleton(task), session.getStaleTasks()); } public void testMarkStaleTasksNoTimeStamp() throws Exception { ITask task = harness.createTask("MarkStaleTasksNoTimeStamp"); SynchronizationSession session = createSession(task); repository.setSynchronizationTimeStamp(null); connector.preSynchronization(session, null); assertTrue(session.needsPerformQueries()); assertEquals(Collections.singleton(task), session.getStaleTasks()); session = createSession(task); repository.setSynchronizationTimeStamp(""); connector.preSynchronization(session, null); assertTrue(session.needsPerformQueries()); assertEquals(Collections.singleton(task), session.getStaleTasks()); session = createSession(task); repository.setSynchronizationTimeStamp("0"); connector.preSynchronization(session, null); assertTrue(session.needsPerformQueries()); assertEquals(Collections.singleton(task), session.getStaleTasks()); session = createSession(task); repository.setSynchronizationTimeStamp("abc"); connector.preSynchronization(session, null); assertTrue(session.needsPerformQueries()); assertEquals(Collections.singleton(task), session.getStaleTasks()); } public void testNonNumericTaskId() { try { connector.getTaskData(repository, "abc", null); fail("Expected CoreException"); } catch (CoreException e) { } } public void testAttachmentChangesLastModifiedDate() throws Exception { AbstractTaskAttachmentHandler attachmentHandler = connector.getTaskAttachmentHandler(); ITask task = harness.createTask("attachmentChangesLastModifiedDate"); Date lastModified = task.getModificationDate(); harness.getFixture().waitToGuaranteeTaskUpdate(); // XXX the test case fails when comment == null attachmentHandler.postContent(repository, task, new TextTaskAttachmentSource("abc"), "comment", null, null); task = harness.getTask(task.getTaskId()); Date newLastModified = task.getModificationDate(); assertTrue("Expected " + newLastModified + " to be more recent than " + lastModified, newLastModified.after(lastModified)); } public void testAttachmentUrlEncoding() throws Exception { AbstractTaskAttachmentHandler attachmentHandler = connector.getTaskAttachmentHandler(); TracTicket ticket = harness.createTicket("attachment url test"); ITask task = harness.getTask(ticket); attachmentHandler.postContent(repository, task, new TextTaskAttachmentSource("abc") { @Override public String getName() { return "https%3A%2F%2Fbugs.eclipse.org%2Fbugs.xml.zip"; } }, "comment", null, null); task = harness.getTask(ticket); List<ITaskAttachment> attachments = TracTestUtil.getTaskAttachments(task); assertEquals(1, attachments.size()); assertEquals(repository.getUrl() + "/attachment/ticket/" + ticket.getId() + "/https%253A%252F%252Fbugs.eclipse.org%252Fbugs.xml.zip", attachments.get(0).getUrl()); } public void testPostTaskDataInvalidCredentials() throws Exception { TaskData taskData = harness.createTaskData("postTaskDataInvalidCredentials"); taskData.getRoot().getMappedAttribute(TaskAttribute.COMMENT_NEW).setValue("new comment"); repository.setCredentials(AuthenticationType.REPOSITORY, new AuthenticationCredentials("foo", "bar"), false); try { taskDataHandler.postTaskData(repository, taskData, null, null); } catch (CoreException expected) { assertEquals(RepositoryStatus.ERROR_REPOSITORY_LOGIN, expected.getStatus().getCode()); } assertEquals("new comment", taskData.getRoot().getMappedAttribute(TaskAttribute.COMMENT_NEW).getValue()); } public void testCanInitializeTaskData() throws Exception { ITask task = new TaskTask(TracCorePlugin.CONNECTOR_KIND, "", ""); assertFalse(taskDataHandler.canInitializeSubTaskData(repository, task)); task.setAttribute(TracRepositoryConnector.TASK_KEY_SUPPORTS_SUBTASKS, Boolean.TRUE.toString()); assertTrue(taskDataHandler.canInitializeSubTaskData(repository, task)); } public void testCanInitializeTaskDataRepositoryTask() throws Exception { ITask task = harness.createTask("canInitializeTaskDataRepositoryTask"); TaskData taskData = taskDataHandler.getTaskData(repository, task.getTaskId(), null); assertFalse(taskDataHandler.canInitializeSubTaskData(repository, task)); taskData.getRoot().createAttribute(TracTaskDataHandler.ATTRIBUTE_BLOCKED_BY); connector.updateTaskFromTaskData(repository, task, taskData); assertTrue(taskDataHandler.canInitializeSubTaskData(repository, task)); task.setAttribute(TracRepositoryConnector.TASK_KEY_SUPPORTS_SUBTASKS, Boolean.FALSE.toString()); connector.updateTaskFromTaskData(repository, task, taskData); assertTrue(taskDataHandler.canInitializeSubTaskData(repository, task)); } public void testInitializeSubTaskDataInvalidParent() throws Exception { TaskData parentTaskData = harness.createTaskData("initializeSubTaskDataInvalidParent"); try { taskDataHandler.initializeSubTaskData(repository, parentTaskData, parentTaskData, null); fail("expected CoreException"); } catch (CoreException expected) { } } public void testInitializeSubTaskData() throws Exception { TaskData parentTaskData = harness.createTaskData("initializeSubTaskData"); TaskMapper parentTaskMapper = new TracTaskMapper(parentTaskData, null); parentTaskMapper.setSummary("abc"); parentTaskMapper.setDescription("def"); String component = parentTaskData.getRoot() .getMappedAttribute(TracAttribute.COMPONENT.getTracKey()) .getOptions() .get(0); parentTaskMapper.setComponent(component); parentTaskData.getRoot().createAttribute(TracTaskDataHandler.ATTRIBUTE_BLOCKED_BY); TaskData subTaskData = new TaskData(parentTaskData.getAttributeMapper(), TracCorePlugin.CONNECTOR_KIND, "", ""); subTaskData.getRoot().createAttribute(TracTaskDataHandler.ATTRIBUTE_BLOCKING); taskDataHandler.initializeSubTaskData(repository, subTaskData, parentTaskData, new NullProgressMonitor()); TaskMapper subTaskMapper = new TracTaskMapper(subTaskData, null); assertEquals("", subTaskMapper.getSummary()); assertEquals("", subTaskMapper.getDescription()); assertEquals(component, subTaskMapper.getComponent()); TaskAttribute attribute = subTaskData.getRoot().getMappedAttribute(TracTaskDataHandler.ATTRIBUTE_BLOCKING); assertEquals(parentTaskData.getTaskId(), attribute.getValue()); attribute = parentTaskData.getRoot().getMappedAttribute(TracTaskDataHandler.ATTRIBUTE_BLOCKED_BY); assertEquals("", attribute.getValue()); } public void testGetSubTaskIds() throws Exception { TaskData taskData = new TaskData(new TracAttributeMapper(new TaskRepository("", ""), client), TracCorePlugin.CONNECTOR_KIND, "", ""); TaskAttribute blockedBy = taskData.getRoot().createAttribute(TracTaskDataHandler.ATTRIBUTE_BLOCKED_BY); Collection<String> subTaskIds; blockedBy.setValue("123 456"); subTaskIds = getSubTaskIds(taskData); assertEquals(2, subTaskIds.size()); assertTrue(subTaskIds.contains("123")); assertTrue(subTaskIds.contains("456")); blockedBy.setValue("7,8"); subTaskIds = getSubTaskIds(taskData); assertEquals(2, subTaskIds.size()); assertTrue(subTaskIds.contains("7")); assertTrue(subTaskIds.contains("8")); blockedBy.setValue(" 7 , 8, "); subTaskIds = getSubTaskIds(taskData); assertEquals(2, subTaskIds.size()); assertTrue(subTaskIds.contains("7")); assertTrue(subTaskIds.contains("8")); blockedBy.setValue("7"); subTaskIds = getSubTaskIds(taskData); assertEquals(1, subTaskIds.size()); assertTrue(subTaskIds.contains("7")); blockedBy.setValue(""); subTaskIds = getSubTaskIds(taskData); assertEquals(0, subTaskIds.size()); blockedBy.setValue(" "); subTaskIds = getSubTaskIds(taskData); assertEquals(0, subTaskIds.size()); } private Collection<String> getSubTaskIds(TaskData taskData) { List<String> subTaskIds = new ArrayList<String>(); Collection<TaskRelation> relations = connector.getTaskRelations(taskData); for (TaskRelation taskRelation : relations) { subTaskIds.add(taskRelation.getTaskId()); } return subTaskIds; } public void testInitializeTaskData() throws Exception { TaskData taskData = new TaskData(taskDataHandler.getAttributeMapper(repository), TracCorePlugin.CONNECTOR_KIND, "", ""); TaskMapping mapping = new TaskMapping() { @Override public String getDescription() { return "description"; } @Override public String getSummary() { return "summary"; } }; taskDataHandler.initializeTaskData(repository, taskData, mapping, new NullProgressMonitor()); // initializeTaskData() should ignore the initialization data TaskMapper mapper = new TracTaskMapper(taskData, null); assertEquals(null, mapper.getResolution()); assertEquals("", mapper.getSummary()); assertEquals("", mapper.getDescription()); // check for default values assertEquals("Defect", mapper.getTaskKind()); assertEquals("major", mapper.getPriority()); // empty attributes should not exist assertNull(taskData.getRoot().getAttribute(TracAttribute.SEVERITY.getTracKey())); } public void testInitializeTaskDataNoMonitor() throws Exception { connector.getClientManager().repositoryRemoved(repository); TaskData taskData = new TaskData(new TaskAttributeMapper(repository), TracCorePlugin.CONNECTOR_KIND, "", ""); boolean initialized = taskDataHandler.initializeTaskData(repository, taskData, new TaskMapping(), null); assertTrue(initialized); } public void testOperations() throws Exception { boolean hasReassign = TracFixture.current().getVersion().compareTo("0.11") >= 0; TaskData taskData = taskDataHandler.getTaskData(repository, "1", new NullProgressMonitor()); List<TaskAttribute> operations = taskData.getAttributeMapper().getAttributesByType(taskData, TaskAttribute.TYPE_OPERATION); assertEquals("Unexpected operations: " + operations, (hasReassign ? 5 : 4), operations.size()); TaskOperation operation = taskData.getAttributeMapper().getTaskOperation(operations.get(0)); assertEquals(TaskAttribute.OPERATION, operation.getTaskAttribute().getId()); operation = taskData.getAttributeMapper().getTaskOperation(operations.get(1)); assertEquals("leave", operation.getOperationId()); assertNotNull(operation.getLabel()); operation = taskData.getAttributeMapper().getTaskOperation(operations.get(2)); assertEquals("resolve", operation.getOperationId()); assertNotNull(operation.getLabel()); String associatedId = operation.getTaskAttribute() .getMetaData() .getValue(TaskAttribute.META_ASSOCIATED_ATTRIBUTE_ID); assertNotNull(associatedId); if (hasReassign) { operation = taskData.getAttributeMapper().getTaskOperation(operations.get(3)); assertEquals("reassign", operation.getOperationId()); assertNotNull(operation.getLabel()); operation = taskData.getAttributeMapper().getTaskOperation(operations.get(4)); assertEquals("accept", operation.getOperationId()); assertNotNull(operation.getLabel()); } else { operation = taskData.getAttributeMapper().getTaskOperation(operations.get(3)); assertEquals("accept", operation.getOperationId()); assertNotNull(operation.getLabel()); } } public void testPostTaskDataUnsetResolution() throws Exception { TracTicket ticket = harness.createTicket("postTaskDataUnsetResolution"); TaskData taskData = taskDataHandler.getTaskData(repository, ticket.getId() + "", new NullProgressMonitor()); TaskAttribute attribute = taskData.getRoot().getMappedAttribute(TaskAttribute.RESOLUTION); attribute.setValue("fixed"); taskDataHandler.postTaskData(repository, taskData, null, new NullProgressMonitor()); // should not set resolution unless resolve operation is selected taskData = taskDataHandler.getTaskData(repository, ticket.getId() + "", new NullProgressMonitor()); attribute = taskData.getRoot().getMappedAttribute(TaskAttribute.RESOLUTION); assertEquals("", attribute.getValue()); } public void testPostTaskDataMidAirCollision() throws Exception { TracTicket ticket = harness.createTicket("midAirCollision"); if (ticket.getValue(Key.TOKEN) == null) { // repository does not have mid-air collision support System.err.println("Skipping TracTaskDataHandler.testPostTaskDataMidAirCollision() due to lack of mid-air collision support on " + repository.getRepositoryUrl()); return; } TaskData taskData = taskDataHandler.getTaskData(repository, ticket.getId() + "", new NullProgressMonitor()); TaskAttribute attribute = taskData.getRoot().getMappedAttribute(TaskAttribute.PRIORITY); attribute.setValue("blocker"); // change ticket in repository ticket.putBuiltinValue(Key.PRIORITY, "trivial"); client.updateTicket(ticket, "changing priority", null); harness.getFixture().waitToGuaranteeTaskUpdate(); // submit conflicting change try { taskDataHandler.postTaskData(repository, taskData, null, new NullProgressMonitor()); fail("Expected CoreException due to mid-air collision"); } catch (CoreException e) { assertEquals(RepositoryStatus.createCollisionError(repository.getRepositoryUrl(), TracCorePlugin.ID_PLUGIN) .getMessage(), e.getStatus().getMessage()); } } }