From 29d441331e3e9e0fc22c0ca07d97133f609c6f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Gond=C5=BEa?= Date: Fri, 11 Oct 2013 11:48:57 +0200 Subject: [PATCH] [FIXED JENKINS-19996] Support view manipulation via Jenkins CLI --- .../java/hudson/cli/CreateViewCommand.java | 76 ++++++++++ .../java/hudson/cli/DeleteViewCommand.java | 67 +++++++++ .../main/java/hudson/cli/GetViewCommand.java | 55 +++++++ .../java/hudson/cli/UpdateViewCommand.java | 55 +++++++ .../cli/handlers/ViewOptionHandler.java | 68 +++++++++ core/src/main/java/hudson/model/View.java | 30 +++- .../resources/hudson/cli/Messages.properties | 8 ++ .../java/hudson/cli/CLICommandInvoker.java | 82 +++++++++++ .../hudson/cli/CreateViewCommandTest.java | 136 ++++++++++++++++++ .../hudson/cli/DeleteViewCommandTest.java | 111 ++++++++++++++ .../java/hudson/cli/GetViewCommandTest.java | 99 +++++++++++++ .../hudson/cli/UpdateViewCommandTest.java | 103 +++++++++++++ test/src/test/resources/hudson/cli/view.xml | 31 ++++ 13 files changed, 915 insertions(+), 6 deletions(-) create mode 100644 core/src/main/java/hudson/cli/CreateViewCommand.java create mode 100644 core/src/main/java/hudson/cli/DeleteViewCommand.java create mode 100644 core/src/main/java/hudson/cli/GetViewCommand.java create mode 100644 core/src/main/java/hudson/cli/UpdateViewCommand.java create mode 100644 core/src/main/java/hudson/cli/handlers/ViewOptionHandler.java create mode 100644 test/src/test/java/hudson/cli/CreateViewCommandTest.java create mode 100644 test/src/test/java/hudson/cli/DeleteViewCommandTest.java create mode 100644 test/src/test/java/hudson/cli/GetViewCommandTest.java create mode 100644 test/src/test/java/hudson/cli/UpdateViewCommandTest.java create mode 100644 test/src/test/resources/hudson/cli/view.xml 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 0000000000..3aeab31c64 --- /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 TODO + */ +@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 0000000000..1dc5a048f8 --- /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 TODO + */ +@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 0000000000..9a8c856127 --- /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 TODO + */ +@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 0000000000..0749ad1f70 --- /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 TODO + */ +@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 0000000000..cad865970c --- /dev/null +++ b/core/src/main/java/hudson/cli/handlers/ViewOptionHandler.java @@ -0,0 +1,68 @@ +/* + * 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.View; +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. + * + * @author ogondza + * @since TODO + */ +@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 { + + String viewName = params.getParameter(0); + + final View view = Jenkins.getInstance().getView(viewName); + if (view == null) throw new CmdLineException(owner, "No such view '" + viewName + "'"); + + setter.addValue(view); + return 1; + } + + @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 2944ea9946..e5ea796786 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 TODO + */ + 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) { @@ -1180,11 +1192,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 b9a2405a79..22c685f2cc 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/test/src/main/java/hudson/cli/CLICommandInvoker.java b/test/src/main/java/hudson/cli/CLICommandInvoker.java index 76f96868d8..ba36fcf247 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 0000000000..695082a5d3 --- /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 0000000000..5b3d8d334f --- /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(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.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.DELETE, Jenkins.READ) + .invokeWithArgs("never_created") + ; + + assertThat(result, failedWith(-1)); + assertThat(result, hasNoStandardOutput()); + assertThat(result.stderr(), containsString("No such view 'never_created'")); + } + + // ViewGroup.canDelete() + @Test public void deleteViewShouldFailIfViewGroupDoesNotAllowDeletion() { + + final CLICommandInvoker.Result result = command + .authorizedTo(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 0000000000..8e2016fd31 --- /dev/null +++ b/test/src/test/java/hudson/cli/GetViewCommandTest.java @@ -0,0 +1,99 @@ +/* + * 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.equalTo; +import static org.hamcrest.Matchers.startsWith; +import static org.hamcrest.text.IsEmptyString.isEmptyString; +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 such view 'never_created'")); + } +} 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 0000000000..7afa8f62fe --- /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(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.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.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 such view 'not_created'")); + } +} 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 0000000000..35c8518544 --- /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 + -- GitLab