diff options
Diffstat (limited to 'spec/models')
-rw-r--r-- | spec/models/board_group_recent_visit_spec.rb | 64 | ||||
-rw-r--r-- | spec/models/board_project_recent_visit_spec.rb | 64 | ||||
-rw-r--r-- | spec/models/clusters/cluster_spec.rb | 67 | ||||
-rw-r--r-- | spec/models/clusters/group_spec.rb | 8 | ||||
-rw-r--r-- | spec/models/environment_spec.rb | 41 | ||||
-rw-r--r-- | spec/models/environment_status_spec.rb | 32 | ||||
-rw-r--r-- | spec/models/global_milestone_spec.rb | 35 | ||||
-rw-r--r-- | spec/models/group_spec.rb | 2 | ||||
-rw-r--r-- | spec/models/merge_request_spec.rb | 20 | ||||
-rw-r--r-- | spec/models/milestone_spec.rb | 37 | ||||
-rw-r--r-- | spec/models/ssh_host_key_spec.rb | 164 |
11 files changed, 461 insertions, 73 deletions
diff --git a/spec/models/board_group_recent_visit_spec.rb b/spec/models/board_group_recent_visit_spec.rb new file mode 100644 index 00000000000..59ad4e5417e --- /dev/null +++ b/spec/models/board_group_recent_visit_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe BoardGroupRecentVisit do + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:board) { create(:board, group: group) } + + describe 'relationships' do + it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:group) } + it { is_expected.to belong_to(:board) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:user) } + it { is_expected.to validate_presence_of(:group) } + it { is_expected.to validate_presence_of(:board) } + end + + describe '#visited' do + it 'creates a visit if one does not exists' do + expect { described_class.visited!(user, board) }.to change(described_class, :count).by(1) + end + + shared_examples 'was visited previously' do + let!(:visit) { create :board_group_recent_visit, group: board.group, board: board, user: user, updated_at: 7.days.ago } + + it 'updates the timestamp' do + Timecop.freeze do + described_class.visited!(user, board) + + expect(described_class.count).to eq 1 + expect(described_class.first.updated_at).to be_like_time(Time.zone.now) + end + end + end + + it_behaves_like 'was visited previously' + + context 'when we try to create a visit that is not unique' do + before do + expect(described_class).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique, 'record not unique') + expect(described_class).to receive(:find_or_create_by).and_return(visit) + end + + it_behaves_like 'was visited previously' + end + end + + describe '#latest' do + it 'returns the most recent visited' do + board2 = create(:board, group: group) + board3 = create(:board, group: group) + + create :board_group_recent_visit, group: board.group, board: board, user: user, updated_at: 7.days.ago + create :board_group_recent_visit, group: board2.group, board: board2, user: user, updated_at: 5.days.ago + recent = create :board_group_recent_visit, group: board3.group, board: board3, user: user, updated_at: 1.day.ago + + expect(described_class.latest(user, group)).to eq recent + end + end +end diff --git a/spec/models/board_project_recent_visit_spec.rb b/spec/models/board_project_recent_visit_spec.rb new file mode 100644 index 00000000000..275581945fa --- /dev/null +++ b/spec/models/board_project_recent_visit_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe BoardProjectRecentVisit do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:board) { create(:board, project: project) } + + describe 'relationships' do + it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:board) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:user) } + it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:board) } + end + + describe '#visited' do + it 'creates a visit if one does not exists' do + expect { described_class.visited!(user, board) }.to change(described_class, :count).by(1) + end + + shared_examples 'was visited previously' do + let!(:visit) { create :board_project_recent_visit, project: board.project, board: board, user: user, updated_at: 7.days.ago } + + it 'updates the timestamp' do + Timecop.freeze do + described_class.visited!(user, board) + + expect(described_class.count).to eq 1 + expect(described_class.first.updated_at).to be_like_time(Time.zone.now) + end + end + end + + it_behaves_like 'was visited previously' + + context 'when we try to create a visit that is not unique' do + before do + expect(described_class).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique, 'record not unique') + expect(described_class).to receive(:find_or_create_by).and_return(visit) + end + + it_behaves_like 'was visited previously' + end + end + + describe '#latest' do + it 'returns the most recent visited' do + board2 = create(:board, project: project) + board3 = create(:board, project: project) + + create :board_project_recent_visit, project: board.project, board: board, user: user, updated_at: 7.days.ago + create :board_project_recent_visit, project: board2.project, board: board2, user: user, updated_at: 5.days.ago + recent = create :board_project_recent_visit, project: board3.project, board: board3, user: user, updated_at: 1.day.ago + + expect(described_class.latest(user, project)).to eq recent + end + end +end diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index f5c4b0b66ae..c245e8df815 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -4,7 +4,10 @@ require 'spec_helper' describe Clusters::Cluster do it { is_expected.to belong_to(:user) } + it { is_expected.to have_many(:cluster_projects) } it { is_expected.to have_many(:projects) } + it { is_expected.to have_many(:cluster_groups) } + it { is_expected.to have_many(:groups) } it { is_expected.to have_one(:provider_gcp) } it { is_expected.to have_one(:platform_kubernetes) } it { is_expected.to have_one(:application_helm) } @@ -178,6 +181,53 @@ describe Clusters::Cluster do it { expect(cluster.update(enabled: false)).to be_truthy } end end + + describe 'cluster_type validations' do + let(:instance_cluster) { create(:cluster, :instance) } + let(:group_cluster) { create(:cluster, :group) } + let(:project_cluster) { create(:cluster, :project) } + + it 'validates presence' do + cluster = build(:cluster, :project, cluster_type: nil) + + expect(cluster).not_to be_valid + expect(cluster.errors.full_messages).to include("Cluster type can't be blank") + end + + context 'project_type cluster' do + it 'does not allow setting group' do + project_cluster.groups << build(:group) + + expect(project_cluster).not_to be_valid + expect(project_cluster.errors.full_messages).to include('Cluster cannot have groups assigned') + end + end + + context 'group_type cluster' do + it 'does not allow setting project' do + group_cluster.projects << build(:project) + + expect(group_cluster).not_to be_valid + expect(group_cluster.errors.full_messages).to include('Cluster cannot have projects assigned') + end + end + + context 'instance_type cluster' do + it 'does not allow setting group' do + instance_cluster.groups << build(:group) + + expect(instance_cluster).not_to be_valid + expect(instance_cluster.errors.full_messages).to include('Cluster cannot have groups assigned') + end + + it 'does not allow setting project' do + instance_cluster.projects << build(:project) + + expect(instance_cluster).not_to be_valid + expect(instance_cluster.errors.full_messages).to include('Cluster cannot have projects assigned') + end + end + end end describe '#provider' do @@ -229,6 +279,23 @@ describe Clusters::Cluster do end end + describe '#group' do + subject { cluster.group } + + context 'when cluster belongs to a group' do + let(:cluster) { create(:cluster, :group) } + let(:group) { cluster.groups.first } + + it { is_expected.to eq(group) } + end + + context 'when cluster does not belong to any group' do + let(:cluster) { create(:cluster) } + + it { is_expected.to be_nil } + end + end + describe '#applications' do set(:cluster) { create(:cluster) } diff --git a/spec/models/clusters/group_spec.rb b/spec/models/clusters/group_spec.rb new file mode 100644 index 00000000000..ba145342cb8 --- /dev/null +++ b/spec/models/clusters/group_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Group do + it { is_expected.to belong_to(:cluster) } + it { is_expected.to belong_to(:group) } +end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index c65e0b81451..1de95d881a7 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -334,7 +334,7 @@ describe Environment do describe '#has_terminals?' do subject { environment.has_terminals? } - context 'when the enviroment is available' do + context 'when the environment is available' do context 'with a deployment service' do shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do context 'and a deployment' do @@ -447,7 +447,7 @@ describe Environment do describe '#has_metrics?' do subject { environment.has_metrics? } - context 'when the enviroment is available' do + context 'when the environment is available' do context 'with a deployment service' do let(:project) { create(:prometheus_project) } @@ -456,8 +456,8 @@ describe Environment do it { is_expected.to be_truthy } end - context 'but no deployments' do - it { is_expected.to be_falsy } + context 'and no deployments' do + it { is_expected.to be_truthy } end end @@ -504,39 +504,6 @@ describe Environment do end end - describe '#has_metrics?' do - subject { environment.has_metrics? } - - context 'when the enviroment is available' do - context 'with a deployment service' do - let(:project) { create(:prometheus_project) } - - context 'and a deployment' do - let!(:deployment) { create(:deployment, environment: environment) } - it { is_expected.to be_truthy } - end - - context 'but no deployments' do - it { is_expected.to be_falsy } - end - end - - context 'without a monitoring service' do - it { is_expected.to be_falsy } - end - end - - context 'when the environment is unavailable' do - let(:project) { create(:prometheus_project) } - - before do - environment.stop - end - - it { is_expected.to be_falsy } - end - end - describe '#additional_metrics' do let(:project) { create(:prometheus_project) } subject { environment.additional_metrics } diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb index f2eb263c98c..e7805d52d75 100644 --- a/spec/models/environment_status_spec.rb +++ b/spec/models/environment_status_spec.rb @@ -5,13 +5,15 @@ describe EnvironmentStatus do let(:environment) { deployment.environment} let(:project) { deployment.project } let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) } + let(:sha) { deployment.sha } - subject(:environment_status) { described_class.new(environment, merge_request) } + subject(:environment_status) { described_class.new(environment, merge_request, sha) } it { is_expected.to delegate_method(:id).to(:environment) } it { is_expected.to delegate_method(:name).to(:environment) } it { is_expected.to delegate_method(:project).to(:environment) } it { is_expected.to delegate_method(:deployed_at).to(:deployment).as(:created_at) } + it { is_expected.to delegate_method(:status).to(:deployment) } describe '#project' do subject { environment_status.project } @@ -58,4 +60,32 @@ describe EnvironmentStatus do ) end end + + describe '.for_merge_request' do + let(:admin) { create(:admin) } + let(:pipeline) { create(:ci_pipeline, sha: sha) } + + it 'is based on merge_request.head_pipeline' do + expect(merge_request).to receive(:head_pipeline).and_return(pipeline) + expect(merge_request).not_to receive(:merge_pipeline) + + described_class.for_merge_request(merge_request, admin) + end + end + + describe '.after_merge_request' do + let(:admin) { create(:admin) } + let(:pipeline) { create(:ci_pipeline, sha: sha) } + + before do + merge_request.mark_as_merged! + end + + it 'is based on merge_request.merge_pipeline' do + expect(merge_request).to receive(:merge_pipeline).and_return(pipeline) + expect(merge_request).not_to receive(:head_pipeline) + + described_class.after_merge_request(merge_request, admin) + end + end end diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb index ab58f5c5021..b6355455c1d 100644 --- a/spec/models/global_milestone_spec.rb +++ b/spec/models/global_milestone_spec.rb @@ -92,41 +92,6 @@ describe GlobalMilestone do end end - describe '.states_count' do - context 'when the projects have milestones' do - before do - create(:closed_milestone, title: 'Active Group Milestone', project: project3) - create(:active_milestone, title: 'Active Group Milestone', project: project1) - create(:active_milestone, title: 'Active Group Milestone', project: project2) - create(:closed_milestone, title: 'Closed Group Milestone', project: project1) - create(:closed_milestone, title: 'Closed Group Milestone', project: project2) - create(:closed_milestone, title: 'Closed Group Milestone', project: project3) - end - - it 'returns the quantity of global milestones in each possible state' do - expected_count = { opened: 1, closed: 2, all: 2 } - - count = described_class.states_count(Project.all) - - expect(count).to eq(expected_count) - end - end - - context 'when the projects do not have milestones' do - before do - project1 - end - - it 'returns 0 as the quantity of global milestones in each state' do - expected_count = { opened: 0, closed: 0, all: 0 } - - count = described_class.states_count(Project.all) - - expect(count).to eq(expected_count) - end - end - end - describe '#initialize' do let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) } let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) } diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 1bf8f89e126..571b160d901 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -19,6 +19,8 @@ describe Group do it { is_expected.to have_one(:chat_team) } it { is_expected.to have_many(:custom_attributes).class_name('GroupCustomAttribute') } it { is_expected.to have_many(:badges).class_name('GroupBadge') } + it { is_expected.to have_many(:cluster_groups).class_name('Clusters::Group') } + it { is_expected.to have_many(:clusters).class_name('Clusters::Cluster') } describe '#members & #requesters' do let(:requester) { create(:user) } diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 666d7e69f89..c8943f2d86f 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1058,6 +1058,26 @@ describe MergeRequest do end end + describe '#merge_pipeline' do + it 'returns nil when not merged' do + expect(subject.merge_pipeline).to be_nil + end + + context 'when the MR is merged' do + let(:sha) { subject.target_project.commit.id } + let(:pipeline) { create(:ci_empty_pipeline, sha: sha, ref: subject.target_branch, project: subject.target_project) } + + before do + subject.mark_as_merged! + subject.update_attribute(:merge_commit_sha, pipeline.sha) + end + + it 'returns the post-merge pipeline' do + expect(subject.merge_pipeline).to eq(pipeline) + end + end + end + describe '#has_ci?' do let(:merge_request) { build_stubbed(:merge_request) } diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 27d4e622710..d11eb46159e 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -348,4 +348,41 @@ describe Milestone do end end end + + describe '.states_count' do + context 'when the projects have milestones' do + before do + project_1 = create(:project) + project_2 = create(:project) + group_1 = create(:group) + group_2 = create(:group) + + create(:active_milestone, title: 'Active Group Milestone', project: project_1) + create(:closed_milestone, title: 'Closed Group Milestone', project: project_1) + create(:active_milestone, title: 'Active Group Milestone', project: project_2) + create(:closed_milestone, title: 'Closed Group Milestone', project: project_2) + create(:closed_milestone, title: 'Active Group Milestone', group: group_1) + create(:closed_milestone, title: 'Closed Group Milestone', group: group_1) + create(:closed_milestone, title: 'Active Group Milestone', group: group_2) + create(:closed_milestone, title: 'Closed Group Milestone', group: group_2) + end + + it 'returns the quantity of milestones in each possible state' do + expected_count = { opened: 5, closed: 6, all: 11 } + + count = described_class.states_count(Project.all, Group.all) + expect(count).to eq(expected_count) + end + end + + context 'when the projects do not have milestones' do + it 'returns 0 as the quantity of global milestones in each state' do + expected_count = { opened: 0, closed: 0, all: 0 } + + count = described_class.states_count([project]) + + expect(count).to eq(expected_count) + end + end + end end diff --git a/spec/models/ssh_host_key_spec.rb b/spec/models/ssh_host_key_spec.rb new file mode 100644 index 00000000000..75db43b3d56 --- /dev/null +++ b/spec/models/ssh_host_key_spec.rb @@ -0,0 +1,164 @@ +require 'spec_helper' + +describe SshHostKey do + using RSpec::Parameterized::TableSyntax + include ReactiveCachingHelpers + + let(:key1) do + 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC3UpyF2iLqy1d63M6k3jH1vuEnq/NWtE+o' \ + 'rJe1Xn7JoRbduKd6zpsJ0JhBGWgcQK0ph0aGW5PcudzzBSc+SlYfCc4GTaxDtmj41hW0o72m' \ + 'NiuDW3oKXXShOiVRde2ZOquH8Z865jGiZIC8BI/bXZD29IGUih0hPu7Rjp70VYiE+35QRf/p' \ + 'sD0Ddrz8QUIG3A/2dMzLI5F5ZORk3BIX2F3mJwJOvZxRhR/SqyphDMZ5eZ0EzqbFBCDE6HAB' \ + 'Woz9ck8RBGLvCIggmDHj3FmMLcQGMDiy6wKp7QdnBtxjCP6vtE6YPUM223AqsWt+9NTtCfB8' \ + 'YdNAH7YcHHOR1FgtSk1x' + end + + let(:key2) do + 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLIp+4ciR2YO9f9rpldc7InNQw/TBUtcNb' \ + 'J2XR0rr15/5ytz7YM16xXG0Qjx576PNSmqs4gbTrvTuFZak+v1Jx/9deHRq/yqp9f+tv33+i' \ + 'aJGCQCX/+OVY7aWgV2R9YsS7XQ4mnv4XlOTEssib/rGAIT+ATd/GcdYSEOO+dh4O09/6O/jI' \ + 'MGSeP+NNetgn1nPCnLOjrXFZUnUtNDi6EEKeIlrliJjSb7Jr4f7gjvZnv4RskWHHFo8FgAAq' \ + 't0gOMT6EmKrnypBe2vLGSAXbtkXr01q6/DNPH+n9VA1LTV6v1KN/W5CN5tQV11wRSKiM8g5O' \ + 'Ebi86VjJRi2sOuYoXQU1' + end + + # Purposefully ordered so that `sort` will make changes + let(:known_hosts) do + <<~EOF + example.com #{key1} git@localhost + @revoked other.example.com #{key2} git@localhost + EOF + end + + let(:extra) { known_hosts + "foo\nbar\n" } + let(:reversed) { known_hosts.lines.reverse.join } + + let(:compare_host_keys) { nil } + + def stub_ssh_keyscan(args, status: true, stdout: "", stderr: "") + stdin = StringIO.new + stdout = double(:stdout, read: stdout) + stderr = double(:stderr, read: stderr) + wait_thr = double(:wait_thr, value: double(success?: status)) + + expect(Open3).to receive(:popen3).with({}, 'ssh-keyscan', *args).and_yield(stdin, stdout, stderr, wait_thr) + + stdin + end + + let(:project) { build(:project) } + + subject(:ssh_host_key) { described_class.new(project: project, url: 'ssh://example.com:2222', compare_host_keys: compare_host_keys) } + + describe '#fingerprints', :use_clean_rails_memory_store_caching do + it 'returns an array of indexed fingerprints when the cache is filled' do + stub_reactive_cache(ssh_host_key, known_hosts: known_hosts) + + expected = [key1, key2] + .map { |data| Gitlab::SSHPublicKey.new(data) } + .each_with_index + .map { |key, i| { bits: key.bits, fingerprint: key.fingerprint, type: key.type, index: i } } + + expect(ssh_host_key.fingerprints.as_json).to eq(expected) + end + + it 'returns an empty array when the cache is empty' do + expect(ssh_host_key.fingerprints).to eq([]) + end + end + + describe '#fingerprints', :use_clean_rails_memory_store_caching do + it 'returns an array of indexed fingerprints when the cache is filled' do + stub_reactive_cache(ssh_host_key, known_hosts: known_hosts) + + expect(ssh_host_key.fingerprints.as_json).to eq( + [ + { bits: 2048, fingerprint: Gitlab::SSHPublicKey.new(key1).fingerprint, type: :rsa, index: 0 }, + { bits: 2048, fingerprint: Gitlab::SSHPublicKey.new(key2).fingerprint, type: :rsa, index: 1 } + ] + ) + end + + it 'returns an empty array when the cache is empty' do + expect(ssh_host_key.fingerprints).to eq([]) + end + end + + describe '#host_keys_changed?' do + where(:known_hosts_a, :known_hosts_b, :result) do + known_hosts | extra | true + known_hosts | "foo\n" | true + known_hosts | '' | true + known_hosts | nil | true + known_hosts | known_hosts | false + reversed | known_hosts | false + extra | "foo\n" | true + '' | '' | false + nil | nil | false + '' | nil | false + end + + with_them do + let(:compare_host_keys) { known_hosts_b } + + subject { ssh_host_key.host_keys_changed? } + + context '(normal)' do + let(:compare_host_keys) { known_hosts_b } + + before do + expect(ssh_host_key).to receive(:known_hosts).and_return(known_hosts_a) + end + + it { is_expected.to eq(result) } + end + + # Comparisons should be symmetrical, so test the reverse too + context '(reversed)' do + let(:compare_host_keys) { known_hosts_a } + + before do + expect(ssh_host_key).to receive(:known_hosts).and_return(known_hosts_b) + end + + it { is_expected.to eq(result) } + end + end + end + + describe '#calculate_reactive_cache' do + subject(:cache) { ssh_host_key.calculate_reactive_cache } + + it 'writes the hostname to STDIN' do + stdin = stub_ssh_keyscan(%w[-T 5 -p 2222 -f-]) + + cache + + expect(stdin.string).to eq("example.com\n") + end + + context 'successful key scan' do + it 'stores the cleaned known_hosts data' do + stub_ssh_keyscan(%w[-T 5 -p 2222 -f-], stdout: "KEY 1\nKEY 1\n\n# comment\nKEY 2\n") + + is_expected.to eq(known_hosts: "KEY 1\nKEY 2\n") + end + end + + context 'failed key scan (exit code 1)' do + it 'returns a generic error' do + stub_ssh_keyscan(%w[-T 5 -p 2222 -f-], stdout: 'blarg', status: false) + + is_expected.to eq(error: 'Failed to detect SSH host keys') + end + end + + context 'failed key scan (exit code 0)' do + it 'returns a generic error' do + stub_ssh_keyscan(%w[-T 5 -p 2222 -f-], stderr: 'Unknown host') + + is_expected.to eq(error: 'Failed to detect SSH host keys') + end + end + end +end |