diff options
32 files changed, 500 insertions, 415 deletions
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index d01e0dedf52..b666aa01d6b 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -16,7 +16,7 @@ class SearchController < ApplicationController @group = nil unless can?(current_user, :read_group, @group) end - return if params[:search].nil? || params[:search].blank? + return if params[:search].blank? @search_term = params[:search] diff --git a/app/views/search/results/_commit.html.haml b/app/views/search/results/_commit.html.haml index 5b2d83d6b92..f34eaf89027 100644 --- a/app/views/search/results/_commit.html.haml +++ b/app/views/search/results/_commit.html.haml @@ -1 +1 @@ -= render 'projects/commits/commit', project: @project, commit: commit += render 'projects/commits/commit', project: @project, commit: commit, ref: nil diff --git a/changelogs/unreleased/24255-search-fix.yml b/changelogs/unreleased/24255-search-fix.yml new file mode 100644 index 00000000000..c0afade9bc8 --- /dev/null +++ b/changelogs/unreleased/24255-search-fix.yml @@ -0,0 +1,4 @@ +--- +title: Fix broken commits search +merge_request: +author: diff --git a/changelogs/unreleased/sidekiq_default_retries.yml b/changelogs/unreleased/sidekiq_default_retries.yml new file mode 100644 index 00000000000..3df2a415dbc --- /dev/null +++ b/changelogs/unreleased/sidekiq_default_retries.yml @@ -0,0 +1,4 @@ +--- +title: Set default Sidekiq retries to 3 +merge_request: 7294 +author: diff --git a/config/initializers/routing_draw.rb b/config/initializers/routing_draw.rb new file mode 100644 index 00000000000..25003cf0239 --- /dev/null +++ b/config/initializers/routing_draw.rb @@ -0,0 +1,7 @@ +# Adds draw method into Rails routing +# It allows us to keep routing splitted into files +class ActionDispatch::Routing::Mapper + def draw(routes_name) + instance_eval(File.read(Rails.root.join("config/routes/#{routes_name}.rb"))) + end +end diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 0455a98dbfe..023af2af23c 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -2,6 +2,9 @@ redis_config_hash = Gitlab::Redis.params redis_config_hash[:namespace] = Gitlab::Redis::SIDEKIQ_NAMESPACE +# Default is to retry 25 times with exponential backoff. That's too much. +Sidekiq.default_worker_options = { retry: 3 } + Sidekiq.configure_server do |config| config.redis = redis_config_hash diff --git a/config/routes.rb b/config/routes.rb index 659ea51bc75..7bf6c03e69b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,12 +2,6 @@ require 'sidekiq/web' require 'sidekiq/cron/web' require 'api/api' -class ActionDispatch::Routing::Mapper - def draw(routes_name) - instance_eval(File.read(Rails.root.join("config/routes/#{routes_name}.rb"))) - end -end - Rails.application.routes.draw do concern :access_requestable do post :request_access, on: :collection diff --git a/config/routes/git_http.rb b/config/routes/git_http.rb new file mode 100644 index 00000000000..03adc4815f3 --- /dev/null +++ b/config/routes/git_http.rb @@ -0,0 +1,37 @@ +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' + + # 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' + + # GitLab LFS object storage + scope constraints: { oid: /[a-f0-9]{64}/ } do + get '/gitlab-lfs/objects/*oid', to: 'lfs_storage#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' + 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 + +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 diff --git a/config/routes/group.rb b/config/routes/group.rb index 826048ba196..3c392f77ef6 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -3,7 +3,7 @@ require 'constraints/group_url_constrainer' constraints(GroupUrlConstrainer.new) do scope(path: ':id', as: :group, - constraints: { id: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }, + constraints: { id: Gitlab::Regex.namespace_route_regex }, controller: :groups) do get '/', action: :show patch '/', action: :update @@ -12,26 +12,26 @@ constraints(GroupUrlConstrainer.new) do end end -scope constraints: { id: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ } do - resources :groups, except: [:show] do - member do - get :issues - get :merge_requests - get :projects - get :activity - end +resources :groups, only: [:index, :new, :create] - scope module: :groups do - resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do - post :resend_invite, on: :member - delete :leave, on: :collection - end - - resource :avatar, only: [:destroy] - resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create] +scope(path: 'groups/:id', controller: :groups) do + get :edit, as: :edit_group + get :issues, as: :issues_group + get :merge_requests, as: :merge_requests_group + get :projects, as: :projects_group + get :activity, as: :activity_group +end - resources :labels, except: [:show], constraints: { id: /\d+/ } - end +scope(path: 'groups/:group_id', module: :groups, as: :group) do + resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do + post :resend_invite, on: :member + delete :leave, on: :collection end - get 'groups/:id' => 'groups#show', as: :group_canonical + + resource :avatar, only: [:destroy] + resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create] + resources :labels, except: [:show], constraints: { id: /\d+/ } end + +# Must be last route in this file +get 'groups/:id' => 'groups#show', as: :group_canonical diff --git a/config/routes/project.rb b/config/routes/project.rb index 7a1bfa6a9f0..82defb0ba71 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -22,149 +22,13 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: end scope module: :projects do - 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' - - # 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' - - # GitLab LFS object storage - scope constraints: { oid: /[a-f0-9]{64}/ } do - get '/gitlab-lfs/objects/*oid', to: 'lfs_storage#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' - 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 - - 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 - - # Blob routes: - get '/new/*id', to: 'blob#new', constraints: { id: /.+/ }, as: 'new_blob' - post '/create/*id', to: 'blob#create', constraints: { id: /.+/ }, as: 'create_blob' - get '/edit/*id', to: 'blob#edit', constraints: { id: /.+/ }, as: 'edit_blob' - put '/update/*id', to: 'blob#update', constraints: { id: /.+/ }, as: 'update_blob' - post '/preview/*id', to: 'blob#preview', constraints: { id: /.+/ }, as: 'preview_blob' + draw :git_http # # Templates # get '/templates/:template_type/:key' => 'templates#show', as: :template - scope do - get( - '/blob/*id/diff', - to: 'blob#diff', - constraints: { id: /.+/, format: false }, - as: :blob_diff - ) - get( - '/blob/*id', - to: 'blob#show', - constraints: { id: /.+/, format: false }, - as: :blob - ) - delete( - '/blob/*id', - to: 'blob#destroy', - constraints: { id: /.+/, format: false } - ) - put( - '/blob/*id', - to: 'blob#update', - constraints: { id: /.+/, format: false } - ) - post( - '/blob/*id', - to: 'blob#create', - constraints: { id: /.+/, format: false } - ) - end - - scope do - get( - '/raw/*id', - to: 'raw#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :raw - ) - end - - scope do - get( - '/tree/*id', - to: 'tree#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :tree - ) - end - - scope do - get( - '/find_file/*id', - to: 'find_file#show', - constraints: { id: /.+/, format: /html/ }, - as: :find_file - ) - end - - scope do - get( - '/files/*id', - to: 'find_file#list', - constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }, - as: :files - ) - end - - scope do - post( - '/create_dir/*id', - to: 'tree#create_dir', - constraints: { id: /.+/ }, - as: 'create_dir' - ) - end - - scope do - get( - '/blame/*id', - to: 'blame#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :blame - ) - end - - scope do - get( - '/commits/*id', - to: 'commits#show', - constraints: { id: /.+/, format: false }, - as: :commits - ) - end - resource :avatar, only: [:show, :destroy] resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do member do @@ -207,29 +71,6 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: end end - 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' - - 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' - end - - resource :repository, only: [:create] do - member do - get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex } - end - end - resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do member do get :test @@ -246,23 +87,6 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: resources :forks, only: [:index, :new, :create] resource :import, only: [:new, :create, :show] - resources :refs, only: [] do - collection do - get 'switch' - end - - member do - # tree viewer logs - get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } - # Directories with leading dots erroneously get rejected if git - # ref regex used in constraints. Regex verification now done in controller. - get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: { - id: /.*/, - path: /.*/ - } - end - end - resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do member do get :commits @@ -468,6 +292,11 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: end end end + + # Since both wiki and repository routing contains wildcard characters + # its preferable to keep it below all other project routes + draw :wiki + draw :repository end end end diff --git a/config/routes/repository.rb b/config/routes/repository.rb new file mode 100644 index 00000000000..76dcf113aea --- /dev/null +++ b/config/routes/repository.rb @@ -0,0 +1,110 @@ +# All routing related to repositoty browsing + +resource :repository, only: [:create] do + member do + get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex } + end +end + +resources :refs, only: [] do + collection do + get 'switch' + end + + member do + # tree viewer logs + get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } + # Directories with leading dots erroneously get rejected if git + # ref regex used in constraints. Regex verification now done in controller. + get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: { + id: /.*/, + path: /.*/ + } + end +end + +get '/new/*id', to: 'blob#new', constraints: { id: /.+/ }, as: 'new_blob' +post '/create/*id', to: 'blob#create', constraints: { id: /.+/ }, as: 'create_blob' +get '/edit/*id', to: 'blob#edit', constraints: { id: /.+/ }, as: 'edit_blob' +put '/update/*id', to: 'blob#update', constraints: { id: /.+/ }, as: 'update_blob' +post '/preview/*id', to: 'blob#preview', constraints: { id: /.+/ }, as: 'preview_blob' + +scope do + get( + '/blob/*id/diff', + to: 'blob#diff', + constraints: { id: /.+/, format: false }, + as: :blob_diff + ) + get( + '/blob/*id', + to: 'blob#show', + constraints: { id: /.+/, format: false }, + as: :blob + ) + delete( + '/blob/*id', + to: 'blob#destroy', + constraints: { id: /.+/, format: false } + ) + put( + '/blob/*id', + to: 'blob#update', + constraints: { id: /.+/, format: false } + ) + post( + '/blob/*id', + to: 'blob#create', + constraints: { id: /.+/, format: false } + ) + + get( + '/raw/*id', + to: 'raw#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :raw + ) + + get( + '/tree/*id', + to: 'tree#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :tree + ) + + get( + '/find_file/*id', + to: 'find_file#show', + constraints: { id: /.+/, format: /html/ }, + as: :find_file + ) + + get( + '/files/*id', + to: 'find_file#list', + constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }, + as: :files + ) + + post( + '/create_dir/*id', + to: 'tree#create_dir', + constraints: { id: /.+/ }, + as: 'create_dir' + ) + + get( + '/blame/*id', + to: 'blame#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :blame + ) + + # File/dir history + get( + '/commits/*id', + to: 'commits#show', + constraints: { id: /.+/, format: false }, + as: :commits + ) +end diff --git a/config/routes/user.rb b/config/routes/user.rb index 0a9c924863d..dc1068af6f6 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -14,31 +14,32 @@ end constraints(UserUrlConstrainer.new) do scope(path: ':username', as: :user, - constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }, + constraints: { username: Gitlab::Regex.namespace_route_regex }, controller: :users) do get '/', action: :show end end -scope(path: 'users/:username', - as: :user, - constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }, - controller: :users) do - get :calendar - get :calendar_activities - get :groups - get :projects - get :contributed, as: :contributed_projects - get :snippets - get :exists - get '/', to: redirect('/%{username}') -end +scope(constraints: { username: Gitlab::Regex.namespace_route_regex }) do + scope(path: 'users/:username', + as: :user, + controller: :users) do + get :calendar + get :calendar_activities + get :groups + get :projects + get :contributed, as: :contributed_projects + get :snippets + get :exists + get '/', to: redirect('/%{username}') + end -# Compatibility with old routing -# TODO (dzaporozhets): remove in 10.0 -get '/u/:username', to: redirect('/%{username}'), constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ } -# TODO (dzaporozhets): remove in 9.0 -get '/u/:username/groups', to: redirect('/users/%{username}/groups'), constraints: { username: /[a-zA-Z.0-9_\-]+/ } -get '/u/:username/projects', to: redirect('/users/%{username}/projects'), constraints: { username: /[a-zA-Z.0-9_\-]+/ } -get '/u/:username/snippets', to: redirect('/users/%{username}/snippets'), constraints: { username: /[a-zA-Z.0-9_\-]+/ } -get '/u/:username/contributed', to: redirect('/users/%{username}/contributed'), constraints: { username: /[a-zA-Z.0-9_\-]+/ } + # Compatibility with old routing + # TODO (dzaporozhets): remove in 10.0 + get '/u/:username', to: redirect('/%{username}') + # TODO (dzaporozhets): remove in 9.0 + get '/u/:username/groups', to: redirect('/users/%{username}/groups') + get '/u/:username/projects', to: redirect('/users/%{username}/projects') + get '/u/:username/snippets', to: redirect('/users/%{username}/snippets') + get '/u/:username/contributed', to: redirect('/users/%{username}/contributed') +end diff --git a/config/routes/wiki.rb b/config/routes/wiki.rb new file mode 100644 index 00000000000..ecd4d395d66 --- /dev/null +++ b/config/routes/wiki.rb @@ -0,0 +1,16 @@ +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' + + 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' +end diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 0e81e99120b..0c88838767c 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -117,7 +117,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps end step 'I visit group "NonExistentGroup" page' do - visit group_path(-1) + visit group_path("NonExistentGroup") end step 'the archived project have some issues' do diff --git a/lib/constraints/constrainer_helper.rb b/lib/constraints/constrainer_helper.rb new file mode 100644 index 00000000000..ab07a6793d9 --- /dev/null +++ b/lib/constraints/constrainer_helper.rb @@ -0,0 +1,15 @@ +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 ca39b1961ae..2af6e1a11c8 100644 --- a/lib/constraints/group_url_constrainer.rb +++ b/lib/constraints/group_url_constrainer.rb @@ -1,7 +1,15 @@ -require 'constraints/namespace_url_constrainer' +require_relative 'constrainer_helper' -class GroupUrlConstrainer < NamespaceUrlConstrainer - def find_resource(id) - Group.find_by_path(id) +class GroupUrlConstrainer + include ConstrainerHelper + + def matches?(request) + id = extract_resource_path(request.path) + + if id =~ Gitlab::Regex.namespace_regex + Group.find_by(path: id).present? + else + false + end end end diff --git a/lib/constraints/namespace_url_constrainer.rb b/lib/constraints/namespace_url_constrainer.rb deleted file mode 100644 index 91b70143f11..00000000000 --- a/lib/constraints/namespace_url_constrainer.rb +++ /dev/null @@ -1,24 +0,0 @@ -class NamespaceUrlConstrainer - def matches?(request) - id = request.path - id = id.sub(/\A#{relative_url_root}/, '') if relative_url_root - id = id.sub(/\A\/+/, '').split('/').first - id = id.sub(/.atom\z/, '') if id - - if id =~ Gitlab::Regex.namespace_regex - find_resource(id) - end - end - - def find_resource(id) - Namespace.find_by_path(id) - 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/user_url_constrainer.rb b/lib/constraints/user_url_constrainer.rb index 504a0f5d93e..4d722ad5af2 100644 --- a/lib/constraints/user_url_constrainer.rb +++ b/lib/constraints/user_url_constrainer.rb @@ -1,7 +1,15 @@ -require 'constraints/namespace_url_constrainer' +require_relative 'constrainer_helper' -class UserUrlConstrainer < NamespaceUrlConstrainer - def find_resource(id) - User.find_by('lower(username) = ?', id.downcase) +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 end end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 0d30e1bb92e..cb1659f9cee 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -8,6 +8,10 @@ module Gitlab @namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze end + def namespace_route_regex + @namespace_route_regex ||= /#{NAMESPACE_REGEX_STR}/.freeze + end + def namespace_regex_message "can contain only letters, digits, '_', '-' and '.'. " \ "Cannot start with '-' or end in '.', '.git' or '.atom'." \ diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb new file mode 100644 index 00000000000..f6409e00f22 --- /dev/null +++ b/spec/features/global_search_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +feature 'Global search', feature: true do + let(:user) { create(:user) } + let(:project) { create(:project, namespace: user.namespace) } + + before do + project.team << [user, :master] + login_with(user) + end + + describe 'I search through the issues and I see pagination' do + before do + allow_any_instance_of(Gitlab::SearchResults).to receive(:per_page).and_return(1) + create_list(:issue, 2, project: project, title: 'initial') + end + + it "has a pagination" do + visit dashboard_projects_path + + fill_in "search", with: "initial" + click_button "Go" + + select_filter("Issues") + expect(page).to have_selector('.gl-pagination .page', count: 2) + end + end +end diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb index fb0c4704285..755f4eb1b0b 100644 --- a/spec/features/issues/new_branch_button_spec.rb +++ b/spec/features/issues/new_branch_button_spec.rb @@ -18,8 +18,8 @@ feature 'Start new branch from an issue', feature: true do end context "when there is a referenced merge request" do - let(:note) do - create(:note, :on_issue, :system, project: project, + let!(:note) do + create(:note, :on_issue, :system, project: project, noteable: issue, note: "Mentioned in !#{referenced_mr.iid}") end let(:referenced_mr) do @@ -28,12 +28,13 @@ feature 'Start new branch from an issue', feature: true do end before do - issue.notes << note + referenced_mr.cache_merge_request_closes_issues!(user) visit namespace_project_issue_path(project.namespace, project, issue) end it "hides the new branch button", js: true do + expect(page).to have_css('#new-branch .unavailable') expect(page).not_to have_css('#new-branch .available') expect(page).to have_content /1 Related Merge Request/ end diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index 1806200c82c..caecd027aaa 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -100,6 +100,32 @@ describe "Search", feature: true do expect(page).to have_link(snippet.title) end + + it 'finds a commit' do + visit namespace_project_path(project.namespace, project) + + page.within '.search' do + fill_in 'search', with: 'add' + click_button 'Go' + end + + click_link "Commits" + + expect(page).to have_selector('.commit-row-description') + end + + it 'finds a code' do + visit namespace_project_path(project.namespace, project) + + page.within '.search' do + fill_in 'search', with: 'def' + click_button 'Go' + end + + click_link "Code" + + expect(page).to have_selector('.file-content .code') + end end describe 'Right header search field', feature: true do diff --git a/spec/javascripts/boards/boards_store_spec.js.es6 b/spec/javascripts/boards/boards_store_spec.js.es6 index 6208c2386b0..b84dfc8197b 100644 --- a/spec/javascripts/boards/boards_store_spec.js.es6 +++ b/spec/javascripts/boards/boards_store_spec.js.es6 @@ -13,8 +13,9 @@ //= require boards/stores/boards_store //= require ./mock_data -(() => { +describe('Store', () => { beforeEach(() => { + Vue.http.interceptors.push(boardsMockInterceptor); gl.boardService = new BoardService('/test/issue-boards/board', '1'); gl.issueBoards.BoardsStore.create(); @@ -24,145 +25,147 @@ }); }); - describe('Store', () => { - it('starts with a blank state', () => { - expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(0); - }); + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor); + }); - describe('lists', () => { - it('creates new list without persisting to DB', () => { - gl.issueBoards.BoardsStore.addList(listObj); + it('starts with a blank state', () => { + expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(0); + }); - expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); - }); + describe('lists', () => { + it('creates new list without persisting to DB', () => { + gl.issueBoards.BoardsStore.addList(listObj); - it('finds list by ID', () => { - gl.issueBoards.BoardsStore.addList(listObj); - const list = gl.issueBoards.BoardsStore.findList('id', 1); + expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); + }); - expect(list.id).toBe(1); - }); + it('finds list by ID', () => { + gl.issueBoards.BoardsStore.addList(listObj); + const list = gl.issueBoards.BoardsStore.findList('id', 1); - it('finds list by type', () => { - gl.issueBoards.BoardsStore.addList(listObj); - const list = gl.issueBoards.BoardsStore.findList('type', 'label'); + expect(list.id).toBe(1); + }); - expect(list).toBeDefined(); - }); + it('finds list by type', () => { + gl.issueBoards.BoardsStore.addList(listObj); + const list = gl.issueBoards.BoardsStore.findList('type', 'label'); - it('finds list limited by type', () => { - gl.issueBoards.BoardsStore.addList({ - id: 1, - position: 0, - title: 'Test', - list_type: 'backlog' - }); - const list = gl.issueBoards.BoardsStore.findList('id', 1, 'backlog'); + expect(list).toBeDefined(); + }); - expect(list).toBeDefined(); + it('finds list limited by type', () => { + gl.issueBoards.BoardsStore.addList({ + id: 1, + position: 0, + title: 'Test', + list_type: 'backlog' }); + const list = gl.issueBoards.BoardsStore.findList('id', 1, 'backlog'); - it('gets issue when new list added', (done) => { - gl.issueBoards.BoardsStore.addList(listObj); - const list = gl.issueBoards.BoardsStore.findList('id', 1); + expect(list).toBeDefined(); + }); - expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); + it('gets issue when new list added', (done) => { + gl.issueBoards.BoardsStore.addList(listObj); + const list = gl.issueBoards.BoardsStore.findList('id', 1); - setTimeout(() => { - expect(list.issues.length).toBe(1); - expect(list.issues[0].id).toBe(1); - done(); - }, 0); - }); + expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); - it('persists new list', (done) => { - gl.issueBoards.BoardsStore.new({ - title: 'Test', - type: 'label', - label: { - id: 1, - title: 'Testing', - color: 'red', - description: 'testing;' - } - }); - expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); - - setTimeout(() => { - const list = gl.issueBoards.BoardsStore.findList('id', 1); - expect(list).toBeDefined(); - expect(list.id).toBe(1); - expect(list.position).toBe(0); - done(); - }, 0); - }); + setTimeout(() => { + expect(list.issues.length).toBe(1); + expect(list.issues[0].id).toBe(1); + done(); + }, 0); + }); - it('check for blank state adding', () => { - expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(true); + it('persists new list', (done) => { + gl.issueBoards.BoardsStore.new({ + title: 'Test', + type: 'label', + label: { + id: 1, + title: 'Testing', + color: 'red', + description: 'testing;' + } }); + expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); - it('check for blank state not adding', () => { - gl.issueBoards.BoardsStore.addList(listObj); - expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(false); - }); + setTimeout(() => { + const list = gl.issueBoards.BoardsStore.findList('id', 1); + expect(list).toBeDefined(); + expect(list.id).toBe(1); + expect(list.position).toBe(0); + done(); + }, 0); + }); - it('check for blank state adding when backlog & done list exist', () => { - gl.issueBoards.BoardsStore.addList({ - list_type: 'backlog' - }); - gl.issueBoards.BoardsStore.addList({ - list_type: 'done' - }); + it('check for blank state adding', () => { + expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(true); + }); + + it('check for blank state not adding', () => { + gl.issueBoards.BoardsStore.addList(listObj); + expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(false); + }); - expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(true); + it('check for blank state adding when backlog & done list exist', () => { + gl.issueBoards.BoardsStore.addList({ + list_type: 'backlog' }); + gl.issueBoards.BoardsStore.addList({ + list_type: 'done' + }); + + expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(true); + }); - it('adds the blank state', () => { - gl.issueBoards.BoardsStore.addBlankState(); + it('adds the blank state', () => { + gl.issueBoards.BoardsStore.addBlankState(); - const list = gl.issueBoards.BoardsStore.findList('type', 'blank', 'blank'); - expect(list).toBeDefined(); - }); + const list = gl.issueBoards.BoardsStore.findList('type', 'blank', 'blank'); + expect(list).toBeDefined(); + }); - it('removes list from state', () => { - gl.issueBoards.BoardsStore.addList(listObj); + it('removes list from state', () => { + gl.issueBoards.BoardsStore.addList(listObj); - expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); + expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); - gl.issueBoards.BoardsStore.removeList(1, 'label'); + gl.issueBoards.BoardsStore.removeList(1, 'label'); - expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(0); - }); + expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(0); + }); - it('moves the position of lists', () => { - const listOne = gl.issueBoards.BoardsStore.addList(listObj), - listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate); + it('moves the position of lists', () => { + const listOne = gl.issueBoards.BoardsStore.addList(listObj), + listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate); - expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2); + expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2); - gl.issueBoards.BoardsStore.moveList(listOne, ['2', '1']); + gl.issueBoards.BoardsStore.moveList(listOne, ['2', '1']); - expect(listOne.position).toBe(1); - }); + expect(listOne.position).toBe(1); + }); - it('moves an issue from one list to another', (done) => { - const listOne = gl.issueBoards.BoardsStore.addList(listObj), - listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate); + it('moves an issue from one list to another', (done) => { + const listOne = gl.issueBoards.BoardsStore.addList(listObj), + listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate); - expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2); + expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2); - setTimeout(() => { - expect(listOne.issues.length).toBe(1); - expect(listTwo.issues.length).toBe(1); + setTimeout(() => { + expect(listOne.issues.length).toBe(1); + expect(listTwo.issues.length).toBe(1); - gl.issueBoards.BoardsStore.moveIssueToList(listOne, listTwo, listOne.findIssue(1)); + gl.issueBoards.BoardsStore.moveIssueToList(listOne, listTwo, listOne.findIssue(1)); - expect(listOne.issues.length).toBe(0); - expect(listTwo.issues.length).toBe(1); + expect(listOne.issues.length).toBe(0); + expect(listTwo.issues.length).toBe(1); - done(); - }, 0); - }); + done(); + }, 0); }); }); -})(); +}); diff --git a/spec/javascripts/boards/list_spec.js.es6 b/spec/javascripts/boards/list_spec.js.es6 index 1a0427fdd90..dfbcbe3a7c1 100644 --- a/spec/javascripts/boards/list_spec.js.es6 +++ b/spec/javascripts/boards/list_spec.js.es6 @@ -17,12 +17,17 @@ describe('List model', () => { let list; beforeEach(() => { + Vue.http.interceptors.push(boardsMockInterceptor); gl.boardService = new BoardService('/test/issue-boards/board', '1'); gl.issueBoards.BoardsStore.create(); list = new List(listObj); }); + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor); + }); + it('gets issues when created', (done) => { setTimeout(() => { expect(list.issues.length).toBe(1); diff --git a/spec/javascripts/boards/mock_data.js.es6 b/spec/javascripts/boards/mock_data.js.es6 index 80d05e8a1a3..fcb3d8f17d8 100644 --- a/spec/javascripts/boards/mock_data.js.es6 +++ b/spec/javascripts/boards/mock_data.js.es6 @@ -48,10 +48,10 @@ const BoardsMockData = { } }; -Vue.http.interceptors.push((request, next) => { +const boardsMockInterceptor = (request, next) => { const body = BoardsMockData[request.method][request.url]; next(request.respondWith(JSON.stringify(body), { status: 200 })); -}); +}; diff --git a/spec/lib/constraints/constrainer_helper_spec.rb b/spec/lib/constraints/constrainer_helper_spec.rb new file mode 100644 index 00000000000..27c8d72aefc --- /dev/null +++ b/spec/lib/constraints/constrainer_helper_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe ConstrainerHelper, lib: true do + include ConstrainerHelper + + describe '#extract_resource_path' do + it { expect(extract_resource_path('/gitlab/')).to eq('gitlab') } + it { expect(extract_resource_path('///gitlab//')).to eq('gitlab') } + it { expect(extract_resource_path('/gitlab.atom')).to eq('gitlab') } + + context 'relative url' do + before do + allow(Gitlab::Application.config).to receive(:relative_url_root) { '/gitlab' } + end + + it { expect(extract_resource_path('/gitlab/foo')).to eq('foo') } + it { expect(extract_resource_path('/foo/bar')).to eq('foo/bar') } + end + end +end diff --git a/spec/lib/constraints/group_url_constrainer_spec.rb b/spec/lib/constraints/group_url_constrainer_spec.rb index f0b75a664f2..42299b17c2b 100644 --- a/spec/lib/constraints/group_url_constrainer_spec.rb +++ b/spec/lib/constraints/group_url_constrainer_spec.rb @@ -1,10 +1,19 @@ require 'spec_helper' describe GroupUrlConstrainer, lib: true do - let!(:username) { create(:group, path: 'gitlab-org') } + let!(:group) { create(:group, path: 'gitlab') } - describe '#find_resource' do - it { expect(!!subject.find_resource('gitlab-org')).to be_truthy } - it { expect(!!subject.find_resource('gitlab-com')).to be_falsey } + describe '#matches?' do + context 'root group' do + it { expect(subject.matches?(request '/gitlab')).to be_truthy } + it { expect(subject.matches?(request '/gitlab.atom')).to be_truthy } + it { expect(subject.matches?(request '/gitlab/edit')).to be_falsey } + it { expect(subject.matches?(request '/gitlab-ce')).to be_falsey } + it { expect(subject.matches?(request '/.gitlab')).to be_falsey } + end + end + + def request(path) + double(:request, path: path) end end diff --git a/spec/lib/constraints/namespace_url_constrainer_spec.rb b/spec/lib/constraints/namespace_url_constrainer_spec.rb deleted file mode 100644 index 7814711fe27..00000000000 --- a/spec/lib/constraints/namespace_url_constrainer_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'spec_helper' - -describe NamespaceUrlConstrainer, lib: true do - let!(:group) { create(:group, path: 'gitlab') } - - describe '#matches?' do - context 'existing namespace' do - it { expect(subject.matches?(request '/gitlab')).to be_truthy } - it { expect(subject.matches?(request '/gitlab.atom')).to be_truthy } - it { expect(subject.matches?(request '/gitlab/')).to be_truthy } - it { expect(subject.matches?(request '//gitlab/')).to be_truthy } - end - - context 'non-existing namespace' do - it { expect(subject.matches?(request '/gitlab-ce')).to be_falsey } - it { expect(subject.matches?(request '/gitlab.ce')).to be_falsey } - it { expect(subject.matches?(request '/g/gitlab')).to be_falsey } - it { expect(subject.matches?(request '/.gitlab')).to be_falsey } - end - - context 'relative url' do - before do - allow(Gitlab::Application.config).to receive(:relative_url_root) { '/gitlab' } - end - - it { expect(subject.matches?(request '/gitlab/gitlab')).to be_truthy } - it { expect(subject.matches?(request '/gitlab/gitlab-ce')).to be_falsey } - it { expect(subject.matches?(request '/gitlab/')).to be_falsey } - end - end - - def request(path) - OpenStruct.new(path: path) - end -end diff --git a/spec/lib/constraints/user_url_constrainer_spec.rb b/spec/lib/constraints/user_url_constrainer_spec.rb index 4b26692672f..b3f8530c609 100644 --- a/spec/lib/constraints/user_url_constrainer_spec.rb +++ b/spec/lib/constraints/user_url_constrainer_spec.rb @@ -3,8 +3,14 @@ require 'spec_helper' describe UserUrlConstrainer, lib: true do let!(:username) { create(:user, username: 'dz') } - describe '#find_resource' do - it { expect(!!subject.find_resource('dz')).to be_truthy } - it { expect(!!subject.find_resource('john')).to be_falsey } + describe '#matches?' do + it { expect(subject.matches?(request '/dz')).to be_truthy } + it { expect(subject.matches?(request '/dz.atom')).to be_truthy } + it { expect(subject.matches?(request '/dz/projects')).to be_falsey } + it { expect(subject.matches?(request '/gitlab')).to be_falsey } + end + + def request(path) + double(:request, path: path) end end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index c18a2d55e43..61dca5d5a62 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -266,13 +266,13 @@ describe "Groups", "routing" do end it "also display group#show on the short path" do - allow(Group).to receive(:find_by_path).and_return(true) + allow(Group).to receive(:find_by).and_return(true) expect(get('/1')).to route_to('groups#show', id: '1') end it "also display group#show with dot in the path" do - allow(Group).to receive(:find_by_path).and_return(true) + allow(Group).to receive(:find_by).and_return(true) expect(get('/group.with.dot')).to route_to('groups#show', id: 'group.with.dot') end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b2ca856f89f..73cf4c9a24c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -29,6 +29,7 @@ RSpec.configure do |config| config.include Devise::Test::ControllerHelpers, type: :controller config.include Warden::Test::Helpers, type: :request config.include LoginHelpers, type: :feature + config.include SearchHelpers, type: :feature config.include StubConfiguration config.include EmailHelpers config.include TestEnv diff --git a/spec/support/search_helpers.rb b/spec/support/search_helpers.rb new file mode 100644 index 00000000000..abbbb636d66 --- /dev/null +++ b/spec/support/search_helpers.rb @@ -0,0 +1,5 @@ +module SearchHelpers + def select_filter(name) + find(:xpath, "//ul[contains(@class, 'search-filter')]//a[contains(.,'#{name}')]").click + end +end |