diff options
Diffstat (limited to 'spec/lib')
38 files changed, 1003 insertions, 210 deletions
diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb index c8e62f528df..707212e07fd 100644 --- a/spec/lib/banzai/filter/emoji_filter_spec.rb +++ b/spec/lib/banzai/filter/emoji_filter_spec.rb @@ -14,12 +14,12 @@ describe Banzai::Filter::EmojiFilter, lib: true do it 'replaces supported name emoji' do doc = filter('<p>:heart:</p>') - expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png' + expect(doc.css('gl-emoji').first.text).to eq '❤' end it 'replaces supported unicode emoji' do doc = filter('<p>❤️</p>') - expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png' + expect(doc.css('gl-emoji').first.text).to eq '❤' end it 'ignores unsupported emoji' do @@ -30,152 +30,78 @@ describe Banzai::Filter::EmojiFilter, lib: true do it 'correctly encodes the URL' do doc = filter('<p>:+1:</p>') - expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/1F44D.png' + expect(doc.css('gl-emoji').first.text).to eq '👍' end it 'correctly encodes unicode to the URL' do doc = filter('<p>👍</p>') - expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/1F44D.png' + expect(doc.css('gl-emoji').first.text).to eq '👍' end it 'matches at the start of a string' do doc = filter(':+1:') - expect(doc.css('img').size).to eq 1 + expect(doc.css('gl-emoji').size).to eq 1 end it 'unicode matches at the start of a string' do doc = filter("'👍'") - expect(doc.css('img').size).to eq 1 + expect(doc.css('gl-emoji').size).to eq 1 end it 'matches at the end of a string' do doc = filter('This gets a :-1:') - expect(doc.css('img').size).to eq 1 + expect(doc.css('gl-emoji').size).to eq 1 end it 'unicode matches at the end of a string' do doc = filter('This gets a 👍') - expect(doc.css('img').size).to eq 1 + expect(doc.css('gl-emoji').size).to eq 1 end it 'matches with adjacent text' do doc = filter('+1 (:+1:)') - expect(doc.css('img').size).to eq 1 + expect(doc.css('gl-emoji').size).to eq 1 end it 'unicode matches with adjacent text' do doc = filter('+1 (👍)') - expect(doc.css('img').size).to eq 1 + expect(doc.css('gl-emoji').size).to eq 1 end it 'matches multiple emoji in a row' do doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:') - expect(doc.css('img').size).to eq 3 + expect(doc.css('gl-emoji').size).to eq 3 end it 'unicode matches multiple emoji in a row' do doc = filter("'🙈🙉🙊'") - expect(doc.css('img').size).to eq 3 + expect(doc.css('gl-emoji').size).to eq 3 end it 'mixed matches multiple emoji in a row' do doc = filter("'🙈:see_no_evil:🙉:hear_no_evil:🙊:speak_no_evil:'") - expect(doc.css('img').size).to eq 6 + expect(doc.css('gl-emoji').size).to eq 6 end - it 'has a title attribute' do + it 'has a data-name attribute' do doc = filter(':-1:') - expect(doc.css('img').first.attr('title')).to eq ':-1:' + expect(doc.css('gl-emoji').first.attr('data-name')).to eq 'thumbsdown' end - it 'unicode has a title attribute' do - doc = filter("'👎'") - expect(doc.css('img').first.attr('title')).to eq ':thumbsdown:' - end - - it 'has an alt attribute' do + it 'has a data-unicode-version attribute' do doc = filter(':-1:') - expect(doc.css('img').first.attr('alt')).to eq ':-1:' - end - - it 'unicode has an alt attribute' do - doc = filter("'👎'") - expect(doc.css('img').first.attr('alt')).to eq ':thumbsdown:' - end - - it 'has an align attribute' do - doc = filter(':8ball:') - expect(doc.css('img').first.attr('align')).to eq 'absmiddle' - end - - it 'unicode has an align attribute' do - doc = filter("'🎱'") - expect(doc.css('img').first.attr('align')).to eq 'absmiddle' - end - - it 'has an emoji class' do - doc = filter(':cat:') - expect(doc.css('img').first.attr('class')).to eq 'emoji' - end - - it 'unicode has an emoji class' do - doc = filter("'🐱'") - expect(doc.css('img').first.attr('class')).to eq 'emoji' - end - - it 'has height and width attributes' do - doc = filter(':dog:') - img = doc.css('img').first - - expect(img.attr('width')).to eq '20' - expect(img.attr('height')).to eq '20' - end - - it 'unicode has height and width attributes' do - doc = filter("'🐶'") - img = doc.css('img').first - - expect(img.attr('width')).to eq '20' - expect(img.attr('height')).to eq '20' + expect(doc.css('gl-emoji').first.attr('data-unicode-version')).to eq '6.0' end it 'keeps whitespace intact' do doc = filter('This deserves a :+1:, big time.') - expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/) + expect(doc.to_html).to match(/^This deserves a <gl-emoji.+>, big time\.\z/) end it 'unicode keeps whitespace intact' do doc = filter('This deserves a 🎱, big time.') - expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/) - end - - it 'uses a custom asset_root context' do - root = Gitlab.config.gitlab.url + 'gitlab/root' - - doc = filter(':smile:', asset_root: root) - expect(doc.css('img').first.attr('src')).to start_with(root) - end - - it 'uses a custom asset_host context' do - ActionController::Base.asset_host = 'https://cdn.example.com' - - doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?') - expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com') - end - - it 'uses a custom asset_root context' do - root = Gitlab.config.gitlab.url + 'gitlab/root' - - doc = filter("'🎱'", asset_root: root) - expect(doc.css('img').first.attr('src')).to start_with(root) - end - - it 'uses a custom asset_host context' do - ActionController::Base.asset_host = 'https://cdn.example.com' - - doc = filter("'🎱'", asset_host: 'https://this-is-ignored-i-guess?') - expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com') + expect(doc.to_html).to match(/^This deserves a <gl-emoji.+>, big time\.\z/) end end diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb index b38e3b17e64..b4cd5f63a15 100644 --- a/spec/lib/banzai/filter/sanitization_filter_spec.rb +++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb @@ -86,6 +86,16 @@ describe Banzai::Filter::SanitizationFilter, lib: true do expect(filter(act).to_html).to eq exp end + it 'allows `summary` elements' do + exp = act = '<summary>summary line</summary>' + expect(filter(act).to_html).to eq exp + end + + it 'allows `details` elements' do + exp = act = '<details>long text goes here</details>' + expect(filter(act).to_html).to eq exp + end + it 'removes `rel` attribute from `a` elements' do act = %q{<a href="#" rel="nofollow">Link</a>} exp = %q{<a href="#">Link</a>} diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 7145f0da1d3..53abc056602 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -15,9 +15,9 @@ module Ci end describe '#build_attributes' do - describe 'coverage entry' do - subject { described_class.new(config, path).build_attributes(:rspec) } + subject { described_class.new(config, path).build_attributes(:rspec) } + describe 'coverage entry' do describe 'code coverage regexp' do let(:config) do YAML.dump(rspec: { script: 'rspec', @@ -30,6 +30,56 @@ module Ci end end end + + describe 'allow failure entry' do + context 'when job is a manual action' do + context 'when allow_failure is defined' do + let(:config) do + YAML.dump(rspec: { script: 'rspec', + when: 'manual', + allow_failure: false }) + end + + it 'is not allowed to fail' do + expect(subject[:allow_failure]).to be false + end + end + + context 'when allow_failure is not defined' do + let(:config) do + YAML.dump(rspec: { script: 'rspec', + when: 'manual' }) + end + + it 'is allowed to fail' do + expect(subject[:allow_failure]).to be true + end + end + end + + context 'when job is not a manual action' do + context 'when allow_failure is defined' do + let(:config) do + YAML.dump(rspec: { script: 'rspec', + allow_failure: false }) + end + + it 'is not allowed to fail' do + expect(subject[:allow_failure]).to be false + end + end + + context 'when allow_failure is not defined' do + let(:config) do + YAML.dump(rspec: { script: 'rspec' }) + end + + it 'is not allowed to fail' do + expect(subject[:allow_failure]).to be false + end + end + end + end end describe "#builds_for_ref" do diff --git a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb new file mode 100644 index 00000000000..94dcddcc30c --- /dev/null +++ b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +describe Gitlab::Auth::UniqueIpsLimiter, :redis, lib: true do + include_context 'unique ips sign in limit' + let(:user) { create(:user) } + + describe '#count_unique_ips' do + context 'non unique IPs' do + it 'properly counts them' do + expect(described_class.update_and_return_ips_count(user.id, 'ip1')).to eq(1) + expect(described_class.update_and_return_ips_count(user.id, 'ip1')).to eq(1) + end + end + + context 'unique IPs' do + it 'properly counts them' do + expect(described_class.update_and_return_ips_count(user.id, 'ip2')).to eq(1) + expect(described_class.update_and_return_ips_count(user.id, 'ip3')).to eq(2) + end + end + + it 'resets count after specified time window' do + Timecop.freeze do + expect(described_class.update_and_return_ips_count(user.id, 'ip2')).to eq(1) + expect(described_class.update_and_return_ips_count(user.id, 'ip3')).to eq(2) + + Timecop.travel(Time.now.utc + described_class.config.unique_ips_limit_time_window) do + expect(described_class.update_and_return_ips_count(user.id, 'ip4')).to eq(1) + expect(described_class.update_and_return_ips_count(user.id, 'ip5')).to eq(2) + end + end + end + end + + describe '#limit_user!' do + include_examples 'user login operation with unique ip limit' do + def operation + described_class.limit_user! { user } + end + end + + context 'allow 2 unique ips' do + before { current_application_settings.update!(unique_ips_limit_per_user: 2) } + + it 'blocks user trying to login from third ip' do + change_ip('ip1') + expect(described_class.limit_user! { user }).to eq(user) + + change_ip('ip2') + expect(described_class.limit_user! { user }).to eq(user) + + change_ip('ip3') + expect { described_class.limit_user! { user } }.to raise_error(Gitlab::Auth::TooManyIps) + end + end + end +end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index b234de4c772..03c4879ed6f 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -3,6 +3,24 @@ require 'spec_helper' describe Gitlab::Auth, lib: true do let(:gl_auth) { described_class } + describe 'constants' do + it 'API_SCOPES contains all scopes for API access' do + expect(subject::API_SCOPES).to eq [:api, :read_user] + end + + it 'OPENID_SCOPES contains all scopes for OpenID Connect' do + expect(subject::OPENID_SCOPES).to eq [:openid] + end + + it 'DEFAULT_SCOPES contains all default scopes' do + expect(subject::DEFAULT_SCOPES).to eq [:api] + end + + it 'OPTIONAL_SCOPES contains all non-default scopes' do + expect(subject::OPTIONAL_SCOPES).to eq [:read_user, :openid] + end + end + describe 'find_for_git_client' do context 'build token' do subject { gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: project, ip: 'ip') } @@ -58,6 +76,14 @@ describe Gitlab::Auth, lib: true do expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)) end + include_examples 'user login operation with unique ip limit' do + let(:user) { create(:user, password: 'password') } + + def operation + expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)) + end + end + context 'while using LFS authenticate' do it 'recognizes user lfs tokens' do user = create(:user) @@ -110,25 +136,37 @@ describe Gitlab::Auth, lib: true do end context 'while using personal access tokens as passwords' do - let(:user) { create(:user) } - let(:token_w_api_scope) { create(:personal_access_token, user: user, scopes: ['api']) } - it 'succeeds for personal access tokens with the `api` scope' do - expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.email) - expect(gl_auth.find_for_git_client(user.email, token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities)) + personal_access_token = create(:personal_access_token, scopes: ['api']) + + expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '') + expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, full_authentication_abilities)) + end + + it 'succeeds if it is an impersonation token' do + impersonation_token = create(:personal_access_token, :impersonation, scopes: ['api']) + + expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '') + expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(impersonation_token.user, nil, :personal_token, full_authentication_abilities)) end it 'fails for personal access tokens with other scopes' do - personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user']) + personal_access_token = create(:personal_access_token, scopes: ['read_user']) - expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: user.email) - expect(gl_auth.find_for_git_client(user.email, personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil)) + expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '') + expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil)) end - it 'does not try password auth before personal access tokens' do - expect(gl_auth).not_to receive(:find_with_user_password) + it 'fails for impersonation token with other scopes' do + impersonation_token = create(:personal_access_token, scopes: ['read_user']) + + expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '') + expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil)) + end - gl_auth.find_for_git_client(user.email, token_w_api_scope.token, project: nil, ip: 'ip') + it 'fails if password is nil' do + expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '') + expect(gl_auth.find_for_git_client('', nil, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil)) end end @@ -196,6 +234,24 @@ describe Gitlab::Auth, lib: true do expect( gl_auth.find_with_user_password(username, password) ).not_to eql user end + include_examples 'user login operation with unique ip limit' do + def operation + expect(gl_auth.find_with_user_password(username, password)).to eq(user) + end + end + + it "does not find user in blocked state" do + user.block + + expect( gl_auth.find_with_user_password(username, password) ).not_to eql user + end + + it "does not find user in ldap_blocked state" do + user.ldap_block + + expect( gl_auth.find_with_user_password(username, password) ).not_to eql user + end + context "with ldap enabled" do before do allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true) diff --git a/spec/lib/gitlab/award_emoji_spec.rb b/spec/lib/gitlab/award_emoji_spec.rb deleted file mode 100644 index 00a110e31f8..00000000000 --- a/spec/lib/gitlab/award_emoji_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'spec_helper' - -describe Gitlab::AwardEmoji do - describe '.urls' do - after do - Gitlab::AwardEmoji.instance_variable_set(:@urls, nil) - end - - subject { Gitlab::AwardEmoji.urls } - - it { is_expected.to be_an_instance_of(Array) } - it { is_expected.not_to be_empty } - - context 'every Hash in the Array' do - it 'has the correct keys and values' do - subject.each do |hash| - expect(hash[:name]).to be_an_instance_of(String) - expect(hash[:path]).to be_an_instance_of(String) - end - end - end - - context 'handles relative root' do - it 'includes the full path' do - allow(Gitlab::Application.config).to receive(:relative_url_root).and_return('/gitlab') - - subject.each do |hash| - expect(hash[:name]).to be_an_instance_of(String) - expect(hash[:path]).to start_with('/gitlab') - end - end - end - end - - describe '.emoji_by_category' do - it "only contains known categories" do - undefined_categories = Gitlab::AwardEmoji.emoji_by_category.keys - Gitlab::AwardEmoji::CATEGORIES.keys - expect(undefined_categories).to be_empty - end - end -end diff --git a/spec/lib/gitlab/ci/build/image_spec.rb b/spec/lib/gitlab/ci/build/image_spec.rb new file mode 100644 index 00000000000..382385dfd6b --- /dev/null +++ b/spec/lib/gitlab/ci/build/image_spec.rb @@ -0,0 +1,67 @@ +require 'spec_helper' + +describe Gitlab::Ci::Build::Image do + let(:job) { create(:ci_build, :no_options) } + + describe '#from_image' do + subject { described_class.from_image(job) } + + context 'when image is defined in job' do + let(:image_name) { 'ruby:2.1' } + let(:job) { create(:ci_build, options: { image: image_name } ) } + + it 'fabricates an object of the proper class' do + is_expected.to be_kind_of(described_class) + end + + it 'populates fabricated object with the proper name attribute' do + expect(subject.name).to eq(image_name) + end + + context 'when image name is empty' do + let(:image_name) { '' } + + it 'does not fabricate an object' do + is_expected.to be_nil + end + end + end + + context 'when image is not defined in job' do + it 'does not fabricate an object' do + is_expected.to be_nil + end + end + end + + describe '#from_services' do + subject { described_class.from_services(job) } + + context 'when services are defined in job' do + let(:service_image_name) { 'postgres' } + let(:job) { create(:ci_build, options: { services: [service_image_name] }) } + + it 'fabricates an non-empty array of objects' do + is_expected.to be_kind_of(Array) + is_expected.not_to be_empty + expect(subject.first.name).to eq(service_image_name) + end + + context 'when service image name is empty' do + let(:service_image_name) { '' } + + it 'fabricates an empty array' do + is_expected.to be_kind_of(Array) + is_expected.to be_empty + end + end + end + + context 'when services are not defined in job' do + it 'fabricates an empty array' do + is_expected.to be_kind_of(Array) + is_expected.to be_empty + end + end + end +end diff --git a/spec/lib/gitlab/ci/build/step_spec.rb b/spec/lib/gitlab/ci/build/step_spec.rb new file mode 100644 index 00000000000..2a314a744ca --- /dev/null +++ b/spec/lib/gitlab/ci/build/step_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe Gitlab::Ci::Build::Step do + let(:job) { create(:ci_build, :no_options, commands: "ls -la\ndate") } + + describe '#from_commands' do + subject { described_class.from_commands(job) } + + it 'fabricates an object' do + expect(subject.name).to eq(:script) + expect(subject.script).to eq(['ls -la', 'date']) + expect(subject.timeout).to eq(job.timeout) + expect(subject.when).to eq('on_success') + expect(subject.allow_failure).to be_falsey + end + end + + describe '#from_after_script' do + subject { described_class.from_after_script(job) } + + context 'when after_script is empty' do + it 'doesn not fabricate an object' do + is_expected.to be_nil + end + end + + context 'when after_script is not empty' do + let(:job) { create(:ci_build, options: { after_script: "ls -la\ndate" }) } + + it 'fabricates an object' do + expect(subject.name).to eq(:after_script) + expect(subject.script).to eq(['ls -la', 'date']) + expect(subject.timeout).to eq(job.timeout) + expect(subject.when).to eq('always') + expect(subject.allow_failure).to be_truthy + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb index 70a327c5183..2ed120f356a 100644 --- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb @@ -24,6 +24,20 @@ describe Gitlab::Ci::Config::Entry::Cache do expect(entry).to be_valid end end + + context 'when key is missing' do + let(:config) do + { untracked: true, + paths: ['some/path/'] } + end + + describe '#value' do + it 'sets key with the default' do + expect(entry.value[:key]) + .to eq(Gitlab::Ci::Config::Entry::Key.default) + end + end + end end context 'when entry value is not correct' do diff --git a/spec/lib/gitlab/ci/config/entry/factory_spec.rb b/spec/lib/gitlab/ci/config/entry/factory_spec.rb index 3395b3c645b..8dd48e4efae 100644 --- a/spec/lib/gitlab/ci/config/entry/factory_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/factory_spec.rb @@ -60,13 +60,13 @@ describe Gitlab::Ci::Config::Entry::Factory do end context 'when creating entry with nil value' do - it 'creates an undefined entry' do + it 'creates an unspecified entry' do entry = factory .value(nil) .create! expect(entry) - .to be_an_instance_of Gitlab::Ci::Config::Entry::Unspecified + .not_to be_specified end end diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb index ebd80ac5e1d..684d01e9056 100644 --- a/spec/lib/gitlab/ci/config/entry/global_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb @@ -155,6 +155,7 @@ describe Gitlab::Ci::Config::Entry::Global do stage: 'test', cache: { key: 'k', untracked: true, paths: ['public/'] }, variables: { VAR: 'value' }, + ignore: false, after_script: ['make clean'] }, spinach: { name: :spinach, before_script: [], @@ -165,6 +166,7 @@ describe Gitlab::Ci::Config::Entry::Global do stage: 'test', cache: { key: 'k', untracked: true, paths: ['public/'] }, variables: {}, + ignore: false, after_script: ['make clean'] }, ) end @@ -186,7 +188,7 @@ describe Gitlab::Ci::Config::Entry::Global do it 'contains unspecified nodes' do expect(global.descendants.first) - .to be_an_instance_of Gitlab::Ci::Config::Entry::Unspecified + .not_to be_specified end end diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index d20f4ec207d..9249bb9c172 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -144,6 +144,7 @@ describe Gitlab::Ci::Config::Entry::Job do script: %w[rspec], commands: "ls\npwd\nrspec", stage: 'test', + ignore: false, after_script: %w[cleanup]) end end @@ -159,4 +160,82 @@ describe Gitlab::Ci::Config::Entry::Job do end end end + + describe '#manual_action?' do + context 'when job is a manual action' do + let(:config) { { script: 'deploy', when: 'manual' } } + + it 'is a manual action' do + expect(entry).to be_manual_action + end + end + + context 'when job is not a manual action' do + let(:config) { { script: 'deploy' } } + + it 'is not a manual action' do + expect(entry).not_to be_manual_action + end + end + end + + describe '#ignored?' do + context 'when job is a manual action' do + context 'when it is not specified if job is allowed to fail' do + let(:config) do + { script: 'deploy', when: 'manual' } + end + + it 'is an ignored job' do + expect(entry).to be_ignored + end + end + + context 'when job is allowed to fail' do + let(:config) do + { script: 'deploy', when: 'manual', allow_failure: true } + end + + it 'is an ignored job' do + expect(entry).to be_ignored + end + end + + context 'when job is not allowed to fail' do + let(:config) do + { script: 'deploy', when: 'manual', allow_failure: false } + end + + it 'is not an ignored job' do + expect(entry).not_to be_ignored + end + end + end + + context 'when job is not a manual action' do + context 'when it is not specified if job is allowed to fail' do + let(:config) { { script: 'deploy' } } + + it 'is not an ignored job' do + expect(entry).not_to be_ignored + end + end + + context 'when job is allowed to fail' do + let(:config) { { script: 'deploy', allow_failure: true } } + + it 'is an ignored job' do + expect(entry).to be_ignored + end + end + + context 'when job is not allowed to fail' do + let(:config) { { script: 'deploy', allow_failure: false } } + + it 'is not an ignored job' do + expect(entry).not_to be_ignored + end + end + end + end end diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb index aaebf783962..7d104372ac6 100644 --- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb @@ -62,10 +62,12 @@ describe Gitlab::Ci::Config::Entry::Jobs do rspec: { name: :rspec, script: %w[rspec], commands: 'rspec', + ignore: false, stage: 'test' }, spinach: { name: :spinach, script: %w[spinach], commands: 'spinach', + ignore: false, stage: 'test' }) end end diff --git a/spec/lib/gitlab/ci/config/entry/key_spec.rb b/spec/lib/gitlab/ci/config/entry/key_spec.rb index 0dd36fe1f44..5d4de60bc8a 100644 --- a/spec/lib/gitlab/ci/config/entry/key_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/key_spec.rb @@ -31,4 +31,10 @@ describe Gitlab::Ci::Config::Entry::Key do end end end + + describe '.default' do + it 'returns default key' do + expect(described_class.default).to eq 'default' + end + end end diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index 0c40fca0c1a..8b3bd08cf13 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -192,7 +192,7 @@ describe Gitlab::Ci::Status::Build::Factory do let(:build) { create(:ci_build, :playable) } it 'matches correct core status' do - expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped + expect(factory.core_status).to be_a Gitlab::Ci::Status::Manual end it 'matches correct extended statuses' do @@ -200,12 +200,13 @@ describe Gitlab::Ci::Status::Build::Factory do .to eq [Gitlab::Ci::Status::Build::Play] end - it 'fabricates a core skipped status' do + it 'fabricates a play detailed status' do expect(status).to be_a Gitlab::Ci::Status::Build::Play end it 'fabricates status with correct details' do expect(status.text).to eq 'manual' + expect(status.group).to eq 'manual' expect(status.icon).to eq 'icon_status_manual' expect(status.label).to eq 'manual play action' expect(status).to have_details @@ -218,7 +219,7 @@ describe Gitlab::Ci::Status::Build::Factory do let(:build) { create(:ci_build, :playable, :teardown_environment) } it 'matches correct core status' do - expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped + expect(factory.core_status).to be_a Gitlab::Ci::Status::Manual end it 'matches correct extended statuses' do @@ -226,12 +227,13 @@ describe Gitlab::Ci::Status::Build::Factory do .to eq [Gitlab::Ci::Status::Build::Stop] end - it 'fabricates a core skipped status' do + it 'fabricates a stop detailed status' do expect(status).to be_a Gitlab::Ci::Status::Build::Stop end it 'fabricates status with correct details' do expect(status.text).to eq 'manual' + expect(status.group).to eq 'manual' expect(status.icon).to eq 'icon_status_manual' expect(status.label).to eq 'manual stop action' expect(status).to have_details diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb index f3e72ea1796..6c97a4fe5ca 100644 --- a/spec/lib/gitlab/ci/status/build/play_spec.rb +++ b/spec/lib/gitlab/ci/status/build/play_spec.rb @@ -6,22 +6,10 @@ describe Gitlab::Ci::Status::Build::Play do subject { described_class.new(status) } - describe '#text' do - it { expect(subject.text).to eq 'manual' } - end - describe '#label' do it { expect(subject.label).to eq 'manual play action' } end - describe '#icon' do - it { expect(subject.icon).to eq 'icon_status_manual' } - end - - describe '#group' do - it { expect(subject.group).to eq 'manual' } - end - describe 'action details' do let(:user) { create(:user) } let(:build) { create(:ci_build) } diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb index 41c2b624774..8d021c35a69 100644 --- a/spec/lib/gitlab/ci/status/build/stop_spec.rb +++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb @@ -8,22 +8,10 @@ describe Gitlab::Ci::Status::Build::Stop do described_class.new(status) end - describe '#text' do - it { expect(subject.text).to eq 'manual' } - end - describe '#label' do it { expect(subject.label).to eq 'manual stop action' } end - describe '#icon' do - it { expect(subject.icon).to eq 'icon_status_manual' } - end - - describe '#group' do - it { expect(subject.group).to eq 'manual' } - end - describe 'action details' do let(:user) { create(:user) } let(:build) { create(:ci_build) } diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb index 38412fe2e4f..768f8926f1d 100644 --- a/spec/lib/gitlab/ci/status/canceled_spec.rb +++ b/spec/lib/gitlab/ci/status/canceled_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Canceled do end describe '#text' do - it { expect(subject.label).to eq 'canceled' } + it { expect(subject.text).to eq 'canceled' } end describe '#label' do diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb index 6d847484693..e96c13aede3 100644 --- a/spec/lib/gitlab/ci/status/created_spec.rb +++ b/spec/lib/gitlab/ci/status/created_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Created do end describe '#text' do - it { expect(subject.label).to eq 'created' } + it { expect(subject.text).to eq 'created' } end describe '#label' do diff --git a/spec/lib/gitlab/ci/status/failed_spec.rb b/spec/lib/gitlab/ci/status/failed_spec.rb index 990d686d22c..e5da0a91159 100644 --- a/spec/lib/gitlab/ci/status/failed_spec.rb +++ b/spec/lib/gitlab/ci/status/failed_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Failed do end describe '#text' do - it { expect(subject.label).to eq 'failed' } + it { expect(subject.text).to eq 'failed' } end describe '#label' do diff --git a/spec/lib/gitlab/ci/status/manual_spec.rb b/spec/lib/gitlab/ci/status/manual_spec.rb new file mode 100644 index 00000000000..3fd3727b92d --- /dev/null +++ b/spec/lib/gitlab/ci/status/manual_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Manual do + subject do + described_class.new(double('subject'), double('user')) + end + + describe '#text' do + it { expect(subject.text).to eq 'manual' } + end + + describe '#label' do + it { expect(subject.label).to eq 'manual action' } + end + + describe '#icon' do + it { expect(subject.icon).to eq 'icon_status_manual' } + end + + describe '#group' do + it { expect(subject.group).to eq 'manual' } + end +end diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb index 7bb6579c317..8d09cf2a05a 100644 --- a/spec/lib/gitlab/ci/status/pending_spec.rb +++ b/spec/lib/gitlab/ci/status/pending_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Pending do end describe '#text' do - it { expect(subject.label).to eq 'pending' } + it { expect(subject.text).to eq 'pending' } end describe '#label' do diff --git a/spec/lib/gitlab/ci/status/running_spec.rb b/spec/lib/gitlab/ci/status/running_spec.rb index 852d6c06baf..10d3bf749c1 100644 --- a/spec/lib/gitlab/ci/status/running_spec.rb +++ b/spec/lib/gitlab/ci/status/running_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Running do end describe '#text' do - it { expect(subject.label).to eq 'running' } + it { expect(subject.text).to eq 'running' } end describe '#label' do diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb index e00b356a24b..10db93d3802 100644 --- a/spec/lib/gitlab/ci/status/skipped_spec.rb +++ b/spec/lib/gitlab/ci/status/skipped_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Skipped do end describe '#text' do - it { expect(subject.label).to eq 'skipped' } + it { expect(subject.text).to eq 'skipped' } end describe '#label' do diff --git a/spec/lib/gitlab/ci/status/success_spec.rb b/spec/lib/gitlab/ci/status/success_spec.rb index 4a89e1faf40..230f24b94a4 100644 --- a/spec/lib/gitlab/ci/status/success_spec.rb +++ b/spec/lib/gitlab/ci/status/success_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Success do end describe '#text' do - it { expect(subject.label).to eq 'passed' } + it { expect(subject.text).to eq 'passed' } end describe '#label' do diff --git a/spec/lib/gitlab/etag_caching/middleware_spec.rb b/spec/lib/gitlab/etag_caching/middleware_spec.rb new file mode 100644 index 00000000000..8b5bfc4dbb0 --- /dev/null +++ b/spec/lib/gitlab/etag_caching/middleware_spec.rb @@ -0,0 +1,163 @@ +require 'spec_helper' + +describe Gitlab::EtagCaching::Middleware do + let(:app) { double(:app) } + let(:middleware) { described_class.new(app) } + let(:app_status_code) { 200 } + let(:if_none_match) { nil } + let(:enabled_path) { '/gitlab-org/gitlab-ce/noteable/issue/1/notes' } + + context 'when ETag caching is not enabled for current route' do + let(:path) { '/gitlab-org/gitlab-ce/tree/master/noteable/issue/1/notes' } + + before do + mock_app_response + end + + it 'does not add ETag header' do + _, headers, _ = middleware.call(build_env(path, if_none_match)) + + expect(headers['ETag']).to be_nil + end + + it 'passes status code from app' do + status, _, _ = middleware.call(build_env(path, if_none_match)) + + expect(status).to eq app_status_code + end + end + + context 'when there is no ETag in store for given resource' do + let(:path) { enabled_path } + + before do + mock_app_response + mock_value_in_store(nil) + end + + it 'generates ETag' do + expect_any_instance_of(Gitlab::EtagCaching::Store) + .to receive(:touch).and_return('123') + + middleware.call(build_env(path, if_none_match)) + end + + context 'when If-None-Match header was specified' do + let(:if_none_match) { 'W/"abc"' } + + it 'tracks "etag_caching_key_not_found" event' do + expect(Gitlab::Metrics).to receive(:add_event) + .with(:etag_caching_middleware_used) + expect(Gitlab::Metrics).to receive(:add_event) + .with(:etag_caching_key_not_found) + + middleware.call(build_env(path, if_none_match)) + end + end + end + + context 'when there is ETag in store for given resource' do + let(:path) { enabled_path } + + before do + mock_app_response + mock_value_in_store('123') + end + + it 'returns this value as header' do + _, headers, _ = middleware.call(build_env(path, if_none_match)) + + expect(headers['ETag']).to eq 'W/"123"' + end + end + + context 'when If-None-Match header matches ETag in store' do + let(:path) { enabled_path } + let(:if_none_match) { 'W/"123"' } + + before do + mock_value_in_store('123') + end + + it 'does not call app' do + expect(app).not_to receive(:call) + + middleware.call(build_env(path, if_none_match)) + end + + it 'returns status code 304' do + status, _, _ = middleware.call(build_env(path, if_none_match)) + + expect(status).to eq 304 + end + + it 'tracks "etag_caching_cache_hit" event' do + expect(Gitlab::Metrics).to receive(:add_event) + .with(:etag_caching_middleware_used) + expect(Gitlab::Metrics).to receive(:add_event) + .with(:etag_caching_cache_hit) + + middleware.call(build_env(path, if_none_match)) + end + end + + context 'when If-None-Match header does not match ETag in store' do + let(:path) { enabled_path } + let(:if_none_match) { 'W/"abc"' } + + before do + mock_value_in_store('123') + end + + it 'calls app' do + expect(app).to receive(:call).and_return([app_status_code, {}, ['body']]) + + middleware.call(build_env(path, if_none_match)) + end + + it 'tracks "etag_caching_resource_changed" event' do + mock_app_response + + expect(Gitlab::Metrics).to receive(:add_event) + .with(:etag_caching_middleware_used) + expect(Gitlab::Metrics).to receive(:add_event) + .with(:etag_caching_resource_changed) + + middleware.call(build_env(path, if_none_match)) + end + end + + context 'when If-None-Match header is not specified' do + let(:path) { enabled_path } + + before do + mock_value_in_store('123') + mock_app_response + end + + it 'tracks "etag_caching_header_missing" event' do + expect(Gitlab::Metrics).to receive(:add_event) + .with(:etag_caching_middleware_used) + expect(Gitlab::Metrics).to receive(:add_event) + .with(:etag_caching_header_missing) + + middleware.call(build_env(path, if_none_match)) + end + end + + def mock_app_response + allow(app).to receive(:call).and_return([app_status_code, {}, ['body']]) + end + + def mock_value_in_store(value) + allow_any_instance_of(Gitlab::EtagCaching::Store) + .to receive(:get).and_return(value) + end + + def build_env(path, if_none_match) + { + 'PATH_INFO' => path, + 'HTTP_IF_NONE_MATCH' => if_none_match + } + end +end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 3f11f0a4516..bc139d5ef28 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -824,6 +824,32 @@ describe Gitlab::Git::Repository, seed_helper: true do it { is_expected.to eq(17) } end + describe '#count_commits' do + context 'with after timestamp' do + it 'returns the number of commits after timestamp' do + options = { ref: 'master', limit: nil, after: Time.iso8601('2013-03-03T20:15:01+00:00') } + + expect(repository.count_commits(options)).to eq(25) + end + end + + context 'with before timestamp' do + it 'returns the number of commits after timestamp' do + options = { ref: 'feature', limit: nil, before: Time.iso8601('2015-03-03T20:15:01+00:00') } + + expect(repository.count_commits(options)).to eq(9) + end + end + + context 'with path' do + it 'returns the number of commits with path ' do + options = { ref: 'master', limit: nil, path: "encoding" } + + expect(repository.count_commits(options)).to eq(2) + end + end + end + describe "branch_names_contains" do subject { repository.branch_names_contains(SeedRepo::LastCommit::ID) } diff --git a/spec/lib/gitlab/github_import/branch_formatter_spec.rb b/spec/lib/gitlab/github_import/branch_formatter_spec.rb index 36e7d739f7e..3a31f93efa5 100644 --- a/spec/lib/gitlab/github_import/branch_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/branch_formatter_spec.rb @@ -6,27 +6,27 @@ describe Gitlab::GithubImport::BranchFormatter, lib: true do let(:repo) { double } let(:raw) do { - ref: 'feature', + ref: 'branch-merged', repo: repo, sha: commit.id } end describe '#exists?' do - it 'returns true when both branch, and commit exists' do + it 'returns true when branch exists and commit is part of the branch' do branch = described_class.new(project, double(raw)) expect(branch.exists?).to eq true end - it 'returns false when branch does not exist' do - branch = described_class.new(project, double(raw.merge(ref: 'removed-branch'))) + it 'returns false when branch exists and commit is not part of the branch' do + branch = described_class.new(project, double(raw.merge(ref: 'feature'))) expect(branch.exists?).to eq false end - it 'returns false when commit does not exist' do - branch = described_class.new(project, double(raw.merge(sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'))) + it 'returns false when branch does not exist' do + branch = described_class.new(project, double(raw.merge(ref: 'removed-branch'))) expect(branch.exists?).to eq false end diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb index 33d83d6d2f1..3f080de99dd 100644 --- a/spec/lib/gitlab/github_import/importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer_spec.rb @@ -130,7 +130,7 @@ describe Gitlab::GithubImport::Importer, lib: true do let!(:user) { create(:user, email: octocat.email) } let(:repository) { double(id: 1, fork: false) } let(:source_sha) { create(:commit, project: project).id } - let(:source_branch) { double(ref: 'feature', repo: repository, sha: source_sha) } + let(:source_branch) { double(ref: 'branch-merged', repo: repository, sha: source_sha) } let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id } let(:target_branch) { double(ref: 'master', repo: repository, sha: target_sha) } let(:pull_request) do diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb index e46be18aa99..eeef23a61c6 100644 --- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb @@ -7,7 +7,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id } let(:repository) { double(id: 1, fork: false) } let(:source_repo) { repository } - let(:source_branch) { double(ref: 'feature', repo: source_repo, sha: source_sha) } + let(:source_branch) { double(ref: 'branch-merged', repo: source_repo, sha: source_sha) } let(:target_repo) { repository } let(:target_branch) { double(ref: 'master', repo: target_repo, sha: target_sha) } let(:removed_branch) { double(ref: 'removed-branch', repo: source_repo, sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b') } @@ -49,7 +49,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do title: 'New feature', description: "*Created by: octocat*\n\nPlease pull these awesome changes", source_project: project, - source_branch: 'feature', + source_branch: 'branch-merged', source_branch_sha: source_sha, target_project: project, target_branch: 'master', @@ -75,7 +75,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do title: 'New feature', description: "*Created by: octocat*\n\nPlease pull these awesome changes", source_project: project, - source_branch: 'feature', + source_branch: 'branch-merged', source_branch_sha: source_sha, target_project: project, target_branch: 'master', @@ -102,7 +102,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do title: 'New feature', description: "*Created by: octocat*\n\nPlease pull these awesome changes", source_project: project, - source_branch: 'feature', + source_branch: 'branch-merged', source_branch_sha: source_sha, target_project: project, target_branch: 'master', @@ -194,7 +194,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do let(:raw_data) { double(base_data) } it 'returns branch ref' do - expect(pull_request.source_branch_name).to eq 'feature' + expect(pull_request.source_branch_name).to eq 'branch-merged' end end @@ -208,7 +208,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do end shared_examples 'Gitlab::GithubImport::PullRequestFormatter#target_branch_name' do - context 'when source branch exists' do + context 'when target branch exists' do let(:raw_data) { double(base_data) } it 'returns branch ref' do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index eef283c2460..e47956a365f 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -97,6 +97,7 @@ variables: triggers: - project - trigger_requests +- owner deploy_keys: - user - deploy_keys_projects @@ -135,6 +136,7 @@ project: - slack_slash_commands_service - irker_service - pivotaltracker_service +- prometheus_service - hipchat_service - flowdock_service - assembla_service @@ -198,6 +200,7 @@ project: - project_authorizations - route - statistics +- uploads award_emoji: - awardable - user diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 6534902b52d..c718e792461 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -21,6 +21,7 @@ Issue: - milestone_id - weight - time_estimate +- relative_position Event: - id - target_type @@ -240,6 +241,8 @@ Ci::Trigger: - created_at - updated_at - gl_project_id +- owner_id +- description DeployKey: - id - user_id diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb index fd3769d75b5..c2ab015d5cb 100644 --- a/spec/lib/gitlab/middleware/go_spec.rb +++ b/spec/lib/gitlab/middleware/go_spec.rb @@ -15,16 +15,93 @@ describe Gitlab::Middleware::Go, lib: true do end describe 'when go-get=1' do - it 'returns a document' do - env = { 'rack.input' => '', - 'QUERY_STRING' => 'go-get=1', - 'PATH_INFO' => '/group/project/path' } - resp = middleware.call(env) - expect(resp[0]).to eq(200) - expect(resp[1]['Content-Type']).to eq('text/html') - expected_body = "<!DOCTYPE html><html><head><meta content='#{Gitlab.config.gitlab.host}/group/project git http://#{Gitlab.config.gitlab.host}/group/project.git' name='go-import'></head></html>\n" - expect(resp[2].body).to eq([expected_body]) + let(:current_user) { nil } + + context 'with simple 2-segment project path' do + let!(:project) { create(:project, :private) } + + context 'with subpackages' do + let(:path) { "#{project.full_path}/subpackage" } + + it 'returns the full project path' do + expect_response_with_path(go, project.full_path) + end + end + + context 'without subpackages' do + let(:path) { project.full_path } + + it 'returns the full project path' do + expect_response_with_path(go, project.full_path) + end + end + end + + context 'with a nested project path' do + let(:group) { create(:group, :nested) } + let!(:project) { create(:project, :public, namespace: group) } + + shared_examples 'a nested project' do + context 'when the project is public' do + it 'returns the full project path' do + expect_response_with_path(go, project.full_path) + end + end + + context 'when the project is private' do + before do + project.update_attribute(:visibility_level, Project::PRIVATE) + end + + context 'with access to the project' do + let(:current_user) { project.creator } + + before do + project.team.add_master(current_user) + end + + it 'returns the full project path' do + expect_response_with_path(go, project.full_path) + end + end + + context 'without access to the project' do + it 'returns the 2-segment group path' do + expect_response_with_path(go, group.full_path) + end + end + end + end + + context 'with subpackages' do + let(:path) { "#{project.full_path}/subpackage" } + + it_behaves_like 'a nested project' + end + + context 'without subpackages' do + let(:path) { project.full_path } + + it_behaves_like 'a nested project' + end end end + + def go + env = { + 'rack.input' => '', + 'QUERY_STRING' => 'go-get=1', + 'PATH_INFO' => "/#{path}", + 'warden' => double(authenticate: current_user) + } + middleware.call(env) + end + + def expect_response_with_path(response, path) + expect(response[0]).to eq(200) + expect(response[1]['Content-Type']).to eq('text/html') + expected_body = "<!DOCTYPE html><html><head><meta content='#{Gitlab.config.gitlab.host}/#{path} git http://#{Gitlab.config.gitlab.host}/#{path}.git' name='go-import'></head></html>\n" + expect(response[2].body).to eq([expected_body]) + end end end diff --git a/spec/lib/gitlab/prometheus_spec.rb b/spec/lib/gitlab/prometheus_spec.rb new file mode 100644 index 00000000000..280264188e2 --- /dev/null +++ b/spec/lib/gitlab/prometheus_spec.rb @@ -0,0 +1,143 @@ +require 'spec_helper' + +describe Gitlab::Prometheus, lib: true do + include PrometheusHelpers + + subject { described_class.new(api_url: 'https://prometheus.example.com') } + + describe '#ping' do + it 'issues a "query" request to the API endpoint' do + req_stub = stub_prometheus_request(prometheus_query_url('1'), body: prometheus_value_body('vector')) + + expect(subject.ping).to eq({ "resultType" => "vector", "result" => [{ "metric" => {}, "value" => [1488772511.004, "0.000041021495238095323"] }] }) + expect(req_stub).to have_been_requested + end + end + + # This shared examples expect: + # - query_url: A query URL + # - execute_query: A query call + shared_examples 'failure response' do + context 'when request returns 400 with an error message' do + it 'raises a Gitlab::PrometheusError error' do + req_stub = stub_prometheus_request(query_url, status: 400, body: { error: 'bar!' }) + + expect { execute_query } + .to raise_error(Gitlab::PrometheusError, 'bar!') + expect(req_stub).to have_been_requested + end + end + + context 'when request returns 400 without an error message' do + it 'raises a Gitlab::PrometheusError error' do + req_stub = stub_prometheus_request(query_url, status: 400) + + expect { execute_query } + .to raise_error(Gitlab::PrometheusError, 'Bad data received') + expect(req_stub).to have_been_requested + end + end + + context 'when request returns 500' do + it 'raises a Gitlab::PrometheusError error' do + req_stub = stub_prometheus_request(query_url, status: 500, body: { message: 'FAIL!' }) + + expect { execute_query } + .to raise_error(Gitlab::PrometheusError, '500 - {"message":"FAIL!"}') + expect(req_stub).to have_been_requested + end + end + end + + describe '#query' do + let(:prometheus_query) { prometheus_cpu_query('env-slug') } + let(:query_url) { prometheus_query_url(prometheus_query) } + + context 'when request returns vector results' do + it 'returns data from the API call' do + req_stub = stub_prometheus_request(query_url, body: prometheus_value_body('vector')) + + expect(subject.query(prometheus_query)).to eq [{ "metric" => {}, "value" => [1488772511.004, "0.000041021495238095323"] }] + expect(req_stub).to have_been_requested + end + end + + context 'when request returns matrix results' do + it 'returns nil' do + req_stub = stub_prometheus_request(query_url, body: prometheus_value_body('matrix')) + + expect(subject.query(prometheus_query)).to be_nil + expect(req_stub).to have_been_requested + end + end + + context 'when request returns no data' do + it 'returns []' do + req_stub = stub_prometheus_request(query_url, body: prometheus_empty_body('vector')) + + expect(subject.query(prometheus_query)).to be_empty + expect(req_stub).to have_been_requested + end + end + + it_behaves_like 'failure response' do + let(:execute_query) { subject.query(prometheus_query) } + end + end + + describe '#query_range' do + let(:prometheus_query) { prometheus_memory_query('env-slug') } + let(:query_url) { prometheus_query_range_url(prometheus_query) } + + around do |example| + Timecop.freeze { example.run } + end + + context 'when a start time is passed' do + let(:query_url) { prometheus_query_range_url(prometheus_query, start: 2.hours.ago) } + + it 'passed it in the requested URL' do + req_stub = stub_prometheus_request(query_url, body: prometheus_values_body('vector')) + + subject.query_range(prometheus_query, start: 2.hours.ago) + expect(req_stub).to have_been_requested + end + end + + context 'when request returns vector results' do + it 'returns nil' do + req_stub = stub_prometheus_request(query_url, body: prometheus_values_body('vector')) + + expect(subject.query_range(prometheus_query)).to be_nil + expect(req_stub).to have_been_requested + end + end + + context 'when request returns matrix results' do + it 'returns data from the API call' do + req_stub = stub_prometheus_request(query_url, body: prometheus_values_body('matrix')) + + expect(subject.query_range(prometheus_query)).to eq([ + { + "metric" => {}, + "values" => [[1488758662.506, "0.00002996364761904785"], [1488758722.506, "0.00003090239047619091"]] + } + ]) + expect(req_stub).to have_been_requested + end + end + + context 'when request returns no data' do + it 'returns []' do + req_stub = stub_prometheus_request(query_url, body: prometheus_empty_body('matrix')) + + expect(subject.query_range(prometheus_query)).to be_empty + expect(req_stub).to have_been_requested + end + end + + it_behaves_like 'failure response' do + let(:execute_query) { subject.query_range(prometheus_query) } + end + end +end diff --git a/spec/lib/gitlab/request_context_spec.rb b/spec/lib/gitlab/request_context_spec.rb new file mode 100644 index 00000000000..a91c8655cdd --- /dev/null +++ b/spec/lib/gitlab/request_context_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe Gitlab::RequestContext, lib: true do + describe '#client_ip' do + subject { Gitlab::RequestContext.client_ip } + let(:app) { -> (env) {} } + let(:env) { Hash.new } + + context 'when RequestStore::Middleware is used' do + around(:each) do |example| + RequestStore::Middleware.new(-> (env) { example.run }).call({}) + end + + context 'request' do + let(:ip) { '192.168.1.11' } + + before do + allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip) + Gitlab::RequestContext.new(app).call(env) + end + + it { is_expected.to eq(ip) } + end + + context 'before RequestContext middleware run' do + it { is_expected.to be_nil } + end + end + end +end diff --git a/spec/lib/gitlab/sidekiq_status_spec.rb b/spec/lib/gitlab/sidekiq_status_spec.rb index 0aa36a3416b..56f06b61afb 100644 --- a/spec/lib/gitlab/sidekiq_status_spec.rb +++ b/spec/lib/gitlab/sidekiq_status_spec.rb @@ -39,6 +39,32 @@ describe Gitlab::SidekiqStatus do end end + describe '.num_running', :redis do + it 'returns 0 if all jobs have been completed' do + expect(described_class.num_running(%w(123))).to eq(0) + end + + it 'returns 2 if two jobs are still running' do + described_class.set('123') + described_class.set('456') + + expect(described_class.num_running(%w(123 456 789))).to eq(2) + end + end + + describe '.num_completed', :redis do + it 'returns 1 if all jobs have been completed' do + expect(described_class.num_completed(%w(123))).to eq(1) + end + + it 'returns 1 if a job has not yet been completed' do + described_class.set('123') + described_class.set('456') + + expect(described_class.num_completed(%w(123 456 789))).to eq(1) + end + end + describe '.key_for' do it 'returns the key for a job ID' do key = described_class.key_for('123') diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index a32c6131030..8e5e8288c49 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -199,4 +199,58 @@ describe Gitlab::Workhorse, lib: true do end end end + + describe '.set_key_and_notify' do + let(:key) { 'test-key' } + let(:value) { 'test-value' } + + subject { described_class.set_key_and_notify(key, value, overwrite: overwrite) } + + shared_examples 'set and notify' do + it 'set and return the same value' do + is_expected.to eq(value) + end + + it 'set and notify' do + expect_any_instance_of(Redis).to receive(:publish) + .with(described_class::NOTIFICATION_CHANNEL, "test-key=test-value") + + subject + end + end + + context 'when we set a new key' do + let(:overwrite) { true } + + it_behaves_like 'set and notify' + end + + context 'when we set an existing key' do + let(:old_value) { 'existing-key' } + + before do + described_class.set_key_and_notify(key, old_value, overwrite: true) + end + + context 'and overwrite' do + let(:overwrite) { true } + + it_behaves_like 'set and notify' + end + + context 'and do not overwrite' do + let(:overwrite) { false } + + it 'try to set but return the previous value' do + is_expected.to eq(old_value) + end + + it 'does not notify' do + expect_any_instance_of(Redis).not_to receive(:publish) + + subject + end + end + end + end end diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb index 6a9c7cebfff..ac493fdb20f 100644 --- a/spec/lib/mattermost/team_spec.rb +++ b/spec/lib/mattermost/team_spec.rb @@ -13,7 +13,7 @@ describe Mattermost::Team do context 'for valid request' do let(:response) do - [{ + { "xiyro8huptfhdndadpz8r3wnbo" => { "id" => "xiyro8huptfhdndadpz8r3wnbo", "create_at" => 1482174222155, "update_at" => 1482174222155, @@ -26,7 +26,7 @@ describe Mattermost::Team do "allowed_domains" => "", "invite_id" => "o4utakb9jtb7imctdfzbf9r5ro", "allow_open_invite" => false - }] + } } end before do @@ -39,7 +39,7 @@ describe Mattermost::Team do end it 'returns a token' do - is_expected.to eq(response) + is_expected.to eq(response.values) end end |