From 6683fdcfb0ae4ceb368b6f5f63dde0a10a4a3e1b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 14 Nov 2016 16:55:31 +0200 Subject: Add nested groups support to the routing Signed-off-by: Dmitriy Zaporozhets --- app/controllers/application_controller.rb | 8 + app/models/project.rb | 1 + app/models/user.rb | 6 +- app/validators/namespace_validator.rb | 22 +- app/validators/project_path_validator.rb | 36 + app/views/projects/wikis/_nav.html.haml | 2 +- .../unreleased/dz-allow-nested-group-routing.yml | 4 + config/routes.rb | 20 +- config/routes/git_http.rb | 68 +- config/routes/group.rb | 20 +- config/routes/project.rb | 58 +- config/routes/repository.rb | 122 +-- config/routes/user.rb | 3 + config/routes/wiki.rb | 27 +- features/steps/project/labels.rb | 2 - features/steps/shared/diff_note.rb | 5 + features/steps/shared/note.rb | 4 + lib/constraints/constrainer_helper.rb | 15 - lib/constraints/group_url_constrainer.rb | 20 +- lib/constraints/project_url_constrainer.rb | 13 + lib/constraints/user_url_constrainer.rb | 12 +- lib/gitlab/regex.rb | 14 +- spec/controllers/application_controller_spec.rb | 23 +- spec/lib/constraints/constrainer_helper_spec.rb | 20 - spec/lib/constraints/group_url_constrainer_spec.rb | 20 +- .../constraints/project_url_constrainer_spec.rb | 32 + spec/lib/constraints/user_url_constrainer_spec.rb | 21 +- spec/models/user_spec.rb | 11 + spec/routing/project_routing_spec.rb | 1034 ++++++++++---------- spec/routing/routing_spec.rb | 16 +- 30 files changed, 904 insertions(+), 755 deletions(-) create mode 100644 app/validators/project_path_validator.rb create mode 100644 changelogs/unreleased/dz-allow-nested-group-routing.yml delete mode 100644 lib/constraints/constrainer_helper.rb create mode 100644 lib/constraints/project_url_constrainer.rb delete mode 100644 spec/lib/constraints/constrainer_helper_spec.rb create mode 100644 spec/lib/constraints/project_url_constrainer_spec.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 517ad4f03f3..6f3ae9bf8eb 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -49,6 +49,14 @@ class ApplicationController < ActionController::Base render_404 end + def route_not_found + if current_user + not_found + else + redirect_to new_user_session_path + end + end + protected # This filter handles both private tokens and personal access tokens diff --git a/app/models/project.rb b/app/models/project.rb index f8a54324341..4976d3ab680 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -174,6 +174,7 @@ class Project < ActiveRecord::Base message: Gitlab::Regex.project_name_regex_message } validates :path, presence: true, + project_path: true, length: { within: 0..255 }, format: { with: Gitlab::Regex.project_path_regex, message: Gitlab::Regex.project_path_regex_message } diff --git a/app/models/user.rb b/app/models/user.rb index 29fb849940a..e3d2c57e47f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -291,8 +291,12 @@ class User < ActiveRecord::Base end end + def find_by_username(username) + iwhere(username: username).take + end + def find_by_username!(username) - find_by!('lower(username) = ?', username.downcase) + iwhere(username: username).take! end def find_by_personal_access_token(token_string) diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb index 2821ecf0a88..eb3ed31b65b 100644 --- a/app/validators/namespace_validator.rb +++ b/app/validators/namespace_validator.rb @@ -35,8 +35,22 @@ class NamespaceValidator < ActiveModel::EachValidator users ].freeze + def self.valid?(value) + !reserved?(value) && follow_format?(value) + end + + def self.reserved?(value) + RESERVED.include?(value) + end + + def self.follow_format?(value) + value =~ Gitlab::Regex.namespace_regex + end + + delegate :reserved?, :follow_format?, to: :class + def validate_each(record, attribute, value) - unless value =~ Gitlab::Regex.namespace_regex + unless follow_format?(value) record.errors.add(attribute, Gitlab::Regex.namespace_regex_message) end @@ -44,10 +58,4 @@ class NamespaceValidator < ActiveModel::EachValidator record.errors.add(attribute, "#{value} is a reserved name") end end - - private - - def reserved?(value) - RESERVED.include?(value) - end end diff --git a/app/validators/project_path_validator.rb b/app/validators/project_path_validator.rb new file mode 100644 index 00000000000..927c67b65b0 --- /dev/null +++ b/app/validators/project_path_validator.rb @@ -0,0 +1,36 @@ +# ProjectPathValidator +# +# Custom validator for GitLab project path values. +# +# Values are checked for formatting and exclusion from a list of reserved path +# names. +class ProjectPathValidator < ActiveModel::EachValidator + # All project routes with wildcard argument must be listed here. + # Otherwise it can lead to routing issues when route considered as project name. + # + # Example: + # /group/project/tree/deploy_keys + # + # without tree as reserved name routing can match 'group/project' as group name, + # 'tree' as project name and 'deploy_keys' as route. + # + RESERVED = (NamespaceValidator::RESERVED + + %w[tree commits wikis new edit create update logs_tree + preview blob blame raw files create_dir find_file]).freeze + + def self.valid?(value) + !reserved?(value) + end + + def self.reserved?(value) + RESERVED.include?(value) + end + + delegate :reserved?, to: :class + + def validate_each(record, attribute, value) + if reserved?(value) + record.errors.add(attribute, "#{value} is a reserved name") + end + end +end diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml index 09c4411d67e..afdef70e1cf 100644 --- a/app/views/projects/wikis/_nav.html.haml +++ b/app/views/projects/wikis/_nav.html.haml @@ -7,7 +7,7 @@ = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) = nav_link(path: 'wikis#pages') do - = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project) + = link_to 'Pages', namespace_project_wikis_pages_path(@project.namespace, @project) = nav_link(path: 'wikis#git_access') do = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do diff --git a/changelogs/unreleased/dz-allow-nested-group-routing.yml b/changelogs/unreleased/dz-allow-nested-group-routing.yml new file mode 100644 index 00000000000..9d8e6e17914 --- /dev/null +++ b/changelogs/unreleased/dz-allow-nested-group-routing.yml @@ -0,0 +1,4 @@ +--- +title: Add nested groups support to the routing +merge_request: 7459 +author: diff --git a/config/routes.rb b/config/routes.rb index 7bf6c03e69b..03b47261e7e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,7 @@ require 'sidekiq/web' require 'sidekiq/cron/web' require 'api/api' +require 'constraints/group_url_constrainer' Rails.application.routes.draw do concern :access_requestable do @@ -78,10 +79,21 @@ Rails.application.routes.draw do draw :user draw :project - # Get all keys of user - get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } - root to: "root#index" - get '*unmatched_route', to: 'application#not_found' + # Since group show page is wildcard routing + # we want all other routing to be checked before matching this one + constraints(GroupUrlConstrainer.new) do + scope(path: '*id', + as: :group, + constraints: { id: Gitlab::Regex.namespace_route_regex, format: /(html|json|atom)/ }, + controller: :groups) do + get '/', action: :show + patch '/', action: :update + put '/', action: :update + delete '/', action: :destroy + end + end + + get '*unmatched_route', to: 'application#route_not_found' end diff --git a/config/routes/git_http.rb b/config/routes/git_http.rb index 03adc4815f3..42d874eeebc 100644 --- a/config/routes/git_http.rb +++ b/config/routes/git_http.rb @@ -1,37 +1,47 @@ -scope constraints: { id: /.+\.git/, format: nil } do - # Git HTTP clients ('git clone' etc.) - get '/info/refs', to: 'git_http#info_refs' - post '/git-upload-pack', to: 'git_http#git_upload_pack' - post '/git-receive-pack', to: 'git_http#git_receive_pack' +scope(path: '*namespace_id/:project_id', constraints: { format: nil }) do + scope(constraints: { project_id: Gitlab::Regex.project_git_route_regex }, module: :projects) do + # Git HTTP clients ('git clone' etc.) + scope(controller: :git_http) do + get '/info/refs', action: :info_refs + post '/git-upload-pack', action: :git_upload_pack + post '/git-receive-pack', action: :git_receive_pack + end - # Git LFS API (metadata) - post '/info/lfs/objects/batch', to: 'lfs_api#batch' - post '/info/lfs/objects', to: 'lfs_api#deprecated' - get '/info/lfs/objects/*oid', to: 'lfs_api#deprecated' + # Git LFS API (metadata) + scope(path: 'info/lfs/objects', controller: :lfs_api) do + post :batch + post '/', action: :deprecated + get '/*oid', action: :deprecated + end - # GitLab LFS object storage - scope constraints: { oid: /[a-f0-9]{64}/ } do - get '/gitlab-lfs/objects/*oid', to: 'lfs_storage#download' + # GitLab LFS object storage + scope(path: 'gitlab-lfs/objects/*oid', controller: :lfs_storage, constraints: { oid: /[a-f0-9]{64}/ }) do + get '/', action: :download - scope constraints: { size: /[0-9]+/ } do - put '/gitlab-lfs/objects/*oid/*size/authorize', to: 'lfs_storage#upload_authorize' - put '/gitlab-lfs/objects/*oid/*size', to: 'lfs_storage#upload_finalize' + scope constraints: { size: /[0-9]+/ } do + put '/*size/authorize', action: :upload_authorize + put '/*size', action: :upload_finalize + end end end -end -# Allow /info/refs, /info/refs?service=git-upload-pack, and -# /info/refs?service=git-receive-pack, but nothing else. -# -git_http_handshake = lambda do |request| - request.query_string.blank? || - request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/) -end + # Redirect /group/project/info/refs to /group/project.git/info/refs + scope(constraints: { project_id: Gitlab::Regex.project_route_regex }) do + # Allow /info/refs, /info/refs?service=git-upload-pack, and + # /info/refs?service=git-receive-pack, but nothing else. + # + git_http_handshake = lambda do |request| + ProjectUrlConstrainer.new.matches?(request) && + (request.query_string.blank? || + request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/)) + end -ref_redirect = redirect do |params, request| - path = "#{params[:namespace_id]}/#{params[:project_id]}.git/info/refs" - path << "?#{request.query_string}" unless request.query_string.blank? - path -end + ref_redirect = redirect do |params, request| + path = "#{params[:namespace_id]}/#{params[:project_id]}.git/info/refs" + path << "?#{request.query_string}" unless request.query_string.blank? + path + end -get '/info/refs', constraints: git_http_handshake, to: ref_redirect + get '/info/refs', constraints: git_http_handshake, to: ref_redirect + end +end diff --git a/config/routes/group.rb b/config/routes/group.rb index a3a001178b4..9fe72990994 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -1,20 +1,6 @@ -require 'constraints/group_url_constrainer' - -constraints(GroupUrlConstrainer.new) do - scope(path: ':id', - as: :group, - constraints: { id: Gitlab::Regex.namespace_route_regex }, - controller: :groups) do - get '/', action: :show - patch '/', action: :update - put '/', action: :update - delete '/', action: :destroy - end -end - resources :groups, only: [:index, :new, :create] -scope(path: 'groups/:id', +scope(path: 'groups/*id', controller: :groups, constraints: { id: Gitlab::Regex.namespace_route_regex }) do get :edit, as: :edit_group @@ -24,7 +10,7 @@ scope(path: 'groups/:id', get :activity, as: :activity_group end -scope(path: 'groups/:group_id', +scope(path: 'groups/*group_id', module: :groups, as: :group, constraints: { group_id: Gitlab::Regex.namespace_route_regex }) do @@ -42,4 +28,4 @@ scope(path: 'groups/:group_id', end # Must be last route in this file -get 'groups/:id' => 'groups#show', as: :group_canonical, constraints: { id: Gitlab::Regex.namespace_route_regex } +get 'groups/*id' => 'groups#show', as: :group_canonical, constraints: { id: Gitlab::Regex.namespace_route_regex } diff --git a/config/routes/project.rb b/config/routes/project.rb index d6eae1c9fce..1336484a399 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -1,28 +1,15 @@ -resources :projects, constraints: { id: /[^\/]+/ }, only: [:index, :new, :create] - -resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do - resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+(? 'profiles/keys#get_keys', constraints: { username: Gitlab::Regex.namespace_route_regex } + scope(path: ':username', as: :user, constraints: { username: Gitlab::Regex.namespace_route_regex }, diff --git a/config/routes/wiki.rb b/config/routes/wiki.rb index ecd4d395d66..dad746d59a1 100644 --- a/config/routes/wiki.rb +++ b/config/routes/wiki.rb @@ -1,16 +1,19 @@ WIKI_SLUG_ID = { id: /\S+/ } unless defined? WIKI_SLUG_ID -scope do - # Order matters to give priority to these matches - get '/wikis/git_access', to: 'wikis#git_access' - get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages' - post '/wikis', to: 'wikis#create' +scope(controller: :wikis) do + scope(path: 'wikis', as: :wikis) do + get :git_access + get :pages + get '/', to: redirect('/%{namespace_id}/%{project_id}/wikis/home') + post '/', to: 'wikis#create' + end - get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID - get '/wikis/*id/edit', to: 'wikis#edit', as: 'wiki_edit', constraints: WIKI_SLUG_ID - - get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID - delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID - put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID - post '/wikis/*id/preview_markdown', to: 'wikis#preview_markdown', constraints: WIKI_SLUG_ID, as: 'wiki_preview_markdown' + scope(path: 'wikis/*id', as: :wiki, constraints: WIKI_SLUG_ID, format: false) do + get :edit + get :history + post :preview_markdown + get '/', action: :show + put '/', action: :update + delete '/', action: :destroy + end end diff --git a/features/steps/project/labels.rb b/features/steps/project/labels.rb index 118ffef4774..dbeb07c78db 100644 --- a/features/steps/project/labels.rb +++ b/features/steps/project/labels.rb @@ -2,9 +2,7 @@ class Spinach::Features::Labels < Spinach::FeatureSteps include SharedAuthentication include SharedIssuable include SharedProject - include SharedNote include SharedPaths - include SharedMarkdown step 'And I visit project "Shop" labels page' do visit namespace_project_labels_path(project.namespace, project) diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb index 35b71599708..11fa85ed2fe 100644 --- a/features/steps/shared/diff_note.rb +++ b/features/steps/shared/diff_note.rb @@ -1,6 +1,11 @@ module SharedDiffNote include Spinach::DSL include RepoHelpers + include WaitForAjax + + after do + wait_for_ajax if javascript_test? + end step 'I cancel the diff comment' do page.within(diff_file_selector) do diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb index 9dc1fc41b3b..1870f6bc0c3 100644 --- a/features/steps/shared/note.rb +++ b/features/steps/shared/note.rb @@ -2,6 +2,10 @@ module SharedNote include Spinach::DSL include WaitForAjax + after do + wait_for_ajax if javascript_test? + end + step 'I delete a comment' do page.within('.main-notes-list') do find('.note').hover diff --git a/lib/constraints/constrainer_helper.rb b/lib/constraints/constrainer_helper.rb deleted file mode 100644 index ab07a6793d9..00000000000 --- a/lib/constraints/constrainer_helper.rb +++ /dev/null @@ -1,15 +0,0 @@ -module ConstrainerHelper - def extract_resource_path(path) - id = path.dup - id.sub!(/\A#{relative_url_root}/, '') if relative_url_root - id.sub(/\A\/+/, '').sub(/\/+\z/, '').sub(/.atom\z/, '') - end - - private - - def relative_url_root - if defined?(Gitlab::Application.config.relative_url_root) - Gitlab::Application.config.relative_url_root - end - end -end diff --git a/lib/constraints/group_url_constrainer.rb b/lib/constraints/group_url_constrainer.rb index 2af6e1a11c8..5711d96a586 100644 --- a/lib/constraints/group_url_constrainer.rb +++ b/lib/constraints/group_url_constrainer.rb @@ -1,15 +1,17 @@ -require_relative 'constrainer_helper' - class GroupUrlConstrainer - include ConstrainerHelper - def matches?(request) - id = extract_resource_path(request.path) + id = request.params[:id] + + return false unless valid?(id) + + Group.find_by(path: id).present? + end + + private - if id =~ Gitlab::Regex.namespace_regex - Group.find_by(path: id).present? - else - false + def valid?(id) + id.split('/').all? do |namespace| + NamespaceValidator.valid?(namespace) end end end diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb new file mode 100644 index 00000000000..730b05bed97 --- /dev/null +++ b/lib/constraints/project_url_constrainer.rb @@ -0,0 +1,13 @@ +class ProjectUrlConstrainer + def matches?(request) + namespace_path = request.params[:namespace_id] + project_path = request.params[:project_id] || request.params[:id] + full_path = namespace_path + '/' + project_path + + unless ProjectPathValidator.valid?(project_path) + return false + end + + Project.find_with_namespace(full_path).present? + end +end diff --git a/lib/constraints/user_url_constrainer.rb b/lib/constraints/user_url_constrainer.rb index 4d722ad5af2..9ab5bcb12ff 100644 --- a/lib/constraints/user_url_constrainer.rb +++ b/lib/constraints/user_url_constrainer.rb @@ -1,15 +1,5 @@ -require_relative 'constrainer_helper' - class UserUrlConstrainer - include ConstrainerHelper - def matches?(request) - id = extract_resource_path(request.path) - - if id =~ Gitlab::Regex.namespace_regex - User.find_by('lower(username) = ?', id.downcase).present? - else - false - end + User.find_by_username(request.params[:username]).present? end end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index c12358ceef4..a06cf6a989c 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -8,8 +8,10 @@ module Gitlab # allow non-regex validatiions, etc), `NAMESPACE_REGEX_STR_SIMPLE` serves as a Javascript-compatible version of # `NAMESPACE_REGEX_STR`, with the negative lookbehind assertion removed. This means that the client-side validation # will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation. - NAMESPACE_REGEX_STR_SIMPLE = '[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze + PATH_REGEX_STR = '[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*'.freeze + NAMESPACE_REGEX_STR_SIMPLE = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze NAMESPACE_REGEX_STR = '(?:' + NAMESPACE_REGEX_STR_SIMPLE + ')(?/.+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?/html/} -# project_files GET /:namespace_id/:project_id/files/*id(.:format) projects/find_file#list {:id=>/(?:[^.]|\.(?!json$))+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?/json/} -describe Projects::FindFileController, 'routing' do - it 'to #show' do - expect(get('/gitlab/gitlabhq/find_file/master')).to route_to('projects/find_file#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') - end - - it 'to #list' do - expect(get('/gitlab/gitlabhq/files/master.json')).to route_to('projects/find_file#list', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') - end -end - -describe Projects::BlobController, 'routing' do - it 'to #edit' do - expect(get('/gitlab/gitlabhq/edit/master/app/models/project.rb')).to( - route_to('projects/blob#edit', - namespace_id: 'gitlab', project_id: 'gitlabhq', - id: 'master/app/models/project.rb')) - end - - it 'to #preview' do - expect(post('/gitlab/gitlabhq/preview/master/app/models/project.rb')).to( - route_to('projects/blob#preview', - namespace_id: 'gitlab', project_id: 'gitlabhq', - id: 'master/app/models/project.rb')) - end -end - -# project_compare_index GET /:project_id/compare(.:format) compare#index {id: /[^\/]+/, project_id: /[^\/]+/} -# POST /:project_id/compare(.:format) compare#create {id: /[^\/]+/, project_id: /[^\/]+/} -# project_compare /:project_id/compare/:from...:to(.:format) compare#show {from: /.+/, to: /.+/, id: /[^\/]+/, project_id: /[^\/]+/} -describe Projects::CompareController, 'routing' do - it 'to #index' do - expect(get('/gitlab/gitlabhq/compare')).to route_to('projects/compare#index', namespace_id: 'gitlab', project_id: 'gitlabhq') - end - - it 'to #compare' do - expect(post('/gitlab/gitlabhq/compare')).to route_to('projects/compare#create', namespace_id: 'gitlab', project_id: 'gitlabhq') - end - - it 'to #show' do - expect(get('/gitlab/gitlabhq/compare/master...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'master', to: 'stable') - expect(get('/gitlab/gitlabhq/compare/issue/1234...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'issue/1234', to: 'stable') - end -end - -describe Projects::NetworkController, 'routing' do - it 'to #show' do - expect(get('/gitlab/gitlabhq/network/master')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') - expect(get('/gitlab/gitlabhq/network/ends-with.json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json') - expect(get('/gitlab/gitlabhq/network/master?format=json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') - end -end - -describe Projects::GraphsController, 'routing' do - it 'to #show' do - expect(get('/gitlab/gitlabhq/graphs/master')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') - expect(get('/gitlab/gitlabhq/graphs/ends-with.json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json') - expect(get('/gitlab/gitlabhq/graphs/master?format=json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') - end -end - -describe Projects::ForksController, 'routing' do - it 'to #new' do - expect(get('/gitlab/gitlabhq/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq') - end - - it 'to #create' do - expect(post('/gitlab/gitlabhq/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq') - end -end - -# project_avatar DELETE /project/avatar(.:format) projects/avatars#destroy -describe Projects::AvatarsController, 'routing' do - it 'to #destroy' do - expect(delete('/gitlab/gitlabhq/avatar')).to route_to( - 'projects/avatars#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq') +describe 'project routing' do + before do + allow(Project).to receive(:find_with_namespace).and_return(false) + allow(Project).to receive(:find_with_namespace).with('gitlab/gitlabhq').and_return(true) + end + + # Shared examples for a resource inside a Project + # + # By default it tests all the default REST actions: index, create, new, edit, + # show, update, and destroy. You can remove actions by customizing the + # `actions` variable. + # + # It also expects a `controller` variable to be available which defines both + # the path to the resource as well as the controller name. + # + # Examples + # + # # Default behavior + # it_behaves_like 'RESTful project resources' do + # let(:controller) { 'issues' } + # end + # + # # Customizing actions + # it_behaves_like 'RESTful project resources' do + # let(:actions) { [:index] } + # let(:controller) { 'issues' } + # end + shared_examples 'RESTful project resources' do + let(:actions) { [:index, :create, :new, :edit, :show, :update, :destroy] } + + it 'to #index' do + expect(get("/gitlab/gitlabhq/#{controller}")).to route_to("projects/#{controller}#index", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:index) + end + + it 'to #create' do + expect(post("/gitlab/gitlabhq/#{controller}")).to route_to("projects/#{controller}#create", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:create) + end + + it 'to #new' do + expect(get("/gitlab/gitlabhq/#{controller}/new")).to route_to("projects/#{controller}#new", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:new) + end + + it 'to #edit' do + expect(get("/gitlab/gitlabhq/#{controller}/1/edit")).to route_to("projects/#{controller}#edit", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:edit) + end + + it 'to #show' do + expect(get("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#show", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:show) + end + + it 'to #update' do + expect(put("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#update", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:update) + end + + it 'to #destroy' do + expect(delete("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#destroy", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:destroy) + end + end + + # projects POST /projects(.:format) projects#create + # new_project GET /projects/new(.:format) projects#new + # files_project GET /:id/files(.:format) projects#files + # edit_project GET /:id/edit(.:format) projects#edit + # project GET /:id(.:format) projects#show + # PUT /:id(.:format) projects#update + # DELETE /:id(.:format) projects#destroy + # preview_markdown_project POST /:id/preview_markdown(.:format) projects#preview_markdown + describe ProjectsController, 'routing' do + it 'to #create' do + expect(post('/projects')).to route_to('projects#create') + end + + it 'to #new' do + expect(get('/projects/new')).to route_to('projects#new') + end + + it 'to #edit' do + expect(get('/gitlab/gitlabhq/edit')).to route_to('projects#edit', namespace_id: 'gitlab', id: 'gitlabhq') + end + + it 'to #autocomplete_sources' do + expect(get('/gitlab/gitlabhq/autocomplete_sources')).to route_to('projects#autocomplete_sources', namespace_id: 'gitlab', id: 'gitlabhq') + end + + describe 'to #show' do + context 'regular name' do + it { expect(get('/gitlab/gitlabhq')).to route_to('projects#show', namespace_id: 'gitlab', id: 'gitlabhq') } + end + + context 'name with dot' do + before { allow(Project).to receive(:find_with_namespace).with('gitlab/gitlabhq.keys').and_return(true) } + + it { expect(get('/gitlab/gitlabhq.keys')).to route_to('projects#show', namespace_id: 'gitlab', id: 'gitlabhq.keys') } + end + + context 'with nested group' do + before { allow(Project).to receive(:find_with_namespace).with('gitlab/subgroup/gitlabhq').and_return(true) } + + it { expect(get('/gitlab/subgroup/gitlabhq')).to route_to('projects#show', namespace_id: 'gitlab/subgroup', id: 'gitlabhq') } + end + end + + it 'to #update' do + expect(put('/gitlab/gitlabhq')).to route_to('projects#update', namespace_id: 'gitlab', id: 'gitlabhq') + end + + it 'to #destroy' do + expect(delete('/gitlab/gitlabhq')).to route_to('projects#destroy', namespace_id: 'gitlab', id: 'gitlabhq') + end + + it 'to #preview_markdown' do + expect(post('/gitlab/gitlabhq/preview_markdown')).to( + route_to('projects#preview_markdown', namespace_id: 'gitlab', id: 'gitlabhq') + ) + end + end + + # pages_project_wikis GET /:project_id/wikis/pages(.:format) projects/wikis#pages + # history_project_wiki GET /:project_id/wikis/:id/history(.:format) projects/wikis#history + # project_wikis POST /:project_id/wikis(.:format) projects/wikis#create + # edit_project_wiki GET /:project_id/wikis/:id/edit(.:format) projects/wikis#edit + # project_wiki GET /:project_id/wikis/:id(.:format) projects/wikis#show + # DELETE /:project_id/wikis/:id(.:format) projects/wikis#destroy + describe Projects::WikisController, 'routing' do + it 'to #pages' do + expect(get('/gitlab/gitlabhq/wikis/pages')).to route_to('projects/wikis#pages', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #history' do + expect(get('/gitlab/gitlabhq/wikis/1/history')).to route_to('projects/wikis#history', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it_behaves_like 'RESTful project resources' do + let(:actions) { [:create, :edit, :show, :destroy] } + let(:controller) { 'wikis' } + end + end + + # branches_project_repository GET /:project_id/repository/branches(.:format) projects/repositories#branches + # tags_project_repository GET /:project_id/repository/tags(.:format) projects/repositories#tags + # archive_project_repository GET /:project_id/repository/archive(.:format) projects/repositories#archive + # edit_project_repository GET /:project_id/repository/edit(.:format) projects/repositories#edit + describe Projects::RepositoriesController, 'routing' do + it 'to #archive' do + expect(get('/gitlab/gitlabhq/repository/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #archive format:zip' do + expect(get('/gitlab/gitlabhq/repository/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip') + end + + it 'to #archive format:tar.bz2' do + expect(get('/gitlab/gitlabhq/repository/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2') + end + end + + describe Projects::BranchesController, 'routing' do + it 'to #branches' do + expect(get('/gitlab/gitlabhq/branches')).to route_to('projects/branches#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(delete('/gitlab/gitlabhq/branches/feature%2345')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') + expect(delete('/gitlab/gitlabhq/branches/feature%2B45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') + expect(delete('/gitlab/gitlabhq/branches/feature@45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') + expect(delete('/gitlab/gitlabhq/branches/feature%2345/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz') + expect(delete('/gitlab/gitlabhq/branches/feature%2B45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz') + expect(delete('/gitlab/gitlabhq/branches/feature@45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz') + end + end + + describe Projects::TagsController, 'routing' do + it 'to #tags' do + expect(get('/gitlab/gitlabhq/tags')).to route_to('projects/tags#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(delete('/gitlab/gitlabhq/tags/feature%2345')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') + expect(delete('/gitlab/gitlabhq/tags/feature%2B45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') + expect(delete('/gitlab/gitlabhq/tags/feature@45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') + expect(delete('/gitlab/gitlabhq/tags/feature%2345/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz') + expect(delete('/gitlab/gitlabhq/tags/feature%2B45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz') + expect(delete('/gitlab/gitlabhq/tags/feature@45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz') + end + end + + # project_deploy_keys GET /:project_id/deploy_keys(.:format) deploy_keys#index + # POST /:project_id/deploy_keys(.:format) deploy_keys#create + # new_project_deploy_key GET /:project_id/deploy_keys/new(.:format) deploy_keys#new + # project_deploy_key GET /:project_id/deploy_keys/:id(.:format) deploy_keys#show + # DELETE /:project_id/deploy_keys/:id(.:format) deploy_keys#destroy + describe Projects::DeployKeysController, 'routing' do + it_behaves_like 'RESTful project resources' do + let(:actions) { [:index, :new, :create] } + let(:controller) { 'deploy_keys' } + end + end + + # project_protected_branches GET /:project_id/protected_branches(.:format) protected_branches#index + # POST /:project_id/protected_branches(.:format) protected_branches#create + # project_protected_branch DELETE /:project_id/protected_branches/:id(.:format) protected_branches#destroy + describe Projects::ProtectedBranchesController, 'routing' do + it_behaves_like 'RESTful project resources' do + let(:actions) { [:index, :create, :destroy] } + let(:controller) { 'protected_branches' } + end + end + + # switch_project_refs GET /:project_id/refs/switch(.:format) refs#switch + # logs_tree_project_ref GET /:project_id/refs/:id/logs_tree(.:format) refs#logs_tree + # logs_file_project_ref GET /:project_id/refs/:id/logs_tree/:path(.:format) refs#logs_tree + describe Projects::RefsController, 'routing' do + it 'to #switch' do + expect(get('/gitlab/gitlabhq/refs/switch')).to route_to('projects/refs#switch', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #logs_tree' do + expect(get('/gitlab/gitlabhq/refs/stable/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable') + expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') + expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') + expect(get('/gitlab/gitlabhq/refs/feature@45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') + expect(get('/gitlab/gitlabhq/refs/stable/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz') + expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45', path: 'foo/bar/baz') + expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45', path: 'foo/bar/baz') + expect(get('/gitlab/gitlabhq/refs/feature@45/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45', path: 'foo/bar/baz') + expect(get('/gitlab/gitlabhq/refs/stable/logs_tree/files.scss')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable', path: 'files.scss') + end + end + + # diffs_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/diffs(.:format) projects/merge_requests#diffs + # commits_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/commits(.:format) projects/merge_requests#commits + # merge_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/merge(.:format) projects/merge_requests#merge + # merge_check_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/merge_check(.:format) projects/merge_requests#merge_check + # ci_status_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/ci_status(.:format) projects/merge_requests#ci_status + # toggle_subscription_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/toggle_subscription(.:format) projects/merge_requests#toggle_subscription + # branch_from_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_from(.:format) projects/merge_requests#branch_from + # branch_to_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_to(.:format) projects/merge_requests#branch_to + # update_branches_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/update_branches(.:format) projects/merge_requests#update_branches + # namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests(.:format) projects/merge_requests#index + # POST /:namespace_id/:project_id/merge_requests(.:format) projects/merge_requests#create + # new_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/new(.:format) projects/merge_requests#new + # edit_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/edit(.:format) projects/merge_requests#edit + # namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#show + # PATCH /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#update + # PUT /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#update + describe Projects::MergeRequestsController, 'routing' do + it 'to #diffs' do + expect(get('/gitlab/gitlabhq/merge_requests/1/diffs')).to route_to('projects/merge_requests#diffs', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it 'to #commits' do + expect(get('/gitlab/gitlabhq/merge_requests/1/commits')).to route_to('projects/merge_requests#commits', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it 'to #merge' do + expect(post('/gitlab/gitlabhq/merge_requests/1/merge')).to route_to( + 'projects/merge_requests#merge', + namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1' + ) + end + + it 'to #merge_check' do + expect(get('/gitlab/gitlabhq/merge_requests/1/merge_check')).to route_to('projects/merge_requests#merge_check', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it 'to #branch_from' do + expect(get('/gitlab/gitlabhq/merge_requests/branch_from')).to route_to('projects/merge_requests#branch_from', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #branch_to' do + expect(get('/gitlab/gitlabhq/merge_requests/branch_to')).to route_to('projects/merge_requests#branch_to', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #show' do + expect(get('/gitlab/gitlabhq/merge_requests/1.diff')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'diff') + expect(get('/gitlab/gitlabhq/merge_requests/1.patch')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'patch') + end + + it_behaves_like 'RESTful project resources' do + let(:controller) { 'merge_requests' } + let(:actions) { [:index, :create, :new, :edit, :show, :update] } + end + end + + # raw_project_snippet GET /:project_id/snippets/:id/raw(.:format) snippets#raw + # project_snippets GET /:project_id/snippets(.:format) snippets#index + # POST /:project_id/snippets(.:format) snippets#create + # new_project_snippet GET /:project_id/snippets/new(.:format) snippets#new + # edit_project_snippet GET /:project_id/snippets/:id/edit(.:format) snippets#edit + # project_snippet GET /:project_id/snippets/:id(.:format) snippets#show + # PUT /:project_id/snippets/:id(.:format) snippets#update + # DELETE /:project_id/snippets/:id(.:format) snippets#destroy + describe SnippetsController, 'routing' do + it 'to #raw' do + expect(get('/gitlab/gitlabhq/snippets/1/raw')).to route_to('projects/snippets#raw', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it 'to #index' do + expect(get('/gitlab/gitlabhq/snippets')).to route_to('projects/snippets#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #create' do + expect(post('/gitlab/gitlabhq/snippets')).to route_to('projects/snippets#create', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #new' do + expect(get('/gitlab/gitlabhq/snippets/new')).to route_to('projects/snippets#new', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #edit' do + expect(get('/gitlab/gitlabhq/snippets/1/edit')).to route_to('projects/snippets#edit', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it 'to #show' do + expect(get('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it 'to #update' do + expect(put('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#update', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it 'to #destroy' do + expect(delete('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + end + + # test_project_hook GET /:project_id/hooks/:id/test(.:format) hooks#test + # project_hooks GET /:project_id/hooks(.:format) hooks#index + # POST /:project_id/hooks(.:format) hooks#create + # project_hook DELETE /:project_id/hooks/:id(.:format) hooks#destroy + describe Projects::HooksController, 'routing' do + it 'to #test' do + expect(get('/gitlab/gitlabhq/hooks/1/test')).to route_to('projects/hooks#test', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it_behaves_like 'RESTful project resources' do + let(:actions) { [:index, :create, :destroy] } + let(:controller) { 'hooks' } + end + end + + # project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /\h{7,40}/, project_id: /[^\/]+/} + describe Projects::CommitController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/commit/4246fbd')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd') + expect(get('/gitlab/gitlabhq/commit/4246fbd.diff')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'diff') + expect(get('/gitlab/gitlabhq/commit/4246fbd.patch')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'patch') + expect(get('/gitlab/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5') + end + end + + # patch_project_commit GET /:project_id/commits/:id/patch(.:format) commits#patch + # project_commits GET /:project_id/commits(.:format) commits#index + # POST /:project_id/commits(.:format) commits#create + # project_commit GET /:project_id/commits/:id(.:format) commits#show + describe Projects::CommitsController, 'routing' do + it_behaves_like 'RESTful project resources' do + let(:actions) { [:show] } + let(:controller) { 'commits' } + end + + it 'to #show' do + expect(get('/gitlab/gitlabhq/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master.atom') + end + end + + # project_project_members GET /:project_id/project_members(.:format) project_members#index + # POST /:project_id/project_members(.:format) project_members#create + # PUT /:project_id/project_members/:id(.:format) project_members#update + # DELETE /:project_id/project_members/:id(.:format) project_members#destroy + describe Projects::ProjectMembersController, 'routing' do + it_behaves_like 'RESTful project resources' do + let(:actions) { [:index, :create, :update, :destroy] } + let(:controller) { 'project_members' } + end + end + + # project_milestones GET /:project_id/milestones(.:format) milestones#index + # POST /:project_id/milestones(.:format) milestones#create + # new_project_milestone GET /:project_id/milestones/new(.:format) milestones#new + # edit_project_milestone GET /:project_id/milestones/:id/edit(.:format) milestones#edit + # project_milestone GET /:project_id/milestones/:id(.:format) milestones#show + # PUT /:project_id/milestones/:id(.:format) milestones#update + # DELETE /:project_id/milestones/:id(.:format) milestones#destroy + describe Projects::MilestonesController, 'routing' do + it_behaves_like 'RESTful project resources' do + let(:controller) { 'milestones' } + let(:actions) { [:index, :create, :new, :edit, :show, :update] } + end + end + + # project_labels GET /:project_id/labels(.:format) labels#index + describe Projects::LabelsController, 'routing' do + it 'to #index' do + expect(get('/gitlab/gitlabhq/labels')).to route_to('projects/labels#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + end + + # sort_project_issues POST /:project_id/issues/sort(.:format) issues#sort + # bulk_update_project_issues POST /:project_id/issues/bulk_update(.:format) issues#bulk_update + # search_project_issues GET /:project_id/issues/search(.:format) issues#search + # project_issues GET /:project_id/issues(.:format) issues#index + # POST /:project_id/issues(.:format) issues#create + # new_project_issue GET /:project_id/issues/new(.:format) issues#new + # edit_project_issue GET /:project_id/issues/:id/edit(.:format) issues#edit + # project_issue GET /:project_id/issues/:id(.:format) issues#show + # PUT /:project_id/issues/:id(.:format) issues#update + # DELETE /:project_id/issues/:id(.:format) issues#destroy + describe Projects::IssuesController, 'routing' do + it 'to #bulk_update' do + expect(post('/gitlab/gitlabhq/issues/bulk_update')).to route_to('projects/issues#bulk_update', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it_behaves_like 'RESTful project resources' do + let(:controller) { 'issues' } + let(:actions) { [:index, :create, :new, :edit, :show, :update] } + end + end + + # project_notes GET /:project_id/notes(.:format) notes#index + # POST /:project_id/notes(.:format) notes#create + # project_note DELETE /:project_id/notes/:id(.:format) notes#destroy + describe Projects::NotesController, 'routing' do + it_behaves_like 'RESTful project resources' do + let(:actions) { [:index, :create, :destroy] } + let(:controller) { 'notes' } + end + end + + # project_blame GET /:project_id/blame/:id(.:format) blame#show {id: /.+/, project_id: /[^\/]+/} + describe Projects::BlameController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/blame/master/app/models/project.rb')).to route_to('projects/blame#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') + expect(get('/gitlab/gitlabhq/blame/master/files.scss')).to route_to('projects/blame#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') + end + end + + # project_blob GET /:project_id/blob/:id(.:format) blob#show {id: /.+/, project_id: /[^\/]+/} + describe Projects::BlobController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/blob/master/app/models/project.rb')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') + expect(get('/gitlab/gitlabhq/blob/master/app/models/compare.rb')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/compare.rb') + expect(get('/gitlab/gitlabhq/blob/master/app/models/diff.js')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/diff.js') + expect(get('/gitlab/gitlabhq/blob/master/files.scss')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') + end + end + + # project_tree GET /:project_id/tree/:id(.:format) tree#show {id: /.+/, project_id: /[^\/]+/} + describe Projects::TreeController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/tree/master/app/models/project.rb')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') + expect(get('/gitlab/gitlabhq/tree/master/files.scss')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') + end + end + + # project_find_file GET /:namespace_id/:project_id/find_file/*id(.:format) projects/find_file#show {:id=>/.+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?/html/} + # project_files GET /:namespace_id/:project_id/files/*id(.:format) projects/find_file#list {:id=>/(?:[^.]|\.(?!json$))+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?/json/} + describe Projects::FindFileController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/find_file/master')).to route_to('projects/find_file#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') + end + + it 'to #list' do + expect(get('/gitlab/gitlabhq/files/master.json')).to route_to('projects/find_file#list', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') + end + end + + describe Projects::BlobController, 'routing' do + it 'to #edit' do + expect(get('/gitlab/gitlabhq/edit/master/app/models/project.rb')).to( + route_to('projects/blob#edit', + namespace_id: 'gitlab', project_id: 'gitlabhq', + id: 'master/app/models/project.rb')) + end + + it 'to #preview' do + expect(post('/gitlab/gitlabhq/preview/master/app/models/project.rb')).to( + route_to('projects/blob#preview', + namespace_id: 'gitlab', project_id: 'gitlabhq', + id: 'master/app/models/project.rb')) + end + end + + # project_compare_index GET /:project_id/compare(.:format) compare#index {id: /[^\/]+/, project_id: /[^\/]+/} + # POST /:project_id/compare(.:format) compare#create {id: /[^\/]+/, project_id: /[^\/]+/} + # project_compare /:project_id/compare/:from...:to(.:format) compare#show {from: /.+/, to: /.+/, id: /[^\/]+/, project_id: /[^\/]+/} + describe Projects::CompareController, 'routing' do + it 'to #index' do + expect(get('/gitlab/gitlabhq/compare')).to route_to('projects/compare#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #compare' do + expect(post('/gitlab/gitlabhq/compare')).to route_to('projects/compare#create', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #show' do + expect(get('/gitlab/gitlabhq/compare/master...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'master', to: 'stable') + expect(get('/gitlab/gitlabhq/compare/issue/1234...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'issue/1234', to: 'stable') + end + end + + describe Projects::NetworkController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/network/master')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') + expect(get('/gitlab/gitlabhq/network/ends-with.json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json') + expect(get('/gitlab/gitlabhq/network/master?format=json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') + end + end + + describe Projects::GraphsController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/graphs/master')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') + expect(get('/gitlab/gitlabhq/graphs/ends-with.json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json') + expect(get('/gitlab/gitlabhq/graphs/master?format=json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') + end + end + + describe Projects::ForksController, 'routing' do + it 'to #new' do + expect(get('/gitlab/gitlabhq/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #create' do + expect(post('/gitlab/gitlabhq/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + end + + # project_avatar DELETE /project/avatar(.:format) projects/avatars#destroy + describe Projects::AvatarsController, 'routing' do + it 'to #destroy' do + expect(delete('/gitlab/gitlabhq/avatar')).to route_to( + 'projects/avatars#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq') + end end end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index f15c45cbaac..9f6defe1450 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -9,7 +9,7 @@ require 'spec_helper' # user_calendar_activities GET /u/:username/calendar_activities(.:format) describe UsersController, "routing" do it "to #show" do - allow(User).to receive(:find_by).and_return(true) + allow_any_instance_of(UserUrlConstrainer).to receive(:matches?).and_return(true) expect(get("/User")).to route_to('users#show', username: 'User') end @@ -195,6 +195,8 @@ describe Profiles::KeysController, "routing" do # get all the ssh-keys of a user it "to #get_keys" do + allow_any_instance_of(UserUrlConstrainer).to receive(:matches?).and_return(true) + expect(get("/foo.keys")).to route_to('profiles/keys#get_keys', username: 'foo') end end @@ -263,13 +265,17 @@ end describe "Groups", "routing" do let(:name) { 'complex.group-namegit' } + before { allow_any_instance_of(GroupUrlConstrainer).to receive(:matches?).and_return(true) } + it "to #show" do expect(get("/groups/#{name}")).to route_to('groups#show', id: name) end - it "also display group#show on the short path" do - allow(Group).to receive(:find_by).and_return(true) + it "also supports nested groups" do + expect(get("/#{name}/#{name}")).to route_to('groups#show', id: "#{name}/#{name}") + end + it "also display group#show on the short path" do expect(get("/#{name}")).to route_to('groups#show', id: name) end @@ -284,6 +290,10 @@ describe "Groups", "routing" do it "to #members" do expect(get("/groups/#{name}/group_members")).to route_to('groups/group_members#index', group_id: name) end + + it "also display group#show with slash in the path" do + expect(get('/group/subgroup')).to route_to('groups#show', id: 'group/subgroup') + end end describe HealthCheckController, 'routing' do -- cgit v1.2.1