diff --git a/app/controllers/concerns/routable_actions.rb b/app/controllers/concerns/routable_actions.rb
index 76c2908463cbb97c3909c91f5c7e845aec4d8e4c..a5b793081b23fc4037bf4786e0c66d7666ff6032 100644
--- a/app/controllers/concerns/routable_actions.rb
+++ b/app/controllers/concerns/routable_actions.rb
@@ -24,15 +24,27 @@ module RoutableActions
     end
   end
 
-  def ensure_canonical_path(routable, requested_path)
+  def ensure_canonical_path(routable, requested_full_path)
     return unless request.get?
 
     canonical_path = routable.full_path
-    if canonical_path != requested_path
-      if canonical_path.casecmp(requested_path) != 0
-        flash[:notice] = "#{routable.class.to_s.titleize} '#{requested_path}' was moved to '#{canonical_path}'. Please update any links and bookmarks that may still have the old path."
+    if canonical_path != requested_full_path
+      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 request.original_fullpath.sub(requested_path, canonical_path)
+      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)
     end
   end
 end
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 0392315b62f64eb95f2abec402836fdec545fef0..993654fddaa675aba2b9c5c0c74a8ee644ce0354 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -84,26 +84,6 @@ describe GroupsController do
         expect(assigns(:issues)).to eq [issue_2, issue_1]
       end
     end
-
-    context 'when requesting the canonical path 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
-
-    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 :issues, id: redirect_route.path
-
-        expect(response).to redirect_to(issues_group_path(group.to_param))
-        expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
-      end
-    end
   end
 
   describe 'GET #merge_requests' do
@@ -129,26 +109,6 @@ describe GroupsController do
         expect(assigns(:merge_requests)).to eq [merge_request_2, merge_request_1]
       end
     end
-
-    context 'when requesting the canonical path with different casing' do
-      it 'redirects to the correct casing' do
-        get :merge_requests, id: group.to_param.upcase
-
-        expect(response).to redirect_to(merge_requests_group_path(group.to_param))
-        expect(controller).not_to set_flash[:notice]
-      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, id: redirect_route.path
-
-        expect(response).to redirect_to(merge_requests_group_path(group.to_param))
-        expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
-      end
-    end
   end
 
   describe 'DELETE #destroy' do
@@ -178,30 +138,6 @@ describe GroupsController do
 
         expect(response).to redirect_to(root_path)
       end
-
-      context 'when requesting the canonical path with different casing' do
-        it 'does not 404' do
-          delete :destroy, id: group.to_param.upcase
-
-          expect(response).not_to have_http_status(404)
-        end
-
-        it 'does not redirect to the correct casing' do
-          delete :destroy, id: group.to_param.upcase
-
-          expect(response).not_to redirect_to(group_path(group.to_param))
-        end
-      end
-
-      context 'when requesting a redirected path' do
-        let(:redirect_route) { group.redirect_routes.create(path: 'old-path') }
-
-        it 'returns not found' do
-          delete :destroy, id: redirect_route.path
-
-          expect(response).to have_http_status(404)
-        end
-      end
     end
   end
 
@@ -224,41 +160,166 @@ describe GroupsController do
       expect(assigns(:group).errors).not_to be_empty
       expect(assigns(:group).path).not_to eq('new_path')
     end
+  end
+
+  describe '#ensure_canonical_path' do
+    before do
+      sign_in(user)
+    end
+
+    context 'for a GET request' do
+      context 'when requesting groups at the root path' do
+        before do
+          allow(request).to receive(:original_fullpath).and_return("/#{group_full_path}")
+          get :show, id: group_full_path
+        end
 
-    context 'when requesting the canonical path with different casing' do
-      it 'does not 404' do
-        post :update, id: group.to_param.upcase, group: { path: 'new_path' }
+        context 'when requesting the canonical path with different casing' do
+          let(:group_full_path) { group.to_param.upcase }
 
-        expect(response).not_to have_http_status(404)
+          it 'redirects to the correct casing' do
+            expect(response).to redirect_to(group)
+            expect(controller).not_to set_flash[:notice]
+          end
+        end
+
+        context 'when requesting a redirected path' do
+          let(:redirect_route) { group.redirect_routes.create(path: 'old-path') }
+          let(:group_full_path) { redirect_route.path }
+
+          it 'redirects to the canonical path' do
+            expect(response).to redirect_to(group)
+            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
+              expect(response).to redirect_to(group)
+              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
+              expect(response).to redirect_to(group)
+              expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
+            end
+          end
+        end
       end
 
