Skip to content
Snippets Groups Projects
Commit 59256b5a authored by Michael Kozono's avatar Michael Kozono
Browse files

Refactor to more robust implementation

In order to avoid string manipulation or modify route params (to make them unambiguous for `url_for`), we are accepting a small behavior change:

When being redirected to the canonical path for a group, if you requested a group show path starting with `/groups/…` then you’ll now be redirected to the group at root `/…`.
parent f9785dce
No related branches found
No related tags found
No related merge requests found
Pipeline #
Loading
Loading
@@ -32,19 +32,7 @@ module RoutableActions
if canonical_path.casecmp(requested_full_path) != 0
flash[:notice] = "#{routable.class.to_s.titleize} '#{requested_full_path}' was moved to '#{canonical_path}'. Please update any links and bookmarks that may still have the old path."
end
redirect_to full_canonical_path(canonical_path, requested_full_path)
end
end
def full_canonical_path(canonical_path, requested_full_path)
request_path = request.original_fullpath
top_level_route_regex = %r{\A(/#{Regexp.union(DynamicPathValidator::TOP_LEVEL_ROUTES)}/)#{requested_full_path}}
top_level_route_match = request_path.match(top_level_route_regex)
if top_level_route_match
request_path.sub(top_level_route_regex, "\\1#{canonical_path}")
else
request_path.sub(requested_full_path, canonical_path)
redirect_to build_canonical_path(routable)
end
end
end
Loading
Loading
@@ -31,4 +31,14 @@ class Groups::ApplicationController < ApplicationController
return render_403
end
end
def build_canonical_path(group)
if params[:group_id]
params[:group_id] = group.to_param
elsif params[:id]
params[:id] = group.to_param
end
url_for(params)
end
end
Loading
Loading
@@ -169,4 +169,9 @@ class GroupsController < Groups::ApplicationController
@notification_setting = current_user.notification_settings_for(group)
end
end
def build_canonical_path(group)
return url_for(group) if action_name == 'show' # root group path
super
end
end
Loading
Loading
@@ -29,6 +29,18 @@ class Projects::ApplicationController < ApplicationController
@project = find_routable!(Project, path, extra_authorization_proc: auth_proc)
end
 
def build_canonical_path(project)
params[:namespace_id] = project.namespace.to_param
if params[:project_id]
params[:project_id] = project.to_param
elsif params[:id]
params[:id] = project.to_param
end
url_for(params)
end
def repository
@repository ||= project.repository
end
Loading
Loading
Loading
Loading
@@ -138,4 +138,8 @@ class UsersController < ApplicationController
def projects_for_current_user
ProjectsFinder.new(current_user: current_user).execute
end
def build_canonical_path(user)
url_for(params.merge(username: user.to_param))
end
end
Loading
Loading
@@ -21,7 +21,6 @@ describe Groups::MilestonesController do
sign_in(user)
group.add_owner(user)
project.team << [user, :master]
controller.instance_variable_set(:@group, group)
end
 
it_behaves_like 'milestone tabs'
Loading
Loading
@@ -29,7 +28,7 @@ describe Groups::MilestonesController do
describe "#create" do
it "creates group milestone with Chinese title" do
post :create,
group_id: group.id,
group_id: group.to_param,
milestone: { project_ids: [project.id, project2.id], title: title }
 
expect(response).to redirect_to(group_milestone_path(group, title.to_slug.to_s, title: title))
Loading
Loading
@@ -37,9 +36,139 @@ describe Groups::MilestonesController do
end
 
it "redirects to new when there are no project ids" do
post :create, group_id: group.id, milestone: { title: title, project_ids: [""] }
post :create, group_id: group.to_param, milestone: { title: title, project_ids: [""] }
expect(response).to render_template :new
expect(assigns(:milestone).errors).not_to be_nil
end
end
describe '#ensure_canonical_path' do
before do
sign_in(user)
end
context 'for a GET request' do
context 'when requesting the canonical path' do
context 'non-show path' do
context 'with exactly matching casing' do
it 'does not redirect' do
get :index, group_id: group.to_param
expect(response).not_to have_http_status(301)
end
end
context 'with different casing' do
it 'redirects to the correct casing' do
get :index, group_id: group.to_param.upcase
expect(response).to redirect_to(group_milestones_path(group.to_param))
expect(controller).not_to set_flash[:notice]
end
end
end
context 'show path' do
context 'with exactly matching casing' do
it 'does not redirect' do
get :show, group_id: group.to_param, id: title
expect(response).not_to have_http_status(301)
end
end
context 'with different casing' do
it 'redirects to the correct casing' do
get :show, group_id: group.to_param.upcase, id: title
expect(response).to redirect_to(group_milestone_path(group.to_param, title))
expect(controller).not_to set_flash[:notice]
end
end
end
end
context 'when requesting a redirected path' do
let(:redirect_route) { group.redirect_routes.create(path: 'old-path') }
it 'redirects to the canonical path' do
get :merge_requests, group_id: redirect_route.path, id: title
expect(response).to redirect_to(merge_requests_group_milestone_path(group.to_param, title))
expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
end
context 'when the old group path is a substring of the scheme or host' do
let(:redirect_route) { group.redirect_routes.create(path: 'http') }
it 'does not modify the requested host' do
get :merge_requests, group_id: redirect_route.path, id: title
expect(response).to redirect_to(merge_requests_group_milestone_path(group.to_param, title))
expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
end
end
context 'when the old group path is substring of groups' do
# I.e. /groups/oups should not become /grfoo/oups
let(:redirect_route) { group.redirect_routes.create(path: 'oups') }
it 'does not modify the /groups part of the path' do
get :merge_requests, group_id: redirect_route.path, id: title
expect(response).to redirect_to(merge_requests_group_milestone_path(group.to_param, title))
expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
end
end
context 'when the old group path is substring of groups plus the new path' do
# I.e. /groups/oups/oup should not become /grfoos
let(:redirect_route) { group.redirect_routes.create(path: 'oups/oup') }
it 'does not modify the /groups part of the path' do
get :merge_requests, group_id: redirect_route.path, id: title
expect(response).to redirect_to(merge_requests_group_milestone_path(group.to_param, title))
expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
end
end
end
end
end
context 'for a non-GET request' do
context 'when requesting the canonical path with different casing' do
it 'does not 404' do
post :create,
group_id: group.to_param,
milestone: { project_ids: [project.id, project2.id], title: title }
expect(response).not_to have_http_status(404)
end
it 'does not redirect to the correct casing' do
post :create,
group_id: group.to_param,
milestone: { project_ids: [project.id, project2.id], title: title }
expect(response).not_to have_http_status(301)
end
end
context 'when requesting a redirected path' do
let(:redirect_route) { group.redirect_routes.create(path: 'old-path') }
it 'returns not found' do
post :create,
group_id: redirect_route.path,
milestone: { project_ids: [project.id, project2.id], title: title }
expect(response).to have_http_status(404)
end
end
end
def group_moved_message(redirect_route, group)
"Group '#{redirect_route.path}' was moved to '#{group.full_path}'. Please update any links and bookmarks that may still have the old path."
end
end
Loading
Loading
@@ -214,12 +214,43 @@ describe GroupsController do
end
 
context 'when requesting groups under the /groups path' do
context 'when requesting the canonical path with different casing' do
it 'redirects to the correct casing' do
get :issues, id: group.to_param.upcase
context 'when requesting the canonical path' do
context 'non-show path' do
context 'with exactly matching casing' do
it 'does not redirect' do
get :issues, id: group.to_param
expect(response).not_to have_http_status(301)
end
end
 
expect(response).to redirect_to(issues_group_path(group.to_param))
expect(controller).not_to set_flash[:notice]
context 'with different casing' do
it 'redirects to the correct casing' do
get :issues, id: group.to_param.upcase
expect(response).to redirect_to(issues_group_path(group.to_param))
expect(controller).not_to set_flash[:notice]
end
end
end
context 'show path' do
context 'with exactly matching casing' do
it 'does not redirect' do
get :show, id: group.to_param
expect(response).not_to have_http_status(301)
end
end
context 'with different casing' do
it 'redirects to the correct casing at the root path' do
get :show, id: group.to_param.upcase
expect(response).to redirect_to(group)
expect(controller).not_to set_flash[:notice]
end
end
end
end
 
Loading
Loading
shared_examples 'milestone tabs' do
def go(path, extra_params = {})
params = if milestone.is_a?(GlobalMilestone)
{ group_id: group.id, id: milestone.safe_title, title: milestone.title }
{ group_id: group.to_param, id: milestone.safe_title, title: milestone.title }
else
{ namespace_id: project.namespace.to_param, project_id: project, id: milestone.iid }
end
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment