/*
* The MIT License
*
* Copyright 2014 Sony Mobile Communications Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.sonyericsson.jenkins.plugins.bfa;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlTable;
import com.gargoylesoftware.htmlunit.html.HtmlTableRow;
import com.sonyericsson.jenkins.plugins.bfa.db.KnowledgeBase;
import com.sonyericsson.jenkins.plugins.bfa.db.MongoDBKnowledgeBase;
import com.sonyericsson.jenkins.plugins.bfa.model.FailureCause;
import com.sonyericsson.jenkins.plugins.bfa.model.FailureCauseModification;
import com.sonyericsson.jenkins.plugins.bfa.model.ScannerJobProperty;
import com.sonyericsson.jenkins.plugins.bfa.model.indication.BuildLogIndication;
import hudson.Util;
import hudson.model.FreeStyleProject;
import hudson.util.Secret;
import org.apache.commons.lang.StringUtils;
import org.jvnet.hudson.test.HudsonTestCase;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.powermock.reflect.Whitebox;
import javax.servlet.http.HttpSession;
import java.text.DateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Hudson tests for {@link CauseManagement}.
*
* @author Robert Sandell <robert.sandell@sonyericsson.com>
*/
public class CauseManagementHudsonTest extends HudsonTestCase {
private static final int NAME_CELL = 0;
private static final int CATEGORY_CELL = 1;
private static final int DESCRIPTION_CELL = 2;
private static final int COMMENT_CELL = 3;
private static final int MODIFIED_CELL = 4;
private static final int LAST_SEEN_CELL = 5;
/**
* Tests {@link com.sonyericsson.jenkins.plugins.bfa.CauseManagement#isUnderTest()}.
*/
public void testIsUnderTest() {
assertTrue(CauseManagement.getInstance().isUnderTest());
}
/**
* Verifies that the table on the {@link CauseManagement} page displays all causes with description and that
* one of them can be navigated to and a valid edit page for that cause is shown.
*
* @throws Exception if so.
*/
public void testTableViewNavigation() throws Exception {
KnowledgeBase kb = PluginImpl.getInstance().getKnowledgeBase();
//Overriding isStatisticsEnabled in order to display all fields on the management page:
KnowledgeBase mockKb = spy(kb);
when(mockKb.isStatisticsEnabled()).thenReturn(true);
Whitebox.setInternalState(PluginImpl.getInstance(), "knowledgeBase", mockKb);
List<String> myCategories = new LinkedList<String>();
myCategories.add("myCtegory");
//CS IGNORE MagicNumber FOR NEXT 5 LINES. REASON: TestData.
Date endOfWorld = new Date(1356106375000L);
Date birthday = new Date(678381175000L);
Date millenniumBug = new Date(946681200000L);
Date pluginReleased = new Date(1351724400000L);
FailureCause cause = new FailureCause(null, "SomeName", "A Description", "Some comment",
endOfWorld, myCategories, null,
Collections.singletonList(new FailureCauseModification("user", birthday)));
cause.addIndication(new BuildLogIndication("."));
kb.addCause(cause);
cause = new FailureCause(null, "SomeOtherName", "A Description", "Another comment",
millenniumBug, myCategories, null,
Collections.singletonList(new FailureCauseModification("user", pluginReleased)));
cause.addIndication(new BuildLogIndication("."));
kb.addCause(cause);
WebClient web = createWebClient();
HtmlPage page = web.goTo(CauseManagement.URL_NAME);
HtmlTable table = (HtmlTable)page.getElementById("failureCausesTable");
Collection<FailureCause> expectedCauses = kb.getShallowCauses();
int rowCount = table.getRowCount();
assertEquals(expectedCauses.size() + 1, rowCount);
Iterator<FailureCause> causeIterator = expectedCauses.iterator();
FailureCause firstCause = null;
for (int i = 1; i < rowCount; i++) {
assertTrue(causeIterator.hasNext());
FailureCause c = causeIterator.next();
HtmlTableRow row = table.getRow(i);
String name = row.getCell(NAME_CELL).getTextContent();
String categories = row.getCell(CATEGORY_CELL).getTextContent();
String description = row.getCell(DESCRIPTION_CELL).getTextContent();
String comment = row.getCell(COMMENT_CELL).getTextContent();
String modified = row.getCell(MODIFIED_CELL).getTextContent();
String lastSeen = row.getCell(LAST_SEEN_CELL).getTextContent();
assertEquals(c.getName(), name);
assertEquals(c.getCategoriesAsString(), categories);
assertEquals(c.getDescription(), description);
assertEquals(c.getComment(), comment);
assertEquals("Modified date should be visible", DateFormat.getDateTimeInstance(
DateFormat.SHORT, DateFormat.SHORT).format(c.getLatestModification().getTime())
+ " by user", modified);
assertEquals("Last seen date should be visible",
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)
.format(c.getLastOccurred()), lastSeen);
if (i == 1) {
firstCause = c;
}
}
//The table looks ok, now lets see if we can navigate correctly.
assertNotNull(firstCause);
HtmlAnchor firstCauseLink = (HtmlAnchor)table.getCellAt(1, 0).getFirstChild();
HtmlPage editPage = firstCauseLink.click();
verifyCorrectCauseEditPage(firstCause, editPage);
}
/**
* Tests that the "new cause" link on the page navigates to a correct page.
* @throws Exception if so.
*/
public void testNewNavigation() throws Exception {
WebClient web = createWebClient();
HtmlPage page = web.goTo(CauseManagement.URL_NAME);
HtmlAnchor newLink = page.getAnchorByHref(CauseManagement.NEW_CAUSE_DYNAMIC_ID);
HtmlPage editPage = newLink.click();
verifyCorrectCauseEditPage(new FailureCause(
CauseManagement.NEW_CAUSE_NAME,
CauseManagement.NEW_CAUSE_DESCRIPTION, ""),
editPage);
}
/**
* Makes a modification to a {@link FailureCause} and verifies that the modification date was updated.
* @throws Exception if something goes wrong
*/
public void testMakeModificationUpdatesDate() throws Exception {
List<FailureCauseModification> modifications = new LinkedList<FailureCauseModification>();
modifications.add(new FailureCauseModification("unknown", new Date(1)));
FailureCause cause = new FailureCause(null, "SomeName", "A Description", "Some comment",
null, "", null, modifications);
cause.addIndication(new BuildLogIndication("."));
PluginImpl.getInstance().getKnowledgeBase().addCause(cause);
WebClient web = createWebClient();
HtmlPage page = web.goTo(CauseManagement.URL_NAME);
HtmlTable table = (HtmlTable)page.getElementById("failureCausesTable");
HtmlTableRow row = table.getRow(1);
final String firstModification = row.getCell(MODIFIED_CELL).getTextContent();
HtmlAnchor firstCauseLink = (HtmlAnchor)table.getCellAt(1, 0).getFirstChild();
HtmlPage editPage = firstCauseLink.click();
editPage.getElementByName("_.comment").setTextContent("new comment");
HtmlForm form = editPage.getFormByName("causeForm");
page = submit(form);
table = (HtmlTable)page.getElementById("failureCausesTable");
row = table.getRow(1);
final String secondModification = row.getCell(MODIFIED_CELL).getTextContent();
assertThat(secondModification, not(equalTo(firstModification)));
assertThat(secondModification, not(equalTo(StringUtils.EMPTY)));
}
/**
* Makes a modification to a {@link FailureCause} and verifies that the modification list was updated.
* @throws Exception if something goes wrong
*/
public void testMakeModificationUpdatesModificationList() throws Exception {
List<FailureCauseModification> modifications = new LinkedList<FailureCauseModification>();
modifications.add(new FailureCauseModification("unknown", new Date(1)));
FailureCause cause = new FailureCause(null, "SomeName", "A Description", "Some comment",
null, "", null, modifications);
cause.addIndication(new BuildLogIndication("."));
PluginImpl.getInstance().getKnowledgeBase().addCause(cause);
WebClient web = createWebClient();
HtmlPage page = web.goTo(CauseManagement.URL_NAME);
HtmlTable table = (HtmlTable)page.getElementById("failureCausesTable");
HtmlAnchor firstCauseLink = (HtmlAnchor)table.getCellAt(1, 0).getFirstChild();
HtmlPage editPage = firstCauseLink.click();
HtmlElement modList = editPage.getElementById("modifications");
int firstNbrOfModifications = modList.getChildNodes().size();
editPage.getElementByName("_.comment").setTextContent("new comment");
HtmlForm form = editPage.getFormByName("causeForm");
submit(form);
editPage = firstCauseLink.click();
modList = editPage.getElementById("modifications");
int secondNbrOfModifications = modList.getChildNodes().size();
assertEquals(firstNbrOfModifications + 1, secondNbrOfModifications);
assertStringContains("Latest modification date should be visible",
modList.getFirstChild().asText(), DateFormat.getDateTimeInstance(
DateFormat.SHORT, DateFormat.SHORT).format(cause.getLatestModification().getTime()));
}
//CS IGNORE MagicNumber FOR NEXT 100 LINES. REASON: TestData.
/**
* Tests that an error message is shown when there is no reachable Mongo database.
* @throws Exception if so.
*/
public void testNoMongoDB() throws Exception {
KnowledgeBase kb = new MongoDBKnowledgeBase("someurl", 1234, "somedb", "user", Secret.fromString("pass"),
false, false);
Whitebox.setInternalState(PluginImpl.getInstance(), kb);
WebClient web = createWebClient();
HtmlPage page = web.goTo(CauseManagement.URL_NAME);
HtmlElement element = page.getElementById("errorMessage");
assertNotNull(element);
}
/**
* Verifies that the page is displaying the expected cause correctly.
*
* @param expectedCause the cause that is expected to be displayed.
* @param editPage the page to verify.
* @see #testNewNavigation()
* @see #testTableViewNavigation()
*/
private void verifyCorrectCauseEditPage(FailureCause expectedCause, HtmlPage editPage) {
HtmlForm form = editPage.getFormByName("causeForm");
String actualId = form.getInputByName("_.id").getValueAttribute();
if (Util.fixEmpty(expectedCause.getId()) == null) {
assertNull(Util.fixEmpty(actualId));
} else {
assertEquals(expectedCause.getId(), actualId);
}
assertEquals(expectedCause.getName(), form.getInputByName("_.name").getValueAttribute());
HtmlElement descrArea = form.getOneHtmlElementByAttribute("textarea", "name", "_.description");
String description = descrArea.getTextContent();
assertEquals(expectedCause.getDescription(), description);
HtmlElement commentArea = form.getOneHtmlElementByAttribute("textarea", "name", "_.comment");
String comment = commentArea.getTextContent();
assertEquals(expectedCause.getComment(), comment);
if (!expectedCause.getIndications().isEmpty()) {
HtmlElement indicationsDiv = form.getOneHtmlElementByAttribute("div", "name", "indications");
HtmlInput patternInput = indicationsDiv.getOneHtmlElementByAttribute("input", "name", "pattern");
assertEquals(expectedCause.getIndications().get(0).getPattern().pattern(), patternInput.getValueAttribute());
}
}
/**
* Tests {@link CauseManagement#doRemoveConfirm(String, org.kohsuke.stapler.StaplerRequest,
* org.kohsuke.stapler.StaplerResponse)}.
* Assumes that the default {@link com.sonyericsson.jenkins.plugins.bfa.db.LocalFileKnowledgeBase} is used.
*
* @throws Exception if so.
*/
public void testDoRemoveConfirm() throws Exception {
FailureCause cause = new FailureCause("SomeName", "A Description");
cause.addIndication(new BuildLogIndication("."));
FailureCause cause1 = PluginImpl.getInstance().getKnowledgeBase().addCause(cause);
cause = new FailureCause("SomeOtherName", "A Description");
cause.addIndication(new BuildLogIndication("."));
FailureCause cause2 = PluginImpl.getInstance().getKnowledgeBase().addCause(cause);
KnowledgeBase kb = spy(PluginImpl.getInstance().getKnowledgeBase());
Whitebox.setInternalState(PluginImpl.getInstance(), KnowledgeBase.class, kb);
StaplerRequest request = mock(StaplerRequest.class);
HttpSession session = mock(HttpSession.class);
when(request.getSession(anyBoolean())).thenReturn(session);
StaplerResponse response = mock(StaplerResponse.class);
CauseManagement.getInstance().doRemoveConfirm(cause1.getId(), request, response);
verify(kb).removeCause(eq(cause1.getId()));
verify(session).setAttribute(eq(CauseManagement.SESSION_REMOVED_FAILURE_CAUSE), same(cause1));
//Check that it is gone.
assertNull(kb.getCause(cause1.getId()));
//Check that the other one is still there
assertSame(cause2, kb.getCause(cause2.getId()));
}
/**
* Test Cause Management project action hiding.
* @throws Exception if so.
*/
public void testProjectCauseManagementActionIsHiddenWhenScanningDisabled() throws Exception {
FreeStyleProject project = createFreeStyleProject();
PluginImpl.getInstance().setGlobalEnabled(true);
doScan(project, true);
assertNotNull(project.getAction(TransientCauseManagement.class));
doScan(project, false);
assertNull(project.getAction(TransientCauseManagement.class));
PluginImpl.getInstance().setGlobalEnabled(false);
doScan(project, true);
assertNull(project.getAction(TransientCauseManagement.class));
doScan(project, false);
assertNull(project.getAction(TransientCauseManagement.class));
}
/**
* Turn project scanning on and off.
*
* @param project A project.
* @param scan Scan or not.
* @throws Exception if so.
*/
private void doScan(FreeStyleProject project, boolean scan) throws Exception {
project.removeProperty(ScannerJobProperty.class);
project.addProperty(new ScannerJobProperty(!scan));
}
}