diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-05 00:07:49 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-05 00:07:49 +0000 |
commit | 77237c5a6b9044f58beabc54d3589e5fa09cbfba (patch) | |
tree | f43188047fe8955f6cf78e05ae9c2e8f6a019e0b /spec | |
parent | 2fd92f2dc784ade9cb4e1c33dd60cbfad7b86818 (diff) | |
download | gitlab-ce-77237c5a6b9044f58beabc54d3589e5fa09cbfba.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
31 files changed, 1188 insertions, 254 deletions
diff --git a/spec/controllers/repositories/git_http_controller_spec.rb b/spec/controllers/repositories/git_http_controller_spec.rb index 10a7b72ca89..2573fdf16ff 100644 --- a/spec/controllers/repositories/git_http_controller_spec.rb +++ b/spec/controllers/repositories/git_http_controller_spec.rb @@ -6,16 +6,18 @@ describe Repositories::GitHttpController do include GitHttpHelpers let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:personal_snippet) { create(:personal_snippet, :public, :repository) } + let_it_be(:project_snippet) { create(:project_snippet, :public, :repository, project: project) } let(:namespace_id) { project.namespace.to_param } let(:repository_id) { project.path + '.git' } - let(:project_params) do + let(:container_params) do { namespace_id: namespace_id, repository_id: repository_id } end - let(:params) { project_params } + let(:params) { container_params } describe 'HEAD #info_refs' do it 'returns 403' do @@ -27,7 +29,7 @@ describe Repositories::GitHttpController do shared_examples 'info_refs behavior' do describe 'GET #info_refs' do - let(:params) { project_params.merge(service: 'git-upload-pack') } + let(:params) { container_params.merge(service: 'git-upload-pack') } it 'returns 401 for unauthenticated requests to public repositories when http protocol is disabled' do stub_application_setting(enabled_git_access_protocol: 'ssh') @@ -41,8 +43,6 @@ describe Repositories::GitHttpController do end context 'with authorized user' do - let(:user) { project.owner } - before do request.headers.merge! auth_env(user.username, user.password, nil) end @@ -122,7 +122,7 @@ describe Repositories::GitHttpController do end shared_examples 'access checker class' do - let(:params) { project_params.merge(service: 'git-upload-pack') } + let(:params) { container_params.merge(service: 'git-upload-pack') } it 'calls the right access class checker with the right object' do allow(controller).to receive(:verify_workhorse_api!).and_return(true) @@ -136,11 +136,41 @@ describe Repositories::GitHttpController do end context 'when repository container is a project' do - it_behaves_like 'info_refs behavior' + it_behaves_like 'info_refs behavior' do + let(:user) { project.owner } + end it_behaves_like 'git_upload_pack behavior', true it_behaves_like 'access checker class' do let(:expected_class) { Gitlab::GitAccess } let(:expected_object) { project } end end + + context 'when repository container is a personal snippet' do + let(:namespace_id) { 'snippets' } + let(:repository_id) { personal_snippet.to_param + '.git' } + + it_behaves_like 'info_refs behavior' do + let(:user) { personal_snippet.author } + end + it_behaves_like 'git_upload_pack behavior', false + it_behaves_like 'access checker class' do + let(:expected_class) { Gitlab::GitAccessSnippet } + let(:expected_object) { personal_snippet } + end + end + + context 'when repository container is a project snippet' do + let(:namespace_id) { project.full_path + '/snippets' } + let(:repository_id) { project_snippet.to_param + '.git' } + + it_behaves_like 'info_refs behavior' do + let(:user) { project_snippet.author } + end + it_behaves_like 'git_upload_pack behavior', false + it_behaves_like 'access checker class' do + let(:expected_class) { Gitlab::GitAccessSnippet } + let(:expected_object) { project_snippet } + end + end end diff --git a/spec/features/broadcast_messages_spec.rb b/spec/features/broadcast_messages_spec.rb index 43fbf1010c9..d7c84b8085b 100644 --- a/spec/features/broadcast_messages_spec.rb +++ b/spec/features/broadcast_messages_spec.rb @@ -3,28 +3,58 @@ require 'spec_helper' describe 'Broadcast Messages' do - let!(:broadcast_message) { create(:broadcast_message, broadcast_type: 'notification', message: 'SampleMessage') } + shared_examples 'a Broadcast Messages' do + it 'shows broadcast message' do + visit root_path - it 'shows broadcast message' do - visit root_path + expect(page).to have_content 'SampleMessage' + end + end + + shared_examples 'a dismissable Broadcast Messages' do + it 'hides broadcast message after dismiss', :js do + visit root_path + + find('.js-dismiss-current-broadcast-notification').click + + expect(page).not_to have_content 'SampleMessage' + end + + it 'broadcast message is still hidden after refresh', :js do + visit root_path + + find('.js-dismiss-current-broadcast-notification').click + visit root_path + + expect(page).not_to have_content 'SampleMessage' + end + end + + describe 'banner type' do + let!(:broadcast_message) { create(:broadcast_message, message: 'SampleMessage') } + + it_behaves_like 'a Broadcast Messages' + + it 'shows broadcast message' do + visit root_path - expect(page).to have_content 'SampleMessage' + expect(page).not_to have_selector('.js-dismiss-current-broadcast-notification') + end end - it 'hides broadcast message after dismiss', :js do - visit root_path + describe 'dismissable banner type' do + let!(:broadcast_message) { create(:broadcast_message, dismissable: true, message: 'SampleMessage') } - find('.js-dismiss-current-broadcast-notification').click + it_behaves_like 'a Broadcast Messages' - expect(page).not_to have_content 'SampleMessage' + it_behaves_like 'a dismissable Broadcast Messages' end - it 'broadcast message is still hidden after refresh', :js do - visit root_path + describe 'notification type' do + let!(:broadcast_message) { create(:broadcast_message, broadcast_type: 'notification', message: 'SampleMessage') } - find('.js-dismiss-current-broadcast-notification').click - visit root_path + it_behaves_like 'a Broadcast Messages' - expect(page).not_to have_content 'SampleMessage' + it_behaves_like 'a dismissable Broadcast Messages' end end diff --git a/spec/helpers/broadcast_messages_helper_spec.rb b/spec/helpers/broadcast_messages_helper_spec.rb index 7e181e429d7..69ff5f16ec1 100644 --- a/spec/helpers/broadcast_messages_helper_spec.rb +++ b/spec/helpers/broadcast_messages_helper_spec.rb @@ -14,7 +14,7 @@ describe BroadcastMessagesHelper do context 'when last broadcast message is hidden' do before do - helper.request.cookies["hide_broadcast_notification_message_#{broadcast_message_2.id}"] = 'true' + helper.request.cookies["hide_broadcast_message_#{broadcast_message_2.id}"] = 'true' end it { is_expected.to eq broadcast_message_1 } @@ -29,6 +29,10 @@ describe BroadcastMessagesHelper do describe 'broadcast_message' do let(:current_broadcast_message) { BroadcastMessage.new(message: 'Current Message') } + before do + allow(helper).to receive(:current_user).and_return(create(:user)) + end + it 'returns nil when no current message' do expect(helper.broadcast_message(nil)).to be_nil end diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb index 06ad0557e37..cee299522ce 100644 --- a/spec/lib/backup/manager_spec.rb +++ b/spec/lib/backup/manager_spec.rb @@ -214,6 +214,30 @@ describe Backup::Manager do end end + describe 'verify_backup_version' do + context 'on version mismatch' do + let(:gitlab_version) { Gitlab::VERSION } + + it 'stops the process' do + allow(YAML).to receive(:load_file) + .and_return({ gitlab_version: "not #{gitlab_version}" }) + + expect { subject.verify_backup_version }.to raise_error SystemExit + end + end + + context 'on version match' do + let(:gitlab_version) { Gitlab::VERSION } + + it 'does nothing' do + allow(YAML).to receive(:load_file) + .and_return({ gitlab_version: "#{gitlab_version}" }) + + expect { subject.verify_backup_version }.not_to raise_error + end + end + end + describe '#unpack' do context 'when there are no backup files in the directory' do before do @@ -292,6 +316,23 @@ describe Backup::Manager do expect(progress).to have_received(:puts).with(a_string_matching('done')) end end + + context 'when there is a non-tarred backup in the directory' do + before do + allow(Dir).to receieve(:glob).and_return( + [ + 'backup_information.yml' + ] + ) + + it 'selects the non-tarred backup to restore from' do + expect { subject.unpack }.to output.to_stdout + expect(progress).to have_received(:puts) + .with(a_string_matching('Non tarred backup found ')) + expect(Kernel).not_to receive(:system) + end + end + end end describe '#upload' do @@ -329,9 +370,7 @@ describe Backup::Manager do .with(hash_including(key: backup_filename, public: false)) .and_return(true) - Dir.chdir(Gitlab.config.backup.path) do - subject.upload - end + subject.upload end it 'adds the DIRECTORY environment variable if present' do @@ -341,9 +380,7 @@ describe Backup::Manager do .with(hash_including(key: "daily/#{backup_filename}", public: false)) .and_return(true) - Dir.chdir(Gitlab.config.backup.path) do - subject.upload - end + subject.upload end end @@ -373,9 +410,7 @@ describe Backup::Manager do .with(hash_excluding(public: false)) .and_return(true) - Dir.chdir(Gitlab.config.backup.path) do - subject.upload - end + subject.upload end end end diff --git a/spec/lib/gitlab/asset_proxy_spec.rb b/spec/lib/gitlab/asset_proxy_spec.rb index f5aa1819982..e406917a5a4 100644 --- a/spec/lib/gitlab/asset_proxy_spec.rb +++ b/spec/lib/gitlab/asset_proxy_spec.rb @@ -33,9 +33,15 @@ describe Gitlab::AssetProxy do expect(described_class.proxy_url(url)).to eq(proxied_url) end + it 'returns original URL for invalid domains' do + url = 'foo_bar://' + + expect(described_class.proxy_url(url)).to eq(url) + end + context 'whitelisted domain' do it 'returns original URL for single domain whitelist' do - url = 'http://gitlab.com/test.png' + url = 'http://gitlab.com/${default_branch}/test.png' expect(described_class.proxy_url(url)).to eq(url) end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index f95349a2125..a29c56c598f 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -240,7 +240,7 @@ describe Gitlab::GitAccess do let(:access) do described_class.new(actor, nil, protocol, authentication_abilities: authentication_abilities, - project_path: project_path, namespace_path: namespace_path, + repository_path: project_path, namespace_path: namespace_path, redirected_path: redirected_path) end @@ -259,7 +259,7 @@ describe Gitlab::GitAccess do let(:access) do described_class.new(actor, nil, protocol, authentication_abilities: authentication_abilities, - project_path: project_path, namespace_path: namespace_path, + repository_path: project_path, namespace_path: namespace_path, redirected_path: redirected_path) end @@ -453,7 +453,7 @@ describe Gitlab::GitAccess do let(:access) do described_class.new(actor, project, protocol, authentication_abilities: authentication_abilities, - project_path: project_path, namespace_path: namespace_path, + repository_path: project_path, namespace_path: namespace_path, redirected_path: redirected_path) end @@ -598,7 +598,7 @@ describe Gitlab::GitAccess do let(:public_project) { create(:project, :public, :repository) } let(:project_path) { public_project.path } let(:namespace_path) { public_project.namespace.path } - let(:access) { described_class.new(nil, public_project, 'web', authentication_abilities: [:download_code], project_path: project_path, namespace_path: namespace_path) } + let(:access) { described_class.new(nil, public_project, 'web', authentication_abilities: [:download_code], repository_path: project_path, namespace_path: namespace_path) } context 'when repository is enabled' do it 'give access to download code' do @@ -1203,7 +1203,7 @@ describe Gitlab::GitAccess do def access described_class.new(actor, project, protocol, authentication_abilities: authentication_abilities, - namespace_path: namespace_path, project_path: project_path, + namespace_path: namespace_path, repository_path: project_path, redirected_path: redirected_path, auth_result_type: auth_result_type) end diff --git a/spec/lib/gitlab/gl_repository/repo_type_spec.rb b/spec/lib/gitlab/gl_repository/repo_type_spec.rb index 7cf0442fbe1..6185b068d4c 100644 --- a/spec/lib/gitlab/gl_repository/repo_type_spec.rb +++ b/spec/lib/gitlab/gl_repository/repo_type_spec.rb @@ -5,46 +5,62 @@ describe Gitlab::GlRepository::RepoType do let_it_be(:project) { create(:project) } let_it_be(:personal_snippet) { create(:personal_snippet, author: project.owner) } let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.owner) } + let(:project_path) { project.repository.full_path } + let(:wiki_path) { project.wiki.repository.full_path } + let(:personal_snippet_path) { "snippets/#{personal_snippet.id}" } + let(:project_snippet_path) { "#{project.full_path}/snippets/#{project_snippet.id}" } describe Gitlab::GlRepository::PROJECT do it_behaves_like 'a repo type' do - let(:expected_identifier) { "project-#{project.id}" } let(:expected_id) { project.id.to_s } + let(:expected_identifier) { "project-#{expected_id}" } let(:expected_suffix) { '' } - let(:expected_repository) { project.repository } let(:expected_container) { project } + let(:expected_repository) { expected_container.repository } end it 'knows its type' do - expect(described_class).not_to be_wiki - expect(described_class).to be_project - expect(described_class).not_to be_snippet + aggregate_failures do + expect(described_class).not_to be_wiki + expect(described_class).to be_project + expect(described_class).not_to be_snippet + end end it 'checks if repository path is valid' do - expect(described_class.valid?(project.repository.full_path)).to be_truthy - expect(described_class.valid?(project.wiki.repository.full_path)).to be_truthy + aggregate_failures do + expect(described_class.valid?(project_path)).to be_truthy + expect(described_class.valid?(wiki_path)).to be_truthy + expect(described_class.valid?(personal_snippet_path)).to be_truthy + expect(described_class.valid?(project_snippet_path)).to be_truthy + end end end describe Gitlab::GlRepository::WIKI do it_behaves_like 'a repo type' do - let(:expected_identifier) { "wiki-#{project.id}" } let(:expected_id) { project.id.to_s } + let(:expected_identifier) { "wiki-#{expected_id}" } let(:expected_suffix) { '.wiki' } - let(:expected_repository) { project.wiki.repository } let(:expected_container) { project } + let(:expected_repository) { expected_container.wiki.repository } end it 'knows its type' do - expect(described_class).to be_wiki - expect(described_class).not_to be_project - expect(described_class).not_to be_snippet + aggregate_failures do + expect(described_class).to be_wiki + expect(described_class).not_to be_project + expect(described_class).not_to be_snippet + end end it 'checks if repository path is valid' do - expect(described_class.valid?(project.repository.full_path)).to be_falsey - expect(described_class.valid?(project.wiki.repository.full_path)).to be_truthy + aggregate_failures do + expect(described_class.valid?(project_path)).to be_falsey + expect(described_class.valid?(wiki_path)).to be_truthy + expect(described_class.valid?(personal_snippet_path)).to be_falsey + expect(described_class.valid?(project_snippet_path)).to be_falsey + end end end @@ -59,9 +75,20 @@ describe Gitlab::GlRepository::RepoType do end it 'knows its type' do - expect(described_class).to be_snippet - expect(described_class).not_to be_wiki - expect(described_class).not_to be_project + aggregate_failures do + expect(described_class).to be_snippet + expect(described_class).not_to be_wiki + expect(described_class).not_to be_project + end + end + + it 'checks if repository path is valid' do + aggregate_failures do + expect(described_class.valid?(project_path)).to be_falsey + expect(described_class.valid?(wiki_path)).to be_falsey + expect(described_class.valid?(personal_snippet_path)).to be_truthy + expect(described_class.valid?(project_snippet_path)).to be_truthy + end end end @@ -75,9 +102,20 @@ describe Gitlab::GlRepository::RepoType do end it 'knows its type' do - expect(described_class).to be_snippet - expect(described_class).not_to be_wiki - expect(described_class).not_to be_project + aggregate_failures do + expect(described_class).to be_snippet + expect(described_class).not_to be_wiki + expect(described_class).not_to be_project + end + end + + it 'checks if repository path is valid' do + aggregate_failures do + expect(described_class.valid?(project_path)).to be_falsey + expect(described_class.valid?(wiki_path)).to be_falsey + expect(described_class.valid?(personal_snippet_path)).to be_truthy + expect(described_class.valid?(project_snippet_path)).to be_truthy + end end end end diff --git a/spec/lib/gitlab/gl_repository_spec.rb b/spec/lib/gitlab/gl_repository_spec.rb index 3cfc4c2a132..858f436047e 100644 --- a/spec/lib/gitlab/gl_repository_spec.rb +++ b/spec/lib/gitlab/gl_repository_spec.rb @@ -5,13 +5,18 @@ require 'spec_helper' describe ::Gitlab::GlRepository do describe '.parse' do let_it_be(:project) { create(:project, :repository) } + let_it_be(:snippet) { create(:personal_snippet) } it 'parses a project gl_repository' do - expect(described_class.parse("project-#{project.id}")).to eq([project, Gitlab::GlRepository::PROJECT]) + expect(described_class.parse("project-#{project.id}")).to eq([project, project, Gitlab::GlRepository::PROJECT]) end it 'parses a wiki gl_repository' do - expect(described_class.parse("wiki-#{project.id}")).to eq([project, Gitlab::GlRepository::WIKI]) + expect(described_class.parse("wiki-#{project.id}")).to eq([project, project, Gitlab::GlRepository::WIKI]) + end + + it 'parses a snippet gl_repository' do + expect(described_class.parse("snippet-#{snippet.id}")).to eq([snippet, nil, Gitlab::GlRepository::SNIPPET]) end it 'throws an argument error on an invalid gl_repository type' do diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb index 3cbcae4cdeb..8dabe5a756b 100644 --- a/spec/lib/gitlab/path_regex_spec.rb +++ b/spec/lib/gitlab/path_regex_spec.rb @@ -411,4 +411,37 @@ describe Gitlab::PathRegex do it { is_expected.not_to match('git lab') } it { is_expected.not_to match('gitlab.git') } end + + shared_examples 'invalid snippet routes' do + it { is_expected.not_to match('gitlab-org/gitlab/snippets/1.git') } + it { is_expected.not_to match('snippets/1.git') } + it { is_expected.not_to match('gitlab-org/gitlab/snippets/') } + it { is_expected.not_to match('/gitlab-org/gitlab/snippets/1') } + it { is_expected.not_to match('gitlab-org/gitlab/snippets/foo') } + it { is_expected.not_to match('root/snippets/1') } + it { is_expected.not_to match('/snippets/1') } + it { is_expected.not_to match('snippets/') } + it { is_expected.not_to match('snippets/foo') } + end + + describe '.full_snippets_repository_path_regex' do + subject { described_class.full_snippets_repository_path_regex } + + it { is_expected.to match('gitlab-org/gitlab/snippets/1') } + it { is_expected.to match('snippets/1') } + + it_behaves_like 'invalid snippet routes' + end + + describe '.personal_and_project_snippets_path_regex' do + subject { %r{\A#{described_class.personal_and_project_snippets_path_regex}\z} } + + it { is_expected.to match('gitlab-org/gitlab/snippets') } + it { is_expected.to match('snippets') } + + it { is_expected.not_to match('gitlab-org/gitlab/snippets/1') } + it { is_expected.not_to match('snippets/1') } + + it_behaves_like 'invalid snippet routes' + end end diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb index 2aeb69db2cb..e72bdc01940 100644 --- a/spec/lib/gitlab/repo_path_spec.rb +++ b/spec/lib/gitlab/repo_path_spec.rb @@ -3,60 +3,72 @@ require 'spec_helper' describe ::Gitlab::RepoPath do - describe '.parse' do - let_it_be(:project) { create(:project, :repository) } + include Gitlab::Routing + + let_it_be(:project) { create(:project, :repository) } + let_it_be(:personal_snippet) { create(:personal_snippet) } + let_it_be(:project_snippet) { create(:project_snippet, project: project) } + let_it_be(:redirect) { project.route.create_redirect('foo/bar/baz') } + describe '.parse' do context 'a repository storage path' do - it 'parses a full repository path' do - expect(described_class.parse(project.repository.full_path)).to eq([project, Gitlab::GlRepository::PROJECT, nil]) + it 'parses a full repository project path' do + expect(described_class.parse(project.repository.full_path)).to eq([project, project, Gitlab::GlRepository::PROJECT, nil]) + end + + it 'parses a full wiki project path' do + expect(described_class.parse(project.wiki.repository.full_path)).to eq([project, project, Gitlab::GlRepository::WIKI, nil]) + end + + it 'parses a personal snippet repository path' do + expect(described_class.parse("snippets/#{personal_snippet.id}")).to eq([personal_snippet, nil, Gitlab::GlRepository::SNIPPET, nil]) end - it 'parses a full wiki path' do - expect(described_class.parse(project.wiki.repository.full_path)).to eq([project, Gitlab::GlRepository::WIKI, nil]) + it 'parses a project snippet repository path' do + expect(described_class.parse("#{project.full_path}/snippets/#{project_snippet.id}")).to eq([project_snippet, project, Gitlab::GlRepository::SNIPPET, nil]) end end context 'a relative path' do it 'parses a relative repository path' do - expect(described_class.parse(project.full_path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, nil]) + expect(described_class.parse(project.full_path + '.git')).to eq([project, project, Gitlab::GlRepository::PROJECT, nil]) end it 'parses a relative wiki path' do - expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, Gitlab::GlRepository::WIKI, nil]) + expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, project, Gitlab::GlRepository::WIKI, nil]) end it 'parses a relative path starting with /' do - expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, nil]) + expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, project, Gitlab::GlRepository::PROJECT, nil]) end context 'of a redirected project' do let(:redirect) { project.route.create_redirect('foo/bar') } it 'parses a relative repository path' do - expect(described_class.parse(redirect.path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, 'foo/bar']) + expect(described_class.parse(redirect.path + '.git')).to eq([project, project, Gitlab::GlRepository::PROJECT, 'foo/bar']) end it 'parses a relative wiki path' do - expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, Gitlab::GlRepository::WIKI, 'foo/bar.wiki']) + expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, project, Gitlab::GlRepository::WIKI, 'foo/bar.wiki']) end it 'parses a relative path starting with /' do - expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, 'foo/bar']) + expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, project, Gitlab::GlRepository::PROJECT, 'foo/bar']) + end + + it 'parses a redirected project snippet repository path' do + expect(described_class.parse(redirect.path + "/snippets/#{project_snippet.id}.git")).to eq([project_snippet, project, Gitlab::GlRepository::SNIPPET, "foo/bar/snippets/#{project_snippet.id}"]) end end end - it "returns the default type for non existent paths" do - _project, type, _redirected = described_class.parse("path/non-existent.git") - - expect(type).to eq(Gitlab::GlRepository.default_type) + it 'returns the default type for non existent paths' do + expect(described_class.parse('path/non-existent.git')).to eq([nil, nil, Gitlab::GlRepository.default_type, nil]) end end describe '.find_project' do - let(:project) { create(:project) } - let(:redirect) { project.route.create_redirect('foo/bar/baz') } - context 'when finding a project by its canonical path' do context 'when the cases match' do it 'returns the project and false' do @@ -81,4 +93,34 @@ describe ::Gitlab::RepoPath do end end end + + describe '.find_snippet' do + it 'extracts path and id from personal snippet route' do + expect(described_class.find_snippet("snippets/#{personal_snippet.id}")).to eq([personal_snippet, false]) + end + + it 'extracts path and id from project snippet route' do + expect(described_class.find_snippet("#{project.full_path}/snippets/#{project_snippet.id}")).to eq([project_snippet, false]) + end + + it 'returns nil for invalid snippet paths' do + aggregate_failures do + expect(described_class.find_snippet("snippets/#{project_snippet.id}")).to eq([nil, false]) + expect(described_class.find_snippet("#{project.full_path}/snippets/#{personal_snippet.id}")).to eq([nil, false]) + expect(described_class.find_snippet('')).to eq([nil, false]) + end + end + + it 'returns nil for snippets not associated with the project' do + snippet = create(:project_snippet) + + expect(described_class.find_snippet("#{project.full_path}/snippets/#{snippet.id}")).to eq([nil, false]) + end + + context 'when finding a project snippet via a redirect' do + it 'returns the project and true' do + expect(described_class.find_snippet("#{redirect.path}/snippets/#{project_snippet.id}")).to eq([project_snippet, true]) + end + end + end end diff --git a/spec/lib/gitlab/repository_cache_spec.rb b/spec/lib/gitlab/repository_cache_spec.rb index e787288fc51..be31be761ad 100644 --- a/spec/lib/gitlab/repository_cache_spec.rb +++ b/spec/lib/gitlab/repository_cache_spec.rb @@ -12,19 +12,44 @@ describe Gitlab::RepositoryCache do describe '#cache_key' do subject { cache.cache_key(:foo) } - it 'includes the namespace' do - expect(subject).to eq "foo:#{namespace}" + shared_examples 'cache_key examples' do + it 'includes the namespace' do + expect(subject).to eq "foo:#{namespace}" + end + + context 'with a given namespace' do + let(:extra_namespace) { 'my:data' } + let(:cache) do + described_class.new(repository, extra_namespace: extra_namespace, + backend: backend) + end + + it 'includes the full namespace' do + expect(subject).to eq "foo:#{namespace}:#{extra_namespace}" + end + end end - context 'with a given namespace' do - let(:extra_namespace) { 'my:data' } - let(:cache) do - described_class.new(repository, extra_namespace: extra_namespace, - backend: backend) + describe 'project repository' do + it_behaves_like 'cache_key examples' do + let(:repository) { project.repository } end + end + + describe 'personal snippet repository' do + let_it_be(:personal_snippet) { create(:personal_snippet) } + let(:namespace) { repository.full_path } + + it_behaves_like 'cache_key examples' do + let(:repository) { personal_snippet.repository } + end + end + + describe 'project snippet repository' do + let_it_be(:project_snippet) { create(:project_snippet, project: project) } - it 'includes the full namespace' do - expect(subject).to eq "foo:#{namespace}:#{extra_namespace}" + it_behaves_like 'cache_key examples' do + let(:repository) { project_snippet.repository } end end end diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb index de0f3602346..bcf27b338f6 100644 --- a/spec/lib/gitlab/repository_set_cache_spec.rb +++ b/spec/lib/gitlab/repository_set_cache_spec.rb @@ -11,16 +11,41 @@ describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do describe '#cache_key' do subject { cache.cache_key(:foo) } - it 'includes the namespace' do - is_expected.to eq("foo:#{namespace}:set") + shared_examples 'cache_key examples' do + it 'includes the namespace' do + is_expected.to eq("foo:#{namespace}:set") + end + + context 'with a given namespace' do + let(:extra_namespace) { 'my:data' } + let(:cache) { described_class.new(repository, extra_namespace: extra_namespace) } + + it 'includes the full namespace' do + is_expected.to eq("foo:#{namespace}:#{extra_namespace}:set") + end + end + end + + describe 'project repository' do + it_behaves_like 'cache_key examples' do + let(:repository) { project.repository } + end + end + + describe 'personal snippet repository' do + let_it_be(:personal_snippet) { create(:personal_snippet) } + let(:namespace) { repository.full_path } + + it_behaves_like 'cache_key examples' do + let(:repository) { personal_snippet.repository } + end end - context 'with a given namespace' do - let(:extra_namespace) { 'my:data' } - let(:cache) { described_class.new(repository, extra_namespace: extra_namespace) } + describe 'project snippet repository' do + let_it_be(:project_snippet) { create(:project_snippet, project: project) } - it 'includes the full namespace' do - is_expected.to eq("foo:#{namespace}:#{extra_namespace}:set") + it_behaves_like 'cache_key examples' do + let(:repository) { project_snippet.repository } end end end diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index d46c9747845..71a18e2fbec 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -211,20 +211,21 @@ describe Milestone, 'Milestoneish' do end end - describe '#complete?' do + describe '#complete?', :use_clean_rails_memory_store_caching do it 'returns false when has items opened' do expect(milestone.complete?(non_member)).to eq false end it 'returns true when all items are closed' do issue.close - merge_request.close + security_issue_1.close + security_issue_2.close expect(milestone.complete?(non_member)).to eq true end end - describe '#percent_complete' do + describe '#percent_complete', :use_clean_rails_memory_store_caching do context 'division by zero' do let(:new_milestone) { build_stubbed(:milestone) } @@ -233,34 +234,58 @@ describe Milestone, 'Milestoneish' do end describe '#count_issues_by_state' do - it 'does not count confidential issues for non project members' do - expect(milestone.closed_issues_count(non_member)).to eq 2 - expect(milestone.total_issues_count(non_member)).to eq 3 + describe '#total_issues_count', :use_clean_rails_memory_store_caching do + it 'counts all issues including confidential' do + expect(milestone.total_issues_count(guest)).to eq 9 + end end - it 'does not count confidential issues for project members with guest role' do - expect(milestone.closed_issues_count(guest)).to eq 2 - expect(milestone.total_issues_count(guest)).to eq 3 + describe '#opened_issues_count', :use_clean_rails_memory_store_caching do + it 'counts all open issues including confidential' do + expect(milestone.opened_issues_count(guest)).to eq 3 + end end - it 'counts confidential issues for author' do - expect(milestone.closed_issues_count(author)).to eq 4 - expect(milestone.total_issues_count(author)).to eq 6 + describe '#closed_issues_count', :use_clean_rails_memory_store_caching do + it 'counts all closed issues including confidential' do + expect(milestone.closed_issues_count(guest)).to eq 6 + end end - it 'counts confidential issues for assignee' do - expect(milestone.closed_issues_count(assignee)).to eq 4 - expect(milestone.total_issues_count(assignee)).to eq 6 - end + context 'when cached_milestone_issue_counters are disabled' do + before do + stub_feature_flags(cached_milestone_issue_counters: false) + end - it 'counts confidential issues for project members' do - expect(milestone.closed_issues_count(member)).to eq 6 - expect(milestone.total_issues_count(member)).to eq 9 - end + it 'does not count confidential issues for non project members' do + expect(milestone.closed_issues_count(non_member)).to eq 2 + expect(milestone.total_issues_count(non_member)).to eq 3 + end - it 'counts confidential issues for admin' do - expect(milestone.closed_issues_count(admin)).to eq 6 - expect(milestone.total_issues_count(admin)).to eq 9 + it 'does not count confidential issues for project members with guest role' do + expect(milestone.closed_issues_count(guest)).to eq 2 + expect(milestone.total_issues_count(guest)).to eq 3 + end + + it 'counts confidential issues for author' do + expect(milestone.closed_issues_count(author)).to eq 4 + expect(milestone.total_issues_count(author)).to eq 6 + end + + it 'counts confidential issues for assignee' do + expect(milestone.closed_issues_count(assignee)).to eq 4 + expect(milestone.total_issues_count(assignee)).to eq 6 + end + + it 'counts confidential issues for project members' do + expect(milestone.closed_issues_count(member)).to eq 6 + expect(milestone.total_issues_count(member)).to eq 9 + end + + it 'counts confidential issues for admin' do + expect(milestone.closed_issues_count(admin)).to eq 6 + expect(milestone.total_issues_count(admin)).to eq 9 + end end end diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/broadcast_messages_spec.rb index 17b25d5c7cc..9bfbbe0daab 100644 --- a/spec/requests/api/broadcast_messages_spec.rb +++ b/spec/requests/api/broadcast_messages_spec.rb @@ -17,7 +17,7 @@ describe API::BroadcastMessages do expect(response).to include_pagination_headers expect(json_response).to be_kind_of(Array) expect(json_response.first.keys) - .to match_array(%w(id message starts_at ends_at color font active target_path broadcast_type)) + .to match_array(%w(id message starts_at ends_at color font active target_path broadcast_type dismissable)) end end @@ -28,7 +28,7 @@ describe API::BroadcastMessages do expect(response).to have_gitlab_http_status(:ok) expect(json_response['id']).to eq message.id expect(json_response.keys) - .to match_array(%w(id message starts_at ends_at color font active target_path broadcast_type)) + .to match_array(%w(id message starts_at ends_at color font active target_path broadcast_type dismissable)) end end @@ -111,6 +111,15 @@ describe API::BroadcastMessages do expect(response).to have_gitlab_http_status(:bad_request) end + + it 'accepts an active dismissable value ' do + attrs = { message: 'new message', dismissable: true } + + post api('/broadcast_messages', admin), params: attrs + + expect(response).to have_gitlab_http_status(:created) + expect(json_response['dismissable']).to eq true + end end end @@ -187,6 +196,15 @@ describe API::BroadcastMessages do expect(response).to have_gitlab_http_status(:bad_request) end + + it 'accepts a new dismissable value ' do + attrs = { message: 'new message', dismissable: true } + + put api("/broadcast_messages/#{message.id}", admin), params: attrs + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['dismissable']).to eq true + end end end diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb index fe0a3ffebd3..1f20a088e4f 100644 --- a/spec/requests/api/internal/base_spec.rb +++ b/spec/requests/api/internal/base_spec.rb @@ -10,6 +10,9 @@ describe API::Internal::Base do let(:gl_repository) { "project-#{project.id}" } let(:reference_counter) { double('ReferenceCounter') } + let_it_be(:personal_snippet) { create(:personal_snippet, :repository, author: user) } + let_it_be(:project_snippet) { create(:project_snippet, :repository, author: user, project: project) } + describe "GET /internal/check" do it do expect_any_instance_of(Redis).to receive(:ping).and_return('PONG') @@ -312,6 +315,54 @@ describe API::Internal::Base do end end + context 'git push with personal snippet' do + it 'responds with success' do + push(key, personal_snippet) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response["status"]).to be_truthy + expect(json_response["gl_project_path"]).to eq(personal_snippet.repository.full_path) + expect(json_response["gl_repository"]).to eq("snippet-#{personal_snippet.id}") + expect(user.reload.last_activity_on).to be_nil + end + end + + context 'git pull with personal snippet' do + it 'responds with success' do + pull(key, personal_snippet) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response["status"]).to be_truthy + expect(json_response["gl_project_path"]).to eq(personal_snippet.repository.full_path) + expect(json_response["gl_repository"]).to eq("snippet-#{personal_snippet.id}") + expect(user.reload.last_activity_on).to eql(Date.today) + end + end + + context 'git push with project snippet' do + it 'responds with success' do + push(key, project_snippet) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response["status"]).to be_truthy + expect(json_response["gl_project_path"]).to eq(project_snippet.repository.full_path) + expect(json_response["gl_repository"]).to eq("snippet-#{project_snippet.id}") + expect(user.reload.last_activity_on).to be_nil + end + end + + context 'git pull with project snippet' do + it 'responds with success' do + pull(key, project_snippet) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response["status"]).to be_truthy + expect(json_response["gl_project_path"]).to eq(project_snippet.repository.full_path) + expect(json_response["gl_repository"]).to eq("snippet-#{project_snippet.id}") + expect(user.reload.last_activity_on).to eql(Date.today) + end + end + context "git pull" do before do allow(Feature).to receive(:persisted_names).and_return(%w[gitaly_mep_mep]) @@ -393,10 +444,28 @@ describe API::Internal::Base do end end - it_behaves_like 'storing arguments in the application context' do - let(:expected_params) { { user: key.user.username, project: project.full_path } } + context 'with Project' do + it_behaves_like 'storing arguments in the application context' do + let(:expected_params) { { user: key.user.username, project: project.full_path } } + + subject { push(key, project) } + end + end + + context 'with PersonalSnippet' do + it_behaves_like 'storing arguments in the application context' do + let(:expected_params) { { user: key.user.username } } + + subject { push(key, personal_snippet) } + end + end - subject { push(key, project) } + context 'with ProjectSnippet' do + it_behaves_like 'storing arguments in the application context' do + let(:expected_params) { { user: key.user.username, project: project_snippet.project.full_path } } + + subject { push(key, project_snippet) } + end end end @@ -450,7 +519,7 @@ describe API::Internal::Base do { authentication_abilities: [:read_project, :download_code, :push_code], namespace_path: project.namespace.path, - project_path: project.path, + repository_path: project.path, redirected_path: nil } ).and_return(access_checker) @@ -835,22 +904,60 @@ describe API::Internal::Base do allow_any_instance_of(Gitlab::Identifier).to receive(:identify).and_return(user) end - it 'executes PostReceiveService' do - message = <<~MESSAGE.strip - To create a merge request for #{branch_name}, visit: - http://#{Gitlab.config.gitlab.host}/#{project.full_path}/-/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name} - MESSAGE + context 'with Project' do + it 'executes PostReceiveService' do + message = <<~MESSAGE.strip + To create a merge request for #{branch_name}, visit: + http://#{Gitlab.config.gitlab.host}/#{project.full_path}/-/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name} + MESSAGE + + subject - subject + expect(json_response).to eq({ + 'messages' => [{ 'message' => message, 'type' => 'basic' }], + 'reference_counter_decreased' => true + }) + end - expect(json_response).to eq({ - 'messages' => [{ 'message' => message, 'type' => 'basic' }], - 'reference_counter_decreased' => true - }) + it_behaves_like 'storing arguments in the application context' do + let(:expected_params) { { user: user.username, project: project.full_path } } + end end - it_behaves_like 'storing arguments in the application context' do - let(:expected_params) { { user: user.username, project: project.full_path } } + context 'with PersonalSnippet' do + let(:gl_repository) { "snippet-#{personal_snippet.id}" } + + it 'executes PostReceiveService' do + subject + + expect(json_response).to eq({ + 'messages' => [], + 'reference_counter_decreased' => true + }) + end + + it_behaves_like 'storing arguments in the application context' do + let(:expected_params) { { user: key.user.username } } + let(:gl_repository) { "snippet-#{personal_snippet.id}" } + end + end + + context 'with ProjectSnippet' do + let(:gl_repository) { "snippet-#{project_snippet.id}" } + + it 'executes PostReceiveService' do + subject + + expect(json_response).to eq({ + 'messages' => [], + 'reference_counter_decreased' => true + }) + end + + it_behaves_like 'storing arguments in the application context' do + let(:expected_params) { { user: key.user.username, project: project_snippet.project.full_path } } + let(:gl_repository) { "snippet-#{project_snippet.id}" } + end end context 'with an orphaned write deploy key' do @@ -866,16 +973,32 @@ describe API::Internal::Base do end context 'when project is nil' do - let(:gl_repository) { 'project-foo' } + context 'with Project' do + let(:gl_repository) { 'project-foo' } - it 'does not try to notify that project moved' do - allow(Gitlab::GlRepository).to receive(:parse).and_return([nil, Gitlab::GlRepository::PROJECT]) + it 'does not try to notify that project moved' do + allow(Gitlab::GlRepository).to receive(:parse).and_return([nil, nil, Gitlab::GlRepository::PROJECT]) - expect(Gitlab::Checks::ProjectMoved).not_to receive(:fetch_message) + expect(Gitlab::Checks::ProjectMoved).not_to receive(:fetch_message) - subject + subject - expect(response).to have_gitlab_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'with PersonalSnippet' do + let(:gl_repository) { "snippet-#{personal_snippet.id}" } + + it 'does not try to notify that project moved' do + allow(Gitlab::GlRepository).to receive(:parse).and_return([personal_snippet, nil, Gitlab::GlRepository::PROJECT]) + + expect(Gitlab::Checks::ProjectMoved).not_to receive(:fetch_message) + + subject + + expect(response).to have_gitlab_http_status(:ok) + end end end end @@ -896,24 +1019,37 @@ describe API::Internal::Base do end end - def gl_repository_for(project_or_wiki) - case project_or_wiki + def gl_repository_for(container) + case container when ProjectWiki - Gitlab::GlRepository::WIKI.identifier_for_container(project_or_wiki.project) + Gitlab::GlRepository::WIKI.identifier_for_container(container.project) when Project - Gitlab::GlRepository::PROJECT.identifier_for_container(project_or_wiki) + Gitlab::GlRepository::PROJECT.identifier_for_container(container) + when Snippet + Gitlab::GlRepository::SNIPPET.identifier_for_container(container) else nil end end - def pull(key, project, protocol = 'ssh') + def full_path_for(container) + case container + when PersonalSnippet + "snippets/#{container.id}" + when ProjectSnippet + "#{container.project.full_path}/snippets/#{container.id}" + else + container.full_path + end + end + + def pull(key, container, protocol = 'ssh') post( api("/internal/allowed"), params: { key_id: key.id, - project: project.full_path, - gl_repository: gl_repository_for(project), + project: full_path_for(container), + gl_repository: gl_repository_for(container), action: 'git-upload-pack', secret_token: secret_token, protocol: protocol @@ -921,12 +1057,12 @@ describe API::Internal::Base do ) end - def push(key, project, protocol = 'ssh', env: nil) + def push(key, container, protocol = 'ssh', env: nil) params = { changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master', key_id: key.id, - project: project.full_path, - gl_repository: gl_repository_for(project), + project: full_path_for(container), + gl_repository: gl_repository_for(container), action: 'git-receive-pack', secret_token: secret_token, protocol: protocol, @@ -939,14 +1075,14 @@ describe API::Internal::Base do ) end - def archive(key, project) + def archive(key, container) post( api("/internal/allowed"), params: { ref: 'master', key_id: key.id, - project: project.full_path, - gl_repository: gl_repository_for(project), + project: full_path_for(container), + gl_repository: gl_repository_for(container), action: 'git-upload-archive', secret_token: secret_token, protocol: 'ssh' diff --git a/spec/requests/groups/milestones_controller_spec.rb b/spec/requests/groups/milestones_controller_spec.rb index 4d15aa43cd2..1c6743dc678 100644 --- a/spec/requests/groups/milestones_controller_spec.rb +++ b/spec/requests/groups/milestones_controller_spec.rb @@ -12,23 +12,45 @@ describe Groups::MilestonesController do end let!(:private_milestone) { create(:milestone, project: public_project_with_private_issues_and_mrs, title: 'project milestone') } - it 'avoids N+1 database queries' do - public_project = create(:project, :public, :merge_requests_enabled, :issues_enabled, group: public_group) - create(:milestone, project: public_project) + describe 'GET #index' do + it 'avoids N+1 database queries' do + public_project = create(:project, :public, :merge_requests_enabled, :issues_enabled, group: public_group) + create(:milestone, project: public_project) - control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) { get "/groups/#{public_group.to_param}/-/milestones.json" }.count + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) { get group_milestones_path(public_group, format: :json) }.count - projects = create_list(:project, 2, :public, :merge_requests_enabled, :issues_enabled, group: public_group) - projects.each do |project| - create(:milestone, project: project) + projects = create_list(:project, 2, :public, :merge_requests_enabled, :issues_enabled, group: public_group) + projects.each do |project| + create(:milestone, project: project) + end + + expect { get group_milestones_path(public_group, format: :json) }.not_to exceed_all_query_limit(control_count) + expect(response).to have_gitlab_http_status(:ok) + milestones = json_response + + expect(milestones.count).to eq(3) + expect(milestones.map {|x| x['title']}).not_to include(private_milestone.title) end + end - expect { get "/groups/#{public_group.to_param}/-/milestones.json" }.not_to exceed_all_query_limit(control_count) - expect(response).to have_gitlab_http_status(:ok) - milestones = json_response + describe 'GET #show' do + let(:milestone) { create(:milestone, group: public_group) } + let(:show_path) { group_milestone_path(public_group, milestone) } - expect(milestones.count).to eq(3) - expect(milestones.map {|x| x['title']}).not_to include(private_milestone.title) + it 'avoids N+1 database queries' do + projects = create_list(:project, 3, :public, :merge_requests_enabled, :issues_enabled, group: public_group) + projects.each do |project| + create_list(:issue, 2, milestone: milestone, project: project) + end + control = ActiveRecord::QueryRecorder.new(skip_cached: false) { get show_path } + + projects = create_list(:project, 3, :public, :merge_requests_enabled, :issues_enabled, group: public_group) + projects.each do |project| + create_list(:issue, 2, milestone: milestone, project: project) + end + + expect { get show_path }.not_to exceed_all_query_limit(control) + end end end end diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index eb3b1d7c754..141567045a2 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -178,35 +178,55 @@ describe Issues::CloseService do end context "valid params" do - before do + def close_issue perform_enqueued_jobs do described_class.new(project, user).close_issue(issue) end end it 'closes the issue' do + close_issue + expect(issue).to be_valid expect(issue).to be_closed end it 'records closed user' do + close_issue + expect(issue.closed_by_id).to be(user.id) end it 'sends email to user2 about assign of new issue', :sidekiq_might_not_need_inline do + close_issue + email = ActionMailer::Base.deliveries.last expect(email.to.first).to eq(user2.email) expect(email.subject).to include(issue.title) end it 'creates system note about issue reassign' do + close_issue + note = issue.notes.last expect(note.note).to include "closed" end it 'marks todos as done' do + close_issue + expect(todo.reload).to be_done end + + it 'deletes milestone issue counters cache' do + issue.update(milestone: create(:milestone, project: project)) + + expect_next_instance_of(Milestones::ClosedIssuesCountService, issue.milestone) do |service| + expect(service).to receive(:delete_cache).and_call_original + end + + close_issue + end end context 'when issue is not confidential' do diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index c9701e5d194..09fff389cec 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -196,6 +196,14 @@ describe Issues::CreateService do end end end + + it 'deletes milestone issues count cache' do + expect_next_instance_of(Milestones::IssuesCountService, milestone) do |service| + expect(service).to receive(:delete_cache).and_call_original + end + + issue + end end context 'issue create service' do diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb index f04029e64aa..ca878ee947a 100644 --- a/spec/services/issues/reopen_service_spec.rb +++ b/spec/services/issues/reopen_service_spec.rb @@ -43,6 +43,16 @@ describe Issues::ReopenService do .to change { project.open_issues_count }.from(0).to(1) end + it 'deletes milestone issue counters cache' do + issue.update(milestone: create(:milestone, project: project)) + + expect_next_instance_of(Milestones::ClosedIssuesCountService, issue.milestone) do |service| + expect(service).to receive(:delete_cache).and_call_original + end + + described_class.new(project, user).execute(issue) + end + context 'when issue is not confidential' do it 'executes issue hooks' do expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks) diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 5da77dd914c..69e47d890a5 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -412,9 +412,24 @@ describe Issues::UpdateService, :mailer do should_email(subscriber) should_not_email(non_subscriber) end + + it 'clears milestone issue counters cache' do + issue.milestone = create(:milestone, project: project) + + issue.save + + expect_next_instance_of(Milestones::IssuesCountService, issue.milestone) do |service| + expect(service).to receive(:delete_cache).and_call_original + end + expect_next_instance_of(Milestones::ClosedIssuesCountService, issue.milestone) do |service| + expect(service).to receive(:delete_cache).and_call_original + end + + update_issue(milestone_id: "") + end end - context 'when the milestone is changed' do + context 'when the milestone is assigned' do before do stub_feature_flags(track_resource_milestone_change_events: false) end @@ -444,6 +459,43 @@ describe Issues::UpdateService, :mailer do should_email(subscriber) should_not_email(non_subscriber) end + + it 'deletes issue counters cache for the milestone' do + milestone = create(:milestone, project: project) + + expect_next_instance_of(Milestones::IssuesCountService, milestone) do |service| + expect(service).to receive(:delete_cache).and_call_original + end + expect_next_instance_of(Milestones::ClosedIssuesCountService, milestone) do |service| + expect(service).to receive(:delete_cache).and_call_original + end + + update_issue(milestone: milestone) + end + end + + context 'when the milestone is changed' do + it 'deletes issue counters cache for both milestones' do + old_milestone = create(:milestone, project: project) + new_milestone = create(:milestone, project: project) + + issue.update!(milestone: old_milestone) + + expect_next_instance_of(Milestones::IssuesCountService, old_milestone) do |service| + expect(service).to receive(:delete_cache).and_call_original + end + expect_next_instance_of(Milestones::ClosedIssuesCountService, old_milestone) do |service| + expect(service).to receive(:delete_cache).and_call_original + end + expect_next_instance_of(Milestones::IssuesCountService, new_milestone) do |service| + expect(service).to receive(:delete_cache).and_call_original + end + expect_next_instance_of(Milestones::ClosedIssuesCountService, new_milestone) do |service| + expect(service).to receive(:delete_cache).and_call_original + end + + update_issue(milestone: new_milestone) + end end context 'when the labels change' do diff --git a/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb index 744693dad15..2f03d18cd1f 100644 --- a/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb +++ b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb @@ -35,12 +35,18 @@ describe Metrics::Dashboard::CustomMetricEmbedService do it { is_expected.to be_truthy } - context 'not embedded' do + context 'missing embedded' do let(:params) { valid_params.except(:embedded) } it { is_expected.to be_falsey } end + context 'not embedded' do + let(:params) { valid_params.merge(embedded: 'false') } + + it { is_expected.to be_falsey } + end + context 'non-system dashboard' do let(:dashboard_path) { '.gitlab/dashboards/test.yml' } diff --git a/spec/services/metrics/dashboard/default_embed_service_spec.rb b/spec/services/metrics/dashboard/default_embed_service_spec.rb index 1b88276368c..8e32316433d 100644 --- a/spec/services/metrics/dashboard/default_embed_service_spec.rb +++ b/spec/services/metrics/dashboard/default_embed_service_spec.rb @@ -27,7 +27,7 @@ describe Metrics::Dashboard::DefaultEmbedService, :use_clean_rails_memory_store_ end context 'not embedded' do - let(:params) { { embedded: false } } + let(:params) { { embedded: 'false' } } it { is_expected.to be_falsey } end diff --git a/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb index c1ce9818f21..ee75284b4ce 100644 --- a/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb +++ b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb @@ -35,12 +35,18 @@ describe Metrics::Dashboard::DynamicEmbedService, :use_clean_rails_memory_store_ it { is_expected.to be_truthy } - context 'not embedded' do + context 'missing embedded' do let(:params) { valid_params.except(:embedded) } it { is_expected.to be_falsey } end + context 'not embedded' do + let(:params) { valid_params.merge(embedded: 'false') } + + it { is_expected.to be_falsey } + end + context 'undefined dashboard' do let(:params) { valid_params.except(:dashboard_path) } diff --git a/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb b/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb index a772b911d8a..034d6aba5d6 100644 --- a/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb +++ b/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb @@ -28,12 +28,18 @@ describe Metrics::Dashboard::GrafanaMetricEmbedService do it { is_expected.to be_truthy } - context 'not embedded' do + context 'missing embedded' do let(:params) { valid_params.except(:embedded) } it { is_expected.to be_falsey } end + context 'not embedded' do + let(:params) { valid_params.merge(embedded: 'false') } + + it { is_expected.to be_falsey } + end + context 'undefined grafana_url' do let(:params) { valid_params.except(:grafana_url) } diff --git a/spec/services/milestones/closed_issues_count_service_spec.rb b/spec/services/milestones/closed_issues_count_service_spec.rb new file mode 100644 index 00000000000..b86eede2e22 --- /dev/null +++ b/spec/services/milestones/closed_issues_count_service_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Milestones::ClosedIssuesCountService, :use_clean_rails_memory_store_caching do + let(:project) { create(:project) } + let(:milestone) { create(:milestone, project: project) } + + before do + create(:issue, milestone: milestone, project: project) + create(:issue, :confidential, milestone: milestone, project: project) + + create(:issue, :closed, milestone: milestone, project: project) + create(:issue, :closed, :confidential, milestone: milestone, project: project) + end + + subject { described_class.new(milestone) } + + it_behaves_like 'a counter caching service' + + it 'counts closed issues including confidential' do + expect(subject.count).to eq(2) + end +end diff --git a/spec/services/milestones/issues_count_service_spec.rb b/spec/services/milestones/issues_count_service_spec.rb new file mode 100644 index 00000000000..22aea884424 --- /dev/null +++ b/spec/services/milestones/issues_count_service_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Milestones::IssuesCountService, :use_clean_rails_memory_store_caching do + let(:project) { create(:project) } + let(:milestone) { create(:milestone, project: project) } + + before do + create(:issue, milestone: milestone, project: project) + create(:issue, :confidential, milestone: milestone, project: project) + + create(:issue, :closed, milestone: milestone, project: project) + create(:issue, :closed, milestone: milestone, project: project) + end + + subject { described_class.new(milestone) } + + it_behaves_like 'a counter caching service' + + it 'counts all issues including confidential' do + expect(subject.count).to eq(4) + end +end diff --git a/spec/services/milestones/transfer_service_spec.rb b/spec/services/milestones/transfer_service_spec.rb index 711969ce504..9b087b07cea 100644 --- a/spec/services/milestones/transfer_service_spec.rb +++ b/spec/services/milestones/transfer_service_spec.rb @@ -40,6 +40,25 @@ describe Milestones::TransferService do expect(new_milestone.project_milestone?).to be_truthy end + it 'deletes milestone issue counters cache for both milestones' do + new_milestone = create(:milestone, project: project, title: group_milestone.title) + + expect_next_instance_of(Milestones::IssuesCountService, group_milestone) do |service| + expect(service).to receive(:delete_cache).and_call_original + end + expect_next_instance_of(Milestones::ClosedIssuesCountService, group_milestone) do |service| + expect(service).to receive(:delete_cache).and_call_original + end + expect_next_instance_of(Milestones::IssuesCountService, new_milestone) do |service| + expect(service).to receive(:delete_cache).and_call_original + end + expect_next_instance_of(Milestones::ClosedIssuesCountService, new_milestone) do |service| + expect(service).to receive(:delete_cache).and_call_original + end + + service.execute + end + it 'does not apply new project milestone to issues with project milestone' do service.execute diff --git a/spec/services/post_receive_service_spec.rb b/spec/services/post_receive_service_spec.rb index 64b4a1125e8..f3631ff922f 100644 --- a/spec/services/post_receive_service_spec.rb +++ b/spec/services/post_receive_service_spec.rb @@ -5,8 +5,10 @@ require 'spec_helper' describe PostReceiveService do include Gitlab::Routing - let_it_be(:project) { create(:project, :repository, :wiki_repo) } let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :repository, :wiki_repo, namespace: user.namespace) } + let_it_be(:project_snippet) { create(:project_snippet, :repository, project: project, author: user) } + let_it_be(:personal_snippet) { create(:personal_snippet, :repository, author: user) } let(:identifier) { 'key-123' } let(:gl_repository) { "project-#{project.id}" } @@ -14,6 +16,7 @@ describe PostReceiveService do let(:secret_token) { Gitlab::Shell.secret_token } let(:reference_counter) { double('ReferenceCounter') } let(:push_options) { ['ci.skip', 'another push option'] } + let(:repository) { project.repository } let(:changes) do "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{branch_name}" @@ -29,104 +32,173 @@ describe PostReceiveService do } end - let(:response) { PostReceiveService.new(user, project, params).execute } + let(:service) { described_class.new(user, repository, project, params) } + let(:response) { service.execute } subject { response.messages.as_json } - it 'enqueues a PostReceive worker job' do - expect(PostReceive).to receive(:perform_async) - .with(gl_repository, identifier, changes, { ci: { skip: true } }) + context 'when project is nil' do + let(:gl_repository) { "snippet-#{personal_snippet.id}" } + let(:project) { nil } + let(:repository) { personal_snippet.repository } - subject + it 'does not return error' do + expect(subject).to be_empty + end end - it 'decreases the reference counter and returns the result' do - expect(Gitlab::ReferenceCounter).to receive(:new).with(gl_repository) - .and_return(reference_counter) - expect(reference_counter).to receive(:decrease).and_return(true) + context 'when repository is nil' do + let(:repository) { nil } - expect(response.reference_counter_decreased).to be(true) + it 'does not return error' do + expect(subject).to be_empty + end end - it 'returns link to create new merge request' do - message = <<~MESSAGE.strip - To create a merge request for #{branch_name}, visit: - http://#{Gitlab.config.gitlab.host}/#{project.full_path}/-/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name} - MESSAGE + context 'when both repository and project are nil' do + let(:gl_repository) { "snippet-#{personal_snippet.id}" } + let(:project) { nil } + let(:repository) { nil } - expect(subject).to include(build_basic_message(message)) + it 'does not return error' do + expect(subject).to be_empty + end end - it 'returns the link to an existing merge request when it exists' do - merge_request = create(:merge_request, source_project: project, source_branch: branch_name, target_branch: 'master') - message = <<~MESSAGE.strip - View merge request for feature: - #{project_merge_request_url(project, merge_request)} - MESSAGE + shared_examples 'post_receive_service actions' do + it 'enqueues a PostReceive worker job' do + expect(PostReceive).to receive(:perform_async) + .with(gl_repository, identifier, changes, { ci: { skip: true } }) - expect(subject).to include(build_basic_message(message)) - end + subject + end - context 'when printing_merge_request_link_enabled is false' do - let(:project) { create(:project, printing_merge_request_link_enabled: false) } + it 'decreases the reference counter and returns the result' do + expect(Gitlab::ReferenceCounter).to receive(:new).with(gl_repository) + .and_return(reference_counter) + expect(reference_counter).to receive(:decrease).and_return(true) - it 'returns no merge request messages' do - expect(subject).to be_blank + expect(response.reference_counter_decreased).to be(true) end end - it 'does not invoke MergeRequests::PushOptionsHandlerService' do - expect(MergeRequests::PushOptionsHandlerService).not_to receive(:new) + context 'with Project' do + it_behaves_like 'post_receive_service actions' - subject - end + it 'returns link to create new merge request' do + message = <<~MESSAGE.strip + To create a merge request for #{branch_name}, visit: + http://#{Gitlab.config.gitlab.host}/#{project.full_path}/-/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name} + MESSAGE - context 'when there are merge_request push options' do - let(:params) { super().merge(push_options: ['merge_request.create']) } + expect(subject).to include(build_basic_message(message)) + end + + it 'returns the link to an existing merge request when it exists' do + merge_request = create(:merge_request, source_project: project, source_branch: branch_name, target_branch: 'master') + message = <<~MESSAGE.strip + View merge request for feature: + #{project_merge_request_url(project, merge_request)} + MESSAGE - before do - project.add_developer(user) + expect(subject).to include(build_basic_message(message)) end - it 'invokes MergeRequests::PushOptionsHandlerService' do - expect(MergeRequests::PushOptionsHandlerService).to receive(:new).and_call_original + context 'when printing_merge_request_link_enabled is false' do + let(:project) { create(:project, printing_merge_request_link_enabled: false) } - subject + it 'returns no merge request messages' do + expect(subject).to be_blank + end end - it 'creates a new merge request' do - expect { Sidekiq::Testing.fake! { subject } }.to change(MergeRequest, :count).by(1) + it 'does not invoke MergeRequests::PushOptionsHandlerService' do + expect(MergeRequests::PushOptionsHandlerService).not_to receive(:new) + + subject end - it 'links to the newly created merge request' do - message = <<~MESSAGE.strip - View merge request for #{branch_name}: - http://#{Gitlab.config.gitlab.host}/#{project.full_path}/-/merge_requests/1 - MESSAGE + context 'when there are merge_request push options' do + let(:params) { super().merge(push_options: ['merge_request.create']) } - expect(subject).to include(build_basic_message(message)) + before do + project.add_developer(user) + end + + it 'invokes MergeRequests::PushOptionsHandlerService' do + expect(MergeRequests::PushOptionsHandlerService).to receive(:new).and_call_original + + subject + end + + it 'creates a new merge request' do + expect { Sidekiq::Testing.fake! { subject } }.to change(MergeRequest, :count).by(1) + end + + it 'links to the newly created merge request' do + message = <<~MESSAGE.strip + View merge request for #{branch_name}: + http://#{Gitlab.config.gitlab.host}/#{project.full_path}/-/merge_requests/1 + MESSAGE + + expect(subject).to include(build_basic_message(message)) + end + + it 'adds errors on the service instance to warnings' do + expect_any_instance_of( + MergeRequests::PushOptionsHandlerService + ).to receive(:errors).at_least(:once).and_return(['my error']) + + message = "WARNINGS:\nError encountered with push options 'merge_request.create': my error" + + expect(subject).to include(build_alert_message(message)) + end + + it 'adds ActiveRecord errors on invalid MergeRequest records to warnings' do + invalid_merge_request = MergeRequest.new + invalid_merge_request.errors.add(:base, 'my error') + message = "WARNINGS:\nError encountered with push options 'merge_request.create': my error" + + expect_any_instance_of( + MergeRequests::CreateService + ).to receive(:execute).and_return(invalid_merge_request) + + expect(subject).to include(build_alert_message(message)) + end end + end + + context 'with PersonalSnippet' do + let(:gl_repository) { "snippet-#{personal_snippet.id}" } + let(:repository) { personal_snippet.repository } - it 'adds errors on the service instance to warnings' do - expect_any_instance_of( - MergeRequests::PushOptionsHandlerService - ).to receive(:errors).at_least(:once).and_return(['my error']) + it_behaves_like 'post_receive_service actions' - message = "WARNINGS:\nError encountered with push options 'merge_request.create': my error" + it 'does not return link to create new merge request' do + expect(subject).to be_empty + end + + it 'does not return the link to an existing merge request when it exists' do + create(:merge_request, source_project: project, source_branch: branch_name, target_branch: 'master') - expect(subject).to include(build_alert_message(message)) + expect(subject).to be_empty end + end - it 'adds ActiveRecord errors on invalid MergeRequest records to warnings' do - invalid_merge_request = MergeRequest.new - invalid_merge_request.errors.add(:base, 'my error') - message = "WARNINGS:\nError encountered with push options 'merge_request.create': my error" + context 'with ProjectSnippet' do + let(:gl_repository) { "snippet-#{project_snippet.id}" } + let(:repository) { project_snippet.repository } - expect_any_instance_of( - MergeRequests::CreateService - ).to receive(:execute).and_return(invalid_merge_request) + it_behaves_like 'post_receive_service actions' - expect(subject).to include(build_alert_message(message)) + it 'does not return link to create new merge request' do + expect(subject).to be_empty + end + + it 'does not return the link to an existing merge request when it exists' do + create(:merge_request, source_project: project, source_branch: branch_name, target_branch: 'master') + + expect(subject).to be_empty end end @@ -178,6 +250,50 @@ describe PostReceiveService do end end + describe '#process_mr_push_options' do + context 'when repository belongs to a snippet' do + context 'with PersonalSnippet' do + let(:repository) { personal_snippet.repository } + + it 'returns an error message' do + result = service.process_mr_push_options(push_options, changes) + + expect(result).to match('Push options are only supported for projects') + end + end + + context 'with ProjectSnippet' do + let(:repository) { project_snippet.repository } + + it 'returns an error message' do + result = service.process_mr_push_options(push_options, changes) + + expect(result).to match('Push options are only supported for projects') + end + end + end + end + + describe '#merge_request_urls' do + context 'when repository belongs to a snippet' do + context 'with PersonalSnippet' do + let(:repository) { personal_snippet.repository } + + it 'returns an empty array' do + expect(service.merge_request_urls).to be_empty + end + end + + context 'with ProjectSnippet' do + let(:repository) { project_snippet.repository } + + it 'returns an empty array' do + expect(service.merge_request_urls).to be_empty + end + end + end + end + def build_alert_message(message) { 'type' => 'alert', 'message' => message } end diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index e58919c8688..5e15ade492b 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -14,6 +14,14 @@ describe 'gitlab:app namespace rake task' do tars_glob.first end + def backup_files + %w(backup_information.yml artifacts.tar.gz builds.tar.gz lfs.tar.gz pages.tar.gz) + end + + def backup_directories + %w(db repositories) + end + before(:all) do Rake.application.rake_require 'tasks/gitlab/helpers' Rake.application.rake_require 'tasks/gitlab/backup' @@ -28,12 +36,16 @@ describe 'gitlab:app namespace rake task' do before do stub_env('force', 'yes') FileUtils.rm(tars_glob, force: true) + FileUtils.rm(backup_files, force: true) + FileUtils.rm_rf(backup_directories, secure: true) reenable_backup_sub_tasks stub_container_registry_config(enabled: enable_registry) end after do FileUtils.rm(tars_glob, force: true) + FileUtils.rm(backup_files, force: true) + FileUtils.rm_rf(backup_directories, secure: true) end def run_rake_task(task_name) @@ -62,15 +74,6 @@ describe 'gitlab:app namespace rake task' do let(:gitlab_version) { Gitlab::VERSION } - it 'fails on mismatch' do - allow(YAML).to receive(:load_file) - .and_return({ gitlab_version: "not #{gitlab_version}" }) - - expect do - expect { run_rake_task('gitlab:backup:restore') }.to output.to_stdout - end.to raise_error(SystemExit) - end - context 'restore with matching gitlab version' do before do allow(YAML).to receive(:load_file) @@ -241,7 +244,7 @@ describe 'gitlab:app namespace rake task' do ) expect(exit_status).to eq(0) - expect(tar_contents).to match('db/') + expect(tar_contents).to match('db') expect(tar_contents).to match('uploads.tar.gz') expect(tar_contents).to match('repositories/') expect(tar_contents).to match('builds.tar.gz') @@ -379,6 +382,50 @@ describe 'gitlab:app namespace rake task' do end end + describe 'skipping tar archive creation' do + before do + stub_env('SKIP', 'tar') + end + + it 'created files with backup content and no tar archive' do + expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout + + dir_contents = Dir.children(Gitlab.config.backup.path) + + expect(dir_contents).to contain_exactly( + 'backup_information.yml', + 'db', + 'uploads.tar.gz', + 'builds.tar.gz', + 'artifacts.tar.gz', + 'lfs.tar.gz', + 'pages.tar.gz', + 'registry.tar.gz', + 'repositories', + 'tmp' + ) + end + + it 'those component files can be restored from' do + expect { run_rake_task("gitlab:backup:create") }.to output.to_stdout + + allow(Rake::Task['gitlab:shell:setup']) + .to receive(:invoke).and_return(true) + + expect(Rake::Task['gitlab:db:drop_tables']).to receive :invoke + expect(Rake::Task['gitlab:backup:db:restore']).to receive :invoke + expect(Rake::Task['gitlab:backup:repo:restore']).to receive :invoke + expect(Rake::Task['gitlab:backup:uploads:restore']).to receive :invoke + expect(Rake::Task['gitlab:backup:builds:restore']).to receive :invoke + expect(Rake::Task['gitlab:backup:artifacts:restore']).to receive :invoke + expect(Rake::Task['gitlab:backup:pages:restore']).to receive :invoke + expect(Rake::Task['gitlab:backup:lfs:restore']).to receive :invoke + expect(Rake::Task['gitlab:backup:registry:restore']).to receive :invoke + expect(Rake::Task['gitlab:shell:setup']).to receive :invoke + expect { run_rake_task("gitlab:backup:restore") }.to output.to_stdout + end + end + describe "Human Readable Backup Name" do it 'name has human readable time' do expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout diff --git a/spec/views/shared/milestones/_issuable.html.haml_spec.rb b/spec/views/shared/milestones/_issuable.html.haml_spec.rb index 3c2b7c6305a..6e81fec79d4 100644 --- a/spec/views/shared/milestones/_issuable.html.haml_spec.rb +++ b/spec/views/shared/milestones/_issuable.html.haml_spec.rb @@ -3,19 +3,38 @@ require 'spec_helper' describe 'shared/milestones/_issuable.html.haml' do - let(:project) { create(:project) } - let(:user) { create(:user) } - let(:milestone) { create(:milestone, project: project) } - let(:issuable) { create(:issue, project: project, assignees: [user]) } + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + let_it_be(:milestone) { create(:milestone, project: project) } before do assign(:project, project) assign(:milestone, milestone) end - it 'avatar links to issues page' do - render 'shared/milestones/issuable', issuable: issuable, show_project_name: true + subject(:rendered) { render 'shared/milestones/issuable', issuable: issuable, show_project_name: true } - expect(rendered).to have_css("a[href='#{project_issues_path(project, milestone_title: milestone.title, assignee_id: user.id, state: 'all')}']") + context 'issue' do + let(:issuable) { create(:issue, project: project, assignees: [user]) } + + it 'links to the page for the issue' do + expect(rendered).to have_css("a[href='#{project_issue_path(project, issuable)}']", class: 'issue-link') + end + + it 'links to issues page for user' do + expect(rendered).to have_css("a[href='#{project_issues_path(project, milestone_title: milestone.title, assignee_id: user.id, state: 'all')}']") + end + end + + context 'merge request' do + let(:issuable) { create(:merge_request, source_project: project, target_project: project, assignees: [user]) } + + it 'links to merge requests page for user' do + expect(rendered).to have_css("a[href='#{project_merge_requests_path(project, milestone_title: milestone.title, assignee_id: user.id, state: 'all')}']") + end + + it 'links to the page for the merge request' do + expect(rendered).to have_css("a[href='#{project_merge_request_path(project, issuable)}']", class: 'issue-link') + end end end diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index 34aaa9bb1e9..72e423db611 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -34,6 +34,31 @@ describe PostReceive do expect(Gitlab::GitLogger).to receive(:error).with("POST-RECEIVE: #{error_message}") expect(perform).to be(false) end + + context 'with PersonalSnippet' do + let(:gl_repository) { "snippet-#{snippet.id}" } + let(:snippet) { create(:personal_snippet, author: project.owner) } + + it 'does not log an error' do + expect(Gitlab::GitLogger).not_to receive(:error) + expect(Gitlab::GitPostReceive).to receive(:new).and_call_original + expect_any_instance_of(described_class) do |instance| + expect(instance).to receive(:process_snippet_changes) + end + + perform + end + end + + context 'with ProjectSnippet' do + let(:gl_repository) { "snippet-#{snippet.id}" } + let(:snippet) { create(:snippet, type: 'ProjectSnippet', project: nil, author: project.owner) } + + it 'returns false and logs an error' do + expect(Gitlab::GitLogger).to receive(:error).with("POST-RECEIVE: #{error_message}") + expect(perform).to be(false) + end + end end describe "#process_project_changes" do @@ -44,7 +69,7 @@ describe PostReceive do before do allow_any_instance_of(Gitlab::GitPostReceive).to receive(:identify).and_return(empty_project.owner) # Need to mock here so we can expect calls on project - allow(Gitlab::GlRepository).to receive(:parse).and_return([empty_project, Gitlab::GlRepository::PROJECT]) + allow(Gitlab::GlRepository).to receive(:parse).and_return([empty_project, empty_project, Gitlab::GlRepository::PROJECT]) end it 'expire the status cache' do @@ -97,7 +122,7 @@ describe PostReceive do before do allow_any_instance_of(Gitlab::GitPostReceive).to receive(:identify).and_return(project.owner) - allow(Gitlab::GlRepository).to receive(:parse).and_return([project, Gitlab::GlRepository::PROJECT]) + allow(Gitlab::GlRepository).to receive(:parse).and_return([project, project, Gitlab::GlRepository::PROJECT]) end shared_examples 'updating remote mirrors' do @@ -176,7 +201,7 @@ describe PostReceive do end before do - expect(Gitlab::GlRepository).to receive(:parse).and_return([project, Gitlab::GlRepository::PROJECT]) + expect(Gitlab::GlRepository).to receive(:parse).and_return([project, project, Gitlab::GlRepository::PROJECT]) end it 'does not expire branches cache' do @@ -256,7 +281,7 @@ describe PostReceive do before do # Need to mock here so we can expect calls on project - allow(Gitlab::GlRepository).to receive(:parse).and_return([project, Gitlab::GlRepository::WIKI]) + allow(Gitlab::GlRepository).to receive(:parse).and_return([project, project, Gitlab::GlRepository::WIKI]) end it 'updates project activity' do @@ -333,4 +358,82 @@ describe PostReceive do perform end end + + describe '#process_snippet_changes' do + let(:gl_repository) { "snippet-#{snippet.id}" } + + before do + # Need to mock here so we can expect calls on project + allow(Gitlab::GlRepository).to receive(:parse).and_return([snippet, snippet.project, Gitlab::GlRepository::SNIPPET]) + end + + shared_examples 'snippet changes actions' do + context 'unidentified user' do + let!(:key_id) { '' } + + it 'returns false' do + expect(perform).to be false + end + end + + context 'with changes' do + context 'branches' do + let(:changes) do + <<~EOF + 123456 789012 refs/heads/tést1 + 123456 789012 refs/heads/tést2 + EOF + end + + it 'expires the branches cache' do + expect(snippet.repository).to receive(:expire_branches_cache).once + + perform + end + + it 'expires the status cache' do + expect(snippet.repository).to receive(:empty?).and_return(true) + expect(snippet.repository).to receive(:expire_status_cache) + + perform + end + end + + context 'tags' do + let(:changes) do + <<~EOF + 654321 210987 refs/tags/tag1 + 654322 210986 refs/tags/tag2 + 654323 210985 refs/tags/tag3 + EOF + end + + it 'does not expire branches cache' do + expect(snippet.repository).not_to receive(:expire_branches_cache) + + perform + end + + it 'only invalidates tags once' do + expect(snippet.repository).to receive(:expire_caches_for_tags).once.and_call_original + expect(snippet.repository).to receive(:expire_tags_cache).once.and_call_original + + perform + end + end + end + end + + context 'with PersonalSnippet' do + let!(:snippet) { create(:personal_snippet, author: project.owner) } + + it_behaves_like 'snippet changes actions' + end + + context 'with ProjectSnippet' do + let!(:snippet) { create(:project_snippet, project: project, author: project.owner) } + + it_behaves_like 'snippet changes actions' + end + end end |