/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts
*
* 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 hudson.model;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.html.DomNodeUtil;
import jenkins.model.Jenkins;
import org.jvnet.hudson.test.Issue;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlLabel;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlRadioButtonInput;
import hudson.XmlFile;
import hudson.matrix.AxisList;
import hudson.matrix.LabelAxis;
import hudson.matrix.MatrixProject;
import hudson.model.Queue.Task;
import hudson.model.Node.Mode;
import org.jvnet.hudson.test.Email;
import org.w3c.dom.Text;
import static hudson.model.Messages.Hudson_ViewName;
import hudson.security.ACL;
import hudson.security.AccessDeniedException2;
import hudson.slaves.DumbSlave;
import hudson.util.FormValidation;
import hudson.util.HudsonIsLoading;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import jenkins.model.ProjectNamingStrategy;
import jenkins.security.NotReallyRoleSensitiveCallable;
import static org.junit.Assert.*;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.JenkinsRule.WebClient;
import org.jvnet.hudson.test.MockAuthorizationStrategy;
import org.jvnet.hudson.test.MockFolder;
import org.jvnet.hudson.test.TestExtension;
import org.jvnet.hudson.test.recipes.LocalData;
import org.kohsuke.stapler.DataBoundConstructor;
/**
* @author Kohsuke Kawaguchi
*/
public class ViewTest {
@Rule public JenkinsRule j = new JenkinsRule();
@Issue("JENKINS-7100")
@Test public void xHudsonHeader() throws Exception {
assertNotNull(j.createWebClient().goTo("").getWebResponse().getResponseHeaderValue("X-Hudson"));
}
/**
* Creating two views with the same name.
*/
@Email("http://d.hatena.ne.jp/ssogabe/20090101/1230744150")
@Test public void conflictingName() throws Exception {
assertNull(j.jenkins.getView("foo"));
HtmlForm form = j.createWebClient().goTo("newView").getFormByName("createItem");
form.getInputByName("name").setValueAttribute("foo");
form.getRadioButtonsByName("mode").get(0).setChecked(true);
j.submit(form);
assertNotNull(j.jenkins.getView("foo"));
// do it again and verify an error
try {
j.submit(form);
fail("shouldn't be allowed to create two views of the same name.");
} catch (FailingHttpStatusCodeException e) {
assertEquals(400, e.getStatusCode());
}
}
@Test public void privateView() throws Exception {
j.createFreeStyleProject("project1");
User user = User.get("me", true); // create user
WebClient wc = j.createWebClient();
HtmlPage userPage = wc.goTo("user/me");
HtmlAnchor privateViewsLink = userPage.getAnchorByText("My Views");
assertNotNull("My Views link not available", privateViewsLink);
HtmlPage privateViewsPage = (HtmlPage) privateViewsLink.click();
Text viewLabel = (Text) privateViewsPage.getFirstByXPath("//div[@class='tabBar']//div[@class='tab active']/a/text()");
assertTrue("'All' view should be selected", viewLabel.getTextContent().contains(Hudson_ViewName()));
View listView = listView("listView");
HtmlPage newViewPage = wc.goTo("user/me/my-views/newView");
HtmlForm form = newViewPage.getFormByName("createItem");
form.getInputByName("name").setValueAttribute("proxy-view");
((HtmlRadioButtonInput) form.getInputByValue("hudson.model.ProxyView")).setChecked(true);
HtmlPage proxyViewConfigurePage = j.submit(form);
View proxyView = user.getProperty(MyViewsProperty.class).getView("proxy-view");
assertNotNull(proxyView);
form = proxyViewConfigurePage.getFormByName("viewConfig");
form.getSelectByName("proxiedViewName").setSelectedAttribute("listView", true);
j.submit(form);
assertTrue(proxyView instanceof ProxyView);
assertEquals(((ProxyView) proxyView).getProxiedViewName(), "listView");
assertEquals(((ProxyView) proxyView).getProxiedView(), listView);
}
@Test public void deleteView() throws Exception {
WebClient wc = j.createWebClient();
ListView v = listView("list");
HtmlPage delete = wc.getPage(v, "delete");
j.submit(delete.getFormByName("delete"));
assertNull(j.jenkins.getView("list"));
User user = User.get("user", true);
MyViewsProperty p = user.getProperty(MyViewsProperty.class);
v = new ListView("list", p);
p.addView(v);
delete = wc.getPage(v, "delete");
j.submit(delete.getFormByName("delete"));
assertNull(p.getView("list"));
}
@Issue("JENKINS-9367")
@Test public void persistence() throws Exception {
ListView view = listView("foo");
ListView v = (ListView) Jenkins.XSTREAM.fromXML(Jenkins.XSTREAM.toXML(view));
System.out.println(v.getProperties());
assertNotNull(v.getProperties());
}
@Issue("JENKINS-9367")
@Test public void allImagesCanBeLoaded() throws Exception {
User.get("user", true);
WebClient webClient = j.createWebClient();
webClient.getOptions().setJavaScriptEnabled(false);
j.assertAllImageLoadSuccessfully(webClient.goTo("asynchPeople"));
}
@Issue("JENKINS-16608")
@Test public void notAllowedName() throws Exception {
HtmlForm form = j.createWebClient().goTo("newView").getFormByName("createItem");
form.getInputByName("name").setValueAttribute("..");
form.getRadioButtonsByName("mode").get(0).setChecked(true);
try {
j.submit(form);
fail("\"..\" should not be allowed.");
} catch (FailingHttpStatusCodeException e) {
assertEquals(400, e.getStatusCode());
}
}
@Ignore("verified manually in Winstone but org.mortbay.JettyResponse.sendRedirect (6.1.26) seems to mangle the location")
@Issue("JENKINS-18373")
@Test public void unicodeName() throws Exception {
HtmlForm form = j.createWebClient().goTo("newView").getFormByName("createItem");
String name = "I ♥ NY";
form.getInputByName("name").setValueAttribute(name);
form.getRadioButtonsByName("mode").get(0).setChecked(true);
j.submit(form);
View view = j.jenkins.getView(name);
assertNotNull(view);
j.submit(j.createWebClient().getPage(view, "configure").getFormByName("viewConfig"));
}
@Issue("JENKINS-17302")
@Test public void doConfigDotXml() throws Exception {
ListView view = listView("v");
view.description = "one";
WebClient wc = j.createWebClient();
String xml = wc.goToXml("view/v/config.xml").getContent();
assertTrue(xml, xml.contains("<description>one</description>"));
xml = xml.replace("<description>one</description>", "<description>two</description>");
WebRequest req = new WebRequest(wc.createCrumbedUrl("view/v/config.xml"), HttpMethod.POST);
req.setRequestBody(xml);
req.setEncodingType(null);
wc.getPage(req);
assertEquals("two", view.getDescription());
xml = new XmlFile(Jenkins.XSTREAM, new File(j.jenkins.getRootDir(), "config.xml")).asString();
assertTrue(xml, xml.contains("<description>two</description>"));
}
@Test
public void testGetQueueItems() throws IOException, Exception{
ListView view1 = listView("view1");
view1.filterQueue = true;
ListView view2 = listView("view2");
view2.filterQueue = true;
FreeStyleProject inView1 = j.createFreeStyleProject("in-view1");
inView1.setAssignedLabel(j.jenkins.getLabelAtom("without-any-slave"));
view1.add(inView1);
MatrixProject inView2 = j.jenkins.createProject(MatrixProject.class, "in-view2");
inView2.setAssignedLabel(j.jenkins.getLabelAtom("without-any-slave"));
view2.add(inView2);
FreeStyleProject notInView = j.createFreeStyleProject("not-in-view");
notInView.setAssignedLabel(j.jenkins.getLabelAtom("without-any-slave"));
FreeStyleProject inBothViews = j.createFreeStyleProject("in-both-views");
inBothViews.setAssignedLabel(j.jenkins.getLabelAtom("without-any-slave"));
view1.add(inBothViews);
view2.add(inBothViews);
Queue.getInstance().schedule(notInView, 0);
Queue.getInstance().schedule(inView1, 0);
Queue.getInstance().schedule(inView2, 0);
Queue.getInstance().schedule(inBothViews, 0);
Thread.sleep(1000);
assertContainsItems(view1, inView1, inBothViews);
assertNotContainsItems(view1, notInView, inView2);
assertContainsItems(view2, inView2, inBothViews);
assertNotContainsItems(view2, notInView, inView1);
}
private void assertContainsItems(View view, Task... items) {
for (Task job: items) {
assertTrue(
"Queued items for " + view.getDisplayName() + " should contain " + job.getDisplayName(),
view.getQueueItems().contains(Queue.getInstance().getItem(job))
);
}
}
private void assertNotContainsItems(View view, Task... items) {
for (Task job: items) {
assertFalse(
"Queued items for " + view.getDisplayName() + " should not contain " + job.getDisplayName(),
view.getQueueItems().contains(Queue.getInstance().getItem(job))
);
}
}
@Test
public void testGetComputers() throws IOException, Exception{
ListView view1 = listView("view1");
ListView view2 = listView("view2");
ListView view3 = listView("view3");
view1.filterExecutors=true;
view2.filterExecutors=true;
view3.filterExecutors=true;
Slave slave0 = j.createOnlineSlave(j.jenkins.getLabel("label0"));
Slave slave1 = j.createOnlineSlave(j.jenkins.getLabel("label1"));
Slave slave2 = j.createOnlineSlave(j.jenkins.getLabel("label2"));
Slave slave3 = j.createOnlineSlave(j.jenkins.getLabel("label0"));
Slave slave4 = j.createOnlineSlave(j.jenkins.getLabel("label4"));
FreeStyleProject freestyleJob = j.createFreeStyleProject("free");
view1.add(freestyleJob);
freestyleJob.setAssignedLabel(j.jenkins.getLabel("label0||label2"));
MatrixProject matrixJob = j.jenkins.createProject(MatrixProject.class, "matrix");
view1.add(matrixJob);
matrixJob.setAxes(new AxisList(
new LabelAxis("label", Arrays.asList("label1"))
));
FreeStyleProject noLabelJob = j.createFreeStyleProject("not-assigned-label");
view3.add(noLabelJob);
noLabelJob.setAssignedLabel(null);
FreeStyleProject foreignJob = j.createFreeStyleProject("in-other-view");
view2.add(foreignJob);
foreignJob.setAssignedLabel(j.jenkins.getLabel("label0||label1"));
// contains all slaves having labels associated with freestyleJob or matrixJob
assertContainsNodes(view1, slave0, slave1, slave2, slave3);
assertNotContainsNodes(view1, slave4);
// contains all slaves having labels associated with foreignJob
assertContainsNodes(view2, slave0, slave1, slave3);
assertNotContainsNodes(view2, slave2, slave4);
// contains all slaves as it contains noLabelJob that can run everywhere
assertContainsNodes(view3, slave0, slave1, slave2, slave3, slave4);
}
@Test
@Issue("JENKINS-21474")
public void testGetComputersNPE() throws Exception {
ListView view = listView("aView");
view.filterExecutors = true;
DumbSlave dedicatedSlave = j.createOnlineSlave();
dedicatedSlave.setMode(Mode.EXCLUSIVE);
view.add(j.createFreeStyleProject());
FreeStyleProject tiedJob = j.createFreeStyleProject();
tiedJob.setAssignedNode(dedicatedSlave);
view.add(tiedJob);
DumbSlave notIncludedSlave = j.createOnlineSlave();
notIncludedSlave.setMode(Mode.EXCLUSIVE);
assertContainsNodes(view, j.jenkins, dedicatedSlave);
assertNotContainsNodes(view, notIncludedSlave);
}
private void assertContainsNodes(View view, Node... slaves) {
for (Node slave: slaves) {
assertTrue(
"Filtered executors for " + view.getDisplayName() + " should contain " + slave.getDisplayName(),
view.getComputers().contains(slave.toComputer())
);
}
}
private void assertNotContainsNodes(View view, Node... slaves) {
for (Node slave: slaves) {
assertFalse(
"Filtered executors for " + view.getDisplayName() + " should not contain " + slave.getDisplayName(),
view.getComputers().contains(slave.toComputer())
);
}
}
@Test
public void testGetItem() throws Exception{
ListView view = listView("foo");
FreeStyleProject job1 = j.createFreeStyleProject("free");
MatrixProject job2 = j.jenkins.createProject(MatrixProject.class, "matrix");
FreeStyleProject job3 = j.createFreeStyleProject("not-included");
view.jobNames.add(job2.getDisplayName());
view.jobNames.add(job1.getDisplayName());
assertEquals("View should return job " + job1.getDisplayName(),job1, view.getItem("free"));
assertNotNull("View should return null.", view.getItem("not-included"));
}
@Test
public void testRename() throws Exception {
ListView view = listView("foo");
view.rename("renamed");
assertEquals("View should have name foo.", "renamed", view.getDisplayName());
ListView view2 = listView("foo");
try{
view2.rename("renamed");
fail("Attempt to rename job with a name used by another view with the same owner should throw exception");
}
catch(Exception Exception){
}
assertEquals("View should not be renamed if required name has another view with the same owner", "foo", view2.getDisplayName());
}
@Test
public void testGetOwnerItemGroup() throws Exception {
ListView view = listView("foo");
assertEquals("View should have owner jenkins.",j.jenkins.getItemGroup(), view.getOwnerItemGroup());
}
@Test
public void testGetOwnerPrimaryView() throws Exception{
ListView view = listView("foo");
j.jenkins.setPrimaryView(view);
assertEquals("View should have primary view " + view.getDisplayName(),view, view.getOwnerPrimaryView());
}
@Test
public void testSave() throws Exception{
ListView view = listView("foo");
FreeStyleProject job = j.createFreeStyleProject("free");
view.jobNames.add("free");
view.save();
j.jenkins.doReload();
//wait until all configuration are reloaded
if(j.jenkins.servletContext.getAttribute("app") instanceof HudsonIsLoading){
Thread.sleep(500);
}
assertTrue("View does not contains job free after load.", j.jenkins.getView(view.getDisplayName()).contains(j.jenkins.getItem(job.getName())));
}
@Test
public void testGetProperties() throws Exception {
View view = listView("foo");
Thread.sleep(100000);
HtmlForm f = j.createWebClient().getPage(view, "configure").getFormByName("viewConfig");
((HtmlLabel) DomNodeUtil.selectSingleNode(f, ".//LABEL[text()='Test property']")).click();
j.submit(f);
assertNotNull("View should contains ViewPropertyImpl property.", view.getProperties().get(PropertyImpl.class));
}
private ListView listView(String name) throws IOException {
ListView view = new ListView(name, j.jenkins);
j.jenkins.addView(view);
return view;
}
public static class PropertyImpl extends ViewProperty {
public String name;
@DataBoundConstructor
public PropertyImpl(String name) {
this.name = name;
}
@TestExtension
public static class DescriptorImpl extends ViewPropertyDescriptor {
@Override
public String getDisplayName() {
return "Test property";
}
}
}
@Issue("JENKINS-20509")
@Test public void checkJobName() throws Exception {
j.createFreeStyleProject("topprj");
final MockFolder d1 = j.createFolder("d1");
d1.createProject(FreeStyleProject.class, "subprj");
final MockFolder d2 = j.createFolder("d2");
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().
grant(Jenkins.ADMINISTER).everywhere().to("admin").
grant(Jenkins.READ).everywhere().toEveryone().
grant(Job.READ).everywhere().toEveryone().
grant(Item.CREATE).onFolders(d1).to("dev")); // not on root or d2
ACL.impersonate(Jenkins.ANONYMOUS, new NotReallyRoleSensitiveCallable<Void,Exception>() {
@Override
public Void call() throws Exception {
try {
assertCheckJobName(j.jenkins, "whatever", FormValidation.Kind.OK);
fail("should not have been allowed");
} catch (AccessDeniedException2 x) {
// OK
}
return null;
}
});
ACL.impersonate(User.get("dev").impersonate(), new NotReallyRoleSensitiveCallable<Void,Exception>() {
@Override
public Void call() throws Exception {
try {
assertCheckJobName(j.jenkins, "whatever", FormValidation.Kind.OK);
fail("should not have been allowed");
} catch (AccessDeniedException2 x) {
// OK
}
try {
assertCheckJobName(d2, "whatever", FormValidation.Kind.OK);
fail("should not have been allowed");
} catch (AccessDeniedException2 x) {
// OK
}
assertCheckJobName(d1, "whatever", FormValidation.Kind.OK);
return null;
}
});
ACL.impersonate(User.get("admin").impersonate(), new NotReallyRoleSensitiveCallable<Void,Exception>() {
@Override
public Void call() throws Exception {
assertCheckJobName(j.jenkins, "whatever", FormValidation.Kind.OK);
assertCheckJobName(d1, "whatever", FormValidation.Kind.OK);
assertCheckJobName(d2, "whatever", FormValidation.Kind.OK);
assertCheckJobName(j.jenkins, "d1", FormValidation.Kind.ERROR);
assertCheckJobName(j.jenkins, "topprj", FormValidation.Kind.ERROR);
assertCheckJobName(d1, "subprj", FormValidation.Kind.ERROR);
assertCheckJobName(j.jenkins, "", FormValidation.Kind.OK);
assertCheckJobName(j.jenkins, "foo/bie", FormValidation.Kind.ERROR);
assertCheckJobName(d2, "New", FormValidation.Kind.OK);
j.jenkins.setProjectNamingStrategy(new ProjectNamingStrategy.PatternProjectNamingStrategy("[a-z]+", "", true));
assertCheckJobName(d2, "New", FormValidation.Kind.ERROR);
assertCheckJobName(d2, "new", FormValidation.Kind.OK);
return null;
}
});
JenkinsRule.WebClient wc = j.createWebClient().login("admin");
assertEquals("original ${rootURL}/checkJobName still supported", "<div/>", wc.goTo("checkJobName?value=stuff").getWebResponse().getContentAsString());
assertEquals("but now possible on a view in a folder", "<div/>", wc.goTo("job/d1/view/All/checkJobName?value=stuff").getWebResponse().getContentAsString());
}
private void assertCheckJobName(ViewGroup context, String name, FormValidation.Kind expected) {
assertEquals(expected, context.getPrimaryView().doCheckJobName(name).kind);
}
@Test
@Issue("JENKINS-36908")
@LocalData
public void testAllViewCreatedIfNoPrimary() throws Exception {
assertNotNull(j.getInstance().getView("All"));
}
@Test
@Issue("JENKINS-36908")
@LocalData
public void testAllViewNotCreatedIfPrimary() throws Exception {
assertNull(j.getInstance().getView("All"));
}
}