diff options
author | Kamil Trzcinski <ayufan@ayufan.eu> | 2017-04-06 22:00:00 +0200 |
---|---|---|
committer | Kamil Trzcinski <ayufan@ayufan.eu> | 2017-04-06 22:05:22 +0200 |
commit | 37a7b099d6eafc87209368a9fc7f608282b99e08 (patch) | |
tree | a2712e439cec8547d84bed2d57d339cb1cd5c502 /spec/models | |
parent | 57c353fca7121a120142161b253004f33d815766 (diff) | |
parent | 73cb71e41c0ade92b9673a5d74c7dd78679fae91 (diff) | |
download | gitlab-ce-optimise-pipelines.tar.gz |
Merge remote-tracking branch 'origin/master' into optimise-pipelinesoptimise-pipelines
Diffstat (limited to 'spec/models')
-rw-r--r-- | spec/models/award_emoji_spec.rb | 14 | ||||
-rw-r--r-- | spec/models/blob_spec.rb | 62 | ||||
-rw-r--r-- | spec/models/ci/build_spec.rb | 302 | ||||
-rw-r--r-- | spec/models/ci/trigger_schedule_spec.rb | 76 | ||||
-rw-r--r-- | spec/models/ci/trigger_spec.rb | 1 | ||||
-rw-r--r-- | spec/models/concerns/routable_spec.rb | 109 | ||||
-rw-r--r-- | spec/models/container_repository_spec.rb | 209 | ||||
-rw-r--r-- | spec/models/environment_spec.rb | 22 | ||||
-rw-r--r-- | spec/models/group_spec.rb | 42 | ||||
-rw-r--r-- | spec/models/issue_spec.rb | 8 | ||||
-rw-r--r-- | spec/models/members/group_member_spec.rb | 17 | ||||
-rw-r--r-- | spec/models/namespace_spec.rb | 63 | ||||
-rw-r--r-- | spec/models/project_spec.rb | 97 | ||||
-rw-r--r-- | spec/models/user_spec.rb | 112 |
14 files changed, 852 insertions, 282 deletions
diff --git a/spec/models/award_emoji_spec.rb b/spec/models/award_emoji_spec.rb index cb3c592f8cd..2a9a27752c1 100644 --- a/spec/models/award_emoji_spec.rb +++ b/spec/models/award_emoji_spec.rb @@ -25,6 +25,20 @@ describe AwardEmoji, models: true do expect(new_award).not_to be_valid end + + # Assume User A and User B both created award emoji of the same name + # on the same awardable. When User A is deleted, User A's award emoji + # is moved to the ghost user. When User B is deleted, User B's award emoji + # also needs to be moved to the ghost user - this cannot happen unless + # the uniqueness validation is disabled for ghost users. + it "allows duplicate award emoji for ghost users" do + user = create(:user, :ghost) + issue = create(:issue) + create(:award_emoji, user: user, awardable: issue) + new_award = build(:award_emoji, user: user, awardable: issue) + + expect(new_award).to be_valid + end end end end diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb index 552229e9b07..0f29766db41 100644 --- a/spec/models/blob_spec.rb +++ b/spec/models/blob_spec.rb @@ -53,6 +53,20 @@ describe Blob do end end + describe '#pdf?' do + it 'is falsey when file extension is not .pdf' do + git_blob = double(name: 'git_blob.txt') + + expect(described_class.decorate(git_blob)).not_to be_pdf + end + + it 'is truthy when file extension is .pdf' do + git_blob = double(name: 'git_blob.pdf') + + expect(described_class.decorate(git_blob)).to be_pdf + end + end + describe '#ipython_notebook?' do it 'is falsey when language is not Jupyter Notebook' do git_blob = double(text?: true, language: double(name: 'JSON')) @@ -67,6 +81,20 @@ describe Blob do end end + describe '#sketch?' do + it 'is falsey with image extension' do + git_blob = Gitlab::Git::Blob.new(name: "design.png") + + expect(described_class.decorate(git_blob)).not_to be_sketch + end + + it 'is truthy with sketch extension' do + git_blob = Gitlab::Git::Blob.new(name: "design.sketch") + + expect(described_class.decorate(git_blob)).to be_sketch + end + end + describe '#video?' do it 'is falsey with image extension' do git_blob = Gitlab::Git::Blob.new(name: 'image.png') @@ -83,16 +111,33 @@ describe Blob do end end + describe '#stl?' do + it 'is falsey with image extension' do + git_blob = Gitlab::Git::Blob.new(name: 'file.png') + + expect(described_class.decorate(git_blob)).not_to be_stl + end + + it 'is truthy with STL extension' do + git_blob = Gitlab::Git::Blob.new(name: 'file.stl') + + expect(described_class.decorate(git_blob)).to be_stl + end + end + describe '#to_partial_path' do let(:project) { double(lfs_enabled?: true) } def stubbed_blob(overrides = {}) overrides.reverse_merge!( + name: nil, image?: false, language: nil, lfs_pointer?: false, svg?: false, - text?: false + text?: false, + binary?: false, + stl?: false ) described_class.decorate(double).tap do |blob| @@ -131,10 +176,25 @@ describe Blob do expect(blob.to_partial_path(project)).to eq 'download' end + it 'handles PDFs' do + blob = stubbed_blob(name: 'blob.pdf', pdf?: true) + expect(blob.to_partial_path(project)).to eq 'pdf' + end + it 'handles iPython notebooks' do blob = stubbed_blob(text?: true, ipython_notebook?: true) expect(blob.to_partial_path(project)).to eq 'notebook' end + + it 'handles Sketch files' do + blob = stubbed_blob(text?: true, sketch?: true, binary?: true) + expect(blob.to_partial_path(project)).to eq 'sketch' + end + + it 'handles STLs' do + blob = stubbed_blob(text?: true, stl?: true) + expect(blob.to_partial_path(project)).to eq 'stl' + end end describe '#size_within_svg_limits?' do diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index ab7cf47bd2d..6e8845cdcf4 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -17,8 +17,9 @@ describe Ci::Build, :models do it { is_expected.to belong_to(:trigger_request) } it { is_expected.to belong_to(:erased_by) } it { is_expected.to have_many(:deployments) } - it { is_expected.to validate_presence_of :ref } - it { is_expected.to respond_to :trace_html } + it { is_expected.to validate_presence_of(:ref) } + it { is_expected.to respond_to(:has_trace?) } + it { is_expected.to respond_to(:trace) } describe '#actionize' do context 'when build is a created' do @@ -78,32 +79,6 @@ describe Ci::Build, :models do end end - describe '#append_trace' do - subject { build.trace_html } - - context 'when build.trace hides runners token' do - let(:token) { 'my_secret_token' } - - before do - build.project.update(runners_token: token) - build.append_trace(token, 0) - end - - it { is_expected.not_to include(token) } - end - - context 'when build.trace hides build token' do - let(:token) { 'my_secret_token' } - - before do - build.update(token: token) - build.append_trace(token, 0) - end - - it { is_expected.not_to include(token) } - end - end - describe '#artifacts?' do subject { build.artifacts? } @@ -272,12 +247,98 @@ describe Ci::Build, :models do describe '#update_coverage' do context "regarding coverage_regex's value," do - it "saves the correct extracted coverage value" do + before do build.coverage_regex = '\(\d+.\d+\%\) covered' - allow(build).to receive(:trace) { 'Coverage 1033 / 1051 LOC (98.29%) covered' } - expect(build).to receive(:update_attributes).with(coverage: 98.29) { true } - expect(build.update_coverage).to be true + build.trace.set('Coverage 1033 / 1051 LOC (98.29%) covered') + end + + it "saves the correct extracted coverage value" do + expect(build.update_coverage).to be(true) + expect(build.coverage).to eq(98.29) + end + end + end + + describe '#trace' do + subject { build.trace } + + it { is_expected.to be_a(Gitlab::Ci::Trace) } + end + + describe '#has_trace?' do + subject { build.has_trace? } + + it "expect to call exist? method" do + expect_any_instance_of(Gitlab::Ci::Trace).to receive(:exist?) + .and_return(true) + + is_expected.to be(true) + end + end + + describe '#trace=' do + it "expect to fail trace=" do + expect { build.trace = "new" }.to raise_error(NotImplementedError) + end + end + + describe '#old_trace' do + subject { build.old_trace } + + before do + build.update_column(:trace, 'old trace') + end + + it "expect to receive data from database" do + is_expected.to eq('old trace') + end + end + + describe '#erase_old_trace!' do + subject { build.send(:read_attribute, :trace) } + + before do + build.send(:write_attribute, :trace, 'old trace') + end + + it "expect to receive data from database" do + build.erase_old_trace! + + is_expected.to be_nil + end + end + + describe '#hide_secrets' do + let(:subject) { build.hide_secrets(data) } + + context 'hide runners token' do + let(:data) { 'new token data'} + + before do + build.project.update(runners_token: 'token') end + + it { is_expected.to eq('new xxxxx data') } + end + + context 'hide build token' do + let(:data) { 'new token data'} + + before do + build.update(token: 'token') + end + + it { is_expected.to eq('new xxxxx data') } + end + + context 'hide build token' do + let(:data) { 'new token data'} + + before do + build.update(token: 'token') + end + + it { is_expected.to eq('new xxxxx data') } end end @@ -438,7 +499,7 @@ describe Ci::Build, :models do end it 'erases build trace in trace file' do - expect(build.trace).to be_empty + expect(build).not_to have_trace end it 'sets erased to true' do @@ -532,38 +593,6 @@ describe Ci::Build, :models do end end - describe '#extract_coverage' do - context 'valid content & regex' do - subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', '\(\d+.\d+\%\) covered') } - - it { is_expected.to eq(98.29) } - end - - context 'valid content & bad regex' do - subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', 'very covered') } - - it { is_expected.to be_nil } - end - - context 'no coverage content & regex' do - subject { build.extract_coverage('No coverage for today :sad:', '\(\d+.\d+\%\) covered') } - - it { is_expected.to be_nil } - end - - context 'multiple results in content & regex' do - subject { build.extract_coverage(' (98.39%) covered. (98.29%) covered', '\(\d+.\d+\%\) covered') } - - it { is_expected.to eq(98.29) } - end - - context 'using a regex capture' do - subject { build.extract_coverage('TOTAL 9926 3489 65%', 'TOTAL\s+\d+\s+\d+\s+(\d{1,3}\%)') } - - it { is_expected.to eq(65) } - end - end - describe '#first_pending' do let!(:first) { create(:ci_build, pipeline: pipeline, status: 'pending', created_at: Date.yesterday) } let!(:second) { create(:ci_build, pipeline: pipeline, status: 'pending') } @@ -949,32 +978,6 @@ describe Ci::Build, :models do it { is_expected.to eq(project.name) } end - describe '#raw_trace' do - subject { build.raw_trace } - - context 'when build.trace hides runners token' do - let(:token) { 'my_secret_token' } - - before do - build.project.update(runners_token: token) - build.update(trace: token) - end - - it { is_expected.not_to include(token) } - end - - context 'when build.trace hides build token' do - let(:token) { 'my_secret_token' } - - before do - build.update(token: token) - build.update(trace: token) - end - - it { is_expected.not_to include(token) } - end - end - describe '#ref_slug' do { 'master' => 'master', @@ -1040,61 +1043,6 @@ describe Ci::Build, :models do end end - describe '#trace' do - it 'obfuscates project runners token' do - allow(build).to receive(:raw_trace).and_return("Test: #{build.project.runners_token}") - - expect(build.trace).to eq("Test: xxxxxxxxxxxxxxxxxxxx") - end - - it 'empty project runners token' do - allow(build).to receive(:raw_trace).and_return(test_trace) - # runners_token can't normally be set to nil - allow(build.project).to receive(:runners_token).and_return(nil) - - expect(build.trace).to eq(test_trace) - end - - context 'when build does not have trace' do - it 'is is empty' do - expect(build.trace).to be_nil - end - end - - context 'when trace contains text' do - let(:text) { 'example output' } - before do - build.trace = text - end - - it { expect(build.trace).to eq(text) } - end - - context 'when trace hides runners token' do - let(:token) { 'my_secret_token' } - - before do - build.update(trace: token) - build.project.update(runners_token: token) - end - - it { expect(build.trace).not_to include(token) } - it { expect(build.raw_trace).to include(token) } - end - - context 'when build.trace hides build token' do - let(:token) { 'my_secret_token' } - - before do - build.update(trace: token) - build.update(token: token) - end - - it { expect(build.trace).not_to include(token) } - it { expect(build.raw_trace).to include(token) } - end - end - describe '#has_expiring_artifacts?' do context 'when artifacts have expiration date set' do before { build.update(artifacts_expire_at: 1.day.from_now) } @@ -1113,66 +1061,6 @@ describe Ci::Build, :models do end end - describe '#has_trace_file?' do - context 'when there is no trace' do - it { expect(build.has_trace_file?).to be_falsey } - it { expect(build.trace).to be_nil } - end - - context 'when there is a trace' do - context 'when trace is stored in file' do - let(:build_with_trace) { create(:ci_build, :trace) } - - it { expect(build_with_trace.has_trace_file?).to be_truthy } - it { expect(build_with_trace.trace).to eq('BUILD TRACE') } - end - - context 'when trace is stored in old file' do - before do - allow(build.project).to receive(:ci_id).and_return(999) - allow(File).to receive(:exist?).with(build.path_to_trace).and_return(false) - allow(File).to receive(:exist?).with(build.old_path_to_trace).and_return(true) - allow(File).to receive(:read).with(build.old_path_to_trace).and_return(test_trace) - end - - it { expect(build.has_trace_file?).to be_truthy } - it { expect(build.trace).to eq(test_trace) } - end - - context 'when trace is stored in DB' do - before do - allow(build.project).to receive(:ci_id).and_return(nil) - allow(build).to receive(:read_attribute).with(:trace).and_return(test_trace) - allow(File).to receive(:exist?).with(build.path_to_trace).and_return(false) - allow(File).to receive(:exist?).with(build.old_path_to_trace).and_return(false) - end - - it { expect(build.has_trace_file?).to be_falsey } - it { expect(build.trace).to eq(test_trace) } - end - end - end - - describe '#trace_file_path' do - context 'when trace is stored in file' do - before do - allow(build).to receive(:has_trace_file?).and_return(true) - allow(build).to receive(:has_old_trace_file?).and_return(false) - end - - it { expect(build.trace_file_path).to eq(build.path_to_trace) } - end - - context 'when trace is stored in old file' do - before do - allow(build).to receive(:has_trace_file?).and_return(true) - allow(build).to receive(:has_old_trace_file?).and_return(true) - end - - it { expect(build.trace_file_path).to eq(build.old_path_to_trace) } - end - end - describe '#update_project_statistics' do let!(:build) { create(:ci_build, artifacts_size: 23) } @@ -1426,7 +1314,7 @@ describe Ci::Build, :models do { key: 'CI_REGISTRY', value: 'registry.example.com', public: true } end let(:ci_registry_image) do - { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_repository_url, public: true } + { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_url, public: true } end context 'and is disabled for project' do diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb new file mode 100644 index 00000000000..75d21541cee --- /dev/null +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -0,0 +1,76 @@ +require 'spec_helper' + +describe Ci::TriggerSchedule, models: true do + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:trigger) } + it { is_expected.to respond_to(:ref) } + + describe '#set_next_run_at' do + context 'when creates new TriggerSchedule' do + before do + trigger_schedule = create(:ci_trigger_schedule, :nightly) + @expected_next_run_at = Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_timezone) + .next_time_from(Time.now) + end + + it 'updates next_run_at automatically' do + expect(Ci::TriggerSchedule.last.next_run_at).to eq(@expected_next_run_at) + end + end + + context 'when updates cron of exsisted TriggerSchedule' do + before do + trigger_schedule = create(:ci_trigger_schedule, :nightly) + new_cron = '0 0 1 1 *' + trigger_schedule.update!(cron: new_cron) # Subject + @expected_next_run_at = Gitlab::Ci::CronParser.new(new_cron, trigger_schedule.cron_timezone) + .next_time_from(Time.now) + end + + it 'updates next_run_at automatically' do + expect(Ci::TriggerSchedule.last.next_run_at).to eq(@expected_next_run_at) + end + end + end + + describe '#schedule_next_run!' do + context 'when reschedules after 10 days from now' do + before do + trigger_schedule = create(:ci_trigger_schedule, :nightly) + time_future = Time.now + 10.days + allow(Time).to receive(:now).and_return(time_future) + trigger_schedule.schedule_next_run! # Subject + @expected_next_run_at = Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_timezone) + .next_time_from(time_future) + end + + it 'points to proper next_run_at' do + expect(Ci::TriggerSchedule.last.next_run_at).to eq(@expected_next_run_at) + end + end + + context 'when cron is invalid' do + before do + trigger_schedule = create(:ci_trigger_schedule, :nightly) + trigger_schedule.cron = 'Invalid-cron' + trigger_schedule.schedule_next_run! # Subject + end + + it 'sets nil to next_run_at' do + expect(Ci::TriggerSchedule.last.next_run_at).to be_nil + end + end + + context 'when cron_timezone is invalid' do + before do + trigger_schedule = create(:ci_trigger_schedule, :nightly) + trigger_schedule.cron_timezone = 'Invalid-cron_timezone' + trigger_schedule.schedule_next_run! # Subject + end + + it 'sets nil to next_run_at' do + expect(Ci::TriggerSchedule.last.next_run_at).to be_nil + end + end + end +end diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb index 92c15c13c18..d26121018ce 100644 --- a/spec/models/ci/trigger_spec.rb +++ b/spec/models/ci/trigger_spec.rb @@ -7,6 +7,7 @@ describe Ci::Trigger, models: true do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:owner) } it { is_expected.to have_many(:trigger_requests) } + it { is_expected.to have_one(:trigger_schedule) } end describe 'before_validation' do diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb index 677e60e1282..f191605dbdb 100644 --- a/spec/models/concerns/routable_spec.rb +++ b/spec/models/concerns/routable_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Group, 'Routable' do - let!(:group) { create(:group) } + let!(:group) { create(:group, name: 'foo') } describe 'Validations' do it { is_expected.to validate_presence_of(:route) } @@ -81,6 +81,113 @@ describe Group, 'Routable' do it { is_expected.to eq([nested_group]) } end + describe '.member_self_and_descendants' do + let!(:user) { create(:user) } + let!(:nested_group) { create(:group, parent: group) } + + before { group.add_owner(user) } + subject { described_class.member_self_and_descendants(user.id) } + + it { is_expected.to match_array [group, nested_group] } + end + + describe '.member_hierarchy' do + # foo/bar would also match foo/barbaz instead of just foo/bar and foo/bar/baz + let!(:user) { create(:user) } + + # group + # _______ (foo) _______ + # | | + # | | + # nested_group_1 nested_group_2 + # (bar) (barbaz) + # | | + # | | + # nested_group_1_1 nested_group_2_1 + # (baz) (baz) + # + let!(:nested_group_1) { create :group, parent: group, name: 'bar' } + let!(:nested_group_1_1) { create :group, parent: nested_group_1, name: 'baz' } + let!(:nested_group_2) { create :group, parent: group, name: 'barbaz' } + let!(:nested_group_2_1) { create :group, parent: nested_group_2, name: 'baz' } + + context 'user is not a member of any group' do + subject { described_class.member_hierarchy(user.id) } + + it 'returns an empty array' do + is_expected.to eq [] + end + end + + context 'user is member of all groups' do + before do + group.add_owner(user) + nested_group_1.add_owner(user) + nested_group_1_1.add_owner(user) + nested_group_2.add_owner(user) + nested_group_2_1.add_owner(user) + end + subject { described_class.member_hierarchy(user.id) } + + it 'returns all groups' do + is_expected.to match_array [ + group, + nested_group_1, nested_group_1_1, + nested_group_2, nested_group_2_1 + ] + end + end + + context 'user is member of the top group' do + before { group.add_owner(user) } + subject { described_class.member_hierarchy(user.id) } + + it 'returns all groups' do + is_expected.to match_array [ + group, + nested_group_1, nested_group_1_1, + nested_group_2, nested_group_2_1 + ] + end + end + + context 'user is member of the first child (internal node), branch 1' do + before { nested_group_1.add_owner(user) } + subject { described_class.member_hierarchy(user.id) } + + it 'returns the groups in the hierarchy' do + is_expected.to match_array [ + group, + nested_group_1, nested_group_1_1 + ] + end + end + + context 'user is member of the first child (internal node), branch 2' do + before { nested_group_2.add_owner(user) } + subject { described_class.member_hierarchy(user.id) } + + it 'returns the groups in the hierarchy' do + is_expected.to match_array [ + group, + nested_group_2, nested_group_2_1 + ] + end + end + + context 'user is member of the last child (leaf node)' do + before { nested_group_1_1.add_owner(user) } + subject { described_class.member_hierarchy(user.id) } + + it 'returns the groups in the hierarchy' do + is_expected.to match_array [ + group, + nested_group_1, nested_group_1_1 + ] + end + end + end + describe '#full_path' do let(:group) { create(:group) } let(:nested_group) { create(:group, parent: group) } diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb new file mode 100644 index 00000000000..f7ee0b57072 --- /dev/null +++ b/spec/models/container_repository_spec.rb @@ -0,0 +1,209 @@ +require 'spec_helper' + +describe ContainerRepository do + let(:group) { create(:group, name: 'group') } + let(:project) { create(:project, path: 'test', group: group) } + + let(:container_repository) do + create(:container_repository, name: 'my_image', project: project) + end + + before do + stub_container_registry_config(enabled: true, + api_url: 'http://registry.gitlab', + host_port: 'registry.gitlab') + + stub_request(:get, 'http://registry.gitlab/v2/group/test/my_image/tags/list') + .with(headers: { 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json' }) + .to_return( + status: 200, + body: JSON.dump(tags: ['test_tag']), + headers: { 'Content-Type' => 'application/json' }) + end + + describe 'associations' do + it 'belongs to the project' do + expect(container_repository).to belong_to(:project) + end + end + + describe '#tag' do + it 'has a test tag' do + expect(container_repository.tag('test')).not_to be_nil + end + end + + describe '#path' do + it 'returns a full path to the repository' do + expect(container_repository.path).to eq('group/test/my_image') + end + end + + describe '#manifest' do + subject { container_repository.manifest } + + it { is_expected.not_to be_nil } + end + + describe '#valid?' do + subject { container_repository.valid? } + + it { is_expected.to be_truthy } + end + + describe '#tags' do + subject { container_repository.tags } + + it { is_expected.not_to be_empty } + end + + describe '#has_tags?' do + it 'has tags' do + expect(container_repository).to have_tags + end + end + + describe '#delete_tags!' do + let(:container_repository) do + create(:container_repository, name: 'my_image', + tags: %w[latest rc1], + project: project) + end + + context 'when action succeeds' do + it 'returns status that indicates success' do + expect(container_repository.client) + .to receive(:delete_repository_tag) + .and_return(true) + + expect(container_repository.delete_tags!).to be_truthy + end + end + + context 'when action fails' do + it 'returns status that indicates failure' do + expect(container_repository.client) + .to receive(:delete_repository_tag) + .and_return(false) + + expect(container_repository.delete_tags!).to be_falsey + end + end + end + + describe '#root_repository?' do + context 'when repository is a root repository' do + let(:repository) { create(:container_repository, :root) } + + it 'returns true' do + expect(repository).to be_root_repository + end + end + + context 'when repository is not a root repository' do + it 'returns false' do + expect(container_repository).not_to be_root_repository + end + end + end + + describe '.build_from_path' do + let(:registry_path) do + ContainerRegistry::Path.new(project.full_path + '/some/image') + end + + let(:repository) do + described_class.build_from_path(registry_path) + end + + it 'fabricates repository assigned to a correct project' do + expect(repository.project).to eq project + end + + it 'fabricates repository with a correct name' do + expect(repository.name).to eq 'some/image' + end + + it 'is not persisted' do + expect(repository).not_to be_persisted + end + end + + describe '.create_from_path!' do + let(:repository) do + described_class.create_from_path!(ContainerRegistry::Path.new(path)) + end + + let(:repository_path) { ContainerRegistry::Path.new(path) } + + context 'when received multi-level repository path' do + let(:path) { project.full_path + '/some/image' } + + it 'fabricates repository assigned to a correct project' do + expect(repository.project).to eq project + end + + it 'fabricates repository with a correct name' do + expect(repository.name).to eq 'some/image' + end + end + + context 'when path is too long' do + let(:path) do + project.full_path + '/a/b/c/d/e/f/g/h/i/j/k/l/n/o/p/s/t/u/x/y/z' + end + + it 'does not create repository and raises error' do + expect { repository }.to raise_error( + ContainerRegistry::Path::InvalidRegistryPathError) + end + end + + context 'when received multi-level repository with nested groups' do + let(:group) { create(:group, :nested, name: 'nested') } + let(:path) { project.full_path + '/some/image' } + + it 'fabricates repository assigned to a correct project' do + expect(repository.project).to eq project + end + + it 'fabricates repository with a correct name' do + expect(repository.name).to eq 'some/image' + end + + it 'has path including a nested group' do + expect(repository.path).to include 'nested/test/some/image' + end + end + + context 'when received root repository path' do + let(:path) { project.full_path } + + it 'fabricates repository assigned to a correct project' do + expect(repository.project).to eq project + end + + it 'fabricates repository with an empty name' do + expect(repository.name).to be_empty + end + end + end + + describe '.build_root_repository' do + let(:repository) do + described_class.build_root_repository(project) + end + + it 'fabricates a root repository object' do + expect(repository).to be_root_repository + end + + it 'assignes it to the correct project' do + expect(repository.project).to eq project + end + + it 'does not persist it' do + expect(repository).not_to be_persisted + end + end +end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 9f0e7fbbe26..af7753caba6 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -100,12 +100,26 @@ describe Environment, models: true do let(:head_commit) { project.commit } let(:commit) { project.commit.parent } - it 'returns deployment id for the environment' do - expect(environment.first_deployment_for(commit)).to eq deployment1 + context 'Gitaly find_ref_name feature disabled' do + it 'returns deployment id for the environment' do + expect(environment.first_deployment_for(commit)).to eq deployment1 + end + + it 'return nil when no deployment is found' do + expect(environment.first_deployment_for(head_commit)).to eq nil + end end - it 'return nil when no deployment is found' do - expect(environment.first_deployment_for(head_commit)).to eq nil + context 'Gitaly find_ref_name feature enabled' do + before do + allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:find_ref_name).and_return(true) + end + + it 'calls GitalyClient' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:find_ref_name) + + environment.first_deployment_for(commit) + end end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 5d87938235a..8ffde6f7fbb 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -55,6 +55,8 @@ describe Group, models: true do it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) } it { is_expected.to validate_presence_of :path } it { is_expected.not_to validate_presence_of :owner } + it { is_expected.to validate_presence_of :two_factor_grace_period } + it { is_expected.to validate_numericality_of(:two_factor_grace_period).is_greater_than_or_equal_to(0) } end describe '.visible_to_user' do @@ -315,4 +317,44 @@ describe Group, models: true do to include(master.id, developer.id) end end + + describe '#update_two_factor_requirement' do + let(:user) { create(:user) } + + before do + group.add_user(user, GroupMember::OWNER) + end + + it 'is called when require_two_factor_authentication is changed' do + expect_any_instance_of(User).to receive(:update_two_factor_requirement) + + group.update!(require_two_factor_authentication: true) + end + + it 'is called when two_factor_grace_period is changed' do + expect_any_instance_of(User).to receive(:update_two_factor_requirement) + + group.update!(two_factor_grace_period: 23) + end + + it 'is not called when other attributes are changed' do + expect_any_instance_of(User).not_to receive(:update_two_factor_requirement) + + group.update!(description: 'foobar') + end + + it 'calls #update_two_factor_requirement on each group member' do + other_user = create(:user) + group.add_user(other_user, GroupMember::OWNER) + + calls = 0 + allow_any_instance_of(User).to receive(:update_two_factor_requirement) do + calls += 1 + end + + group.update!(require_two_factor_authentication: true, two_factor_grace_period: 23) + + expect(calls).to eq 2 + end + end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index b8584301baa..4bdd46a581d 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -51,14 +51,6 @@ describe Issue, models: true do expect(issue.closed_at).to eq(now) end - - it 'sets closed_at to nil when issue is reopened' do - issue = create(:issue, state: 'closed') - - issue.reopen - - expect(issue.closed_at).to be_nil - end end describe '#to_reference' do diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb index 370aeb9e0a9..024380b7ebb 100644 --- a/spec/models/members/group_member_spec.rb +++ b/spec/models/members/group_member_spec.rb @@ -61,7 +61,7 @@ describe GroupMember, models: true do describe '#after_accept_request' do it 'calls NotificationService.accept_group_access_request' do - member = create(:group_member, user: build_stubbed(:user), requested_at: Time.now) + member = create(:group_member, user: build(:user), requested_at: Time.now) expect_any_instance_of(NotificationService).to receive(:new_group_member) @@ -75,4 +75,19 @@ describe GroupMember, models: true do it { is_expected.to eq 'Group' } end end + + describe '#update_two_factor_requirement' do + let(:user) { build :user } + let(:group_member) { build :group_member, user: user } + + it 'is called after creation and deletion' do + expect(user).to receive(:update_two_factor_requirement) + + group_member.save + + expect(user).to receive(:update_two_factor_requirement) + + group_member.destroy + end + end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index ccaf0d7abc7..e406d0a16bd 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -148,42 +148,62 @@ describe Namespace, models: true do expect(@namespace.move_dir).to be_truthy end - context "when any project has container tags" do + context "when any project has container images" do + let(:container_repository) { create(:container_repository) } + before do stub_container_registry_config(enabled: true) - stub_container_registry_tags('tag') + stub_container_registry_tags(repository: :any, tags: ['tag']) - create(:empty_project, namespace: @namespace) + create(:empty_project, namespace: @namespace, container_repositories: [container_repository]) allow(@namespace).to receive(:path_was).and_return(@namespace.path) allow(@namespace).to receive(:path).and_return('new_path') end - it { expect { @namespace.move_dir }.to raise_error('Namespace cannot be moved, because at least one project has tags in container registry') } + it 'raises an error about not movable project' do + expect { @namespace.move_dir }.to raise_error(/Namespace cannot be moved/) + end end - context 'renaming a sub-group' do + context 'with subgroups' do let(:parent) { create(:group, name: 'parent', path: 'parent') } let(:child) { create(:group, name: 'child', path: 'child', parent: parent) } let!(:project) { create(:project_empty_repo, path: 'the-project', namespace: child) } - let(:uploads_dir) { File.join(CarrierWave.root, 'uploads', 'parent') } - let(:pages_dir) { File.join(TestEnv.pages_path, 'parent') } + let(:uploads_dir) { File.join(CarrierWave.root, 'uploads') } + let(:pages_dir) { TestEnv.pages_path } before do - FileUtils.mkdir_p(File.join(uploads_dir, 'child', 'the-project')) - FileUtils.mkdir_p(File.join(pages_dir, 'child', 'the-project')) + FileUtils.mkdir_p(File.join(uploads_dir, 'parent', 'child', 'the-project')) + FileUtils.mkdir_p(File.join(pages_dir, 'parent', 'child', 'the-project')) + end + + context 'renaming child' do + it 'correctly moves the repository, uploads and pages' do + expected_repository_path = File.join(TestEnv.repos_path, 'parent', 'renamed', 'the-project.git') + expected_upload_path = File.join(uploads_dir, 'parent', 'renamed', 'the-project') + expected_pages_path = File.join(pages_dir, 'parent', 'renamed', 'the-project') + + child.update_attributes!(path: 'renamed') + + expect(File.directory?(expected_repository_path)).to be(true) + expect(File.directory?(expected_upload_path)).to be(true) + expect(File.directory?(expected_pages_path)).to be(true) + end end - it 'correctly moves the repository, uploads and pages' do - expected_repository_path = File.join(TestEnv.repos_path, 'parent', 'renamed', 'the-project.git') - expected_upload_path = File.join(uploads_dir, 'renamed', 'the-project') - expected_pages_path = File.join(pages_dir, 'renamed', 'the-project') + context 'renaming parent' do + it 'correctly moves the repository, uploads and pages' do + expected_repository_path = File.join(TestEnv.repos_path, 'renamed', 'child', 'the-project.git') + expected_upload_path = File.join(uploads_dir, 'renamed', 'child', 'the-project') + expected_pages_path = File.join(pages_dir, 'renamed', 'child', 'the-project') - child.update_attributes!(path: 'renamed') + parent.update_attributes!(path: 'renamed') - expect(File.directory?(expected_repository_path)).to be(true) - expect(File.directory?(expected_upload_path)).to be(true) - expect(File.directory?(expected_pages_path)).to be(true) + expect(File.directory?(expected_repository_path)).to be(true) + expect(File.directory?(expected_upload_path)).to be(true) + expect(File.directory?(expected_pages_path)).to be(true) + end end end end @@ -295,4 +315,13 @@ describe Namespace, models: true do to eq([namespace.owner_id]) end end + + describe '#all_projects' do + let(:group) { create(:group) } + let(:child) { create(:group, parent: group) } + let!(:project1) { create(:project_empty_repo, namespace: group) } + let!(:project2) { create(:project_empty_repo, namespace: child) } + + it { expect(group.all_projects.to_a).to eq([project2, project1]) } + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index a899cfcceb7..abb61660e5d 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1158,11 +1158,12 @@ describe Project, models: true 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}"). @@ -1186,10 +1187,13 @@ describe Project, models: true do project.rename_repo end - context 'container registry with tags' do + context 'container registry with images' do + let(:container_repository) { create(:container_repository) } + before do stub_container_registry_config(enabled: true) - stub_container_registry_tags('tag') + stub_container_registry_tags(repository: :any, tags: ['tag']) + project.container_repositories << container_repository end subject { project.rename_repo } @@ -1387,38 +1391,17 @@ describe Project, models: true do end end - describe '#container_registry_path_with_namespace' do - let(:project) { create(:empty_project, path: 'PROJECT') } - - subject { project.container_registry_path_with_namespace } - - it { is_expected.not_to eq(project.path_with_namespace) } - it { is_expected.to eq(project.path_with_namespace.downcase) } - end - - describe '#container_registry_repository' do - let(:project) { create(:empty_project) } - - before { stub_container_registry_config(enabled: true) } - - subject { project.container_registry_repository } - - it { is_expected.not_to be_nil } - end - - describe '#container_registry_repository_url' do + describe '#container_registry_url' do let(:project) { create(:empty_project) } - subject { project.container_registry_repository_url } + subject { project.container_registry_url } before { stub_container_registry_config(**registry_settings) } context 'for enabled registry' do let(:registry_settings) do - { - enabled: true, - host_port: 'example.com', - } + { enabled: true, + host_port: 'example.com' } end it { is_expected.not_to be_nil } @@ -1426,9 +1409,7 @@ describe Project, models: true do context 'for disabled registry' do let(:registry_settings) do - { - enabled: false - } + { enabled: false } end it { is_expected.to be_nil } @@ -1438,28 +1419,60 @@ describe Project, models: true do describe '#has_container_registry_tags?' do let(:project) { create(:empty_project) } - subject { project.has_container_registry_tags? } - - context 'for enabled registry' do + context 'when container registry is enabled' do before { stub_container_registry_config(enabled: true) } - context 'with tags' do - before { stub_container_registry_tags('test', 'test2') } + context 'when tags are present for multi-level registries' do + before do + create(:container_repository, project: project, name: 'image') + + stub_container_registry_tags(repository: /image/, + tags: %w[latest rc1]) + end - it { is_expected.to be_truthy } + it 'should have image tags' do + expect(project).to have_container_registry_tags + end end - context 'when no tags' do - before { stub_container_registry_tags } + context 'when tags are present for root repository' do + before do + stub_container_registry_tags(repository: project.full_path, + tags: %w[latest rc1 pre1]) + end - it { is_expected.to be_falsey } + it 'should have image tags' do + expect(project).to have_container_registry_tags + end + end + + context 'when there are no tags at all' do + before do + stub_container_registry_tags(repository: :any, tags: []) + end + + it 'should not have image tags' do + expect(project).not_to have_container_registry_tags + end end end - context 'for disabled registry' do + context 'when container registry is disabled' do before { stub_container_registry_config(enabled: false) } - it { is_expected.to be_falsey } + it 'should not have image tags' do + expect(project).not_to have_container_registry_tags + end + + it 'should not check root repository tags' do + expect(project).not_to receive(:full_path) + expect(project).not_to have_container_registry_tags + end + + it 'should iterate through container repositories' do + expect(project).to receive(:container_repositories) + expect(project).not_to have_container_registry_tags + end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a9e37be1157..6f7b9c2388a 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -28,7 +28,6 @@ describe User, models: true do it { is_expected.to have_many(:merge_requests).dependent(:destroy) } it { is_expected.to have_many(:assigned_merge_requests).dependent(:nullify) } it { is_expected.to have_many(:identities).dependent(:destroy) } - it { is_expected.to have_one(:abuse_report) } it { is_expected.to have_many(:spam_logs).dependent(:destroy) } it { is_expected.to have_many(:todos).dependent(:destroy) } it { is_expected.to have_many(:award_emoji).dependent(:destroy) } @@ -37,6 +36,34 @@ describe User, models: true do it { is_expected.to have_many(:pipelines).dependent(:nullify) } it { is_expected.to have_many(:chat_names).dependent(:destroy) } it { is_expected.to have_many(:uploads).dependent(:destroy) } + it { is_expected.to have_many(:reported_abuse_reports).dependent(:destroy).class_name('AbuseReport') } + + describe "#abuse_report" do + let(:current_user) { create(:user) } + let(:other_user) { create(:user) } + + it { is_expected.to have_one(:abuse_report) } + + it "refers to the abuse report whose user_id is the current user" do + abuse_report = create(:abuse_report, reporter: other_user, user: current_user) + + expect(current_user.abuse_report).to eq(abuse_report) + end + + it "does not refer to the abuse report whose reporter_id is the current user" do + create(:abuse_report, reporter: current_user, user: other_user) + + expect(current_user.abuse_report).to be_nil + end + + it "does not update the user_id of an abuse report when the user is updated" do + abuse_report = create(:abuse_report, reporter: current_user, user: other_user) + + current_user.block + + expect(abuse_report.reload.user).to eq(other_user) + end + end describe '#group_members' do it 'does not include group memberships for which user is a requester' do @@ -1407,6 +1434,17 @@ describe User, models: true do it { expect(user.nested_groups).to eq([nested_group]) } end + describe '#all_expanded_groups' do + let!(:user) { create(:user) } + let!(:group) { create(:group) } + let!(:nested_group_1) { create(:group, parent: group) } + let!(:nested_group_2) { create(:group, parent: group) } + + before { nested_group_1.add_owner(user) } + + it { expect(user.all_expanded_groups).to match_array [group, nested_group_1] } + end + describe '#nested_groups_projects' do let!(:user) { create(:user) } let!(:group) { create(:group) } @@ -1521,4 +1559,76 @@ describe User, models: true do end end end + + describe '#update_two_factor_requirement' do + let(:user) { create :user } + + context 'with 2FA requirement on groups' do + let(:group1) { create :group, require_two_factor_authentication: true, two_factor_grace_period: 23 } + let(:group2) { create :group, require_two_factor_authentication: true, two_factor_grace_period: 32 } + + before do + group1.add_user(user, GroupMember::OWNER) + group2.add_user(user, GroupMember::OWNER) + + user.update_two_factor_requirement + end + + it 'requires 2FA' do + expect(user.require_two_factor_authentication_from_group).to be true + end + + it 'uses the shortest grace period' do + expect(user.two_factor_grace_period).to be 23 + end + end + + context 'with 2FA requirement on nested parent group' do + let!(:group1) { create :group, require_two_factor_authentication: true } + let!(:group1a) { create :group, require_two_factor_authentication: false, parent: group1 } + + before do + group1a.add_user(user, GroupMember::OWNER) + + user.update_two_factor_requirement + end + + it 'requires 2FA' do + expect(user.require_two_factor_authentication_from_group).to be true + end + end + + context 'with 2FA requirement on nested child group' do + let!(:group1) { create :group, require_two_factor_authentication: false } + let!(:group1a) { create :group, require_two_factor_authentication: true, parent: group1 } + + before do + group1.add_user(user, GroupMember::OWNER) + + user.update_two_factor_requirement + end + + it 'requires 2FA' do + expect(user.require_two_factor_authentication_from_group).to be true + end + end + + context 'without 2FA requirement on groups' do + let(:group) { create :group } + + before do + group.add_user(user, GroupMember::OWNER) + + user.update_two_factor_requirement + end + + it 'does not require 2FA' do + expect(user.require_two_factor_authentication_from_group).to be false + end + + it 'falls back to the default grace period' do + expect(user.two_factor_grace_period).to be 48 + end + end + end end |