diff options
author | Lin Jen-Shin <godfat@godfat.org> | 2016-12-14 19:24:31 +0800 |
---|---|---|
committer | Lin Jen-Shin <godfat@godfat.org> | 2016-12-14 19:24:31 +0800 |
commit | 367024f1707ebbf986e5f25ac208f24e35746389 (patch) | |
tree | 8ff45ed585720aa4eb2f5a6c772a2a20f89b946f /spec/models | |
parent | 101cde38cf6d5506ea37c5f912fb4c37af50c541 (diff) | |
parent | 3a90612660ab90225907ec6d79032905885c2507 (diff) | |
download | gitlab-ce-367024f1707ebbf986e5f25ac208f24e35746389.tar.gz |
Merge remote-tracking branch 'upstream/master' into show-commit-status-from-latest-pipeline
* upstream/master: (557 commits)
Fix wrong error message expectation in API::Commits spec
Move admin settings spinach feature to rspec
Encode when migrating ProcessCommitWorker jobs
Prevent overflow with vertical scroll when we have space to show content
Make rubocop happy
API: Ability to cherry-pick a commit
Be smarter when finding a sudoed user in API::Helpers
Backport hooks on group policies for the EE-specific implementation
API: Ability to get group's project in simple representation
Add AddLowerPathIndexToRoutes to setup_postgresql.rake
For single line git commit messages, the close quote should be on the same line as the open quote
added border-radius and padding to labels
Allow all alphanumeric characters in file names (!8002)
Add failing test for #20190
Don't allow blank MR titles in API
Replace static fixture for awards_handler_spec (!7661)
Crontab typo '* */6' -> '0 */6' (4x/day not 1x-per-min-for-1h 4x/day)
Fix test
Tweak style and add back wording
Clean up commit copy to clipboard and make consistent
...
Diffstat (limited to 'spec/models')
38 files changed, 1101 insertions, 291 deletions
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index ef07f2275b1..d4970e38f7c 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -730,8 +730,8 @@ describe Ci::Build, models: true do pipeline2 = create(:ci_pipeline, project: project) @build2 = create(:ci_build, pipeline: pipeline2) - commits = [double(id: pipeline.sha), double(id: pipeline2.sha)] - allow(@merge_request).to receive(:commits).and_return(commits) + allow(@merge_request).to receive(:commits_sha). + and_return([pipeline.sha, pipeline2.sha]) allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request]) end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index ea022e03608..8158e71dd55 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Ci::Pipeline, models: true do + include EmailHelpers + let(:project) { FactoryGirl.create :empty_project } let(:pipeline) { FactoryGirl.create :ci_empty_pipeline, status: 'created', project: project } @@ -18,8 +20,6 @@ describe Ci::Pipeline, models: true do it { is_expected.to respond_to :git_author_email } it { is_expected.to respond_to :short_sha } - it { is_expected.to delegate_method(:stages).to(:statuses) } - describe '#valid_commit_sha' do context 'commit.sha can not start with 00000000' do before do @@ -123,16 +123,55 @@ describe Ci::Pipeline, models: true do end describe '#stages' do - let(:pipeline2) { FactoryGirl.create :ci_pipeline, project: project } - subject { CommitStatus.where(pipeline: [pipeline, pipeline2]).stages } - before do - FactoryGirl.create :ci_build, pipeline: pipeline2, stage: 'test', stage_idx: 1 - FactoryGirl.create :ci_build, pipeline: pipeline, stage: 'build', stage_idx: 0 + create(:commit_status, pipeline: pipeline, stage: 'build', name: 'linux', stage_idx: 0, status: 'success') + create(:commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'failed') + create(:commit_status, pipeline: pipeline, stage: 'deploy', name: 'staging', stage_idx: 2, status: 'running') + create(:commit_status, pipeline: pipeline, stage: 'test', name: 'rspec', stage_idx: 1, status: 'success') + end + + subject { pipeline.stages } + + context 'stages list' do + it 'returns ordered list of stages' do + expect(subject.map(&:name)).to eq(%w[build test deploy]) + end + end + + it 'returns a valid number of stages' do + expect(pipeline.stages_count).to eq(3) end - it 'return all stages' do - is_expected.to eq(%w(build test)) + it 'returns a valid names of stages' do + expect(pipeline.stages_name).to eq(['build', 'test', 'deploy']) + end + + context 'stages with statuses' do + let(:statuses) do + subject.map do |stage| + [stage.name, stage.status] + end + end + + it 'returns list of stages with statuses' do + expect(statuses).to eq([['build', 'failed'], + ['test', 'success'], + ['deploy', 'running'] + ]) + end + + context 'when build is retried' do + before do + create(:commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'success') + end + + it 'ignores the previous state' do + expect(statuses).to eq([['build', 'success'], + ['test', 'success'], + ['deploy', 'running'] + ]) + end + end end end @@ -402,6 +441,230 @@ describe Ci::Pipeline, models: true do end end + describe '#detailed_status' do + context 'when pipeline is created' do + let(:pipeline) { create(:ci_pipeline, status: :created) } + + it 'returns detailed status for created pipeline' do + expect(pipeline.detailed_status.text).to eq 'created' + end + end + + context 'when pipeline is pending' do + let(:pipeline) { create(:ci_pipeline, status: :pending) } + + it 'returns detailed status for pending pipeline' do + expect(pipeline.detailed_status.text).to eq 'pending' + end + end + + context 'when pipeline is running' do + let(:pipeline) { create(:ci_pipeline, status: :running) } + + it 'returns detailed status for running pipeline' do + expect(pipeline.detailed_status.text).to eq 'running' + end + end + + context 'when pipeline is successful' do + let(:pipeline) { create(:ci_pipeline, status: :success) } + + it 'returns detailed status for successful pipeline' do + expect(pipeline.detailed_status.text).to eq 'passed' + end + end + + context 'when pipeline is failed' do + let(:pipeline) { create(:ci_pipeline, status: :failed) } + + it 'returns detailed status for failed pipeline' do + expect(pipeline.detailed_status.text).to eq 'failed' + end + end + + context 'when pipeline is canceled' do + let(:pipeline) { create(:ci_pipeline, status: :canceled) } + + it 'returns detailed status for canceled pipeline' do + expect(pipeline.detailed_status.text).to eq 'canceled' + end + end + + context 'when pipeline is skipped' do + let(:pipeline) { create(:ci_pipeline, status: :skipped) } + + it 'returns detailed status for skipped pipeline' do + expect(pipeline.detailed_status.text).to eq 'skipped' + end + end + + context 'when pipeline is successful but with warnings' do + let(:pipeline) { create(:ci_pipeline, status: :success) } + + before do + create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline) + end + + it 'retruns detailed status for successful pipeline with warnings' do + expect(pipeline.detailed_status.label).to eq 'passed with warnings' + end + end + end + + describe '#cancelable?' do + %i[created running pending].each do |status0| + context "when there is a build #{status0}" do + before do + create(:ci_build, status0, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end + + context "when there is an external job #{status0}" do + before do + create(:generic_commit_status, status0, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end + + %i[success failed canceled].each do |status1| + context "when there are generic_commit_status jobs for #{status0} and #{status1}" do + before do + create(:generic_commit_status, status0, pipeline: pipeline) + create(:generic_commit_status, status1, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end + + context "when there are generic_commit_status and ci_build jobs for #{status0} and #{status1}" do + before do + create(:generic_commit_status, status0, pipeline: pipeline) + create(:ci_build, status1, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end + + context "when there are ci_build jobs for #{status0} and #{status1}" do + before do + create(:ci_build, status0, pipeline: pipeline) + create(:ci_build, status1, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end + end + end + + %i[success failed canceled].each do |status| + context "when there is a build #{status}" do + before do + create(:ci_build, status, pipeline: pipeline) + end + + it 'is not cancelable' do + expect(pipeline.cancelable?).to be_falsey + end + end + + context "when there is an external job #{status}" do + before do + create(:generic_commit_status, status, pipeline: pipeline) + end + + it 'is not cancelable' do + expect(pipeline.cancelable?).to be_falsey + end + end + end + end + + describe '#cancel_running' do + let(:latest_status) { pipeline.statuses.pluck(:status) } + + context 'when there is a running external job and created build' do + before do + create(:ci_build, :running, pipeline: pipeline) + create(:generic_commit_status, :running, pipeline: pipeline) + + pipeline.cancel_running + end + + it 'cancels both jobs' do + expect(latest_status).to contain_exactly('canceled', 'canceled') + end + end + + context 'when builds are in different stages' do + before do + create(:ci_build, :running, stage_idx: 0, pipeline: pipeline) + create(:ci_build, :running, stage_idx: 1, pipeline: pipeline) + + pipeline.cancel_running + end + + it 'cancels both jobs' do + expect(latest_status).to contain_exactly('canceled', 'canceled') + end + end + end + + describe '#retry_failed' do + let(:latest_status) { pipeline.statuses.latest.pluck(:status) } + + context 'when there is a failed build and failed external status' do + before do + create(:ci_build, :failed, name: 'build', pipeline: pipeline) + create(:generic_commit_status, :failed, name: 'jenkins', pipeline: pipeline) + + pipeline.retry_failed(create(:user)) + end + + it 'retries only build' do + expect(latest_status).to contain_exactly('pending', 'failed') + end + end + + context 'when builds are in different stages' do + before do + create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline) + create(:ci_build, :failed, name: 'jenkins', stage_idx: 1, pipeline: pipeline) + + pipeline.retry_failed(create(:user)) + end + + it 'retries both builds' do + expect(latest_status).to contain_exactly('pending', 'pending') + end + end + + context 'when there are canceled and failed' do + before do + create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline) + create(:ci_build, :canceled, name: 'jenkins', stage_idx: 1, pipeline: pipeline) + + pipeline.retry_failed(create(:user)) + end + + it 'retries both builds' do + expect(latest_status).to contain_exactly('pending', 'pending') + end + end + end + describe '#execute_hooks' do let!(:build_a) { create_build('a', 0) } let!(:build_b) { create_build('b', 1) } diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb new file mode 100644 index 00000000000..f232761dba2 --- /dev/null +++ b/spec/models/ci/stage_spec.rb @@ -0,0 +1,133 @@ +require 'spec_helper' + +describe Ci::Stage, models: true do + let(:stage) { build(:ci_stage) } + let(:pipeline) { stage.pipeline } + let(:stage_name) { stage.name } + + describe '#expectations' do + subject { stage } + + it { is_expected.to include_module(StaticModel) } + + it { is_expected.to respond_to(:pipeline) } + it { is_expected.to respond_to(:name) } + + it { is_expected.to delegate_method(:project).to(:pipeline) } + end + + describe '#statuses' do + let!(:stage_build) { create_job(:ci_build) } + let!(:commit_status) { create_job(:commit_status) } + let!(:other_build) { create_job(:ci_build, stage: 'other stage') } + + subject { stage.statuses } + + it "returns only matching statuses" do + is_expected.to contain_exactly(stage_build, commit_status) + end + end + + describe '#builds' do + let!(:stage_build) { create_job(:ci_build) } + let!(:commit_status) { create_job(:commit_status) } + + subject { stage.builds } + + it "returns only builds" do + is_expected.to contain_exactly(stage_build) + end + end + + describe '#status' do + subject { stage.status } + + context 'if status is already defined' do + let(:stage) { build(:ci_stage, status: 'success') } + + it "returns defined status" do + is_expected.to eq('success') + end + end + + context 'if status has to be calculated' do + let!(:stage_build) { create_job(:ci_build, status: :failed) } + + it "returns status of a build" do + is_expected.to eq('failed') + end + + context 'and builds are retried' do + let!(:new_build) { create_job(:ci_build, status: :success) } + + it "returns status of latest build" do + is_expected.to eq('success') + end + end + end + end + + describe '#detailed_status' do + subject { stage.detailed_status } + + context 'when build is created' do + let!(:stage_build) { create_job(:ci_build, status: :created) } + + it 'returns detailed status for created stage' do + expect(subject.text).to eq 'created' + end + end + + context 'when build is pending' do + let!(:stage_build) { create_job(:ci_build, status: :pending) } + + it 'returns detailed status for pending stage' do + expect(subject.text).to eq 'pending' + end + end + + context 'when build is running' do + let!(:stage_build) { create_job(:ci_build, status: :running) } + + it 'returns detailed status for running stage' do + expect(subject.text).to eq 'running' + end + end + + context 'when build is successful' do + let!(:stage_build) { create_job(:ci_build, status: :success) } + + it 'returns detailed status for successful stage' do + expect(subject.text).to eq 'passed' + end + end + + context 'when build is failed' do + let!(:stage_build) { create_job(:ci_build, status: :failed) } + + it 'returns detailed status for failed stage' do + expect(subject.text).to eq 'failed' + end + end + + context 'when build is canceled' do + let!(:stage_build) { create_job(:ci_build, status: :canceled) } + + it 'returns detailed status for canceled stage' do + expect(subject.text).to eq 'canceled' + end + end + + context 'when build is skipped' do + let!(:stage_build) { create_job(:ci_build, status: :skipped) } + + it 'returns detailed status for skipped stage' do + expect(subject.text).to eq 'skipped' + end + end + end + + def create_job(type, status: 'success', stage: stage_name) + create(type, pipeline: pipeline, stage: stage, status: status) + end +end diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index 4e7833c3162..bee9f714849 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -5,6 +5,13 @@ describe Ci::Variable, models: true do let(:secret_value) { 'secret' } + it { is_expected.to validate_presence_of(:key) } + it { is_expected.to validate_uniqueness_of(:key).scoped_to(:gl_project_id) } + it { is_expected.to validate_length_of(:key).is_at_most(255) } + it { is_expected.to allow_value('foo').for(:key) } + it { is_expected.not_to allow_value('foo bar').for(:key) } + it { is_expected.not_to allow_value('foo/bar').for(:key) } + before :each do subject.value = secret_value end diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb index c41359b55a3..30782ca75a0 100644 --- a/spec/models/commit_range_spec.rb +++ b/spec/models/commit_range_spec.rb @@ -45,7 +45,7 @@ describe CommitRange, models: true do end describe '#to_reference' do - let(:cross) { create(:project) } + let(:cross) { create(:empty_project, namespace: project.namespace) } it 'returns a String reference to the object' do expect(range.to_reference).to eq "#{full_sha_from}...#{full_sha_to}" @@ -56,12 +56,12 @@ describe CommitRange, models: true do end it 'supports a cross-project reference' do - expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{full_sha_from}...#{full_sha_to}" + expect(range.to_reference(cross)).to eq "#{project.path}@#{full_sha_from}...#{full_sha_to}" end end describe '#reference_link_text' do - let(:cross) { create(:project) } + let(:cross) { create(:empty_project, namespace: project.namespace) } it 'returns a String reference to the object' do expect(range.reference_link_text).to eq "#{sha_from}...#{sha_to}" @@ -72,7 +72,7 @@ describe CommitRange, models: true do end it 'supports a cross-project reference' do - expect(range.reference_link_text(cross)).to eq "#{project.to_reference}@#{sha_from}...#{sha_to}" + expect(range.reference_link_text(cross)).to eq "#{project.path}@#{sha_from}...#{sha_to}" end end @@ -137,26 +137,25 @@ describe CommitRange, models: true do end describe '#has_been_reverted?' do - it 'returns true if the commit has been reverted' do - issue = create(:issue) + let(:issue) { create(:issue) } + let(:user) { issue.author } + it 'returns true if the commit has been reverted' do create(:note_on_issue, noteable: issue, system: true, - note: commit1.revert_description, + note: commit1.revert_description(user), project: issue.project) expect_any_instance_of(Commit).to receive(:reverts_commit?). - with(commit1). + with(commit1, user). and_return(true) - expect(commit1.has_been_reverted?(nil, issue)).to eq(true) + expect(commit1.has_been_reverted?(user, issue)).to eq(true) end it 'returns false a commit has not been reverted' do - issue = create(:issue) - - expect(commit1.has_been_reverted?(nil, issue)).to eq(false) + expect(commit1.has_been_reverted?(user, issue)).to eq(false) end end end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 62b77ef86c5..b81fab0372a 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -34,24 +34,30 @@ describe Commit, models: true do end describe '#to_reference' do + let(:project) { create(:project, path: 'sample-project') } + let(:commit) { project.commit } + it 'returns a String reference to the object' do expect(commit.to_reference).to eq commit.id end it 'supports a cross-project reference' do - cross = double('project') - expect(commit.to_reference(cross)).to eq "#{project.to_reference}@#{commit.id}" + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(commit.to_reference(another_project)).to eq "sample-project@#{commit.id}" end end describe '#reference_link_text' do + let(:project) { create(:project, path: 'sample-project') } + let(:commit) { project.commit } + it 'returns a String reference to the object' do expect(commit.reference_link_text).to eq commit.short_id end it 'supports a cross-project reference' do - cross = double('project') - expect(commit.reference_link_text(cross)).to eq "#{project.to_reference}@#{commit.short_id}" + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(commit.reference_link_text(another_project)).to eq "sample-project@#{commit.short_id}" end end @@ -173,25 +179,26 @@ eos describe '#reverts_commit?' do let(:another_commit) { double(:commit, revert_description: "This reverts commit #{commit.sha}") } + let(:user) { commit.author } - it { expect(commit.reverts_commit?(another_commit)).to be_falsy } + it { expect(commit.reverts_commit?(another_commit, user)).to be_falsy } context 'commit has no description' do before { allow(commit).to receive(:description?).and_return(false) } - it { expect(commit.reverts_commit?(another_commit)).to be_falsy } + it { expect(commit.reverts_commit?(another_commit, user)).to be_falsy } end context "another_commit's description does not revert commit" do before { allow(commit).to receive(:description).and_return("Foo Bar") } - it { expect(commit.reverts_commit?(another_commit)).to be_falsy } + it { expect(commit.reverts_commit?(another_commit, user)).to be_falsy } end context "another_commit's description reverts commit" do before { allow(commit).to receive(:description).and_return("Foo #{another_commit.revert_description} Bar") } - it { expect(commit.reverts_commit?(another_commit)).to be_truthy } + it { expect(commit.reverts_commit?(another_commit, user)).to be_truthy } end context "another_commit's description reverts merged merge request" do @@ -201,7 +208,7 @@ eos allow(commit).to receive(:description).and_return("Foo #{another_commit.revert_description} Bar") end - it { expect(commit.reverts_commit?(another_commit)).to be_truthy } + it { expect(commit.reverts_commit?(another_commit, user)).to be_truthy } end end @@ -297,4 +304,21 @@ eos expect(commit.uri_type('this/path/doesnt/exist')).to be_nil end end + + describe '.from_hash' do + let(:new_commit) { described_class.from_hash(commit.to_hash, project) } + + it 'returns a Commit' do + expect(new_commit).to be_an_instance_of(described_class) + end + + it 'wraps a Gitlab::Git::Commit' do + expect(new_commit.raw).to be_an_instance_of(Gitlab::Git::Commit) + end + + it 'stores the correct commit fields' do + expect(new_commit.id).to eq(commit.id) + expect(new_commit.message).to eq(commit.message) + end + end end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 80c2a1bc7a9..1ec08c2a9d0 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -175,7 +175,7 @@ describe CommitStatus, models: true do end it 'returns statuses without what we want to ignore' do - is_expected.to eq(statuses.values_at(1, 2, 4, 5, 6, 8, 9)) + is_expected.to eq(statuses.values_at(0, 1, 2, 3, 4, 5, 6, 8, 9)) end end @@ -200,49 +200,6 @@ describe CommitStatus, models: true do end end - describe '#stages' do - before do - create :commit_status, pipeline: pipeline, stage: 'build', name: 'linux', stage_idx: 0, status: 'success' - create :commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'failed' - create :commit_status, pipeline: pipeline, stage: 'deploy', name: 'staging', stage_idx: 2, status: 'running' - create :commit_status, pipeline: pipeline, stage: 'test', name: 'rspec', stage_idx: 1, status: 'success' - end - - context 'stages list' do - subject { CommitStatus.where(pipeline: pipeline).stages } - - it 'returns ordered list of stages' do - is_expected.to eq(%w[build test deploy]) - end - end - - context 'stages with statuses' do - subject { CommitStatus.where(pipeline: pipeline).latest.stages_status } - - it 'returns list of stages with statuses' do - is_expected.to eq({ - 'build' => 'failed', - 'test' => 'success', - 'deploy' => 'running' - }) - end - - context 'when build is retried' do - before do - create :commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'success' - end - - it 'ignores a previous state' do - is_expected.to eq({ - 'build' => 'success', - 'test' => 'success', - 'deploy' => 'running' - }) - end - end - end - end - describe '#commit' do it 'returns commit pipeline has been created for' do expect(commit_status.commit).to eq project.commit diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb index 87bffbdc54e..4d0f51fe82a 100644 --- a/spec/models/concerns/has_status_spec.rb +++ b/spec/models/concerns/has_status_spec.rb @@ -48,7 +48,7 @@ describe HasStatus do [create(type, status: :failed, allow_failure: true)] end - it { is_expected.to eq 'success' } + it { is_expected.to eq 'skipped' } end context 'success and canceled' do @@ -123,4 +123,100 @@ describe HasStatus do it_behaves_like 'build status summary' end end + + context 'for scope with one status' do + shared_examples 'having a job' do |status| + %i[ci_build generic_commit_status].each do |type| + context "when it's #{status} #{type} job" do + let!(:job) { create(type, status) } + + describe ".#{status}" do + it 'contains the job' do + expect(CommitStatus.public_send(status).all). + to contain_exactly(job) + end + end + + describe '.relevant' do + if status == :created + it 'contains nothing' do + expect(CommitStatus.relevant.all).to be_empty + end + else + it 'contains the job' do + expect(CommitStatus.relevant.all).to contain_exactly(job) + end + end + end + end + end + end + + %i[created running pending success + failed canceled skipped].each do |status| + it_behaves_like 'having a job', status + end + end + + context 'for scope with more statuses' do + shared_examples 'containing the job' do |status| + %i[ci_build generic_commit_status].each do |type| + context "when it's #{status} #{type} job" do + let!(:job) { create(type, status) } + + it 'contains the job' do + is_expected.to contain_exactly(job) + end + end + end + end + + shared_examples 'not containing the job' do |status| + %i[ci_build generic_commit_status].each do |type| + context "when it's #{status} #{type} job" do + let!(:job) { create(type, status) } + + it 'contains nothing' do + is_expected.to be_empty + end + end + end + end + + describe '.running_or_pending' do + subject { CommitStatus.running_or_pending } + + %i[running pending].each do |status| + it_behaves_like 'containing the job', status + end + + %i[created failed success].each do |status| + it_behaves_like 'not containing the job', status + end + end + + describe '.finished' do + subject { CommitStatus.finished } + + %i[success failed canceled].each do |status| + it_behaves_like 'containing the job', status + end + + %i[created running pending].each do |status| + it_behaves_like 'not containing the job', status + end + end + + describe '.cancelable' do + subject { CommitStatus.cancelable } + + %i[running pending created].each do |status| + it_behaves_like 'containing the job', status + end + + %i[failed success skipped canceled].each do |status| + it_behaves_like 'not containing the job', status + end + end + end end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 6f84bffe046..4fa06a8c60a 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -35,7 +35,7 @@ describe Issue, "Issuable" do it { is_expected.to validate_presence_of(:iid) } it { is_expected.to validate_presence_of(:author) } it { is_expected.to validate_presence_of(:title) } - it { is_expected.to validate_length_of(:title).is_at_least(0).is_at_most(255) } + it { is_expected.to validate_length_of(:title).is_at_most(255) } end describe "Scope" do diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb new file mode 100644 index 00000000000..b556135532f --- /dev/null +++ b/spec/models/concerns/routable_spec.rb @@ -0,0 +1,71 @@ +require 'spec_helper' + +describe Group, 'Routable' do + let!(:group) { create(:group) } + + describe 'Validations' do + it { is_expected.to validate_presence_of(:route) } + end + + describe 'Associations' do + it { is_expected.to have_one(:route).dependent(:destroy) } + end + + describe 'Callbacks' do + it 'creates route record on create' do + expect(group.route.path).to eq(group.path) + end + + it 'updates route record on path change' do + group.update_attributes(path: 'wow') + + expect(group.route.path).to eq('wow') + end + + it 'ensure route path uniqueness across different objects' do + create(:group, parent: group, path: 'xyz') + duplicate = build(:project, namespace: group, path: 'xyz') + + expect { duplicate.save! }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Route path has already been taken, Route is invalid') + end + end + + describe '.find_by_full_path' do + let!(:nested_group) { create(:group, parent: group) } + + it { expect(described_class.find_by_full_path(group.to_param)).to eq(group) } + it { expect(described_class.find_by_full_path(group.to_param.upcase)).to eq(group) } + it { expect(described_class.find_by_full_path(nested_group.to_param)).to eq(nested_group) } + it { expect(described_class.find_by_full_path('unknown')).to eq(nil) } + end + + describe '.where_full_path_in' do + context 'without any paths' do + it 'returns an empty relation' do + expect(described_class.where_full_path_in([])).to eq([]) + end + end + + context 'without any valid paths' do + it 'returns an empty relation' do + expect(described_class.where_full_path_in(%w[unknown])).to eq([]) + end + end + + context 'with valid paths' do + let!(:nested_group) { create(:group, parent: group) } + + it 'returns the projects matching the paths' do + result = described_class.where_full_path_in([group.to_param, nested_group.to_param]) + + expect(result).to contain_exactly(group, nested_group) + end + + it 'returns projects regardless of the casing of paths' do + result = described_class.where_full_path_in([group.to_param.upcase, nested_group.to_param.upcase]) + + expect(result).to contain_exactly(group, nested_group) + end + end + end +end diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb index 7691d690db0..7771785ead3 100644 --- a/spec/models/cycle_analytics/code_spec.rb +++ b/spec/models/cycle_analytics/code_spec.rb @@ -6,7 +6,7 @@ describe 'CycleAnalytics#code', feature: true do let(:project) { create(:project) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } - subject { CycleAnalytics.new(project, from: from_date) } + subject { CycleAnalytics.new(project, user, from: from_date) } context 'with deployment' do generate_cycle_analytics_spec( diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb index f649b44d367..5ed3d37f2fb 100644 --- a/spec/models/cycle_analytics/issue_spec.rb +++ b/spec/models/cycle_analytics/issue_spec.rb @@ -6,7 +6,7 @@ describe 'CycleAnalytics#issue', models: true do let(:project) { create(:project) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } - subject { CycleAnalytics.new(project, from: from_date) } + subject { CycleAnalytics.new(project, user, from: from_date) } generate_cycle_analytics_spec( phase: :issue, diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb index 2cdefbeef21..baf3e3241a1 100644 --- a/spec/models/cycle_analytics/plan_spec.rb +++ b/spec/models/cycle_analytics/plan_spec.rb @@ -6,7 +6,7 @@ describe 'CycleAnalytics#plan', feature: true do let(:project) { create(:project) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } - subject { CycleAnalytics.new(project, from: from_date) } + subject { CycleAnalytics.new(project, user, from: from_date) } generate_cycle_analytics_spec( phase: :plan, diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb index 1f5e5cab92d..21b9c6e7150 100644 --- a/spec/models/cycle_analytics/production_spec.rb +++ b/spec/models/cycle_analytics/production_spec.rb @@ -6,7 +6,7 @@ describe 'CycleAnalytics#production', feature: true do let(:project) { create(:project) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } - subject { CycleAnalytics.new(project, from: from_date) } + subject { CycleAnalytics.new(project, user, from: from_date) } generate_cycle_analytics_spec( phase: :production, diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb index 0ed080a42b1..158621d59a4 100644 --- a/spec/models/cycle_analytics/review_spec.rb +++ b/spec/models/cycle_analytics/review_spec.rb @@ -6,7 +6,7 @@ describe 'CycleAnalytics#review', feature: true do let(:project) { create(:project) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } - subject { CycleAnalytics.new(project, from: from_date) } + subject { CycleAnalytics.new(project, user, from: from_date) } generate_cycle_analytics_spec( phase: :review, diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb index af1c4477ddb..dad653964b7 100644 --- a/spec/models/cycle_analytics/staging_spec.rb +++ b/spec/models/cycle_analytics/staging_spec.rb @@ -6,7 +6,7 @@ describe 'CycleAnalytics#staging', feature: true do let(:project) { create(:project) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } - subject { CycleAnalytics.new(project, from: from_date) } + subject { CycleAnalytics.new(project, user, from: from_date) } generate_cycle_analytics_spec( phase: :staging, diff --git a/spec/models/cycle_analytics/summary_spec.rb b/spec/models/cycle_analytics/summary_spec.rb index 9d67bc82cba..725bc68b25f 100644 --- a/spec/models/cycle_analytics/summary_spec.rb +++ b/spec/models/cycle_analytics/summary_spec.rb @@ -4,7 +4,7 @@ describe CycleAnalytics::Summary, models: true do let(:project) { create(:project) } let(:from) { Time.now } let(:user) { create(:user, :admin) } - subject { described_class.new(project, from: from) } + subject { described_class.new(project, user, from: from) } describe "#new_issues" do it "finds the number of issues created after the 'from date'" do diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb index 02ddfeed9c1..2313724e8f3 100644 --- a/spec/models/cycle_analytics/test_spec.rb +++ b/spec/models/cycle_analytics/test_spec.rb @@ -6,7 +6,7 @@ describe 'CycleAnalytics#test', feature: true do let(:project) { create(:project) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } - subject { CycleAnalytics.new(project, from: from_date) } + subject { CycleAnalytics.new(project, user, from: from_date) } generate_cycle_analytics_spec( phase: :test, diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb index 0142706d140..bc32fadd391 100644 --- a/spec/models/discussion_spec.rb +++ b/spec/models/discussion_spec.rb @@ -521,6 +521,15 @@ describe Discussion, model: true do end end + describe "#first_note_to_resolve" do + it "returns the first not that still needs to be resolved" do + allow(first_note).to receive(:to_be_resolved?).and_return(false) + allow(second_note).to receive(:to_be_resolved?).and_return(true) + + expect(subject.first_note_to_resolve).to eq(second_note) + end + end + describe "#collapsed?" do context "when a diff discussion" do before do @@ -590,4 +599,23 @@ describe Discussion, model: true do end end end + + describe "#truncated_diff_lines" do + let(:truncated_lines) { subject.truncated_diff_lines } + + context "when diff is greater than allowed number of truncated diff lines " do + it "returns fewer lines" do + expect(subject.diff_lines.count).to be > described_class::NUMBER_OF_TRUNCATED_DIFF_LINES + + expect(truncated_lines.count).to be <= described_class::NUMBER_OF_TRUNCATED_DIFF_LINES + end + end + + context "when some diff lines are meta" do + it "returns no meta lines" do + expect(subject.diff_lines).to include(be_meta) + expect(truncated_lines).not_to include(be_meta) + end + end + end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index d06665197db..c8170164898 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -13,9 +13,9 @@ describe Environment, models: true do it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) } - it { is_expected.to validate_length_of(:name).is_within(0..255) } + it { is_expected.to validate_length_of(:name).is_at_most(255) } - it { is_expected.to validate_length_of(:external_url).is_within(0..255) } + it { is_expected.to validate_length_of(:external_url).is_at_most(255) } # To circumvent a not null violation of the name column: # https://github.com/thoughtbot/shoulda-matchers/issues/336 diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index b684053cd02..f8660da031d 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -260,6 +260,24 @@ describe Event, models: true do end end + describe '#authored_by?' do + let(:event) { build(:event) } + + it 'returns true when the event author and user are the same' do + expect(event.authored_by?(event.author)).to eq(true) + end + + it 'returns false when passing nil as an argument' do + expect(event.authored_by?(nil)).to eq(false) + end + + it 'returns false when the given user is not the author of the event' do + user = double(:user, id: -1) + + expect(event.authored_by?(user)).to eq(false) + end + end + def create_event(project, user, attrs = {}) data = { before: Gitlab::Git::BLANK_SHA, diff --git a/spec/models/group_label_spec.rb b/spec/models/group_label_spec.rb index 2369658bf78..668aa6fb357 100644 --- a/spec/models/group_label_spec.rb +++ b/spec/models/group_label_spec.rb @@ -37,6 +37,16 @@ describe GroupLabel, models: true do end end + context 'cross-project' do + let(:namespace) { build_stubbed(:namespace) } + let(:source_project) { build_stubbed(:empty_project, name: 'project-1', namespace: namespace) } + let(:target_project) { build_stubbed(:empty_project, name: 'project-2', namespace: namespace) } + + it 'returns a String reference to the object' do + expect(label.to_reference(source_project, target_project)).to eq %(project-1~#{label.id}) + end + end + context 'using invalid format' do it 'raises error' do expect { label.to_reference(format: :invalid) } diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 1613a586a2c..850b1a3cf1e 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -271,4 +271,11 @@ describe Group, models: true do expect(group.web_url).to include("groups/#{group.name}") end end + + describe 'nested group' do + subject { create(:group, :nested) } + + it { is_expected.to be_valid } + it { expect(subject.parent).to be_kind_of(Group) } + end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 89e93dce8c5..24e216329a9 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -43,14 +43,16 @@ describe Issue, models: true do end describe '#to_reference' do + let(:project) { build(:empty_project, name: 'sample-project') } + let(:issue) { build(:issue, iid: 1, project: project) } + it 'returns a String reference to the object' do - expect(subject.to_reference).to eq "##{subject.iid}" + expect(issue.to_reference).to eq "#1" end it 'supports a cross-project reference' do - cross = double('project') - expect(subject.to_reference(cross)). - to eq "#{subject.project.to_reference}##{subject.iid}" + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(issue.to_reference(another_project)).to eq "sample-project#1" end end diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index 90731f55470..2a33d819138 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -7,9 +7,13 @@ describe Key, models: true do describe "Validation" do it { is_expected.to validate_presence_of(:title) } + it { is_expected.to validate_length_of(:title).is_at_most(255) } + it { is_expected.to validate_presence_of(:key) } - it { is_expected.to validate_length_of(:title).is_within(0..255) } - it { is_expected.to validate_length_of(:key).is_within(0..5000) } + it { is_expected.to validate_length_of(:key).is_at_most(5000) } + it { is_expected.to allow_value('ssh-foo').for(:key) } + it { is_expected.to allow_value('ecdsa-foo').for(:key) } + it { is_expected.not_to allow_value('foo-bar').for(:key) } end describe "Methods" do diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index e5007424041..eb876d105da 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -77,24 +77,13 @@ describe MergeRequestDiff, models: true do end describe '#commits_sha' do - shared_examples 'returning all commits SHA' do - it 'returns all commits SHA' do - commits_sha = subject.commits_sha + it 'returns all commits SHA using serialized commits' do + subject.st_commits = [ + { id: 'sha1' }, + { id: 'sha2' } + ] - expect(commits_sha).to eq(subject.commits.map(&:sha)) - end - end - - context 'when commits were loaded' do - before do - subject.commits - end - - it_behaves_like 'returning all commits SHA' - end - - context 'when commits were not loaded' do - it_behaves_like 'returning all commits SHA' + expect(subject.commits_sha).to eq(['sha1', 'sha2']) end end @@ -113,4 +102,15 @@ describe MergeRequestDiff, models: true do expect(diffs.size).to eq(3) end end + + describe '#commits_count' do + it 'returns number of commits using serialized commits' do + subject.st_commits = [ + { id: 'sha1' }, + { id: 'sha2' } + ] + + expect(subject.commits_count).to eq 2 + end + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 58ccd056328..8b730be91fd 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -31,7 +31,7 @@ describe MergeRequest, models: true do it { is_expected.to validate_presence_of(:target_branch) } it { is_expected.to validate_presence_of(:source_branch) } - context "Validation of merge user with Merge When Build succeeds" do + context "Validation of merge user with Merge When Pipeline Succeeds" do it "allows user to be nil when the feature is disabled" do expect(subject).to be_valid end @@ -142,13 +142,16 @@ describe MergeRequest, models: true do end describe '#to_reference' do + let(:project) { build(:empty_project, name: 'sample-project') } + let(:merge_request) { build(:merge_request, target_project: project, iid: 1) } + it 'returns a String reference to the object' do - expect(subject.to_reference).to eq "!#{subject.iid}" + expect(merge_request.to_reference).to eq "!1" end it 'supports a cross-project reference' do - cross = double('project') - expect(subject.to_reference(cross)).to eq "#{subject.source_project.to_reference}!#{subject.iid}" + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(merge_request.to_reference(another_project)).to eq "sample-project!1" end end @@ -557,20 +560,17 @@ describe MergeRequest, models: true do end describe '#commits_sha' do - let(:commit0) { double('commit0', sha: 'sha1') } - let(:commit1) { double('commit1', sha: 'sha2') } - let(:commit2) { double('commit2', sha: 'sha3') } - before do - allow(subject.merge_request_diff).to receive(:commits).and_return([commit0, commit1, commit2]) + allow(subject.merge_request_diff).to receive(:commits_sha). + and_return(['sha1']) end - it 'returns sha of commits' do - expect(subject.commits_sha).to contain_exactly('sha1', 'sha2', 'sha3') + it 'delegates to merge request diff' do + expect(subject.commits_sha).to eq ['sha1'] end end - describe '#pipeline' do + describe '#head_pipeline' do describe 'when the source project exists' do it 'returns the latest pipeline' do pipeline = double(:ci_pipeline, ref: 'master') @@ -581,7 +581,7 @@ describe MergeRequest, models: true do with('master', '123abc'). and_return(pipeline) - expect(subject.pipeline).to eq(pipeline) + expect(subject.head_pipeline).to eq(pipeline) end end @@ -589,7 +589,7 @@ describe MergeRequest, models: true do it 'returns nil' do allow(subject).to receive(:source_project).and_return(nil) - expect(subject.pipeline).to be_nil + expect(subject.head_pipeline).to be_nil end end end @@ -857,7 +857,7 @@ describe MergeRequest, models: true do context 'and a failed pipeline is associated' do before do pipeline.update(status: 'failed') - allow(subject).to receive(:pipeline) { pipeline } + allow(subject).to receive(:head_pipeline) { pipeline } end it { expect(subject.mergeable_ci_state?).to be_falsey } @@ -866,7 +866,7 @@ describe MergeRequest, models: true do context 'and a successful pipeline is associated' do before do pipeline.update(status: 'success') - allow(subject).to receive(:pipeline) { pipeline } + allow(subject).to receive(:head_pipeline) { pipeline } end it { expect(subject.mergeable_ci_state?).to be_truthy } @@ -875,7 +875,7 @@ describe MergeRequest, models: true do context 'and a skipped pipeline is associated' do before do pipeline.update(status: 'skipped') - allow(subject).to receive(:pipeline) { pipeline } + allow(subject).to receive(:head_pipeline) { pipeline } end it { expect(subject.mergeable_ci_state?).to be_truthy } @@ -883,7 +883,7 @@ describe MergeRequest, models: true do context 'when no pipeline is associated' do before do - allow(subject).to receive(:pipeline) { nil } + allow(subject).to receive(:head_pipeline) { nil } end it { expect(subject.mergeable_ci_state?).to be_truthy } @@ -896,7 +896,7 @@ describe MergeRequest, models: true do context 'and a failed pipeline is associated' do before do pipeline.statuses << create(:commit_status, status: 'failed', project: project) - allow(subject).to receive(:pipeline) { pipeline } + allow(subject).to receive(:head_pipeline) { pipeline } end it { expect(subject.mergeable_ci_state?).to be_truthy } @@ -904,7 +904,7 @@ describe MergeRequest, models: true do context 'when no pipeline is associated' do before do - allow(subject).to receive(:pipeline) { nil } + allow(subject).to receive(:head_pipeline) { nil } end it { expect(subject.mergeable_ci_state?).to be_truthy } @@ -1127,6 +1127,46 @@ describe MergeRequest, models: true do allow(subject).to receive(:diff_discussions).and_return([first_discussion, second_discussion, third_discussion]) end + describe '#resolvable_discussions' do + before do + allow(first_discussion).to receive(:to_be_resolved?).and_return(true) + allow(second_discussion).to receive(:to_be_resolved?).and_return(false) + allow(third_discussion).to receive(:to_be_resolved?).and_return(false) + end + + it 'includes only discussions that need to be resolved' do + expect(subject.resolvable_discussions).to eq([first_discussion]) + end + end + + describe '#discussions_can_be_resolved_by? user' do + let(:user) { build(:user) } + + context 'all discussions can be resolved by the user' do + before do + allow(first_discussion).to receive(:can_resolve?).with(user).and_return(true) + allow(second_discussion).to receive(:can_resolve?).with(user).and_return(true) + allow(third_discussion).to receive(:can_resolve?).with(user).and_return(true) + end + + it 'allows a user to resolve the discussions' do + expect(subject.discussions_can_be_resolved_by?(user)).to be(true) + end + end + + context 'one discussion cannot be resolved by the user' do + before do + allow(first_discussion).to receive(:can_resolve?).with(user).and_return(true) + allow(second_discussion).to receive(:can_resolve?).with(user).and_return(true) + allow(third_discussion).to receive(:can_resolve?).with(user).and_return(false) + end + + it 'allows a user to resolve the discussions' do + expect(subject.discussions_can_be_resolved_by?(user)).to be(false) + end + end + end + describe "#discussions_resolvable?" do context "when all discussions are unresolvable" do before do @@ -1440,4 +1480,26 @@ describe MergeRequest, models: true do end end end + + describe '#has_commits?' do + before do + allow(subject.merge_request_diff).to receive(:commits_count). + and_return(2) + end + + it 'returns true when merge request diff has commits' do + expect(subject.has_commits?).to be_truthy + end + end + + describe '#has_no_commits?' do + before do + allow(subject.merge_request_diff).to receive(:commits_count). + and_return(0) + end + + it 'returns true when merge request diff has 0 commits' do + expect(subject.has_no_commits?).to be_truthy + end + end end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index a4bfe851dfb..0cc2efae5f9 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -246,4 +246,18 @@ describe Milestone, models: true do end end end + + describe '#to_reference' do + let(:project) { build(:empty_project, name: 'sample-project') } + let(:milestone) { build(:milestone, iid: 1, project: project) } + + it 'returns a String reference to the object' do + expect(milestone.to_reference).to eq "%1" + end + + it 'supports a cross-project reference' do + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(milestone.to_reference(another_project)).to eq "sample-project%1" + end + end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 431b3e4435f..7f82e85563b 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -4,11 +4,18 @@ describe Namespace, models: true do let!(:namespace) { create(:namespace) } it { is_expected.to have_many :projects } - it { is_expected.to validate_presence_of :name } + + it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_uniqueness_of(:name) } - it { is_expected.to validate_presence_of :path } + it { is_expected.to validate_length_of(:name).is_at_most(255) } + + it { is_expected.to validate_length_of(:description).is_at_most(255) } + + it { is_expected.to validate_presence_of(:path) } it { is_expected.to validate_uniqueness_of(:path) } - it { is_expected.to validate_presence_of :owner } + it { is_expected.to validate_length_of(:path).is_at_most(255) } + + it { is_expected.to validate_presence_of(:owner) } describe "Mass assignment" do end @@ -117,4 +124,12 @@ describe Namespace, models: true do expect(Namespace.clean_path("--%+--valid_*&%name=.git.%.atom.atom.@email.com")).to eq("valid_name") end end + + describe '#full_path' do + let(:group) { create(:group) } + let(:nested_group) { create(:group, parent: group) } + + it { expect(group.full_path).to eq(group.path) } + it { expect(nested_group.full_path).to eq("#{group.path}/#{nested_group.path}") } + end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index e6b6e7c0634..17a15b12dcb 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -223,7 +223,7 @@ describe Note, models: true do let(:note) do create :note, noteable: ext_issue, project: ext_proj, - note: "Mentioned in issue #{private_issue.to_reference(ext_proj)}", + note: "mentioned in issue #{private_issue.to_reference(ext_proj)}", system: true end diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index 18c9d449ee5..4d538cac007 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -105,14 +105,14 @@ describe ProjectLabel, models: true do context 'using name' do it 'returns cross reference with label name' do expect(label.to_reference(project, format: :name)) - .to eq %Q(#{label.project.to_reference}~"#{label.name}") + .to eq %Q(#{label.project.path_with_namespace}~"#{label.name}") end end context 'using id' do it 'returns cross reference with label id' do expect(label.to_reference(project, format: :id)) - .to eq %Q(#{label.project.to_reference}~#{label.id}) + .to eq %Q(#{label.project.path_with_namespace}~#{label.id}) end end end diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index f5da967cd14..862e3a72a73 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -68,7 +68,7 @@ describe JiraService, models: true do end end - describe "Execute" do + describe '#close_issue' do let(:custom_base_url) { 'http://custom_url' } let(:user) { create(:user) } let(:project) { create(:project) } @@ -101,12 +101,10 @@ describe JiraService, models: true do @jira_service.save project_issues_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123' - @project_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/project/GitLabProject' @transitions_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/transitions' @comment_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/comment' @remote_link_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/remotelink' - WebMock.stub_request(:get, @project_url) WebMock.stub_request(:get, project_issues_url) WebMock.stub_request(:post, @transitions_url) WebMock.stub_request(:post, @comment_url) @@ -114,7 +112,7 @@ describe JiraService, models: true do end it "calls JIRA API" do - @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project)) + @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) expect(WebMock).to have_requested(:post, @comment_url).with( body: /Issue solved with/ @@ -124,7 +122,7 @@ describe JiraService, models: true do # Check https://developer.atlassian.com/jiradev/jira-platform/guides/other/guide-jira-remote-issue-links/fields-in-remote-issue-links # for more information it "creates Remote Link reference in JIRA for comment" do - @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project)) + @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) # Creates comment expect(WebMock).to have_requested(:post, @comment_url) @@ -146,7 +144,7 @@ describe JiraService, models: true do it "does not send comment or remote links to issues already closed" do allow_any_instance_of(JIRA::Resource::Issue).to receive(:resolution).and_return(true) - @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project)) + @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) expect(WebMock).not_to have_requested(:post, @comment_url) expect(WebMock).not_to have_requested(:post, @remote_link_url) @@ -155,7 +153,7 @@ describe JiraService, models: true do it "references the GitLab commit/merge request" do stub_config_setting(base_url: custom_base_url) - @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project)) + @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) expect(WebMock).to have_requested(:post, @comment_url).with( body: /#{custom_base_url}\/#{project.path_with_namespace}\/commit\/#{merge_request.diff_head_sha}/ @@ -170,7 +168,7 @@ describe JiraService, models: true do { script_name: '/gitlab' } end - @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project)) + @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) expect(WebMock).to have_requested(:post, @comment_url).with( body: /#{Gitlab.config.gitlab.url}\/#{project.path_with_namespace}\/commit\/#{merge_request.diff_head_sha}/ @@ -178,19 +176,33 @@ describe JiraService, models: true do end it "calls the api with jira_issue_transition_id" do - @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project)) + @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) expect(WebMock).to have_requested(:post, @transitions_url).with( body: /custom-id/ ).once end + end - context "when testing" do - it "tries to get jira project" do - @jira_service.execute(nil) + describe '#test_settings' do + let(:jira_service) do + described_class.new( + url: 'http://jira.example.com', + username: 'gitlab_jira_username', + password: 'gitlab_jira_password', + project_key: 'GitLabProject' + ) + end + let(:project_url) { 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/project/GitLabProject' } - expect(WebMock).to have_requested(:get, @project_url) - end + before do + WebMock.stub_request(:get, project_url) + end + + it 'tries to get JIRA project' do + jira_service.test_settings + + expect(WebMock).to have_requested(:get, project_url) end end diff --git a/spec/models/project_services/pipeline_email_service_spec.rb b/spec/models/project_services/pipeline_email_service_spec.rb index 4f56bceda44..7c8824485f5 100644 --- a/spec/models/project_services/pipeline_email_service_spec.rb +++ b/spec/models/project_services/pipeline_email_service_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe PipelinesEmailService do + include EmailHelpers + let(:pipeline) do create(:ci_pipeline, project: project, sha: project.commit('master').sha) end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index da38254d1bc..21ff238841e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -131,14 +131,18 @@ describe Project, models: true do it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) } - it { is_expected.to validate_length_of(:name).is_within(0..255) } + it { is_expected.to validate_length_of(:name).is_at_most(255) } it { is_expected.to validate_presence_of(:path) } it { is_expected.to validate_uniqueness_of(:path).scoped_to(:namespace_id) } - it { is_expected.to validate_length_of(:path).is_within(0..255) } - it { is_expected.to validate_length_of(:description).is_within(0..2000) } + it { is_expected.to validate_length_of(:path).is_at_most(255) } + + it { is_expected.to validate_length_of(:description).is_at_most(2000) } + it { is_expected.to validate_presence_of(:creator) } + it { is_expected.to validate_presence_of(:namespace) } + it { is_expected.to validate_presence_of(:repository_storage) } it 'does not allow new projects beyond user limits' do @@ -258,10 +262,70 @@ describe Project, models: true do end describe '#to_reference' do - let(:project) { create(:empty_project) } + let(:owner) { create(:user, name: 'Gitlab') } + let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) } + let(:project) { create(:empty_project, path: 'sample-project', namespace: namespace) } + + context 'when nil argument' do + it 'returns nil' do + expect(project.to_reference).to be_nil + end + end + + context 'when same project argument' do + it 'returns nil' do + expect(project.to_reference(project)).to be_nil + end + end + + context 'when cross namespace project argument' do + let(:another_namespace_project) { create(:empty_project, name: 'another-project') } + + it 'returns complete path to the project' do + expect(project.to_reference(another_namespace_project)).to eq 'sample-namespace/sample-project' + end + end + + context 'when same namespace / cross-project argument' do + let(:another_project) { create(:empty_project, namespace: namespace) } + + it 'returns complete path to the project' do + expect(project.to_reference(another_project)).to eq 'sample-project' + end + end + end + + describe '#to_human_reference' do + let(:owner) { create(:user, name: 'Gitlab') } + let(:namespace) { create(:namespace, name: 'Sample namespace', owner: owner) } + let(:project) { create(:empty_project, name: 'Sample project', namespace: namespace) } + + context 'when nil argument' do + it 'returns nil' do + expect(project.to_human_reference).to be_nil + end + end - it 'returns a String reference to the object' do - expect(project.to_reference).to eq project.path_with_namespace + context 'when same project argument' do + it 'returns nil' do + expect(project.to_human_reference(project)).to be_nil + end + end + + context 'when cross namespace project argument' do + let(:another_namespace_project) { create(:empty_project, name: 'another-project') } + + it 'returns complete name with namespace of the project' do + expect(project.to_human_reference(another_namespace_project)).to eq 'Gitlab / Sample project' + end + end + + context 'when same namespace / cross-project argument' do + let(:another_project) { create(:empty_project, namespace: namespace) } + + it 'returns name of the project' do + expect(project.to_human_reference(another_project)).to eq 'Sample project' + end end end @@ -361,10 +425,15 @@ describe Project, models: true do describe '#get_issue' do let(:project) { create(:empty_project) } let!(:issue) { create(:issue, project: project) } + let(:user) { create(:user) } + + before do + project.team << [user, :developer] + end context 'with default issues tracker' do it 'returns an issue' do - expect(project.get_issue(issue.iid)).to eq issue + expect(project.get_issue(issue.iid, user)).to eq issue end it 'returns count of open issues' do @@ -372,7 +441,12 @@ describe Project, models: true do end it 'returns nil when no issue found' do - expect(project.get_issue(999)).to be_nil + expect(project.get_issue(999, user)).to be_nil + end + + it "returns nil when user doesn't have access" do + user = create(:user) + expect(project.get_issue(issue.iid, user)).to eq nil end end @@ -382,7 +456,7 @@ describe Project, models: true do end it 'returns an ExternalIssue' do - issue = project.get_issue('FOO-1234') + issue = project.get_issue('FOO-1234', user) expect(issue).to be_kind_of(ExternalIssue) expect(issue.iid).to eq 'FOO-1234' expect(issue.project).to eq project @@ -404,35 +478,6 @@ describe Project, models: true do end end - describe '.find_with_namespace' do - context 'with namespace' do - before do - @group = create :group, name: 'gitlab' - @project = create(:project, name: 'gitlabhq', namespace: @group) - end - - it { expect(Project.find_with_namespace('gitlab/gitlabhq')).to eq(@project) } - it { expect(Project.find_with_namespace('GitLab/GitlabHQ')).to eq(@project) } - it { expect(Project.find_with_namespace('gitlab-ci')).to be_nil } - end - - context 'when multiple projects using a similar name exist' do - let(:group) { create(:group, name: 'gitlab') } - - let!(:project1) do - create(:empty_project, name: 'gitlab1', path: 'gitlab', namespace: group) - end - - let!(:project2) do - create(:empty_project, name: 'gitlab2', path: 'GITLAB', namespace: group) - end - - it 'returns the row where the path matches literally' do - expect(Project.find_with_namespace('gitlab/GITLAB')).to eq(project2) - end - end - end - describe '#to_param' do context 'with namespace' do before do @@ -1474,39 +1519,6 @@ describe Project, models: true do end end - describe '.where_paths_in' do - context 'without any paths' do - it 'returns an empty relation' do - expect(Project.where_paths_in([])).to eq([]) - end - end - - context 'without any valid paths' do - it 'returns an empty relation' do - expect(Project.where_paths_in(%w[foo])).to eq([]) - end - end - - context 'with valid paths' do - let!(:project1) { create(:project) } - let!(:project2) { create(:project) } - - it 'returns the projects matching the paths' do - projects = Project.where_paths_in([project1.path_with_namespace, - project2.path_with_namespace]) - - expect(projects).to contain_exactly(project1, project2) - end - - it 'returns projects regardless of the casing of paths' do - projects = Project.where_paths_in([project1.path_with_namespace.upcase, - project2.path_with_namespace.upcase]) - - expect(projects).to contain_exactly(project1, project2) - end - end - end - describe 'change_head' do let(:project) { create(:project) } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 04afb8ebc98..b5a42edd192 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -768,7 +768,6 @@ describe Repository, models: true do expect(repository).not_to receive(:expire_root_ref_cache) expect(repository).not_to receive(:expire_emptiness_caches) expect(repository).to receive(:expire_branches_cache) - expect(repository).to receive(:expire_has_visible_content_cache) repository.update_branch_with_hooks(user, 'new-feature') { new_rev } end @@ -786,7 +785,6 @@ describe Repository, models: true do expect(empty_repository).to receive(:expire_root_ref_cache) expect(empty_repository).to receive(:expire_emptiness_caches) expect(empty_repository).to receive(:expire_branches_cache) - expect(empty_repository).to receive(:expire_has_visible_content_cache) empty_repository.commit_file(user, 'CHANGELOG', 'Changelog!', 'Updates file content', 'master', false) @@ -829,15 +827,6 @@ describe Repository, models: true do expect(subject).to eq(true) end - - it 'caches the output' do - expect(repository).to receive(:branch_count). - once. - and_return(3) - - repository.has_visible_content? - repository.has_visible_content? - end end end @@ -918,20 +907,6 @@ describe Repository, models: true do end end - describe '#expire_has_visible_content_cache' do - it 'expires the visible content cache' do - repository.has_visible_content? - - expect(repository).to receive(:branch_count). - once. - and_return(0) - - repository.expire_has_visible_content_cache - - expect(repository.has_visible_content?).to eq(false) - end - end - describe '#expire_branch_cache' do # This method is private but we need it for testing purposes. Sadly there's # no other proper way of testing caching operations. @@ -967,7 +942,6 @@ describe Repository, models: true do allow(repository).to receive(:empty?).and_return(true) expect(cache).to receive(:expire).with(:empty?) - expect(repository).to receive(:expire_has_visible_content_cache) repository.expire_emptiness_caches end @@ -976,7 +950,6 @@ describe Repository, models: true do allow(repository).to receive(:empty?).and_return(false) expect(cache).not_to receive(:expire).with(:empty?) - expect(repository).not_to receive(:expire_has_visible_content_cache) repository.expire_emptiness_caches end @@ -1203,16 +1176,16 @@ describe Repository, models: true do end describe '#after_create_branch' do - it 'flushes the visible content cache' do - expect(repository).to receive(:expire_has_visible_content_cache) + it 'expires the branch caches' do + expect(repository).to receive(:expire_branches_cache) repository.after_create_branch end end describe '#after_remove_branch' do - it 'flushes the visible content cache' do - expect(repository).to receive(:expire_has_visible_content_cache) + it 'expires the branch caches' do + expect(repository).to receive(:expire_branches_cache) repository.after_remove_branch end @@ -1303,32 +1276,36 @@ describe Repository, models: true do repository.add_tag(user, '8.5', 'master', 'foo') end - it 'does not create a tag when a pre-hook fails' do - allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) - - expect do - repository.add_tag(user, '8.5', 'master', 'foo') - end.to raise_error(GitHooksService::PreReceiveError) + it 'returns a Gitlab::Git::Tag object' do + tag = repository.add_tag(user, '8.5', 'master', 'foo') - repository.expire_tags_cache - expect(repository.find_tag('8.5')).to be_nil + expect(tag).to be_a(Gitlab::Git::Tag) end - it 'passes tag SHA to hooks' do - spy = GitHooksService.new - allow(GitHooksService).to receive(:new).and_return(spy) - allow(spy).to receive(:execute).and_call_original + it 'passes commit SHA to pre-receive and update hooks,\ + and tag SHA to post-receive hook' do + pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', repository.path_to_repo) + update_hook = Gitlab::Git::Hook.new('update', repository.path_to_repo) + post_receive_hook = Gitlab::Git::Hook.new('post-receive', repository.path_to_repo) - tag = repository.add_tag(user, '8.5', 'master', 'foo') + allow(Gitlab::Git::Hook).to receive(:new). + and_return(pre_receive_hook, update_hook, post_receive_hook) - expect(spy).to have_received(:execute). - with(anything, anything, anything, tag.target, anything) - end + allow(pre_receive_hook).to receive(:trigger).and_call_original + allow(update_hook).to receive(:trigger).and_call_original + allow(post_receive_hook).to receive(:trigger).and_call_original - it 'returns a Gitlab::Git::Tag object' do tag = repository.add_tag(user, '8.5', 'master', 'foo') - expect(tag).to be_a(Gitlab::Git::Tag) + commit_sha = repository.commit('master').id + tag_sha = tag.target + + expect(pre_receive_hook).to have_received(:trigger). + with(anything, anything, commit_sha, anything) + expect(update_hook).to have_received(:trigger). + with(anything, anything, commit_sha, anything) + expect(post_receive_hook).to have_received(:trigger). + with(anything, anything, tag_sha, anything) end end diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb new file mode 100644 index 00000000000..6f491fdf9a0 --- /dev/null +++ b/spec/models/route_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe Route, models: true do + let!(:group) { create(:group) } + let!(:route) { group.route } + + describe 'relationships' do + it { is_expected.to belong_to(:source) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:source) } + it { is_expected.to validate_presence_of(:path) } + it { is_expected.to validate_uniqueness_of(:path) } + end + + describe '#rename_children' do + let!(:nested_group) { create(:group, path: "test", parent: group) } + let!(:deep_nested_group) { create(:group, path: "foo", parent: nested_group) } + + it "updates children routes with new path" do + route.update_attributes(path: 'bar') + + expect(described_class.exists?(path: 'bar')).to be_truthy + expect(described_class.exists?(path: 'bar/test')).to be_truthy + expect(described_class.exists?(path: 'bar/test/foo')).to be_truthy + end + end +end diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index f62f6bacbaa..7425a897769 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -23,9 +23,9 @@ describe Snippet, models: true do it { is_expected.to validate_presence_of(:author) } it { is_expected.to validate_presence_of(:title) } - it { is_expected.to validate_length_of(:title).is_within(0..255) } + it { is_expected.to validate_length_of(:title).is_at_most(255) } - it { is_expected.to validate_length_of(:file_name).is_within(0..255) } + it { is_expected.to validate_length_of(:file_name).is_at_most(255) } it { is_expected.to validate_presence_of(:content) } @@ -33,16 +33,51 @@ describe Snippet, models: true do end describe '#to_reference' do + context 'when snippet belongs to a project' do + let(:project) { build(:empty_project, name: 'sample-project') } + let(:snippet) { build(:snippet, id: 1, project: project) } + + it 'returns a String reference to the object' do + expect(snippet.to_reference).to eq "$1" + end + + it 'supports a cross-project reference' do + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(snippet.to_reference(another_project)).to eq "sample-project$1" + end + end + + context 'when snippet does not belong to a project' do + let(:snippet) { build(:snippet, id: 1, project: nil) } + + it 'returns a String reference to the object' do + expect(snippet.to_reference).to eq "$1" + end + + it 'still returns shortest reference when project arg present' do + another_project = build(:project, name: 'another-project') + expect(snippet.to_reference(another_project)).to eq "$1" + end + end + end + + describe '#file_name' do let(:project) { create(:empty_project) } - let(:snippet) { create(:snippet, project: project) } - it 'returns a String reference to the object' do - expect(snippet.to_reference).to eq "$#{snippet.id}" + context 'file_name is nil' do + let(:snippet) { create(:snippet, project: project, file_name: nil) } + + it 'returns an empty string' do + expect(snippet.file_name).to eq '' + end end - it 'supports a cross-project reference' do - cross = double('project') - expect(snippet.to_reference(cross)).to eq "#{project.to_reference}$#{snippet.id}" + context 'file_name is not nil' do + let(:snippet) { create(:snippet, project: project, file_name: 'foo.txt') } + + it 'returns the file_name' do + expect(snippet.file_name).to eq 'foo.txt' + end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 91826e5884d..8b20ee81614 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -79,7 +79,7 @@ describe User, models: true do it { is_expected.to allow_value(0).for(:projects_limit) } it { is_expected.not_to allow_value(-1).for(:projects_limit) } - it { is_expected.to validate_length_of(:bio).is_within(0..255) } + it { is_expected.to validate_length_of(:bio).is_at_most(255) } it_behaves_like 'an object with email-formated attributes', :email do subject { build(:user) } @@ -575,6 +575,23 @@ describe User, models: true do end end end + + describe '#require_ssh_key?' do + protocol_and_expectation = { + 'http' => false, + 'ssh' => true, + '' => true, + } + + protocol_and_expectation.each do |protocol, expected| + it "has correct require_ssh_key?" do + stub_application_setting(enabled_git_access_protocol: protocol) + user = build(:user) + + expect(user.require_ssh_key?).to eq(expected) + end + end + end end describe '.find_by_any_email' do @@ -710,17 +727,6 @@ describe User, models: true do end end - describe 'by_username_or_id' do - let(:user1) { create(:user, username: 'foo') } - - it "gets the correct user" do - expect(User.by_username_or_id(user1.id)).to eq(user1) - expect(User.by_username_or_id('foo')).to eq(user1) - expect(User.by_username_or_id(-1)).to be_nil - expect(User.by_username_or_id('bar')).to be_nil - end - end - describe '.find_by_ssh_key_id' do context 'using an existing SSH key ID' do let(:user) { create(:user) } @@ -1349,4 +1355,31 @@ describe User, models: true do expect(projects).to be_empty end end + + describe '#refresh_authorized_projects', redis: true do + let(:project1) { create(:empty_project) } + let(:project2) { create(:empty_project) } + let(:user) { create(:user) } + + before do + project1.team << [user, :reporter] + project2.team << [user, :guest] + + user.project_authorizations.delete_all + user.refresh_authorized_projects + end + + it 'refreshes the list of authorized projects' do + expect(user.project_authorizations.count).to eq(2) + end + + it 'sets the authorized_projects_populated column' do + expect(user.authorized_projects_populated).to eq(true) + end + + it 'stores the correct access levels' do + expect(user.project_authorizations.where(access_level: Gitlab::Access::GUEST).exists?).to eq(true) + expect(user.project_authorizations.where(access_level: Gitlab::Access::REPORTER).exists?).to eq(true) + end + end end |