Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FIXED JENKINS-17575] Redirecting properly after deleting jobs in fol…
…ders.

Expanded MockFolder to be a ViewGroup so that it is possible to test views inside folders,
and generally fixed it to service Stapler hierarchies properly so that WebClient-based tests can work.
(cherry picked from commit 1205e9f)
  • Loading branch information
jglick authored and olivergondza committed Sep 18, 2013
1 parent adcdf61 commit 134e01e
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 6 deletions.
21 changes: 16 additions & 5 deletions core/src/main/java/hudson/model/AbstractProject.java
Expand Up @@ -95,6 +95,7 @@
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.stapler.Ancestor;
import org.kohsuke.stapler.ForwardToView;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
Expand All @@ -117,6 +118,7 @@
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
Expand Down Expand Up @@ -1892,11 +1894,20 @@ public void doDoDelete(StaplerRequest req, StaplerResponse rsp) throws IOExcepti
delete();
if (req == null || rsp == null)
return;
View view = req.findAncestorObject(View.class);
if (view == null)
rsp.sendRedirect2(req.getContextPath() + '/' + getParent().getUrl());
else
rsp.sendRedirect2(req.getContextPath() + '/' + view.getUrl());
List<Ancestor> ancestors = req.getAncestors();
ListIterator<Ancestor> it = ancestors.listIterator(ancestors.size());
String url = getParent().getUrl(); // fallback but we ought to get to Jenkins.instance at the root
while (it.hasPrevious()) {
Object a = it.previous().getObject();
if (a instanceof View) {
url = ((View) a).getUrl();
break;
} else if (a instanceof ViewGroup) {
url = ((ViewGroup) a).getUrl();
break;
}
}
rsp.sendRedirect2(req.getContextPath() + '/' + url);
}

@Override
Expand Down
89 changes: 88 additions & 1 deletion test/src/main/java/org/jvnet/hudson/test/MockFolder.java
Expand Up @@ -26,37 +26,53 @@

import hudson.Extension;
import hudson.model.AbstractItem;
import hudson.model.Action;
import hudson.model.AllView;
import hudson.model.FreeStyleProject;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.ItemGroupMixIn;
import hudson.model.Job;
import hudson.model.TopLevelItem;
import hudson.model.TopLevelItemDescriptor;
import hudson.model.View;
import hudson.model.ViewGroup;
import hudson.model.ViewGroupMixIn;
import hudson.util.Function1;
import hudson.views.DefaultViewsTabBar;
import hudson.views.ViewsTabBar;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.servlet.ServletException;
import jenkins.model.Jenkins;
import jenkins.model.ModifiableTopLevelItemGroup;
import org.kohsuke.stapler.StaplerFallback;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.WebMethod;

/**
* Minimal implementation of a modifiable item group akin to the CloudBees Folders plugin.
* No UI, just enough implementation to test functionality of code which should deal with item full names, etc.
* @since 1.494
*/
@SuppressWarnings({"unchecked", "rawtypes"}) // the usual API mistakes
public class MockFolder extends AbstractItem implements ModifiableTopLevelItemGroup, TopLevelItem { // could be a ViewGroup too
public class MockFolder extends AbstractItem implements ModifiableTopLevelItemGroup, TopLevelItem, ViewGroup, StaplerFallback {

private transient Map<String,TopLevelItem> items = new TreeMap<String,TopLevelItem>();
private final List<View> views = new ArrayList<View>(Collections.singleton(new AllView("All", this)));
private String primaryView;
private ViewsTabBar viewsTabBar;

private MockFolder(ItemGroup parent, String name) {
super(parent, name);
Expand Down Expand Up @@ -102,6 +118,20 @@ private ItemGroupMixIn mixin() {
};
}

private ViewGroupMixIn vgmixin() {
return new ViewGroupMixIn(this) {
@Override protected List<View> views() {
return views;
}
@Override protected String primaryView() {
return primaryView != null ? primaryView : views.get(0).getViewName();
}
@Override protected void primaryView(String newName) {
primaryView = newName;
}
};
}

@Override public <T extends TopLevelItem> T copy(T src, String name) throws IOException {
return mixin().copy(src, name);
}
Expand Down Expand Up @@ -156,6 +186,63 @@ public <T extends TopLevelItem> T createProject(Class<T> type, String name) thro
return Jenkins.getInstance().getDescriptorByType(DescriptorImpl.class);
}

public void addView(View view) throws IOException {
vgmixin().addView(view);
}

@Override public boolean canDelete(View view) {
return vgmixin().canDelete(view);
}

@Override public void deleteView(View view) throws IOException {
vgmixin().deleteView(view);
}

@Override public Collection<View> getViews() {
return vgmixin().getViews();
}

@Override public View getView(String name) {
return vgmixin().getView(name);
}

@Override public View getPrimaryView() {
return vgmixin().getPrimaryView();
}

@Override public void onViewRenamed(View view, String oldName, String newName) {
vgmixin().onViewRenamed(view, oldName, newName);
}

@Override public ViewsTabBar getViewsTabBar() {
if (viewsTabBar == null) {
viewsTabBar = new DefaultViewsTabBar();
}
return viewsTabBar;
}

@Override public ItemGroup<? extends TopLevelItem> getItemGroup() {
return this;
}

@Override public List<Action> getViewActions() {
// XXX what should the default be? View.getOwnerViewActions uses Jenkins.actions; Jenkins.viewActions would make more sense as a default;
// or should it be empty by default since non-top-level folders probably do not need the same actions as root?
return Collections.emptyList();
}

@Override public Object getStaplerFallback() {
return getPrimaryView();
}

/**
* Same as {@link #getItem} but named this way as a {@link WebMethod}.
* @see Hudson#getJob
*/
public TopLevelItem getJob(String name) {
return getItem(name);
}

@Extension public static class DescriptorImpl extends TopLevelItemDescriptor {

@Override public String getDisplayName() {
Expand Down
9 changes: 9 additions & 0 deletions test/src/test/java/hudson/model/AbstractProjectTest.java
Expand Up @@ -379,12 +379,21 @@ public void testExternalBuildDirectoryRenameDelete() throws Exception {
assertFalse(b.getRootDir().isDirectory());
}

@Bug(17575)
public void testDeleteRedirect() throws Exception {
createFreeStyleProject("j1");
assertEquals("", deleteRedirectTarget("job/j1"));
createFreeStyleProject("j2");
Jenkins.getInstance().addView(new AllView("v1"));
assertEquals("view/v1/", deleteRedirectTarget("view/v1/job/j2"));
MockFolder d = Jenkins.getInstance().createProject(MockFolder.class, "d");
d.addView(new AllView("v2"));
d.createProject(FreeStyleProject.class, "j3");
d.createProject(FreeStyleProject.class, "j4");
d.createProject(FreeStyleProject.class, "j5");
assertEquals("job/d/", deleteRedirectTarget("job/d/job/j3"));
assertEquals("job/d/view/v2/", deleteRedirectTarget("job/d/view/v2/job/j4"));
assertEquals("view/v1/job/d/", deleteRedirectTarget("view/v1/job/d/job/j5"));
}
private String deleteRedirectTarget(String job) throws Exception {
WebClient wc = new WebClient();
Expand Down

0 comments on commit 134e01e

Please sign in to comment.