summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/factories/ci/stages.rb8
-rw-r--r--spec/factories/projects.rb4
-rw-r--r--spec/features/atom/users_spec.rb6
-rw-r--r--spec/features/groups_spec.rb17
-rw-r--r--spec/helpers/events_helper_spec.rb6
-rw-r--r--spec/javascripts/fixtures/project_select_combo_button.html.haml2
-rw-r--r--spec/javascripts/project_select_combo_button_spec.js35
-rw-r--r--spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb80
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb17
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb14
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml2
-rw-r--r--spec/lib/gitlab/job_waiter_spec.rb41
-rw-r--r--spec/lib/gitlab/sidekiq_throttler_spec.rb50
-rw-r--r--spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb2
-rw-r--r--spec/migrations/migrate_stage_id_reference_in_background_spec.rb13
-rw-r--r--spec/migrations/migrate_stages_statuses_spec.rb67
-rw-r--r--spec/models/ci/stage_spec.rb79
-rw-r--r--spec/models/commit_status_spec.rb6
-rw-r--r--spec/models/project_spec.rb231
-rw-r--r--spec/policies/group_policy_spec.rb30
-rw-r--r--spec/requests/ci/api/builds_spec.rb912
-rw-r--r--spec/requests/ci/api/runners_spec.rb127
-rw-r--r--spec/requests/ci/api/triggers_spec.rb90
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb58
-rw-r--r--spec/services/groups/create_service_spec.rb14
-rw-r--r--spec/services/groups/destroy_service_spec.rb26
-rw-r--r--spec/services/merge_requests/create_from_issue_service_spec.rb20
-rw-r--r--spec/services/users/destroy_service_spec.rb27
-rw-r--r--spec/support/background_migrations_matchers.rb13
-rw-r--r--spec/workers/authorized_projects_worker_spec.rb16
-rw-r--r--spec/workers/namespaceless_project_destroy_worker_spec.rb2
-rw-r--r--spec/workers/stage_update_worker_spec.rb22
32 files changed, 756 insertions, 1281 deletions
diff --git a/spec/factories/ci/stages.rb b/spec/factories/ci/stages.rb
index d3c8bf9d54f..b2ded945738 100644
--- a/spec/factories/ci/stages.rb
+++ b/spec/factories/ci/stages.rb
@@ -15,4 +15,12 @@ FactoryGirl.define do
warnings: warnings)
end
end
+
+ factory :ci_stage_entity, class: Ci::Stage do
+ project factory: :project
+ pipeline factory: :ci_empty_pipeline
+
+ name 'test'
+ status 'pending'
+ end
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 4a2034b31b3..9ebda0ba03b 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -81,6 +81,10 @@ FactoryGirl.define do
archived true
end
+ trait :hashed do
+ storage_version Project::LATEST_STORAGE_VERSION
+ end
+
trait :access_requestable do
request_access_enabled true
end
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index 79069bbca8e..9ce687afb31 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -41,6 +41,8 @@ describe "User Feed" do
target_project: project,
description: "Here is the fix: ![an image](image.png)")
end
+ let(:push_event) { create(:push_event, project: project, author: user) }
+ let!(:push_event_payload) { create(:push_event_payload, event: push_event) }
before do
project.team << [user, :master]
@@ -70,6 +72,10 @@ describe "User Feed" do
it 'has XHTML summaries in merge request descriptions' do
expect(body).to match /Here is the fix: <a[^>]*><img[^>]*\/><\/a>/
end
+
+ it 'has push event commit ID' do
+ expect(body).to have_content(Commit.truncate_sha(push_event.commit_id))
+ end
end
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index e59a484d992..20f9818b08b 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -104,18 +104,15 @@ feature 'Group' do
end
context 'as group owner' do
- let(:user) { create(:user) }
+ it 'creates a nested group' do
+ user = create(:user)
- before do
group.add_owner(user)
sign_out(:user)
sign_in(user)
visit subgroups_group_path(group)
click_link 'New Subgroup'
- end
-
- it 'creates a nested group' do
fill_in 'Group path', with: 'bar'
click_button 'Create group'
@@ -123,6 +120,16 @@ feature 'Group' do
expect(page).to have_content("Group 'bar' was successfully created.")
end
end
+
+ context 'when nested group feature is disabled' do
+ it 'renders 404' do
+ allow(Group).to receive(:supports_nested_groups?).and_return(false)
+
+ visit subgroups_group_path(group)
+
+ expect(page.status_code).to eq(404)
+ end
+ end
end
it 'checks permissions to avoid exposing groups by parent_id' do
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index aa138f25bd3..4b72dbb7964 100644
--- a/spec/helpers/events_helper_spec.rb
+++ b/spec/helpers/events_helper_spec.rb
@@ -62,6 +62,12 @@ describe EventsHelper do
expect(helper.event_note(input)).to eq(expected)
end
+ it 'preserves data-src for lazy images' do
+ input = "![ImageTest](/uploads/test.png)"
+ image_url = "data-src=\"/uploads/test.png\""
+ expect(helper.event_note(input)).to match(image_url)
+ end
+
context 'labels formatting' do
let(:input) { 'this should be ~label_1' }
diff --git a/spec/javascripts/fixtures/project_select_combo_button.html.haml b/spec/javascripts/fixtures/project_select_combo_button.html.haml
index 54bc1a59279..432cd5fcc74 100644
--- a/spec/javascripts/fixtures/project_select_combo_button.html.haml
+++ b/spec/javascripts/fixtures/project_select_combo_button.html.haml
@@ -1,6 +1,6 @@
.project-item-select-holder
%input.project-item-select{ data: { group_id: '12345' , relative_path: 'issues/new' } }
- %a.new-project-item-link{ data: { label: 'New issue' }, href: ''}
+ %a.new-project-item-link{ data: { label: 'New issue', type: 'issues' }, href: ''}
%i.fa.fa-spinner.spin
%a.new-project-item-select-button
%i.fa.fa-caret-down
diff --git a/spec/javascripts/project_select_combo_button_spec.js b/spec/javascripts/project_select_combo_button_spec.js
index e10a5a3bef6..021804e0769 100644
--- a/spec/javascripts/project_select_combo_button_spec.js
+++ b/spec/javascripts/project_select_combo_button_spec.js
@@ -101,5 +101,40 @@ describe('Project Select Combo Button', function () {
window.localStorage.clear();
});
});
+
+ describe('deriveTextVariants', function () {
+ beforeEach(function () {
+ this.mockExecutionContext = {
+ resourceType: '',
+ resourceLabel: '',
+ };
+
+ this.comboButton = new ProjectSelectComboButton(this.projectSelectInput);
+
+ this.method = this.comboButton.deriveTextVariants.bind(this.mockExecutionContext);
+ });
+
+ it('correctly derives test variants for merge requests', function () {
+ this.mockExecutionContext.resourceType = 'merge_requests';
+ this.mockExecutionContext.resourceLabel = 'New merge request';
+
+ const returnedVariants = this.method();
+
+ expect(returnedVariants.localStorageItemType).toBe('new-merge-request');
+ expect(returnedVariants.defaultTextPrefix).toBe('New merge request');
+ expect(returnedVariants.presetTextSuffix).toBe('merge request');
+ });
+
+ it('correctly derives text variants for issues', function () {
+ this.mockExecutionContext.resourceType = 'issues';
+ this.mockExecutionContext.resourceLabel = 'New issue';
+
+ const returnedVariants = this.method();
+
+ expect(returnedVariants.localStorageItemType).toBe('new-issue');
+ expect(returnedVariants.defaultTextPrefix).toBe('New issue');
+ expect(returnedVariants.presetTextSuffix).toBe('issue');
+ });
+ });
});
diff --git a/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb b/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb
new file mode 100644
index 00000000000..878158910be
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb
@@ -0,0 +1,80 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20170711145320 do
+ let(:projects) { table(:projects) }
+ let(:pipelines) { table(:ci_pipelines) }
+ let(:stages) { table(:ci_stages) }
+ let(:jobs) { table(:ci_builds) }
+
+ STATUSES = { created: 0, pending: 1, running: 2, success: 3,
+ failed: 4, canceled: 5, skipped: 6, manual: 7 }.freeze
+
+ before do
+ projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1')
+ pipelines.create!(id: 1, project_id: 1, ref: 'master', sha: 'adf43c3a')
+ stages.create!(id: 1, pipeline_id: 1, project_id: 1, name: 'test', status: nil)
+ stages.create!(id: 2, pipeline_id: 1, project_id: 1, name: 'deploy', status: nil)
+ end
+
+ context 'when stage status is known' do
+ before do
+ create_job(project: 1, pipeline: 1, stage: 'test', status: 'success')
+ create_job(project: 1, pipeline: 1, stage: 'test', status: 'running')
+ create_job(project: 1, pipeline: 1, stage: 'deploy', status: 'failed')
+ end
+
+ it 'sets a correct stage status' do
+ described_class.new.perform(1, 2)
+
+ expect(stages.first.status).to eq STATUSES[:running]
+ expect(stages.second.status).to eq STATUSES[:failed]
+ end
+ end
+
+ context 'when stage status is not known' do
+ it 'sets a skipped stage status' do
+ described_class.new.perform(1, 2)
+
+ expect(stages.first.status).to eq STATUSES[:skipped]
+ expect(stages.second.status).to eq STATUSES[:skipped]
+ end
+ end
+
+ context 'when stage status includes status of a retried job' do
+ before do
+ create_job(project: 1, pipeline: 1, stage: 'test', status: 'canceled')
+ create_job(project: 1, pipeline: 1, stage: 'deploy', status: 'failed', retried: true)
+ create_job(project: 1, pipeline: 1, stage: 'deploy', status: 'success')
+ end
+
+ it 'sets a correct stage status' do
+ described_class.new.perform(1, 2)
+
+ expect(stages.first.status).to eq STATUSES[:canceled]
+ expect(stages.second.status).to eq STATUSES[:success]
+ end
+ end
+
+ context 'when some job in the stage is blocked / manual' do
+ before do
+ create_job(project: 1, pipeline: 1, stage: 'test', status: 'failed')
+ create_job(project: 1, pipeline: 1, stage: 'test', status: 'manual')
+ create_job(project: 1, pipeline: 1, stage: 'deploy', status: 'success', when: 'manual')
+ end
+
+ it 'sets a correct stage status' do
+ described_class.new.perform(1, 2)
+
+ expect(stages.first.status).to eq STATUSES[:manual]
+ expect(stages.second.status).to eq STATUSES[:success]
+ end
+ end
+
+ def create_job(project:, pipeline:, stage:, status:, **opts)
+ stages = { test: 1, build: 2, deploy: 3 }
+
+ jobs.create!(project_id: project, commit_id: pipeline,
+ stage_idx: stages[stage.to_sym], stage: stage,
+ status: status, **opts)
+ end
+end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index a3ae0a4686d..8ec8dfe6acf 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -235,18 +235,10 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { is_expected.to be < 2 }
end
- describe '#has_commits?' do
- it { expect(repository.has_commits?).to be_truthy }
- end
-
describe '#empty?' do
it { expect(repository.empty?).to be_falsey }
end
- describe '#bare?' do
- it { expect(repository.bare?).to be_truthy }
- end
-
describe '#ref_names' do
let(:ref_names) { repository.ref_names }
subject { ref_names }
@@ -441,15 +433,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
- describe "#remote_names" do
- let(:remotes) { repository.remote_names }
-
- it "should have one entry: 'origin'" do
- expect(remotes.size).to eq(1)
- expect(remotes.first).to eq("origin")
- end
- end
-
describe "#refs_hash" do
let(:refs) { repository.refs_hash }
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index 7fe698fcb18..2eaf4222964 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -111,6 +111,20 @@ describe Gitlab::GitalyClient::CommitService do
client.tree_entries(repository, revision, path)
end
+
+ context 'with UTF-8 params strings' do
+ let(:revision) { "branch\u011F" }
+ let(:path) { "foo/\u011F.txt" }
+
+ it 'handles string encodings correctly' do
+ expect_any_instance_of(Gitaly::CommitService::Stub)
+ .to receive(:get_tree_entries)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return([])
+
+ client.tree_entries(repository, revision, path)
+ end
+ end
end
describe '#find_commit' do
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index ae3b0173160..a5e03e149a7 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -227,6 +227,8 @@ Ci::Pipeline:
Ci::Stage:
- id
- name
+- status
+- lock_version
- project_id
- pipeline_id
- created_at
diff --git a/spec/lib/gitlab/job_waiter_spec.rb b/spec/lib/gitlab/job_waiter_spec.rb
index 6186cec2689..b0b4fdc09bc 100644
--- a/spec/lib/gitlab/job_waiter_spec.rb
+++ b/spec/lib/gitlab/job_waiter_spec.rb
@@ -1,30 +1,39 @@
require 'spec_helper'
describe Gitlab::JobWaiter do
- describe '#wait' do
- let(:waiter) { described_class.new(%w(a)) }
- it 'returns when all jobs have been completed' do
- expect(Gitlab::SidekiqStatus).to receive(:all_completed?).with(%w(a))
- .and_return(true)
+ describe '.notify' do
+ it 'pushes the jid to the named queue' do
+ key = 'gitlab:job_waiter:foo'
+ jid = 1
- expect(waiter).not_to receive(:sleep)
+ redis = double('redis')
+ expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis)
+ expect(redis).to receive(:lpush).with(key, jid)
- waiter.wait
+ described_class.notify(key, jid)
end
+ end
+
+ describe '#wait' do
+ let(:waiter) { described_class.new(2) }
- it 'sleeps between checking the job statuses' do
- expect(Gitlab::SidekiqStatus).to receive(:all_completed?)
- .with(%w(a))
- .and_return(false, true)
+ it 'returns when all jobs have been completed' do
+ described_class.notify(waiter.key, 'a')
+ described_class.notify(waiter.key, 'b')
- expect(waiter).to receive(:sleep).with(described_class::INTERVAL)
+ result = nil
+ expect { Timeout.timeout(1) { result = waiter.wait(2) } }.not_to raise_error
- waiter.wait
+ expect(result).to contain_exactly('a', 'b')
end
- it 'returns when timing out' do
- expect(waiter).not_to receive(:sleep)
- waiter.wait(0)
+ it 'times out if not all jobs complete' do
+ described_class.notify(waiter.key, 'a')
+
+ result = nil
+ expect { Timeout.timeout(2) { result = waiter.wait(1) } }.not_to raise_error
+
+ expect(result).to contain_exactly('a')
end
end
end
diff --git a/spec/lib/gitlab/sidekiq_throttler_spec.rb b/spec/lib/gitlab/sidekiq_throttler_spec.rb
index 6374ac80207..2dbb7bb7c34 100644
--- a/spec/lib/gitlab/sidekiq_throttler_spec.rb
+++ b/spec/lib/gitlab/sidekiq_throttler_spec.rb
@@ -1,28 +1,44 @@
require 'spec_helper'
describe Gitlab::SidekiqThrottler do
- before do
- Sidekiq.options[:concurrency] = 35
-
- stub_application_setting(
- sidekiq_throttling_enabled: true,
- sidekiq_throttling_factor: 0.1,
- sidekiq_throttling_queues: %w[build project_cache]
- )
- end
-
describe '#execute!' do
- it 'sets limits on the selected queues' do
- described_class.execute!
+ context 'when job throttling is enabled' do
+ before do
+ Sidekiq.options[:concurrency] = 35
+
+ stub_application_setting(
+ sidekiq_throttling_enabled: true,
+ sidekiq_throttling_factor: 0.1,
+ sidekiq_throttling_queues: %w[build project_cache]
+ )
+ end
+
+ it 'requires sidekiq-limit_fetch' do
+ expect(described_class).to receive(:require).with('sidekiq-limit_fetch').and_call_original
+
+ described_class.execute!
+ end
+
+ it 'sets limits on the selected queues' do
+ described_class.execute!
+
+ expect(Sidekiq::Queue['build'].limit).to eq 4
+ expect(Sidekiq::Queue['project_cache'].limit).to eq 4
+ end
+
+ it 'does not set limits on other queues' do
+ described_class.execute!
- expect(Sidekiq::Queue['build'].limit).to eq 4
- expect(Sidekiq::Queue['project_cache'].limit).to eq 4
+ expect(Sidekiq::Queue['merge'].limit).to be_nil
+ end
end
- it 'does not set limits on other queues' do
- described_class.execute!
+ context 'when job throttling is disabled' do
+ it 'does not require sidekiq-limit_fetch' do
+ expect(described_class).not_to receive(:require).with('sidekiq-limit_fetch')
- expect(Sidekiq::Queue['merge'].limit).to be_nil
+ described_class.execute!
+ end
end
end
end
diff --git a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
index 12cac1d033d..b47f3314926 100644
--- a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
+++ b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
@@ -4,7 +4,7 @@ require Rails.root.join('db', 'post_migrate', '20170502101023_cleanup_namespacel
describe CleanupNamespacelessPendingDeleteProjects do
before do
# Stub after_save callbacks that will fail when Project has no namespace
- allow_any_instance_of(Project).to receive(:ensure_storage_path_exist).and_return(nil)
+ allow_any_instance_of(Project).to receive(:ensure_storage_path_exists).and_return(nil)
allow_any_instance_of(Project).to receive(:update_project_statistics).and_return(nil)
end
diff --git a/spec/migrations/migrate_stage_id_reference_in_background_spec.rb b/spec/migrations/migrate_stage_id_reference_in_background_spec.rb
index 260378adaa7..9b92f4b70b0 100644
--- a/spec/migrations/migrate_stage_id_reference_in_background_spec.rb
+++ b/spec/migrations/migrate_stage_id_reference_in_background_spec.rb
@@ -2,19 +2,6 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170628080858_migrate_stage_id_reference_in_background')
describe MigrateStageIdReferenceInBackground, :migration, :sidekiq do
- matcher :be_scheduled_migration do |delay, *expected|
- match do |migration|
- BackgroundMigrationWorker.jobs.any? do |job|
- job['args'] == [migration, expected] &&
- job['at'].to_i == (delay.to_i + Time.now.to_i)
- end
- end
-
- failure_message do |migration|
- "Migration `#{migration}` with args `#{expected.inspect}` not scheduled!"
- end
- end
-
let(:jobs) { table(:ci_builds) }
let(:stages) { table(:ci_stages) }
let(:pipelines) { table(:ci_pipelines) }
diff --git a/spec/migrations/migrate_stages_statuses_spec.rb b/spec/migrations/migrate_stages_statuses_spec.rb
new file mode 100644
index 00000000000..4102d57e368
--- /dev/null
+++ b/spec/migrations/migrate_stages_statuses_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170711145558_migrate_stages_statuses.rb')
+
+describe MigrateStagesStatuses, :migration do
+ let(:jobs) { table(:ci_builds) }
+ let(:stages) { table(:ci_stages) }
+ let(:pipelines) { table(:ci_pipelines) }
+ let(:projects) { table(:projects) }
+
+ STATUSES = { created: 0, pending: 1, running: 2, success: 3,
+ failed: 4, canceled: 5, skipped: 6, manual: 7 }.freeze
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+ stub_const("#{described_class.name}::RANGE_SIZE", 2)
+
+ projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1')
+ projects.create!(id: 2, name: 'gitlab2', path: 'gitlab2')
+
+ pipelines.create!(id: 1, project_id: 1, ref: 'master', sha: 'adf43c3a')
+ pipelines.create!(id: 2, project_id: 2, ref: 'feature', sha: '21a3deb')
+
+ create_job(project: 1, pipeline: 1, stage: 'test', status: 'success')
+ create_job(project: 1, pipeline: 1, stage: 'test', status: 'running')
+ create_job(project: 1, pipeline: 1, stage: 'build', status: 'success')
+ create_job(project: 1, pipeline: 1, stage: 'build', status: 'failed')
+ create_job(project: 2, pipeline: 2, stage: 'test', status: 'success')
+ create_job(project: 2, pipeline: 2, stage: 'test', status: 'success')
+ create_job(project: 2, pipeline: 2, stage: 'test', status: 'failed', retried: true)
+
+ stages.create!(id: 1, pipeline_id: 1, project_id: 1, name: 'test', status: nil)
+ stages.create!(id: 2, pipeline_id: 1, project_id: 1, name: 'build', status: nil)
+ stages.create!(id: 3, pipeline_id: 2, project_id: 2, name: 'test', status: nil)
+ end
+
+ it 'correctly migrates stages statuses' do
+ Sidekiq::Testing.inline! do
+ expect(stages.where(status: nil).count).to eq 3
+
+ migrate!
+
+ expect(stages.where(status: nil)).to be_empty
+ expect(stages.all.order('id ASC').pluck(:status))
+ .to eq [STATUSES[:running], STATUSES[:failed], STATUSES[:success]]
+ end
+ end
+
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_migration(5.minutes, 1, 2)
+ expect(described_class::MIGRATION).to be_scheduled_migration(10.minutes, 3, 3)
+ expect(BackgroundMigrationWorker.jobs.size).to eq 2
+ end
+ end
+ end
+
+ def create_job(project:, pipeline:, stage:, status:, **opts)
+ stages = { test: 1, build: 2, deploy: 3 }
+
+ jobs.create!(project_id: project, commit_id: pipeline,
+ stage_idx: stages[stage.to_sym], stage: stage,
+ status: status, **opts)
+ end
+end
diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb
new file mode 100644
index 00000000000..74c9d6145e2
--- /dev/null
+++ b/spec/models/ci/stage_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+
+describe Ci::Stage, :models do
+ let(:stage) { create(:ci_stage_entity) }
+
+ describe 'associations' do
+ before do
+ create(:ci_build, stage_id: stage.id)
+ create(:commit_status, stage_id: stage.id)
+ end
+
+ describe '#statuses' do
+ it 'returns all commit statuses' do
+ expect(stage.statuses.count).to be 2
+ end
+ end
+
+ describe '#builds' do
+ it 'returns only builds' do
+ expect(stage.builds).to be_one
+ end
+ end
+ end
+
+ describe '#status' do
+ context 'when stage is pending' do
+ let(:stage) { create(:ci_stage_entity, status: 'pending') }
+
+ it 'has a correct status value' do
+ expect(stage.status).to eq 'pending'
+ end
+ end
+
+ context 'when stage is success' do
+ let(:stage) { create(:ci_stage_entity, status: 'success') }
+
+ it 'has a correct status value' do
+ expect(stage.status).to eq 'success'
+ end
+ end
+ end
+
+ describe 'update_status' do
+ context 'when stage objects needs to be updated' do
+ before do
+ create(:ci_build, :success, stage_id: stage.id)
+ create(:ci_build, :running, stage_id: stage.id)
+ end
+
+ it 'updates stage status correctly' do
+ expect { stage.update_status }
+ .to change { stage.reload.status }
+ .to 'running'
+ end
+ end
+
+ context 'when stage is skipped' do
+ it 'updates status to skipped' do
+ expect { stage.update_status }
+ .to change { stage.reload.status }
+ .to 'skipped'
+ end
+ end
+
+ context 'when stage object is locked' do
+ before do
+ create(:ci_build, :failed, stage_id: stage.id)
+ end
+
+ it 'retries a lock to update a stage status' do
+ stage.lock_version = 100
+
+ stage.update_status
+
+ expect(stage.reload).to be_failed
+ end
+ end
+ end
+end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 8c4a366ef8f..f7583645e69 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -7,10 +7,10 @@ describe CommitStatus do
create(:ci_pipeline, project: project, sha: project.commit.id)
end
- let(:commit_status) { create_status }
+ let(:commit_status) { create_status(stage: 'test') }
- def create_status(args = {})
- create(:commit_status, args.merge(pipeline: pipeline))
+ def create_status(**opts)
+ create(:commit_status, pipeline: pipeline, **opts)
end
it { is_expected.to belong_to(:pipeline) }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 5e60511f3a8..2e613c44357 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1251,60 +1251,6 @@ describe Project do
end
end
- describe '#rename_repo' do
- let(:project) { create(:project, :repository) }
- let(:gitlab_shell) { Gitlab::Shell.new }
-
- before do
- # Project#gitlab_shell returns a new instance of Gitlab::Shell on every
- # call. This makes testing a bit easier.
- allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
- allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
- end
-
- it 'renames a repository' do
- stub_container_registry_config(enabled: false)
-
- expect(gitlab_shell).to receive(:mv_repository)
- .ordered
- .with(project.repository_storage_path, "#{project.namespace.full_path}/foo", "#{project.full_path}")
- .and_return(true)
-
- expect(gitlab_shell).to receive(:mv_repository)
- .ordered
- .with(project.repository_storage_path, "#{project.namespace.full_path}/foo.wiki", "#{project.full_path}.wiki")
- .and_return(true)
-
- expect_any_instance_of(SystemHooksService)
- .to receive(:execute_hooks_for)
- .with(project, :rename)
-
- expect_any_instance_of(Gitlab::UploadsTransfer)
- .to receive(:rename_project)
- .with('foo', project.path, project.namespace.full_path)
-
- expect(project).to receive(:expire_caches_before_rename)
-
- expect(project).to receive(:expires_full_path_cache)
-
- project.rename_repo
- end
-
- context 'container registry with images' do
- let(:container_repository) { create(:container_repository) }
-
- before do
- stub_container_registry_config(enabled: true)
- stub_container_registry_tags(repository: :any, tags: ['tag'])
- project.container_repositories << container_repository
- end
-
- subject { project.rename_repo }
-
- it { expect {subject}.to raise_error(StandardError) }
- end
- end
-
describe '#expire_caches_before_rename' do
let(:project) { create(:project, :repository) }
let(:repo) { double(:repo, exists?: true) }
@@ -2367,4 +2313,181 @@ describe Project do
expect(project.forks_count).to eq(1)
end
end
+
+ context 'legacy storage' do
+ let(:project) { create(:project, :repository) }
+ let(:gitlab_shell) { Gitlab::Shell.new }
+
+ before do
+ allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
+ end
+
+ describe '#base_dir' do
+ it 'returns base_dir based on namespace only' do
+ expect(project.base_dir).to eq(project.namespace.full_path)
+ end
+ end
+
+ describe '#disk_path' do
+ it 'returns disk_path based on namespace and project path' do
+ expect(project.disk_path).to eq("#{project.namespace.full_path}/#{project.path}")
+ end
+ end
+
+ describe '#ensure_storage_path_exists' do
+ it 'delegates to gitlab_shell to ensure namespace is created' do
+ expect(gitlab_shell).to receive(:add_namespace).with(project.repository_storage_path, project.base_dir)
+
+ project.ensure_storage_path_exists
+ end
+ end
+
+ describe '#legacy_storage?' do
+ it 'returns true when storage_version is nil' do
+ project = build(:project)
+
+ expect(project.legacy_storage?).to be_truthy
+ end
+ end
+
+ describe '#rename_repo' do
+ before do
+ # Project#gitlab_shell returns a new instance of Gitlab::Shell on every
+ # call. This makes testing a bit easier.
+ allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
+ allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
+ end
+
+ it 'renames a repository' do
+ stub_container_registry_config(enabled: false)
+
+ expect(gitlab_shell).to receive(:mv_repository)
+ .ordered
+ .with(project.repository_storage_path, "#{project.namespace.full_path}/foo", "#{project.full_path}")
+ .and_return(true)
+
+ expect(gitlab_shell).to receive(:mv_repository)
+ .ordered
+ .with(project.repository_storage_path, "#{project.namespace.full_path}/foo.wiki", "#{project.full_path}.wiki")
+ .and_return(true)
+
+ expect_any_instance_of(SystemHooksService)
+ .to receive(:execute_hooks_for)
+ .with(project, :rename)
+
+ expect_any_instance_of(Gitlab::UploadsTransfer)
+ .to receive(:rename_project)
+ .with('foo', project.path, project.namespace.full_path)
+
+ expect(project).to receive(:expire_caches_before_rename)
+
+ expect(project).to receive(:expires_full_path_cache)
+
+ project.rename_repo
+ end
+
+ context 'container registry with images' do
+ let(:container_repository) { create(:container_repository) }
+
+ before do
+ stub_container_registry_config(enabled: true)
+ stub_container_registry_tags(repository: :any, tags: ['tag'])
+ project.container_repositories << container_repository
+ end
+
+ subject { project.rename_repo }
+
+ it { expect { subject }.to raise_error(StandardError) }
+ end
+ end
+
+ describe '#pages_path' do
+ it 'returns a path where pages are stored' do
+ expect(project.pages_path).to eq(File.join(Settings.pages.path, project.namespace.full_path, project.path))
+ end
+ end
+ end
+
+ context 'hashed storage' do
+ let(:project) { create(:project, :repository) }
+ let(:gitlab_shell) { Gitlab::Shell.new }
+ let(:hash) { '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' }
+
+ before do
+ stub_application_setting(hashed_storage_enabled: true)
+ allow(Digest::SHA2).to receive(:hexdigest) { hash }
+ allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
+ end
+
+ describe '#base_dir' do
+ it 'returns base_dir based on hash of project id' do
+ expect(project.base_dir).to eq('@hashed/6b/86')
+ end
+ end
+
+ describe '#disk_path' do
+ it 'returns disk_path based on hash of project id' do
+ hashed_path = '@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b'
+
+ expect(project.disk_path).to eq(hashed_path)
+ end
+ end
+
+ describe '#ensure_storage_path_exists' do
+ it 'delegates to gitlab_shell to ensure namespace is created' do
+ expect(gitlab_shell).to receive(:add_namespace).with(project.repository_storage_path, '@hashed/6b/86')
+
+ project.ensure_storage_path_exists
+ end
+ end
+
+ describe '#rename_repo' do
+ before do
+ # Project#gitlab_shell returns a new instance of Gitlab::Shell on every
+ # call. This makes testing a bit easier.
+ allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
+ allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
+ end
+
+ it 'renames a repository' do
+ stub_container_registry_config(enabled: false)
+
+ expect(gitlab_shell).not_to receive(:mv_repository)
+
+ expect_any_instance_of(SystemHooksService)
+ .to receive(:execute_hooks_for)
+ .with(project, :rename)
+
+ expect_any_instance_of(Gitlab::UploadsTransfer)
+ .to receive(:rename_project)
+ .with('foo', project.path, project.namespace.full_path)
+
+ expect(project).to receive(:expire_caches_before_rename)
+
+ expect(project).to receive(:expires_full_path_cache)
+
+ project.rename_repo
+ end
+
+ context 'container registry with images' do
+ let(:container_repository) { create(:container_repository) }
+
+ before do
+ stub_container_registry_config(enabled: true)
+ stub_container_registry_tags(repository: :any, tags: ['tag'])
+ project.container_repositories << container_repository
+ end
+
+ subject { project.rename_repo }
+
+ it { expect { subject }.to raise_error(StandardError) }
+ end
+ end
+
+ describe '#pages_path' do
+ it 'returns a path where pages are stored' do
+ expect(project.pages_path).to eq(File.join(Settings.pages.path, project.namespace.full_path, project.path))
+ end
+ end
+ end
end
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index b17a93e3fbe..cf420ae3ea6 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -123,6 +123,36 @@ describe GroupPolicy do
end
end
+ describe 'when nested group support feature is disabled' do
+ before do
+ allow(Group).to receive(:supports_nested_groups?).and_return(false)
+ end
+
+ context 'admin' do
+ let(:current_user) { admin }
+
+ it 'allows every owner permission except creating subgroups' do
+ create_subgroup_permission = [:create_subgroup]
+ updated_owner_permissions = owner_permissions - create_subgroup_permission
+
+ expect_disallowed(*create_subgroup_permission)
+ expect_allowed(*updated_owner_permissions)
+ end
+ end
+
+ context 'owner' do
+ let(:current_user) { owner }
+
+ it 'allows every owner permission except creating subgroups' do
+ create_subgroup_permission = [:create_subgroup]
+ updated_owner_permissions = owner_permissions - create_subgroup_permission
+
+ expect_disallowed(*create_subgroup_permission)
+ expect_allowed(*updated_owner_permissions)
+ end
+ end
+ end
+
describe 'private nested group use the highest access level from the group and inherited permissions', :nested_groups do
let(:nested_group) { create(:group, :private, parent: group) }
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
deleted file mode 100644
index 7ccba4ba3ec..00000000000
--- a/spec/requests/ci/api/builds_spec.rb
+++ /dev/null
@@ -1,912 +0,0 @@
-require 'spec_helper'
-
-describe Ci::API::Builds do
- let(:runner) { FactoryGirl.create(:ci_runner, tag_list: %w(mysql ruby)) }
- let(:project) { FactoryGirl.create(:project, shared_runners_enabled: false) }
- let(:last_update) { nil }
-
- describe "Builds API for runners" do
- let(:pipeline) { create(:ci_pipeline_without_jobs, project: project, ref: 'master') }
-
- before do
- project.runners << runner
- end
-
- describe "POST /builds/register" do
- let!(:build) { create(:ci_build, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
- let(:user_agent) { 'gitlab-ci-multi-runner 1.5.2 (1-5-stable; go1.6.3; linux/amd64)' }
- let!(:last_update) { }
- let!(:new_update) { }
-
- before do
- stub_container_registry_config(enabled: false)
- end
-
- shared_examples 'no builds available' do
- context 'when runner sends version in User-Agent' do
- context 'for stable version' do
- it 'gives 204 and set X-GitLab-Last-Update' do
- expect(response).to have_http_status(204)
- expect(response.header).to have_key('X-GitLab-Last-Update')
- end
- end
-
- context 'when last_update is up-to-date' do
- let(:last_update) { runner.ensure_runner_queue_value }
-
- it 'gives 204 and set the same X-GitLab-Last-Update' do
- expect(response).to have_http_status(204)
- expect(response.header['X-GitLab-Last-Update'])
- .to eq(last_update)
- end
- end
-
- context 'when last_update is outdated' do
- let(:last_update) { runner.ensure_runner_queue_value }
- let(:new_update) { runner.tick_runner_queue }
-
- it 'gives 204 and set a new X-GitLab-Last-Update' do
- expect(response).to have_http_status(204)
- expect(response.header['X-GitLab-Last-Update'])
- .to eq(new_update)
- end
- end
-
- context 'for beta version' do
- let(:user_agent) { 'gitlab-ci-multi-runner 1.6.0~beta.167.g2b2bacc (1-5-stable; go1.6.3; linux/amd64)' }
- it { expect(response).to have_http_status(204) }
- end
- end
-
- context "when runner doesn't send version in User-Agent" do
- let(:user_agent) { 'Go-http-client/1.1' }
- it { expect(response).to have_http_status(404) }
- end
-
- context "when runner doesn't have a User-Agent" do
- let(:user_agent) { nil }
- it { expect(response).to have_http_status(404) }
- end
- end
-
- context 'when an old image syntax is used' do
- before do
- build.update!(options: { image: 'codeclimate' })
- end
-
- it 'starts a build' do
- register_builds info: { platform: :darwin }
-
- expect(response).to have_http_status(201)
- expect(json_response["options"]).to eq({ "image" => "codeclimate" })
- end
- end
-
- context 'when a new image syntax is used' do
- before do
- build.update!(options: { image: { name: 'codeclimate' } })
- end
-
- it 'starts a build' do
- register_builds info: { platform: :darwin }
-
- expect(response).to have_http_status(201)
- expect(json_response["options"]).to eq({ "image" => "codeclimate" })
- end
- end
-
- context 'when an old service syntax is used' do
- before do
- build.update!(options: { services: ['mysql'] })
- end
-
- it 'starts a build' do
- register_builds info: { platform: :darwin }
-
- expect(response).to have_http_status(201)
- expect(json_response["options"]).to eq({ "services" => ["mysql"] })
- end
- end
-
- context 'when a new service syntax is used' do
- before do
- build.update!(options: { services: [name: 'mysql'] })
- end
-
- it 'starts a build' do
- register_builds info: { platform: :darwin }
-
- expect(response).to have_http_status(201)
- expect(json_response["options"]).to eq({ "services" => ["mysql"] })
- end
- end
-
- context 'when no image or service is defined' do
- before do
- build.update!(options: {})
- end
-
- it 'starts a build' do
- register_builds info: { platform: :darwin }
-
- expect(response).to have_http_status(201)
-
- expect(json_response["options"]).to be_empty
- end
- end
-
- context 'when there is a pending build' do
- it 'starts a build' do
- register_builds info: { platform: :darwin }
-
- expect(response).to have_http_status(201)
- expect(response.headers).not_to have_key('X-GitLab-Last-Update')
- expect(json_response['sha']).to eq(build.sha)
- expect(runner.reload.platform).to eq("darwin")
- expect(json_response["options"]).to eq({ "image" => "ruby:2.1", "services" => ["postgres"] })
- expect(json_response["variables"]).to include(
- { "key" => "CI_JOB_NAME", "value" => "spinach", "public" => true },
- { "key" => "CI_JOB_STAGE", "value" => "test", "public" => true },
- { "key" => "DB_NAME", "value" => "postgres", "public" => true }
- )
- end
-
- it 'updates runner info' do
- expect { register_builds }.to change { runner.reload.contacted_at }
- end
-
- context 'when concurrently updating build' do
- before do
- expect_any_instance_of(Ci::Build).to receive(:run!)
- .and_raise(ActiveRecord::StaleObjectError.new(nil, nil))
- end
-
- it 'returns a conflict' do
- register_builds info: { platform: :darwin }
-
- expect(response).to have_http_status(409)
- expect(response.headers).not_to have_key('X-GitLab-Last-Update')
- end
- end
-
- context 'registry credentials' do
- let(:registry_credentials) do
- { 'type' => 'registry',
- 'url' => 'registry.example.com:5005',
- 'username' => 'gitlab-ci-token',
- 'password' => build.token }
- end
-
- context 'when registry is enabled' do
- before do
- stub_container_registry_config(enabled: true, host_port: 'registry.example.com:5005')
- end
-
- it 'sends registry credentials key' do
- register_builds info: { platform: :darwin }
-
- expect(json_response).to have_key('credentials')
- expect(json_response['credentials']).to include(registry_credentials)
- end
- end
-
- context 'when registry is disabled' do
- before do
- stub_container_registry_config(enabled: false, host_port: 'registry.example.com:5005')
- end
-
- it 'does not send registry credentials' do
- register_builds info: { platform: :darwin }
-
- expect(json_response).to have_key('credentials')
- expect(json_response['credentials']).not_to include(registry_credentials)
- end
- end
- end
-
- context 'when docker configuration options are used' do
- let!(:build) { create(:ci_build, :extended_options, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
-
- it 'starts a build' do
- register_builds info: { platform: :darwin }
-
- expect(response).to have_http_status(201)
- expect(json_response['options']['image']).to eq('ruby:2.1')
- expect(json_response['options']['services']).to eq(['postgres', 'docker:dind'])
- end
- end
- end
-
- context 'when builds are finished' do
- before do
- build.success
- register_builds
- end
-
- it_behaves_like 'no builds available'
- end
-
- context 'for other project with builds' do
- before do
- build.success
- create(:ci_build, :pending)
- register_builds
- end
-
- it_behaves_like 'no builds available'
- end
-
- context 'for shared runner' do
- let!(:runner) { create(:ci_runner, :shared, token: "SharedRunner") }
-
- before do
- register_builds(runner.token)
- end
-
- it_behaves_like 'no builds available'
- end
-
- context 'for triggered build' do
- before do
- trigger = create(:ci_trigger, project: project)
- create(:ci_trigger_request_with_variables, pipeline: pipeline, builds: [build], trigger: trigger)
- project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
- end
-
- it "returns variables for triggers" do
- register_builds info: { platform: :darwin }
-
- expect(response).to have_http_status(201)
- expect(json_response["variables"]).to include(
- { "key" => "CI_JOB_NAME", "value" => "spinach", "public" => true },
- { "key" => "CI_JOB_STAGE", "value" => "test", "public" => true },
- { "key" => "CI_PIPELINE_TRIGGERED", "value" => "true", "public" => true },
- { "key" => "DB_NAME", "value" => "postgres", "public" => true },
- { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
- { "key" => "TRIGGER_KEY_1", "value" => "TRIGGER_VALUE_1", "public" => false }
- )
- end
- end
-
- context 'with multiple builds' do
- before do
- build.success
- end
-
- let!(:test_build) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) }
-
- it "returns dependent builds" do
- register_builds info: { platform: :darwin }
-
- expect(response).to have_http_status(201)
- expect(json_response["id"]).to eq(test_build.id)
- expect(json_response["depends_on_builds"].count).to eq(1)
- expect(json_response["depends_on_builds"][0]).to include('id' => build.id, 'name' => 'spinach')
- end
- end
-
- %w(name version revision platform architecture).each do |param|
- context "updates runner #{param}" do
- let(:value) { "#{param}_value" }
-
- subject { runner.read_attribute(param.to_sym) }
-
- it do
- register_builds info: { param => value }
-
- expect(response).to have_http_status(201)
- runner.reload
- is_expected.to eq(value)
- end
- end
- end
-
- context 'when build has no tags' do
- before do
- build.update(tags: [])
- end
-
- context 'when runner is allowed to pick untagged builds' do
- before do
- runner.update_column(:run_untagged, true)
- end
-
- it 'picks build' do
- register_builds
-
- expect(response).to have_http_status 201
- end
- end
-
- context 'when runner is not allowed to pick untagged builds' do
- before do
- runner.update_column(:run_untagged, false)
- register_builds
- end
-
- it_behaves_like 'no builds available'
- end
- end
-
- context 'when runner is paused' do
- let(:runner) { create(:ci_runner, :inactive, token: 'InactiveRunner') }
-
- it 'responds with 404' do
- register_builds
-
- expect(response).to have_http_status 404
- end
-
- it 'does not update runner info' do
- expect { register_builds }
- .not_to change { runner.reload.contacted_at }
- end
- end
-
- def register_builds(token = runner.token, **params)
- new_params = params.merge(token: token, last_update: last_update)
-
- post ci_api("/builds/register"), new_params, { 'User-Agent' => user_agent }
- end
- end
-
- describe "PUT /builds/:id" do
- let(:build) { create(:ci_build, :pending, :trace, pipeline: pipeline, runner_id: runner.id) }
-
- before do
- build.run!
- put ci_api("/builds/#{build.id}"), token: runner.token
- end
-
- it "updates a running build" do
- expect(response).to have_http_status(200)
- end
-
- it 'does not override trace information when no trace is given' do
- expect(build.reload.trace.raw).to eq 'BUILD TRACE'
- end
-
- context 'job has been erased' do
- let(:build) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
-
- it 'responds with forbidden' do
- expect(response.status).to eq 403
- end
- end
- end
-
- describe 'PATCH /builds/:id/trace.txt' do
- let(:build) do
- attributes = { runner_id: runner.id, pipeline: pipeline }
- create(:ci_build, :running, :trace, attributes)
- end
-
- let(:headers) { { Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token, 'Content-Type' => 'text/plain' } }
- let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) }
- let(:update_interval) { 10.seconds.to_i }
-
- def patch_the_trace(content = ' appended', request_headers = nil)
- unless request_headers
- build.trace.read do |stream|
- offset = stream.size
- limit = offset + content.length - 1
- request_headers = headers.merge({ 'Content-Range' => "#{offset}-#{limit}" })
- end
- end
-
- Timecop.travel(build.updated_at + update_interval) do
- patch ci_api("/builds/#{build.id}/trace.txt"), content, request_headers
- build.reload
- end
- end
-
- def initial_patch_the_trace
- patch_the_trace(' appended', headers_with_range)
- end
-
- def force_patch_the_trace
- 2.times { patch_the_trace('') }
- end
-
- before do
- initial_patch_the_trace
- end
-
- context 'when request is valid' do
- it 'gets correct response' do
- expect(response.status).to eq 202
- expect(build.reload.trace.raw).to eq 'BUILD TRACE appended'
- expect(response.header).to have_key 'Range'
- expect(response.header).to have_key 'Build-Status'
- end
-
- context 'when build has been updated recently' do
- it { expect { patch_the_trace }.not_to change { build.updated_at }}
-
- it 'changes the build trace' do
- patch_the_trace
-
- expect(build.reload.trace.raw).to eq 'BUILD TRACE appended appended'
- end
-
- context 'when Runner makes a force-patch' do
- it { expect { force_patch_the_trace }.not_to change { build.updated_at }}
-
- it "doesn't change the build.trace" do
- force_patch_the_trace
-
- expect(build.reload.trace.raw).to eq 'BUILD TRACE appended'
- end
- end
- end
-
- context 'when build was not updated recently' do
- let(:update_interval) { 15.minutes.to_i }
-
- it { expect { patch_the_trace }.to change { build.updated_at } }
-
- it 'changes the build.trace' do
- patch_the_trace
-
- expect(build.reload.trace.raw).to eq 'BUILD TRACE appended appended'
- end
-
- context 'when Runner makes a force-patch' do
- it { expect { force_patch_the_trace }.to change { build.updated_at } }
-
- it "doesn't change the build.trace" do
- force_patch_the_trace
-
- expect(build.reload.trace.raw).to eq 'BUILD TRACE appended'
- end
- end
- end
-
- context 'when project for the build has been deleted' do
- let(:build) do
- attributes = { runner_id: runner.id, pipeline: pipeline }
- create(:ci_build, :running, :trace, attributes) do |build|
- build.project.update(pending_delete: true)
- end
- end
-
- it 'responds with forbidden' do
- expect(response.status).to eq(403)
- end
- end
- end
-
- context 'when Runner makes a force-patch' do
- before do
- force_patch_the_trace
- end
-
- it 'gets correct response' do
- expect(response.status).to eq 202
- expect(build.reload.trace.raw).to eq 'BUILD TRACE appended'
- expect(response.header).to have_key 'Range'
- expect(response.header).to have_key 'Build-Status'
- end
- end
-
- context 'when content-range start is too big' do
- let(:headers_with_range) { headers.merge({ 'Content-Range' => '15-20' }) }
-
- it 'gets 416 error response with range headers' do
- expect(response.status).to eq 416
- expect(response.header).to have_key 'Range'
- expect(response.header['Range']).to eq '0-11'
- end
- end
-
- context 'when content-range start is too small' do
- let(:headers_with_range) { headers.merge({ 'Content-Range' => '8-20' }) }
-
- it 'gets 416 error response with range headers' do
- expect(response.status).to eq 416
- expect(response.header).to have_key 'Range'
- expect(response.header['Range']).to eq '0-11'
- end
- end
-
- context 'when Content-Range header is missing' do
- let(:headers_with_range) { headers }
-
- it { expect(response.status).to eq 400 }
- end
-
- context 'when build has been errased' do
- let(:build) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
-
- it { expect(response.status).to eq 403 }
- 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(:build) { create(:ci_build, :pending, pipeline: pipeline, 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(:jwt_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
- let(:headers) { { "GitLab-Workhorse" => "1.0", Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => jwt_token } }
- let(:token) { build.token }
- let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => token) }
-
- before do
- build.run!
- end
-
- describe "POST /builds/:id/artifacts/authorize" do
- context "authorizes posting artifact to running build" do
- it "using token as parameter" do
- post authorize_url, { token: build.token }, headers
-
- expect(response).to have_http_status(200)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- expect(json_response["TempPath"]).not_to be_nil
- end
-
- it "using token as header" do
- post authorize_url, {}, headers_with_token
-
- expect(response).to have_http_status(200)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- expect(json_response["TempPath"]).not_to be_nil
- end
-
- it "using runners token" do
- post authorize_url, { token: build.project.runners_token }, headers
-
- expect(response).to have_http_status(200)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- expect(json_response["TempPath"]).not_to be_nil
- end
-
- it "reject requests that did not go through gitlab-workhorse" do
- headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
-
- post authorize_url, { token: build.token }, headers
-
- expect(response).to have_http_status(500)
- end
- end
-
- context "fails to post too large artifact" do
- it "using token as parameter" do
- stub_application_setting(max_artifacts_size: 0)
-
- post authorize_url, { token: build.token, filesize: 100 }, headers
-
- expect(response).to have_http_status(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).to have_http_status(413)
- end
- end
-
- context 'authorization token is invalid' do
- before do
- post authorize_url, { token: 'invalid', filesize: 100 }
- end
-
- it 'responds with forbidden' do
- expect(response).to have_http_status(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
-
- describe 'build has been erased' do
- let(:build) { create(:ci_build, erased_at: Time.now) }
-
- before do
- upload_artifacts(file_upload, headers_with_token)
- end
-
- it 'responds with forbidden' do
- expect(response.status).to eq 403
- end
- end
-
- describe 'uploading artifacts for a running build' do
- shared_examples 'successful artifacts upload' do
- it 'updates successfully' do
- response_filename =
- json_response['artifacts_file']['filename']
-
- expect(response).to have_http_status(201)
- expect(response_filename).to eq(file_upload.original_filename)
- end
- end
-
- context 'uses regular file post' do
- before do
- upload_artifacts(file_upload, headers_with_token, false)
- end
-
- it_behaves_like 'successful artifacts upload'
- end
-
- context 'uses accelerated file post' do
- before do
- upload_artifacts(file_upload, headers_with_token, true)
- end
-
- it_behaves_like 'successful artifacts upload'
- end
-
- context 'updates artifact' do
- before do
- upload_artifacts(file_upload2, headers_with_token)
- upload_artifacts(file_upload, headers_with_token)
- end
-
- it_behaves_like 'successful artifacts upload'
- end
-
- context 'when using runners token' do
- let(:token) { build.project.runners_token }
-
- before do
- upload_artifacts(file_upload, headers_with_token)
- end
-
- it_behaves_like 'successful artifacts upload'
- end
- end
-
- context 'posts artifacts file and metadata file' do
- let!(:artifacts) { file_upload }
- let!(:metadata) { file_upload2 }
-
- let(:stored_artifacts_file) { build.reload.artifacts_file.file }
- let(:stored_metadata_file) { build.reload.artifacts_metadata.file }
- let(:stored_artifacts_size) { build.reload.artifacts_size }
-
- before do
- post(post_url, post_data, headers_with_token)
- end
-
- context 'posts data accelerated by workhorse is correct' do
- let(:post_data) do
- { 'file.path' => artifacts.path,
- 'file.name' => artifacts.original_filename,
- 'metadata.path' => metadata.path,
- 'metadata.name' => metadata.original_filename }
- end
-
- it 'stores artifacts and artifacts metadata' do
- expect(response).to have_http_status(201)
- expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename)
- expect(stored_metadata_file.original_filename).to eq(metadata.original_filename)
- expect(stored_artifacts_size).to eq(71759)
- end
- end
-
- context 'no artifacts file in post data' do
- let(:post_data) do
- { 'metadata' => metadata }
- end
-
- it 'is expected to respond with bad request' do
- expect(response).to have_http_status(400)
- end
-
- it 'does not store metadata' do
- expect(stored_metadata_file).to be_nil
- end
- end
- end
-
- context 'with an expire date' do
- let!(:artifacts) { file_upload }
- let(:default_artifacts_expire_in) {}
-
- let(:post_data) do
- { 'file.path' => artifacts.path,
- 'file.name' => artifacts.original_filename,
- 'expire_in' => expire_in }
- end
-
- before do
- stub_application_setting(
- default_artifacts_expire_in: default_artifacts_expire_in)
-
- post(post_url, post_data, headers_with_token)
- end
-
- context 'with an expire_in given' do
- let(:expire_in) { '7 days' }
-
- it 'updates when specified' do
- build.reload
- expect(response).to have_http_status(201)
- expect(json_response['artifacts_expire_at']).not_to be_empty
- expect(build.artifacts_expire_at)
- .to be_within(5.minutes).of(7.days.from_now)
- end
- end
-
- context 'with no expire_in given' do
- let(:expire_in) { nil }
-
- it 'ignores if not specified' do
- build.reload
- expect(response).to have_http_status(201)
- expect(json_response['artifacts_expire_at']).to be_nil
- expect(build.artifacts_expire_at).to be_nil
- end
-
- context 'with application default' do
- context 'default to 5 days' do
- let(:default_artifacts_expire_in) { '5 days' }
-
- it 'sets to application default' do
- build.reload
- expect(response).to have_http_status(201)
- expect(json_response['artifacts_expire_at'])
- .not_to be_empty
- expect(build.artifacts_expire_at)
- .to be_within(5.minutes).of(5.days.from_now)
- end
- end
-
- context 'default to 0' do
- let(:default_artifacts_expire_in) { '0' }
-
- it 'does not set expire_in' do
- build.reload
- expect(response).to have_http_status(201)
- expect(json_response['artifacts_expire_at']).to be_nil
- expect(build.artifacts_expire_at).to be_nil
- end
- end
- end
- end
- end
-
- context "artifacts file is too large" do
- it "fails to post too large artifact" do
- stub_application_setting(max_artifacts_size: 0)
- upload_artifacts(file_upload, headers_with_token)
- expect(response).to have_http_status(413)
- end
- end
-
- context "artifacts post request does not contain file" do
- it "fails to post artifacts without file" do
- post post_url, {}, headers_with_token
- expect(response).to have_http_status(400)
- end
- end
-
- context 'GitLab Workhorse is not configured' do
- it "fails to post artifacts without GitLab-Workhorse" do
- post post_url, { token: build.token }, {}
- expect(response).to have_http_status(403)
- end
- end
- end
-
- context "artifacts are being stored 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)
- end
-
- after do
- FileUtils.remove_entry @tmpdir
- end
-
- it "fails to post artifacts for outside of tmp path" do
- upload_artifacts(file_upload, headers_with_token)
- expect(response).to have_http_status(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
- let(:build) { create(:ci_build, :artifacts) }
-
- before do
- delete delete_url, token: build.token
- end
-
- shared_examples 'having removable artifacts' do
- it 'removes build artifacts' do
- build.reload
-
- expect(response).to have_http_status(200)
- expect(build.artifacts_file.exists?).to be_falsy
- expect(build.artifacts_metadata.exists?).to be_falsy
- expect(build.artifacts_size).to be_nil
- end
- end
-
- context 'when using build token' do
- before do
- delete delete_url, token: build.token
- end
-
- it_behaves_like 'having removable artifacts'
- end
-
- context 'when using runnners token' do
- before do
- delete delete_url, token: build.project.runners_token
- end
-
- it_behaves_like 'having removable artifacts'
- end
- end
-
- describe 'GET /builds/:id/artifacts' do
- before do
- get get_url, token: token
- end
-
- context 'build has artifacts' do
- let(:build) { create(:ci_build, :artifacts) }
- let(:download_headers) do
- { 'Content-Transfer-Encoding' => 'binary',
- 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
- end
-
- shared_examples 'having downloadable artifacts' do
- it 'download artifacts' do
- expect(response).to have_http_status(200)
- expect(response.headers).to include download_headers
- end
- end
-
- context 'when using build token' do
- let(:token) { build.token }
-
- it_behaves_like 'having downloadable artifacts'
- end
-
- context 'when using runnners token' do
- let(:token) { build.project.runners_token }
-
- it_behaves_like 'having downloadable artifacts'
- end
- end
-
- context 'build does not has artifacts' do
- let(:token) { build.token }
-
- it 'responds with not found' do
- expect(response).to have_http_status(404)
- end
- end
- end
- end
- end
-end
diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb
deleted file mode 100644
index 75059dd20a0..00000000000
--- a/spec/requests/ci/api/runners_spec.rb
+++ /dev/null
@@ -1,127 +0,0 @@
-require 'spec_helper'
-
-describe Ci::API::Runners do
- include StubGitlabCalls
-
- let(:registration_token) { 'abcdefg123456' }
-
- before do
- stub_gitlab_calls
- stub_application_setting(runners_registration_token: registration_token)
- end
-
- describe "POST /runners/register" do
- context 'when runner token is provided' do
- before do
- post ci_api("/runners/register"), token: registration_token
- end
-
- it 'creates runner with default values' do
- expect(response).to have_http_status 201
- expect(Ci::Runner.first.run_untagged).to be true
- expect(Ci::Runner.first.token).not_to eq(registration_token)
- end
- end
-
- context 'when runner description is provided' do
- before do
- post ci_api("/runners/register"), token: registration_token,
- description: "server.hostname"
- end
-
- it 'creates runner' do
- expect(response).to have_http_status 201
- expect(Ci::Runner.first.description).to eq("server.hostname")
- end
- end
-
- context 'when runner tags are provided' do
- before do
- post ci_api("/runners/register"), token: registration_token,
- tag_list: "tag1, tag2"
- end
-
- it 'creates runner' do
- expect(response).to have_http_status 201
- expect(Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
- end
- end
-
- context 'when option for running untagged jobs is provided' do
- context 'when tags are provided' do
- it 'creates runner' do
- post ci_api("/runners/register"), token: registration_token,
- run_untagged: false,
- tag_list: ['tag']
-
- expect(response).to have_http_status 201
- expect(Ci::Runner.first.run_untagged).to be false
- end
- end
-
- context 'when tags are not provided' do
- it 'does not create runner' do
- post ci_api("/runners/register"), token: registration_token,
- run_untagged: false
-
- expect(response).to have_http_status 404
- end
- end
- end
-
- context 'when project token is provided' do
- let(:project) { FactoryGirl.create(:project) }
-
- before do
- post ci_api("/runners/register"), token: project.runners_token
- end
-
- it 'creates runner' do
- expect(response).to have_http_status 201
- expect(project.runners.size).to eq(1)
- expect(Ci::Runner.first.token).not_to eq(registration_token)
- expect(Ci::Runner.first.token).not_to eq(project.runners_token)
- end
- end
-
- context 'when token is invalid' do
- it 'returns 403 error' do
- post ci_api("/runners/register"), token: 'invalid'
-
- expect(response).to have_http_status 403
- end
- end
-
- context 'when no token provided' do
- it 'returns 400 error' do
- post ci_api("/runners/register")
-
- expect(response).to have_http_status 400
- end
- end
-
- %w(name version revision platform architecture).each do |param|
- context "creates runner with #{param} saved" do
- let(:value) { "#{param}_value" }
-
- subject { Ci::Runner.first.read_attribute(param.to_sym) }
-
- it do
- post ci_api("/runners/register"), token: registration_token, info: { param => value }
- expect(response).to have_http_status 201
- is_expected.to eq(value)
- end
- end
- end
- end
-
- describe "DELETE /runners/delete" do
- it 'returns 200' do
- runner = FactoryGirl.create(:ci_runner)
- delete ci_api("/runners/delete"), token: runner.token
-
- expect(response).to have_http_status 200
- expect(Ci::Runner.count).to eq(0)
- end
- end
-end
diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb
deleted file mode 100644
index 7c77ebb69a2..00000000000
--- a/spec/requests/ci/api/triggers_spec.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-require 'spec_helper'
-
-describe Ci::API::Triggers do
- describe 'POST /projects/:project_id/refs/:ref/trigger' do
- let!(:trigger_token) { 'secure token' }
- let!(:project) { create(:project, :repository, ci_id: 10) }
- let!(:project2) { create(:project, ci_id: 11) }
-
- let!(:trigger) do
- create(:ci_trigger,
- project: project,
- token: trigger_token,
- owner: create(:user))
- end
-
- let(:options) do
- {
- token: trigger_token
- }
- end
-
- before do
- stub_ci_pipeline_to_return_yaml_file
-
- project.add_developer(trigger.owner)
- end
-
- context 'Handles errors' do
- it 'returns bad request if token is missing' do
- post ci_api("/projects/#{project.ci_id}/refs/master/trigger")
- expect(response).to have_http_status(400)
- end
-
- it 'returns not found if project is not found' do
- post ci_api('/projects/0/refs/master/trigger'), options
- expect(response).to have_http_status(404)
- end
-
- it 'returns unauthorized if token is for different project' do
- post ci_api("/projects/#{project2.ci_id}/refs/master/trigger"), options
- expect(response).to have_http_status(401)
- end
- end
-
- context 'Have a commit' do
- let(:pipeline) { project.pipelines.last }
-
- it 'creates builds' do
- post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options
- expect(response).to have_http_status(201)
- pipeline.builds.reload
- expect(pipeline.builds.pending.size).to eq(2)
- expect(pipeline.builds.size).to eq(5)
- end
-
- it 'returns bad request with no builds created if there\'s no commit for that ref' do
- post ci_api("/projects/#{project.ci_id}/refs/other-branch/trigger"), options
- expect(response).to have_http_status(400)
- expect(json_response['message']['base'])
- .to contain_exactly('Reference not found')
- end
-
- context 'Validates variables' do
- let(:variables) do
- { 'TRIGGER_KEY' => 'TRIGGER_VALUE' }
- end
-
- it 'validates variables to be a hash' do
- post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: 'value')
- expect(response).to have_http_status(400)
-
- expect(json_response['error']).to eq('variables is invalid')
- end
-
- it 'validates variables needs to be a map of key-valued strings' do
- post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: { key: %w(1 2) })
- expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('variables needs to be a map of key-valued strings')
- end
-
- it 'creates trigger request with variables' do
- post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: variables)
- expect(response).to have_http_status(201)
- pipeline.builds.reload
- expect(pipeline.builds.first.trigger_request.variables).to eq(variables)
- end
- end
- end
- end
-end
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 53d4fcfed18..8465a6f99bd 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -55,10 +55,15 @@ describe Ci::CreatePipelineService do
context 'when merge requests already exist for this source branch' do
it 'updates head pipeline of each merge request' do
- merge_request_1 = create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project)
- merge_request_2 = create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project)
+ merge_request_1 = create(:merge_request, source_branch: 'master',
+ target_branch: "branch_1",
+ source_project: project)
- head_pipeline = pipeline
+ merge_request_2 = create(:merge_request, source_branch: 'master',
+ target_branch: "branch_2",
+ source_project: project)
+
+ head_pipeline = execute_service
expect(merge_request_1.reload.head_pipeline).to eq(head_pipeline)
expect(merge_request_2.reload.head_pipeline).to eq(head_pipeline)
@@ -66,9 +71,11 @@ describe Ci::CreatePipelineService do
context 'when there is no pipeline for source branch' do
it "does not update merge request head pipeline" do
- merge_request = create(:merge_request, source_branch: 'feature', target_branch: "branch_1", source_project: project)
+ merge_request = create(:merge_request, source_branch: 'feature',
+ target_branch: "branch_1",
+ source_project: project)
- head_pipeline = pipeline
+ head_pipeline = execute_service
expect(merge_request.reload.head_pipeline).not_to eq(head_pipeline)
end
@@ -76,13 +83,19 @@ describe Ci::CreatePipelineService do
context 'when merge request target project is different from source project' do
let!(:target_project) { create(:project, :repository) }
- let!(:forked_project_link) { create(:forked_project_link, forked_to_project: project, forked_from_project: target_project) }
+
+ let!(:forked_project_link) do
+ create(:forked_project_link, forked_to_project: project,
+ forked_from_project: target_project)
+ end
it 'updates head pipeline for merge request' do
- merge_request =
- create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project, target_project: target_project)
+ merge_request = create(:merge_request, source_branch: 'master',
+ target_branch: "branch_1",
+ source_project: project,
+ target_project: target_project)
- head_pipeline = pipeline
+ head_pipeline = execute_service
expect(merge_request.reload.head_pipeline).to eq(head_pipeline)
end
@@ -90,15 +103,36 @@ describe Ci::CreatePipelineService do
context 'when the pipeline is not the latest for the branch' do
it 'does not update merge request head pipeline' do
- merge_request = create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project)
+ merge_request = create(:merge_request, source_branch: 'master',
+ target_branch: "branch_1",
+ source_project: project)
- allow_any_instance_of(Ci::Pipeline).to receive(:latest?).and_return(false)
+ allow_any_instance_of(Ci::Pipeline)
+ .to receive(:latest?).and_return(false)
- pipeline
+ execute_service
expect(merge_request.reload.head_pipeline).to be_nil
end
end
+
+ context 'when pipeline has errors' do
+ before do
+ stub_ci_pipeline_yaml_file('some invalid syntax')
+ end
+
+ it 'updates merge request head pipeline reference' do
+ merge_request = create(:merge_request, source_branch: 'master',
+ target_branch: 'feature',
+ source_project: project)
+
+ head_pipeline = execute_service
+
+ expect(head_pipeline).to be_persisted
+ expect(head_pipeline.yaml_errors).to be_present
+ expect(merge_request.reload.head_pipeline).to eq head_pipeline
+ end
+ end
end
context 'auto-cancel enabled' do
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index b2175717a70..6973e7ff990 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -32,12 +32,24 @@ describe Groups::CreateService, '#execute' do
end
it { is_expected.to be_persisted }
+
+ context 'when nested groups feature is disabled' do
+ it 'does not save group and returns an error' do
+ allow(Group).to receive(:supports_nested_groups?).and_return(false)
+
+ is_expected.not_to be_persisted
+ expect(subject.errors[:parent_id]).to include('You don’t have permission to create a subgroup in this group.')
+ expect(subject.parent_id).to be_nil
+ end
+ end
end
context 'as guest' do
it 'does not save group and returns an error' do
+ allow(Group).to receive(:supports_nested_groups?).and_return(true)
+
is_expected.not_to be_persisted
- expect(subject.errors[:parent_id].first).to eq('manage access required to create subgroup')
+ expect(subject.errors[:parent_id].first).to eq('You don’t have permission to create a subgroup in this group.')
expect(subject.parent_id).to be_nil
end
end
diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb
index 1b2ce3cd03e..ac4b9c02ba7 100644
--- a/spec/services/groups/destroy_service_spec.rb
+++ b/spec/services/groups/destroy_service_spec.rb
@@ -8,8 +8,8 @@ describe Groups::DestroyService do
let!(:nested_group) { create(:group, parent: group) }
let!(:project) { create(:project, namespace: group) }
let!(:notification_setting) { create(:notification_setting, source: group)}
- let!(:gitlab_shell) { Gitlab::Shell.new }
- let!(:remove_path) { group.path + "+#{group.id}+deleted" }
+ let(:gitlab_shell) { Gitlab::Shell.new }
+ let(:remove_path) { group.path + "+#{group.id}+deleted" }
before do
group.add_user(user, Gitlab::Access::OWNER)
@@ -134,4 +134,26 @@ describe Groups::DestroyService do
it_behaves_like 'group destruction', false
end
+
+ describe 'repository removal' do
+ before do
+ destroy_group(group, user, false)
+ end
+
+ context 'legacy storage' do
+ let!(:project) { create(:project, :empty_repo, namespace: group) }
+
+ it 'removes repository' do
+ expect(gitlab_shell.exists?(project.repository_storage_path, "#{project.disk_path}.git")).to be_falsey
+ end
+ end
+
+ context 'hashed storage' do
+ let!(:project) { create(:project, :hashed, :empty_repo, namespace: group) }
+
+ it 'removes repository' do
+ expect(gitlab_shell.exists?(project.repository_storage_path, "#{project.disk_path}.git")).to be_falsey
+ end
+ end
+ end
end
diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb
index 492b55cdece..313f87ae1f6 100644
--- a/spec/services/merge_requests/create_from_issue_service_spec.rb
+++ b/spec/services/merge_requests/create_from_issue_service_spec.rb
@@ -2,8 +2,10 @@ require 'spec_helper'
describe MergeRequests::CreateFromIssueService do
let(:project) { create(:project, :repository) }
- let(:user) { create(:user) }
- let(:issue) { create(:issue, project: project) }
+ let(:user) { create(:user) }
+ let(:label_ids) { create_pair(:label, project: project).map(&:id) }
+ let(:milestone_id) { create(:milestone, project: project).id }
+ let(:issue) { create(:issue, project: project, milestone_id: milestone_id) }
subject(:service) { described_class.new(project, user, issue_iid: issue.iid) }
@@ -25,6 +27,20 @@ describe MergeRequests::CreateFromIssueService do
described_class.new(project, user, issue_iid: -1).execute
end
+ it "inherits labels" do
+ issue.assign_attributes(label_ids: label_ids)
+
+ result = service.execute
+
+ expect(result[:merge_request].label_ids).to eq(label_ids)
+ end
+
+ it "inherits milestones" do
+ result = service.execute
+
+ expect(result[:merge_request].milestone_id).to eq(milestone_id)
+ end
+
it 'delegates the branch creation to CreateBranchService' do
expect_any_instance_of(CreateBranchService).to receive(:execute).once.and_call_original
diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb
index a82567f6f43..58a5bede3de 100644
--- a/spec/services/users/destroy_service_spec.rb
+++ b/spec/services/users/destroy_service_spec.rb
@@ -4,9 +4,10 @@ describe Users::DestroyService do
describe "Deletes a user and all their personal projects" do
let!(:user) { create(:user) }
let!(:admin) { create(:admin) }
- let!(:namespace) { create(:namespace, owner: user) }
+ let!(:namespace) { user.namespace }
let!(:project) { create(:project, namespace: namespace) }
let(:service) { described_class.new(admin) }
+ let(:gitlab_shell) { Gitlab::Shell.new }
context 'no options are given' do
it 'deletes the user' do
@@ -14,7 +15,7 @@ describe Users::DestroyService do
expect { user_data['email'].to eq(user.email) }
expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
- expect { Namespace.with_deleted.find(user.namespace.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { Namespace.with_deleted.find(namespace.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
it 'will delete the project' do
@@ -165,5 +166,27 @@ describe Users::DestroyService do
expect(Issue.exists?(issue.id)).to be_falsy
end
end
+
+ describe "user personal's repository removal" do
+ before do
+ Sidekiq::Testing.inline! { service.execute(user) }
+ end
+
+ context 'legacy storage' do
+ let!(:project) { create(:project, :empty_repo, namespace: user.namespace) }
+
+ it 'removes repository' do
+ expect(gitlab_shell.exists?(project.repository_storage_path, "#{project.disk_path}.git")).to be_falsey
+ end
+ end
+
+ context 'hashed storage' do
+ let!(:project) { create(:project, :empty_repo, :hashed, namespace: user.namespace) }
+
+ it 'removes repository' do
+ expect(gitlab_shell.exists?(project.repository_storage_path, "#{project.disk_path}.git")).to be_falsey
+ end
+ end
+ end
end
end
diff --git a/spec/support/background_migrations_matchers.rb b/spec/support/background_migrations_matchers.rb
new file mode 100644
index 00000000000..423c0e4cefc
--- /dev/null
+++ b/spec/support/background_migrations_matchers.rb
@@ -0,0 +1,13 @@
+RSpec::Matchers.define :be_scheduled_migration do |delay, *expected|
+ match do |migration|
+ BackgroundMigrationWorker.jobs.any? do |job|
+ job['args'] == [migration, expected] &&
+ job['at'].to_i == (delay.to_i + Time.now.to_i)
+ end
+ end
+
+ failure_message do |migration|
+ "Migration `#{migration}` with args `#{expected.inspect}` " \
+ 'not scheduled in expected time!'
+ end
+end
diff --git a/spec/workers/authorized_projects_worker_spec.rb b/spec/workers/authorized_projects_worker_spec.rb
index 03b9b99e263..f8385ae7c72 100644
--- a/spec/workers/authorized_projects_worker_spec.rb
+++ b/spec/workers/authorized_projects_worker_spec.rb
@@ -29,21 +29,27 @@ describe AuthorizedProjectsWorker do
end
describe '#perform' do
- subject { described_class.new }
+ let(:user) { create(:user) }
- it "refreshes user's authorized projects" do
- user = create(:user)
+ subject(:job) { described_class.new }
+ it "refreshes user's authorized projects" do
expect_any_instance_of(User).to receive(:refresh_authorized_projects)
- subject.perform(user.id)
+ job.perform(user.id)
+ end
+
+ it 'notifies the JobWaiter when done if the key is provided' do
+ expect(Gitlab::JobWaiter).to receive(:notify).with('notify-key', job.jid)
+
+ job.perform(user.id, 'notify-key')
end
context "when the user is not found" do
it "does nothing" do
expect_any_instance_of(User).not_to receive(:refresh_authorized_projects)
- subject.perform(-1)
+ job.perform(-1)
end
end
end
diff --git a/spec/workers/namespaceless_project_destroy_worker_spec.rb b/spec/workers/namespaceless_project_destroy_worker_spec.rb
index f2706254284..817e103fd9a 100644
--- a/spec/workers/namespaceless_project_destroy_worker_spec.rb
+++ b/spec/workers/namespaceless_project_destroy_worker_spec.rb
@@ -5,7 +5,7 @@ describe NamespacelessProjectDestroyWorker do
before do
# Stub after_save callbacks that will fail when Project has no namespace
- allow_any_instance_of(Project).to receive(:ensure_storage_path_exist).and_return(nil)
+ allow_any_instance_of(Project).to receive(:ensure_storage_path_exists).and_return(nil)
allow_any_instance_of(Project).to receive(:update_project_statistics).and_return(nil)
end
diff --git a/spec/workers/stage_update_worker_spec.rb b/spec/workers/stage_update_worker_spec.rb
new file mode 100644
index 00000000000..7bc76c79464
--- /dev/null
+++ b/spec/workers/stage_update_worker_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe StageUpdateWorker do
+ describe '#perform' do
+ context 'when stage exists' do
+ let(:stage) { create(:ci_stage_entity) }
+
+ it 'updates stage status' do
+ expect_any_instance_of(Ci::Stage).to receive(:update_status)
+
+ described_class.new.perform(stage.id)
+ end
+ end
+
+ context 'when stage does not exist' do
+ it 'does not raise exception' do
+ expect { described_class.new.perform(123) }
+ .not_to raise_error
+ end
+ end
+ end
+end