-      it 'does not redirect to the correct casing' do
-        post :update, id: group.to_param.upcase, group: { path: 'new_path' }
+      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
 
-        expect(response).not_to redirect_to(group_path(group.to_param))
+            expect(response).to redirect_to(issues_group_path(group.to_param))
+            expect(controller).not_to set_flash[:notice]
+          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 :issues, id: redirect_route.path
+
+            expect(response).to redirect_to(issues_group_path(group.to_param))
+            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 :issues, id: redirect_route.path
+
+              expect(response).to redirect_to(issues_group_path(group.to_param))
+              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 :issues, id: redirect_route.path
+
+              expect(response).to redirect_to(issues_group_path(group.to_param))
+              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 :issues, id: redirect_route.path
+
+              expect(response).to redirect_to(issues_group_path(group.to_param))
+              expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
+            end
+          end
+        end
       end
     end
 
-    context 'when requesting a redirected path' do
-      let(:redirect_route) { group.redirect_routes.create(path: 'old-path') }
+    context 'for a POST request' do
+      context 'when requesting the canonical path with different casing' do
+        it 'does not 404' do
+          post :update, id: group.to_param.upcase, group: { path: 'new_path' }
+
+          expect(response).not_to have_http_status(404)
+        end
+
+        it 'does not redirect to the correct casing' do
+          post :update, id: group.to_param.upcase, group: { path: 'new_path' }
+
+          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 :update, id: redirect_route.path, group: { path: 'new_path' }
+        it 'returns not found' do
+          post :update, id: redirect_route.path, group: { path: 'new_path' }
 
-        expect(response).to have_http_status(404)
+          expect(response).to have_http_status(404)
+        end
       end
     end
-  end
 
-  describe 'ensure_canonical_path' do
-    context 'when the old group path is a substring of the scheme or host' do
-      let(:redirect_route) { group.redirect_routes.create(path: 'http') }
+    context 'for a DELETE request' do
+      context 'when requesting the canonical path with different casing' do
+        it 'does not 404' do
+          delete :destroy, id: group.to_param.upcase
 
-      it 'does not modify the requested host' do
-        get :issues, id: redirect_route.path
+          expect(response).not_to have_http_status(404)
+        end
 
-        expect(response).to redirect_to(issues_group_path(group.to_param))
-        expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
+        it 'does not redirect to the correct casing' do
+          delete :destroy, id: group.to_param.upcase
+
+          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
+          delete :destroy, id: redirect_route.path
+
+          expect(response).to have_http_status(404)
+        end
       end
     end
   end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index e230944d52e69a07afda7c5bf70dd19144f1fa5f..a8be6768a47ecccaa5ce4560ae550969a8da7816 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -169,27 +169,6 @@ describe ProjectsController do
       end
     end
 
-    context "when requested with case sensitive namespace and project path" do
-      context "when there is a match with the same casing" do
-        it "loads the project" do
-          get :show, namespace_id: public_project.namespace, id: public_project
-
-          expect(assigns(:project)).to eq(public_project)
-          expect(response).to have_http_status(200)
-        end
-      end
-
-      context "when there is a match with different casing" do
-        it "redirects to the normalized path" do
-          get :show, namespace_id: public_project.namespace, id: public_project.path.upcase
-
-          expect(assigns(:project)).to eq(public_project)
-          expect(response).to redirect_to("/#{public_project.full_path}")
-          expect(controller).not_to set_flash[:notice]
-        end
-      end
-    end
-
     context "when the url contains .atom" do
       let(:public_project_with_dot_atom) { build(:empty_project, :public, name: 'my.atom', path: 'my.atom') }
 
@@ -219,17 +198,6 @@ describe ProjectsController do
         expect(response).to redirect_to(namespace_project_path)
       end
     end
-
-    context 'when requesting a redirected path' do
-      let!(:redirect_route) { public_project.redirect_routes.create!(path: "foo/bar") }
-
-      it 'redirects to the canonical path' do
-        get :show, namespace_id: 'foo', id: 'bar'
-
-        expect(response).to redirect_to(public_project)
-        expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, public_project))
-      end
-    end
   end
 
   describe "#update" do
@@ -256,34 +224,6 @@ describe ProjectsController do
       expect(assigns(:repository).path).to eq(project.repository.path)
       expect(response).to have_http_status(302)
     end
