Improved the /cli help screen.
(issue 20023)
diff --git a/core/src/main/java/hudson/cli/CreateViewCommand.java b/core/src/main/java/hudson/cli/CreateViewCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..73fd374b854195f0d96de49f582f61c364509b32
--- /dev/null
+++ b/core/src/main/java/hudson/cli/CreateViewCommand.java
@@ -0,0 +1,76 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2013 Red Hat, Inc.
+ *
+ * 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.cli;
+
+import org.kohsuke.args4j.Argument;
+
+import jenkins.model.Jenkins;
+import hudson.Extension;
+import hudson.model.Failure;
+import hudson.model.View;
+
+/**
+ * @author ogondza
+ * @since 1.538
+ */
+@Extension
+public class CreateViewCommand extends CLICommand {
+
+ @Argument(usage="Name of the view to use instead of the one in XML")
+ public String viewName = null;
+
+ @Override
+ public String getShortDescription() {
+
+ return Messages.CreateViewCommand_ShortDescription();
+ }
+
+ @Override
+ protected int run() throws Exception {
+
+ final Jenkins jenkins = Jenkins.getInstance();
+ jenkins.checkPermission(View.CREATE);
+
+ View newView;
+ try {
+
+ newView = View.createViewFromXML(viewName, stdin);
+ } catch (Failure ex) {
+
+ stderr.format("Invalid view name: %s\n", ex.getMessage());
+ return -1;
+ }
+
+ final String newName = newView.getViewName();
+ if (jenkins.getView(newName) != null) {
+
+ stderr.format("View '%s' already exists\n", newName);
+ return -1;
+ }
+
+ jenkins.addView(newView);
+
+ return 0;
+ }
+}
diff --git a/core/src/main/java/hudson/cli/DeleteViewCommand.java b/core/src/main/java/hudson/cli/DeleteViewCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..73414701139bab47cc931edb63df4b12e4e7b4c7
--- /dev/null
+++ b/core/src/main/java/hudson/cli/DeleteViewCommand.java
@@ -0,0 +1,67 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2013 Red Hat, Inc.
+ *
+ * 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.cli;
+
+import hudson.Extension;
+import hudson.model.ViewGroup;
+import hudson.model.View;
+
+import org.kohsuke.args4j.Argument;
+
+/**
+ * @author ogondza
+ * @since 1.538
+ */
+@Extension
+public class DeleteViewCommand extends CLICommand {
+
+ @Argument(usage="Name of the view to delete", required=true)
+ private View view;
+
+ @Override
+ public String getShortDescription() {
+
+ return Messages.DeleteViewCommand_ShortDescription();
+ }
+
+ @Override
+ protected int run() throws Exception {
+
+ view.checkPermission(View.DELETE);
+
+ final ViewGroup group = view.getOwner();
+ if (!group.canDelete(view)) {
+
+ stderr.format("%s does not allow to delete '%s' view\n",
+ group.getDisplayName(),
+ view.getViewName()
+ );
+ return -1;
+ }
+
+ group.deleteView(view);;
+
+ return 0;
+ }
+}
diff --git a/core/src/main/java/hudson/cli/GetViewCommand.java b/core/src/main/java/hudson/cli/GetViewCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..0ae0ffa552af691e4cdef28be2526917bb291eda
--- /dev/null
+++ b/core/src/main/java/hudson/cli/GetViewCommand.java
@@ -0,0 +1,55 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2013 Red Hat, Inc.
+ *
+ * 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.cli;
+
+import hudson.Extension;
+import hudson.model.View;
+
+import org.kohsuke.args4j.Argument;
+
+/**
+ * @author ogondza
+ * @since 1.538
+ */
+@Extension
+public class GetViewCommand extends CLICommand {
+
+ @Argument(usage="Name of the view to obtain", required=true)
+ private View view;
+
+ @Override
+ public String getShortDescription() {
+
+ return Messages.GetViewCommand_ShortDescription();
+ }
+
+ @Override
+ protected int run() throws Exception {
+
+ view.checkPermission(View.READ);
+ view.writeXml(stdout);
+
+ return 0;
+ }
+}
diff --git a/core/src/main/java/hudson/cli/UpdateViewCommand.java b/core/src/main/java/hudson/cli/UpdateViewCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..4f861fcb04d991d1d3a7ea49f3180994878cebdb
--- /dev/null
+++ b/core/src/main/java/hudson/cli/UpdateViewCommand.java
@@ -0,0 +1,55 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2013 Red Hat, Inc.
+ *
+ * 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.cli;
+
+import javax.xml.transform.stream.StreamSource;
+
+import hudson.Extension;
+import hudson.model.View;
+
+import org.kohsuke.args4j.Argument;
+
+/**
+ * @author ogondza
+ * @since 1.538
+ */
+@Extension
+public class UpdateViewCommand extends CLICommand {
+
+ @Argument(usage="Name of the view to update", required=true)
+ private View view;
+
+ @Override
+ public String getShortDescription() {
+
+ return Messages.UpdateViewCommand_ShortDescription();
+ }
+
+ @Override
+ protected int run() throws Exception {
+
+ view.updateByXml(new StreamSource(stdin));
+ return 0;
+ }
+}
diff --git a/core/src/main/java/hudson/cli/handlers/ViewOptionHandler.java b/core/src/main/java/hudson/cli/handlers/ViewOptionHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..8e9373a3b74d2a60c31c5483d27dd8cbde09ce86
--- /dev/null
+++ b/core/src/main/java/hudson/cli/handlers/ViewOptionHandler.java
@@ -0,0 +1,111 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2013, Red Hat, Inc.
+ *
+ * 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.cli.handlers;
+
+import hudson.model.ViewGroup;
+import hudson.model.View;
+
+import java.util.StringTokenizer;
+
+import jenkins.model.Jenkins;
+
+import org.kohsuke.MetaInfServices;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.OptionDef;
+import org.kohsuke.args4j.spi.OptionHandler;
+import org.kohsuke.args4j.spi.Parameters;
+import org.kohsuke.args4j.spi.Setter;
+
+/**
+ * Refers to {@link View} by its name.
+ *
+ *
+ * For example:
+ *
+ * - my_view_name
- refers to a top level view with given name.
+ * - nested/inner
- refers to a view named inner inside of a top level view group named nested.
+ *
+ *
+ *
+ * View name is a non-empty sequence of {@link View} names delimited by '/'.
+ * Handler traverse the view names from left to right. First one is expected to
+ * be a top level view and all but the last one are expected to be instances of
+ * {@link ViewGroup}. Handler fails to resolve view provided a view with given
+ * name does not exist or a user was not granted {@link View.READ} permission.
+ *
+ * @author ogondza
+ * @since 1.538
+ */
+@MetaInfServices
+public class ViewOptionHandler extends OptionHandler {
+
+ public ViewOptionHandler(CmdLineParser parser, OptionDef option, Setter setter) {
+
+ super(parser, option, setter);
+ }
+
+ @Override
+ public int parseArguments(Parameters params) throws CmdLineException {
+
+ setter.addValue(getView(params.getParameter(0)));
+ return 1;
+ }
+
+ private View getView(String name) throws CmdLineException {
+
+ View view = null;
+ ViewGroup group = Jenkins.getInstance();
+
+ final StringTokenizer tok = new StringTokenizer(name, "/");
+ while(tok.hasMoreTokens()) {
+
+ String viewName = tok.nextToken();
+
+ view = group.getView(viewName);
+ if (view == null) throw new CmdLineException(owner, String.format(
+ "No view named %s inside view %s",
+ viewName, group.getDisplayName()
+ ));
+
+ view.checkPermission(View.READ);
+
+ if (view instanceof ViewGroup) {
+ group = (ViewGroup) view;
+ } else if (tok.hasMoreTokens()) {
+ throw new CmdLineException(
+ owner, view.getViewName() + " view can not contain views"
+ );
+ }
+ }
+
+ return view;
+ }
+
+ @Override
+ public String getDefaultMetaVariable() {
+
+ return "VIEW";
+ }
+}
diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java
index 8e564dbc63faf91fdf8b17972dcfd83717029d27..266e61acf25dd78a1369086f98d756e6cde90bb2 100644
--- a/core/src/main/java/hudson/model/View.java
+++ b/core/src/main/java/hudson/model/View.java
@@ -83,6 +83,7 @@ import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -1044,16 +1045,13 @@ public abstract class View extends AbstractModelObject implements AccessControll
return new HttpResponse() {
public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) throws IOException, ServletException {
rsp.setContentType("application/xml");
- // pity we don't have a handy way to clone Jenkins.XSTREAM to temp add the omit Field
- XStream2 xStream2 = new XStream2();
- xStream2.omitField(View.class, "owner");
- xStream2.toXMLUTF8(View.this, rsp.getOutputStream());
+ View.this.writeXml(rsp.getOutputStream());
}
};
}
if (req.getMethod().equals("POST")) {
// submission
- updateByXml((Source)new StreamSource(req.getReader()));
+ updateByXml(new StreamSource(req.getReader()));
return HttpResponses.ok();
}
@@ -1061,6 +1059,16 @@ public abstract class View extends AbstractModelObject implements AccessControll
return HttpResponses.error(SC_BAD_REQUEST, "Unexpected request method " + req.getMethod());
}
+ /**
+ * @since 1.538
+ */
+ public void writeXml(OutputStream out) throws IOException {
+ // pity we don't have a handy way to clone Jenkins.XSTREAM to temp add the omit Field
+ XStream2 xStream2 = new XStream2();
+ xStream2.omitField(View.class, "owner");
+ xStream2.toXMLUTF8(View.this, out);
+ }
+
/**
* Updates Job by its XML definition.
*/
@@ -1083,7 +1091,11 @@ public abstract class View extends AbstractModelObject implements AccessControll
// try to reflect the changes by reloading
InputStream in = new BufferedInputStream(new ByteArrayInputStream(out.toString().getBytes("UTF-8")));
try {
+ // Do not allow overwriting view name as it might collide with another
+ // view in same ViewGroup and might not satisfy Jenkins.checkGoodName.
+ String oldname = name;
Jenkins.XSTREAM.unmarshal(new XppDriver().createReader(in), this);
+ name = oldname;
} catch (StreamException e) {
throw new IOException2("Unable to read",e);
} catch(ConversionException e) {
@@ -1185,11 +1197,17 @@ public abstract class View extends AbstractModelObject implements AccessControll
return v;
}
+ /**
+ * Instantiate View subtype from XML stream.
+ *
+ * @param name Alternative name to use or null to keep the one in xml.
+ */
public static View createViewFromXML(String name, InputStream xml) throws IOException {
InputStream in = new BufferedInputStream(xml);
try {
View v = (View) Jenkins.XSTREAM.fromXML(in);
- v.name = name;
+ if (name != null) v.name = name;
+ checkGoodName(v.name);
return v;
} catch(StreamException e) {
throw new IOException2("Unable to read",e);
diff --git a/core/src/main/resources/hudson/cli/Messages.properties b/core/src/main/resources/hudson/cli/Messages.properties
index 427399eaa00cd00a1a86d4b08a295781930f50ad..f831782c13306f95d5ba68ac26590482a28dafdb 100644
--- a/core/src/main/resources/hudson/cli/Messages.properties
+++ b/core/src/main/resources/hudson/cli/Messages.properties
@@ -14,8 +14,12 @@ CreateJobCommand.ShortDescription=\
Creates a new job by reading stdin as a configuration XML file.
CreateNodeCommand.ShortDescription=\
Creates a new node by reading stdin as a XML configuration.
+CreateViewCommand.ShortDescription=\
+ Creates a new view by reading stdin as a XML configuration.
DeleteBuildsCommand.ShortDescription=\
Deletes build record(s).
+DeleteViewCommand.ShortDescription=\
+ Deletes view.
GroovyCommand.ShortDescription=\
Executes the specified Groovy script.
GroovyshCommand.ShortDescription=\
@@ -49,6 +53,8 @@ GetJobCommand.ShortDescription=\
Dumps the job definition XML to stdout.
GetNodeCommand.ShortDescription=\
Dumps the node definition XML to stdout.
+GetViewCommand.ShortDescription=\
+ Dumps the view definition XML to stdout.
SetBuildDisplayNameCommand.ShortDescription=\
Sets the displayName of a build.
WhoAmICommand.ShortDescription=\
@@ -58,6 +64,8 @@ UpdateJobCommand.ShortDescription=\
UpdateNodeCommand.ShortDescription=\
Updates the node definition XML from stdin. The opposite of the get-node command.
SessionIdCommand.ShortDescription=Outputs the session ID, which changes every time Jenkins restarts.
+UpdateViewCommand.ShortDescription=\
+ Updates the view definition XML from stdin. The opposite of the get-view command.
BuildCommand.CLICause.ShortDescription=Started by command line by {0}
BuildCommand.CLICause.CannotBuildDisabled=\
diff --git a/core/src/test/java/hudson/cli/handlers/ViewOptionHandlerTest.java b/core/src/test/java/hudson/cli/handlers/ViewOptionHandlerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..98b29e2e1d77ae77807c0d72210f3dfd1e70a5f1
--- /dev/null
+++ b/core/src/test/java/hudson/cli/handlers/ViewOptionHandlerTest.java
@@ -0,0 +1,232 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2013 Red Hat, Inc.
+ *
+ * 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.cli.handlers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doThrow;
+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 hudson.model.ViewGroup;
+import hudson.model.View;
+
+import jenkins.model.Jenkins;
+
+import org.acegisecurity.AccessDeniedException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.spi.Parameters;
+import org.kohsuke.args4j.spi.Setter;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+@PrepareForTest(Jenkins.class)
+@RunWith(PowerMockRunner.class)
+public class ViewOptionHandlerTest {
+
+ @Mock private Setter setter;
+ private ViewOptionHandler handler;
+
+ // Hierarchy of views used as a shared fixture:
+ // $JENKINS_URL/view/outer/view/nested/view/inner/
+ @Mock private View inner;
+ @Mock private CompositeView nested;
+ @Mock private CompositeView outer;
+ @Mock private Jenkins jenkins;
+
+ @Before public void setUp() {
+
+ MockitoAnnotations.initMocks(this);
+
+ handler = new ViewOptionHandler(null, null, setter);
+
+ when(inner.getViewName()).thenReturn("inner");
+ when(inner.getDisplayName()).thenCallRealMethod();
+
+ when(nested.getViewName()).thenReturn("nested");
+ when(nested.getDisplayName()).thenCallRealMethod();
+ when(nested.getView("inner")).thenReturn(inner);
+
+ when(outer.getViewName()).thenReturn("outer");
+ when(outer.getDisplayName()).thenCallRealMethod();
+ when(outer.getView("nested")).thenReturn(nested);
+
+ PowerMockito.mockStatic(Jenkins.class);
+ PowerMockito.when(Jenkins.getInstance()).thenReturn(jenkins);
+ when(jenkins.getView("outer")).thenReturn(outer);
+ when(jenkins.getDisplayName()).thenReturn("Jenkins");
+ }
+
+ @Test public void resolveTopLevelView() throws Exception {
+
+ parse("outer");
+
+ verify(setter).addValue(outer);
+ }
+
+ @Test public void resolveNestedView() throws Exception {
+
+ parse("outer/nested");
+
+ verify(setter).addValue(nested);
+ }
+
+ @Test public void resolveOuterView() throws Exception {
+
+ parse("outer/nested/inner");
+
+ verify(setter).addValue(inner);
+ }
+
+ @Test public void ignoreLeadingAndTrailingSlashes() throws Exception {
+
+ parse("/outer/nested/inner/");
+
+ verify(setter).addValue(inner);
+ }
+
+ @Test public void reportNonexistentTopLevelView() throws Exception {
+
+ assertEquals(
+ "No view named missing_view inside view Jenkins",
+ parseFailedWith(CmdLineException.class, "missing_view")
+ );
+
+ verifyZeroInteractions(setter);
+ }
+
+ @Test public void reportNonexistentNestedView() throws Exception {
+
+ assertEquals(
+ "No view named missing_view inside view outer",
+ parseFailedWith(CmdLineException.class, "outer/missing_view")
+ );
+
+ verifyZeroInteractions(setter);
+ }
+
+ @Test public void reportNonexistentInnerView() throws Exception {
+
+ assertEquals(
+ "No view named missing_view inside view nested",
+ parseFailedWith(CmdLineException.class, "outer/nested/missing_view")
+ );
+
+ verifyZeroInteractions(setter);
+ }
+
+ @Test public void reportTraversingViewThatIsNotAViewGroup() throws Exception {
+
+ assertEquals(
+ "inner view can not contain views",
+ parseFailedWith(CmdLineException.class, "outer/nested/inner/missing")
+ );
+
+ verifyZeroInteractions(setter);
+ }
+
+ @Test public void refuseToReadOuterView() throws Exception {
+
+ denyAccessOn(outer);
+
+ parseFailedWith(AccessDeniedException.class, "outer/nested/inner");
+
+ verify(outer).checkPermission(View.READ);
+ verifyNoMoreInteractions(outer);
+
+ verifyZeroInteractions(nested);
+ verifyZeroInteractions(inner);
+ verifyZeroInteractions(setter);
+ }
+
+ @Test public void refuseToReadNestedView() throws Exception {
+
+ denyAccessOn(nested);
+
+ parseFailedWith(AccessDeniedException.class, "outer/nested/inner");
+
+ verify(nested).checkPermission(View.READ);
+ verifyNoMoreInteractions(nested);
+
+ verifyZeroInteractions(inner);
+ verifyZeroInteractions(setter);
+ }
+
+ @Test public void refuseToReadInnerView() throws Exception {
+
+ denyAccessOn(inner);
+
+ parseFailedWith(AccessDeniedException.class, "outer/nested/inner");
+
+ verify(inner).checkPermission(View.READ);
+ verifyNoMoreInteractions(inner);
+
+ verifyZeroInteractions(setter);
+ }
+
+ private void denyAccessOn(View view) {
+
+ doThrow(new AccessDeniedException(null)).when(view).checkPermission(View.READ);
+ }
+
+ private String parseFailedWith(Class extends Exception> type, final String... params) throws Exception {
+
+ try {
+ parse(params);
+
+ } catch (Exception ex) {
+
+ if (!type.isAssignableFrom(ex.getClass())) throw ex;
+
+ return ex.getMessage();
+ }
+
+ fail("No exception thrown. Expected " + type.getClass());
+ return null;
+ }
+
+ private void parse(final String... params) throws CmdLineException {
+ handler.parseArguments(new Parameters() {
+ public String getParameter(int idx) throws CmdLineException {
+ return params[idx];
+ }
+ public int size() {
+ return params.length;
+ }
+ });
+ }
+
+ private static abstract class CompositeView extends View implements ViewGroup {
+ protected CompositeView(String name) {
+ super(name);
+ }
+ }
+}
diff --git a/test/src/main/java/hudson/cli/CLICommandInvoker.java b/test/src/main/java/hudson/cli/CLICommandInvoker.java
index 76f96868d895f491d8945b43795d5bed9ed3d449..ba36fcf24739d98a5c51bcfd10423a9cbc667eb8 100644
--- a/test/src/main/java/hudson/cli/CLICommandInvoker.java
+++ b/test/src/main/java/hudson/cli/CLICommandInvoker.java
@@ -24,6 +24,7 @@
package hudson.cli;
+import hudson.Extension;
import hudson.model.User;
import hudson.security.Permission;
import hudson.security.GlobalMatrixAuthorizationStrategy;
@@ -31,11 +32,16 @@ import hudson.security.GlobalMatrixAuthorizationStrategy;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintStream;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import jenkins.model.Jenkins;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
import org.jvnet.hudson.test.JenkinsRule;
/**
@@ -56,6 +62,14 @@ public class CLICommandInvoker {
public CLICommandInvoker(final JenkinsRule rule, final CLICommand command) {
+ if (command.getClass().getAnnotation(Extension.class) == null) {
+
+ throw new AssertionError(String.format(
+ "Command %s is missing @Extension annotation.",
+ command.getClass()
+ ));
+ }
+
this.rule = rule;
this.command = command;
}
@@ -68,6 +82,8 @@ public class CLICommandInvoker {
public CLICommandInvoker withStdin(final InputStream stdin) {
+ if (stdin == null) throw new NullPointerException("No stdin provided");
+
this.stdin = stdin;
return this;
}
@@ -151,5 +167,71 @@ public class CLICommandInvoker {
return err.toString();
}
+
+ @Override
+ public String toString() {
+
+ StringBuilder builder = new StringBuilder("CLI command exited with ").append(result);
+ String stdout = stdout();
+ if (!"".equals(stdout)) {
+ builder.append("\nSTDOUT:\n").append(stdout);
+ }
+ String stderr = stderr();
+ if (!"".equals(stderr)) {
+ builder.append("\nSTDERR:\n").append(stderr);
+ }
+
+ return builder.toString();
+ }
+ }
+
+ public abstract static class Matcher extends TypeSafeMatcher {
+
+ private final String description;
+
+ private Matcher(String description) {
+ this.description = description;
+ }
+
+ @Override
+ protected void describeMismatchSafely(Result result, Description description) {
+ description.appendText(result.toString());
+ }
+
+ public void describeTo(Description description) {
+ description.appendText(this.description);
+ }
+
+ public static Matcher hasNoStandardOutput() {
+ return new Matcher("No standard output") {
+ @Override protected boolean matchesSafely(Result result) {
+ return "".equals(result.stdout());
+ }
+ };
+ }
+
+ public static Matcher hasNoErrorOutput() {
+ return new Matcher("No error output") {
+ @Override protected boolean matchesSafely(Result result) {
+ return "".equals(result.stderr());
+ }
+ };
+ }
+
+ public static Matcher succeeded() {
+ return new Matcher("Exited with 0 return code") {
+ @Override protected boolean matchesSafely(Result result) {
+ return result.result == 0;
+ }
+ };
+ }
+
+ public static Matcher failedWith(final long expectedCode) {
+ return new Matcher("Exited with " + expectedCode + " return code") {
+ @Override protected boolean matchesSafely(Result result) {
+ return result.result == expectedCode;
+ }
+ };
+ }
}
}
diff --git a/test/src/test/java/hudson/cli/CreateViewCommandTest.java b/test/src/test/java/hudson/cli/CreateViewCommandTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..695082a5d377bc61c0e601cad87d5477fbdef4b3
--- /dev/null
+++ b/test/src/test/java/hudson/cli/CreateViewCommandTest.java
@@ -0,0 +1,136 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * 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.cli;
+
+import static hudson.cli.CLICommandInvoker.Matcher.failedWith;
+import static hudson.cli.CLICommandInvoker.Matcher.hasNoStandardOutput;
+import static hudson.cli.CLICommandInvoker.Matcher.hasNoErrorOutput;
+import static hudson.cli.CLICommandInvoker.Matcher.succeeded;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.nullValue;
+
+import java.io.IOException;
+
+import hudson.model.ListView;
+import hudson.model.View;
+import jenkins.model.Jenkins;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.JenkinsRule;
+
+public class CreateViewCommandTest {
+
+ private CLICommandInvoker command;
+
+ @Rule public final JenkinsRule j = new JenkinsRule();
+
+ @Before public void setUp() {
+
+ command = new CLICommandInvoker(j, new CreateViewCommand());
+ }
+
+ @Test public void createViewShouldFailWithoutViewCreatePermission() {
+
+ final CLICommandInvoker.Result result = command
+ .authorizedTo(Jenkins.READ)
+ .withStdin(this.getClass().getResourceAsStream("/hudson/cli/view.xml"))
+ .invoke()
+ ;
+
+ assertThat(result, failedWith(-1));
+ assertThat(result, hasNoStandardOutput());
+ assertThat(result.stderr(), containsString("user is missing the View/Create permission"));
+ }
+
+ @Test public void createViewShouldSucceed() {
+
+ final CLICommandInvoker.Result result = command
+ .authorizedTo(View.CREATE, Jenkins.READ)
+ .withStdin(this.getClass().getResourceAsStream("/hudson/cli/view.xml"))
+ .invoke()
+ ;
+
+ assertThat(result, succeeded());
+ assertThat(result, hasNoErrorOutput());
+ assertThat(result, hasNoStandardOutput());
+
+ final View updatedView = j.jenkins.getView("ViewFromXML");
+ assertThat(updatedView.getViewName(), equalTo("ViewFromXML"));
+ assertThat(updatedView.isFilterExecutors(), equalTo(true));
+ assertThat(updatedView.isFilterQueue(), equalTo(false));
+ }
+
+ @Test public void createViewSpecifyingNameExplicitlyShouldSucceed() {
+
+ final CLICommandInvoker.Result result = command
+ .authorizedTo(View.CREATE, Jenkins.READ)
+ .withStdin(this.getClass().getResourceAsStream("/hudson/cli/view.xml"))
+ .invokeWithArgs("CustomViewName")
+ ;
+
+ assertThat(result, succeeded());
+ assertThat(result, hasNoErrorOutput());
+ assertThat(result, hasNoStandardOutput());
+
+ assertThat("A view with original name should not exist", j.jenkins.getView("ViewFromXML"), nullValue());
+
+ final View updatedView = j.jenkins.getView("CustomViewName");
+ assertThat(updatedView.getViewName(), equalTo("CustomViewName"));
+ assertThat(updatedView.isFilterExecutors(), equalTo(true));
+ assertThat(updatedView.isFilterQueue(), equalTo(false));
+ }
+
+ @Test public void createViewShouldFailIfViewAlreadyExists() throws IOException {
+
+ j.jenkins.addView(new ListView("ViewFromXML"));
+
+ final CLICommandInvoker.Result result = command
+ .authorizedTo(View.CREATE, Jenkins.READ)
+ .withStdin(this.getClass().getResourceAsStream("/hudson/cli/view.xml"))
+ .invoke()
+ ;
+
+ assertThat(result, failedWith(-1));
+ assertThat(result, hasNoStandardOutput());
+ assertThat(result.stderr(), containsString("View 'ViewFromXML' already exists"));
+ }
+
+ @Test public void createViewShouldFailUsingInvalidName() {
+
+ final CLICommandInvoker.Result result = command
+ .authorizedTo(View.CREATE, Jenkins.READ)
+ .withStdin(this.getClass().getResourceAsStream("/hudson/cli/view.xml"))
+ .invokeWithArgs("..")
+ ;
+
+ assertThat(result, failedWith(-1));
+ assertThat(result, hasNoStandardOutput());
+ assertThat(result.stderr(), containsString("Invalid view name"));
+ }
+}
diff --git a/test/src/test/java/hudson/cli/DeleteViewCommandTest.java b/test/src/test/java/hudson/cli/DeleteViewCommandTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..93998cfa4f705e0ac76c2a235a88811c2275547c
--- /dev/null
+++ b/test/src/test/java/hudson/cli/DeleteViewCommandTest.java
@@ -0,0 +1,111 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * 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.cli;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.nullValue;
+
+import static hudson.cli.CLICommandInvoker.Matcher.hasNoStandardOutput;
+import static hudson.cli.CLICommandInvoker.Matcher.hasNoErrorOutput;
+import static hudson.cli.CLICommandInvoker.Matcher.succeeded;
+import static hudson.cli.CLICommandInvoker.Matcher.failedWith;
+
+import java.io.IOException;
+
+import hudson.model.ListView;
+import hudson.model.View;
+import jenkins.model.Jenkins;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.JenkinsRule;
+
+public class DeleteViewCommandTest {
+
+ private CLICommandInvoker command;
+
+ @Rule public final JenkinsRule j = new JenkinsRule();
+
+ @Before public void setUp() {
+
+ command = new CLICommandInvoker(j, new DeleteViewCommand());
+ }
+
+ @Test public void deleteViewShouldFailWithoutViewDeletePermission() throws IOException {
+
+ j.jenkins.addView(new ListView("aView"));
+
+ final CLICommandInvoker.Result result = command
+ .authorizedTo(View.READ, Jenkins.READ)
+ .invokeWithArgs("aView")
+ ;
+
+ assertThat(result, failedWith(-1));
+ assertThat(result, hasNoStandardOutput());
+ assertThat(result.stderr(), containsString("user is missing the View/Delete permission"));
+ }
+
+ @Test public void deleteViewShouldSucceed() throws Exception {
+
+ j.jenkins.addView(new ListView("aView"));
+
+ final CLICommandInvoker.Result result = command
+ .authorizedTo(View.READ, View.DELETE, Jenkins.READ)
+ .invokeWithArgs("aView")
+ ;
+
+ assertThat(result, succeeded());
+ assertThat(result, hasNoStandardOutput());
+ assertThat(result, hasNoErrorOutput());
+ assertThat(j.jenkins.getView("aView"), nullValue());
+ }
+
+ @Test public void deleteViewShouldFailIfViewDoesNotExist() {
+
+ final CLICommandInvoker.Result result = command
+ .authorizedTo(View.READ, View.DELETE, Jenkins.READ)
+ .invokeWithArgs("never_created")
+ ;
+
+ assertThat(result, failedWith(-1));
+ assertThat(result, hasNoStandardOutput());
+ assertThat(result.stderr(), containsString("No view named never_created inside view Jenkins"));
+ }
+
+ // ViewGroup.canDelete()
+ @Test public void deleteViewShouldFailIfViewGroupDoesNotAllowDeletion() {
+
+ final CLICommandInvoker.Result result = command
+ .authorizedTo(View.READ, View.DELETE, Jenkins.READ)
+ .invokeWithArgs("All")
+ ;
+
+ assertThat(result, failedWith(-1));
+ assertThat(result, hasNoStandardOutput());
+ assertThat(result.stderr(), containsString("Jenkins does not allow to delete 'All' view"));
+ }
+}
diff --git a/test/src/test/java/hudson/cli/GetViewCommandTest.java b/test/src/test/java/hudson/cli/GetViewCommandTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4eb47f1553b11d7530ed8c72f24670e0bba331c5
--- /dev/null
+++ b/test/src/test/java/hudson/cli/GetViewCommandTest.java
@@ -0,0 +1,97 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * 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.cli;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.startsWith;
+import static hudson.cli.CLICommandInvoker.Matcher.failedWith;
+import static hudson.cli.CLICommandInvoker.Matcher.hasNoStandardOutput;
+import static hudson.cli.CLICommandInvoker.Matcher.hasNoErrorOutput;
+import static hudson.cli.CLICommandInvoker.Matcher.succeeded;
+
+import java.io.IOException;
+
+import hudson.model.ListView;
+import hudson.model.View;
+import jenkins.model.Jenkins;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.JenkinsRule;
+
+public class GetViewCommandTest {
+
+ private CLICommandInvoker command;
+
+ @Rule public final JenkinsRule j = new JenkinsRule();
+
+ @Before public void setUp() {
+
+ command = new CLICommandInvoker(j, new GetViewCommand());
+ }
+
+ @Test public void getViewShouldFailWithoutViewReadPermission() throws IOException {
+
+ j.jenkins.addView(new ListView("aView"));
+
+ final CLICommandInvoker.Result result = command
+ .authorizedTo(Jenkins.READ)
+ .invokeWithArgs("aView")
+ ;
+
+ assertThat(result, failedWith(-1));
+ assertThat(result, hasNoStandardOutput());
+ assertThat(result.stderr(), containsString("user is missing the View/Read permission"));
+ }
+
+ @Test public void getViewShouldYieldConfigXml() throws Exception {
+
+ j.jenkins.addView(new ListView("aView"));
+
+ final CLICommandInvoker.Result result = command
+ .authorizedTo(View.READ, Jenkins.READ)
+ .invokeWithArgs("aView")
+ ;
+
+ assertThat(result, succeeded());
+ assertThat(result, hasNoErrorOutput());
+ assertThat(result.stdout(), startsWith(""));
+ assertThat(result.stdout(), containsString("aView"));
+ }
+
+ @Test public void getViewShouldFailIfViewDoesNotExist() {
+
+ final CLICommandInvoker.Result result = command
+ .authorizedTo(View.READ, Jenkins.READ)
+ .invokeWithArgs("never_created")
+ ;
+
+ assertThat(result, failedWith(-1));
+ assertThat(result, hasNoStandardOutput());
+ assertThat(result.stderr(), containsString("No view named never_created inside view Jenkins"));
+ }
+}
diff --git a/test/src/test/java/hudson/cli/UpdateViewCommandTest.java b/test/src/test/java/hudson/cli/UpdateViewCommandTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d46483b88b8eb082c15ee50cd412789ed43bd8c
--- /dev/null
+++ b/test/src/test/java/hudson/cli/UpdateViewCommandTest.java
@@ -0,0 +1,103 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * 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.cli;
+
+import static hudson.cli.CLICommandInvoker.Matcher.failedWith;
+import static hudson.cli.CLICommandInvoker.Matcher.hasNoStandardOutput;
+import static hudson.cli.CLICommandInvoker.Matcher.hasNoErrorOutput;
+import static hudson.cli.CLICommandInvoker.Matcher.succeeded;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.nullValue;
+import hudson.model.ListView;
+import hudson.model.View;
+import jenkins.model.Jenkins;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.JenkinsRule;
+
+public class UpdateViewCommandTest {
+
+ private CLICommandInvoker command;
+
+ @Rule public final JenkinsRule j = new JenkinsRule();
+
+ @Before public void setUp() {
+
+ command = new CLICommandInvoker(j, new UpdateViewCommand());
+ }
+
+ @Test public void updateViewShouldFailWithoutViewConfigurePermission() throws Exception {
+
+ j.jenkins.addView(new ListView("aView"));
+
+ final CLICommandInvoker.Result result = command
+ .authorizedTo(View.READ, Jenkins.READ)
+ .withStdin(this.getClass().getResourceAsStream("/hudson/cli/view.xml"))
+ .invokeWithArgs("aView")
+ ;
+
+ assertThat(result, failedWith(-1));
+ assertThat(result, hasNoStandardOutput());
+ assertThat(result.stderr(), containsString("user is missing the View/Configure permission"));
+ }
+
+ @Test public void updateViewShouldModifyViewConfiguration() throws Exception {
+
+ j.jenkins.addView(new ListView("aView"));
+
+ final CLICommandInvoker.Result result = command
+ .authorizedTo(View.READ, View.CONFIGURE, Jenkins.READ)
+ .withStdin(this.getClass().getResourceAsStream("/hudson/cli/view.xml"))
+ .invokeWithArgs("aView")
+ ;
+
+ assertThat(result, succeeded());
+ assertThat(result, hasNoErrorOutput());
+
+ assertThat("Update should not modify view name", j.jenkins.getView("ViewFromXML"), nullValue());
+
+ final View updatedView = j.jenkins.getView("aView");
+ assertThat(updatedView.getViewName(), equalTo("aView"));
+ assertThat(updatedView.isFilterExecutors(), equalTo(true));
+ assertThat(updatedView.isFilterQueue(), equalTo(false));
+ }
+
+ @Test public void updateViewShouldFailIfViewDoesNotExist() {
+
+ final CLICommandInvoker.Result result = command
+ .authorizedTo(View.READ, View.CONFIGURE, Jenkins.READ)
+ .withStdin(this.getClass().getResourceAsStream("/hudson/cli/view.xml"))
+ .invokeWithArgs("not_created")
+ ;
+
+ assertThat(result, failedWith(-1));
+ assertThat(result, hasNoStandardOutput());
+ assertThat(result.stderr(), containsString("No view named not_created inside view Jenkins"));
+ }
+}
diff --git a/test/src/test/resources/hudson/cli/view.xml b/test/src/test/resources/hudson/cli/view.xml
new file mode 100644
index 0000000000000000000000000000000000000000..35c85185447072fc5b392151602b49c49e458756
--- /dev/null
+++ b/test/src/test/resources/hudson/cli/view.xml
@@ -0,0 +1,31 @@
+
+
+ ViewFromXML
+ true
+ false
+
+
+
+ core_selenium-test
+ jenkins_lts_branch
+ jenkins_main_maven-3.1.0
+ jenkins_main_trunk
+ jenkins_pom
+ jenkins_rc_branch
+ jenkins_ui-changes_branch
+ remoting
+ selenium-tests
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+