/*
* The MIT License
*
* Copyright 2011 Sony Ericsson Mobile Communications. All rights reserved.
* Copyright 2012 Sony Mobile Communications AB. 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.externalresource.dispatcher.data;
import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.DomNodeList;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.sonyericsson.hudson.plugins.metadata.cli.HttpCliRootAction;
import com.sonyericsson.hudson.plugins.metadata.model.MetadataNodeProperty;
import com.sonyericsson.hudson.plugins.metadata.model.values.AbstractMetadataValue;
import com.sonyericsson.hudson.plugins.metadata.model.values.DateMetadataValue;
import com.sonyericsson.hudson.plugins.metadata.model.values.MetadataValue;
import com.sonyericsson.hudson.plugins.metadata.model.values.StringMetadataValue;
import com.sonyericsson.hudson.plugins.metadata.model.values.TreeNodeMetadataValue;
import com.sonyericsson.hudson.plugins.metadata.model.values.TreeStructureUtil;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.SelectionCriteria;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.selection.AbstractResourceSelection;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.selection.StringResourceSelection;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.spec.TestUtils;
import hudson.EnvVars;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.Hudson;
import hudson.model.labels.LabelAtom;
import hudson.slaves.DumbSlave;
import hudson.tasks.Mailer;
import net.sf.json.JSONObject;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.TestBuilder;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import javax.servlet.ServletOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import static com.sonyericsson.hudson.plugins.metadata.Constants.REQUEST_ATTR_METADATA_CONTAINER;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* {@link HudsonTestCase}s for {@link ExternalResource}.
*
* @author Robert Sandell <robert.sandell@sonymobile.com>
*/
public class ExternalResourceJenkinsTest extends HudsonTestCase {
private DumbSlave slave;
private MetadataNodeProperty property;
private ExternalResource resource;
//CS IGNORE LineLength FOR NEXT 4 LINES. REASON: JavaDoc
/**
* Tests {@link com.sonyericsson.jenkins.plugins.externalresource.dispatcher.data.ExternalResource.ExternalResourceDescriptor#getValueDescriptors(org.kohsuke.stapler.StaplerRequest)}.
*/
public void testGetValueDescriptors() {
MetadataNodeProperty.MetadataNodePropertyDescriptor nodeDescriptor =
Hudson.getInstance().getDescriptorByType(MetadataNodeProperty.MetadataNodePropertyDescriptor.class);
StaplerRequest request = mock(StaplerRequest.class);
when(request.getAttribute(REQUEST_ATTR_METADATA_CONTAINER)).thenReturn(nodeDescriptor);
ExternalResource.ExternalResourceDescriptor descriptor =
Hudson.getInstance().getDescriptorByType(ExternalResource.ExternalResourceDescriptor.class);
List<AbstractMetadataValue.AbstractMetaDataValueDescriptor> descriptors =
descriptor.getValueDescriptors(request);
boolean foundString = false;
boolean foundDate = false;
boolean foundNode = false;
boolean foundExternalResource = false;
for (AbstractMetadataValue.AbstractMetaDataValueDescriptor d : descriptors) {
if (d instanceof StringMetadataValue.StringMetaDataValueDescriptor) {
foundString = true;
} else if (d instanceof DateMetadataValue.DateMetaDataValueDescriptor) {
foundDate = true;
} else if (d instanceof TreeNodeMetadataValue.TreeNodeMetaDataValueDescriptor) {
foundNode = true;
} else if (d instanceof ExternalResource.ExternalResourceDescriptor) {
foundExternalResource = true;
}
}
assertTrue(foundString);
assertTrue(foundDate);
assertTrue(foundNode);
assertFalse(foundExternalResource);
}
/**
* Tests that an {@link ExternalResource} is correctly replaced by the metadata update command. In essence checks
* that {@link ExternalResource#replacementOf(com.sonyericsson.hudson.plugins.metadata.model.values.MetadataValue)}
* is correctly implemented.
*
* @throws Exception if so.
*/
public void testReplacedWithEnabled() throws Exception {
setUpSlave();
resource.doEnable(false);
String stashedBy = "theCoolBuild/1";
String key = "oneKeyToRuleThemAll";
resource.setLocked(new StashInfo(StashInfo.StashType.INTERNAL, stashedBy, null, key));
ExternalResource nres = new ExternalResource("TestDevice", "description", "1", true,
new LinkedList<MetadataValue>());
String notAnyMore = "notAnyMore";
String value = "value";
TreeStructureUtil.addValue(nres, notAnyMore, "description", "is", "matching");
TreeStructureUtil.addValue(nres, value, "description", "some", "other");
TreeNodeMetadataValue tree = TreeStructureUtil.createPath(nres, "attached-devices", "test");
JSONObject json = tree.toJson();
String jsonString = json.toString();
StaplerRequest request = mock(StaplerRequest.class);
StaplerResponse response = mock(StaplerResponse.class);
when(request.getParameter("data")).thenReturn(jsonString);
when(request.getParameter("node")).thenReturn(slave.getNodeName());
when(request.getParameter("replace")).thenReturn("true");
ServletOutputStream out = mock(ServletOutputStream.class);
when(response.getOutputStream()).thenReturn(out);
HttpCliRootAction metadataCliAction = TestUtils.getHttpCliRootAction();
assertNotNull(metadataCliAction);
metadataCliAction.doUpdate(request, response);
ExternalResource replacedResource = (ExternalResource)TreeStructureUtil.getPath(property,
"attached-devices", "test", "TestDevice");
assertNotNull(replacedResource);
assertNotNull(replacedResource.getLocked());
assertEquals(stashedBy, replacedResource.getLocked().getStashedBy());
assertEquals(key, replacedResource.getLocked().getKey());
assertTrue(replacedResource.isEnabled());
assertEquals(notAnyMore, TreeStructureUtil.getPath(replacedResource, "is", "matching").getValue());
assertEquals(value, TreeStructureUtil.getPath(replacedResource, "some", "other").getValue());
assertEquals(2, replacedResource.getChildren().size());
JSONObject expectedJson = new JSONObject();
expectedJson.put("type", "ok");
expectedJson.put("errorCode", 0);
expectedJson.put("message", "OK");
verify(out).print(eq(expectedJson.toString()));
}
/**
* Tests that the environment variables for the locked resource is available to the build.
*
* @throws Exception if so.
*/
public void testLockedResourceEnvironment() throws Exception {
setUpSlave();
TreeStructureUtil.addValue(resource, "USB", "the type of connector", "connector", "type");
FreeStyleProject project = createFreeStyleProject();
project.setAssignedLabel(new LabelAtom("TEST"));
StringResourceSelection selection = new StringResourceSelection("is.matching", "yes");
List<AbstractResourceSelection> list = new LinkedList<AbstractResourceSelection>();
list.add(selection);
SelectionCriteria selectionCriteria = new SelectionCriteria(true, list);
project.addProperty(selectionCriteria);
final HashMap<Object, String> environment = new HashMap<Object, String>();
project.getBuildersList().add(new TestBuilder() {
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener)
throws InterruptedException, IOException {
EnvVars vars = build.getEnvironment(listener);
environment.putAll(vars);
return true;
}
});
FreeStyleBuild build = this.buildAndAssertSuccess(project);
assertNotNull(build);
assertEquals("USB", environment.get("MD_EXTERNAL_RESOURCES_LOCKED_CONNECTOR_TYPE"));
assertEquals("yes", environment.get("MD_EXTERNAL_RESOURCES_LOCKED_IS_MATCHING"));
assertEquals(resource.getId(), environment.get("MD_EXTERNAL_RESOURCES_LOCKED_ID"));
}
/**
* Setup method for some of the tests. It creates a slave with an attached external resource.
*
* @throws Exception if so.
*/
private void setUpSlave() throws Exception {
slave = this.createOnlineSlave(new LabelAtom("TEST"));
property = new MetadataNodeProperty((new LinkedList<MetadataValue>()));
slave.getNodeProperties().add(property);
resource = new ExternalResource("TestDevice", "description", "1", null,
new LinkedList<MetadataValue>());
TreeStructureUtil.addValue(resource, "yes", "description", "is", "matching");
TreeStructureUtil.addValue(property, resource, "attached-devices", "test");
Mailer.descriptor().setHudsonUrl(this.getURL().toString());
}
/**
* Tests that the Disable button is visible on the node page.
*
* @throws Exception if so.
*/
public void testCanEnableDisableOnNode() throws Exception {
slave = this.createOnlineSlave();
property = new MetadataNodeProperty();
ExternalResource value = new ExternalResource("device1", "d1");
TreeStructureUtil.addValue(value, "One", "The product version", "product", "version");
ExternalResourceTreeNode tn = new ExternalResourceTreeNode("attached-devices");
tn.addChild(value);
TreeStructureUtil.addValue(property, tn, false, "external");
slave.getNodeProperties().add(property);
WebClient web = createWebClient();
HtmlPage page = web.getPage(slave);
DomNode text = getTextElement(page.getDocumentElement(), "external.attached-devices.device1");
assertNotNull("Expected to find the device's name on the page", text);
HtmlElement el = (HtmlElement)text.getParentNode();
List<HtmlElement> buttons = el.getHtmlElementsByTagName("button");
assertEquals(1, buttons.size());
assertEquals("Disable", buttons.get(0).getFirstChild().asText());
}
/**
* Tests that the Disable button is not on the build metadata page.
*
* @throws Exception if so.
*/
public void testCantEnableDisableOnBuild() throws Exception {
this.configRoundtrip();
slave = this.createOnlineSlave();
property = new MetadataNodeProperty();
ExternalResource value = new ExternalResource("device1", "d1");
TreeStructureUtil.addValue(value, "One", "The product version", "product", "version");
ExternalResourceTreeNode tn = new ExternalResourceTreeNode("attached-devices");
tn.addChild(value);
TreeStructureUtil.addValue(property, tn, false, "external");
slave.getNodeProperties().add(property);
FreeStyleProject project = this.createFreeStyleProject();
StringResourceSelection selection = new StringResourceSelection("product.version", "One");
LinkedList<AbstractResourceSelection> list = new LinkedList<AbstractResourceSelection>();
list.add(selection);
project.addProperty(new SelectionCriteria(true, list));
FreeStyleBuild build = this.buildAndAssertSuccess(project);
WebClient web = createWebClient();
HtmlPage page = web.getPage(build, "metadata");
DomNode text = getTextElement(page.getDocumentElement(), "external-resources.locked");
assertNotNull("Expected to find the device's name on the page", text);
HtmlElement el = (HtmlElement)text.getParentNode();
List<HtmlElement> buttons = el.getHtmlElementsByTagName("button");
assertEquals("Expected no buttons on the resource row!", 0, buttons.size());
}
/**
* Finds the DomNode beneath parent that has no children
* and the provided text is returned from {@link DomNode#asText()}.
*
* @param parent the base parent.
* @param needle the text to find.
* @return the node that is the text or null if nothing was found.
*/
private DomNode getTextElement(DomNode parent, String needle) {
DomNodeList<DomNode> nodes = parent.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
DomNode node = nodes.get(i);
if (node.hasChildNodes()) {
DomNode text = getTextElement(node, needle);
if (text != null) {
return text;
}
} else {
if (needle.equals(node.asText())) {
return node;
}
}
}
return null;
}
}