-
-    context 'when requesting the canonical path' do
-      it "is case-insensitive" do
-        controller.instance_variable_set(:@project, project)
-
-        put :update,
-            namespace_id: 'FOo',
-            id: 'baR',
-            project: project_params
-
-        expect(project.repository.path).to include(new_path)
-        expect(assigns(:repository).path).to eq(project.repository.path)
-        expect(response).to have_http_status(302)
-      end
-    end
-
-    context 'when requesting a redirected path' do
-      let!(:redirect_route) { project.redirect_routes.create!(path: "foo/bar") }
-
-      it 'returns not found' do
-        put :update,
-            namespace_id: 'foo',
-            id: 'bar',
-            project: project_params
-
-        expect(response).to have_http_status(404)
-      end
-    end
   end
 
   describe "#destroy" do
@@ -319,31 +259,6 @@ describe ProjectsController do
         expect(merge_request.reload.state).to eq('closed')
       end
     end
-
-    context 'when requesting the canonical path' do
-      it "is case-insensitive" do
-        controller.instance_variable_set(:@project, project)
-        sign_in(admin)
-
-        orig_id = project.id
-        delete :destroy, namespace_id: project.namespace, id: project.path.upcase
-
-        expect { Project.find(orig_id) }.to raise_error(ActiveRecord::RecordNotFound)
-        expect(response).to have_http_status(302)
-        expect(response).to redirect_to(dashboard_projects_path)
-      end
-    end
-
-    context 'when requesting a redirected path' do
-      let!(:redirect_route) { project.redirect_routes.create!(path: "foo/bar") }
-
-      it 'returns not found' do
-        sign_in(admin)
-        delete :destroy, namespace_id: 'foo', id: 'bar'
-
-        expect(response).to have_http_status(404)
-      end
-    end
   end
 
   describe 'PUT #new_issue_address' do
@@ -465,17 +380,6 @@ describe ProjectsController do
       expect(parsed_body["Tags"]).to include("v1.0.0")
       expect(parsed_body["Commits"]).to include("123456")
     end
-
-    context 'when requesting a redirected path' do
-      let!(:redirect_route) { public_project.redirect_routes.create!(path: "foo/bar") }
-
-      it 'redirects to the canonical path' do
-        get :refs, namespace_id: 'foo', id: 'bar'
-
-        expect(response).to redirect_to(refs_namespace_project_path(namespace_id: public_project.namespace, id: public_project))
-        expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, public_project))
-      end
-    end
   end
 
   describe 'POST #preview_markdown' do
@@ -488,6 +392,109 @@ describe ProjectsController do
     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 "with exactly matching casing" do
+          it "loads the project" do
+            get :show, namespace_id: public_project.namespace, id: public_project
+
+            expect(assigns(:project)).to eq(public_project)
+            expect(response).to have_http_status(200)
+          end
+        end
+
+        context "with different casing" do
+          it "redirects to the normalized path" do
+            get :show, namespace_id: public_project.namespace, id: public_project.path.upcase
+
+            expect(assigns(:project)).to eq(public_project)
+            expect(response).to redirect_to("/#{public_project.full_path}")
+            expect(controller).not_to set_flash[:notice]
+          end
+        end
+      end
+
+      context 'when requesting a redirected path' do
+        let!(:redirect_route) { public_project.redirect_routes.create!(path: "foo/bar") }
+
+        it 'redirects to the canonical path' do
+          get :show, namespace_id: 'foo', id: 'bar'
+
+          expect(response).to redirect_to(public_project)
+          expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, public_project))
+        end
+
+        it 'redirects to the canonical path (testing non-show action)' do
+          get :refs, namespace_id: 'foo', id: 'bar'
+
+          expect(response).to redirect_to(refs_namespace_project_path(namespace_id: public_project.namespace, id: public_project))
+          expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, public_project))
+        end
+      end
+    end
+
+    context 'for a POST request' do
+      context 'when requesting the canonical path with different casing' do
+        it 'does not 404' do
+          post :toggle_star, namespace_id: public_project.namespace, id: public_project.path.upcase
+
+          expect(response).not_to have_http_status(404)
+        end
+
+        it 'does not redirect to the correct casing' do
+          post :toggle_star, namespace_id: public_project.namespace, id: public_project.path.upcase
+
+          expect(response).not_to have_http_status(301)
+        end
+      end
+
+      context 'when requesting a redirected path' do
+        let!(:redirect_route) { public_project.redirect_routes.create!(path: "foo/bar") }
+
+        it 'returns not found' do
+          post :toggle_star, namespace_id: 'foo', id: 'bar'
+
+          expect(response).to have_http_status(404)
+        end
+      end
+    end
+
+    context 'for a DELETE request' do
+      before do
+        sign_in(create(:admin))
+      end
+
+      context 'when requesting the canonical path with different casing' do
+        it 'does not 404' do
+          delete :destroy, namespace_id: project.namespace, id: project.path.upcase
+
+          expect(response).not_to have_http_status(404)
+        end
+
+        it 'does not redirect to the correct casing' do
+          delete :destroy, namespace_id: project.namespace, id: project.path.upcase
+
+          expect(response).not_to have_http_status(301)
+        end
+      end
+
+      context 'when requesting a redirected path' do
+        let!(:redirect_route) { project.redirect_routes.create!(path: "foo/bar") }
+
+        it 'returns not found' do
+          delete :destroy, namespace_id: 'foo', id: 'bar'
+
+          expect(response).to have_http_status(404)
+        end
+      end
+    end
+  end
+
   def project_moved_message(redirect_route, project)
     "Project '#{redirect_route.path}' was moved to '#{project.full_path}'. Please update any links and bookmarks that may still have the old path."
   end
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 1d61719f1d019e064248e085db81be432734578b..d33e2ba1e53587c550d6c1ab6986ef3b53b46284 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -53,40 +53,6 @@ describe UsersController do
       end
     end
 
