diff options
Diffstat (limited to 'spec')
78 files changed, 2934 insertions, 429 deletions
diff --git a/spec/benchmarks/models/user_spec.rb b/spec/benchmarks/models/user_spec.rb index cc5c3904193..1be7a8d3ed9 100644 --- a/spec/benchmarks/models/user_spec.rb +++ b/spec/benchmarks/models/user_spec.rb @@ -1,6 +1,16 @@ require 'spec_helper' describe User, benchmark: true do + describe '.all' do + before do + 10.times { create(:user) } + end + + benchmark_subject { User.all.to_a } + + it { is_expected.to iterate_per_second(500) } + end + describe '.by_login' do before do %w{Alice Bob Eve}.each do |name| @@ -39,4 +49,30 @@ describe User, benchmark: true do it { is_expected.to iterate_per_second(iterations) } end end + + describe '.find_by_any_email' do + let(:user) { create(:user) } + + describe 'using a user with only a single Email address' do + let(:email) { user.email } + + benchmark_subject { User.find_by_any_email(email) } + + it { is_expected.to iterate_per_second(1000) } + end + + describe 'using a user with multiple Email addresses' do + let(:email) { user.emails.first.email } + + benchmark_subject { User.find_by_any_email(email) } + + before do + 10.times do + user.emails.create(email: FFaker::Internet.email) + end + end + + it { is_expected.to iterate_per_second(1000) } + end + end end diff --git a/spec/benchmarks/services/projects/create_service_spec.rb b/spec/benchmarks/services/projects/create_service_spec.rb new file mode 100644 index 00000000000..25ed48c34fd --- /dev/null +++ b/spec/benchmarks/services/projects/create_service_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Projects::CreateService, benchmark: true do + describe '#execute' do + let(:user) { create(:user, :admin) } + + let(:group) do + group = create(:group) + + create(:group_member, group: group, user: user) + + group + end + + benchmark_subject do + name = SecureRandom.hex + service = described_class.new(user, + name: name, + path: name, + namespace_id: group.id, + visibility_level: Gitlab::VisibilityLevel::PUBLIC) + + service.execute + end + + it { is_expected.to iterate_per_second(0.5) } + end +end diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index fcbe62cace8..8b7af4d3a0a 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -7,21 +7,6 @@ describe Admin::UsersController do sign_in(admin) end - describe 'POST login_as' do - let(:user) { create(:user) } - - it 'logs admin as another user' do - expect(warden.authenticate(scope: :user)).not_to eq(user) - post :login_as, id: user.username - expect(warden.authenticate(scope: :user)).to eq(user) - end - - it 'redirects user to homepage' do - post :login_as, id: user.username - expect(response).to redirect_to(root_path) - end - end - describe 'DELETE #user with projects' do let(:user) { create(:user) } let(:project) { create(:project, namespace: user.namespace) } diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index 2a447248b70..be19f1abc53 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -23,6 +23,22 @@ describe Projects::CompareController do expect(assigns(:commits).length).to be >= 1 end + it 'compare should show some diffs with ignore whitespace change option' do + get(:show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + from: '08f22f25', + to: '66eceea0', + w: 1) + + expect(response).to be_success + expect(assigns(:diffs).length).to be >= 1 + expect(assigns(:commits).length).to be >= 1 + # without whitespace option, there are more than 2 diff_splits + diff_splits = assigns(:diffs)[0].diff.split("\n") + expect(diff_splits.length).to be <= 2 + end + describe 'non-existent refs' do it 'invalid source ref' do get(:show, diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index b8db8591709..3e5e1fa87ae 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -147,6 +147,34 @@ describe Projects::MergeRequestsController do end end + describe 'GET diffs with ignore_whitespace_change' do + def go(format: 'html') + get :diffs, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: merge_request.iid, + format: format, + w: 1 + end + + context 'as html' do + it 'renders the diff template' do + go + + expect(response).to render_template('diffs') + end + end + + context 'as json' do + it 'renders the diffs template to a string' do + go format: 'json' + + expect(response).to render_template('projects/merge_requests/show/_diffs') + expect(JSON.parse(response.body)).to have_key('html') + end + end + end + describe 'GET commits' do def go(format: 'html') get :commits, diff --git a/spec/factories/ci/projects.rb b/spec/factories/ci/projects.rb index 111e1a82816..11cb8c9eeaa 100644 --- a/spec/factories/ci/projects.rb +++ b/spec/factories/ci/projects.rb @@ -31,14 +31,20 @@ FactoryGirl.define do factory :ci_project_without_token, class: Ci::Project do default_ref 'master' - gl_project factory: :empty_project + shared_runners_enabled false factory :ci_project do token 'iPWx6WM4lhHNedGfBpPJNP' end - factory :ci_public_project do - public true + initialize_with do + # TODO: + # this is required, because builds_enabled is initialized when Project is created + # and this create gitlab_ci_project if builds is set to true + # here we take created gitlab_ci_project and update it's attributes + ci_project = create(:empty_project).ensure_gitlab_ci_project + ci_project.update_attributes(attributes) + ci_project end end end diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb index 6829387c660..8b12ee11af5 100644 --- a/spec/factories/labels.rb +++ b/spec/factories/labels.rb @@ -8,6 +8,7 @@ # project_id :integer # created_at :datetime # updated_at :datetime +# template :boolean default(FALSE) # # Read about factories at https://github.com/thoughtbot/factory_girl diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb new file mode 100644 index 00000000000..7fb2d77ca32 --- /dev/null +++ b/spec/factories/lfs_objects.rb @@ -0,0 +1,12 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :lfs_object do + oid "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" + size 499013 + end + + trait :with_file do + file { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") } + end +end diff --git a/spec/factories/lfs_objects_projects.rb b/spec/factories/lfs_objects_projects.rb new file mode 100644 index 00000000000..93de6607df8 --- /dev/null +++ b/spec/factories/lfs_objects_projects.rb @@ -0,0 +1,8 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :lfs_objects_project do + lfs_object + project + end +end diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 6080d0ccdef..729a49c9f72 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -20,6 +20,7 @@ # position :integer default(0) # locked_at :datetime # updated_by_id :integer +# merge_error :string(255) # FactoryGirl.define do diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb new file mode 100644 index 00000000000..43d09b17534 --- /dev/null +++ b/spec/factories/releases.rb @@ -0,0 +1,21 @@ +# == Schema Information +# +# Table name: releases +# +# id :integer not null, primary key +# tag :string(255) +# description :text +# project_id :integer +# created_at :datetime +# updated_at :datetime +# + +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :release do + tag "v1.1.0" + description "Awesome release" + project + end +end diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index c2c7364f6c5..4c756a8e732 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -111,24 +111,50 @@ describe "Admin::Users", feature: true do expect(page).to have_content(@user.name) end - describe 'Login as another user' do - it 'should show login button for other users and check that it works' do - another_user = create(:user) + describe 'Impersonation' do + let(:another_user) { create(:user) } + before { visit admin_user_path(another_user) } - visit admin_user_path(another_user) - - click_link 'Log in as this user' + context 'before impersonating' do + it 'shows impersonate button for other users' do + expect(page).to have_content('Impersonate') + end - expect(page).to have_content("Logged in as #{another_user.username}") + it 'should not show impersonate button for admin itself' do + visit admin_user_path(@user) - page.within '.sidebar-user .username' do - expect(page).to have_content(another_user.username) + expect(page).not_to have_content('Impersonate') end end - it 'should not show login button for admin itself' do - visit admin_user_path(@user) - expect(page).not_to have_content('Log in as this user') + context 'when impersonating' do + before { click_link 'Impersonate' } + + it 'logs in as the user when impersonate is clicked' do + page.within '.sidebar-user .username' do + expect(page).to have_content(another_user.username) + end + end + + it 'sees impersonation log out icon' do + icon = first('.fa.fa-user-secret') + + expect(icon).to_not eql nil + end + + it 'can log out of impersonated user back to original user' do + find(:css, 'li.impersonation a').click + + page.within '.sidebar-user .username' do + expect(page).to have_content(@user.username) + end + end + + it 'is redirected back to the impersonated users page in the admin after stopping' do + find(:css, 'li.impersonation a').click + + expect(current_path).to eql "/admin/users/#{another_user.username}" + end end end diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb index 154857e77fe..5213ce1099f 100644 --- a/spec/features/builds_spec.rb +++ b/spec/features/builds_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe "Builds" do + let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } + before do login_as(:user) @commit = FactoryGirl.create :ci_commit @@ -47,10 +49,11 @@ describe "Builds" do end end - describe "GET /:project/builds/:id/cancel_all" do + describe "POST /:project/builds/:id/cancel_all" do before do @build.run! - visit cancel_all_namespace_project_builds_path(@gl_project.namespace, @gl_project) + visit namespace_project_builds_path(@gl_project.namespace, @gl_project) + click_link "Cancel all" end it { expect(page).to have_content 'No builds to show' } @@ -65,12 +68,22 @@ describe "Builds" do it { expect(page).to have_content @commit.sha[0..7] } it { expect(page).to have_content @commit.git_commit_message } it { expect(page).to have_content @commit.git_author_name } + + context "Download artifacts" do + before do + @build.update_attributes(artifacts_file: artifacts_file) + visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build) + end + + it { expect(page).to have_content 'Download artifacts' } + end end - describe "GET /:project/builds/:id/cancel" do + describe "POST /:project/builds/:id/cancel" do before do @build.run! - visit cancel_namespace_project_build_path(@gl_project.namespace, @gl_project, @build) + visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build) + click_link "Cancel" end it { expect(page).to have_content 'canceled' } @@ -79,11 +92,23 @@ describe "Builds" do describe "POST /:project/builds/:id/retry" do before do - visit cancel_namespace_project_build_path(@gl_project.namespace, @gl_project, @build) + @build.run! + visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build) + click_link "Cancel" click_link 'Retry' end it { expect(page).to have_content 'pending' } it { expect(page).to have_content 'Cancel' } end + + describe "GET /:project/builds/:id/download" do + before do + @build.update_attributes(artifacts_file: artifacts_file) + visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build) + click_link 'Download artifacts' + end + + it { expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type) } + end end diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 1adc2cdf70a..90739cd6a28 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -19,7 +19,7 @@ describe "Commits" do stub_ci_commit_to_return_yaml_file end - describe "GET /:project/commits/:sha" do + describe "GET /:project/commits/:sha/ci" do before do visit ci_status_path(@commit) end @@ -29,10 +29,24 @@ describe "Commits" do it { expect(page).to have_content @commit.git_author_name } end + context "Download artifacts" do + let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } + + before do + @build.update_attributes(artifacts_file: artifacts_file) + end + + it do + visit ci_status_path(@commit) + click_on "Download artifacts" + expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type) + end + end + describe "Cancel all builds" do it "cancels commit" do visit ci_status_path(@commit) - click_on "Cancel all" + click_on "Cancel running" expect(page).to have_content "canceled" end end diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index 06adb7633b2..b0259026630 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -14,15 +14,25 @@ describe "Runners" do @project2 = FactoryGirl.create :ci_project @project2.gl_project.team << [user, :master] + @project3 = FactoryGirl.create :ci_project + @project3.gl_project.team << [user, :developer] + @shared_runner = FactoryGirl.create :ci_shared_runner @specific_runner = FactoryGirl.create :ci_specific_runner @specific_runner2 = FactoryGirl.create :ci_specific_runner + @specific_runner3 = FactoryGirl.create :ci_specific_runner @project.runners << @specific_runner @project2.runners << @specific_runner2 + @project3.runners << @specific_runner3 visit runners_path(@project.gl_project) end + before do + expect(page).to_not have_content(@specific_runner3.display_name) + expect(page).to_not have_content(@specific_runner3.display_name) + end + it "places runners in right places" do expect(page.find(".available-specific-runners")).to have_content(@specific_runner2.display_name) expect(page.find(".activated-specific-runners")).to have_content(@specific_runner.display_name) @@ -76,10 +86,10 @@ describe "Runners" do @project.gl_project.team << [user, :master] @specific_runner = FactoryGirl.create :ci_specific_runner @project.runners << @specific_runner - visit runners_path(@project.gl_project) end it "shows runner information" do + visit runners_path(@project.gl_project) click_on @specific_runner.short_sha expect(page).to have_content(@specific_runner.platform) end diff --git a/spec/finders/group_finder_spec.rb b/spec/finders/group_finder_spec.rb new file mode 100644 index 00000000000..78dc027837c --- /dev/null +++ b/spec/finders/group_finder_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe GroupsFinder do + let(:user) { create :user } + let!(:group) { create :group } + let!(:public_group) { create :group, public: true } + + describe :execute do + it 'finds public group' do + groups = GroupsFinder.new.execute(user) + expect(groups.size).to eq(1) + expect(groups.first).to eq(public_group) + end + end +end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index b327f4f911a..ebe9c29d91c 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -42,6 +42,11 @@ describe SearchHelper do expect(search_autocomplete_opts(project.name).size).to eq(1) end + it "includes the public group" do + group = create(:group, public: true) + expect(search_autocomplete_opts(group.name).size).to eq(1) + end + context "with a current project" do before { @project = create(:project) } diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index abdb6b89ac5..7d90f9877c6 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' module Ci describe GitlabCiYamlProcessor do - + let(:path) { 'path' } + describe "#builds_for_ref" do let(:type) { 'test' } @@ -12,7 +13,7 @@ module Ci rspec: { script: "rspec" } }) - config_processor = GitlabCiYamlProcessor.new(config) + config_processor = GitlabCiYamlProcessor.new(config, path) expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1) expect(config_processor.builds_for_stage_and_ref(type, "master").first).to eq({ @@ -28,78 +29,218 @@ module Ci when: "on_success" }) end + + describe :only do + it "does not return builds if only has another branch" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", only: ["deploy"] } + }) - it "does not return builds if only has another branch" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", only: ["deploy"] } - }) + config_processor = GitlabCiYamlProcessor.new(config, path) - config_processor = GitlabCiYamlProcessor.new(config) + expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0) + end - expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0) - end + it "does not return builds if only has regexp with another branch" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", only: ["/^deploy$/"] } + }) - it "does not return builds if only has regexp with another branch" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", only: ["/^deploy$/"] } - }) + config_processor = GitlabCiYamlProcessor.new(config, path) - config_processor = GitlabCiYamlProcessor.new(config) + expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0) + end - expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0) - end + it "returns builds if only has specified this branch" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", only: ["master"] } + }) - it "returns builds if only has specified this branch" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", only: ["master"] } - }) + config_processor = GitlabCiYamlProcessor.new(config, path) - config_processor = GitlabCiYamlProcessor.new(config) + expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1) + end - expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1) - end + it "returns builds if only has a list of branches including specified" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", type: type, only: ["master", "deploy"] } + }) - it "does not build tags" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", except: ["tags"] } - }) + config_processor = GitlabCiYamlProcessor.new(config, path) - config_processor = GitlabCiYamlProcessor.new(config) + expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1) + end - expect(config_processor.builds_for_stage_and_ref(type, "0-1", true).size).to eq(0) - end + it "returns builds if only has a branches keyword specified" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", type: type, only: ["branches"] } + }) - it "returns builds if only has a list of branches including specified" do - config = YAML.dump({ - before_script: ["pwd"], - rspec: { script: "rspec", type: type, only: ["master", "deploy"] } - }) + config_processor = GitlabCiYamlProcessor.new(config, path) - config_processor = GitlabCiYamlProcessor.new(config) + expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1) + end + + it "does not return builds if only has a tags keyword" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", type: type, only: ["tags"] } + }) + + config_processor = GitlabCiYamlProcessor.new(config, path) + + expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0) + end + + it "returns builds if only has current repository path" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", type: type, only: ["branches@path"] } + }) + + config_processor = GitlabCiYamlProcessor.new(config, path) + + expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1) + end + + it "does not return builds if only has different repository path" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", type: type, only: ["branches@fork"] } + }) + + config_processor = GitlabCiYamlProcessor.new(config, path) + + expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0) + end + + it "returns build only for specified type" do + + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", type: "test", only: ["master", "deploy"] }, + staging: { script: "deploy", type: "deploy", only: ["master", "deploy"] }, + production: { script: "deploy", type: "deploy", only: ["master@path", "deploy"] }, + }) - expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1) + config_processor = GitlabCiYamlProcessor.new(config, 'fork') + + expect(config_processor.builds_for_stage_and_ref("deploy", "deploy").size).to eq(2) + expect(config_processor.builds_for_stage_and_ref("test", "deploy").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("deploy", "master").size).to eq(1) + end end - it "returns build only for specified type" do + describe :except do + it "returns builds if except has another branch" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", except: ["deploy"] } + }) - config = YAML.dump({ - before_script: ["pwd"], - build: { script: "build", type: "build", only: ["master", "deploy"] }, - rspec: { script: "rspec", type: type, only: ["master", "deploy"] }, - staging: { script: "deploy", type: "deploy", only: ["master", "deploy"] }, - production: { script: "deploy", type: "deploy", only: ["master", "deploy"] }, - }) + config_processor = GitlabCiYamlProcessor.new(config, path) - config_processor = GitlabCiYamlProcessor.new(config) + expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1) + end + + it "returns builds if except has regexp with another branch" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", except: ["/^deploy$/"] } + }) + + config_processor = GitlabCiYamlProcessor.new(config, path) + + expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1) + end + + it "does not return builds if except has specified this branch" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", except: ["master"] } + }) + + config_processor = GitlabCiYamlProcessor.new(config, path) - expect(config_processor.builds_for_stage_and_ref("production", "deploy").size).to eq(0) - expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1) - expect(config_processor.builds_for_stage_and_ref("deploy", "deploy").size).to eq(2) + expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0) + end + + it "does not return builds if except has a list of branches including specified" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", type: type, except: ["master", "deploy"] } + }) + + config_processor = GitlabCiYamlProcessor.new(config, path) + + expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0) + end + + it "does not return builds if except has a branches keyword specified" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", type: type, except: ["branches"] } + }) + + config_processor = GitlabCiYamlProcessor.new(config, path) + + expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0) + end + + it "returns builds if except has a tags keyword" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", type: type, except: ["tags"] } + }) + + config_processor = GitlabCiYamlProcessor.new(config, path) + + expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1) + end + + it "does not return builds if except has current repository path" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", type: type, except: ["branches@path"] } + }) + + config_processor = GitlabCiYamlProcessor.new(config, path) + + expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0) + end + + it "returns builds if except has different repository path" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", type: type, except: ["branches@fork"] } + }) + + config_processor = GitlabCiYamlProcessor.new(config, path) + + expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1) + end + + it "returns build except specified type" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: { script: "rspec", type: "test", except: ["master", "deploy", "test@fork"] }, + staging: { script: "deploy", type: "deploy", except: ["master"] }, + production: { script: "deploy", type: "deploy", except: ["master@fork"] }, + }) + + config_processor = GitlabCiYamlProcessor.new(config, 'fork') + + expect(config_processor.builds_for_stage_and_ref("deploy", "deploy").size).to eq(2) + expect(config_processor.builds_for_stage_and_ref("test", "test").size).to eq(0) + expect(config_processor.builds_for_stage_and_ref("deploy", "master").size).to eq(0) + end end + end describe "Image and service handling" do @@ -111,7 +252,7 @@ module Ci rspec: { script: "rspec" } }) - config_processor = GitlabCiYamlProcessor.new(config) + config_processor = GitlabCiYamlProcessor.new(config, path) expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({ @@ -139,7 +280,7 @@ module Ci rspec: { image: "ruby:2.5", services: ["postgresql"], script: "rspec" } }) - config_processor = GitlabCiYamlProcessor.new(config) + config_processor = GitlabCiYamlProcessor.new(config, path) expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({ @@ -172,7 +313,7 @@ module Ci rspec: { script: "rspec" } }) - config_processor = GitlabCiYamlProcessor.new(config) + config_processor = GitlabCiYamlProcessor.new(config, path) expect(config_processor.variables).to eq(variables) end end @@ -184,7 +325,7 @@ module Ci rspec: { script: "rspec", when: when_state } }) - config_processor = GitlabCiYamlProcessor.new(config) + config_processor = GitlabCiYamlProcessor.new(config, path) builds = config_processor.builds_for_stage_and_ref("test", "master") expect(builds.size).to eq(1) expect(builds.first[:when]).to eq(when_state) @@ -192,6 +333,97 @@ module Ci end end + describe "Caches" do + it "returns cache when defined globally" do + config = YAML.dump({ + cache: { paths: ["logs/", "binaries/"], untracked: true }, + rspec: { + script: "rspec" + } + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( + paths: ["logs/", "binaries/"], + untracked: true, + ) + end + + it "returns cache when defined in a job" do + config = YAML.dump({ + rspec: { + cache: { paths: ["logs/", "binaries/"], untracked: true }, + script: "rspec" + } + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( + paths: ["logs/", "binaries/"], + untracked: true, + ) + end + + it "overwrite cache when defined for a job and globally" do + config = YAML.dump({ + cache: { paths: ["logs/", "binaries/"], untracked: true }, + rspec: { + script: "rspec", + cache: { paths: ["test/"], untracked: false }, + } + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( + paths: ["test/"], + untracked: false, + ) + end + end + + describe "Artifacts" do + it "returns artifacts when defined" do + config = YAML.dump({ + image: "ruby:2.1", + services: ["mysql"], + before_script: ["pwd"], + rspec: { + artifacts: { paths: ["logs/", "binaries/"], untracked: true }, + script: "rspec" + } + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({ + except: nil, + stage: "test", + stage_idx: 1, + name: :rspec, + only: nil, + commands: "pwd\nrspec", + tag_list: [], + options: { + image: "ruby:2.1", + services: ["mysql"], + artifacts: { + paths: ["logs/", "binaries/"], + untracked: true + } + }, + when: "on_success", + allow_failure: false + }) + end + end + describe "Error handling" do it "indicates that object is invalid" do expect{GitlabCiYamlProcessor.new("invalid_yaml\n!ccdvlf%612334@@@@")}.to raise_error(GitlabCiYamlProcessor::ValidationError) @@ -200,156 +432,198 @@ module Ci it "returns errors if tags parameter is invalid" do config = YAML.dump({ rspec: { script: "test", tags: "mysql" } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: tags parameter should be an array of strings") end it "returns errors if before_script parameter is invalid" do config = YAML.dump({ before_script: "bundle update", rspec: { script: "test" } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "before_script should be an array of strings") end it "returns errors if image parameter is invalid" do config = YAML.dump({ image: ["test"], rspec: { script: "test" } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "image should be a string") end it "returns errors if job name is blank" do config = YAML.dump({ '' => { script: "test" } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "job name should be non-empty string") end it "returns errors if job name is non-string" do config = YAML.dump({ 10 => { script: "test" } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "job name should be non-empty string") end it "returns errors if job image parameter is invalid" do config = YAML.dump({ rspec: { script: "test", image: ["test"] } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: image should be a string") end it "returns errors if services parameter is not an array" do config = YAML.dump({ services: "test", rspec: { script: "test" } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services should be an array of strings") end it "returns errors if services parameter is not an array of strings" do config = YAML.dump({ services: [10, "test"], rspec: { script: "test" } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services should be an array of strings") end it "returns errors if job services parameter is not an array" do config = YAML.dump({ rspec: { script: "test", services: "test" } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: services should be an array of strings") end it "returns errors if job services parameter is not an array of strings" do config = YAML.dump({ rspec: { script: "test", services: [10, "test"] } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: services should be an array of strings") end it "returns errors if there are unknown parameters" do config = YAML.dump({ extra: "bundle update" }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Unknown parameter: extra") end it "returns errors if there are unknown parameters that are hashes, but doesn't have a script" do config = YAML.dump({ extra: { services: "test" } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Unknown parameter: extra") end it "returns errors if there is no any jobs defined" do config = YAML.dump({ before_script: ["bundle update"] }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Please define at least one job") end it "returns errors if job allow_failure parameter is not an boolean" do config = YAML.dump({ rspec: { script: "test", allow_failure: "string" } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: allow_failure parameter should be an boolean") end it "returns errors if job stage is not a string" do config = YAML.dump({ rspec: { script: "test", type: 1, allow_failure: "string" } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy") end it "returns errors if job stage is not a pre-defined stage" do config = YAML.dump({ rspec: { script: "test", type: "acceptance", allow_failure: "string" } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy") end it "returns errors if job stage is not a defined stage" do config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", type: "acceptance", allow_failure: "string" } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test") end it "returns errors if stages is not an array" do config = YAML.dump({ types: "test", rspec: { script: "test" } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "stages should be an array of strings") end it "returns errors if stages is not an array of strings" do config = YAML.dump({ types: [true, "test"], rspec: { script: "test" } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "stages should be an array of strings") end it "returns errors if variables is not a map" do config = YAML.dump({ variables: "test", rspec: { script: "test" } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings") end it "returns errors if variables is not a map of key-valued strings" do config = YAML.dump({ variables: { test: false }, rspec: { script: "test" } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings") end it "returns errors if job when is not on_success, on_failure or always" do config = YAML.dump({ rspec: { script: "test", when: 1 } }) expect do - GitlabCiYamlProcessor.new(config) + GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always") end + + it "returns errors if job artifacts:untracked is not an array of strings" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { untracked: "string" } } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:untracked parameter should be an boolean") + end + + it "returns errors if job artifacts:paths is not an array of strings" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { paths: "string" } } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:paths parameter should be an array of strings") + end + + it "returns errors if cache:untracked is not an array of strings" do + config = YAML.dump({ cache: { untracked: "string" }, rspec: { script: "test" } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:untracked parameter should be an boolean") + end + + it "returns errors if cache:paths is not an array of strings" do + config = YAML.dump({ cache: { paths: "string" }, rspec: { script: "test" } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:paths parameter should be an array of strings") + end + + it "returns errors if job cache:untracked is not an array of strings" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { untracked: "string" } } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:untracked parameter should be an boolean") + end + + it "returns errors if job cache:paths is not an array of strings" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { paths: "string" } } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:paths parameter should be an array of strings") + end end end end diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/inline_diff_spec.rb index 2e0a05088cc..2e0a05088cc 100644 --- a/spec/lib/gitlab/diff/inline_diff_spec.rb +++ b/spec/lib/gitlab/inline_diff_spec.rb diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb new file mode 100644 index 00000000000..cebcb5bc887 --- /dev/null +++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb @@ -0,0 +1,650 @@ +require 'spec_helper' + +describe Gitlab::Lfs::Router do + let(:project) { create(:project) } + let(:public_project) { create(:project, :public) } + let(:forked_project) { fork_project(public_project, user) } + + let(:user) { create(:user) } + let(:user_two) { create(:user) } + let!(:lfs_object) { create(:lfs_object, :with_file) } + + let(:request) { Rack::Request.new(env) } + let(:env) do + { + 'rack.input' => '', + 'REQUEST_METHOD' => 'GET', + } + end + + let(:lfs_router_auth) { new_lfs_router(project, user) } + let(:lfs_router_noauth) { new_lfs_router(project, nil) } + let(:lfs_router_public_auth) { new_lfs_router(public_project, user) } + let(:lfs_router_public_noauth) { new_lfs_router(public_project, nil) } + let(:lfs_router_forked_noauth) { new_lfs_router(forked_project, nil) } + let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user_two) } + + let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" } + let(:sample_size) { 499013 } + + describe 'when lfs is disabled' do + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(false) + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}" + end + + it 'responds with 501' do + respond_with_disabled = [ 501, + { "Content-Type"=>"application/vnd.git-lfs+json" }, + ["{\"message\":\"Git LFS is not enabled on this GitLab server, contact your admin.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"] + ] + expect(lfs_router_auth.try_call).to match_array(respond_with_disabled) + end + end + + describe 'when fetching lfs object' do + before do + enable_lfs + env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8" + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}" + end + + describe 'when user is authenticated' do + context 'and user has project download access' do + before do + @auth = authorize(user) + env["HTTP_AUTHORIZATION"] = @auth + project.lfs_objects << lfs_object + project.team << [user, :master] + end + + it "responds with status 200" do + expect(lfs_router_auth.try_call.first).to eq(200) + end + + it "responds with download hypermedia" do + json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first) + + expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") + expect(json_response['_links']['download']['header']).to eq("Authorization" => @auth, "Accept" => "application/vnd.git-lfs+json; charset=utf-8") + end + end + + context 'and user does not have project access' do + it "responds with status 403" do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + + describe 'when user is unauthenticated' do + context 'and user does not have download access' do + it "responds with status 401" do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'and user has download access' do + before do + project.team << [user, :master] + end + + it "responds with status 401" do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + end + + describe 'and project is public' do + context 'and project has access to the lfs object' do + before do + public_project.lfs_objects << lfs_object + end + + context 'and user is authenticated' do + it "responds with status 200 and sends download hypermedia" do + expect(lfs_router_public_auth.try_call.first).to eq(200) + json_response = ActiveSupport::JSON.decode(lfs_router_public_auth.try_call.last.first) + + expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{public_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") + expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8") + end + end + + context 'and user is unauthenticated' do + it "responds with status 200 and sends download hypermedia" do + expect(lfs_router_public_noauth.try_call.first).to eq(200) + json_response = ActiveSupport::JSON.decode(lfs_router_public_noauth.try_call.last.first) + + expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{public_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") + expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8") + end + end + end + + context 'and project does not have access to the lfs object' do + it "responds with status 404" do + expect(lfs_router_public_auth.try_call.first).to eq(404) + end + end + end + + describe 'and request comes from gitlab-workhorse' do + before do + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}" + end + context 'without user being authorized' do + it "responds with status 401" do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'with required headers' do + before do + env['HTTP_X_SENDFILE_TYPE'] = "X-Sendfile" + end + + context 'when user does not have project access' do + it "responds with status 403" do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + + context 'when user has project access' do + before do + project.lfs_objects << lfs_object + project.team << [user, :master] + end + + it "responds with status 200" do + expect(lfs_router_auth.try_call.first).to eq(200) + end + + it "responds with the file location" do + expect(lfs_router_auth.try_call[1]['Content-Type']).to eq("application/octet-stream") + expect(lfs_router_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path) + end + end + end + + context 'without required headers' do + it "responds with status 403" do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + + describe 'from a forked public project' do + before do + env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8" + env["PATH_INFO"] = "#{forked_project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}" + end + + context "when fetching a lfs object" do + context "and user has project download access" do + before do + public_project.lfs_objects << lfs_object + end + + it "can download the lfs object" do + expect(lfs_router_forked_auth.try_call.first).to eq(200) + json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) + + expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{forked_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") + expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8") + end + end + + context "and user is not authenticated but project is public" do + before do + public_project.lfs_objects << lfs_object + end + + it "can download the lfs object" do + expect(lfs_router_forked_auth.try_call.first).to eq(200) + end + end + + context "and user has project download access" do + before do + env["PATH_INFO"] = "#{forked_project.repository.path_with_namespace}.git/info/lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897" + @auth = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) + env["HTTP_AUTHORIZATION"] = @auth + lfs_object_two = create(:lfs_object, :with_file, oid: "91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", size: 1575078) + public_project.lfs_objects << lfs_object_two + end + + it "can get a lfs object that is not in the forked project" do + expect(lfs_router_forked_auth.try_call.first).to eq(200) + + json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) + expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{forked_project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") + expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8", "Authorization" => @auth) + end + end + + context "and user has project download access" do + before do + env["PATH_INFO"] = "#{forked_project.repository.path_with_namespace}.git/info/lfs/objects/267c8b1d876743971e3a9978405818ff5ca731c4c870b06507619cd9b1847b6b" + lfs_object_three = create(:lfs_object, :with_file, oid: "267c8b1d876743971e3a9978405818ff5ca731c4c870b06507619cd9b1847b6b", size: 127192524) + project.lfs_objects << lfs_object_three + end + + it "cannot get a lfs object that is not in the project" do + expect(lfs_router_forked_auth.try_call.first).to eq(404) + end + end + end + end + end + + describe 'when initiating pushing of the lfs object' do + before do + enable_lfs + env['REQUEST_METHOD'] = 'POST' + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch" + end + + describe 'when user is authenticated' do + before do + body = { 'objects' => [{ + 'oid' => sample_oid, + 'size' => sample_size + }] + }.to_json + env['rack.input'] = StringIO.new(body) + end + + describe 'when user has project push access' do + before do + @auth = authorize(user) + env["HTTP_AUTHORIZATION"] = @auth + project.team << [user, :master] + end + + context 'when pushing an lfs object that already exists' do + before do + public_project.lfs_objects << lfs_object + end + + it "responds with status 200 and links the object to the project" do + response_body = lfs_router_auth.try_call.last + response = ActiveSupport::JSON.decode(response_body.first) + + expect(response['objects']).to be_kind_of(Array) + expect(response['objects'].first['oid']).to eq(sample_oid) + expect(response['objects'].first['size']).to eq(sample_size) + expect(lfs_object.projects.pluck(:id)).to_not include(project.id) + expect(lfs_object.projects.pluck(:id)).to include(public_project.id) + expect(response['objects'].first).to have_key('_links') + end + end + + context 'when pushing a lfs object that does not exist' do + before do + body = { + 'objects' => [{ + 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }] + }.to_json + env['rack.input'] = StringIO.new(body) + end + + it "responds with status 200 and upload hypermedia link" do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + + response_body = ActiveSupport::JSON.decode(response.last.first) + expect(response_body['objects']).to be_kind_of(Array) + expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") + expect(response_body['objects'].first['size']).to eq(1575078) + expect(lfs_object.projects.pluck(:id)).not_to include(project.id) + expect(response_body['objects'].first['_links']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") + expect(response_body['objects'].first['_links']['upload']['header']).to eq("Authorization" => @auth) + end + end + + context 'when pushing one new and one existing lfs object' do + before do + body = { + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }, + { 'oid' => sample_oid, + 'size' => sample_size + } + ] + }.to_json + env['rack.input'] = StringIO.new(body) + public_project.lfs_objects << lfs_object + end + + it "responds with status 200 with upload hypermedia link for the new object" do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + + response_body = ActiveSupport::JSON.decode(response.last.first) + expect(response_body['objects']).to be_kind_of(Array) + + + expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") + expect(response_body['objects'].first['size']).to eq(1575078) + expect(response_body['objects'].first['_links']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") + expect(response_body['objects'].first['_links']['upload']['header']).to eq("Authorization" => @auth) + + expect(response_body['objects'].last['oid']).to eq(sample_oid) + expect(response_body['objects'].last['size']).to eq(sample_size) + expect(lfs_object.projects.pluck(:id)).to_not include(project.id) + expect(lfs_object.projects.pluck(:id)).to include(public_project.id) + expect(response_body['objects'].last).to have_key('_links') + end + end + end + + context 'when user does not have push access' do + it 'responds with 403' do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + + context 'when user is not authenticated' do + context 'when user has push access' do + before do + project.team << [user, :master] + end + + it "responds with status 401" do + expect(lfs_router_public_noauth.try_call.first).to eq(401) + end + end + + context 'when user does not have push access' do + it "responds with status 401" do + expect(lfs_router_public_noauth.try_call.first).to eq(401) + end + end + end + end + + describe 'when pushing a lfs object' do + before do + enable_lfs + env['REQUEST_METHOD'] = 'PUT' + end + + describe 'to one project' do + describe 'when user has push access to the project' do + before do + project.team << [user, :master] + end + + describe 'when user is authenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(project) + end + + it 'responds with status 200, location of lfs store and object details' do + json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first) + + expect(lfs_router_auth.try_call.first).to eq(200) + expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") + expect(json_response['LfsOid']).to eq(sample_oid) + expect(json_response['LfsSize']).to eq(sample_size) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(project) + end + + it 'responds with status 200 and lfs object is linked to the project' do + expect(lfs_router_auth.try_call.first).to eq(200) + expect(lfs_object.projects.pluck(:id)).to include(project.id) + end + end + end + + describe 'when user is unauthenticated' do + let(:lfs_router_noauth) { new_lfs_router(project, nil) } + + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(project) + end + + it 'responds with status 401' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(project) + end + + it 'responds with status 401' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent with a malformed headers' do + before do + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" + env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd" + end + + it 'does not recognize it as a valid lfs command' do + expect(lfs_router_noauth.try_call).to eq(nil) + end + end + end + end + + describe 'and user does not have push access' do + describe 'when user is authenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(project) + end + + it 'responds with 403' do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(project) + end + + it 'responds with 403' do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + + describe 'when user is unauthenticated' do + let(:lfs_router_noauth) { new_lfs_router(project, nil) } + + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(project) + end + + it 'responds with 401' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(project) + end + + it 'responds with 401' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + end + end + end + + describe "to a forked project" do + let(:forked_project) { fork_project(public_project, user) } + + describe 'when user has push access to the project' do + before do + forked_project.team << [user_two, :master] + end + + describe 'when user is authenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(forked_project) + end + + it 'responds with status 200, location of lfs store and object details' do + json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) + + expect(lfs_router_forked_auth.try_call.first).to eq(200) + expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") + expect(json_response['LfsOid']).to eq(sample_oid) + expect(json_response['LfsSize']).to eq(sample_size) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(forked_project) + end + + it 'responds with status 200 and lfs object is linked to the source project' do + expect(lfs_router_forked_auth.try_call.first).to eq(200) + expect(lfs_object.projects.pluck(:id)).to include(public_project.id) + end + end + end + + describe 'when user is unauthenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(forked_project) + end + + it 'responds with status 401' do + expect(lfs_router_forked_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(forked_project) + end + + it 'responds with status 401' do + expect(lfs_router_forked_noauth.try_call.first).to eq(401) + end + end + end + end + + describe 'and user does not have push access' do + describe 'when user is authenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(forked_project) + end + + it 'responds with 403' do + expect(lfs_router_forked_auth.try_call.first).to eq(403) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(forked_project) + end + + it 'responds with 403' do + expect(lfs_router_forked_auth.try_call.first).to eq(403) + end + end + end + + describe 'when user is unauthenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(forked_project) + end + + it 'responds with 401' do + expect(lfs_router_forked_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(forked_project) + end + + it 'responds with 401' do + expect(lfs_router_forked_noauth.try_call.first).to eq(401) + end + end + end + end + + describe 'and second project not related to fork or a source project' do + let(:second_project) { create(:project) } + let(:lfs_router_second_project) { new_lfs_router(second_project, user) } + + before do + public_project.lfs_objects << lfs_object + headers_for_upload_finalize(second_project) + end + + context 'when pushing the same lfs object to the second project' do + before do + second_project.team << [user, :master] + end + + it 'responds with 200 and links the lfs object to the project' do + expect(lfs_router_second_project.try_call.first).to eq(200) + expect(lfs_object.projects.pluck(:id)).to include(second_project.id, public_project.id) + end + end + end + end + end + + def enable_lfs + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + end + + def authorize(user) + ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) + end + + def new_lfs_router(project, user) + Gitlab::Lfs::Router.new(project, user, request) + end + + def header_for_upload_authorize(project) + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize" + end + + def headers_for_upload_finalize(project) + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" + env["HTTP_X_GITLAB_LFS_TMP"] = "#{sample_oid}6e561c9d4" + end + + def fork_project(project, user, object = nil) + allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) + Projects::ForkService.new(project, user, {}).execute + end +end diff --git a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb index e50c82d0b3c..27cd00e8054 100644 --- a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb +++ b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb @@ -44,7 +44,7 @@ module Gitlab::Markdown instance = described_class.new('Foo') 3.times { instance.whitelist } - expect(instance.whitelist[:transformers].size).to eq 4 + expect(instance.whitelist[:transformers].size).to eq 5 end it 'allows syntax highlighting' do @@ -77,19 +77,100 @@ module Gitlab::Markdown end it 'removes `rel` attribute from `a` elements' do - doc = filter(%q{<a href="#" rel="nofollow">Link</a>}) + act = %q{<a href="#" rel="nofollow">Link</a>} + exp = %q{<a href="#">Link</a>} - expect(doc.css('a').size).to eq 1 - expect(doc.at_css('a')['href']).to eq '#' - expect(doc.at_css('a')['rel']).to be_nil + expect(filter(act).to_html).to eq exp end - it 'removes script-like `href` attribute from `a` elements' do - html = %q{<a href="javascript:alert('Hi')">Hi</a>} - doc = filter(html) + # Adapted from the Sanitize test suite: http://git.io/vczrM + protocols = { + 'protocol-based JS injection: simple, no spaces' => { + input: '<a href="javascript:alert(\'XSS\');">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: simple, spaces before' => { + input: '<a href="javascript :alert(\'XSS\');">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: simple, spaces after' => { + input: '<a href="javascript: alert(\'XSS\');">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: simple, spaces before and after' => { + input: '<a href="javascript : alert(\'XSS\');">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: preceding colon' => { + input: '<a href=":javascript:alert(\'XSS\');">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: UTF-8 encoding' => { + input: '<a href="javascript:">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: long UTF-8 encoding' => { + input: '<a href="javascript:">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: long UTF-8 encoding without semicolons' => { + input: '<a href=javascript:alert('XSS')>foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: hex encoding' => { + input: '<a href="javascript:">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: long hex encoding' => { + input: '<a href="javascript:">foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: hex encoding without semicolons' => { + input: '<a href=javascript:alert('XSS')>foo</a>', + output: '<a>foo</a>' + }, + + 'protocol-based JS injection: null char' => { + input: "<a href=java\0script:alert(\"XSS\")>foo</a>", + output: '<a href="java"></a>' + }, + + 'protocol-based JS injection: spaces and entities' => { + input: '<a href="  javascript:alert(\'XSS\');">foo</a>', + output: '<a href="">foo</a>' + }, + } + + protocols.each do |name, data| + it "handles #{name}" do + doc = filter(data[:input]) + + expect(doc.to_html).to eq data[:output] + end + end + + it 'allows non-standard anchor schemes' do + exp = %q{<a href="irc://irc.freenode.net/git">IRC</a>} + act = filter(exp) + + expect(act.to_html).to eq exp + end + + it 'allows relative links' do + exp = %q{<a href="foo/bar.md">foo/bar.md</a>} + act = filter(exp) - expect(doc.css('a').size).to eq 1 - expect(doc.at_css('a')['href']).to be_nil + expect(act.to_html).to eq exp end end diff --git a/spec/lib/gitlab/push_data_builder_spec.rb b/spec/lib/gitlab/push_data_builder_spec.rb index 1b8ba7b4d43..02710742625 100644 --- a/spec/lib/gitlab/push_data_builder_spec.rb +++ b/spec/lib/gitlab/push_data_builder_spec.rb @@ -17,6 +17,9 @@ describe 'Gitlab::PushDataBuilder' do it { expect(data[:repository][:git_ssh_url]).to eq(project.ssh_url_to_repo) } it { expect(data[:repository][:visibility_level]).to eq(project.visibility_level) } it { expect(data[:total_commits_count]).to eq(3) } + it { expect(data[:added]).to eq(["gitlab-grack"]) } + it { expect(data[:modified]).to eq([".gitmodules", "files/ruby/popen.rb", "files/ruby/regex.rb"]) } + it { expect(data[:removed]).to eq([]) } end describe :build do @@ -35,5 +38,8 @@ describe 'Gitlab::PushDataBuilder' do it { expect(data[:ref]).to eq('refs/tags/v1.1.0') } it { expect(data[:commits]).to be_empty } it { expect(data[:total_commits_count]).to be_zero } + it { expect(data[:added]).to eq([]) } + it { expect(data[:modified]).to eq([]) } + it { expect(data[:removed]).to eq([]) } end end diff --git a/spec/lib/gitlab/sherlock/collection_spec.rb b/spec/lib/gitlab/sherlock/collection_spec.rb new file mode 100644 index 00000000000..a8a9d6fc7bc --- /dev/null +++ b/spec/lib/gitlab/sherlock/collection_spec.rb @@ -0,0 +1,82 @@ +require 'spec_helper' + +describe Gitlab::Sherlock::Collection do + let(:collection) { described_class.new } + + let(:transaction) do + Gitlab::Sherlock::Transaction.new('POST', '/cat_pictures') + end + + describe '#add' do + it 'adds a new transaction' do + collection.add(transaction) + + expect(collection).to_not be_empty + end + + it 'is aliased as <<' do + collection << transaction + + expect(collection).to_not be_empty + end + end + + describe '#each' do + it 'iterates over every transaction' do + collection.add(transaction) + + expect { |b| collection.each(&b) }.to yield_with_args(transaction) + end + end + + describe '#clear' do + it 'removes all transactions' do + collection.add(transaction) + + collection.clear + + expect(collection).to be_empty + end + end + + describe '#empty?' do + it 'returns true for an empty collection' do + expect(collection).to be_empty + end + + it 'returns false for a collection with a transaction' do + collection.add(transaction) + + expect(collection).to_not be_empty + end + end + + describe '#find_transaction' do + it 'returns the transaction for the given ID' do + collection.add(transaction) + + expect(collection.find_transaction(transaction.id)).to eq(transaction) + end + + it 'returns nil when no transaction could be found' do + collection.add(transaction) + + expect(collection.find_transaction('cats')).to be_nil + end + end + + describe '#newest_first' do + it 'returns transactions sorted from new to old' do + trans1 = Gitlab::Sherlock::Transaction.new('POST', '/cat_pictures') + trans2 = Gitlab::Sherlock::Transaction.new('POST', '/more_cat_pictures') + + allow(trans1).to receive(:finished_at).and_return(Time.utc(2015, 1, 1)) + allow(trans2).to receive(:finished_at).and_return(Time.utc(2015, 1, 2)) + + collection.add(trans1) + collection.add(trans2) + + expect(collection.newest_first).to eq([trans2, trans1]) + end + end +end diff --git a/spec/lib/gitlab/sherlock/file_sample_spec.rb b/spec/lib/gitlab/sherlock/file_sample_spec.rb new file mode 100644 index 00000000000..f05a59f56f6 --- /dev/null +++ b/spec/lib/gitlab/sherlock/file_sample_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe Gitlab::Sherlock::FileSample do + let(:sample) { described_class.new(__FILE__, [], 150.4, 2) } + + describe '#id' do + it 'returns the ID' do + expect(sample.id).to be_an_instance_of(String) + end + end + + describe '#file' do + it 'returns the file path' do + expect(sample.file).to eq(__FILE__) + end + end + + describe '#line_samples' do + it 'returns the line samples' do + expect(sample.line_samples).to eq([]) + end + end + + describe '#events' do + it 'returns the total number of events' do + expect(sample.events).to eq(2) + end + end + + describe '#duration' do + it 'returns the total execution time' do + expect(sample.duration).to eq(150.4) + end + end + + describe '#relative_path' do + it 'returns the relative path' do + expect(sample.relative_path). + to eq('spec/lib/gitlab/sherlock/file_sample_spec.rb') + end + end + + describe '#to_param' do + it 'returns the sample ID' do + expect(sample.to_param).to eq(sample.id) + end + end + + describe '#source' do + it 'returns the contents of the file' do + expect(sample.source).to eq(File.read(__FILE__)) + end + end +end diff --git a/spec/lib/gitlab/sherlock/line_profiler_spec.rb b/spec/lib/gitlab/sherlock/line_profiler_spec.rb new file mode 100644 index 00000000000..8f2e1299714 --- /dev/null +++ b/spec/lib/gitlab/sherlock/line_profiler_spec.rb @@ -0,0 +1,73 @@ +require 'spec_helper' + +describe Gitlab::Sherlock::LineProfiler do + let(:profiler) { described_class.new } + + describe '#profile' do + it 'runs the profiler when using MRI' do + allow(profiler).to receive(:mri?).and_return(true) + allow(profiler).to receive(:profile_mri) + + profiler.profile { 'cats' } + end + + it 'raises NotImplementedError when profiling an unsupported platform' do + allow(profiler).to receive(:mri?).and_return(false) + + expect { profiler.profile { 'cats' } }.to raise_error(NotImplementedError) + end + end + + describe '#profile_mri' do + it 'returns an Array containing the return value and profiling samples' do + allow(profiler).to receive(:lineprof). + and_yield. + and_return({ __FILE__ => [[0, 0, 0, 0]] }) + + retval, samples = profiler.profile_mri { 42 } + + expect(retval).to eq(42) + expect(samples).to eq([]) + end + end + + describe '#aggregate_rblineprof' do + let(:raw_samples) do + { __FILE__ => [[30000, 30000, 5, 0], [15000, 15000, 4, 0]] } + end + + it 'returns an Array of FileSample objects' do + samples = profiler.aggregate_rblineprof(raw_samples) + + expect(samples).to be_an_instance_of(Array) + expect(samples[0]).to be_an_instance_of(Gitlab::Sherlock::FileSample) + end + + describe 'the first FileSample object' do + let(:file_sample) do + profiler.aggregate_rblineprof(raw_samples)[0] + end + + it 'uses the correct file path' do + expect(file_sample.file).to eq(__FILE__) + end + + it 'contains a list of line samples' do + line_sample = file_sample.line_samples[0] + + expect(line_sample).to be_an_instance_of(Gitlab::Sherlock::LineSample) + + expect(line_sample.duration).to eq(15.0) + expect(line_sample.events).to eq(4) + end + + it 'contains the total file execution time' do + expect(file_sample.duration).to eq(30.0) + end + + it 'contains the total amount of file events' do + expect(file_sample.events).to eq(5) + end + end + end +end diff --git a/spec/lib/gitlab/sherlock/line_sample_spec.rb b/spec/lib/gitlab/sherlock/line_sample_spec.rb new file mode 100644 index 00000000000..5f02f6a3213 --- /dev/null +++ b/spec/lib/gitlab/sherlock/line_sample_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe Gitlab::Sherlock::LineSample do + let(:sample) { described_class.new(150.0, 4) } + + describe '#duration' do + it 'returns the duration' do + expect(sample.duration).to eq(150.0) + end + end + + describe '#events' do + it 'returns the amount of events' do + expect(sample.events).to eq(4) + end + end + + describe '#percentage_of' do + it 'returns the percentage of 1500.0' do + expect(sample.percentage_of(1500.0)).to be_within(0.1).of(10.0) + end + end + + describe '#majority_of' do + it 'returns true if the sample takes up the majority of the given duration' do + expect(sample.majority_of?(500.0)).to eq(true) + end + + it "returns false if the sample doesn't take up the majority of the given duration" do + expect(sample.majority_of?(1500.0)).to eq(false) + end + end +end diff --git a/spec/lib/gitlab/sherlock/location_spec.rb b/spec/lib/gitlab/sherlock/location_spec.rb new file mode 100644 index 00000000000..b295a624b35 --- /dev/null +++ b/spec/lib/gitlab/sherlock/location_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe Gitlab::Sherlock::Location do + let(:location) { described_class.new(__FILE__, 1) } + + describe 'from_ruby_location' do + it 'creates a Location from a Thread::Backtrace::Location' do + input = caller_locations[0] + output = described_class.from_ruby_location(input) + + expect(output).to be_an_instance_of(described_class) + expect(output.path).to eq(input.path) + expect(output.line).to eq(input.lineno) + end + end + + describe '#path' do + it 'returns the file path' do + expect(location.path).to eq(__FILE__) + end + end + + describe '#line' do + it 'returns the line number' do + expect(location.line).to eq(1) + end + end + + describe '#application?' do + it 'returns true for an application frame' do + expect(location.application?).to eq(true) + end + + it 'returns false for a non application frame' do + loc = described_class.new('/tmp/cats.rb', 1) + + expect(loc.application?).to eq(false) + end + end +end diff --git a/spec/lib/gitlab/sherlock/middleware_spec.rb b/spec/lib/gitlab/sherlock/middleware_spec.rb new file mode 100644 index 00000000000..aa74fc53a79 --- /dev/null +++ b/spec/lib/gitlab/sherlock/middleware_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' + +describe Gitlab::Sherlock::Middleware do + let(:app) { double(:app) } + let(:middleware) { described_class.new(app) } + + describe '#call' do + describe 'when instrumentation is enabled' do + it 'instruments a request' do + allow(middleware).to receive(:instrument?).and_return(true) + allow(middleware).to receive(:call_with_instrumentation) + + middleware.call({}) + end + end + + describe 'when instrumentation is disabled' do + it "doesn't instrument a request" do + allow(middleware).to receive(:instrument).and_return(false) + allow(app).to receive(:call) + + middleware.call({}) + end + end + end + + describe '#call_with_instrumentation' do + it 'instruments a request' do + trans = double(:transaction) + retval = 'cats are amazing' + env = {} + + allow(app).to receive(:call).with(env).and_return(retval) + allow(middleware).to receive(:transaction_from_env).and_return(trans) + allow(trans).to receive(:run).and_yield.and_return(retval) + allow(Gitlab::Sherlock.collection).to receive(:add).with(trans) + + middleware.call_with_instrumentation(env) + end + end + + describe '#instrument?' do + it 'returns false for a text/css request' do + env = { 'HTTP_ACCEPT' => 'text/css', 'REQUEST_URI' => '/' } + + expect(middleware.instrument?(env)).to eq(false) + end + + it 'returns false for a request to a Sherlock route' do + env = { + 'HTTP_ACCEPT' => 'text/html', + 'REQUEST_URI' => '/sherlock/transactions' + } + + expect(middleware.instrument?(env)).to eq(false) + end + + it 'returns true for a request that should be instrumented' do + env = { + 'HTTP_ACCEPT' => 'text/html', + 'REQUEST_URI' => '/cats' + } + + expect(middleware.instrument?(env)).to eq(true) + end + end + + describe '#transaction_from_env' do + it 'returns a Transaction' do + env = { + 'HTTP_ACCEPT' => 'text/html', + 'REQUEST_URI' => '/cats' + } + + expect(middleware.transaction_from_env(env)). + to be_an_instance_of(Gitlab::Sherlock::Transaction) + end + end +end diff --git a/spec/lib/gitlab/sherlock/query_spec.rb b/spec/lib/gitlab/sherlock/query_spec.rb new file mode 100644 index 00000000000..a9afef5dc1d --- /dev/null +++ b/spec/lib/gitlab/sherlock/query_spec.rb @@ -0,0 +1,113 @@ +require 'spec_helper' + +describe Gitlab::Sherlock::Query do + let(:started_at) { Time.utc(2015, 1, 1) } + let(:finished_at) { started_at + 5 } + + let(:query) do + described_class.new('SELECT COUNT(*) FROM users', started_at, finished_at) + end + + describe 'new_with_bindings' do + it 'returns a Query' do + sql = 'SELECT COUNT(*) FROM users WHERE id = $1' + bindings = [[double(:column), 10]] + + query = described_class. + new_with_bindings(sql, bindings, started_at, finished_at) + + expect(query.query).to eq('SELECT COUNT(*) FROM users WHERE id = 10;') + end + end + + describe '#id' do + it 'returns a String' do + expect(query.id).to be_an_instance_of(String) + end + end + + describe '#query' do + it 'returns the query with a trailing semi-colon' do + expect(query.query).to eq('SELECT COUNT(*) FROM users;') + end + end + + describe '#started_at' do + it 'returns the start time' do + expect(query.started_at).to eq(started_at) + end + end + + describe '#finished_at' do + it 'returns the completion time' do + expect(query.finished_at).to eq(finished_at) + end + end + + describe '#backtrace' do + it 'returns the backtrace' do + expect(query.backtrace).to be_an_instance_of(Array) + end + end + + describe '#duration' do + it 'returns the duration in milliseconds' do + expect(query.duration).to be_within(0.1).of(5000.0) + end + end + + describe '#to_param' do + it 'returns the query ID' do + expect(query.to_param).to eq(query.id) + end + end + + describe '#formatted_query' do + it 'returns a formatted version of the query' do + expect(query.formatted_query).to eq(<<-EOF.strip) +SELECT COUNT(*) +FROM users; + EOF + end + end + + describe '#last_application_frame' do + it 'returns the last application frame' do + frame = query.last_application_frame + + expect(frame).to be_an_instance_of(Gitlab::Sherlock::Location) + expect(frame.path).to eq(__FILE__) + end + end + + describe '#application_backtrace' do + it 'returns an Array of application frames' do + frames = query.application_backtrace + + expect(frames).to be_an_instance_of(Array) + expect(frames).to_not be_empty + + frames.each do |frame| + expect(frame.path).to start_with(Rails.root.to_s) + end + end + end + + describe '#explain' do + it 'returns the query plan as a String' do + lines = [ + ['Aggregate (cost=123 rows=1)'], + [' -> Index Only Scan using index_cats_are_amazing'] + ] + + result = double(:result, values: lines) + + allow(query).to receive(:raw_explain).and_return(result) + + expect(query.explain).to eq(<<-EOF.strip) +Aggregate (cost=123 rows=1) + -> Index Only Scan using index_cats_are_amazing + EOF + end + end +end diff --git a/spec/lib/gitlab/sherlock/transaction_spec.rb b/spec/lib/gitlab/sherlock/transaction_spec.rb new file mode 100644 index 00000000000..bb49fb65cf8 --- /dev/null +++ b/spec/lib/gitlab/sherlock/transaction_spec.rb @@ -0,0 +1,222 @@ +require 'spec_helper' + +describe Gitlab::Sherlock::Transaction do + let(:transaction) { described_class.new('POST', '/cat_pictures') } + + describe '#id' do + it 'returns the transaction ID' do + expect(transaction.id).to be_an_instance_of(String) + end + end + + describe '#type' do + it 'returns the type' do + expect(transaction.type).to eq('POST') + end + end + + describe '#path' do + it 'returns the path' do + expect(transaction.path).to eq('/cat_pictures') + end + end + + describe '#queries' do + it 'returns an Array of queries' do + expect(transaction.queries).to be_an_instance_of(Array) + end + end + + describe '#file_samples' do + it 'returns an Array of file samples' do + expect(transaction.file_samples).to be_an_instance_of(Array) + end + end + + describe '#started_at' do + it 'returns the start time' do + allow(transaction).to receive(:profile_lines).and_yield + + transaction.run { 'cats are amazing' } + + expect(transaction.started_at).to be_an_instance_of(Time) + end + end + + describe '#finished_at' do + it 'returns the completion time' do + allow(transaction).to receive(:profile_lines).and_yield + + transaction.run { 'cats are amazing' } + + expect(transaction.finished_at).to be_an_instance_of(Time) + end + end + + describe '#view_counts' do + it 'returns a Hash' do + expect(transaction.view_counts).to be_an_instance_of(Hash) + end + + it 'sets the default value of a key to 0' do + expect(transaction.view_counts['cats.rb']).to be_zero + end + end + + describe '#run' do + it 'runs the transaction' do + allow(transaction).to receive(:profile_lines).and_yield + + retval = transaction.run { 'cats are amazing' } + + expect(retval).to eq('cats are amazing') + end + end + + describe '#duration' do + it 'returns the duration in seconds' do + start_time = Time.now + + allow(transaction).to receive(:started_at).and_return(start_time) + allow(transaction).to receive(:finished_at).and_return(start_time + 5) + + expect(transaction.duration).to be_within(0.1).of(5.0) + end + end + + describe '#to_param' do + it 'returns the transaction ID' do + expect(transaction.to_param).to eq(transaction.id) + end + end + + describe '#sorted_queries' do + it 'returns the queries in descending order' do + start_time = Time.now + + query1 = Gitlab::Sherlock::Query.new('SELECT 1', start_time, start_time) + + query2 = Gitlab::Sherlock::Query. + new('SELECT 2', start_time, start_time + 5) + + transaction.queries << query1 + transaction.queries << query2 + + expect(transaction.sorted_queries).to eq([query2, query1]) + end + end + + describe '#sorted_file_samples' do + it 'returns the file samples in descending order' do + sample1 = Gitlab::Sherlock::FileSample.new(__FILE__, [], 10.0, 1) + sample2 = Gitlab::Sherlock::FileSample.new(__FILE__, [], 15.0, 1) + + transaction.file_samples << sample1 + transaction.file_samples << sample2 + + expect(transaction.sorted_file_samples).to eq([sample2, sample1]) + end + end + + describe '#find_query' do + it 'returns a Query when found' do + query = Gitlab::Sherlock::Query.new('SELECT 1', Time.now, Time.now) + + transaction.queries << query + + expect(transaction.find_query(query.id)).to eq(query) + end + + it 'returns nil when no query could be found' do + expect(transaction.find_query('cats')).to be_nil + end + end + + describe '#find_file_sample' do + it 'returns a FileSample when found' do + sample = Gitlab::Sherlock::FileSample.new(__FILE__, [], 10.0, 1) + + transaction.file_samples << sample + + expect(transaction.find_file_sample(sample.id)).to eq(sample) + end + + it 'returns nil when no file sample could be found' do + expect(transaction.find_file_sample('cats')).to be_nil + end + end + + describe '#profile_lines' do + describe 'when line profiling is enabled' do + it 'yields the block using the line profiler' do + allow(Gitlab::Sherlock).to receive(:enable_line_profiler?). + and_return(true) + + allow_any_instance_of(Gitlab::Sherlock::LineProfiler). + to receive(:profile).and_return('cats are amazing', []) + + retval = transaction.profile_lines { 'cats are amazing' } + + expect(retval).to eq('cats are amazing') + end + end + + describe 'when line profiling is disabled' do + it 'yields the block' do + allow(Gitlab::Sherlock).to receive(:enable_line_profiler?). + and_return(false) + + retval = transaction.profile_lines { 'cats are amazing' } + + expect(retval).to eq('cats are amazing') + end + end + end + + describe '#subscribe_to_active_record' do + let(:subscription) { transaction.subscribe_to_active_record } + let(:time) { Time.now } + let(:query_data) { { sql: 'SELECT 1', binds: [] } } + + after do + ActiveSupport::Notifications.unsubscribe(subscription) + end + + it 'tracks executed queries' do + expect(transaction).to receive(:track_query). + with('SELECT 1', [], time, time) + + subscription.publish('test', time, time, nil, query_data) + end + + it 'only tracks queries triggered from the transaction thread' do + expect(transaction).to_not receive(:track_query) + + Thread.new { subscription.publish('test', time, time, nil, query_data) }. + join + end + end + + describe '#subscribe_to_action_view' do + let(:subscription) { transaction.subscribe_to_action_view } + let(:time) { Time.now } + let(:view_data) { { identifier: 'foo.rb' } } + + after do + ActiveSupport::Notifications.unsubscribe(subscription) + end + + it 'tracks rendered views' do + expect(transaction).to receive(:track_view).with('foo.rb') + + subscription.publish('test', time, time, nil, view_data) + end + + it 'only tracks views rendered from the transaction thread' do + expect(transaction).to_not receive(:track_view) + + Thread.new { subscription.publish('test', time, time, nil, view_data) }. + join + end + end +end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index cb67ec95d57..47863d54579 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -468,7 +468,7 @@ describe Notify do subject { Notify.note_commit_email(recipient.id, note.id) } it_behaves_like 'a note email' - it_behaves_like 'an answer to an existing thread', 'commits' + it_behaves_like 'an answer to an existing thread', 'commit' it 'has the correct subject' do is_expected.to have_subject /#{commit.title} \(#{commit.short_id}\)/ diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index de0b2ef4cda..dfbac7b4004 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -23,16 +23,20 @@ # after_sign_out_path :string(255) # session_expire_delay :integer default(10080), not null # import_sources :text +# help_page_text :text +# admin_notification_email :string(255) +# shared_runners_enabled :boolean default(TRUE), not null +# max_artifacts_size :integer default(100), not null # require 'spec_helper' describe ApplicationSetting, models: true do - it { expect(ApplicationSetting.create_from_defaults).to be_valid } + let(:setting) { ApplicationSetting.create_from_defaults } - context 'restricted signup domains' do - let(:setting) { ApplicationSetting.create_from_defaults } + it { expect(setting).to be_valid } + context 'restricted signup domains' do it 'set single domain' do setting.restricted_signup_domains_raw = 'example.com' expect(setting.restricted_signup_domains).to eq(['example.com']) @@ -53,4 +57,26 @@ describe ApplicationSetting, models: true do expect(setting.restricted_signup_domains).to eq(['example.com', '*.example.com']) end end + + context 'shared runners' do + let(:gl_project) { create(:empty_project) } + + before do + allow_any_instance_of(Project).to receive(:current_application_settings).and_return(setting) + end + + subject { gl_project.ensure_gitlab_ci_project.shared_runners_enabled } + + context 'enabled' do + before { setting.update_attributes(shared_runners_enabled: true) } + + it { is_expected.to be_truthy } + end + + context 'disabled' do + before { setting.update_attributes(shared_runners_enabled: false) } + + it { is_expected.to be_falsey } + end + end end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 7f5abb83ac2..839b4c6b16e 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -400,4 +400,19 @@ describe Ci::Build do end end end + + describe :download_url do + subject { build.download_url } + + it "should be nil if artifact doesn't exist" do + build.update_attributes(artifacts_file: nil) + is_expected.to be_nil + end + + it 'should be nil if artifact exist' do + gif = fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') + build.update_attributes(artifacts_file: gif) + is_expected.to_not be_nil + end + end end diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index 44dbd083f06..a13f6458cac 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -1,18 +1,19 @@ # == Schema Information # -# Table name: commits +# Table name: ci_commits # -# id :integer not null, primary key -# project_id :integer -# ref :string(255) -# sha :string(255) -# before_sha :string(255) -# push_data :text -# created_at :datetime -# updated_at :datetime -# tag :boolean default(FALSE) -# yaml_errors :text -# committed_at :datetime +# id :integer not null, primary key +# project_id :integer +# ref :string(255) +# sha :string(255) +# before_sha :string(255) +# push_data :text +# created_at :datetime +# updated_at :datetime +# tag :boolean default(FALSE) +# yaml_errors :text +# committed_at :datetime +# gl_project_id :integer # require 'spec_helper' diff --git a/spec/models/ci/project_spec.rb b/spec/models/ci/project_spec.rb index 490c6a67982..ac7e38bbcb0 100644 --- a/spec/models/ci/project_spec.rb +++ b/spec/models/ci/project_spec.rb @@ -1,9 +1,9 @@ # == Schema Information # -# Table name: projects +# Table name: ci_projects # # id :integer not null, primary key -# name :string(255) not null +# name :string(255) # timeout :integer default(3600), not null # created_at :datetime # updated_at :datetime @@ -28,8 +28,8 @@ require 'spec_helper' describe Ci::Project do - let(:gl_project) { FactoryGirl.create :empty_project } - let(:project) { FactoryGirl.create :ci_project, gl_project: gl_project } + let(:project) { FactoryGirl.create :ci_project } + let(:gl_project) { project.gl_project } subject { project } it { is_expected.to have_many(:runner_projects) } @@ -194,18 +194,6 @@ describe Ci::Project do end end - describe 'Project.parse' do - let(:project) { FactoryGirl.create :project } - - subject { Ci::Project.parse(project) } - - it { is_expected.to be_valid } - it { is_expected.to be_kind_of(Ci::Project) } - it { expect(subject.name).to eq(project.name_with_namespace) } - it { expect(subject.gitlab_id).to eq(project.id) } - it { expect(subject.gitlab_url).to eq(project.web_url) } - end - describe :repo_url_with_auth do let(:project) { FactoryGirl.create :ci_project } subject { project.repo_url_with_auth } diff --git a/spec/models/ci/runner_project_spec.rb b/spec/models/ci/runner_project_spec.rb index 0218d484130..37682c6ea0c 100644 --- a/spec/models/ci/runner_project_spec.rb +++ b/spec/models/ci/runner_project_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: runner_projects +# Table name: ci_runner_projects # # id :integer not null, primary key # runner_id :integer not null diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index f8a51c29dc2..9a1233b9095 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: runners +# Table name: ci_runners # # id :integer not null, primary key # token :string(255) diff --git a/spec/models/ci/service_spec.rb b/spec/models/ci/service_spec.rb index 2df70e88888..36cda988eb4 100644 --- a/spec/models/ci/service_spec.rb +++ b/spec/models/ci/service_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: services +# Table name: ci_services # # id :integer not null, primary key # type :string(255) diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb index 19c14ef2da2..b8aa3c1e777 100644 --- a/spec/models/ci/trigger_spec.rb +++ b/spec/models/ci/trigger_spec.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: ci_triggers +# +# id :integer not null, primary key +# token :string(255) +# project_id :integer not null +# deleted_at :datetime +# created_at :datetime +# updated_at :datetime +# + require 'spec_helper' describe Ci::Trigger do diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index d034a6c7b9f..a515f5881ff 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: variables +# Table name: ci_variables # # id :integer not null, primary key # project_id :integer not null diff --git a/spec/models/ci/web_hook_spec.rb b/spec/models/ci/web_hook_spec.rb index bf9481ab81d..2865482a212 100644 --- a/spec/models/ci/web_hook_spec.rb +++ b/spec/models/ci/web_hook_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: web_hooks +# Table name: ci_web_hooks # # id :integer not null, primary key # url :string(255) not null diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index c96a606fdaa..dca0715eed8 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -1,3 +1,36 @@ +# == Schema Information +# +# Table name: ci_builds +# +# id :integer not null, primary key +# project_id :integer +# status :string(255) +# finished_at :datetime +# trace :text +# created_at :datetime +# updated_at :datetime +# started_at :datetime +# runner_id :integer +# coverage :float +# commit_id :integer +# commands :text +# job_id :integer +# name :string(255) +# deploy :boolean default(FALSE) +# options :text +# allow_failure :boolean default(FALSE), not null +# stage :string(255) +# trigger_request_id :integer +# stage_idx :integer +# tag :boolean +# ref :string(255) +# user_id :integer +# type :string(255) +# target_url :string(255) +# description :string(255) +# artifacts_file :text +# + require 'spec_helper' describe CommitStatus do diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index f442fa5fbe5..c86314c454c 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -1,3 +1,36 @@ +# == Schema Information +# +# Table name: ci_builds +# +# id :integer not null, primary key +# project_id :integer +# status :string(255) +# finished_at :datetime +# trace :text +# created_at :datetime +# updated_at :datetime +# started_at :datetime +# runner_id :integer +# coverage :float +# commit_id :integer +# commands :text +# job_id :integer +# name :string(255) +# deploy :boolean default(FALSE) +# options :text +# allow_failure :boolean default(FALSE), not null +# stage :string(255) +# trigger_request_id :integer +# stage_idx :integer +# tag :boolean +# ref :string(255) +# user_id :integer +# type :string(255) +# target_url :string(255) +# description :string(255) +# artifacts_file :text +# + require 'spec_helper' describe GenericCommitStatus do diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb new file mode 100644 index 00000000000..6eeff30b20e --- /dev/null +++ b/spec/models/global_milestone_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe GlobalMilestone do + let(:user) { create(:user) } + let(:user2) { create(:user) } + let(:group) { create(:group) } + let(:project1) { create(:project, group: group) } + let(:project2) { create(:project, path: 'gitlab-ci', group: group) } + let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) } + let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) } + let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) } + let(:milestone1_project3) { create(:milestone, title: "Milestone v1.2", project: project3) } + let(:milestone2_project1) { create(:milestone, title: "VD-123", project: project1) } + let(:milestone2_project2) { create(:milestone, title: "VD-123", project: project2) } + let(:milestone2_project3) { create(:milestone, title: "VD-123", project: project3) } + + describe :build_collection do + before do + milestones = + [ + milestone1_project1, + milestone1_project2, + milestone1_project3, + milestone2_project1, + milestone2_project2, + milestone2_project3 + ] + + @global_milestones = GlobalMilestone.build_collection(milestones) + end + + it 'should have all project milestones' do + expect(@global_milestones.count).to eq(2) + end + + it 'should have all project milestones titles' do + expect(@global_milestones.map(&:title)).to match_array(['Milestone v1.2', 'VD-123']) + end + + it 'should have all project milestones' do + expect(@global_milestones.map { |group_milestone| group_milestone.milestones.count }.sum).to eq(6) + end + end + + describe :initialize do + before do + milestones = + [ + milestone1_project1, + milestone1_project2, + milestone1_project3, + ] + + @global_milestone = GlobalMilestone.new(milestone1_project1.title, milestones) + end + + it 'should have exactly one group milestone' do + expect(@global_milestone.title).to eq('Milestone v1.2') + end + + it 'should have all project milestones with the same title' do + expect(@global_milestone.milestones.count).to eq(3) + end + end +end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 80638fc8db2..bbfc5535eec 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -11,6 +11,7 @@ # type :string(255) # description :string(255) default(""), not null # avatar :string(255) +# public :boolean default(FALSE) # require 'spec_helper' @@ -84,4 +85,23 @@ describe Group do expect(group.avatar_type).to eq(["only images allowed"]) end end + + describe "public_profile?" do + it "returns true for public group" do + group = create(:group, public: true) + expect(group.public_profile?).to be_truthy + end + + it "returns true for non-public group with public project" do + group = create(:group) + create(:project, :public, group: group) + expect(group.public_profile?).to be_truthy + end + + it "returns false for non-public group with no public projects" do + group = create(:group) + create(:project, group: group) + expect(group.public_profile?).to be_falsy + end + end end diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 6518213d71c..511ee8cbd96 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -8,6 +8,7 @@ # project_id :integer # created_at :datetime # updated_at :datetime +# template :boolean default(FALSE) # require 'spec_helper' diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index e42534a8491..1bd09a1b0fb 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -20,6 +20,7 @@ # position :integer default(0) # locked_at :datetime # updated_by_id :integer +# merge_error :string(255) # require 'spec_helper' diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 1d72a9503ae..a98b9cb7321 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -11,6 +11,7 @@ # type :string(255) # description :string(255) default(""), not null # avatar :string(255) +# public :boolean default(FALSE) # require 'spec_helper' diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb index 842089ebe0d..b9006b693b2 100644 --- a/spec/models/project_services/gitlab_ci_service_spec.rb +++ b/spec/models/project_services/gitlab_ci_service_spec.rb @@ -39,7 +39,7 @@ describe GitlabCiService do end describe :build_page do - it { expect(@service.build_page("2ab7834c", 'master')).to eq("http://localhost/#{@ci_project.gl_project.path_with_namespace}/commit/2ab7834c/ci")} + it { expect(@service.build_page("2ab7834c", 'master')).to eq("http://localhost/#{@ci_project.gl_project.path_with_namespace}/commit/2ab7834c/builds")} end describe "execute" do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index f93935ebe3b..8d7e6e76766 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -415,12 +415,15 @@ describe Project do it { expect(project.ci_commit(commit.sha)).to eq(commit) } end - describe :enable_ci do + describe :builds_enabled do let(:project) { create :project } - before { project.enable_ci } + before { project.builds_enabled = true } - it { expect(project.gitlab_ci?).to be_truthy } + subject { project.builds_enabled } + + it { is_expected.to eq(project.gitlab_ci_service.active) } + it { expect(project.builds_enabled?).to be_truthy } it { expect(project.gitlab_ci_project).to be_a(Ci::Project) } end diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 94802dcfb79..3b889144447 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -184,6 +184,12 @@ describe ProjectWiki do subject.create_page("test page", "some content", :markdown, "commit message") expect(subject.pages.first.page.version.message).to eq("commit message") end + + it 'updates project activity' do + expect(subject).to receive(:update_project_activity) + + subject.create_page('Test Page', 'This is content') + end end describe "#update_page" do @@ -205,6 +211,12 @@ describe ProjectWiki do it "sets the correct commit message" do expect(@page.version.message).to eq("updated page") end + + it 'updates project activity' do + expect(subject).to receive(:update_project_activity) + + subject.update_page(@gollum_page, 'Yet more content', :markdown, 'Updated page again') + end end describe "#delete_page" do @@ -217,13 +229,19 @@ describe ProjectWiki do subject.delete_page(@page) expect(subject.pages.count).to eq(0) end + + it 'updates project activity' do + expect(subject).to receive(:update_project_activity) + + subject.delete_page(@page) + end end private def create_temp_repo(path) FileUtils.mkdir_p path - system(*%W(git init --quiet --bare -- #{path})) + system(*%W(#{Gitlab.config.git.bin_path} init --quiet --bare -- #{path})) end def remove_temp_repo(path) diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb new file mode 100644 index 00000000000..72ecb442a36 --- /dev/null +++ b/spec/models/release_spec.rb @@ -0,0 +1,28 @@ +# == Schema Information +# +# Table name: releases +# +# id :integer not null, primary key +# tag :string(255) +# description :text +# project_id :integer +# created_at :datetime +# updated_at :datetime +# + +require 'rails_helper' + +RSpec.describe Release, type: :model do + let(:release) { create(:release) } + + it { expect(release).to be_valid } + + describe 'associations' do + it { is_expected.to belong_to(:project) } + end + + describe 'validation' do + it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:description) } + end +end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 05e51532eb8..319fa0a7c8d 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -26,6 +26,15 @@ describe Repository do it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') } end + describe :find_commits_by_message do + subject { repository.find_commits_by_message('submodule').map{ |k| k.id } } + + it { is_expected.to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } + it { is_expected.to include('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } + it { is_expected.to include('cfe32cf61b73a0d5e9f13e774abde7ff789b1660') } + it { is_expected.not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e') } + end + describe :blob_at do context 'blank sha' do subject { repository.blob_at(Gitlab::Git::BLANK_SHA, '.gitignore') } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index c71cfb3ebe3..7d716c23120 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -54,6 +54,8 @@ # public_email :string(255) default(""), not null # dashboard :integer default(0) # project_view :integer default(0) +# consumed_timestep :integer +# layout :integer default(0) # require 'spec_helper' @@ -663,24 +665,24 @@ describe User do @user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega' end - it "sorts users as recently_signed_in" do + it "sorts users by the recent sign-in time" do expect(User.sort('recent_sign_in').first).to eq(@user) end - it "sorts users as late_signed_in" do + it "sorts users by the oldest sign-in time" do expect(User.sort('oldest_sign_in').first).to eq(@user1) end - it "sorts users as recently_created" do + it "sorts users in descending order by their creation time" do expect(User.sort('created_desc').first).to eq(@user) end - it "sorts users as late_created" do + it "sorts users in ascending order by their creation time" do expect(User.sort('created_asc').first).to eq(@user1) end - it "sorts users by name when nil is passed" do - expect(User.sort(nil).first).to eq(@user) + it "sorts users by id in descending order when nil is passed" do + expect(User.sort(nil).first).to eq(@user1) end end diff --git a/spec/requests/api/commit_status_spec.rb b/spec/requests/api/commit_status_spec.rb index b9e6dfc15a7..a28607bd240 100644 --- a/spec/requests/api/commit_status_spec.rb +++ b/spec/requests/api/commit_status_spec.rb @@ -18,7 +18,7 @@ describe API::API, api: true do before do @status1 = create(:commit_status, commit: ci_commit, status: 'running') @status2 = create(:commit_status, commit: ci_commit, name: 'coverage', status: 'pending') - @status3 = create(:commit_status, commit: ci_commit, name: 'coverage', ref: 'develop', status: 'running') + @status3 = create(:commit_status, commit: ci_commit, name: 'coverage', ref: 'develop', status: 'running', allow_failure: true) @status4 = create(:commit_status, commit: ci_commit, name: 'coverage', status: 'success') @status5 = create(:commit_status, commit: ci_commit, ref: 'develop', status: 'success') @status6 = create(:commit_status, commit: ci_commit, status: 'success') @@ -30,6 +30,8 @@ describe API::API, api: true do expect(json_response).to be_an Array expect(statuses_id).to contain_exactly(@status3.id, @status4.id, @status5.id, @status6.id) + json_response.sort_by!{ |status| status['id'] } + expect(json_response.map{ |status| status['allow_failure'] }).to eq([true, false, false, false]) end it "should return all commit statuses" do diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 042e6352567..8efa09f75fd 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -19,6 +19,7 @@ describe API::API, api: true do expect(response.status).to eq(200) expect(json_response['file_path']).to eq(file_path) expect(json_response['file_name']).to eq('popen.rb') + expect(json_response['last_commit_id']).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') expect(Base64.decode64(json_response['content']).lines.first).to eq("require 'fileutils'\n") end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index e9de9e0826d..9fc294118ae 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -88,8 +88,11 @@ describe API::API, api: true do end it 'returns projects in the correct order when ci_enabled_first parameter is passed' do - [project, project2, project3].each{ |project| project.build_missing_services } - project2.gitlab_ci_service.update(active: true) + [project, project2, project3].each do |project| + project.builds_enabled = false + project.build_missing_services + end + project2.builds_enabled = true get api('/projects', user), { ci_enabled_first: 'true' } expect(response.status).to eq(200) expect(json_response).to be_an Array diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 1149f7e7989..4911cdd9da6 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -11,81 +11,6 @@ describe API::API, api: true do let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } - describe "GET /projects/:id/repository/tags" do - it "should return an array of project tags" do - get api("/projects/#{project.id}/repository/tags", user) - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(project.repo.tags.sort_by(&:name).reverse.first.name) - end - end - - describe 'POST /projects/:id/repository/tags' do - context 'lightweight tags' do - it 'should create a new tag' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v7.0.1', - ref: 'master' - - expect(response.status).to eq(201) - expect(json_response['name']).to eq('v7.0.1') - end - end - - context 'annotated tag' do - it 'should create a new annotated tag' do - # Identity must be set in .gitconfig to create annotated tag. - repo_path = project.repository.path_to_repo - system(*%W(git --git-dir=#{repo_path} config user.name #{user.name})) - system(*%W(git --git-dir=#{repo_path} config user.email #{user.email})) - - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v7.1.0', - ref: 'master', - message: 'Release 7.1.0' - - expect(response.status).to eq(201) - expect(json_response['name']).to eq('v7.1.0') - expect(json_response['message']).to eq('Release 7.1.0') - end - end - - it 'should deny for user without push access' do - post api("/projects/#{project.id}/repository/tags", user2), - tag_name: 'v1.9.0', - ref: '621491c677087aa243f165eab467bfdfbee00be1' - expect(response.status).to eq(403) - end - - it 'should return 400 if tag name is invalid' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v 1.0.0', - ref: 'master' - expect(response.status).to eq(400) - expect(json_response['message']).to eq('Tag name invalid') - end - - it 'should return 400 if tag already exists' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v8.0.0', - ref: 'master' - expect(response.status).to eq(201) - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v8.0.0', - ref: 'master' - expect(response.status).to eq(400) - expect(json_response['message']).to eq('Tag already exists') - end - - it 'should return 400 if ref name is invalid' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'mytag', - ref: 'foo' - expect(response.status).to eq(400) - expect(json_response['message']).to eq('Invalid reference name') - end - end - describe "GET /projects/:id/repository/tree" do context "authorized user" do before { project.team << [user2, :reporter] } diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index c0226605a23..b180d2fec77 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -46,6 +46,7 @@ describe API::API, api: true do delete api("/projects/#{project.id}/services/#{dashed_service}", user) expect(response.status).to eq(200) + project.send(service_method).reload expect(project.send(service_method).activated?).to be_falsey end end diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb new file mode 100644 index 00000000000..cc9a5f47582 --- /dev/null +++ b/spec/requests/api/tags_spec.rb @@ -0,0 +1,135 @@ +require 'spec_helper' +require 'mime/types' + +describe API::API, api: true do + include ApiHelpers + include RepoHelpers + + let(:user) { create(:user) } + let(:user2) { create(:user) } + let!(:project) { create(:project, creator_id: user.id) } + let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } + let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } + + describe "GET /projects/:id/repository/tags" do + let(:tag_name) { project.repository.tag_names.sort.reverse.first } + let(:description) { 'Awesome release!' } + + context 'without releases' do + it "should return an array of project tags" do + get api("/projects/#{project.id}/repository/tags", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq(tag_name) + end + end + + context 'with releases' do + before do + release = project.releases.find_or_initialize_by(tag: tag_name) + release.update_attributes(description: description) + get api("/projects/#{project.id}/repository/tags", user) + end + + it "should return an array of project tags with release info" do + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq(tag_name) + expect(json_response.first['release']['description']).to eq(description) + end + end + end + + describe 'POST /projects/:id/repository/tags' do + context 'lightweight tags' do + it 'should create a new tag' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v7.0.1', + ref: 'master' + + expect(response.status).to eq(201) + expect(json_response['name']).to eq('v7.0.1') + end + end + + context 'lightweight tags with release notes' do + it 'should create a new tag' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v7.0.1', + ref: 'master', + release_description: 'Wow' + + expect(response.status).to eq(201) + expect(json_response['name']).to eq('v7.0.1') + expect(json_response['release']['description']).to eq('Wow') + end + end + + context 'annotated tag' do + it 'should create a new annotated tag' do + # Identity must be set in .gitconfig to create annotated tag. + repo_path = project.repository.path_to_repo + system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.name #{user.name})) + system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.email #{user.email})) + + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v7.1.0', + ref: 'master', + message: 'Release 7.1.0' + + expect(response.status).to eq(201) + expect(json_response['name']).to eq('v7.1.0') + expect(json_response['message']).to eq('Release 7.1.0') + end + end + + it 'should deny for user without push access' do + post api("/projects/#{project.id}/repository/tags", user2), + tag_name: 'v1.9.0', + ref: '621491c677087aa243f165eab467bfdfbee00be1' + expect(response.status).to eq(403) + end + + it 'should return 400 if tag name is invalid' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v 1.0.0', + ref: 'master' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Tag name invalid') + end + + it 'should return 400 if tag already exists' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v8.0.0', + ref: 'master' + expect(response.status).to eq(201) + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v8.0.0', + ref: 'master' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Tag already exists') + end + + it 'should return 400 if ref name is invalid' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'mytag', + ref: 'foo' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Invalid reference name') + end + end + + describe 'PUT /projects/:id/repository/:tag/release' do + let(:tag_name) { project.repository.tag_names.first } + let(:description) { 'Awesome release!' } + + it 'should create description for existing git tag' do + put api("/projects/#{project.id}/repository/#{tag_name}/release", user), + description: description + + expect(response.status).to eq(200) + expect(json_response['tag']).to eq(tag_name) + expect(json_response['description']).to eq(description) + end + end +end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index d26a300ed82..a9ef2fe5885 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -343,8 +343,9 @@ describe API::API, api: true do end.to change{ user.keys.count }.by(1) end - it "should raise error for invalid ID" do - expect{post api("/users/ASDF/keys", admin) }.to raise_error(ActionController::RoutingError) + it "should return 405 for invalid ID" do + post api("/users/ASDF/keys", admin) + expect(response.status).to eq(405) end end @@ -374,9 +375,9 @@ describe API::API, api: true do expect(json_response.first['title']).to eq(key.title) end - it "should return 404 for invalid ID" do + it "should return 405 for invalid ID" do get api("/users/ASDF/keys", admin) - expect(response.status).to eq(404) + expect(response.status).to eq(405) end end end @@ -434,7 +435,8 @@ describe API::API, api: true do end it "should raise error for invalid ID" do - expect{post api("/users/ASDF/emails", admin) }.to raise_error(ActionController::RoutingError) + post api("/users/ASDF/emails", admin) + expect(response.status).to eq(405) end end @@ -465,7 +467,8 @@ describe API::API, api: true do end it "should raise error for invalid ID" do - expect{put api("/users/ASDF/emails", admin) }.to raise_error(ActionController::RoutingError) + put api("/users/ASDF/emails", admin) + expect(response.status).to eq(405) end end end diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 88218a93e1f..c2be045099d 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -5,7 +5,7 @@ describe Ci::API::API do let(:runner) { FactoryGirl.create(:ci_runner, tag_list: ["mysql", "ruby"]) } let(:project) { FactoryGirl.create(:ci_project) } - let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) } + let(:gl_project) { project.gl_project } before do stub_ci_commit_to_return_yaml_file @@ -14,7 +14,7 @@ describe Ci::API::API do describe "Builds API for runners" do let(:shared_runner) { FactoryGirl.create(:ci_runner, token: "SharedRunner") } let(:shared_project) { FactoryGirl.create(:ci_project, name: "SharedProject") } - let(:shared_gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: shared_project) } + let(:shared_gl_project) { shared_project.gl_project } before do FactoryGirl.create :ci_runner_project, project_id: project.id, runner_id: runner.id @@ -41,7 +41,7 @@ describe Ci::API::API do it "should return 404 error if no builds for specific runner" do commit = FactoryGirl.create(:ci_commit, gl_project: shared_gl_project) - FactoryGirl.create(:ci_build, commit: commit, status: 'pending' ) + FactoryGirl.create(:ci_build, commit: commit, status: 'pending') post ci_api("/builds/register"), token: runner.token @@ -50,7 +50,7 @@ describe Ci::API::API do it "should return 404 error if no builds for shared runner" do commit = FactoryGirl.create(:ci_commit, gl_project: gl_project) - FactoryGirl.create(:ci_build, commit: commit, status: 'pending' ) + FactoryGirl.create(:ci_build, commit: commit, status: 'pending') post ci_api("/builds/register"), token: shared_runner.token @@ -79,7 +79,7 @@ describe Ci::API::API do { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true }, { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true }, { "key" => "DB_NAME", "value" => "postgres", "public" => true }, - { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false }, + { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false } ]) end @@ -122,5 +122,191 @@ describe Ci::API::API do expect(build.reload.trace).to eq 'hello_world' end end + + context "Artifacts" do + let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } + let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') } + let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) } + let(:build) { FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) } + let(:authorize_url) { ci_api("/builds/#{build.id}/artifacts/authorize") } + let(:post_url) { ci_api("/builds/#{build.id}/artifacts") } + let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") } + let(:get_url) { ci_api("/builds/#{build.id}/artifacts") } + let(:headers) { { "GitLab-Workhorse" => "1.0" } } + let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.project.token) } + + describe "POST /builds/:id/artifacts/authorize" do + context "should authorize posting artifact to running build" do + before do + build.run! + end + + it "using token as parameter" do + post authorize_url, { token: build.project.token }, headers + expect(response.status).to eq(200) + expect(json_response["TempPath"]).to_not be_nil + end + + it "using token as header" do + post authorize_url, {}, headers_with_token + expect(response.status).to eq(200) + expect(json_response["TempPath"]).to_not be_nil + end + end + + context "should fail to post too large artifact" do + before do + build.run! + end + + it "using token as parameter" do + stub_application_setting(max_artifacts_size: 0) + post authorize_url, { token: build.project.token, filesize: 100 }, headers + expect(response.status).to eq(413) + end + + it "using token as header" do + stub_application_setting(max_artifacts_size: 0) + post authorize_url, { filesize: 100 }, headers_with_token + expect(response.status).to eq(413) + end + end + + context "should get denied" do + it do + post authorize_url, { token: 'invalid', filesize: 100 } + expect(response.status).to eq(403) + end + end + end + + describe "POST /builds/:id/artifacts" do + context "Disable sanitizer" do + before do + # by configuring this path we allow to pass temp file from any path + allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return('/') + end + + context "should post artifact to running build" do + before do + build.run! + end + + it "uses regual file post" do + upload_artifacts(file_upload, headers_with_token, false) + expect(response.status).to eq(201) + expect(json_response["artifacts_file"]["filename"]).to eq(file_upload.original_filename) + end + + it "uses accelerated file post" do + upload_artifacts(file_upload, headers_with_token, true) + expect(response.status).to eq(201) + expect(json_response["artifacts_file"]["filename"]).to eq(file_upload.original_filename) + end + + it "updates artifact" do + upload_artifacts(file_upload, headers_with_token) + upload_artifacts(file_upload2, headers_with_token) + expect(response.status).to eq(201) + expect(json_response["artifacts_file"]["filename"]).to eq(file_upload2.original_filename) + end + end + + context "should fail to post too large artifact" do + before do + build.run! + end + + it do + stub_application_setting(max_artifacts_size: 0) + upload_artifacts(file_upload, headers_with_token) + expect(response.status).to eq(413) + end + end + + context "should fail to post artifacts without file" do + before do + build.run! + end + + it do + post post_url, {}, headers_with_token + expect(response.status).to eq(400) + end + end + + context "should fail to post artifacts without GitLab-Workhorse" do + before do + build.run! + end + + it do + post post_url, { token: build.project.token }, {} + expect(response.status).to eq(403) + end + end + end + + context "should fail to post artifacts for outside of tmp path" do + before do + # by configuring this path we allow to pass file from @tmpdir only + # but all temporary files are stored in system tmp directory + @tmpdir = Dir.mktmpdir + allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return(@tmpdir) + build.run! + end + + after do + FileUtils.remove_entry @tmpdir + end + + it do + upload_artifacts(file_upload, headers_with_token) + expect(response.status).to eq(400) + end + end + + def upload_artifacts(file, headers = {}, accelerated = true) + if accelerated + post post_url, { + 'file.path' => file.path, + 'file.name' => file.original_filename + }, headers + else + post post_url, { file: file }, headers + end + end + end + + describe "DELETE /builds/:id/artifacts" do + before do + build.run! + post delete_url, token: build.project.token, file: file_upload + end + + it "should delete artifact build" do + build.success + delete delete_url, token: build.project.token + expect(response.status).to eq(200) + end + end + + describe "GET /builds/:id/artifacts" do + before do + build.run! + end + + it "should download artifact" do + build.update_attributes(artifacts_file: file_upload) + get get_url, token: build.project.token + expect(response.status).to eq(200) + end + + it "should fail to download if no artifact uploaded" do + get get_url, token: build.project.token + expect(response.status).to eq(404) + end + end + end end end diff --git a/spec/requests/ci/api/commits_spec.rb b/spec/requests/ci/api/commits_spec.rb index 6049135fd10..aa51ba95bca 100644 --- a/spec/requests/ci/api/commits_spec.rb +++ b/spec/requests/ci/api/commits_spec.rb @@ -4,7 +4,7 @@ describe Ci::API::API, 'Commits' do include ApiHelpers let(:project) { FactoryGirl.create(:ci_project) } - let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) } + let(:gl_project) { project.gl_project } let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) } let(:options) do diff --git a/spec/requests/ci/api/projects_spec.rb b/spec/requests/ci/api/projects_spec.rb index 53f7f91cc1f..893fd168d3e 100644 --- a/spec/requests/ci/api/projects_spec.rb +++ b/spec/requests/ci/api/projects_spec.rb @@ -41,8 +41,8 @@ describe Ci::API::API do describe "GET /projects/owned" do let!(:gl_project1) {FactoryGirl.create(:empty_project, namespace: user.namespace)} let!(:gl_project2) {FactoryGirl.create(:empty_project, namespace: user.namespace)} - let!(:project1) { FactoryGirl.create(:ci_project, gl_project: gl_project1) } - let!(:project2) { FactoryGirl.create(:ci_project, gl_project: gl_project2) } + let!(:project1) { gl_project1.ensure_gitlab_ci_project } + let!(:project2) { gl_project2.ensure_gitlab_ci_project } before do project1.gl_project.team << [user, :developer] @@ -180,87 +180,53 @@ describe Ci::API::API do end end - describe "POST /projects" do - let(:gl_project) { FactoryGirl.create :empty_project } - let(:project_info) do - { - gitlab_id: gl_project.id - } - end - - let(:invalid_project_info) { {} } + describe "POST /projects/:id/runners/:id" do + let(:project) { FactoryGirl.create(:ci_project) } + let(:runner) { FactoryGirl.create(:ci_runner) } - context "with valid project info" do - before do - options.merge!(project_info) - end + it "should add the project to the runner" do + project.gl_project.team << [user, :master] + post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + expect(response.status).to eq(201) - it "should create a project with valid data" do - post ci_api("/projects"), options - expect(response.status).to eq(201) - expect(json_response['name']).to eq(gl_project.name_with_namespace) - end + project.reload + expect(project.runners.first.id).to eq(runner.id) end - context "with invalid project info" do - before do - options.merge!(invalid_project_info) - end + it "should fail if it tries to link a non-existing project or runner" do + post ci_api("/projects/#{project.id}/runners/non-existing"), options + expect(response.status).to eq(404) - it "should error with invalid data" do - post ci_api("/projects"), options - expect(response.status).to eq(400) - end + post ci_api("/projects/non-existing/runners/#{runner.id}"), options + expect(response.status).to eq(404) end - describe "POST /projects/:id/runners/:id" do - let(:project) { FactoryGirl.create(:ci_project) } - let(:runner) { FactoryGirl.create(:ci_runner) } - - it "should add the project to the runner" do - project.gl_project.team << [user, :master] - post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(response.status).to eq(201) - - project.reload - expect(project.runners.first.id).to eq(runner.id) - end - - it "should fail if it tries to link a non-existing project or runner" do - post ci_api("/projects/#{project.id}/runners/non-existing"), options - expect(response.status).to eq(404) - - post ci_api("/projects/non-existing/runners/#{runner.id}"), options - expect(response.status).to eq(404) - end - - it "non-manager is not authorized" do - allow_any_instance_of(User).to receive(:can_manage_project?).and_return(false) - post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(response.status).to eq(401) - end + it "non-manager is not authorized" do + allow_any_instance_of(User).to receive(:can_manage_project?).and_return(false) + post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + expect(response.status).to eq(401) end + end - describe "DELETE /projects/:id/runners/:id" do - let(:project) { FactoryGirl.create(:ci_project) } - let(:runner) { FactoryGirl.create(:ci_runner) } + describe "DELETE /projects/:id/runners/:id" do + let(:project) { FactoryGirl.create(:ci_project) } + let(:runner) { FactoryGirl.create(:ci_runner) } - it "should remove the project from the runner" do - project.gl_project.team << [user, :master] - post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + it "should remove the project from the runner" do + project.gl_project.team << [user, :master] + post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(project.runners).to be_present - delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(response.status).to eq(200) + expect(project.runners).to be_present + delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + expect(response.status).to eq(200) - project.reload - expect(project.runners).to be_empty - end + project.reload + expect(project.runners).to be_empty + end - it "non-manager is not authorized" do - delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(response.status).to eq(401) - end + it "non-manager is not authorized" do + delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + expect(response.status).to eq(401) end end end diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb index 93617fc4b3f..a2b436d5811 100644 --- a/spec/requests/ci/api/triggers_spec.rb +++ b/spec/requests/ci/api/triggers_spec.rb @@ -6,7 +6,7 @@ describe Ci::API::API do describe 'POST /projects/:project_id/refs/:ref/trigger' do let!(:trigger_token) { 'secure token' } let!(:gl_project) { FactoryGirl.create(:project) } - let!(:project) { FactoryGirl.create(:ci_project, gl_project: gl_project) } + let!(:project) { gl_project.ensure_gitlab_ci_project } let!(:project2) { FactoryGirl.create(:ci_project) } let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) } let(:options) do diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb index fcafae38644..2ef4bb50a57 100644 --- a/spec/services/ci/create_trigger_request_service_spec.rb +++ b/spec/services/ci/create_trigger_request_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Ci::CreateTriggerRequestService do let(:service) { Ci::CreateTriggerRequestService.new } let(:gl_project) { create(:project) } - let(:project) { create(:ci_project, gl_project: gl_project) } + let(:project) { gl_project.ensure_gitlab_ci_project } let(:trigger) { create(:ci_trigger, project: project) } before do diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb index 781764627ac..b370dfbe113 100644 --- a/spec/services/ci/register_build_service_spec.rb +++ b/spec/services/ci/register_build_service_spec.rb @@ -70,6 +70,10 @@ module Ci end context 'disallow shared runners' do + before do + gl_project.gitlab_ci_project.update(shared_runners_enabled: false) + end + context 'shared runner' do let(:build) { service.execute(shared_runner) } diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index a91be3b4472..f55527ee9a3 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -3,13 +3,15 @@ require 'spec_helper' describe Issues::UpdateService do let(:user) { create(:user) } let(:user2) { create(:user) } - let(:issue) { create(:issue, title: 'Old title') } + let(:user3) { create(:user) } + let(:issue) { create(:issue, title: 'Old title', assignee_id: user3.id) } let(:label) { create(:label) } let(:project) { issue.project } before do project.team << [user, :master] project.team << [user2, :developer] + project.team << [user3, :developer] end describe 'execute' do @@ -34,9 +36,11 @@ describe Issues::UpdateService do it { expect(@issue.labels.count).to eq(1) } it { expect(@issue.labels.first.title).to eq('Bug') } - it 'should send email to user2 about assign of new issue' do - email = ActionMailer::Base.deliveries.last - expect(email.to.first).to eq(user2.email) + it 'should send email to user2 about assign of new issue and email to user3 about issue unassignment' do + deliveries = ActionMailer::Base.deliveries + email = deliveries.last + recipients = deliveries.last(2).map(&:to).flatten + expect(recipients).to include(user2.email, user3.email) expect(email.subject).to include(issue.title) end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 227ac995ec2..7ee4488521d 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -62,6 +62,25 @@ describe MergeRequests::RefreshService do it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') } end + context 'manual merge of source branch' do + before do + # Merge master -> feature branch + author = { email: 'test@gitlab.com', time: Time.now, name: "Me" } + commit_options = { message: 'Test message', committer: author, author: author } + master_commit = @project.repository.commit('master') + @project.repository.merge(@user, master_commit.id, 'feature', commit_options) + commit = @project.repository.commit('feature') + service.new(@project, @user).execute(@oldrev, commit.id, 'refs/heads/feature') + reload_mrs + end + + it { expect(@merge_request.notes.last.note).to include('changed to merged') } + it { expect(@merge_request).to be_merged } + it { expect(@merge_request.diffs.length).to be > 0 } + it { expect(@fork_merge_request).to be_merged } + it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') } + end + context 'push to fork repo source branch' do let(:refresh_service) { service.new(@fork_project, @user) } before do diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index c75173c1452..2ed51d223b7 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' describe MergeRequests::UpdateService do let(:user) { create(:user) } let(:user2) { create(:user) } - let(:merge_request) { create(:merge_request, :simple, title: 'Old title') } + let(:user3) { create(:user) } + let(:merge_request) { create(:merge_request, :simple, title: 'Old title', assignee_id: user3.id) } let(:project) { merge_request.project } let(:label) { create(:label) } @@ -47,9 +48,11 @@ describe MergeRequests::UpdateService do with(@merge_request, 'update') end - it 'should send email to user2 about assign of new merge_request' do - email = ActionMailer::Base.deliveries.last - expect(email.to.first).to eq(user2.email) + it 'should send email to user2 about assign of new merge request and email to user3 about merge request unassignment' do + deliveries = ActionMailer::Base.deliveries + email = deliveries.last + recipients = deliveries.last(2).map(&:to).flatten + expect(recipients).to include(user2.email, user3.email) expect(email.subject).to include(merge_request.title) end diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb new file mode 100644 index 00000000000..034c0f22e12 --- /dev/null +++ b/spec/services/milestones/close_service_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Milestones::CloseService do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:milestone) { create(:milestone, title: "Milestone v1.2", project: project) } + + before do + project.team << [user, :master] + end + + describe :execute do + before do + Milestones::CloseService.new(project, user, {}).execute(milestone) + end + + it { expect(milestone).to be_valid } + it { expect(milestone).to be_closed } + + describe :event do + let(:event) { Event.first } + + it { expect(event.milestone).to be_truthy } + it { expect(event.target).to eq(milestone) } + it { expect(event.action_name).to eq('closed') } + end + end +end diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb new file mode 100644 index 00000000000..757c9a226d8 --- /dev/null +++ b/spec/services/milestones/create_service_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Milestones::CreateService do + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + + describe :execute do + context "valid params" do + before do + project.team << [user, :master] + + opts = { + title: 'v2.1.9', + description: 'Patch release to fix security issue' + } + + @milestone = Milestones::CreateService.new(project, user, opts).execute + end + + it { expect(@milestone).to be_valid } + it { expect(@milestone.title).to eq('v2.1.9') } + end + end +end diff --git a/spec/services/milestones/group_service_spec.rb b/spec/services/milestones/group_service_spec.rb deleted file mode 100644 index 74eb0f99e0f..00000000000 --- a/spec/services/milestones/group_service_spec.rb +++ /dev/null @@ -1,70 +0,0 @@ -require 'spec_helper' - -describe Milestones::GroupService do - let(:user) { create(:user) } - let(:user2) { create(:user) } - let(:group) { create(:group) } - let(:project1) { create(:project, group: group) } - let(:project2) { create(:project, path: 'gitlab-ci', group: group) } - let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) } - let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) } - let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) } - let(:milestone1_project3) { create(:milestone, title: "Milestone v1.2", project: project3) } - let(:milestone2_project1) { create(:milestone, title: "VD-123", project: project1) } - let(:milestone2_project2) { create(:milestone, title: "VD-123", project: project2) } - let(:milestone2_project3) { create(:milestone, title: "VD-123", project: project3) } - - describe 'execute' do - context 'with valid projects' do - before do - milestones = - [ - milestone1_project1, - milestone1_project2, - milestone1_project3, - milestone2_project1, - milestone2_project2, - milestone2_project3 - ] - @group_milestones = Milestones::GroupService.new(milestones).execute - end - - it 'should have all project milestones' do - expect(@group_milestones.count).to eq(2) - end - - it 'should have all project milestones titles' do - expect(@group_milestones.map { |group_milestone| group_milestone.title }).to match_array(['Milestone v1.2', 'VD-123']) - end - - it 'should have all project milestones' do - expect(@group_milestones.map { |group_milestone| group_milestone.milestones.count }.sum).to eq(6) - end - end - end - - describe 'milestone' do - context 'with valid title' do - before do - milestones = - [ - milestone1_project1, - milestone1_project2, - milestone1_project3, - milestone2_project1, - milestone2_project2, - milestone2_project3 - ] - @group_milestones = Milestones::GroupService.new(milestones).milestone('Milestone v1.2') - end - - it 'should have exactly one group milestone' do - expect(@group_milestones.title).to eq('Milestone v1.2') - end - - it 'should have all project milestones with the same title' do - expect(@group_milestones.milestones.count).to eq(3) - end - end - end -end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 25277f07482..e81c4edb7d8 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -70,6 +70,28 @@ describe Projects::CreateService do end end + context 'builds_enabled global setting' do + let(:project) { create_project(@user, @opts) } + + subject { project.builds_enabled? } + + context 'global builds_enabled false does not enable CI by default' do + before do + @opts.merge!(builds_enabled: false) + end + + it { is_expected.to be_falsey } + end + + context 'global builds_enabled true does enable CI by default' do + before do + @opts.merge!(builds_enabled: true) + end + + it { is_expected.to be_truthy } + end + end + context 'restricted visibility level' do before do stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 65a8c81204d..e397b2b9b4a 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -46,7 +46,7 @@ describe Projects::ForkService do it "fork and enable CI for fork" do @from_project.enable_ci @to_project = fork_project(@from_project, @to_user) - expect(@to_project.gitlab_ci?).to be_truthy + expect(@to_project.builds_enabled?).to be_truthy end end end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index d12ba25b71b..787670e9297 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -96,15 +96,15 @@ module TestEnv clone_url = "https://gitlab.com/gitlab-org/#{repo_name}.git" unless File.directory?(repo_path) - system(*%W(git clone -q #{clone_url} #{repo_path})) + system(*%W(#{Gitlab.config.git.bin_path} clone -q #{clone_url} #{repo_path})) end Dir.chdir(repo_path) do branch_sha.each do |branch, sha| # Try to reset without fetching to avoid using the network. - reset = %W(git update-ref refs/heads/#{branch} #{sha}) + reset = %W(#{Gitlab.config.git.bin_path} update-ref refs/heads/#{branch} #{sha}) unless system(*reset) - if system(*%w(git fetch origin)) + if system(*%W(#{Gitlab.config.git.bin_path} fetch origin)) unless system(*reset) raise 'The fetched test seed '\ 'does not contain the required revision.' @@ -117,7 +117,7 @@ module TestEnv end # We must copy bare repositories because we will push to them. - system(git_env, *%W(git clone -q --bare #{repo_path} #{repo_path_bare})) + system(git_env, *%W(#{Gitlab.config.git.bin_path} clone -q --bare #{repo_path} #{repo_path_bare})) end def copy_repo(project) diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 3be7dd4e52b..06559c3925d 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -16,7 +16,7 @@ describe 'gitlab:app namespace rake task' do end def reenable_backup_sub_tasks - %w{db repo uploads builds}.each do |subtask| + %w{db repo uploads builds artifacts}.each do |subtask| Rake::Task["gitlab:backup:#{subtask}:create"].reenable end end @@ -55,6 +55,8 @@ describe 'gitlab:app namespace rake task' do expect(Rake::Task["gitlab:backup:db:restore"]).to receive(:invoke) expect(Rake::Task["gitlab:backup:repo:restore"]).to receive(:invoke) expect(Rake::Task["gitlab:backup:builds:restore"]).to receive(:invoke) + expect(Rake::Task["gitlab:backup:uploads:restore"]).to receive(:invoke) + expect(Rake::Task["gitlab:backup:artifacts:restore"]).to receive(:invoke) expect(Rake::Task["gitlab:shell:setup"]).to receive(:invoke) expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error end @@ -112,19 +114,20 @@ describe 'gitlab:app namespace rake task' do it 'should set correct permissions on the tar contents' do tar_contents, exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar} db uploads repositories builds} + %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz} ) expect(exit_status).to eq(0) expect(tar_contents).to match('db/') - expect(tar_contents).to match('uploads/') + expect(tar_contents).to match('uploads.tar.gz') expect(tar_contents).to match('repositories/') - expect(tar_contents).to match('builds/') - expect(tar_contents).not_to match(/^.{4,9}[rwx].* (db|uploads|repositories|builds)\/$/) + expect(tar_contents).to match('builds.tar.gz') + expect(tar_contents).to match('artifacts.tar.gz') + expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|artifacts.tar.gz)\/$/) end it 'should delete temp directories' do temp_dirs = Dir.glob( - File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds}') + File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts}') ) expect(temp_dirs).to be_empty @@ -160,12 +163,13 @@ describe 'gitlab:app namespace rake task' do it "does not contain skipped item" do tar_contents, _exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar} db uploads repositories builds} + %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz} ) expect(tar_contents).to match('db/') - expect(tar_contents).to match('uploads/') - expect(tar_contents).to match('builds/') + expect(tar_contents).to match('uploads.tar.gz') + expect(tar_contents).to match('builds.tar.gz') + expect(tar_contents).to match('artifacts.tar.gz') expect(tar_contents).not_to match('repositories/') end @@ -177,6 +181,7 @@ describe 'gitlab:app namespace rake task' do expect(Rake::Task["gitlab:backup:db:restore"]).to receive :invoke expect(Rake::Task["gitlab:backup:repo:restore"]).not_to receive :invoke expect(Rake::Task["gitlab:backup:builds:restore"]).to receive :invoke + expect(Rake::Task["gitlab:backup:artifacts:restore"]).to receive :invoke expect(Rake::Task["gitlab:shell:setup"]).to receive :invoke expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error end diff --git a/spec/workers/stuck_ci_builds_worker_spec.rb b/spec/workers/stuck_ci_builds_worker_spec.rb new file mode 100644 index 00000000000..f9d87d97014 --- /dev/null +++ b/spec/workers/stuck_ci_builds_worker_spec.rb @@ -0,0 +1,44 @@ +require "spec_helper" + +describe StuckCiBuildsWorker do + let!(:build) { create :ci_build } + + subject do + build.reload + build.status + end + + %w(pending running).each do |status| + context "#{status} build" do + before do + build.update!(status: status) + end + + it 'gets dropped if it was updated over 2 days ago' do + build.update!(updated_at: 2.day.ago) + StuckCiBuildsWorker.new.perform + is_expected.to eq('failed') + end + + it "is still #{status}" do + build.update!(updated_at: 1.minute.ago) + StuckCiBuildsWorker.new.perform + is_expected.to eq(status) + end + end + end + + %w(success failed canceled).each do |status| + context "#{status} build" do + before do + build.update!(status: status) + end + + it "is still #{status}" do + build.update!(updated_at: 2.day.ago) + StuckCiBuildsWorker.new.perform + is_expected.to eq(status) + end + end + end +end |