package com.metservice.kanban.web;
import static com.metservice.kanban.utils.DateUtils.parseConventionalNewZealandDate;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsCollectionContaining.hasItem;
import static org.hamcrest.core.IsCollectionContaining.hasItems;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.OutputStream;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap;
import org.springframework.web.servlet.view.RedirectView;
import com.metservice.kanban.KanbanPropertiesFile;
import com.metservice.kanban.KanbanService;
import com.metservice.kanban.charts.burnup.BurnUpChartGenerator;
import com.metservice.kanban.model.BoardIdentifier;
import com.metservice.kanban.model.DefaultKanbanProject;
import com.metservice.kanban.model.DefaultWorkItemTree;
import com.metservice.kanban.model.KanbanBacklog;
import com.metservice.kanban.model.KanbanBoard;
import com.metservice.kanban.model.KanbanJournalItem;
import com.metservice.kanban.model.KanbanProject;
import com.metservice.kanban.model.KanbanProjectConfiguration;
import com.metservice.kanban.model.TreeNode;
import com.metservice.kanban.model.WorkItem;
import com.metservice.kanban.model.WorkItemComment;
import com.metservice.kanban.model.WorkItemTree;
import com.metservice.kanban.model.WorkItemType;
import com.metservice.kanban.model.WorkItemTypeCollection;
public class KanbanBoardControllerTest {
Map<String, String> workStreams;
KanbanBoardController kanbanController;
KanbanProject project;
KanbanService kanbanService;
WorkItemTypeCollection workItemTypes;
@Before
public void setUp() {
workStreams = new HashMap<String, String>();
kanbanController = new KanbanBoardController();
project = mock(KanbanProject.class);
kanbanService = mock(KanbanService.class);
workItemTypes = mock(WorkItemTypeCollection.class);
when(project.getWorkItemTypes()).thenReturn(workItemTypes);
}
// TODO Rewrite KanbanController legacy test in this form
@Test
public void modelContainsKanban() throws IOException {
when(kanbanService.getKanbanProject("project")).thenReturn(project);
kanbanController.setKanbanService(kanbanService);
assertThat(kanbanController.populateProject("project"), is(project));
}
// @Test
// public void modelContainsRedirectViewThatReturnsToTheBoard() {
// KanbanBoardController kanbanController = new KanbanBoardController();
// kanbanController.setKanbanService(null);
//
// RedirectView redirectView = kanbanController.populateRedirectView("project", "board");
//
// assertThat(redirectView.getUrl(), is("/projects/project/board"));
// // Note: should also test that the RedirectView is context-relative, but this property is not exposed
// }
@Test
public void canDeleteWorkItems() throws IOException {
kanbanController.setKanbanService(null);
RedirectView actualView = kanbanController.deleteWorkItem(project, 3, "wall");
verify(project).deleteWorkItem(3);
verify(project).save();
assertThat(actualView.getUrl(), is("../wall"));
}
@SuppressWarnings("unchecked")
@Test
public void putsWorkItemAndItsChildrenIntoModel() throws IOException {
WorkItemType type = new WorkItemType("feature");
WorkItemTree tree = new DefaultWorkItemTree();
WorkItem feature = new WorkItem(1, type);
WorkItem story1 = new WorkItem(2, 1, type);
WorkItem story2 = new WorkItem(3, 1, type);
tree.addWorkItems(feature, story1, story2);
// Phases by board, persistence and service aren't used
KanbanProject project = new DefaultKanbanProject(null, null, tree, null, null);
kanbanController.setKanbanService(null);
ModelAndView modelAndView = kanbanController.editItem(project, "project name", "backlog",
feature.getId());
assertThat((WorkItem) modelAndView.getModelMap().get("workItem"), is(feature));
assertThat((List<WorkItem>) modelAndView.getModelMap().get("children"), hasItems(story1, story2));
}
@Test
public void canSaveEditedWorkItems() throws IOException, ParseException {
WorkItemType type = new WorkItemType("backlog", "completed");
DefaultWorkItemTree tree = new DefaultWorkItemTree();
WorkItem feature = new WorkItem(1, type);
tree.addWorkItem(feature);
when(project.getWorkItemTree()).thenReturn(tree);
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter("date-backlog", "10/02/2011");
request.addParameter("date-completed", "11/02/2011");
kanbanController.setKanbanService(null);
kanbanController.editItemAction(project, "wall", feature.getId(), feature.getParentId(), "new feature name",
"5", "7", "8", "some notes", "FFFFFF", "on", "a, b, c ", null, request);
assertThat(feature.getName(), is("new feature name"));
assertThat(feature.getAverageCaseEstimate(), is(5));
assertThat(feature.getWorstCaseEstimate(), is(7));
assertThat(feature.getImportance(), is(8));
assertThat(feature.getNotes(), is("some notes"));
assertThat(feature.isExcluded(), is(true));
assertThat(feature.getWorkStreamsAsString(), is("a,b,c"));
assertThat(feature.getWorkStreams().size(), is(3));
assertThat(feature.getDate("backlog"), is(parseConventionalNewZealandDate("10/02/2011")));
assertThat(feature.getDate("completed"), is(parseConventionalNewZealandDate("11/02/2011")));
}
@Test
public void canSaveEditedWorkItemsWithNoImportanceNoSizeNoWorkStreams() throws IOException, ParseException {
WorkItemType type = new WorkItemType("backlog");
DefaultWorkItemTree tree = new DefaultWorkItemTree();
WorkItem feature = new WorkItem(1, type);
tree.addWorkItem(feature);
when(project.getWorkItemTree()).thenReturn(tree);
MockHttpServletRequest request = new MockHttpServletRequest();
kanbanController.setKanbanService(null);
kanbanController.editItemAction(project, "wall", feature.getId(), feature.getParentId(), "new feature name",
"", "", "", "some notes", "FFFFFF", "on", "", null, request);
assertThat(feature.getAverageCaseEstimate(), is(0));
assertThat(feature.getImportance(), is(0));
assertThat(feature.getWorkStreams().size(), is(0));
}
@Test
public void canReparentWorkItems() throws IOException, ParseException {
WorkItemType featureType = new WorkItemType("feature-phase");
WorkItemType storyType = new WorkItemType("story-phase");
WorkItem feature1 = new WorkItem(1, featureType);
WorkItem story = new WorkItem(2, 1, storyType);
WorkItem feature2 = new WorkItem(3, featureType);
WorkItemTree tree = new DefaultWorkItemTree();
tree.addWorkItems(feature1, story, feature2);
assertThat(story.getParentId(), is(feature1.getId()));
when(project.getWorkItemTree()).thenReturn(tree);
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter("date-story-phase", "10/02/2011");
kanbanController.setKanbanService(null);
kanbanController.editItemAction(project, "wall", story.getId(), feature2.getId(), "new name", "4", "6", "1",
"new notes", "FFFFFF", "false", "", null, request);
WorkItem reparentedStory = tree.getWorkItem(story.getId());
assertThat(reparentedStory.getParentId(), is(feature2.getId()));
}
@Test
public void ifTheParentHasNotChangedSiblingsAreNotReordered() throws IOException, ParseException {
WorkItemType type = new WorkItemType("phase");
WorkItem middleFeature = new WorkItem(2, type);
WorkItemTree tree = new DefaultWorkItemTree();
tree.addWorkItem(new WorkItem(1, type));
tree.addWorkItem(middleFeature);
tree.addWorkItem(new WorkItem(2, type));
when(project.getWorkItemTree()).thenReturn(tree);
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter("date-story-phase", "10/02/2011");
kanbanController.setKanbanService(null);
kanbanController.editItemAction(project, "wall", middleFeature.getId(), middleFeature.getParentId(),
"new name", "3", "8", "11", "new notes", "FFFFFF", null, "", null, request);
List<WorkItem> workItems = tree.getChildren(middleFeature.getParentId());
assertThat(workItems.get(1), is(middleFeature));
}
@Test
public void presentsChartPage() {
kanbanController.setKanbanService(null);
when(workItemTypes.getByName("feature")).thenReturn(new WorkItemType("Dev", "Completed"));
ModelAndView modelAndView = kanbanController.chart(project, "cool-chart", "feature", "projectName", "",
"", "");
assertThat(modelAndView.getViewName(), is("/chart.jsp"));
assertThat((String) modelAndView.getModelMap().get("workItemTypeName"), is("feature"));
assertThat((String) modelAndView.getModelMap().get("imageName"), is("cool-chart.png"));
}
@Test
public void forNotExistingWorkItemTypePresentsChartForRootItem() {
WorkItemType workItem = new WorkItemType("Dev", "Completed");
workItem.setName("feature");
kanbanController.setKanbanService(null);
when(workItemTypes.getByName("feature")).thenReturn(workItem);
when(workItemTypes.getByName("feature12")).thenThrow(new IllegalArgumentException("unknown phase"));
@SuppressWarnings("unchecked")
TreeNode<WorkItemType> node = mock(TreeNode.class);
when(workItemTypes.getRoot()).thenReturn(node);
when(node.getValue()).thenReturn(workItem);
ModelAndView modelAndView = kanbanController.chart(project, "cool-chart", "feature12", "projectName", "",
"", "");
assertThat(modelAndView.getViewName(), is("/chart.jsp"));
assertThat((String) modelAndView.getModelMap().get("workItemTypeName"), is("feature"));
assertThat((String) modelAndView.getModelMap().get("imageName"), is("cool-chart.png"));
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Test
public void generatesBurnUpChart() throws IOException {
WorkItemType featureType = new WorkItemType("feature-phase");
WorkItemType storyType = new WorkItemType("story-phase");
WorkItem feature1 = new WorkItem(1, featureType);
WorkItem story = new WorkItem(2, 1, storyType);
WorkItem feature2 = new WorkItem(3, featureType);
WorkItemTree tree = new DefaultWorkItemTree();
tree.addWorkItems(feature1, story, feature2);
WorkItemTypeCollection workItems = new WorkItemTypeCollection(TreeNode.create(WorkItemType.class,
featureType));
DefaultKanbanProject project = new DefaultKanbanProject(workItems, null, tree, null, null);
BurnUpChartGenerator chartGenerator = mock(BurnUpChartGenerator.class);
OutputStream outputStream = mock(OutputStream.class);
kanbanController.setKanbanService(null);
kanbanController.burnUpChartPng(project, chartGenerator, null, null, null, outputStream);
ArgumentCaptor<List> workItemsCaptor = ArgumentCaptor.forClass(List.class);
ArgumentCaptor<OutputStream> outputStreamCaptor = ArgumentCaptor.forClass(OutputStream.class);
verify(chartGenerator).generateBurnUpChart(eq(project),
eq(featureType), workItemsCaptor.capture(), (LocalDate) eq(null), (LocalDate) eq(null), outputStreamCaptor.capture());
assertThat((Iterable<WorkItem>) workItemsCaptor.getValue(), hasItems(feature1, feature2));
assertThat((Iterable<WorkItem>) workItemsCaptor.getValue(), not(hasItem(story)));
assertThat(outputStreamCaptor.getValue(), is(outputStream));
}
@Test
public void addComment() throws IOException {
WorkItemType featureType = new WorkItemType("feature-phase");
WorkItem feature = new WorkItem(99, featureType);
when(project.getWorkItemById(99))
.thenReturn(feature);
ResponseEntity<String> responseEntity = kanbanController.addComment(project, 99, "user name",
"this is a cool new feature!");
assertThat(responseEntity, is(notNullValue()));
verify(project).save();
}
@Test
public void verifyProjectNames() {
assertNull(KanbanBoardController.isProjectNameValid("New Project Name"));
assertEquals("Project name contains incorrect characters at least one of (/\\|<>*?&:\")",
KanbanBoardController.isProjectNameValid("New Project Name with bad character/"));
assertEquals("Project name contains incorrect characters at least one of (/\\|<>*?&:\")",
KanbanBoardController.isProjectNameValid("\\"));
assertEquals("Project name contains incorrect characters at least one of (/\\|<>*?&:\")",
KanbanBoardController.isProjectNameValid("|"));
assertEquals("Project name contains incorrect characters at least one of (/\\|<>*?&:\")",
KanbanBoardController.isProjectNameValid("<"));
assertEquals("Project name contains incorrect characters at least one of (/\\|<>*?&:\")",
KanbanBoardController.isProjectNameValid(">"));
assertEquals("Project name contains incorrect characters at least one of (/\\|<>*?&:\")",
KanbanBoardController.isProjectNameValid("*"));
assertEquals("Project name contains incorrect characters at least one of (/\\|<>*?&:\")",
KanbanBoardController.isProjectNameValid("\""));
assertEquals("Project name is too long, maximum allowed length is 32 charactes, but is 76",
KanbanBoardController
.isProjectNameValid("New Project Name which is very long and we dont like such long project names"));
assertEquals("Project name should not be empty",
KanbanBoardController
.isProjectNameValid(" "));
}
@Test
public void testWallBoard() throws IOException {
KanbanBoard board = mock(KanbanBoard.class);
when(project.getBoard(eq(BoardIdentifier.WALL), anyString())).thenReturn(board);
ModelAndView wallBoardResult = kanbanController.wallBoard(project, "project", null, workStreams, null,
"", null);
assertEquals("/project.jsp", wallBoardResult.getViewName());
assertNull(wallBoardResult.getModel().get("highlight"));
assertEquals(board, wallBoardResult.getModel().get("board"));
assertEquals("wall", wallBoardResult.getModel().get("boardType"));
assertEquals("project", wallBoardResult.getModel().get("projectName"));
}
@Test
public void testBacklogBoard() throws IOException {
KanbanBacklog backlog = mock(KanbanBacklog.class);
WorkItemType type = new WorkItemType("phase");
WorkItemTypeCollection workItems = new WorkItemTypeCollection(TreeNode.create(WorkItemType.class, type));
when(project.getWorkItemTypes()).thenReturn(workItems);
when(project.getBacklog(anyString())).thenReturn(backlog);
ModelAndView backlogBoardResult = kanbanController.backlogBoard(project, "project", null, workStreams,
"");
assertEquals("/backlog.jsp", backlogBoardResult.getViewName());
assertEquals(backlog, backlogBoardResult.getModel().get("kanbanBacklog"));
assertNotNull(backlogBoardResult.getModel().get("type"));
assertEquals("phase", backlogBoardResult.getModel().get("phase"));
assertEquals("backlog", backlogBoardResult.getModel().get("boardType"));
assertEquals("project", backlogBoardResult.getModel().get("projectName"));
}
@Test
public void testJournalBoard() throws IOException {
List<KanbanJournalItem> journal = new ArrayList<KanbanJournalItem>();
journal.add(new KanbanJournalItem(1, "2012-01-10", "test", "user"));
journal.add(new KanbanJournalItem(2, "2012-01-11", "test 2", "user 2"));
when(project.getJournal()).thenReturn(journal);
ModelAndView journalBoardResult = kanbanController.journalBoard(project, "project", null, workStreams,
"");
assertEquals("/journal.jsp", journalBoardResult.getViewName());
assertEquals(journal, journalBoardResult.getModel().get("kanbanJournal"));
assertEquals("journal", journalBoardResult.getModel().get("boardType"));
assertEquals("project", journalBoardResult.getModel().get("projectName"));
}
@Test
public void testCompletedBoard() throws IOException {
KanbanBoard completed = mock(KanbanBoard.class);
WorkItemType type = new WorkItemType("phase");
WorkItemTypeCollection workItems = new WorkItemTypeCollection(TreeNode.create(WorkItemType.class, type));
when(project.getWorkItemTypes()).thenReturn(workItems);
when(project.getCompleted(anyString())).thenReturn(completed);
ModelAndView completedBoardResult = kanbanController
.completedBoard(project, "project", null, workStreams, "");
assertEquals("/completed.jsp", completedBoardResult.getViewName());
assertEquals(completed, completedBoardResult.getModel().get("board"));
assertNotNull(completedBoardResult.getModel().get("type"));
assertEquals("phase", completedBoardResult.getModel().get("phase"));
assertEquals("completed", completedBoardResult.getModel().get("boardType"));
assertEquals("project", completedBoardResult.getModel().get("projectName"));
}
@Test
public void testCreateBlockedComment() {
WorkItemComment blockedComment = KanbanBoardController.createBlockedComment(true, "comment", "user");
assertEquals("Blocked: comment", blockedComment.getCommentText());
WorkItemComment unblockedComment = KanbanBoardController.createBlockedComment(false, "comment", "user");
assertEquals("Unblocked: comment", unblockedComment.getCommentText());
}
@Test
public void setWorkStreamTestForRegularBoard() {
RedirectView setWorkStreamResult = kanbanController.setWorkStream(project, "project", "wall", "ws1", null,
null, workStreams);
assertEquals("/projects/project/wall", setWorkStreamResult.getUrl());
assertEquals("ws1", workStreams.get("project"));
}
@Test
public void setWorkStreamTestForCharts() {
RedirectView setWorkStreamResult = kanbanController.setWorkStream(project, "project", "chart", "ws1",
"cycle-chart",
"Story", workStreams);
assertEquals("/projects/project/chart?chartName=cycle-chart&workItemTypeName=Story",
setWorkStreamResult.getUrl());
assertEquals("ws1", workStreams.get("project"));
}
@Test
public void editProjectActionForRenamingProjectValidProjectName() throws IOException {
kanbanController.setKanbanService(kanbanService);
when(kanbanService.getKanbanProject("project")).thenReturn(project);
RedirectView editProjectActionResult = kanbanController.editProjectAction(project, "project", "new project",
"content");
verify(kanbanService).renameProject("project", "new project");
verify(kanbanService).editProject("new project", "content");
verifyNoMoreInteractions(kanbanService);
assertEquals("/projects/new project/wall", editProjectActionResult.getUrl());
}
@Test
public void editProjectActionForRenamingProjectInvalidProjectNameShouldRedirectError() throws IOException {
kanbanController.setKanbanService(kanbanService);
when(kanbanService.getKanbanProject("project")).thenReturn(project);
RedirectView editProjectActionResult = kanbanController.editProjectAction(project, "project",
"invalid/project",
"content");
verifyNoMoreInteractions(kanbanService);
assertEquals(
"edit-project?createNewProject=false&error=Project+name+contains+incorrect+characters+at+least+one+of+%28%2F%5C%7C%3C%3E*%3F%26%3A%22%29",
editProjectActionResult.getUrl());
}
@Test
public void editProjectNameNotChanged() throws IOException {
kanbanController.setKanbanService(kanbanService);
when(kanbanService.getKanbanProject("project")).thenReturn(project);
RedirectView editProjectActionResult = kanbanController.editProjectAction(project, "project", "project",
"content");
verify(kanbanService).editProject("project", "content");
verifyNoMoreInteractions(kanbanService);
assertEquals("/projects/project/wall", editProjectActionResult.getUrl());
}
@Test
public void redirectToWall() {
RedirectView result = kanbanController.redirectToWall("a project");
assertEquals("/projects/a project/wall", result.getUrl());
}
@Test
public void addJournalEntry() {
KanbanJournalItem result = kanbanController.addJournalEntry(project, "project name", "user", "2012-01-16",
"a text");
assertEquals("user", result.getUserName());
assertEquals(LocalDateTime.parse("2012-01-16"), result.getDate());
assertEquals("a text", result.getText());
verify(project, times(1)).addJournalItem(result);
}
@Test
public void removeJournalEntry() {
kanbanController.removeJournalEntry(project, 10);
verify(project, times(1)).deleteJournalItem(10);
}
@Test
public void editColumnSetWipValidInteger() throws IOException {
WorkItemType type = new WorkItemType("Backlog", "Dev", "Done");
type.setName("feature");
WorkItemTypeCollection itemTypes = mock(WorkItemTypeCollection.class);
KanbanProjectConfiguration config = mock(KanbanProjectConfiguration.class);
KanbanPropertiesFile propertiesFile = mock(KanbanPropertiesFile.class);
when(project.getWorkItemTypes()).thenReturn(itemTypes);
when(itemTypes.getByName("feature")).thenReturn(type);
when(kanbanService.getProjectConfiguration("a project")).thenReturn(config);
when(config.getKanbanPropertiesFile()).thenReturn(propertiesFile);
kanbanController.setKanbanService(kanbanService);
JsonStatus result = kanbanController.editColumn(project, "a project", "feature", "Dev", "Dev", 10);
assertEquals("ok", result.status);
verify(kanbanService).setColumnWipLimit("a project", type, "Dev", 10);
}
@Test
public void editColumnSetWipNull() throws IOException {
WorkItemType type = new WorkItemType("Backlog", "Dev", "Done");
type.setName("feature");
WorkItemTypeCollection itemTypes = mock(WorkItemTypeCollection.class);
KanbanProjectConfiguration config = mock(KanbanProjectConfiguration.class);
KanbanPropertiesFile propertiesFile = mock(KanbanPropertiesFile.class);
when(project.getWorkItemTypes()).thenReturn(itemTypes);
when(itemTypes.getByName("feature")).thenReturn(type);
when(kanbanService.getProjectConfiguration("a project")).thenReturn(config);
when(config.getKanbanPropertiesFile()).thenReturn(propertiesFile);
kanbanController.setKanbanService(kanbanService);
JsonStatus result = kanbanController.editColumn(project, "a project", "feature", "Dev", "Dev", null);
assertEquals("ok", result.status);
verify(kanbanService).setColumnWipLimit("a project", type, "Dev", null);
}
@Test
public void editColumnSetWipLessThan0() throws IOException {
WorkItemType type = new WorkItemType("Backlog", "Dev", "Done");
type.setName("feature");
WorkItemTypeCollection itemTypes = mock(WorkItemTypeCollection.class);
KanbanProjectConfiguration config = mock(KanbanProjectConfiguration.class);
KanbanPropertiesFile propertiesFile = mock(KanbanPropertiesFile.class);
when(project.getWorkItemTypes()).thenReturn(itemTypes);
when(itemTypes.getByName("feature")).thenReturn(type);
when(kanbanService.getProjectConfiguration("a project")).thenReturn(config);
when(config.getKanbanPropertiesFile()).thenReturn(propertiesFile);
kanbanController.setKanbanService(kanbanService);
JsonStatus result = kanbanController.editColumn(project, "a project", "feature", "Dev", "Dev", -1);
assertEquals("error", result.status);
assertEquals("WIP Limit should be empty or positive value", result.message);
verifyZeroInteractions(kanbanService);
}
@Test
public void editColumnColumnNameEmpty() throws IOException {
WorkItemType type = new WorkItemType("Backlog", "Dev", "Done");
type.setName("feature");
WorkItemTypeCollection itemTypes = mock(WorkItemTypeCollection.class);
KanbanProjectConfiguration config = mock(KanbanProjectConfiguration.class);
KanbanPropertiesFile propertiesFile = mock(KanbanPropertiesFile.class);
when(project.getWorkItemTypes()).thenReturn(itemTypes);
when(itemTypes.getByName("feature")).thenReturn(type);
when(kanbanService.getProjectConfiguration("a project")).thenReturn(config);
when(config.getKanbanPropertiesFile()).thenReturn(propertiesFile);
kanbanController.setKanbanService(kanbanService);
JsonStatus result = kanbanController.editColumn(project, "a project", "feature", "Dev", "", null);
assertEquals("error", result.status);
assertEquals("New column name cannot be empty", result.message);
verifyZeroInteractions(kanbanService);
}
@Test
public void advanceItemAction() throws IOException {
RedirectAttributes redirectAttr = new RedirectAttributesModelMap();
WorkItemType type = new WorkItemType("Backlog", "Dev", "Done");
WorkItem wi = new WorkItem(1, type);
wi.advance(LocalDate.now());
wi.advance(LocalDate.now());
when(project.getWorkItemById(1)).thenReturn(wi);
RedirectView result = kanbanController.advanceItemAction(project, "wall", "1", "Dev", 0, redirectAttr);
assertEquals("../wall?scrollTop=0&highlight=1", result.getUrl());
assertNull(redirectAttr.getFlashAttributes().get("error"));
verify(project).advance(eq(1), any(LocalDate.class));
verify(project).save();
}
@Test
public void advanceItemActionWrongPhase() throws IOException {
RedirectAttributes redirectAttr = new RedirectAttributesModelMap();
WorkItemType type = new WorkItemType("Backlog", "Dev", "Done");
WorkItem wi = new WorkItem(1, type);
wi.advance(LocalDate.now());
when(project.getWorkItemById(1)).thenReturn(wi);
RedirectView result = kanbanController.advanceItemAction(project, "wall", "1", "Dev", 0, redirectAttr);
assertEquals("../wall", result.getUrl());
assertEquals(
"Your board view was out of date, your request has been canceled and the board has been updated. Please review the board now and apply your changes.",
redirectAttr.getFlashAttributes().get("error"));
}
@Test
public void stopItemAction() throws IOException {
WorkItemType type = new WorkItemType("Backlog", "Dev", "Done");
final WorkItem wi = new WorkItem(1, type);
wi.advance(LocalDate.now());
when(project.getWorkItemById(1)).thenReturn(wi);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
wi.setBlocked(true);
return null;
}
}).when(project).stop(1);
RedirectView result = kanbanController.stopItemAction(project, "wall", "1", "comment", "user");
assertEquals("../wall", result.getUrl());
verify(project).save();
List<WorkItemComment> comments = wi.getComments();
assertEquals(1, comments.size());
assertEquals("Blocked: comment", comments.get(0).getCommentText());
assertEquals("user", comments.get(0).getAddedBy());
}
@Test
public void stopItemActionNullComment() throws IOException {
WorkItemType type = new WorkItemType("Backlog", "Dev", "Done");
final WorkItem wi = new WorkItem(1, type);
wi.advance(LocalDate.now());
when(project.getWorkItemById(1)).thenReturn(wi);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
wi.setBlocked(true);
return null;
}
}).when(project).stop(1);
RedirectView result = kanbanController.stopItemAction(project, "wall", "1", null, "user");
assertEquals("../wall", result.getUrl());
verify(project).save();
List<WorkItemComment> comments = wi.getComments();
assertEquals(1, comments.size());
assertEquals("Blocked: ", comments.get(0).getCommentText());
assertEquals("user", comments.get(0).getAddedBy());
}
@Test
public void addItemTopLevel() throws IOException {
WorkItemType rootType = new WorkItemType("Backlog", "Dev", "Done");
rootType.setName("feature");
TreeNode<WorkItemType> rootNode = TreeNode.create(WorkItemType.class, rootType);
when(workItemTypes.getRoot()).thenReturn(rootNode);
ModelAndView result = kanbanController.addItem(project, "abcd", 0);
assertEquals("/add.jsp", result.getViewName());
assertEquals("Add feature", result.getModel().get("legend"));
assertEquals(0, result.getModel().get("parentId"));
assertEquals(rootType, result.getModel().get("type"));
assertEquals(true, result.getModel().get("topLevel"));
}
@Test
public void addItemNotTopLevel() throws IOException {
WorkItemType rootType = new WorkItemType("Backlog", "Dev", "Done");
rootType.setName("feature");
WorkItemType type = new WorkItemType("Backlog", "Dev", "Done");
type.setName("story");
WorkItem parentItem = new WorkItem(1, rootType);
parentItem.setName("feature name");
TreeNode<WorkItemType> rootNode = TreeNode.create(WorkItemType.class, rootType);
when(workItemTypes.getRoot()).thenReturn(rootNode);
when(project.getWorkItemById(1)).thenReturn(parentItem);
when(project.getChildType(rootType)).thenReturn(type);
ModelAndView result = kanbanController.addItem(project, "abcd", 1);
assertEquals("/add.jsp", result.getViewName());
assertEquals("Add a story to feature name", result.getModel().get("legend"));
assertEquals(1, result.getModel().get("parentId"));
assertEquals(type, result.getModel().get("type"));
// not sure if this is what we want, probably should be false
assertEquals(true, result.getModel().get("topLevel"));
}
}