-    context 'when requesting the canonical path' do
-      let(:user) { create(:user, username: 'CamelCaseUser') }
-
-      before { sign_in(user) }
-
-      context 'with exactly matching casing' do
-        it 'responds with success' do
-          get :show, username: user.username
-
-          expect(response).to be_success
-        end
-      end
-
-      context 'with different casing' do
-        it 'redirects to the correct casing' do
-          get :show, username: user.username.downcase
-
-          expect(response).to redirect_to(user)
-          expect(controller).not_to set_flash[:notice]
-        end
-      end
-    end
-
-    context 'when requesting a redirected path' do
-      let(:redirect_route) { user.namespace.redirect_routes.create(path: 'old-username') }
-
-      it 'redirects to the canonical path' do
-        get :show, username: redirect_route.path
-
-        expect(response).to redirect_to(user)
-        expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user))
-      end
-    end
-
     context 'when a user by that username does not exist' do
       context 'when logged out' do
         it 'redirects to login page' do
@@ -131,40 +97,6 @@ describe UsersController do
         expect(assigns(:contributions_calendar).projects.count).to eq(2)
       end
     end
-
-    context 'when requesting the canonical path' do
-      let(:user) { create(:user, username: 'CamelCaseUser') }
-
-      before { sign_in(user) }
-
-      context 'with exactly matching casing' do
-        it 'responds with success' do
-          get :calendar, username: user.username
-
-          expect(response).to be_success
-        end
-      end
-
-      context 'with different casing' do
-        it 'redirects to the correct casing' do
-          get :calendar, username: user.username.downcase
-
-          expect(response).to redirect_to(user_calendar_path(user))
-          expect(controller).not_to set_flash[:notice]
-        end
-      end
-    end
-
-    context 'when requesting a redirected path' do
-      let(:redirect_route) { user.namespace.redirect_routes.create(path: 'old-username') }
-
-      it 'redirects to the canonical path' do
-        get :calendar, username: redirect_route.path
-
-        expect(response).to redirect_to(user_calendar_path(user))
-        expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user))
-      end
-    end
   end
 
   describe 'GET #calendar_activities' do
@@ -187,38 +119,6 @@ describe UsersController do
       get :calendar_activities, username: user.username
       expect(response).to render_template('calendar_activities')
     end
-
-    context 'when requesting the canonical path' do
-      let(:user) { create(:user, username: 'CamelCaseUser') }
-
-      context 'with exactly matching casing' do
-        it 'responds with success' do
-          get :calendar_activities, username: user.username
-
-          expect(response).to be_success
-        end
-      end
-
-      context 'with different casing' do
-        it 'redirects to the correct casing' do
-          get :calendar_activities, username: user.username.downcase
-
-          expect(response).to redirect_to(user_calendar_activities_path(user))
-          expect(controller).not_to set_flash[:notice]
-        end
-      end
-    end
-
-    context 'when requesting a redirected path' do
-      let(:redirect_route) { user.namespace.redirect_routes.create(path: 'old-username') }
-
-      it 'redirects to the canonical path' do
-        get :calendar_activities, username: redirect_route.path
-
-        expect(response).to redirect_to(user_calendar_activities_path(user))
-        expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user))
-      end
-    end
   end
 
   describe 'GET #snippets' do
@@ -241,38 +141,6 @@ describe UsersController do
         expect(JSON.parse(response.body)).to have_key('html')
       end
     end
