diff options
Diffstat (limited to 'spec/lib')
48 files changed, 1443 insertions, 2131 deletions
diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb index c73c6023b60..0a7682d906b 100644 --- a/spec/lib/api/helpers/pagination_spec.rb +++ b/spec/lib/api/helpers/pagination_spec.rb @@ -189,9 +189,9 @@ describe API::Helpers::Pagination do it 'it returns the right link to the next page' do allow(subject).to receive(:params) .and_return({ pagination: 'keyset', ks_prev_id: projects[3].id, ks_prev_name: projects[3].name, per_page: 2 }) + expect_header('X-Per-Page', '2') expect_header('X-Next-Page', "#{value}?ks_prev_id=#{projects[6].id}&ks_prev_name=#{projects[6].name}&pagination=keyset&per_page=2") - expect_header('Link', anything) do |_key, val| expect(val).to include('rel="next"') end diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb index a515d07b072..cf49249756a 100644 --- a/spec/lib/banzai/filter/markdown_filter_spec.rb +++ b/spec/lib/banzai/filter/markdown_filter_spec.rb @@ -40,6 +40,12 @@ describe Banzai::Filter::MarkdownFilter do expect(result).to start_with("<pre><code>") end + + it 'works with utf8 chars in language' do + result = filter("```æ—¥\nsome code\n```") + + expect(result).to start_with("<pre><code lang=\"æ—¥\">") + end end context 'using Redcarpet' do @@ -60,4 +66,21 @@ describe Banzai::Filter::MarkdownFilter do end end end + + describe 'footnotes in tables' do + it 'processes footnotes in table cells' do + text = <<-MD.strip_heredoc + | Column1 | + | --------- | + | foot [^1] | + + [^1]: a footnote + MD + + result = filter(text) + + expect(result).to include('<td>foot <sup') + expect(result).to include('<section class="footnotes">') + end + end end diff --git a/spec/lib/banzai/filter/spaced_link_filter_spec.rb b/spec/lib/banzai/filter/spaced_link_filter_spec.rb index 4463c011522..1ad7f3ff567 100644 --- a/spec/lib/banzai/filter/spaced_link_filter_spec.rb +++ b/spec/lib/banzai/filter/spaced_link_filter_spec.rb @@ -3,49 +3,73 @@ require 'spec_helper' describe Banzai::Filter::SpacedLinkFilter do include FilterSpecHelper - let(:link) { '[example](page slug)' } + let(:link) { '[example](page slug)' } + let(:image) { '![example](img test.jpg)' } - it 'converts slug with spaces to a link' do - doc = filter("See #{link}") + context 'when a link is detected' do + it 'converts slug with spaces to a link' do + doc = filter("See #{link}") - expect(doc.at_css('a').text).to eq 'example' - expect(doc.at_css('a')['href']).to eq 'page%20slug' - expect(doc.at_css('p')).to eq nil - end + expect(doc.at_css('a').text).to eq 'example' + expect(doc.at_css('a')['href']).to eq 'page%20slug' + expect(doc.at_css('a')['title']).to be_nil + expect(doc.at_css('p')).to be_nil + end - it 'converts slug with spaces and a title to a link' do - link = '[example](page slug "title")' - doc = filter("See #{link}") + it 'converts slug with spaces and a title to a link' do + link = '[example](page slug "title")' + doc = filter("See #{link}") - expect(doc.at_css('a').text).to eq 'example' - expect(doc.at_css('a')['href']).to eq 'page%20slug' - expect(doc.at_css('a')['title']).to eq 'title' - expect(doc.at_css('p')).to eq nil - end + expect(doc.at_css('a').text).to eq 'example' + expect(doc.at_css('a')['href']).to eq 'page%20slug' + expect(doc.at_css('a')['title']).to eq 'title' + expect(doc.at_css('p')).to be_nil + end - it 'does nothing when markdown_engine is redcarpet' do - exp = act = link - expect(filter(act, markdown_engine: :redcarpet).to_html).to eq exp - end + it 'does nothing when markdown_engine is redcarpet' do + exp = act = link + expect(filter(act, markdown_engine: :redcarpet).to_html).to eq exp + end + + it 'does nothing with empty text' do + link = '[](page slug)' + doc = filter("See #{link}") + + expect(doc.at_css('a')).to be_nil + end - it 'does nothing with empty text' do - link = '[](page slug)' - doc = filter("See #{link}") + it 'does nothing with an empty slug' do + link = '[example]()' + doc = filter("See #{link}") - expect(doc.at_css('a')).to eq nil + expect(doc.at_css('a')).to be_nil + end end - it 'does nothing with an empty slug' do - link = '[example]()' - doc = filter("See #{link}") + context 'when an image is detected' do + it 'converts slug with spaces to an iamge' do + doc = filter("See #{image}") + + expect(doc.at_css('img')['src']).to eq 'img%20test.jpg' + expect(doc.at_css('img')['alt']).to eq 'example' + expect(doc.at_css('p')).to be_nil + end + + it 'converts slug with spaces and a title to an image' do + image = '![example](img test.jpg "title")' + doc = filter("See #{image}") - expect(doc.at_css('a')).to eq nil + expect(doc.at_css('img')['src']).to eq 'img%20test.jpg' + expect(doc.at_css('img')['alt']).to eq 'example' + expect(doc.at_css('img')['title']).to eq 'title' + expect(doc.at_css('p')).to be_nil + end end it 'converts multiple URLs' do link1 = '[first](slug one)' link2 = '[second](http://example.com/slug two)' - doc = filter("See #{link1} and #{link2}") + doc = filter("See #{link1} and #{image} and #{link2}") found_links = doc.css('a') @@ -54,6 +78,12 @@ describe Banzai::Filter::SpacedLinkFilter do expect(found_links[0]['href']).to eq 'slug%20one' expect(found_links[1].text).to eq 'second' expect(found_links[1]['href']).to eq 'http://example.com/slug%20two' + + found_images = doc.css('img') + + expect(found_images.size).to eq(1) + expect(found_images[0]['src']).to eq 'img%20test.jpg' + expect(found_images[0]['alt']).to eq 'example' end described_class::IGNORE_PARENTS.each do |elem| diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb index 75413596431..df24cef0b8b 100644 --- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb @@ -87,4 +87,22 @@ describe Banzai::Pipeline::GfmPipeline do end end end + + describe 'markdown link or image urls having spaces' do + let(:project) { create(:project, :public) } + + it 'rewrites links with spaces in url' do + markdown = "[Link to Page](page slug)" + output = described_class.to_html(markdown, project: project) + + expect(output).to include("href=\"page%20slug\"") + end + + it 'rewrites images with spaces in url' do + markdown = "![My Image](test image.png)" + output = described_class.to_html(markdown, project: project) + + expect(output).to include("src=\"test%20image.png\"") + end + end end diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb index 52b8c9be647..64ca3ec345d 100644 --- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb @@ -178,4 +178,25 @@ describe Banzai::Pipeline::WikiPipeline do end end end + + describe 'videos' do + let(:namespace) { create(:namespace, name: "wiki_link_ns") } + let(:project) { create(:project, :public, name: "wiki_link_project", namespace: namespace) } + let(:project_wiki) { ProjectWiki.new(project, double(:user)) } + let(:page) { build(:wiki_page, wiki: project_wiki, page: OpenStruct.new(url_path: 'nested/twice/start-page')) } + + it 'generates video html structure' do + markdown = "![video_file](video_file_name.mp4)" + output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) + + expect(output).to include('<video src="/wiki_link_ns/wiki_link_project/wikis/nested/twice/video_file_name.mp4"') + end + + it 'rewrites and replaces video links names with white spaces to %20' do + markdown = "![video file](video file name.mp4)" + output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) + + expect(output).to include('<video src="/wiki_link_ns/wiki_link_project/wikis/nested/twice/video%20file%20name.mp4"') + end + end end diff --git a/spec/lib/forever_spec.rb b/spec/lib/forever_spec.rb index cf40c467c72..494c0561975 100644 --- a/spec/lib/forever_spec.rb +++ b/spec/lib/forever_spec.rb @@ -7,6 +7,7 @@ describe Forever do context 'when using PostgreSQL' do it 'should return Postgresql future date' do allow(Gitlab::Database).to receive(:postgresql?).and_return(true) + expect(subject).to eq(described_class::POSTGRESQL_DATE) end end @@ -14,6 +15,7 @@ describe Forever do context 'when using MySQL' do it 'should return MySQL future date' do allow(Gitlab::Database).to receive(:postgresql?).and_return(false) + expect(subject).to eq(described_class::MYSQL_DATE) end end diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index 5a78ce783dd..b43aca8a354 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -124,4 +124,237 @@ describe Gitlab::Ci::Config do end end end + + context "when using 'include' directive" do + let(:project) { create(:project, :repository) } + let(:remote_location) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } + let(:local_location) { 'spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml' } + + let(:remote_file_content) do + <<~HEREDOC + variables: + AUTO_DEVOPS_DOMAIN: domain.example.com + POSTGRES_USER: user + POSTGRES_PASSWORD: testing-password + POSTGRES_ENABLED: "true" + POSTGRES_DB: $CI_ENVIRONMENT_SLUG + HEREDOC + end + + let(:local_file_content) do + File.read(Rails.root.join(local_location)) + end + + let(:gitlab_ci_yml) do + <<~HEREDOC + include: + - #{local_location} + - #{remote_location} + + image: ruby:2.2 + HEREDOC + end + + let(:config) do + described_class.new(gitlab_ci_yml, project: project, sha: '12345') + end + + before do + WebMock.stub_request(:get, remote_location) + .to_return(body: remote_file_content) + + allow(project.repository) + .to receive(:blob_data_at).and_return(local_file_content) + end + + context "when gitlab_ci_yml has valid 'include' defined" do + it 'should return a composed hash' do + before_script_values = [ + "apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs", "ruby -v", + "which ruby", + "gem install bundler --no-ri --no-rdoc", + "bundle install --jobs $(nproc) \"${FLAGS[@]}\"" + ] + variables = { + AUTO_DEVOPS_DOMAIN: "domain.example.com", + POSTGRES_USER: "user", + POSTGRES_PASSWORD: "testing-password", + POSTGRES_ENABLED: "true", + POSTGRES_DB: "$CI_ENVIRONMENT_SLUG" + } + composed_hash = { + before_script: before_script_values, + image: "ruby:2.2", + rspec: { script: ["bundle exec rspec"] }, + variables: variables + } + + expect(config.to_hash).to eq(composed_hash) + end + end + + context "when gitlab_ci.yml has invalid 'include' defined" do + let(:gitlab_ci_yml) do + <<~HEREDOC + include: invalid + HEREDOC + end + + it 'raises error YamlProcessor validationError' do + expect { config }.to raise_error( + ::Gitlab::Ci::YamlProcessor::ValidationError, + "Local file 'invalid' is not valid." + ) + end + end + + describe 'external file version' do + context 'when external local file SHA is defined' do + it 'is using a defined value' do + expect(project.repository).to receive(:blob_data_at) + .with('eeff1122', local_location) + + described_class.new(gitlab_ci_yml, project: project, sha: 'eeff1122') + end + end + + context 'when external local file SHA is not defined' do + it 'is using latest SHA on the default branch' do + expect(project.repository).to receive(:root_ref_sha) + + described_class.new(gitlab_ci_yml, project: project) + end + end + end + + context "when both external files and gitlab_ci.yml defined the same key" do + let(:gitlab_ci_yml) do + <<~HEREDOC + include: + - #{remote_location} + + image: ruby:2.2 + HEREDOC + end + + let(:remote_file_content) do + <<~HEREDOC + image: php:5-fpm-alpine + HEREDOC + end + + it 'should take precedence' do + expect(config.to_hash).to eq({ image: 'ruby:2.2' }) + end + end + + context "when both external files and gitlab_ci.yml define a dictionary of distinct variables" do + let(:remote_file_content) do + <<~HEREDOC + variables: + A: 'alpha' + B: 'beta' + HEREDOC + end + + let(:gitlab_ci_yml) do + <<~HEREDOC + include: + - #{remote_location} + + variables: + C: 'gamma' + D: 'delta' + HEREDOC + end + + it 'should merge the variables dictionaries' do + expect(config.to_hash).to eq({ variables: { A: 'alpha', B: 'beta', C: 'gamma', D: 'delta' } }) + end + end + + context "when both external files and gitlab_ci.yml define a dictionary of overlapping variables" do + let(:remote_file_content) do + <<~HEREDOC + variables: + A: 'alpha' + B: 'beta' + C: 'omnicron' + HEREDOC + end + + let(:gitlab_ci_yml) do + <<~HEREDOC + include: + - #{remote_location} + + variables: + C: 'gamma' + D: 'delta' + HEREDOC + end + + it 'later declarations should take precedence' do + expect(config.to_hash).to eq({ variables: { A: 'alpha', B: 'beta', C: 'gamma', D: 'delta' } }) + end + end + + context 'when both external files and gitlab_ci.yml define a job' do + let(:remote_file_content) do + <<~HEREDOC + job1: + script: + - echo 'hello from remote file' + HEREDOC + end + + let(:gitlab_ci_yml) do + <<~HEREDOC + include: + - #{remote_location} + + job1: + variables: + VARIABLE_DEFINED_IN_MAIN_FILE: 'some value' + HEREDOC + end + + it 'merges the jobs' do + expect(config.to_hash).to eq({ + job1: { + script: ["echo 'hello from remote file'"], + variables: { + VARIABLE_DEFINED_IN_MAIN_FILE: 'some value' + } + } + }) + end + + context 'when the script key is in both' do + let(:gitlab_ci_yml) do + <<~HEREDOC + include: + - #{remote_location} + + job1: + script: + - echo 'hello from main file' + variables: + VARIABLE_DEFINED_IN_MAIN_FILE: 'some value' + HEREDOC + end + + it 'uses the script from the gitlab_ci.yml' do + expect(config.to_hash).to eq({ + job1: { + script: ["echo 'hello from main file'"], + variables: { + VARIABLE_DEFINED_IN_MAIN_FILE: 'some value' + } + } + }) + end + end + end + end end diff --git a/spec/lib/gitlab/ci/external/file/local_spec.rb b/spec/lib/gitlab/ci/external/file/local_spec.rb new file mode 100644 index 00000000000..3f32d81a827 --- /dev/null +++ b/spec/lib/gitlab/ci/external/file/local_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::External::File::Local do + let(:project) { create(:project, :repository) } + let(:local_file) { described_class.new(location, { project: project, sha: '12345' }) } + + describe '#valid?' do + context 'when is a valid local path' do + let(:location) { '/vendor/gitlab-ci-yml/existent-file.yml' } + + before do + allow_any_instance_of(described_class).to receive(:fetch_local_content).and_return("image: 'ruby2:2'") + end + + it 'should return true' do + expect(local_file.valid?).to be_truthy + end + end + + context 'when is not a valid local path' do + let(:location) { '/vendor/gitlab-ci-yml/non-existent-file.yml' } + + it 'should return false' do + expect(local_file.valid?).to be_falsy + end + end + + context 'when is not a yaml file' do + let(:location) { '/config/application.rb' } + + it 'should return false' do + expect(local_file.valid?).to be_falsy + end + end + end + + describe '#content' do + context 'with a a valid file' do + let(:local_file_content) do + <<~HEREDOC + before_script: + - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs + - ruby -v + - which ruby + - gem install bundler --no-ri --no-rdoc + - bundle install --jobs $(nproc) "${FLAGS[@]}" + HEREDOC + end + let(:location) { '/vendor/gitlab-ci-yml/existent-file.yml' } + + before do + allow_any_instance_of(described_class).to receive(:fetch_local_content).and_return(local_file_content) + end + + it 'should return the content of the file' do + expect(local_file.content).to eq(local_file_content) + end + end + + context 'with an invalid file' do + let(:location) { '/vendor/gitlab-ci-yml/non-existent-file.yml' } + + it 'should be nil' do + expect(local_file.content).to be_nil + end + end + end + + describe '#error_message' do + let(:location) { '/vendor/gitlab-ci-yml/non-existent-file.yml' } + + it 'should return an error message' do + expect(local_file.error_message).to eq("Local file '#{location}' is not valid.") + end + end +end diff --git a/spec/lib/gitlab/ci/external/file/remote_spec.rb b/spec/lib/gitlab/ci/external/file/remote_spec.rb new file mode 100644 index 00000000000..b1819c8960b --- /dev/null +++ b/spec/lib/gitlab/ci/external/file/remote_spec.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::External::File::Remote do + let(:remote_file) { described_class.new(location) } + let(:location) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } + let(:remote_file_content) do + <<~HEREDOC + before_script: + - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs + - ruby -v + - which ruby + - gem install bundler --no-ri --no-rdoc + - bundle install --jobs $(nproc) "${FLAGS[@]}" + HEREDOC + end + + describe "#valid?" do + context 'when is a valid remote url' do + before do + WebMock.stub_request(:get, location).to_return(body: remote_file_content) + end + + it 'should return true' do + expect(remote_file.valid?).to be_truthy + end + end + + context 'with an irregular url' do + let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } + + it 'should return false' do + expect(remote_file.valid?).to be_falsy + end + end + + context 'with a timeout' do + before do + allow(Gitlab::HTTP).to receive(:get).and_raise(Timeout::Error) + end + + it 'should be falsy' do + expect(remote_file.valid?).to be_falsy + end + end + + context 'when is not a yaml file' do + let(:location) { 'https://asdasdasdaj48ggerexample.com' } + + it 'should be falsy' do + expect(remote_file.valid?).to be_falsy + end + end + + context 'with an internal url' do + let(:location) { 'http://localhost:8080' } + + it 'should be falsy' do + expect(remote_file.valid?).to be_falsy + end + end + end + + describe "#content" do + context 'with a valid remote file' do + before do + WebMock.stub_request(:get, location).to_return(body: remote_file_content) + end + + it 'should return the content of the file' do + expect(remote_file.content).to eql(remote_file_content) + end + end + + context 'with a timeout' do + before do + allow(Gitlab::HTTP).to receive(:get).and_raise(Timeout::Error) + end + + it 'should be falsy' do + expect(remote_file.content).to be_falsy + end + end + + context 'with an invalid remote url' do + let(:location) { 'https://asdasdasdaj48ggerexample.com' } + + before do + WebMock.stub_request(:get, location).to_raise(SocketError.new('Some HTTP error')) + end + + it 'should be nil' do + expect(remote_file.content).to be_nil + end + end + + context 'with an internal url' do + let(:location) { 'http://localhost:8080' } + + it 'should be nil' do + expect(remote_file.content).to be_nil + end + end + end + + describe "#error_message" do + let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } + + it 'should return an error message' do + expect(remote_file.error_message).to eq("Remote file '#{location}' is not valid.") + end + end +end diff --git a/spec/lib/gitlab/ci/external/mapper_spec.rb b/spec/lib/gitlab/ci/external/mapper_spec.rb new file mode 100644 index 00000000000..6270d27a36d --- /dev/null +++ b/spec/lib/gitlab/ci/external/mapper_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::External::Mapper do + let(:project) { create(:project, :repository) } + let(:file_content) do + <<~HEREDOC + image: 'ruby:2.2' + HEREDOC + end + + describe '#process' do + subject { described_class.new(values, project, '123456').process } + + context "when 'include' keyword is defined as string" do + context 'when the string is a local file' do + let(:values) do + { + include: '/vendor/gitlab-ci-yml/non-existent-file.yml', + image: 'ruby:2.2' + } + end + + it 'returns an array' do + expect(subject).to be_an(Array) + end + + it 'returns File instances' do + expect(subject.first).to be_an_instance_of(Gitlab::Ci::External::File::Local) + end + end + + context 'when the string is a remote file' do + let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } + let(:values) do + { + include: remote_url, + image: 'ruby:2.2' + } + end + + before do + WebMock.stub_request(:get, remote_url).to_return(body: file_content) + end + + it 'returns an array' do + expect(subject).to be_an(Array) + end + + it 'returns File instances' do + expect(subject.first).to be_an_instance_of(Gitlab::Ci::External::File::Remote) + end + end + end + + context "when 'include' is defined as an array" do + let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } + let(:values) do + { + include: + [ + remote_url, + '/vendor/gitlab-ci-yml/template.yml' + ], + image: 'ruby:2.2' + } + end + + before do + WebMock.stub_request(:get, remote_url).to_return(body: file_content) + end + + it 'returns an array' do + expect(subject).to be_an(Array) + end + + it 'returns Files instances' do + expect(subject).to all(respond_to(:valid?)) + expect(subject).to all(respond_to(:content)) + end + end + + context "when 'include' is not defined" do + let(:values) do + { + image: 'ruby:2.2' + } + end + + it 'returns an empty array' do + expect(subject).to be_empty + end + end + end +end diff --git a/spec/lib/gitlab/ci/external/processor_spec.rb b/spec/lib/gitlab/ci/external/processor_spec.rb new file mode 100644 index 00000000000..688c2b3c8aa --- /dev/null +++ b/spec/lib/gitlab/ci/external/processor_spec.rb @@ -0,0 +1,182 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::External::Processor do + let(:project) { create(:project, :repository) } + let(:processor) { described_class.new(values, project, '12345') } + + describe "#perform" do + context 'when no external files defined' do + let(:values) { { image: 'ruby:2.2' } } + + it 'should return the same values' do + expect(processor.perform).to eq(values) + end + end + + context 'when an invalid local file is defined' do + let(:values) { { include: '/vendor/gitlab-ci-yml/non-existent-file.yml', image: 'ruby:2.2' } } + + it 'should raise an error' do + expect { processor.perform }.to raise_error( + described_class::FileError, + "Local file '/vendor/gitlab-ci-yml/non-existent-file.yml' is not valid." + ) + end + end + + context 'when an invalid remote file is defined' do + let(:remote_file) { 'http://doesntexist.com/.gitlab-ci-1.yml' } + let(:values) { { include: remote_file, image: 'ruby:2.2' } } + + before do + WebMock.stub_request(:get, remote_file).to_raise(SocketError.new('Some HTTP error')) + end + + it 'should raise an error' do + expect { processor.perform }.to raise_error( + described_class::FileError, + "Remote file '#{remote_file}' is not valid." + ) + end + end + + context 'with a valid remote external file is defined' do + let(:remote_file) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } + let(:values) { { include: remote_file, image: 'ruby:2.2' } } + let(:external_file_content) do + <<-HEREDOC + before_script: + - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs + - ruby -v + - which ruby + - gem install bundler --no-ri --no-rdoc + - bundle install --jobs $(nproc) "${FLAGS[@]}" + + rspec: + script: + - bundle exec rspec + + rubocop: + script: + - bundle exec rubocop + HEREDOC + end + + before do + WebMock.stub_request(:get, remote_file).to_return(body: external_file_content) + end + + it 'should append the file to the values' do + output = processor.perform + expect(output.keys).to match_array([:image, :before_script, :rspec, :rubocop]) + end + + it "should remove the 'include' keyword" do + expect(processor.perform[:include]).to be_nil + end + end + + context 'with a valid local external file is defined' do + let(:values) { { include: '/vendor/gitlab-ci-yml/template.yml', image: 'ruby:2.2' } } + let(:local_file_content) do + <<-HEREDOC + before_script: + - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs + - ruby -v + - which ruby + - gem install bundler --no-ri --no-rdoc + - bundle install --jobs $(nproc) "${FLAGS[@]}" + HEREDOC + end + + before do + allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content) + end + + it 'should append the file to the values' do + output = processor.perform + expect(output.keys).to match_array([:image, :before_script]) + end + + it "should remove the 'include' keyword" do + expect(processor.perform[:include]).to be_nil + end + end + + context 'with multiple external files are defined' do + let(:remote_file) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } + let(:external_files) do + [ + '/spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml', + remote_file + ] + end + let(:values) do + { + include: external_files, + image: 'ruby:2.2' + } + end + + let(:remote_file_content) do + <<-HEREDOC + stages: + - build + - review + - cleanup + HEREDOC + end + + before do + local_file_content = File.read(Rails.root.join('spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml')) + allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content) + WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content) + end + + it 'should append the files to the values' do + expect(processor.perform.keys).to match_array([:image, :stages, :before_script, :rspec]) + end + + it "should remove the 'include' keyword" do + expect(processor.perform[:include]).to be_nil + end + end + + context 'when external files are defined but not valid' do + let(:values) { { include: '/vendor/gitlab-ci-yml/template.yml', image: 'ruby:2.2' } } + + let(:local_file_content) { 'invalid content file ////' } + + before do + allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content) + end + + it 'should raise an error' do + expect { processor.perform }.to raise_error(Gitlab::Ci::Config::Loader::FormatError) + end + end + + context "when both external files and values defined the same key" do + let(:remote_file) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } + let(:values) do + { + include: remote_file, + image: 'ruby:2.2' + } + end + + let(:remote_file_content) do + <<~HEREDOC + image: php:5-fpm-alpine + HEREDOC + end + + it 'should take precedence' do + WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content) + expect(processor.perform[:image]).to eq('ruby:2.2') + end + end + end +end diff --git a/spec/lib/gitlab/cleanup/project_uploads_spec.rb b/spec/lib/gitlab/cleanup/project_uploads_spec.rb index 11e605eece6..bf130b8fabd 100644 --- a/spec/lib/gitlab/cleanup/project_uploads_spec.rb +++ b/spec/lib/gitlab/cleanup/project_uploads_spec.rb @@ -132,7 +132,6 @@ describe Gitlab::Cleanup::ProjectUploads do let!(:path) { File.join(FileUploader.root, orphaned.model.full_path, orphaned.path) } before do - stub_feature_flags(import_export_object_storage: true) stub_uploads_object_storage(FileUploader) FileUtils.mkdir_p(File.dirname(path)) @@ -156,7 +155,6 @@ describe Gitlab::Cleanup::ProjectUploads do let!(:new_path) { File.join(FileUploader.root, '-', 'project-lost-found', 'wrong', orphaned.path) } before do - stub_feature_flags(import_export_object_storage: true) stub_uploads_object_storage(FileUploader) FileUtils.mkdir_p(File.dirname(path)) diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb index 2c63f3b0455..6d29044ffd5 100644 --- a/spec/lib/gitlab/contributions_calendar_spec.rb +++ b/spec/lib/gitlab/contributions_calendar_spec.rb @@ -62,13 +62,16 @@ describe Gitlab::ContributionsCalendar do expect(calendar.activity_dates).to eq(last_week => 2, today => 1) end - it "only shows private events to authorized users" do - create_event(private_project, today) - create_event(feature_project, today) + context "when the user has opted-in for private contributions" do + it "shows private and public events to all users" do + user.update_column(:include_private_contributions, true) + create_event(private_project, today) + create_event(public_project, today) - expect(calendar.activity_dates[today]).to eq(0) - expect(calendar(user).activity_dates[today]).to eq(0) - expect(calendar(contributor).activity_dates[today]).to eq(2) + expect(calendar.activity_dates[today]).to eq(1) + expect(calendar(user).activity_dates[today]).to eq(1) + expect(calendar(contributor).activity_dates[today]).to eq(2) + end end it "counts the diff notes on merge request" do @@ -128,7 +131,7 @@ describe Gitlab::ContributionsCalendar do e3 = create_event(feature_project, today) create_event(public_project, last_week) - expect(calendar.events_by_date(today)).to contain_exactly(e1) + expect(calendar.events_by_date(today)).to contain_exactly(e1, e3) expect(calendar(contributor).events_by_date(today)).to contain_exactly(e1, e2, e3) end diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb new file mode 100644 index 00000000000..bfcfed4231f --- /dev/null +++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Diff::HighlightCache do + let(:merge_request) { create(:merge_request_with_diffs) } + + subject(:cache) { described_class.new(merge_request.diffs, backend: backend) } + + describe '#decorate' do + let(:backend) { double('backend').as_null_object } + + # Manually creates a Diff::File object to avoid triggering the cache on + # the FileCollection::MergeRequestDiff + let(:diff_file) do + diffs = merge_request.diffs + raw_diff = diffs.diffable.raw_diffs(diffs.diff_options.merge(paths: ['CHANGELOG'])).first + Gitlab::Diff::File.new(raw_diff, + repository: diffs.project.repository, + diff_refs: diffs.diff_refs, + fallback_diff_refs: diffs.fallback_diff_refs) + end + + it 'does not calculate highlighting when reading from cache' do + cache.write_if_empty + cache.decorate(diff_file) + + expect_any_instance_of(Gitlab::Diff::Highlight).not_to receive(:highlight) + + diff_file.highlighted_diff_lines + end + + it 'assigns highlighted diff lines to the DiffFile' do + cache.write_if_empty + cache.decorate(diff_file) + + expect(diff_file.highlighted_diff_lines.size).to be > 5 + end + + it 'submits a single reading from the cache' do + cache.decorate(diff_file) + cache.decorate(diff_file) + + expect(backend).to have_received(:read).with(cache.key).once + end + end + + describe '#write_if_empty' do + let(:backend) { double('backend', read: {}).as_null_object } + + it 'submits a single writing to the cache' do + cache.write_if_empty + cache.write_if_empty + + expect(backend).to have_received(:write).with(cache.key, + hash_including('CHANGELOG-false-false-false'), + expires_in: 1.week).once + end + end + + describe '#clear' do + let(:backend) { double('backend').as_null_object } + + it 'clears cache' do + cache.clear + + expect(backend).to have_received(:delete).with(cache.key) + end + end +end diff --git a/spec/lib/gitlab/git/committer_with_hooks_spec.rb b/spec/lib/gitlab/git/committer_with_hooks_spec.rb deleted file mode 100644 index c7626058acd..00000000000 --- a/spec/lib/gitlab/git/committer_with_hooks_spec.rb +++ /dev/null @@ -1,156 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Git::CommitterWithHooks, :seed_helper do - # TODO https://gitlab.com/gitlab-org/gitaly/issues/1234 - skip 'needs to be moved to gitaly-ruby test suite' do - shared_examples 'calling wiki hooks' do - let(:project) { create(:project) } - let(:user) { project.owner } - let(:project_wiki) { ProjectWiki.new(project, user) } - let(:wiki) { project_wiki.wiki } - let(:options) do - { - id: user.id, - username: user.username, - name: user.name, - email: user.email, - message: 'commit message' - } - end - - subject { described_class.new(wiki, options) } - - before do - project_wiki.create_page('home', 'test content') - end - - shared_examples 'failing pre-receive hook' do - before do - expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([false, '']) - expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('update') - expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive') - end - - it 'raises exception' do - expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError) - end - - it 'does not create a new commit inside the repository' do - current_rev = find_current_rev - - expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError) - - expect(current_rev).to eq find_current_rev - end - end - - shared_examples 'failing update hook' do - before do - expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, '']) - expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([false, '']) - expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive') - end - - it 'raises exception' do - expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError) - end - - it 'does not create a new commit inside the repository' do - current_rev = find_current_rev - - expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError) - - expect(current_rev).to eq find_current_rev - end - end - - shared_examples 'failing post-receive hook' do - before do - expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, '']) - expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([true, '']) - expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('post-receive').and_return([false, '']) - end - - it 'does not raise exception' do - expect { subject.commit }.not_to raise_error - end - - it 'creates the commit' do - current_rev = find_current_rev - - subject.commit - - expect(current_rev).not_to eq find_current_rev - end - end - - shared_examples 'when hooks call succceeds' do - let(:hook) { double(:hook) } - - it 'calls the three hooks' do - expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook) - expect(hook).to receive(:trigger).exactly(3).times.and_return([true, nil]) - - subject.commit - end - - it 'creates the commit' do - current_rev = find_current_rev - - subject.commit - - expect(current_rev).not_to eq find_current_rev - end - end - - context 'when creating a page' do - before do - project_wiki.create_page('index', 'test content') - end - - it_behaves_like 'failing pre-receive hook' - it_behaves_like 'failing update hook' - it_behaves_like 'failing post-receive hook' - it_behaves_like 'when hooks call succceeds' - end - - context 'when updating a page' do - before do - project_wiki.update_page(find_page('home'), content: 'some other content', format: :markdown) - end - - it_behaves_like 'failing pre-receive hook' - it_behaves_like 'failing update hook' - it_behaves_like 'failing post-receive hook' - it_behaves_like 'when hooks call succceeds' - end - - context 'when deleting a page' do - before do - project_wiki.delete_page(find_page('home')) - end - - it_behaves_like 'failing pre-receive hook' - it_behaves_like 'failing update hook' - it_behaves_like 'failing post-receive hook' - it_behaves_like 'when hooks call succceeds' - end - - def find_current_rev - wiki.gollum_wiki.repo.commits.first&.sha - end - - def find_page(name) - wiki.page(title: name) - end - end - - context 'when Gitaly is enabled' do - it_behaves_like 'calling wiki hooks' - end - - context 'when Gitaly is disabled', :disable_gitaly do - it_behaves_like 'calling wiki hooks' - end - end -end diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb index 87d9fcee39e..27d803e0117 100644 --- a/spec/lib/gitlab/git/diff_spec.rb +++ b/spec/lib/gitlab/git/diff_spec.rb @@ -2,12 +2,24 @@ require "spec_helper" describe Gitlab::Git::Diff, :seed_helper do let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } + let(:gitaly_diff) do + Gitlab::GitalyClient::Diff.new( + from_path: '.gitmodules', + to_path: '.gitmodules', + old_mode: 0100644, + new_mode: 0100644, + from_id: '0792c58905eff3432b721f8c4a64363d8e28d9ae', + to_id: 'efd587ccb47caf5f31fc954edb21f0a713d9ecc3', + overflow_marker: false, + collapsed: false, + too_large: false, + patch: "@@ -4,3 +4,6 @@\n [submodule \"gitlab-shell\"]\n \tpath = gitlab-shell\n \turl = https://github.com/gitlabhq/gitlab-shell.git\n+[submodule \"gitlab-grack\"]\n+\tpath = gitlab-grack\n+\turl = https://gitlab.com/gitlab-org/gitlab-grack.git\n" + ) + end before do @raw_diff_hash = { diff: <<EOT.gsub(/^ {8}/, "").sub(/\n$/, ""), - --- a/.gitmodules - +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "gitlab-shell"] \tpath = gitlab-shell @@ -26,12 +38,6 @@ EOT deleted_file: false, too_large: false } - - # TODO use a Gitaly diff object instead - @rugged_diff = Gitlab::GitalyClient::StorageSettings.allow_disk_access do - repository.rugged.diff("5937ac0a7beb003549fc5fd26fc247adbce4a52e^", "5937ac0a7beb003549fc5fd26fc247adbce4a52e", paths: - [".gitmodules"]).patches.first - end end describe '.new' do @@ -60,7 +66,7 @@ EOT context 'using a Rugged::Patch' do context 'with a small diff' do - let(:diff) { described_class.new(@rugged_diff) } + let(:diff) { described_class.new(gitaly_diff) } it 'initializes the diff' do expect(diff.to_hash).to eq(@raw_diff_hash) @@ -73,10 +79,8 @@ EOT context 'using a diff that is too large' do it 'prunes the diff' do - expect_any_instance_of(String).to receive(:bytesize) - .and_return(1024 * 1024 * 1024) - - diff = described_class.new(@rugged_diff) + gitaly_diff.too_large = true + diff = described_class.new(gitaly_diff) expect(diff.diff).to be_empty expect(diff).to be_too_large @@ -84,33 +88,15 @@ EOT end context 'using a collapsable diff that is too large' do - before do - # The patch total size is 200, with lines between 21 and 54. - # This is a quick-and-dirty way to test this. Ideally, a new patch is - # added to the test repo with a size that falls between the real limits. - stub_const("#{described_class}::SIZE_LIMIT", 150) - stub_const("#{described_class}::COLLAPSE_LIMIT", 100) - end - it 'prunes the diff as a large diff instead of as a collapsed diff' do - diff = described_class.new(@rugged_diff, expanded: false) + gitaly_diff.too_large = true + diff = described_class.new(gitaly_diff, expanded: false) expect(diff.diff).to be_empty expect(diff).to be_too_large expect(diff).not_to be_collapsed end end - - context 'using a large binary diff' do - it 'does not prune the diff' do - expect_any_instance_of(Rugged::Diff::Delta).to receive(:binary?) - .and_return(true) - - diff = described_class.new(@rugged_diff) - - expect(diff.diff).not_to be_empty - end - end end context 'using a GitalyClient::Diff' do @@ -259,31 +245,37 @@ EOT end it 'leave non-binary diffs as-is' do - diff = described_class.new(@rugged_diff) + diff = described_class.new(gitaly_diff) expect(diff.json_safe_diff).to eq(diff.diff) end end describe '#submodule?' do - before do - # TODO use a Gitaly diff object instead - rugged_commit = Gitlab::GitalyClient::StorageSettings.allow_disk_access do - repository.rugged.rev_parse('5937ac0a7beb003549fc5fd26fc247adbce4a52e') - end - - @diffs = rugged_commit.parents[0].diff(rugged_commit).patches + let(:gitaly_submodule_diff) do + Gitlab::GitalyClient::Diff.new( + from_path: 'gitlab-grack', + to_path: 'gitlab-grack', + old_mode: 0, + new_mode: 57344, + from_id: '0000000000000000000000000000000000000000', + to_id: '645f6c4c82fd3f5e06f67134450a570b795e55a6', + overflow_marker: false, + collapsed: false, + too_large: false, + patch: "@@ -0,0 +1 @@\n+Subproject commit 645f6c4c82fd3f5e06f67134450a570b795e55a6\n" + ) end - it { expect(described_class.new(@diffs[0]).submodule?).to eq(false) } - it { expect(described_class.new(@diffs[1]).submodule?).to eq(true) } + it { expect(described_class.new(gitaly_diff).submodule?).to eq(false) } + it { expect(described_class.new(gitaly_submodule_diff).submodule?).to eq(true) } end describe '#line_count' do it 'returns the correct number of lines' do - diff = described_class.new(@rugged_diff) + diff = described_class.new(gitaly_diff) - expect(diff.line_count).to eq(9) + expect(diff.line_count).to eq(7) end end diff --git a/spec/lib/gitlab/git/gitlab_projects_spec.rb b/spec/lib/gitlab/git/gitlab_projects_spec.rb deleted file mode 100644 index f5d8503c30c..00000000000 --- a/spec/lib/gitlab/git/gitlab_projects_spec.rb +++ /dev/null @@ -1,321 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Git::GitlabProjects do - after do - TestEnv.clean_test_path - end - - around do |example| - # TODO move this spec to gitaly-ruby. GitlabProjects is not used in gitlab-ce - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - example.run - end - end - - let(:project) { create(:project, :repository) } - - if $VERBOSE - let(:logger) { Logger.new(STDOUT) } - else - let(:logger) { double('logger').as_null_object } - end - - let(:tmp_repos_path) { TestEnv.repos_path } - let(:repo_name) { project.disk_path + '.git' } - let(:tmp_repo_path) { File.join(tmp_repos_path, repo_name) } - let(:gl_projects) { build_gitlab_projects(TestEnv::REPOS_STORAGE, repo_name) } - - describe '#initialize' do - it { expect(gl_projects.shard_path).to eq(tmp_repos_path) } - it { expect(gl_projects.repository_relative_path).to eq(repo_name) } - it { expect(gl_projects.repository_absolute_path).to eq(File.join(tmp_repos_path, repo_name)) } - it { expect(gl_projects.logger).to eq(logger) } - end - - describe '#push_branches' do - let(:remote_name) { 'remote-name' } - let(:branch_name) { 'master' } - let(:cmd) { %W(#{Gitlab.config.git.bin_path} push -- #{remote_name} #{branch_name}) } - let(:force) { false } - - subject { gl_projects.push_branches(remote_name, 600, force, [branch_name]) } - - it 'executes the command' do - stub_spawn(cmd, 600, tmp_repo_path, success: true) - - is_expected.to be_truthy - end - - it 'fails' do - stub_spawn(cmd, 600, tmp_repo_path, success: false) - - is_expected.to be_falsy - end - - context 'with --force' do - let(:cmd) { %W(#{Gitlab.config.git.bin_path} push --force -- #{remote_name} #{branch_name}) } - let(:force) { true } - - it 'executes the command' do - stub_spawn(cmd, 600, tmp_repo_path, success: true) - - is_expected.to be_truthy - end - end - end - - describe '#fetch_remote' do - let(:remote_name) { 'remote-name' } - let(:branch_name) { 'master' } - let(:force) { false } - let(:prune) { true } - let(:tags) { true } - let(:args) { { force: force, tags: tags, prune: prune }.merge(extra_args) } - let(:extra_args) { {} } - let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --prune --tags) } - - subject { gl_projects.fetch_remote(remote_name, 600, args) } - - def stub_tempfile(name, filename, opts = {}) - chmod = opts.delete(:chmod) - file = StringIO.new - - allow(file).to receive(:close!) - allow(file).to receive(:path).and_return(name) - - expect(Tempfile).to receive(:new).with(filename).and_return(file) - expect(file).to receive(:chmod).with(chmod) if chmod - - file - end - - context 'with default args' do - it 'executes the command' do - stub_spawn(cmd, 600, tmp_repo_path, {}, success: true) - - is_expected.to be_truthy - end - - it 'fails' do - stub_spawn(cmd, 600, tmp_repo_path, {}, success: false) - - is_expected.to be_falsy - end - end - - context 'with --force' do - let(:force) { true } - let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --prune --force --tags) } - - it 'executes the command with forced option' do - stub_spawn(cmd, 600, tmp_repo_path, {}, success: true) - - is_expected.to be_truthy - end - end - - context 'with --no-tags' do - let(:tags) { false } - let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --prune --no-tags) } - - it 'executes the command' do - stub_spawn(cmd, 600, tmp_repo_path, {}, success: true) - - is_expected.to be_truthy - end - end - - context 'with no prune' do - let(:prune) { false } - let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --tags) } - - it 'executes the command' do - stub_spawn(cmd, 600, tmp_repo_path, {}, success: true) - - is_expected.to be_truthy - end - end - - describe 'with an SSH key' do - let(:extra_args) { { ssh_key: 'SSH KEY' } } - - it 'sets GIT_SSH to a custom script' do - script = stub_tempfile('scriptFile', 'gitlab-shell-ssh-wrapper', chmod: 0o755) - key = stub_tempfile('/tmp files/keyFile', 'gitlab-shell-key-file', chmod: 0o400) - - stub_spawn(cmd, 600, tmp_repo_path, { 'GIT_SSH' => 'scriptFile' }, success: true) - - is_expected.to be_truthy - - expect(script.string).to eq("#!/bin/sh\nexec ssh '-oIdentityFile=\"/tmp files/keyFile\"' '-oIdentitiesOnly=\"yes\"' \"$@\"") - expect(key.string).to eq('SSH KEY') - end - end - - describe 'with known_hosts data' do - let(:extra_args) { { known_hosts: 'KNOWN HOSTS' } } - - it 'sets GIT_SSH to a custom script' do - script = stub_tempfile('scriptFile', 'gitlab-shell-ssh-wrapper', chmod: 0o755) - key = stub_tempfile('/tmp files/knownHosts', 'gitlab-shell-known-hosts', chmod: 0o400) - - stub_spawn(cmd, 600, tmp_repo_path, { 'GIT_SSH' => 'scriptFile' }, success: true) - - is_expected.to be_truthy - - expect(script.string).to eq("#!/bin/sh\nexec ssh '-oStrictHostKeyChecking=\"yes\"' '-oUserKnownHostsFile=\"/tmp files/knownHosts\"' \"$@\"") - expect(key.string).to eq('KNOWN HOSTS') - end - end - end - - describe '#import_project' do - let(:project) { create(:project) } - let(:import_url) { TestEnv.factory_repo_path_bare } - let(:cmd) { %W(#{Gitlab.config.git.bin_path} clone --bare -- #{import_url} #{tmp_repo_path}) } - let(:timeout) { 600 } - - subject { gl_projects.import_project(import_url, timeout) } - - shared_examples 'importing repository' do - context 'success import' do - it 'imports a repo' do - expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy - - is_expected.to be_truthy - - expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_truthy - end - end - - context 'already exists' do - it "doesn't import" do - FileUtils.mkdir_p(tmp_repo_path) - - is_expected.to be_falsy - end - end - end - - describe 'logging' do - it 'imports a repo' do - message = "Importing project from <#{import_url}> to <#{tmp_repo_path}>." - expect(logger).to receive(:info).with(message) - - subject - end - end - - context 'timeout' do - it 'does not import a repo' do - stub_spawn_timeout(cmd, timeout, nil) - - message = "Importing project from <#{import_url}> to <#{tmp_repo_path}> failed." - expect(logger).to receive(:error).with(message) - - is_expected.to be_falsy - - expect(gl_projects.output).to eq("Timed out\n") - expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy - end - end - - it_behaves_like 'importing repository' - end - - describe '#fork_repository' do - let(:dest_repos) { TestEnv::REPOS_STORAGE } - let(:dest_repos_path) { tmp_repos_path } - let(:dest_repo_name) { File.join('@hashed', 'aa', 'bb', 'xyz.git') } - let(:dest_repo) { File.join(dest_repos_path, dest_repo_name) } - - subject { gl_projects.fork_repository(dest_repos, dest_repo_name) } - - before do - FileUtils.mkdir_p(dest_repos_path) - end - - after do - FileUtils.rm_rf(dest_repos_path) - end - - shared_examples 'forking a repository' do - it 'forks the repository' do - is_expected.to be_truthy - - expect(File.exist?(dest_repo)).to be_truthy - expect(File.exist?(File.join(dest_repo, 'hooks', 'pre-receive'))).to be_truthy - expect(File.exist?(File.join(dest_repo, 'hooks', 'post-receive'))).to be_truthy - end - - it 'does not fork if a project of the same name already exists' do - # create a fake project at the intended destination - FileUtils.mkdir_p(dest_repo) - - is_expected.to be_falsy - end - end - - it_behaves_like 'forking a repository' - - # We seem to be stuck to having only one working Gitaly storage in tests, changing - # that is not very straight-forward so I'm leaving this test here for now till - # https://gitlab.com/gitlab-org/gitlab-ce/issues/41393 is fixed. - context 'different storages' do - let(:dest_repos) { 'alternative' } - let(:dest_repos_path) { File.join(File.dirname(tmp_repos_path), dest_repos) } - - before do - stub_storage_settings(dest_repos => { 'path' => dest_repos_path }) - end - - it 'forks the repo' do - is_expected.to be_truthy - - expect(File.exist?(dest_repo)).to be_truthy - expect(File.exist?(File.join(dest_repo, 'hooks', 'pre-receive'))).to be_truthy - expect(File.exist?(File.join(dest_repo, 'hooks', 'post-receive'))).to be_truthy - end - end - - describe 'log messages' do - describe 'successful fork' do - it do - message = "Forking repository from <#{tmp_repo_path}> to <#{dest_repo}>." - expect(logger).to receive(:info).with(message) - - subject - end - end - - describe 'failed fork due existing destination' do - it do - FileUtils.mkdir_p(dest_repo) - message = "fork-repository failed: destination repository <#{dest_repo}> already exists." - expect(logger).to receive(:error).with(message) - - subject - end - end - end - end - - def build_gitlab_projects(*args) - described_class.new( - *args, - global_hooks_path: Gitlab.config.gitlab_shell.hooks_path, - logger: logger - ) - end - - def stub_spawn(*args, success: true) - exitstatus = success ? 0 : nil - expect(gl_projects).to receive(:popen_with_timeout).with(*args) - .and_return(["output", exitstatus]) - end - - def stub_spawn_timeout(*args) - expect(gl_projects).to receive(:popen_with_timeout).with(*args) - .and_raise(Timeout::Error) - end -end diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb deleted file mode 100644 index a45c8510b15..00000000000 --- a/spec/lib/gitlab/git/hook_spec.rb +++ /dev/null @@ -1,111 +0,0 @@ -require 'spec_helper' -require 'fileutils' - -describe Gitlab::Git::Hook do - before do - # We need this because in the spec/spec_helper.rb we define it like this: - # allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil]) - allow_any_instance_of(described_class).to receive(:trigger).and_call_original - end - - around do |example| - # TODO move hook tests to gitaly-ruby. Hook will disappear from gitlab-ce - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - example.run - end - end - - describe "#trigger" do - set(:project) { create(:project, :repository) } - let(:repository) { project.repository.raw_repository } - let(:repo_path) { repository.path } - let(:hooks_dir) { File.join(repo_path, 'hooks') } - let(:user) { create(:user) } - let(:gl_id) { Gitlab::GlId.gl_id(user) } - let(:gl_username) { user.username } - - def create_hook(name) - FileUtils.mkdir_p(hooks_dir) - hook_path = File.join(hooks_dir, name) - File.open(hook_path, 'w', 0755) do |f| - f.write(<<~HOOK) - #!/bin/sh - exit 0 - HOOK - end - end - - def create_failing_hook(name) - FileUtils.mkdir_p(hooks_dir) - hook_path = File.join(hooks_dir, name) - File.open(hook_path, 'w', 0755) do |f| - f.write(<<~HOOK) - #!/bin/sh - echo 'regular message from the hook' - echo 'error message from the hook' 1>&2 - echo 'error message from the hook line 2' 1>&2 - exit 1 - HOOK - end - end - - ['pre-receive', 'post-receive', 'update'].each do |hook_name| - context "when triggering a #{hook_name} hook" do - context "when the hook is successful" do - let(:hook_path) { File.join(hooks_dir, hook_name) } - let(:gl_repository) { Gitlab::GlRepository.gl_repository(project, false) } - let(:env) do - { - 'GL_ID' => gl_id, - 'GL_USERNAME' => gl_username, - 'PWD' => repo_path, - 'GL_PROTOCOL' => 'web', - 'GL_REPOSITORY' => gl_repository - } - end - - it "returns success with no errors" do - create_hook(hook_name) - hook = described_class.new(hook_name, repository) - blank = Gitlab::Git::BLANK_SHA - ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch' - - if hook_name != 'update' - expect(Open3).to receive(:popen3) - .with(env, hook_path, chdir: repo_path).and_call_original - end - - status, errors = hook.trigger(gl_id, gl_username, blank, blank, ref) - expect(status).to be true - expect(errors).to be_blank - end - end - - context "when the hook is unsuccessful" do - it "returns failure with errors" do - create_failing_hook(hook_name) - hook = described_class.new(hook_name, repository) - blank = Gitlab::Git::BLANK_SHA - ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch' - - status, errors = hook.trigger(gl_id, gl_username, blank, blank, ref) - expect(status).to be false - expect(errors).to eq("error message from the hook\nerror message from the hook line 2\n") - end - end - end - end - - context "when the hook doesn't exist" do - it "returns success with no errors" do - hook = described_class.new('unknown_hook', repository) - blank = Gitlab::Git::BLANK_SHA - ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch' - - status, errors = hook.trigger(gl_id, gl_username, blank, blank, ref) - expect(status).to be true - expect(errors).to be_nil - end - end - end -end diff --git a/spec/lib/gitlab/git/hooks_service_spec.rb b/spec/lib/gitlab/git/hooks_service_spec.rb deleted file mode 100644 index 55ffced36ac..00000000000 --- a/spec/lib/gitlab/git/hooks_service_spec.rb +++ /dev/null @@ -1,50 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Git::HooksService, :seed_helper do - let(:gl_id) { 'user-456' } - let(:gl_username) { 'janedoe' } - let(:user) { Gitlab::Git::User.new(gl_username, 'Jane Doe', 'janedoe@example.com', gl_id) } - let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, 'project-123') } - let(:service) { described_class.new } - let(:blankrev) { Gitlab::Git::BLANK_SHA } - let(:oldrev) { SeedRepo::Commit::PARENT_ID } - let(:newrev) { SeedRepo::Commit::ID } - let(:ref) { 'refs/heads/feature' } - - describe '#execute' do - context 'when receive hooks were successful' do - let(:hook) { double(:hook) } - - it 'calls all three hooks' do - expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook) - expect(hook).to receive(:trigger).with(gl_id, gl_username, blankrev, newrev, ref) - .exactly(3).times.and_return([true, nil]) - - service.execute(user, repository, blankrev, newrev, ref) { } - end - end - - context 'when pre-receive hook failed' do - it 'does not call post-receive hook' do - expect(service).to receive(:run_hook).with('pre-receive').and_return([false, 'hello world']) - expect(service).not_to receive(:run_hook).with('post-receive') - - expect do - service.execute(user, repository, blankrev, newrev, ref) - end.to raise_error(Gitlab::Git::PreReceiveError, 'hello world') - end - end - - context 'when update hook failed' do - it 'does not call post-receive hook' do - expect(service).to receive(:run_hook).with('pre-receive').and_return([true, nil]) - expect(service).to receive(:run_hook).with('update').and_return([false, 'hello world']) - expect(service).not_to receive(:run_hook).with('post-receive') - - expect do - service.execute(user, repository, blankrev, newrev, ref) - end.to raise_error(Gitlab::Git::PreReceiveError, 'hello world') - end - end - end -end diff --git a/spec/lib/gitlab/git/index_spec.rb b/spec/lib/gitlab/git/index_spec.rb deleted file mode 100644 index c4edd6961e1..00000000000 --- a/spec/lib/gitlab/git/index_spec.rb +++ /dev/null @@ -1,239 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Git::Index, :seed_helper do - let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } - let(:index) { described_class.new(repository) } - - before do - index.read_tree(lookup('master').tree) - end - - around do |example| - # TODO move these specs to gitaly-ruby. The Index class will disappear from gitlab-ce - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - example.run - end - end - - describe '#create' do - let(:options) do - { - content: 'Lorem ipsum...', - file_path: 'documents/story.txt' - } - end - - context 'when no file at that path exists' do - it 'creates the file in the index' do - index.create(options) - - entry = index.get(options[:file_path]) - - expect(entry).not_to be_nil - expect(lookup(entry[:oid]).content).to eq(options[:content]) - end - end - - context 'when a file at that path exists' do - before do - options[:file_path] = 'files/executables/ls' - end - - it 'raises an error' do - expect { index.create(options) }.to raise_error('A file with this name already exists') - end - end - - context 'when content is in base64' do - before do - options[:content] = Base64.encode64(options[:content]) - options[:encoding] = 'base64' - end - - it 'decodes base64' do - index.create(options) - - entry = index.get(options[:file_path]) - expect(lookup(entry[:oid]).content).to eq(Base64.decode64(options[:content])) - end - end - - context 'when content contains CRLF' do - before do - repository.autocrlf = :input - options[:content] = "Hello,\r\nWorld" - end - - it 'converts to LF' do - index.create(options) - - entry = index.get(options[:file_path]) - expect(lookup(entry[:oid]).content).to eq("Hello,\nWorld") - end - end - end - - describe '#create_dir' do - let(:options) do - { - file_path: 'newdir' - } - end - - context 'when no file or dir at that path exists' do - it 'creates the dir in the index' do - index.create_dir(options) - - entry = index.get(options[:file_path] + '/.gitkeep') - - expect(entry).not_to be_nil - end - end - - context 'when a file at that path exists' do - before do - options[:file_path] = 'files/executables/ls' - end - - it 'raises an error' do - expect { index.create_dir(options) }.to raise_error('A file with this name already exists') - end - end - - context 'when a directory at that path exists' do - before do - options[:file_path] = 'files/executables' - end - - it 'raises an error' do - expect { index.create_dir(options) }.to raise_error('A directory with this name already exists') - end - end - end - - describe '#update' do - let(:options) do - { - content: 'Lorem ipsum...', - file_path: 'README.md' - } - end - - context 'when no file at that path exists' do - before do - options[:file_path] = 'documents/story.txt' - end - - it 'raises an error' do - expect { index.update(options) }.to raise_error("A file with this name doesn't exist") - end - end - - context 'when a file at that path exists' do - it 'updates the file in the index' do - index.update(options) - - entry = index.get(options[:file_path]) - - expect(lookup(entry[:oid]).content).to eq(options[:content]) - end - - it 'preserves file mode' do - options[:file_path] = 'files/executables/ls' - - index.update(options) - - entry = index.get(options[:file_path]) - - expect(entry[:mode]).to eq(0100755) - end - end - end - - describe '#move' do - let(:options) do - { - content: 'Lorem ipsum...', - previous_path: 'README.md', - file_path: 'NEWREADME.md' - } - end - - context 'when no file at that path exists' do - it 'raises an error' do - options[:previous_path] = 'documents/story.txt' - - expect { index.move(options) }.to raise_error("A file with this name doesn't exist") - end - end - - context 'when a file at the new path already exists' do - it 'raises an error' do - options[:file_path] = 'CHANGELOG' - - expect { index.move(options) }.to raise_error("A file with this name already exists") - end - end - - context 'when a file at that path exists' do - it 'removes the old file in the index' do - index.move(options) - - entry = index.get(options[:previous_path]) - - expect(entry).to be_nil - end - - it 'creates the new file in the index' do - index.move(options) - - entry = index.get(options[:file_path]) - - expect(entry).not_to be_nil - expect(lookup(entry[:oid]).content).to eq(options[:content]) - end - - it 'preserves file mode' do - options[:previous_path] = 'files/executables/ls' - - index.move(options) - - entry = index.get(options[:file_path]) - - expect(entry[:mode]).to eq(0100755) - end - end - end - - describe '#delete' do - let(:options) do - { - file_path: 'README.md' - } - end - - context 'when no file at that path exists' do - before do - options[:file_path] = 'documents/story.txt' - end - - it 'raises an error' do - expect { index.delete(options) }.to raise_error("A file with this name doesn't exist") - end - end - - context 'when a file at that path exists' do - it 'removes the file in the index' do - index.delete(options) - - entry = index.get(options[:file_path]) - - expect(entry).to be_nil - end - end - end - - def lookup(revision) - repository.rugged.rev_parse(revision) - end -end diff --git a/spec/lib/gitlab/git/popen_spec.rb b/spec/lib/gitlab/git/popen_spec.rb deleted file mode 100644 index 074e66d2a5d..00000000000 --- a/spec/lib/gitlab/git/popen_spec.rb +++ /dev/null @@ -1,179 +0,0 @@ -require 'spec_helper' - -describe 'Gitlab::Git::Popen' do - let(:path) { Rails.root.join('tmp').to_s } - let(:test_string) { 'The quick brown fox jumped over the lazy dog' } - # The pipe buffer is typically 64K. This string is about 440K. - let(:spew_command) { ['bash', '-c', "for i in {1..10000}; do echo '#{test_string}' 1>&2; done"] } - - let(:klass) do - Class.new(Object) do - include Gitlab::Git::Popen - end - end - - context 'popen' do - context 'zero status' do - let(:result) { klass.new.popen(%w(ls), path) } - let(:output) { result.first } - let(:status) { result.last } - - it { expect(status).to be_zero } - it { expect(output).to include('tests') } - end - - context 'non-zero status' do - let(:result) { klass.new.popen(%w(cat NOTHING), path) } - let(:output) { result.first } - let(:status) { result.last } - - it { expect(status).to eq(1) } - it { expect(output).to include('No such file or directory') } - end - - context 'unsafe string command' do - it 'raises an error when it gets called with a string argument' do - expect { klass.new.popen('ls', path) }.to raise_error(RuntimeError) - end - end - - context 'with custom options' do - let(:vars) { { 'foobar' => 123, 'PWD' => path } } - let(:options) { { chdir: path } } - - it 'calls popen3 with the provided environment variables' do - expect(Open3).to receive(:popen3).with(vars, 'ls', options) - - klass.new.popen(%w(ls), path, { 'foobar' => 123 }) - end - end - - context 'use stdin' do - let(:result) { klass.new.popen(%w[cat], path) { |stdin| stdin.write 'hello' } } - let(:output) { result.first } - let(:status) { result.last } - - it { expect(status).to be_zero } - it { expect(output).to eq('hello') } - end - - context 'with lazy block' do - it 'yields a lazy io' do - expect_lazy_io = lambda do |io| - expect(io).to be_a Enumerator::Lazy - expect(io.inspect).to include('#<IO:fd') - end - - klass.new.popen(%w[ls], path, lazy_block: expect_lazy_io) - end - - it "doesn't wait for process exit" do - Timeout.timeout(2) do - klass.new.popen(%w[yes], path, lazy_block: ->(io) {}) - end - end - end - - context 'with a process that writes a lot of data to stderr' do - it 'returns zero' do - output, status = klass.new.popen(spew_command, path) - - expect(output).to include(test_string) - expect(status).to eq(0) - end - end - end - - context 'popen_with_timeout' do - let(:timeout) { 1.second } - - context 'no timeout' do - context 'zero status' do - let(:result) { klass.new.popen_with_timeout(%w(ls), timeout, path) } - let(:output) { result.first } - let(:status) { result.last } - - it { expect(status).to be_zero } - it { expect(output).to include('tests') } - end - - context 'multi-line string' do - let(:test_string) { "this is 1 line\n2nd line\n3rd line\n" } - let(:result) { klass.new.popen_with_timeout(['echo', test_string], timeout, path) } - let(:output) { result.first } - let(:status) { result.last } - - it { expect(status).to be_zero } - # echo adds its own line - it { expect(output).to eq(test_string + "\n") } - end - - context 'non-zero status' do - let(:result) { klass.new.popen_with_timeout(%w(cat NOTHING), timeout, path) } - let(:output) { result.first } - let(:status) { result.last } - - it { expect(status).to eq(1) } - it { expect(output).to include('No such file or directory') } - end - - context 'unsafe string command' do - it 'raises an error when it gets called with a string argument' do - expect { klass.new.popen_with_timeout('ls', timeout, path) }.to raise_error(RuntimeError) - end - end - end - - context 'timeout' do - context 'timeout' do - it "raises a Timeout::Error" do - expect { klass.new.popen_with_timeout(%w(sleep 1000), timeout, path) }.to raise_error(Timeout::Error) - end - - it "handles processes that do not shutdown correctly" do - expect { klass.new.popen_with_timeout(['bash', '-c', "trap -- '' SIGTERM; sleep 1000"], timeout, path) }.to raise_error(Timeout::Error) - end - - it 'handles process that writes a lot of data to stderr' do - output, status = klass.new.popen_with_timeout(spew_command, timeout, path) - - expect(output).to include(test_string) - expect(status).to eq(0) - end - end - - context 'timeout period' do - let(:time_taken) do - begin - start = Time.now - klass.new.popen_with_timeout(%w(sleep 1000), timeout, path) - rescue - Time.now - start - end - end - - it { expect(time_taken).to be >= timeout } - end - - context 'clean up' do - let(:instance) { klass.new } - - it 'kills the child process' do - expect(instance).to receive(:kill_process_group_for_pid).and_wrap_original do |m, *args| - # is the PID, and it should not be running at this point - m.call(*args) - - pid = args.first - begin - Process.getpgid(pid) - raise "The child process should have been killed" - rescue Errno::ESRCH - end - end - - expect { instance.popen_with_timeout(['bash', '-c', "trap -- '' SIGTERM; sleep 1000"], timeout, path) }.to raise_error(Timeout::Error) - end - end - end - end -end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 17348b01006..dfe36d7d459 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -322,21 +322,12 @@ describe Gitlab::Git::Repository, :seed_helper do end describe '#commit_count' do - shared_examples 'simple commit counting' do - it { expect(repository.commit_count("master")).to eq(25) } - it { expect(repository.commit_count("feature")).to eq(9) } - it { expect(repository.commit_count("does-not-exist")).to eq(0) } - end - - context 'when Gitaly commit_count feature is enabled' do - it_behaves_like 'simple commit counting' - it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::CommitService, :commit_count do - subject { repository.commit_count('master') } - end - end + it { expect(repository.commit_count("master")).to eq(25) } + it { expect(repository.commit_count("feature")).to eq(9) } + it { expect(repository.commit_count("does-not-exist")).to eq(0) } - context 'when Gitaly commit_count feature is disabled', :skip_gitaly_mock do - it_behaves_like 'simple commit counting' + it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::CommitService, :commit_count do + subject { repository.commit_count('master') } end end @@ -378,118 +369,88 @@ describe Gitlab::Git::Repository, :seed_helper do end describe "#delete_branch" do - shared_examples "deleting a branch" do - let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') } - - after do - ensure_seeds - end + let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') } - it "removes the branch from the repo" do - branch_name = "to-be-deleted-soon" + after do + ensure_seeds + end - repository.create_branch(branch_name) - expect(repository_rugged.branches[branch_name]).not_to be_nil + it "removes the branch from the repo" do + branch_name = "to-be-deleted-soon" - repository.delete_branch(branch_name) - expect(repository_rugged.branches[branch_name]).to be_nil - end + repository.create_branch(branch_name) + expect(repository_rugged.branches[branch_name]).not_to be_nil - context "when branch does not exist" do - it "raises a DeleteBranchError exception" do - expect { repository.delete_branch("this-branch-does-not-exist") }.to raise_error(Gitlab::Git::Repository::DeleteBranchError) - end - end + repository.delete_branch(branch_name) + expect(repository_rugged.branches[branch_name]).to be_nil end - context "when Gitaly delete_branch is enabled" do - it_behaves_like "deleting a branch" - end - - context "when Gitaly delete_branch is disabled", :skip_gitaly_mock do - it_behaves_like "deleting a branch" + context "when branch does not exist" do + it "raises a DeleteBranchError exception" do + expect { repository.delete_branch("this-branch-does-not-exist") }.to raise_error(Gitlab::Git::Repository::DeleteBranchError) + end end end describe "#create_branch" do - shared_examples 'creating a branch' do - let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') } - - after do - ensure_seeds - end - - it "should create a new branch" do - expect(repository.create_branch('new_branch', 'master')).not_to be_nil - end + let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') } - it "should create a new branch with the right name" do - expect(repository.create_branch('another_branch', 'master').name).to eq('another_branch') - end + after do + ensure_seeds + end - it "should fail if we create an existing branch" do - repository.create_branch('duplicated_branch', 'master') - expect {repository.create_branch('duplicated_branch', 'master')}.to raise_error("Branch duplicated_branch already exists") - end + it "should create a new branch" do + expect(repository.create_branch('new_branch', 'master')).not_to be_nil + end - it "should fail if we create a branch from a non existing ref" do - expect {repository.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge") - end + it "should create a new branch with the right name" do + expect(repository.create_branch('another_branch', 'master').name).to eq('another_branch') end - context 'when Gitaly create_branch feature is enabled' do - it_behaves_like 'creating a branch' + it "should fail if we create an existing branch" do + repository.create_branch('duplicated_branch', 'master') + expect {repository.create_branch('duplicated_branch', 'master')}.to raise_error("Branch duplicated_branch already exists") end - context 'when Gitaly create_branch feature is disabled', :skip_gitaly_mock do - it_behaves_like 'creating a branch' + it "should fail if we create a branch from a non existing ref" do + expect {repository.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge") end end describe '#delete_refs' do - shared_examples 'deleting refs' do - let(:repo) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') } - - def repo_rugged - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - repo.rugged - end - end + let(:repo) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') } - after do - ensure_seeds + def repo_rugged + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + repo.rugged end + end - it 'deletes the ref' do - repo.delete_refs('refs/heads/feature') + after do + ensure_seeds + end - expect(repo_rugged.references['refs/heads/feature']).to be_nil - end + it 'deletes the ref' do + repo.delete_refs('refs/heads/feature') - it 'deletes all refs' do - refs = %w[refs/heads/wip refs/tags/v1.1.0] - repo.delete_refs(*refs) + expect(repo_rugged.references['refs/heads/feature']).to be_nil + end - refs.each do |ref| - expect(repo_rugged.references[ref]).to be_nil - end - end + it 'deletes all refs' do + refs = %w[refs/heads/wip refs/tags/v1.1.0] + repo.delete_refs(*refs) - it 'does not fail when deleting an empty list of refs' do - expect { repo.delete_refs(*[]) }.not_to raise_error - end - - it 'raises an error if it failed' do - expect { repo.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError) + refs.each do |ref| + expect(repo_rugged.references[ref]).to be_nil end end - context 'when Gitaly delete_refs feature is enabled' do - it_behaves_like 'deleting refs' + it 'does not fail when deleting an empty list of refs' do + expect { repo.delete_refs(*[]) }.not_to raise_error end - context 'when Gitaly delete_refs feature is disabled', :disable_gitaly do - it_behaves_like 'deleting refs' + it 'raises an error if it failed' do + expect { repo.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError) end end @@ -542,44 +503,65 @@ describe Gitlab::Git::Repository, :seed_helper do Gitlab::Shell.new.remove_repository('default', 'my_project') end - shared_examples 'repository mirror fetching' do - it 'fetches a repository as a mirror remote' do + it 'fetches a repository as a mirror remote' do + subject + + expect(refs(new_repository_path)).to eq(refs(repository_path)) + end + + context 'with keep-around refs' do + let(:sha) { SeedRepo::Commit::ID } + let(:keep_around_ref) { "refs/keep-around/#{sha}" } + let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" } + + before do + repository_rugged.references.create(keep_around_ref, sha, force: true) + repository_rugged.references.create(tmp_ref, sha, force: true) + end + + it 'includes the temporary and keep-around refs' do subject - expect(refs(new_repository_path)).to eq(refs(repository_path)) + expect(refs(new_repository_path)).to include(keep_around_ref) + expect(refs(new_repository_path)).to include(tmp_ref) end + end - context 'with keep-around refs' do - let(:sha) { SeedRepo::Commit::ID } - let(:keep_around_ref) { "refs/keep-around/#{sha}" } - let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" } + def new_repository_path + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + new_repository.path + end + end + end - before do - repository_rugged.references.create(keep_around_ref, sha, force: true) - repository_rugged.references.create(tmp_ref, sha, force: true) - end + describe '#find_remote_root_ref' do + it 'gets the remote root ref from GitalyClient' do + expect_any_instance_of(Gitlab::GitalyClient::RemoteService) + .to receive(:find_remote_root_ref).and_call_original - it 'includes the temporary and keep-around refs' do - subject + expect(repository.find_remote_root_ref('origin')).to eq 'master' + end - expect(refs(new_repository_path)).to include(keep_around_ref) - expect(refs(new_repository_path)).to include(tmp_ref) - end - end + it 'returns UTF-8' do + expect(repository.find_remote_root_ref('origin')).to be_utf8 end - context 'with gitaly enabled' do - it_behaves_like 'repository mirror fetching' + it 'returns nil when remote name is nil' do + expect_any_instance_of(Gitlab::GitalyClient::RemoteService) + .not_to receive(:find_remote_root_ref) + + expect(repository.find_remote_root_ref(nil)).to be_nil end - context 'with gitaly enabled', :skip_gitaly_mock do - it_behaves_like 'repository mirror fetching' + it 'returns nil when remote name is empty' do + expect_any_instance_of(Gitlab::GitalyClient::RemoteService) + .not_to receive(:find_remote_root_ref) + + expect(repository.find_remote_root_ref('')).to be_nil end - def new_repository_path - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - new_repository.path - end + it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RemoteService, :find_remote_root_ref do + subject { repository.find_remote_root_ref('origin') } end end @@ -887,25 +869,15 @@ describe Gitlab::Git::Repository, :seed_helper do end describe '#merge_base' do - shared_examples '#merge_base' do - where(:from, :to, :result) do - '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' - '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' - '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | 'foobar' | nil - 'foobar' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | nil - end - - with_them do - it { expect(repository.merge_base(from, to)).to eq(result) } - end - end - - context 'with gitaly' do - it_behaves_like '#merge_base' + where(:from, :to, :result) do + '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' + '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' + '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | 'foobar' | nil + 'foobar' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | nil end - context 'without gitaly', :skip_gitaly_mock do - it_behaves_like '#merge_base' + with_them do + it { expect(repository.merge_base(from, to)).to eq(result) } end end @@ -997,54 +969,6 @@ describe Gitlab::Git::Repository, :seed_helper do end end - describe '#autocrlf' do - before(:all) do - @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') - @repo.rugged.config['core.autocrlf'] = true - end - - around do |example| - # OK because autocrlf is only used in gitaly-ruby - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - example.run - end - end - - it 'return the value of the autocrlf option' do - expect(@repo.autocrlf).to be(true) - end - - after(:all) do - @repo.rugged.config.delete('core.autocrlf') - end - end - - describe '#autocrlf=' do - before(:all) do - @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') - @repo.rugged.config['core.autocrlf'] = false - end - - around do |example| - # OK because autocrlf= is only used in gitaly-ruby - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - example.run - end - end - - it 'should set the autocrlf option to the provided option' do - @repo.autocrlf = :input - - File.open(File.join(SEED_STORAGE_PATH, TEST_MUTABLE_REPO_PATH, 'config')) do |config_file| - expect(config_file.read).to match('autocrlf = input') - end - end - - after(:all) do - @repo.rugged.config.delete('core.autocrlf') - end - end - describe '#find_branch' do it 'should return a Branch for master' do branch = repository.find_branch('master') @@ -1144,57 +1068,47 @@ describe Gitlab::Git::Repository, :seed_helper do end describe '#merged_branch_names' do - shared_examples 'finding merged branch names' do - context 'when branch names are passed' do - it 'only returns the names we are asking' do - names = repository.merged_branch_names(%w[merge-test]) + context 'when branch names are passed' do + it 'only returns the names we are asking' do + names = repository.merged_branch_names(%w[merge-test]) - expect(names).to contain_exactly('merge-test') - end + expect(names).to contain_exactly('merge-test') + end - it 'does not return unmerged branch names' do - names = repository.merged_branch_names(%w[feature]) + it 'does not return unmerged branch names' do + names = repository.merged_branch_names(%w[feature]) - expect(names).to be_empty - end + expect(names).to be_empty end + end - context 'when no root ref is available' do - it 'returns empty list' do - project = create(:project, :empty_repo) + context 'when no root ref is available' do + it 'returns empty list' do + project = create(:project, :empty_repo) - names = project.repository.merged_branch_names(%w[feature]) + names = project.repository.merged_branch_names(%w[feature]) - expect(names).to be_empty - end + expect(names).to be_empty end + end - context 'when no branch names are specified' do - before do - repository.create_branch('identical', 'master') - end - - after do - ensure_seeds - end - - it 'returns all merged branch names except for identical one' do - names = repository.merged_branch_names + context 'when no branch names are specified' do + before do + repository.create_branch('identical', 'master') + end - expect(names).to include('merge-test') - expect(names).to include('fix-mode') - expect(names).not_to include('feature') - expect(names).not_to include('identical') - end + after do + ensure_seeds end - end - context 'when Gitaly merged_branch_names feature is enabled' do - it_behaves_like 'finding merged branch names' - end + it 'returns all merged branch names except for identical one' do + names = repository.merged_branch_names - context 'when Gitaly merged_branch_names feature is disabled', :disable_gitaly do - it_behaves_like 'finding merged branch names' + expect(names).to include('merge-test') + expect(names).to include('fix-mode') + expect(names).not_to include('feature') + expect(names).not_to include('identical') + end end end @@ -1311,76 +1225,46 @@ describe Gitlab::Git::Repository, :seed_helper do end describe '#ref_exists?' do - shared_examples 'checks the existence of refs' do - it 'returns true for an existing tag' do - expect(repository.ref_exists?('refs/heads/master')).to eq(true) - end - - it 'returns false for a non-existing tag' do - expect(repository.ref_exists?('refs/tags/THIS_TAG_DOES_NOT_EXIST')).to eq(false) - end - - it 'raises an ArgumentError for an empty string' do - expect { repository.ref_exists?('') }.to raise_error(ArgumentError) - end + it 'returns true for an existing tag' do + expect(repository.ref_exists?('refs/heads/master')).to eq(true) + end - it 'raises an ArgumentError for an invalid ref' do - expect { repository.ref_exists?('INVALID') }.to raise_error(ArgumentError) - end + it 'returns false for a non-existing tag' do + expect(repository.ref_exists?('refs/tags/THIS_TAG_DOES_NOT_EXIST')).to eq(false) end - context 'when Gitaly ref_exists feature is enabled' do - it_behaves_like 'checks the existence of refs' + it 'raises an ArgumentError for an empty string' do + expect { repository.ref_exists?('') }.to raise_error(ArgumentError) end - context 'when Gitaly ref_exists feature is disabled', :skip_gitaly_mock do - it_behaves_like 'checks the existence of refs' + it 'raises an ArgumentError for an invalid ref' do + expect { repository.ref_exists?('INVALID') }.to raise_error(ArgumentError) end end describe '#tag_exists?' do - shared_examples 'checks the existence of tags' do - it 'returns true for an existing tag' do - tag = repository.tag_names.first + it 'returns true for an existing tag' do + tag = repository.tag_names.first - expect(repository.tag_exists?(tag)).to eq(true) - end - - it 'returns false for a non-existing tag' do - expect(repository.tag_exists?('v9000')).to eq(false) - end - end - - context 'when Gitaly ref_exists_tags feature is enabled' do - it_behaves_like 'checks the existence of tags' + expect(repository.tag_exists?(tag)).to eq(true) end - context 'when Gitaly ref_exists_tags feature is disabled', :skip_gitaly_mock do - it_behaves_like 'checks the existence of tags' + it 'returns false for a non-existing tag' do + expect(repository.tag_exists?('v9000')).to eq(false) end end describe '#branch_exists?' do - shared_examples 'checks the existence of branches' do - it 'returns true for an existing branch' do - expect(repository.branch_exists?('master')).to eq(true) - end - - it 'returns false for a non-existing branch' do - expect(repository.branch_exists?('kittens')).to eq(false) - end - - it 'returns false when using an invalid branch name' do - expect(repository.branch_exists?('.bla')).to eq(false) - end + it 'returns true for an existing branch' do + expect(repository.branch_exists?('master')).to eq(true) end - context 'when Gitaly ref_exists_branches feature is enabled' do - it_behaves_like 'checks the existence of branches' + it 'returns false for a non-existing branch' do + expect(repository.branch_exists?('kittens')).to eq(false) end - context 'when Gitaly ref_exists_branches feature is disabled', :skip_gitaly_mock do - it_behaves_like 'checks the existence of branches' + it 'returns false when using an invalid branch name' do + expect(repository.branch_exists?('.bla')).to eq(false) end end @@ -1471,52 +1355,6 @@ describe Gitlab::Git::Repository, :seed_helper do end end - describe '#with_repo_branch_commit' do - context 'when comparing with the same repository' do - let(:start_repository) { repository } - - context 'when the branch exists' do - let(:start_branch_name) { 'master' } - - it 'yields the commit' do - expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) } - .to yield_with_args(an_instance_of(Gitlab::Git::Commit)) - end - end - - context 'when the branch does not exist' do - let(:start_branch_name) { 'definitely-not-master' } - - it 'yields nil' do - expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) } - .to yield_with_args(nil) - end - end - end - - context 'when comparing with another repository' do - let(:start_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') } - - context 'when the branch exists' do - let(:start_branch_name) { 'master' } - - it 'yields the commit' do - expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) } - .to yield_with_args(an_instance_of(Gitlab::Git::Commit)) - end - end - - context 'when the branch does not exist' do - let(:start_branch_name) { 'definitely-not-master' } - - it 'yields nil' do - expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) } - .to yield_with_args(nil) - end - end - end - end - describe '#fetch_source_branch!' do let(:local_ref) { 'refs/merge-requests/1/head' } let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } @@ -1567,29 +1405,19 @@ describe Gitlab::Git::Repository, :seed_helper do end describe '#rm_branch' do - shared_examples "user deleting a branch" do - let(:project) { create(:project, :repository) } - let(:repository) { project.repository.raw } - let(:branch_name) { "to-be-deleted-soon" } - - before do - project.add_developer(user) - repository.create_branch(branch_name) - end + let(:project) { create(:project, :repository) } + let(:repository) { project.repository.raw } + let(:branch_name) { "to-be-deleted-soon" } - it "removes the branch from the repo" do - repository.rm_branch(branch_name, user: user) - - expect(repository_rugged.branches[branch_name]).to be_nil - end + before do + project.add_developer(user) + repository.create_branch(branch_name) end - context "when Gitaly user_delete_branch is enabled" do - it_behaves_like "user deleting a branch" - end + it "removes the branch from the repo" do + repository.rm_branch(branch_name, user: user) - context "when Gitaly user_delete_branch is disabled", :skip_gitaly_mock do - it_behaves_like "user deleting a branch" + expect(repository_rugged.branches[branch_name]).to be_nil end end @@ -1713,39 +1541,29 @@ describe Gitlab::Git::Repository, :seed_helper do ensure_seeds end - shared_examples '#merge' do - it 'can perform a merge' do - merge_commit_id = nil - result = repository.merge(user, source_sha, target_branch, 'Test merge') do |commit_id| - merge_commit_id = commit_id - end - - expect(result.newrev).to eq(merge_commit_id) - expect(result.repo_created).to eq(false) - expect(result.branch_created).to eq(false) + it 'can perform a merge' do + merge_commit_id = nil + result = repository.merge(user, source_sha, target_branch, 'Test merge') do |commit_id| + merge_commit_id = commit_id end - it 'returns nil if there was a concurrent branch update' do - concurrent_update_id = '33f3729a45c02fc67d00adb1b8bca394b0e761d9' - result = repository.merge(user, source_sha, target_branch, 'Test merge') do - # This ref update should make the merge fail - repository.write_ref(Gitlab::Git::BRANCH_REF_PREFIX + target_branch, concurrent_update_id) - end - - # This 'nil' signals that the merge was not applied - expect(result).to be_nil + expect(result.newrev).to eq(merge_commit_id) + expect(result.repo_created).to eq(false) + expect(result.branch_created).to eq(false) + end - # Our concurrent ref update should not have been undone - expect(repository.find_branch(target_branch).target).to eq(concurrent_update_id) + it 'returns nil if there was a concurrent branch update' do + concurrent_update_id = '33f3729a45c02fc67d00adb1b8bca394b0e761d9' + result = repository.merge(user, source_sha, target_branch, 'Test merge') do + # This ref update should make the merge fail + repository.write_ref(Gitlab::Git::BRANCH_REF_PREFIX + target_branch, concurrent_update_id) end - end - context 'with gitaly' do - it_behaves_like '#merge' - end + # This 'nil' signals that the merge was not applied + expect(result).to be_nil - context 'without gitaly', :skip_gitaly_mock do - it_behaves_like '#merge' + # Our concurrent ref update should not have been undone + expect(repository.find_branch(target_branch).target).to eq(concurrent_update_id) end end @@ -1857,88 +1675,47 @@ describe Gitlab::Git::Repository, :seed_helper do describe '#add_remote' do let(:mirror_refmap) { '+refs/*:refs/*' } - shared_examples 'add_remote' do - it 'added the remote' do - begin - rugged.remotes.delete(remote_name) - rescue Rugged::ConfigError - end - - repository.add_remote(remote_name, url, mirror_refmap: mirror_refmap) - - expect(rugged.remotes[remote_name]).not_to be_nil - expect(rugged.config["remote.#{remote_name}.mirror"]).to eq('true') - expect(rugged.config["remote.#{remote_name}.prune"]).to eq('true') - expect(rugged.config["remote.#{remote_name}.fetch"]).to eq(mirror_refmap) + it 'added the remote' do + begin + rugged.remotes.delete(remote_name) + rescue Rugged::ConfigError end - end - context 'using Gitaly' do - it_behaves_like 'add_remote' - end + repository.add_remote(remote_name, url, mirror_refmap: mirror_refmap) - context 'with Gitaly disabled', :disable_gitaly do - it_behaves_like 'add_remote' + expect(rugged.remotes[remote_name]).not_to be_nil + expect(rugged.config["remote.#{remote_name}.mirror"]).to eq('true') + expect(rugged.config["remote.#{remote_name}.prune"]).to eq('true') + expect(rugged.config["remote.#{remote_name}.fetch"]).to eq(mirror_refmap) end end describe '#remove_remote' do - shared_examples 'remove_remote' do - it 'removes the remote' do - rugged.remotes.create(remote_name, url) - - repository.remove_remote(remote_name) - - expect(rugged.remotes[remote_name]).to be_nil - end - end - - context 'using Gitaly' do - it_behaves_like 'remove_remote' - end - - context 'with Gitaly disabled', :disable_gitaly do - it_behaves_like 'remove_remote' - end - end - end + it 'removes the remote' do + rugged.remotes.create(remote_name, url) - describe '#gitlab_projects' do - subject { repository.gitlab_projects } + repository.remove_remote(remote_name) - it do - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - expect(subject.shard_path).to eq(storage_path) + expect(rugged.remotes[remote_name]).to be_nil end end - it { expect(subject.repository_relative_path).to eq(repository.relative_path) } end describe '#bundle_to_disk' do - shared_examples 'bundling to disk' do - let(:save_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") } + let(:save_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") } - after do - FileUtils.rm_rf(save_path) - end - - it 'saves a bundle to disk' do - repository.bundle_to_disk(save_path) - - success = system( - *%W(#{Gitlab.config.git.bin_path} -C #{repository_path} bundle verify #{save_path}), - [:out, :err] => '/dev/null' - ) - expect(success).to be true - end + after do + FileUtils.rm_rf(save_path) end - context 'when Gitaly bundle_to_disk feature is enabled' do - it_behaves_like 'bundling to disk' - end + it 'saves a bundle to disk' do + repository.bundle_to_disk(save_path) - context 'when Gitaly bundle_to_disk feature is disabled', :disable_gitaly do - it_behaves_like 'bundling to disk' + success = system( + *%W(#{Gitlab.config.git.bin_path} -C #{repository_path} bundle verify #{save_path}), + [:out, :err] => '/dev/null' + ) + expect(success).to be true end end @@ -2013,138 +1790,41 @@ describe Gitlab::Git::Repository, :seed_helper do end end - context 'gitlab_projects commands' do - let(:gitlab_projects) { repository.gitlab_projects } - let(:timeout) { Gitlab.config.gitlab_shell.git_timeout } + describe '#clean_stale_repository_files' do + let(:worktree_path) { File.join(repository_path, 'worktrees', 'delete-me') } - describe '#push_remote_branches' do - subject do - repository.push_remote_branches('downstream-remote', ['master']) - end + it 'cleans up the files' do + create_worktree = %W[git -C #{repository_path} worktree add --detach #{worktree_path} master] + raise 'preparation failed' unless system(*create_worktree, err: '/dev/null') - it 'executes the command' do - expect(gitlab_projects).to receive(:push_branches) - .with('downstream-remote', timeout, true, ['master']) - .and_return(true) + FileUtils.touch(worktree_path, mtime: Time.now - 8.hours) + # git rev-list --all will fail in git 2.16 if HEAD is pointing to a non-existent object, + # but the HEAD must be 40 characters long or git will ignore it. + File.write(File.join(worktree_path, 'HEAD'), Gitlab::Git::BLANK_SHA) - is_expected.to be_truthy - end + # git 2.16 fails with "fatal: bad object HEAD" + expect(rev_list_all).to be false - it 'raises an error if the command fails' do - allow(gitlab_projects).to receive(:output) { 'error' } - expect(gitlab_projects).to receive(:push_branches) - .with('downstream-remote', timeout, true, ['master']) - .and_return(false) + repository.clean_stale_repository_files - expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error') - end + expect(rev_list_all).to be true + expect(File.exist?(worktree_path)).to be_falsey end - describe '#delete_remote_branches' do - subject do - repository.delete_remote_branches('downstream-remote', ['master']) - end - - it 'executes the command' do - expect(gitlab_projects).to receive(:delete_remote_branches) - .with('downstream-remote', ['master']) - .and_return(true) - - is_expected.to be_truthy - end - - it 'raises an error if the command fails' do - allow(gitlab_projects).to receive(:output) { 'error' } - expect(gitlab_projects).to receive(:delete_remote_branches) - .with('downstream-remote', ['master']) - .and_return(false) - - expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error') - end - end - - describe '#delete_remote_branches' do - subject do - repository.delete_remote_branches('downstream-remote', ['master']) - end - - it 'executes the command' do - expect(gitlab_projects).to receive(:delete_remote_branches) - .with('downstream-remote', ['master']) - .and_return(true) - - is_expected.to be_truthy - end - - it 'raises an error if the command fails' do - allow(gitlab_projects).to receive(:output) { 'error' } - expect(gitlab_projects).to receive(:delete_remote_branches) - .with('downstream-remote', ['master']) - .and_return(false) - - expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error') - end - end - - describe '#clean_stale_repository_files' do - let(:worktree_path) { File.join(repository_path, 'worktrees', 'delete-me') } - - it 'cleans up the files' do - create_worktree = %W[git -C #{repository_path} worktree add --detach #{worktree_path} master] - raise 'preparation failed' unless system(*create_worktree, err: '/dev/null') - - FileUtils.touch(worktree_path, mtime: Time.now - 8.hours) - # git rev-list --all will fail in git 2.16 if HEAD is pointing to a non-existent object, - # but the HEAD must be 40 characters long or git will ignore it. - File.write(File.join(worktree_path, 'HEAD'), Gitlab::Git::BLANK_SHA) - - # git 2.16 fails with "fatal: bad object HEAD" - expect(rev_list_all).to be false - - repository.clean_stale_repository_files - - expect(rev_list_all).to be true - expect(File.exist?(worktree_path)).to be_falsey - end - - def rev_list_all - system(*%W[git -C #{repository_path} rev-list --all], out: '/dev/null', err: '/dev/null') - end - - it 'increments a counter upon an error' do - expect(repository.gitaly_repository_client).to receive(:cleanup).and_raise(Gitlab::Git::CommandError) - - counter = double(:counter) - - expect(counter).to receive(:increment) - expect(Gitlab::Metrics).to receive(:counter).with(:failed_repository_cleanup_total, - 'Number of failed repository cleanup events').and_return(counter) - - repository.clean_stale_repository_files - end + def rev_list_all + system(*%W[git -C #{repository_path} rev-list --all], out: '/dev/null', err: '/dev/null') end - describe '#delete_remote_branches' do - subject do - repository.delete_remote_branches('downstream-remote', ['master']) - end + it 'increments a counter upon an error' do + expect(repository.gitaly_repository_client).to receive(:cleanup).and_raise(Gitlab::Git::CommandError) - it 'executes the command' do - expect(gitlab_projects).to receive(:delete_remote_branches) - .with('downstream-remote', ['master']) - .and_return(true) + counter = double(:counter) - is_expected.to be_truthy - end - - it 'raises an error if the command fails' do - allow(gitlab_projects).to receive(:output) { 'error' } - expect(gitlab_projects).to receive(:delete_remote_branches) - .with('downstream-remote', ['master']) - .and_return(false) + expect(counter).to receive(:increment) + expect(Gitlab::Metrics).to receive(:counter).with(:failed_repository_cleanup_total, + 'Number of failed repository cleanup events').and_return(counter) - expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error') - end + repository.clean_stale_repository_files end end diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index 54f2ea33f90..bcdf12a00a0 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -19,7 +19,14 @@ describe Gitlab::GitalyClient::CommitService do right_commit_id: commit.id, collapse_diffs: false, enforce_limits: true, - **Gitlab::Git::DiffCollection.collection_limits.to_h + # Tests limitation parameters explicitly + max_files: 100, + max_lines: 5000, + max_bytes: 512000, + safe_max_files: 100, + safe_max_lines: 5000, + safe_max_bytes: 512000, + max_patch_bytes: 102400 ) expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash)) @@ -37,7 +44,14 @@ describe Gitlab::GitalyClient::CommitService do right_commit_id: initial_commit.id, collapse_diffs: false, enforce_limits: true, - **Gitlab::Git::DiffCollection.collection_limits.to_h + # Tests limitation parameters explicitly + max_files: 100, + max_lines: 5000, + max_bytes: 512000, + safe_max_files: 100, + safe_max_lines: 5000, + safe_max_bytes: 512000, + max_patch_bytes: 102400 ) expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash)) diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb index f03c7e3f04b..9030a49983d 100644 --- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb @@ -45,6 +45,26 @@ describe Gitlab::GitalyClient::RemoteService do end end + describe '#find_remote_root_ref' do + it 'sends an find_remote_root_ref message and returns the root ref' do + expect_any_instance_of(Gitaly::RemoteService::Stub) + .to receive(:find_remote_root_ref) + .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) + .and_return(double(ref: 'master')) + + expect(client.find_remote_root_ref('origin')).to eq 'master' + end + + it 'ensure ref is a valid UTF-8 string' do + expect_any_instance_of(Gitaly::RemoteService::Stub) + .to receive(:find_remote_root_ref) + .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) + .and_return(double(ref: "an_invalid_ref_\xE5")) + + expect(client.find_remote_root_ref('origin')).to eq "an_invalid_ref_Ã¥" + end + end + describe '#update_remote_mirror' do let(:ref_name) { 'remote_mirror_1' } let(:only_branches_matching) { ['my-branch', 'master'] } diff --git a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_object_storage_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_object_storage_spec.rb deleted file mode 100644 index 5059d68e54b..00000000000 --- a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_object_storage_spec.rb +++ /dev/null @@ -1,105 +0,0 @@ -require 'spec_helper' - -describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do - let!(:service) { described_class.new } - let!(:project) { create(:project, :with_object_export) } - let(:shared) { project.import_export_shared } - let!(:user) { create(:user) } - - describe '#execute' do - before do - allow(service).to receive(:strategy_execute) - stub_feature_flags(import_export_object_storage: true) - end - - it 'returns if project exported file is not found' do - allow(project).to receive(:export_project_object_exists?).and_return(false) - - expect(service).not_to receive(:strategy_execute) - - service.execute(user, project) - end - - it 'creates a lock file in the export dir' do - allow(service).to receive(:delete_after_export_lock) - - service.execute(user, project) - - expect(lock_path_exist?).to be_truthy - end - - context 'when the method succeeds' do - it 'removes the lock file' do - service.execute(user, project) - - expect(lock_path_exist?).to be_falsey - end - end - - context 'when the method fails' do - before do - allow(service).to receive(:strategy_execute).and_call_original - end - - context 'when validation fails' do - before do - allow(service).to receive(:invalid?).and_return(true) - end - - it 'does not create the lock file' do - expect(service).not_to receive(:create_or_update_after_export_lock) - - service.execute(user, project) - end - - it 'does not execute main logic' do - expect(service).not_to receive(:strategy_execute) - - service.execute(user, project) - end - - it 'logs validation errors in shared context' do - expect(service).to receive(:log_validation_errors) - - service.execute(user, project) - end - end - - context 'when an exception is raised' do - it 'removes the lock' do - expect { service.execute(user, project) }.to raise_error(NotImplementedError) - - expect(lock_path_exist?).to be_falsey - end - end - end - end - - describe '#log_validation_errors' do - it 'add the message to the shared context' do - errors = %w(test_message test_message2) - - allow(service).to receive(:invalid?).and_return(true) - allow(service.errors).to receive(:full_messages).and_return(errors) - - expect(shared).to receive(:add_error_message).twice.and_call_original - - service.execute(user, project) - - expect(shared.errors).to eq errors - end - end - - describe '#to_json' do - it 'adds the current strategy class to the serialized attributes' do - params = { param1: 1 } - result = params.merge(klass: described_class.to_s).to_json - - expect(described_class.new(params).to_json).to eq result - end - end - - def lock_path_exist? - File.exist?(described_class.lock_file_path(project)) - end -end diff --git a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb index 566b7f46c87..9a442de2900 100644 --- a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb +++ b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb @@ -9,11 +9,10 @@ describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do describe '#execute' do before do allow(service).to receive(:strategy_execute) - stub_feature_flags(import_export_object_storage: false) end it 'returns if project exported file is not found' do - allow(project).to receive(:export_project_path).and_return(nil) + allow(project).to receive(:export_file_exists?).and_return(false) expect(service).not_to receive(:strategy_execute) diff --git a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb index 7f2e0a4ee2c..ec17ad8541f 100644 --- a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb +++ b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb @@ -24,34 +24,13 @@ describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do end describe '#execute' do - context 'without object storage' do - before do - stub_feature_flags(import_export_object_storage: false) - end - - it 'removes the exported project file after the upload' do - allow(strategy).to receive(:send_file) - allow(strategy).to receive(:handle_response_error) - - expect(project).to receive(:remove_exported_project_file) - - strategy.execute(user, project) - end - end - - context 'with object storage' do - before do - stub_feature_flags(import_export_object_storage: true) - end + it 'removes the exported project file after the upload' do + allow(strategy).to receive(:send_file) + allow(strategy).to receive(:handle_response_error) - it 'removes the exported project file after the upload' do - allow(strategy).to receive(:send_file) - allow(strategy).to receive(:handle_response_error) + expect(project).to receive(:remove_exports) - expect(project).to receive(:remove_exported_project_file) - - strategy.execute(user, project) - end + strategy.execute(user, project) end end end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index b4269bd5786..ec2bdbe22e1 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -288,6 +288,7 @@ project: - fork_network_member - fork_network - custom_attributes +- prometheus_metrics - lfs_file_locks - project_badges - source_of_merge_requests @@ -303,6 +304,8 @@ award_emoji: - user priorities: - label +prometheus_metrics: +- project timelogs: - issue - merge_request @@ -321,3 +324,9 @@ metrics: - latest_closed_by - merged_by - pipeline +resource_label_events: +- user +- issue +- merge_request +- epic +- label diff --git a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb index 4897d604bc1..e44ff6bbcbd 100644 --- a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb @@ -6,22 +6,35 @@ describe Gitlab::ImportExport::AvatarRestorer do let(:shared) { project.import_export_shared } let(:project) { create(:project) } - before do - allow_any_instance_of(described_class).to receive(:avatar_export_file) - .and_return(uploaded_image_temp_path) - end - after do project.remove_avatar! end - it 'restores a project avatar' do - expect(described_class.new(project: project, shared: shared).restore).to be true + context 'with avatar' do + before do + allow_any_instance_of(described_class).to receive(:avatar_export_file) + .and_return(uploaded_image_temp_path) + end + + it 'restores a project avatar' do + expect(described_class.new(project: project, shared: shared).restore).to be true + end + + it 'saves the avatar into the project' do + described_class.new(project: project, shared: shared).restore + + expect(project.reload.avatar.file.exists?).to be true + end end - it 'saves the avatar into the project' do - described_class.new(project: project, shared: shared).restore + it 'does not break if there is just a directory' do + Dir.mktmpdir do |tmpdir| + FileUtils.mkdir_p("#{tmpdir}/a/b") + + allow_any_instance_of(described_class).to receive(:avatar_export_path) + .and_return("#{tmpdir}/a") - expect(project.reload.avatar.file.exists?).to be true + expect(described_class.new(project: project, shared: shared).restore).to be true + end end end diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb index 90e6d653d34..2bd1b9924c6 100644 --- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb +++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb @@ -8,8 +8,7 @@ describe Gitlab::ImportExport::AvatarSaver do before do FileUtils.mkdir_p("#{shared.export_path}/avatar/") - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) - stub_feature_flags(import_export_object_storage: false) + allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:export_path).and_return(export_path) end after do @@ -19,7 +18,7 @@ describe Gitlab::ImportExport::AvatarSaver do it 'saves a project avatar' do described_class.new(project: project_with_avatar, shared: shared).save - expect(File).to exist("#{shared.export_path}/avatar/dk.png") + expect(File).to exist(Dir["#{shared.export_path}/avatar/**/dk.png"].first) end it 'is fine not to have an avatar' do diff --git a/spec/lib/gitlab/import_export/file_importer_object_storage_spec.rb b/spec/lib/gitlab/import_export/file_importer_object_storage_spec.rb deleted file mode 100644 index 287745eb40e..00000000000 --- a/spec/lib/gitlab/import_export/file_importer_object_storage_spec.rb +++ /dev/null @@ -1,89 +0,0 @@ -require 'spec_helper' - -describe Gitlab::ImportExport::FileImporter do - let(:shared) { Gitlab::ImportExport::Shared.new(nil) } - let(:storage_path) { "#{Dir.tmpdir}/file_importer_spec" } - let(:valid_file) { "#{shared.export_path}/valid.json" } - let(:symlink_file) { "#{shared.export_path}/invalid.json" } - let(:hidden_symlink_file) { "#{shared.export_path}/.hidden" } - let(:subfolder_symlink_file) { "#{shared.export_path}/subfolder/invalid.json" } - let(:evil_symlink_file) { "#{shared.export_path}/.\nevil" } - - before do - stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0) - stub_feature_flags(import_export_object_storage: true) - stub_uploads_object_storage(FileUploader) - - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(storage_path) - allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true) - allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('test') - allow(SecureRandom).to receive(:hex).and_return('abcd') - setup_files - end - - after do - FileUtils.rm_rf(storage_path) - end - - context 'normal run' do - before do - described_class.import(project: build(:project), archive_file: '', shared: shared) - end - - it 'removes symlinks in root folder' do - expect(File.exist?(symlink_file)).to be false - end - - it 'removes hidden symlinks in root folder' do - expect(File.exist?(hidden_symlink_file)).to be false - end - - it 'removes evil symlinks in root folder' do - expect(File.exist?(evil_symlink_file)).to be false - end - - it 'removes symlinks in subfolders' do - expect(File.exist?(subfolder_symlink_file)).to be false - end - - it 'does not remove a valid file' do - expect(File.exist?(valid_file)).to be true - end - - it 'creates the file in the right subfolder' do - expect(shared.export_path).to include('test/abcd') - end - end - - context 'error' do - before do - allow_any_instance_of(described_class).to receive(:wait_for_archived_file).and_raise(StandardError) - described_class.import(project: build(:project), archive_file: '', shared: shared) - end - - it 'removes symlinks in root folder' do - expect(File.exist?(symlink_file)).to be false - end - - it 'removes hidden symlinks in root folder' do - expect(File.exist?(hidden_symlink_file)).to be false - end - - it 'removes symlinks in subfolders' do - expect(File.exist?(subfolder_symlink_file)).to be false - end - - it 'does not remove a valid file' do - expect(File.exist?(valid_file)).to be true - end - end - - def setup_files - FileUtils.mkdir_p("#{shared.export_path}/subfolder/") - FileUtils.touch(valid_file) - FileUtils.ln_s(valid_file, symlink_file) - FileUtils.ln_s(valid_file, subfolder_symlink_file) - FileUtils.ln_s(valid_file, hidden_symlink_file) - FileUtils.ln_s(valid_file, evil_symlink_file) - end -end diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb index 78fccdf1dfc..bf34cefe18f 100644 --- a/spec/lib/gitlab/import_export/file_importer_spec.rb +++ b/spec/lib/gitlab/import_export/file_importer_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::ImportExport::FileImporter do let(:shared) { Gitlab::ImportExport::Shared.new(nil) } - let(:export_path) { "#{Dir.tmpdir}/file_importer_spec" } + let(:storage_path) { "#{Dir.tmpdir}/file_importer_spec" } let(:valid_file) { "#{shared.export_path}/valid.json" } let(:symlink_file) { "#{shared.export_path}/invalid.json" } let(:hidden_symlink_file) { "#{shared.export_path}/.hidden" } @@ -11,7 +11,9 @@ describe Gitlab::ImportExport::FileImporter do before do stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0) - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + stub_uploads_object_storage(FileUploader) + + allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(storage_path) allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true) allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('test') allow(SecureRandom).to receive(:hex).and_return('abcd') @@ -19,12 +21,12 @@ describe Gitlab::ImportExport::FileImporter do end after do - FileUtils.rm_rf(export_path) + FileUtils.rm_rf(storage_path) end context 'normal run' do before do - described_class.import(project: nil, archive_file: '', shared: shared) + described_class.import(project: build(:project), archive_file: '', shared: shared) end it 'removes symlinks in root folder' do @@ -55,7 +57,7 @@ describe Gitlab::ImportExport::FileImporter do context 'error' do before do allow_any_instance_of(described_class).to receive(:wait_for_archived_file).and_raise(StandardError) - described_class.import(project: nil, archive_file: '', shared: shared) + described_class.import(project: build(:project), archive_file: '', shared: shared) end it 'removes symlinks in root folder' do diff --git a/spec/lib/gitlab/import_export/importer_object_storage_spec.rb b/spec/lib/gitlab/import_export/importer_object_storage_spec.rb deleted file mode 100644 index 24a994b3611..00000000000 --- a/spec/lib/gitlab/import_export/importer_object_storage_spec.rb +++ /dev/null @@ -1,115 +0,0 @@ -require 'spec_helper' - -describe Gitlab::ImportExport::Importer do - let(:user) { create(:user) } - let(:test_path) { "#{Dir.tmpdir}/importer_spec" } - let(:shared) { project.import_export_shared } - let(:project) { create(:project) } - let(:import_file) { fixture_file_upload('spec/features/projects/import_export/test_project_export.tar.gz') } - - subject(:importer) { described_class.new(project) } - - before do - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(test_path) - allow_any_instance_of(Gitlab::ImportExport::FileImporter).to receive(:remove_import_file) - stub_feature_flags(import_export_object_storage: true) - stub_uploads_object_storage(FileUploader) - - FileUtils.mkdir_p(shared.export_path) - ImportExportUpload.create(project: project, import_file: import_file) - end - - after do - FileUtils.rm_rf(test_path) - end - - describe '#execute' do - it 'succeeds' do - importer.execute - - expect(shared.errors).to be_empty - end - - it 'extracts the archive' do - expect(Gitlab::ImportExport::FileImporter).to receive(:import).and_call_original - - importer.execute - end - - it 'checks the version' do - expect(Gitlab::ImportExport::VersionChecker).to receive(:check!).and_call_original - - importer.execute - end - - context 'all restores are executed' do - [ - Gitlab::ImportExport::AvatarRestorer, - Gitlab::ImportExport::RepoRestorer, - Gitlab::ImportExport::WikiRestorer, - Gitlab::ImportExport::UploadsRestorer, - Gitlab::ImportExport::LfsRestorer, - Gitlab::ImportExport::StatisticsRestorer - ].each do |restorer| - it "calls the #{restorer}" do - fake_restorer = double(restorer.to_s) - - expect(fake_restorer).to receive(:restore).and_return(true).at_least(1) - expect(restorer).to receive(:new).and_return(fake_restorer).at_least(1) - - importer.execute - end - end - - it 'restores the ProjectTree' do - expect(Gitlab::ImportExport::ProjectTreeRestorer).to receive(:new).and_call_original - - importer.execute - end - - it 'removes the import file' do - expect(importer).to receive(:remove_import_file).and_call_original - - importer.execute - - expect(project.import_export_upload.import_file&.file).to be_nil - end - end - - context 'when project successfully restored' do - let!(:existing_project) { create(:project, namespace: user.namespace) } - let(:project) { create(:project, namespace: user.namespace, name: 'whatever', path: 'whatever') } - - before do - restorers = double(:restorers, all?: true) - - allow(subject).to receive(:import_file).and_return(true) - allow(subject).to receive(:check_version!).and_return(true) - allow(subject).to receive(:restorers).and_return(restorers) - allow(project).to receive(:import_data).and_return(double(data: { 'original_path' => existing_project.path })) - end - - context 'when import_data' do - context 'has original_path' do - it 'overwrites existing project' do - expect_any_instance_of(::Projects::OverwriteProjectService).to receive(:execute).with(existing_project) - - subject.execute - end - end - - context 'has not original_path' do - before do - allow(project).to receive(:import_data).and_return(double(data: {})) - end - - it 'does not call the overwrite service' do - expect_any_instance_of(::Projects::OverwriteProjectService).not_to receive(:execute).with(existing_project) - - subject.execute - end - end - end - end - end -end diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb index 8053c48ad6c..11f98d782b1 100644 --- a/spec/lib/gitlab/import_export/importer_spec.rb +++ b/spec/lib/gitlab/import_export/importer_spec.rb @@ -4,16 +4,18 @@ describe Gitlab::ImportExport::Importer do let(:user) { create(:user) } let(:test_path) { "#{Dir.tmpdir}/importer_spec" } let(:shared) { project.import_export_shared } - let(:project) { create(:project, import_source: File.join(test_path, 'test_project_export.tar.gz')) } + let(:project) { create(:project) } + let(:import_file) { fixture_file_upload('spec/features/projects/import_export/test_project_export.tar.gz') } subject(:importer) { described_class.new(project) } before do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(test_path) allow_any_instance_of(Gitlab::ImportExport::FileImporter).to receive(:remove_import_file) + stub_uploads_object_storage(FileUploader) FileUtils.mkdir_p(shared.export_path) - FileUtils.cp(Rails.root.join('spec/features/projects/import_export/test_project_export.tar.gz'), test_path) + ImportExportUpload.create(project: project, import_file: import_file) end after do @@ -64,6 +66,14 @@ describe Gitlab::ImportExport::Importer do importer.execute end + it 'removes the import file' do + expect(importer).to receive(:remove_import_file).and_call_original + + importer.execute + + expect(project.import_export_upload.import_file&.file).to be_nil + end + it 'sets the correct visibility_level when visibility level is a string' do project.create_or_update_import_data( data: { override_params: { visibility_level: Gitlab::VisibilityLevel::PRIVATE.to_s } } @@ -85,7 +95,6 @@ describe Gitlab::ImportExport::Importer do allow(subject).to receive(:import_file).and_return(true) allow(subject).to receive(:check_version!).and_return(true) allow(subject).to receive(:restorers).and_return(restorers) - allow(restorers).to receive(:all?).and_return(true) allow(project).to receive(:import_data).and_return(double(data: { 'original_path' => existing_project.path })) end diff --git a/spec/lib/gitlab/import_export/model_configuration_spec.rb b/spec/lib/gitlab/import_export/model_configuration_spec.rb index 5cb8f2589c8..2e28f978c3a 100644 --- a/spec/lib/gitlab/import_export/model_configuration_spec.rb +++ b/spec/lib/gitlab/import_export/model_configuration_spec.rb @@ -16,14 +16,30 @@ describe 'Import/Export model configuration' do # - User, Author... Models we do not care about for checking models names.flatten.uniq - %w(milestones labels user author) + ['project'] end + let(:ce_models_yml) { 'spec/lib/gitlab/import_export/all_models.yml' } + let(:ce_models_hash) { YAML.load_file(ce_models_yml) } + + let(:ee_models_yml) { 'ee/spec/lib/gitlab/import_export/all_models.yml' } + let(:ee_models_hash) { File.exist?(ee_models_yml) ? YAML.load_file(ee_models_yml) : {} } - let(:all_models_yml) { 'spec/lib/gitlab/import_export/all_models.yml' } - let(:all_models) { YAML.load_file(all_models_yml) } let(:current_models) { setup_models } + let(:all_models_hash) do + all_models_hash = ce_models_hash.dup + + all_models_hash.each do |model, associations| + associations.concat(ee_models_hash[model] || []) + end + + ee_models_hash.each do |model, associations| + all_models_hash[model] ||= associations + end + + all_models_hash + end it 'has no new models' do model_names.each do |model_name| - new_models = Array(current_models[model_name]) - Array(all_models[model_name]) + new_models = Array(current_models[model_name]) - Array(all_models_hash[model_name]) expect(new_models).to be_empty, failure_message(model_name.classify, new_models) end end @@ -31,27 +47,21 @@ describe 'Import/Export model configuration' do # List of current models between models, in the format of # {model: [model_2, model3], ...} def setup_models - all_models_hash = {} - - model_names.each do |model_name| - model_class = relation_class_for_name(model_name) - - all_models_hash[model_name] = associations_for(model_class) - ['project'] + model_names.each_with_object({}) do |model_name, hash| + hash[model_name] = associations_for(relation_class_for_name(model_name)) - ['project'] end - - all_models_hash end def failure_message(parent_model_name, new_models) - <<-MSG + <<~MSG New model(s) <#{new_models.join(',')}> have been added, related to #{parent_model_name}, which is exported by the Import/Export feature. - If you think this model should be included in the export, please add it to IMPORT_EXPORT_CONFIG. - Definitely add it to MODELS_JSON to signal that you've handled this error and to prevent it from showing up in the future. + If you think this model should be included in the export, please add it to `#{Gitlab::ImportExport.config_file}`. - MODELS_JSON: #{File.expand_path(all_models_yml)} - IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file} + Definitely add it to `#{File.expand_path(ce_models_yml)}` + #{"or `#{File.expand_path(ee_models_yml)}` if the model/associations are EE-specific\n" if ee_models_hash.any?} + to signal that you've handled this error and to prevent it from showing up in the future. MSG end end diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 1b7fa11cb3c..eefd00e7383 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -331,6 +331,28 @@ }, "events": [] } + ], + "resource_label_events": [ + { + "id":244, + "action":"remove", + "issue_id":40, + "merge_request_id":null, + "label_id":2, + "user_id":1, + "created_at":"2018-08-28T08:24:00.494Z", + "label": { + "id": 2, + "title": "test2", + "color": "#428bca", + "project_id": 8, + "created_at": "2016-07-22T08:55:44.161Z", + "updated_at": "2016-07-22T08:55:44.161Z", + "template": false, + "description": "", + "type": "ProjectLabel" + } + } ] }, { @@ -2515,6 +2537,17 @@ "events": [] } ], + "resource_label_events": [ + { + "id":243, + "action":"add", + "issue_id":null, + "merge_request_id":27, + "label_id":null, + "user_id":1, + "created_at":"2018-08-28T08:24:00.494Z" + } + ], "merge_request_diff": { "id": 27, "state": "collected", diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index a88ac0a091e..3ff6be595a8 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -89,6 +89,14 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do expect(ProtectedTag.first.create_access_levels).not_to be_empty end + it 'restores issue resource label events' do + expect(Issue.find_by(title: 'Voluptatem').resource_label_events).not_to be_empty + end + + it 'restores merge requests resource label events' do + expect(MergeRequest.find_by(title: 'MR1').resource_label_events).not_to be_empty + end + context 'event at forth level of the tree' do let(:event) { Event.where(action: 6).first } diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index fec8a2af9ab..5dc372263ad 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -169,6 +169,14 @@ describe Gitlab::ImportExport::ProjectTreeSaver do expect(priorities.flatten).not_to be_empty end + it 'has issue resource label events' do + expect(saved_project_json['issues'].first['resource_label_events']).not_to be_empty + end + + it 'has merge request resource label events' do + expect(saved_project_json['merge_requests'].first['resource_label_events']).not_to be_empty + end + it 'saves the correct service type' do expect(saved_project_json['services'].first['type']).to eq('CustomIssueTrackerService') end @@ -291,6 +299,9 @@ describe Gitlab::ImportExport::ProjectTreeSaver do project: project, commit_id: ci_build.pipeline.sha) + create(:resource_label_event, label: project_label, issue: issue) + create(:resource_label_event, label: group_label, merge_request: merge_request) + create(:event, :created, target: milestone, project: project, author: user) create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker', properties: { one: 'value' }) diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb index 7ffa84f906d..8a699eb1461 100644 --- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::ImportExport::RepoRestorer do + include GitHelpers + describe 'bundle a project Git repo' do let(:user) { create(:user) } let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } @@ -36,9 +38,7 @@ describe Gitlab::ImportExport::RepoRestorer do it 'has the webhooks' do restorer.restore - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - expect(Gitlab::Git::Hook.new('post-receive', project.repository.raw_repository)).to exist - end + expect(project_hook_exists?(project)).to be true end end end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 579f175c4a8..e9f1be172b0 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -555,6 +555,19 @@ ProjectCustomAttribute: - project_id - key - value +PrometheusMetric: +- id +- created_at +- updated_at +- project_id +- y_label +- unit +- legend +- title +- query +- group +- common +- identifier Badge: - id - link_url @@ -566,3 +579,11 @@ Badge: - type ProjectCiCdSetting: - group_runners_enabled +ResourceLabelEvent: +- id +- action +- issue_id +- merge_request_id +- label_id +- user_id +- created_at diff --git a/spec/lib/gitlab/import_export/saver_spec.rb b/spec/lib/gitlab/import_export/saver_spec.rb index 02f1a4b81aa..d185ff2dfcc 100644 --- a/spec/lib/gitlab/import_export/saver_spec.rb +++ b/spec/lib/gitlab/import_export/saver_spec.rb @@ -18,26 +18,12 @@ describe Gitlab::ImportExport::Saver do FileUtils.rm_rf(export_path) end - context 'local archive' do - it 'saves the repo to disk' do - stub_feature_flags(import_export_object_storage: false) + it 'saves the repo using object storage' do + stub_uploads_object_storage(ImportExportUploader) - subject.save + subject.save - expect(shared.errors).to be_empty - expect(Dir.empty?(shared.archive_path)).to be false - end - end - - context 'object storage' do - it 'saves the repo using object storage' do - stub_feature_flags(import_export_object_storage: true) - stub_uploads_object_storage(ImportExportUploader) - - subject.save - - expect(ImportExportUpload.find_by(project: project).export_file.url) - .to match(%r[\/uploads\/-\/system\/import_export_upload\/export_file.*]) - end + expect(ImportExportUpload.find_by(project: project).export_file.url) + .to match(%r[\/uploads\/-\/system\/import_export_upload\/export_file.*]) end end diff --git a/spec/lib/gitlab/import_export/uploads_manager_spec.rb b/spec/lib/gitlab/import_export/uploads_manager_spec.rb index f799de18cd0..792117e1df1 100644 --- a/spec/lib/gitlab/import_export/uploads_manager_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_manager_spec.rb @@ -4,6 +4,7 @@ describe Gitlab::ImportExport::UploadsManager do let(:shared) { project.import_export_shared } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } let(:project) { create(:project) } + let(:upload) { create(:upload, :issuable_upload, :object_storage, model: project) } let(:exported_file_path) { "#{shared.export_path}/uploads/#{upload.secret}/#{File.basename(upload.path)}" } subject(:manager) { described_class.new(project: project, shared: shared) } @@ -69,44 +70,20 @@ describe Gitlab::ImportExport::UploadsManager do end end end + end - context 'using object storage' do - let!(:upload) { create(:upload, :issuable_upload, :object_storage, model: project) } - - before do - stub_feature_flags(import_export_object_storage: true) - stub_uploads_object_storage(FileUploader) - end - - it 'saves the file' do - fake_uri = double - - expect(fake_uri).to receive(:open).and_return(StringIO.new('File content')) - expect(URI).to receive(:parse).and_return(fake_uri) - - manager.save + describe '#restore' do + before do + stub_uploads_object_storage(FileUploader) - expect(File.read(exported_file_path)).to eq('File content') - end + FileUtils.mkdir_p(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038')) + FileUtils.touch(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038', "dummy.txt")) end - describe '#restore' do - context 'using object storage' do - before do - stub_feature_flags(import_export_object_storage: true) - stub_uploads_object_storage(FileUploader) - - FileUtils.mkdir_p(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038')) - FileUtils.touch(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038', "dummy.txt")) - end + it 'restores the file' do + manager.restore - it 'restores the file' do - manager.restore - - expect(project.uploads.size).to eq(1) - expect(project.uploads.first.build_uploader.filename).to eq('dummy.txt') - end - end + expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt') end end end diff --git a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb index acef97459b8..6072f18b8c7 100644 --- a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::ImportExport::UploadsRestorer do before do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) FileUtils.mkdir_p(File.join(shared.export_path, 'uploads/random')) - FileUtils.touch(File.join(shared.export_path, 'uploads/random', "dummy.txt")) + FileUtils.touch(File.join(shared.export_path, 'uploads/random', 'dummy.txt')) end after do @@ -27,9 +27,7 @@ describe Gitlab::ImportExport::UploadsRestorer do it 'copies the uploads to the project path' do subject.restore - uploads = Dir.glob(File.join(subject.uploads_path, '**/*')).map { |file| File.basename(file) } - - expect(uploads).to include('dummy.txt') + expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt') end end @@ -45,9 +43,7 @@ describe Gitlab::ImportExport::UploadsRestorer do it 'copies the uploads to the project path' do subject.restore - uploads = Dir.glob(File.join(subject.uploads_path, '**/*')).map { |file| File.basename(file) } - - expect(uploads).to include('dummy.txt') + expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt') end end end diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb index c716edd9397..24993460e51 100644 --- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb @@ -7,7 +7,6 @@ describe Gitlab::ImportExport::UploadsSaver do let(:shared) { project.import_export_shared } before do - stub_feature_flags(import_export_object_storage: false) allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) end diff --git a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb index 5589db92b1d..1a108003bc2 100644 --- a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb +++ b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Prometheus::AdditionalMetricsParser do let(:parser_error_class) { Gitlab::Prometheus::ParsingError } describe '#load_groups_from_yaml' do - subject { described_class.load_groups_from_yaml } + subject { described_class.load_groups_from_yaml('dummy.yaml') } describe 'parsing sample yaml' do let(:sample_yaml) do diff --git a/spec/lib/gitlab/prometheus/metric_group_spec.rb b/spec/lib/gitlab/prometheus/metric_group_spec.rb new file mode 100644 index 00000000000..e7d16e73663 --- /dev/null +++ b/spec/lib/gitlab/prometheus/metric_group_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Gitlab::Prometheus::MetricGroup do + describe '.common_metrics' do + let!(:project_metric) { create(:prometheus_metric) } + let!(:common_metric_group_a) { create(:prometheus_metric, :common, group: :aws_elb) } + let!(:common_metric_group_b_q1) { create(:prometheus_metric, :common, group: :kubernetes) } + let!(:common_metric_group_b_q2) { create(:prometheus_metric, :common, group: :kubernetes) } + + subject { described_class.common_metrics } + + it 'returns exactly two groups' do + expect(subject.map(&:name)).to contain_exactly( + 'Response metrics (AWS ELB)', 'System metrics (Kubernetes)') + end + + it 'returns exactly three metric queries' do + expect(subject.map(&:metrics).flatten.map(&:id)).to contain_exactly( + common_metric_group_a.id, common_metric_group_b_q1.id, + common_metric_group_b_q2.id) + end + end + + describe '.for_project' do + let!(:other_project) { create(:project) } + let!(:project_metric) { create(:prometheus_metric) } + let!(:common_metric) { create(:prometheus_metric, :common, group: :aws_elb) } + + subject do + described_class.for_project(other_project) + .map(&:metrics).flatten + .map(&:id) + end + + it 'returns exactly one common metric' do + is_expected.to contain_exactly(common_metric.id) + end + end +end diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index f8bf896950e..b1b7c427313 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -7,15 +7,10 @@ describe Gitlab::Shell do let(:repository) { project.repository } let(:gitlab_shell) { described_class.new } let(:popen_vars) { { 'GIT_TERMINAL_PROMPT' => ENV['GIT_TERMINAL_PROMPT'] } } - let(:gitlab_projects) { double('gitlab_projects') } let(:timeout) { Gitlab.config.gitlab_shell.git_timeout } before do allow(Project).to receive(:find).and_return(project) - - allow(gitlab_shell).to receive(:gitlab_projects) - .with(project.repository_storage, project.disk_path + '.git') - .and_return(gitlab_projects) end it { is_expected.to respond_to :add_key } diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 23869f3d2da..b3f55a2e1bd 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -336,6 +336,22 @@ describe Gitlab::Workhorse do it { expect { subject }.to raise_exception('Unsupported action: download') } end end + + context 'when receive_max_input_size has been updated' do + it 'returns custom git config' do + allow(Gitlab::CurrentSettings).to receive(:receive_max_input_size) { 1 } + + expect(subject[:GitConfigOptions]).to be_present + end + end + + context 'when receive_max_input_size is empty' do + it 'returns an empty git config' do + allow(Gitlab::CurrentSettings).to receive(:receive_max_input_size) { nil } + + expect(subject[:GitConfigOptions]).to be_empty + end + end end describe '.set_key_and_notify' do |