-
-    context 'when requesting the canonical path' do
-      let(:user) { create(:user, username: 'CamelCaseUser') }
-
-      context 'with exactly matching casing' do
-        it 'responds with success' do
-          get :snippets, username: user.username
-
-          expect(response).to be_success
-        end
-      end
-
-      context 'with different casing' do
-        it 'redirects to the correct casing' do
-          get :snippets, username: user.username.downcase
-
-          expect(response).to redirect_to(user_snippets_path(user))
-          expect(controller).not_to set_flash[:notice]
-        end
-      end
-    end
-
-    context 'when requesting a redirected path' do
-      let(:redirect_route) { user.namespace.redirect_routes.create(path: 'old-username') }
-
-      it 'redirects to the canonical path' do
-        get :snippets, username: redirect_route.path
-
-        expect(response).to redirect_to(user_snippets_path(user))
-        expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user))
-      end
-    end
   end
 
   describe 'GET #exists' do
@@ -321,6 +189,127 @@ describe UsersController do
     end
   end
 
+  describe '#ensure_canonical_path' do
+    before do
+      sign_in(user)
+    end
+
+    context 'for a GET request' do
+      context 'when requesting users at the root path' do
+        context 'when requesting the canonical path' do
+          let(:user) { create(:user, username: 'CamelCaseUser') }
+
+          context 'with exactly matching casing' do
+            it 'responds with success' do
+              get :show, username: user.username
+
+              expect(response).to be_success
+            end
+          end
+
+          context 'with different casing' do
+            it 'redirects to the correct casing' do
+              get :show, username: user.username.downcase
+
+              expect(response).to redirect_to(user)
+              expect(controller).not_to set_flash[:notice]
+            end
+          end
+        end
+
+        context 'when requesting a redirected path' do
+          let(:redirect_route) { user.namespace.redirect_routes.create(path: 'old-path') }
+
+          it 'redirects to the canonical path' do
+            get :show, username: redirect_route.path
+
+            expect(response).to redirect_to(user)
+            expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user))
+          end
+
+          context 'when the old path is a substring of the scheme or host' do
+            let(:redirect_route) { user.namespace.redirect_routes.create(path: 'http') }
+
+            it 'does not modify the requested host' do
+              get :show, username: redirect_route.path
+
+              expect(response).to redirect_to(user)
+              expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user))
+            end
+          end
+
+          context 'when the old path is substring of users' do
+            let(:redirect_route) { user.namespace.redirect_routes.create(path: 'ser') }
+
+            it 'redirects to the canonical path' do
+              get :show, username: redirect_route.path
+
+              expect(response).to redirect_to(user)
+              expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user))
+            end
+          end
+        end
+      end
+
+      context 'when requesting users under the /users path' do
+        context 'when requesting the canonical path' do
+          let(:user) { create(:user, username: 'CamelCaseUser') }
+
+          context 'with exactly matching casing' do
+            it 'responds with success' do
+              get :projects, username: user.username
+
+              expect(response).to be_success
+            end
+          end
+
+          context 'with different casing' do
+            it 'redirects to the correct casing' do
+              get :projects, username: user.username.downcase
+
+              expect(response).to redirect_to(user_projects_path(user))
+              expect(controller).not_to set_flash[:notice]
+            end
+          end
+        end
+
+        context 'when requesting a redirected path' do
+          let(:redirect_route) { user.namespace.redirect_routes.create(path: 'old-path') }
+
+          it 'redirects to the canonical path' do
+            get :projects, username: redirect_route.path
+
+            expect(response).to redirect_to(user_projects_path(user))
+            expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user))
+          end
+
+          context 'when the old path is a substring of the scheme or host' do
+            let(:redirect_route) { user.namespace.redirect_routes.create(path: 'http') }
+
+            it 'does not modify the requested host' do
+              get :projects, username: redirect_route.path
+
+              expect(response).to redirect_to(user_projects_path(user))
+              expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user))
+            end
+          end
+
+          context 'when the old path is substring of users' do
+            let(:redirect_route) { user.namespace.redirect_routes.create(path: 'ser') }
+
+            # I.e. /users/ser should not become /ufoos/ser
+            it 'does not modify the /users part of the path' do
+              get :projects, username: redirect_route.path
+
+              expect(response).to redirect_to(user_projects_path(user))
+              expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user))
+            end
+          end
+        end
+      end
+    end
+  end
+
   def user_moved_message(redirect_route, user)
     "User '#{redirect_route.path}' was moved to '#{user.full_path}'. Please update any links and bookmarks that may still have the old path."
   end