diff options
Diffstat (limited to 'spec/lib/gitlab')
37 files changed, 1299 insertions, 77 deletions
diff --git a/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb b/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb new file mode 100644 index 00000000000..1a4ea2bac48 --- /dev/null +++ b/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb @@ -0,0 +1,117 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::CreateForkNetworkMembershipsRange, :migration, schema: 20170929131201 do + let(:migration) { described_class.new } + + let(:base1) { create(:project) } + let(:base1_fork1) { create(:project) } + let(:base1_fork2) { create(:project) } + + let(:base2) { create(:project) } + let(:base2_fork1) { create(:project) } + let(:base2_fork2) { create(:project) } + + let(:fork_of_fork) { create(:project) } + let(:fork_of_fork2) { create(:project) } + let(:second_level_fork) { create(:project) } + let(:third_level_fork) { create(:project) } + + let(:fork_network1) { fork_networks.find_by(root_project_id: base1.id) } + let(:fork_network2) { fork_networks.find_by(root_project_id: base2.id) } + + let!(:forked_project_links) { table(:forked_project_links) } + let!(:fork_networks) { table(:fork_networks) } + let!(:fork_network_members) { table(:fork_network_members) } + + before do + # The fork-network relation created for the forked project + fork_networks.create(id: 1, root_project_id: base1.id) + fork_network_members.create(project_id: base1.id, fork_network_id: 1) + fork_networks.create(id: 2, root_project_id: base2.id) + fork_network_members.create(project_id: base2.id, fork_network_id: 2) + + # Normal fork links + forked_project_links.create(id: 1, forked_from_project_id: base1.id, forked_to_project_id: base1_fork1.id) + forked_project_links.create(id: 2, forked_from_project_id: base1.id, forked_to_project_id: base1_fork2.id) + forked_project_links.create(id: 3, forked_from_project_id: base2.id, forked_to_project_id: base2_fork1.id) + forked_project_links.create(id: 4, forked_from_project_id: base2.id, forked_to_project_id: base2_fork2.id) + + # Fork links + forked_project_links.create(id: 5, forked_from_project_id: base1_fork1.id, forked_to_project_id: fork_of_fork.id) + forked_project_links.create(id: 6, forked_from_project_id: base1_fork1.id, forked_to_project_id: fork_of_fork2.id) + + # Forks 3 levels down + forked_project_links.create(id: 7, forked_from_project_id: fork_of_fork.id, forked_to_project_id: second_level_fork.id) + forked_project_links.create(id: 8, forked_from_project_id: second_level_fork.id, forked_to_project_id: third_level_fork.id) + + migration.perform(1, 8) + end + + it 'creates a memberships for the direct forks' do + base1_fork1_membership = fork_network_members.find_by(fork_network_id: fork_network1.id, + project_id: base1_fork1.id) + base1_fork2_membership = fork_network_members.find_by(fork_network_id: fork_network1.id, + project_id: base1_fork2.id) + base2_fork1_membership = fork_network_members.find_by(fork_network_id: fork_network2.id, + project_id: base2_fork1.id) + base2_fork2_membership = fork_network_members.find_by(fork_network_id: fork_network2.id, + project_id: base2_fork2.id) + + expect(base1_fork1_membership.forked_from_project_id).to eq(base1.id) + expect(base1_fork2_membership.forked_from_project_id).to eq(base1.id) + expect(base2_fork1_membership.forked_from_project_id).to eq(base2.id) + expect(base2_fork2_membership.forked_from_project_id).to eq(base2.id) + end + + it 'adds the fork network members for forks of forks' do + fork_of_fork_membership = fork_network_members.find_by(project_id: fork_of_fork.id, + fork_network_id: fork_network1.id) + fork_of_fork2_membership = fork_network_members.find_by(project_id: fork_of_fork2.id, + fork_network_id: fork_network1.id) + second_level_fork_membership = fork_network_members.find_by(project_id: second_level_fork.id, + fork_network_id: fork_network1.id) + third_level_fork_membership = fork_network_members.find_by(project_id: third_level_fork.id, + fork_network_id: fork_network1.id) + + expect(fork_of_fork_membership.forked_from_project_id).to eq(base1_fork1.id) + expect(fork_of_fork2_membership.forked_from_project_id).to eq(base1_fork1.id) + expect(second_level_fork_membership.forked_from_project_id).to eq(fork_of_fork.id) + expect(third_level_fork_membership.forked_from_project_id).to eq(second_level_fork.id) + end + + it 'reschedules itself when there are missing members' do + allow(migration).to receive(:missing_members?).and_return(true) + + expect(BackgroundMigrationWorker) + .to receive(:perform_in).with(described_class::RESCHEDULE_DELAY, "CreateForkNetworkMembershipsRange", [1, 3]) + + migration.perform(1, 3) + end + + it 'can be repeated without effect' do + expect { fork_network_members.count }.not_to change { migration.perform(1, 7) } + end + + it 'knows it is finished for this range' do + expect(migration.missing_members?(1, 7)).to be_falsy + end + + context 'with more forks' do + before do + forked_project_links.create(id: 9, forked_from_project_id: fork_of_fork.id, forked_to_project_id: create(:project).id) + forked_project_links.create(id: 10, forked_from_project_id: fork_of_fork.id, forked_to_project_id: create(:project).id) + end + + it 'only processes a single batch of links at a time' do + expect(fork_network_members.count).to eq(10) + + migration.perform(8, 10) + + expect(fork_network_members.count).to eq(12) + end + + it 'knows when not all memberships withing a batch have been created' do + expect(migration.missing_members?(8, 10)).to be_truthy + end + end +end diff --git a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb index 59f69d1e4b1..7b5a00c6111 100644 --- a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder do end describe '#perform' do - it 'renames the path of system-uploads', truncate: true do + it 'renames the path of system-uploads', :truncate do upload = create(:upload, model: create(:project), path: 'uploads/system/project/avatar.jpg') migration.perform('uploads/system/', 'uploads/-/system/') diff --git a/spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb b/spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb new file mode 100644 index 00000000000..dfbf1bb681a --- /dev/null +++ b/spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::NormalizeLdapExternUidsRange, :migration, schema: 20170921101004 do + let!(:identities) { table(:identities) } + + before do + # LDAP identities + (1..4).each do |i| + identities.create!(id: i, provider: 'ldapmain', extern_uid: " uid = foo #{i}, ou = People, dc = example, dc = com ", user_id: i) + end + + # Non-LDAP identity + identities.create!(id: 5, provider: 'foo', extern_uid: " uid = foo 5, ou = People, dc = example, dc = com ", user_id: 5) + + # Another LDAP identity + identities.create!(id: 6, provider: 'ldapmain', extern_uid: " uid = foo 6, ou = People, dc = example, dc = com ", user_id: 6) + end + + it 'normalizes the LDAP identities in the range' do + described_class.new.perform(1, 3) + expect(identities.find(1).extern_uid).to eq("uid=foo 1,ou=people,dc=example,dc=com") + expect(identities.find(2).extern_uid).to eq("uid=foo 2,ou=people,dc=example,dc=com") + expect(identities.find(3).extern_uid).to eq("uid=foo 3,ou=people,dc=example,dc=com") + expect(identities.find(4).extern_uid).to eq(" uid = foo 4, ou = People, dc = example, dc = com ") + expect(identities.find(5).extern_uid).to eq(" uid = foo 5, ou = People, dc = example, dc = com ") + expect(identities.find(6).extern_uid).to eq(" uid = foo 6, ou = People, dc = example, dc = com ") + + described_class.new.perform(4, 6) + expect(identities.find(1).extern_uid).to eq("uid=foo 1,ou=people,dc=example,dc=com") + expect(identities.find(2).extern_uid).to eq("uid=foo 2,ou=people,dc=example,dc=com") + expect(identities.find(3).extern_uid).to eq("uid=foo 3,ou=people,dc=example,dc=com") + expect(identities.find(4).extern_uid).to eq("uid=foo 4,ou=people,dc=example,dc=com") + expect(identities.find(5).extern_uid).to eq(" uid = foo 5, ou = People, dc = example, dc = com ") + expect(identities.find(6).extern_uid).to eq("uid=foo 6,ou=people,dc=example,dc=com") + end +end diff --git a/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb b/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb new file mode 100644 index 00000000000..2c2684a6fc9 --- /dev/null +++ b/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb @@ -0,0 +1,93 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, schema: 20170929131201 do + let(:migration) { described_class.new } + let(:base1) { create(:project) } + let(:base1_fork1) { create(:project) } + let(:base1_fork2) { create(:project) } + + let(:base2) { create(:project) } + let(:base2_fork1) { create(:project) } + let(:base2_fork2) { create(:project) } + + let!(:forked_project_links) { table(:forked_project_links) } + let!(:fork_networks) { table(:fork_networks) } + let!(:fork_network_members) { table(:fork_network_members) } + + let(:fork_network1) { fork_networks.find_by(root_project_id: base1.id) } + let(:fork_network2) { fork_networks.find_by(root_project_id: base2.id) } + + before do + # A normal fork link + forked_project_links.create(id: 1, + forked_from_project_id: base1.id, + forked_to_project_id: base1_fork1.id) + forked_project_links.create(id: 2, + forked_from_project_id: base1.id, + forked_to_project_id: base1_fork2.id) + + forked_project_links.create(id: 3, + forked_from_project_id: base2.id, + forked_to_project_id: base2_fork1.id) + forked_project_links.create(id: 4, + forked_from_project_id: base2_fork1.id, + forked_to_project_id: create(:project).id) + + forked_project_links.create(id: 5, + forked_from_project_id: base2.id, + forked_to_project_id: base2_fork2.id) + + migration.perform(1, 3) + end + + it 'it creates the fork network' do + expect(fork_network1).not_to be_nil + expect(fork_network2).not_to be_nil + end + + it 'does not create a fork network for a fork-of-fork' do + # perfrom the entire batch + migration.perform(1, 5) + + expect(fork_networks.find_by(root_project_id: base2_fork1.id)).to be_nil + end + + it 'creates memberships for the root of fork networks' do + base1_membership = fork_network_members.find_by(fork_network_id: fork_network1.id, + project_id: base1.id) + base2_membership = fork_network_members.find_by(fork_network_id: fork_network2.id, + project_id: base2.id) + + expect(base1_membership).not_to be_nil + expect(base2_membership).not_to be_nil + end + + it 'skips links that had their source project deleted' do + forked_project_links.create(id: 6, forked_from_project_id: 99999, forked_to_project_id: create(:project).id) + + migration.perform(5, 8) + + expect(fork_networks.find_by(root_project_id: 99999)).to be_nil + end + + it 'schedules a job for inserting memberships for forks-of-forks' do + delay = Gitlab::BackgroundMigration::CreateForkNetworkMembershipsRange::RESCHEDULE_DELAY + + expect(BackgroundMigrationWorker) + .to receive(:perform_in).with(delay, "CreateForkNetworkMembershipsRange", [1, 3]) + + migration.perform(1, 3) + end + + it 'only processes a single batch of links at a time' do + expect(fork_network_members.count).to eq(5) + + migration.perform(3, 5) + + expect(fork_network_members.count).to eq(7) + end + + it 'can be repeated without effect' do + expect { migration.perform(1, 3) }.not_to change { fork_network_members.count } + end +end diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb index 2c7ef622c51..633e319f46d 100644 --- a/spec/lib/gitlab/checks/force_push_spec.rb +++ b/spec/lib/gitlab/checks/force_push_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Checks::ForcePush do let(:project) { create(:project, :repository) } - context "exit code checking", skip_gitaly_mock: true do + context "exit code checking", :skip_gitaly_mock do it "does not raise a runtime error if the `popen` call to git returns a zero exit code" do allow_any_instance_of(Gitlab::Git::RevList).to receive(:popen).and_return(['normal output', 0]) diff --git a/spec/lib/gitlab/ci/trace/section_parser_spec.rb b/spec/lib/gitlab/ci/trace/section_parser_spec.rb new file mode 100644 index 00000000000..ca53ff87c6f --- /dev/null +++ b/spec/lib/gitlab/ci/trace/section_parser_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe Gitlab::Ci::Trace::SectionParser do + def lines_with_pos(text) + pos = 0 + StringIO.new(text).each_line do |line| + yield line, pos + pos += line.bytesize + 1 # newline + end + end + + def build_lines(text) + to_enum(:lines_with_pos, text) + end + + def section(name, start, duration, text) + end_ = start + duration + "section_start:#{start.to_i}:#{name}\r\033[0K#{text}section_end:#{end_.to_i}:#{name}\r\033[0K" + end + + let(:lines) { build_lines('') } + subject { described_class.new(lines) } + + describe '#sections' do + before do + subject.parse! + end + + context 'empty trace' do + let(:lines) { build_lines('') } + + it { expect(subject.sections).to be_empty } + end + + context 'with a sectionless trace' do + let(:lines) { build_lines("line 1\nline 2\n") } + + it { expect(subject.sections).to be_empty } + end + + context 'with trace markers' do + let(:start_time) { Time.new(2017, 10, 5).utc } + let(:section_b_duration) { 1.second } + let(:section_a) { section('a', start_time, 0, 'a line') } + let(:section_b) { section('b', start_time, section_b_duration, "another line\n") } + let(:lines) { build_lines(section_a + section_b) } + + it { expect(subject.sections.size).to eq(2) } + it { expect(subject.sections[1][:name]).to eq('b') } + it { expect(subject.sections[1][:date_start]).to eq(start_time) } + it { expect(subject.sections[1][:date_end]).to eq(start_time + section_b_duration) } + end + end + + describe '#parse!' do + context 'multiple "section_" but no complete markers' do + let(:lines) { build_lines('section_section_section_') } + + it 'must find 3 possible section start but no complete sections' do + expect(subject).to receive(:find_next_marker).exactly(3).times.and_call_original + + subject.parse! + + expect(subject.sections).to be_empty + end + end + + context 'trace with UTF-8 chars' do + let(:line) { 'GitLab ❤️ 狸 (tanukis)\n' } + let(:trace) { section('test_section', Time.new(2017, 10, 5).utc, 3.seconds, line) } + let(:lines) { build_lines(trace) } + + it 'must handle correctly byte positioning' do + expect(subject).to receive(:find_next_marker).exactly(2).times.and_call_original + + subject.parse! + + sections = subject.sections + + expect(sections.size).to eq(1) + s = sections[0] + len = s[:byte_end] - s[:byte_start] + expect(trace.byteslice(s[:byte_start], len)).to eq(line) + end + end + end +end diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb index 9cb0b62590a..3546532b9b4 100644 --- a/spec/lib/gitlab/ci/trace_spec.rb +++ b/spec/lib/gitlab/ci/trace_spec.rb @@ -61,6 +61,93 @@ describe Gitlab::Ci::Trace do end end + describe '#extract_sections' do + let(:log) { 'No sections' } + let(:sections) { trace.extract_sections } + + before do + trace.set(log) + end + + context 'no sections' do + it 'returs []' do + expect(trace.extract_sections).to eq([]) + end + end + + context 'multiple sections available' do + let(:log) { File.read(expand_fixture_path('trace/trace_with_sections')) } + let(:sections_data) do + [ + { name: 'prepare_script', lines: 2, duration: 3.seconds }, + { name: 'get_sources', lines: 4, duration: 1.second }, + { name: 'restore_cache', lines: 0, duration: 0.seconds }, + { name: 'download_artifacts', lines: 0, duration: 0.seconds }, + { name: 'build_script', lines: 2, duration: 1.second }, + { name: 'after_script', lines: 0, duration: 0.seconds }, + { name: 'archive_cache', lines: 0, duration: 0.seconds }, + { name: 'upload_artifacts', lines: 0, duration: 0.seconds } + ] + end + + it "returns valid sections" do + expect(sections).not_to be_empty + expect(sections.size).to eq(sections_data.size), + "expected #{sections_data.size} sections, got #{sections.size}" + + buff = StringIO.new(log) + sections.each_with_index do |s, i| + expected = sections_data[i] + + expect(s[:name]).to eq(expected[:name]) + expect(s[:date_end] - s[:date_start]).to eq(expected[:duration]) + + buff.seek(s[:byte_start], IO::SEEK_SET) + length = s[:byte_end] - s[:byte_start] + lines = buff.read(length).count("\n") + expect(lines).to eq(expected[:lines]) + end + end + end + + context 'logs contains "section_start"' do + let(:log) { "section_start:1506417476:a_section\r\033[0Klooks like a section_start:invalid\nsection_end:1506417477:a_section\r\033[0K"} + + it "returns only one section" do + expect(sections).not_to be_empty + expect(sections.size).to eq(1) + + section = sections[0] + expect(section[:name]).to eq('a_section') + expect(section[:byte_start]).not_to eq(section[:byte_end]), "got an empty section" + end + end + + context 'missing section_end' do + let(:log) { "section_start:1506417476:a_section\r\033[0KSome logs\nNo section_end\n"} + + it "returns no sections" do + expect(sections).to be_empty + end + end + + context 'missing section_start' do + let(:log) { "Some logs\nNo section_start\nsection_end:1506417476:a_section\r\033[0K"} + + it "returns no sections" do + expect(sections).to be_empty + end + end + + context 'inverted section_start section_end' do + let(:log) { "section_end:1506417476:a_section\r\033[0Klooks like a section_start:invalid\nsection_start:1506417477:a_section\r\033[0K"} + + it "returns no sections" do + expect(sections).to be_empty + end + end + end + describe '#set' do before do trace.set("12") diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb index 90aa4f63dd5..596cc435bd9 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb @@ -229,7 +229,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca end end - describe '#track_rename', redis: true do + describe '#track_rename', :redis do it 'tracks a rename in redis' do key = 'rename:FakeRenameReservedPathMigrationV1:namespace' @@ -246,7 +246,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca end end - describe '#reverts_for_type', redis: true do + describe '#reverts_for_type', :redis do it 'yields for each tracked rename' do subject.track_rename('project', 'old_path', 'new_path') subject.track_rename('project', 'old_path2', 'new_path2') diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb index 32ac0b88a9b..1143182531f 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb @@ -241,7 +241,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : end end - describe '#revert_renames', redis: true do + describe '#revert_renames', :redis do it 'renames the routes back to the previous values' do project = create(:project, :repository, path: 'a-project', namespace: namespace) subject.rename_namespace(namespace) diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb index 595e06a9748..8922370b0a0 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb @@ -115,7 +115,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :tr end end - describe '#revert_renames', redis: true do + describe '#revert_renames', :redis do it 'renames the routes back to the previous values' do subject.rename_project(project) diff --git a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb new file mode 100644 index 00000000000..2f99febe04e --- /dev/null +++ b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Gitlab::Diff::Formatters::ImageFormatter do + it_behaves_like "position formatter" do + let(:base_attrs) do + { + base_sha: 123, + start_sha: 456, + head_sha: 789, + old_path: 'old_image.png', + new_path: 'new_image.png', + position_type: 'image' + } + end + + let(:attrs) do + base_attrs.merge(width: 100, height: 100, x: 1, y: 2) + end + end +end diff --git a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb new file mode 100644 index 00000000000..897dc917f6a --- /dev/null +++ b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe Gitlab::Diff::Formatters::TextFormatter do + let!(:base) do + { + base_sha: 123, + start_sha: 456, + head_sha: 789, + old_path: 'old_path.txt', + new_path: 'new_path.txt' + } + end + + let!(:complete) do + base.merge(old_line: 1, new_line: 2) + end + + it_behaves_like "position formatter" do + let(:base_attrs) { base } + + let(:attrs) { complete } + end + + # Specific text formatter examples + let!(:formatter) { described_class.new(attrs) } + + describe '#line_age' do + subject { formatter.line_age } + + context ' when there is only new_line' do + let(:attrs) { base.merge(new_line: 1) } + + it { is_expected.to eq('new') } + end + + context ' when there is only old_line' do + let(:attrs) { base.merge(old_line: 1) } + + it { is_expected.to eq('old') } + end + end +end diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb index 7798736a4dc..9bf54fdecc4 100644 --- a/spec/lib/gitlab/diff/position_spec.rb +++ b/spec/lib/gitlab/diff/position_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::Diff::Position do let(:project) { create(:project, :repository) } - describe "position for an added file" do + describe "position for an added text file" do let(:commit) { project.commit("2ea1f3dec713d940208fb5ce4a38765ecb5d3f73") } subject do @@ -47,6 +47,31 @@ describe Gitlab::Diff::Position do end end + describe "position for an added image file" do + let(:commit) { project.commit("33f3729a45c02fc67d00adb1b8bca394b0e761d9") } + + subject do + described_class.new( + old_path: "files/images/6049019_460s.jpg", + new_path: "files/images/6049019_460s.jpg", + width: 100, + height: 100, + x: 1, + y: 100, + diff_refs: commit.diff_refs, + position_type: "image" + ) + end + + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.new_file?).to be true + expect(diff_file.new_path).to eq(subject.new_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + describe "position for a changed file" do let(:commit) { project.commit("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") } @@ -468,26 +493,54 @@ describe Gitlab::Diff::Position do end describe "#to_json" do - let(:hash) do - { - old_path: "files/ruby/popen.rb", - new_path: "files/ruby/popen.rb", - old_line: nil, - new_line: 14, - base_sha: nil, - head_sha: nil, - start_sha: nil - } + shared_examples "diff position json" do + it "returns the position as JSON" do + expect(JSON.parse(diff_position.to_json)).to eq(hash.stringify_keys) + end + + it "works when nested under another hash" do + expect(JSON.parse(JSON.generate(pos: diff_position))).to eq('pos' => hash.stringify_keys) + end end - let(:diff_position) { described_class.new(hash) } + context "for text positon" do + let(:hash) do + { + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 14, + base_sha: nil, + head_sha: nil, + start_sha: nil, + position_type: "text" + } + end + + let(:diff_position) { described_class.new(hash) } - it "returns the position as JSON" do - expect(JSON.parse(diff_position.to_json)).to eq(hash.stringify_keys) + it_behaves_like "diff position json" end - it "works when nested under another hash" do - expect(JSON.parse(JSON.generate(pos: diff_position))).to eq('pos' => hash.stringify_keys) + context "for image positon" do + let(:hash) do + { + old_path: "files/any.img", + new_path: "files/any.img", + base_sha: nil, + head_sha: nil, + start_sha: nil, + width: 100, + height: 100, + x: 1, + y: 100, + position_type: "image" + } + end + + let(:diff_position) { described_class.new(hash) } + + it_behaves_like "diff position json" end end end diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb index 4fa30d8df8b..e5138705443 100644 --- a/spec/lib/gitlab/diff/position_tracer_spec.rb +++ b/spec/lib/gitlab/diff/position_tracer_spec.rb @@ -71,6 +71,10 @@ describe Gitlab::Diff::PositionTracer do Gitlab::Diff::DiffRefs.new(base_sha: base_commit.id, head_sha: head_commit.id) end + def text_position_attrs + [:old_line, :new_line] + end + def position(attrs = {}) attrs.reverse_merge!( diff_refs: old_diff_refs @@ -91,7 +95,11 @@ describe Gitlab::Diff::PositionTracer do expect(new_position.diff_refs).to eq(new_diff_refs) attrs.each do |attr, value| - expect(new_position.send(attr)).to eq(value) + if text_position_attrs.include?(attr) + expect(new_position.formatter.send(attr)).to eq(value) + else + expect(new_position.send(attr)).to eq(value) + end end end end @@ -110,7 +118,11 @@ describe Gitlab::Diff::PositionTracer do expect(change_position.diff_refs).to eq(change_diff_refs) attrs.each do |attr, value| - expect(change_position.send(attr)).to eq(value) + if text_position_attrs.include?(attr) + expect(change_position.formatter.send(attr)).to eq(value) + else + expect(change_position.send(attr)).to eq(value) + end end end end diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb index 465c2012b05..793228701cf 100644 --- a/spec/lib/gitlab/git/blame_spec.rb +++ b/spec/lib/gitlab/git/blame_spec.rb @@ -73,7 +73,7 @@ describe Gitlab::Git::Blame, seed_helper: true do it_behaves_like 'blaming a file' end - context 'when Gitaly blame feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly blame feature is disabled', :skip_gitaly_mock do it_behaves_like 'blaming a file' end end diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index f3945e748ab..412a0093d97 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -112,7 +112,7 @@ describe Gitlab::Git::Blob, seed_helper: true do it_behaves_like 'finding blobs' end - context 'when project_raw_show Gitaly feature is disabled', skip_gitaly_mock: true do + context 'when project_raw_show Gitaly feature is disabled', :skip_gitaly_mock do it_behaves_like 'finding blobs' end end @@ -150,7 +150,7 @@ describe Gitlab::Git::Blob, seed_helper: true do it_behaves_like 'finding blobs by ID' end - context 'when the blob_raw Gitaly feature is disabled', skip_gitaly_mock: true do + context 'when the blob_raw Gitaly feature is disabled', :skip_gitaly_mock do it_behaves_like 'finding blobs by ID' end end diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 3815055139a..9f4e3c49adc 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -261,7 +261,7 @@ describe Gitlab::Git::Commit, seed_helper: true do it_should_behave_like '.where' end - describe '.where without gitaly', skip_gitaly_mock: true do + describe '.where without gitaly', :skip_gitaly_mock do it_should_behave_like '.where' end @@ -336,7 +336,7 @@ describe Gitlab::Git::Commit, seed_helper: true do it_behaves_like 'finding all commits' end - context 'when Gitaly find_all_commits feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly find_all_commits feature is disabled', :skip_gitaly_mock do it_behaves_like 'finding all commits' context 'while applying a sort order based on the `order` option' do @@ -405,7 +405,7 @@ describe Gitlab::Git::Commit, seed_helper: true do it_should_behave_like '#stats' end - describe '#stats with gitaly disabled', skip_gitaly_mock: true do + describe '#stats with gitaly disabled', :skip_gitaly_mock do it_should_behave_like '#stats' end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 5f12125beb2..1ee4acfd193 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -54,7 +54,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe "#rugged" do - describe 'when storage is broken', broken_storage: true do + describe 'when storage is broken', :broken_storage do it 'raises a storage exception when storage is not available' do broken_repo = described_class.new('broken', 'a/path.git', '') @@ -384,7 +384,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - context 'when Gitaly commit_count feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly commit_count feature is disabled', :skip_gitaly_mock do it_behaves_like 'simple commit counting' end end @@ -418,7 +418,7 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like 'check for local branches' end - context 'without gitaly', skip_gitaly_mock: true do + context 'without gitaly', :skip_gitaly_mock do it_behaves_like 'check for local branches' end end @@ -453,7 +453,7 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like "deleting a branch" end - context "when Gitaly delete_branch is disabled", skip_gitaly_mock: true do + context "when Gitaly delete_branch is disabled", :skip_gitaly_mock do it_behaves_like "deleting a branch" end end @@ -489,7 +489,7 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like 'creating a branch' end - context 'when Gitaly create_branch feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly create_branch feature is disabled', :skip_gitaly_mock do it_behaves_like 'creating a branch' end end @@ -929,7 +929,7 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like 'extended commit counting' end - context 'when Gitaly count_commits feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly count_commits feature is disabled', :skip_gitaly_mock do it_behaves_like 'extended commit counting' end end @@ -996,7 +996,7 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like 'finding a branch' end - context 'when Gitaly find_branch feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly find_branch feature is disabled', :skip_gitaly_mock do it_behaves_like 'finding a branch' it 'should reload Rugged::Repository and return master' do @@ -1238,7 +1238,7 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like 'checks the existence of refs' end - context 'when Gitaly ref_exists feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly ref_exists feature is disabled', :skip_gitaly_mock do it_behaves_like 'checks the existence of refs' end end @@ -1260,7 +1260,7 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like 'checks the existence of tags' end - context 'when Gitaly ref_exists_tags feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly ref_exists_tags feature is disabled', :skip_gitaly_mock do it_behaves_like 'checks the existence of tags' end end @@ -1284,7 +1284,7 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like 'checks the existence of branches' end - context 'when Gitaly ref_exists_branches feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly ref_exists_branches feature is disabled', :skip_gitaly_mock do it_behaves_like 'checks the existence of branches' end end @@ -1361,7 +1361,7 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like 'languages' - context 'with rugged', skip_gitaly_mock: true do + context 'with rugged', :skip_gitaly_mock do it_behaves_like 'languages' end end @@ -1467,7 +1467,7 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like "user deleting a branch" end - context "when Gitaly user_delete_branch is disabled", skip_gitaly_mock: true do + context "when Gitaly user_delete_branch is disabled", :skip_gitaly_mock do it_behaves_like "user deleting a branch" end end diff --git a/spec/lib/gitlab/git/tag_spec.rb b/spec/lib/gitlab/git/tag_spec.rb index cc10679ef1e..6c4f538bf01 100644 --- a/spec/lib/gitlab/git/tag_spec.rb +++ b/spec/lib/gitlab/git/tag_spec.rb @@ -29,7 +29,7 @@ describe Gitlab::Git::Tag, seed_helper: true do it_behaves_like 'Gitlab::Git::Repository#tags' end - context 'when Gitaly tags feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly tags feature is disabled', :skip_gitaly_mock do it_behaves_like 'Gitlab::Git::Repository#tags' end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 458627ee4de..c9643c5da47 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -165,7 +165,7 @@ describe Gitlab::GitAccess do stub_application_setting(rsa_key_restriction: 4096) end - it 'does not allow keys which are too small', aggregate_failures: true do + it 'does not allow keys which are too small', :aggregate_failures do expect(actor).not_to be_valid expect { pull_access_check }.to raise_unauthorized('Your SSH key must be at least 4096 bits.') expect { push_access_check }.to raise_unauthorized('Your SSH key must be at least 4096 bits.') @@ -177,7 +177,7 @@ describe Gitlab::GitAccess do stub_application_setting(rsa_key_restriction: ApplicationSetting::FORBIDDEN_KEY_VALUE) end - it 'does not allow keys which are too small', aggregate_failures: true do + it 'does not allow keys which are too small', :aggregate_failures do expect(actor).not_to be_valid expect { pull_access_check }.to raise_unauthorized(/Your SSH key type is forbidden/) expect { push_access_check }.to raise_unauthorized(/Your SSH key type is forbidden/) @@ -598,6 +598,19 @@ describe Gitlab::GitAccess do admin: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false })) end end + + context "when in a read-only GitLab instance" do + before do + create(:protected_branch, name: 'feature', project: project) + allow(Gitlab::Database).to receive(:read_only?) { true } + end + + # Only check admin; if an admin can't do it, other roles can't either + matrix = permissions_matrix[:admin].dup + matrix.each { |key, _| matrix[key] = false } + + run_permission_checks(admin: matrix) + end end describe 'build authentication abilities' do @@ -632,6 +645,16 @@ describe Gitlab::GitAccess do end end + context 'when the repository is read only' do + let(:project) { create(:project, :repository, :read_only) } + + it 'denies push access' do + project.add_master(user) + + expect { push_access_check }.to raise_unauthorized('The repository is temporarily read-only. Please try again later.') + end + end + describe 'deploy key permissions' do let(:key) { create(:deploy_key, user: user, can_push: can_push) } let(:actor) { key } diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index 0376b4ee783..1056074264a 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -4,6 +4,7 @@ describe Gitlab::GitAccessWiki do let(:access) { described_class.new(user, project, 'web', authentication_abilities: authentication_abilities, redirected_path: redirected_path) } let(:project) { create(:project, :repository) } let(:user) { create(:user) } + let(:changes) { ['6f6d7e7ed 570e7b2ab refs/heads/master'] } let(:redirected_path) { nil } let(:authentication_abilities) do [ @@ -13,19 +14,27 @@ describe Gitlab::GitAccessWiki do ] end - describe 'push_allowed?' do - before do - create(:protected_branch, name: 'master', project: project) - project.team << [user, :developer] - end + describe '#push_access_check' do + context 'when user can :create_wiki' do + before do + create(:protected_branch, name: 'master', project: project) + project.team << [user, :developer] + end - subject { access.check('git-receive-pack', changes) } + subject { access.check('git-receive-pack', changes) } - it { expect { subject }.not_to raise_error } - end + it { expect { subject }.not_to raise_error } + + context 'when in a read-only GitLab instance' do + before do + allow(Gitlab::Database).to receive(:read_only?) { true } + end - def changes - ['6f6d7e7ed 570e7b2ab refs/heads/master'] + it 'does not give access to upload wiki code' do + expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "You can't push code to a read-only GitLab instance.") + end + end + end end describe '#access_check_download!' do diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb index 6f59750b4da..8127b4842b7 100644 --- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb @@ -84,14 +84,14 @@ describe Gitlab::GitalyClient::RefService do end end - describe '#find_ref_name', seed_helper: true do + describe '#find_ref_name', :seed_helper do subject { client.find_ref_name(SeedRepo::Commit::ID, 'refs/heads/master') } it { is_expected.to be_utf8 } it { is_expected.to eq('refs/heads/master') } end - describe '#ref_exists?', seed_helper: true do + describe '#ref_exists?', :seed_helper do it 'finds the master branch ref' do expect(client.ref_exists?('refs/heads/master')).to eq(true) end diff --git a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb index 73dd236a5c6..4c1ca4349ea 100644 --- a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb +++ b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb @@ -44,7 +44,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do describe '#readiness' do subject { described_class.readiness } - context 'storage has a tripped circuitbreaker', broken_storage: true do + context 'storage has a tripped circuitbreaker', :broken_storage do let(:repository_storages) { ['broken'] } let(:storages_paths) do Gitlab.config.repositories.storages diff --git a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb new file mode 100644 index 00000000000..30da56bec16 --- /dev/null +++ b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb @@ -0,0 +1,105 @@ +require 'spec_helper' + +describe Gitlab::HookData::IssuableBuilder do + set(:user) { create(:user) } + + # This shared example requires a `builder` and `user` variable + shared_examples 'issuable hook data' do |kind| + let(:data) { builder.build(user: user) } + + include_examples 'project hook data' do + let(:project) { builder.issuable.project } + end + include_examples 'deprecated repository hook data' + + context "with a #{kind}" do + it 'contains issuable data' do + expect(data[:object_kind]).to eq(kind) + expect(data[:user]).to eq(user.hook_attrs) + expect(data[:project]).to eq(builder.issuable.project.hook_attrs) + expect(data[:object_attributes]).to eq(builder.issuable.hook_attrs) + expect(data[:changes]).to eq({}) + expect(data[:repository]).to eq(builder.issuable.project.hook_attrs.slice(:name, :url, :description, :homepage)) + end + + it 'does not contain certain keys' do + expect(data).not_to have_key(:assignees) + expect(data).not_to have_key(:assignee) + end + + describe 'changes are given' do + let(:changes) do + { + cached_markdown_version: %w[foo bar], + description: ['A description', 'A cool description'], + description_html: %w[foo bar], + in_progress_merge_commit_sha: %w[foo bar], + lock_version: %w[foo bar], + merge_jid: %w[foo bar], + title: ['A title', 'Hello World'], + title_html: %w[foo bar], + labels: [ + [{ id: 1, title: 'foo' }], + [{ id: 1, title: 'foo' }, { id: 2, title: 'bar' }] + ] + } + end + let(:data) { builder.build(user: user, changes: changes) } + + it 'populates the :changes hash' do + expect(data[:changes]).to match(hash_including({ + title: { previous: 'A title', current: 'Hello World' }, + description: { previous: 'A description', current: 'A cool description' }, + labels: { + previous: [{ id: 1, title: 'foo' }], + current: [{ id: 1, title: 'foo' }, { id: 2, title: 'bar' }] + } + })) + end + + it 'does not contain certain keys' do + expect(data[:changes]).not_to have_key('cached_markdown_version') + expect(data[:changes]).not_to have_key('description_html') + expect(data[:changes]).not_to have_key('lock_version') + expect(data[:changes]).not_to have_key('title_html') + expect(data[:changes]).not_to have_key('in_progress_merge_commit_sha') + expect(data[:changes]).not_to have_key('merge_jid') + end + end + end + end + + describe '#build' do + it_behaves_like 'issuable hook data', 'issue' do + let(:issuable) { create(:issue, description: 'A description') } + let(:builder) { described_class.new(issuable) } + end + + it_behaves_like 'issuable hook data', 'merge_request' do + let(:issuable) { create(:merge_request, description: 'A description') } + let(:builder) { described_class.new(issuable) } + end + + context 'issue is assigned' do + let(:issue) { create(:issue, assignees: [user]) } + let(:data) { described_class.new(issue).build(user: user) } + + it 'returns correct hook data' do + expect(data[:object_attributes]['assignee_id']).to eq(user.id) + expect(data[:assignees].first).to eq(user.hook_attrs) + expect(data).not_to have_key(:assignee) + end + end + + context 'merge_request is assigned' do + let(:merge_request) { create(:merge_request, assignee: user) } + let(:data) { described_class.new(merge_request).build(user: user) } + + it 'returns correct hook data' do + expect(data[:object_attributes]['assignee_id']).to eq(user.id) + expect(data[:assignee]).to eq(user.hook_attrs) + expect(data).not_to have_key(:assignees) + end + end + end +end diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb new file mode 100644 index 00000000000..6c529cdd051 --- /dev/null +++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe Gitlab::HookData::IssueBuilder do + set(:issue) { create(:issue) } + let(:builder) { described_class.new(issue) } + + describe '#build' do + let(:data) { builder.build } + + it 'includes safe attribute' do + %w[ + assignee_id + author_id + branch_name + closed_at + confidential + created_at + deleted_at + description + due_date + id + iid + last_edited_at + last_edited_by_id + milestone_id + moved_to_id + project_id + relative_position + state + time_estimate + title + updated_at + updated_by_id + ].each do |key| + expect(data).to include(key) + end + end + + it 'includes additional attrs' do + expect(data).to include(:total_time_spent) + expect(data).to include(:human_time_estimate) + expect(data).to include(:human_total_time_spent) + expect(data).to include(:assignee_ids) + end + end +end diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb new file mode 100644 index 00000000000..92bf87bbad4 --- /dev/null +++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper' + +describe Gitlab::HookData::MergeRequestBuilder do + set(:merge_request) { create(:merge_request) } + let(:builder) { described_class.new(merge_request) } + + describe '#build' do + let(:data) { builder.build } + + it 'includes safe attribute' do + %w[ + assignee_id + author_id + created_at + deleted_at + description + head_pipeline_id + id + iid + last_edited_at + last_edited_by_id + merge_commit_sha + merge_error + merge_params + merge_status + merge_user_id + merge_when_pipeline_succeeds + milestone_id + ref_fetched + source_branch + source_project_id + state + target_branch + target_project_id + time_estimate + title + updated_at + updated_by_id + ].each do |key| + expect(data).to include(key) + end + end + + %i[source target].each do |key| + describe "#{key} key" do + include_examples 'project hook data', project_key: key do + let(:project) { merge_request.public_send("#{key}_project") } + end + end + end + + it 'includes additional attrs' do + expect(data).to include(:source) + expect(data).to include(:target) + expect(data).to include(:last_commit) + expect(data).to include(:work_in_progress) + expect(data).to include(:total_time_spent) + expect(data).to include(:human_time_estimate) + expect(data).to include(:human_total_time_spent) + end + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index ec425fd2803..29baa70d5ae 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -271,6 +271,10 @@ project: - container_repositories - uploads - members_and_requesters +- build_trace_section_names +- root_of_fork_network +- fork_network_member +- fork_network award_emoji: - awardable - user diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb index c7fbc2bc92f..dd0ce0dae41 100644 --- a/spec/lib/gitlab/import_export/fork_spec.rb +++ b/spec/lib/gitlab/import_export/fork_spec.rb @@ -1,13 +1,15 @@ require 'spec_helper' describe 'forked project import' do + include ProjectForksHelper + let(:user) { create(:user) } let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } let!(:project) { create(:project, name: 'test-repo-restorer-no-repo', path: 'test-repo-restorer-no-repo') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } let(:forked_from_project) { create(:project, :repository) } - let(:fork_link) { create(:forked_project_link, forked_from_project: project_with_repo) } + let(:forked_project) { fork_project(project_with_repo, nil, repository: true) } let(:repo_saver) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) } let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) } @@ -16,7 +18,7 @@ describe 'forked project import' do end let!(:merge_request) do - create(:merge_request, source_project: fork_link.forked_to_project, target_project: project_with_repo) + create(:merge_request, source_project: forked_project, target_project: project_with_repo) end let(:saver) do diff --git a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb index 4d87f27ce05..473ba40fae7 100644 --- a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb +++ b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb @@ -1,13 +1,14 @@ require 'spec_helper' describe Gitlab::ImportExport::MergeRequestParser do + include ProjectForksHelper + let(:user) { create(:user) } let!(:project) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } - let(:forked_from_project) { create(:project, :repository) } - let(:fork_link) { create(:forked_project_link, forked_from_project: project) } + let(:forked_project) { fork_project(project) } let!(:merge_request) do - create(:merge_request, source_project: fork_link.forked_to_project, target_project: project) + create(:merge_request, source_project: forked_project, target_project: project) end let(:parsed_merge_request) do diff --git a/spec/lib/gitlab/ldap/auth_hash_spec.rb b/spec/lib/gitlab/ldap/auth_hash_spec.rb index 8370adf9211..1785094af10 100644 --- a/spec/lib/gitlab/ldap/auth_hash_spec.rb +++ b/spec/lib/gitlab/ldap/auth_hash_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::LDAP::AuthHash do let(:auth_hash) do described_class.new( OmniAuth::AuthHash.new( - uid: '123456', + uid: given_uid, provider: 'ldapmain', info: info, extra: { @@ -32,6 +32,8 @@ describe Gitlab::LDAP::AuthHash do end context "without overridden attributes" do + let(:given_uid) { 'uid=John Smith,ou=People,dc=example,dc=com' } + it "has the correct username" do expect(auth_hash.username).to eq("123456") end @@ -42,6 +44,8 @@ describe Gitlab::LDAP::AuthHash do end context "with overridden attributes" do + let(:given_uid) { 'uid=John Smith,ou=People,dc=example,dc=com' } + let(:attributes) do { 'username' => %w(mail email), @@ -61,4 +65,22 @@ describe Gitlab::LDAP::AuthHash do expect(auth_hash.name).to eq("John Smith") end end + + describe '#uid' do + context 'when there is extraneous (but valid) whitespace' do + let(:given_uid) { 'uid =john smith , ou = people, dc= example,dc =com' } + + it 'removes the extraneous whitespace' do + expect(auth_hash.uid).to eq('uid=john smith,ou=people,dc=example,dc=com') + end + end + + context 'when there are upper case characters' do + let(:given_uid) { 'UID=John Smith,ou=People,dc=example,dc=com' } + + it 'downcases' do + expect(auth_hash.uid).to eq('uid=john smith,ou=people,dc=example,dc=com') + end + end + end end diff --git a/spec/lib/gitlab/ldap/dn_spec.rb b/spec/lib/gitlab/ldap/dn_spec.rb new file mode 100644 index 00000000000..8e21ecdf9ab --- /dev/null +++ b/spec/lib/gitlab/ldap/dn_spec.rb @@ -0,0 +1,224 @@ +require 'spec_helper' + +describe Gitlab::LDAP::DN do + using RSpec::Parameterized::TableSyntax + + describe '#normalize_value' do + subject { described_class.normalize_value(given) } + + it_behaves_like 'normalizes a DN attribute value' + + context 'when the given DN is malformed' do + context 'when ending with a comma' do + let(:given) { 'John Smith,' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'when given a BER encoded attribute value with a space in it' do + let(:given) { '#aa aa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"") + end + end + + context 'when given a BER encoded attribute value with a non-hex character in it' do + let(:given) { '#aaXaaa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"") + end + end + + context 'when given a BER encoded attribute value with a non-hex character in it' do + let(:given) { '#aaaYaa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"") + end + end + + context 'when given a hex pair with a non-hex character in it, inside double quotes' do + let(:given) { '"Sebasti\\cX\\a1n"' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair inside a double quoted value, but got \"X\"") + end + end + + context 'with an open (as opposed to closed) double quote' do + let(:given) { '"James' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'with an invalid escaped hex code' do + let(:given) { 'J\ames' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Invalid escaped hex code "\am"') + end + end + + context 'with a value ending with the escape character' do + let(:given) { 'foo\\' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + end + end + + describe '#to_normalized_s' do + subject { described_class.new(given).to_normalized_s } + + it_behaves_like 'normalizes a DN' + + context 'when we do not support the given DN format' do + context 'multivalued RDNs' do + context 'without extraneous whitespace' do + let(:given) { 'uid=john smith+telephonenumber=+1 555-555-5555,ou=people,dc=example,dc=com' } + + it 'raises UnsupportedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::UnsupportedError) + end + end + + context 'with extraneous whitespace' do + context 'around the phone number plus sign' do + let(:given) { 'uid = John Smith + telephoneNumber = + 1 555-555-5555 , ou = People,dc=example,dc=com' } + + it 'raises UnsupportedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::UnsupportedError) + end + end + + context 'not around the phone number plus sign' do + let(:given) { 'uid = John Smith + telephoneNumber = +1 555-555-5555 , ou = People,dc=example,dc=com' } + + it 'raises UnsupportedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::UnsupportedError) + end + end + end + end + end + + context 'when the given DN is malformed' do + context 'when ending with a comma' do + let(:given) { 'uid=John Smith,' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'when given a BER encoded attribute value with a space in it' do + let(:given) { '0.9.2342.19200300.100.1.25=#aa aa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"") + end + end + + context 'when given a BER encoded attribute value with a non-hex character in it' do + let(:given) { '0.9.2342.19200300.100.1.25=#aaXaaa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"") + end + end + + context 'when given a BER encoded attribute value with a non-hex character in it' do + let(:given) { '0.9.2342.19200300.100.1.25=#aaaYaa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"") + end + end + + context 'when given a hex pair with a non-hex character in it, inside double quotes' do + let(:given) { 'uid="Sebasti\\cX\\a1n"' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair inside a double quoted value, but got \"X\"") + end + end + + context 'without a name value pair' do + let(:given) { 'John' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'with an open (as opposed to closed) double quote' do + let(:given) { 'cn="James' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'with an invalid escaped hex code' do + let(:given) { 'cn=J\ames' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Invalid escaped hex code "\am"') + end + end + + context 'with a value ending with the escape character' do + let(:given) { 'cn=\\' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'with an invalid OID attribute type name' do + let(:given) { '1.2.d=Value' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized RDN OID attribute type name character "d"') + end + end + + context 'with a period in a non-OID attribute type name' do + let(:given) { 'd1.2=Value' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized RDN attribute type name character "."') + end + end + + context 'when starting with non-space, non-alphanumeric character' do + let(:given) { ' -uid=John Smith' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized first character of an RDN attribute type name "-"') + end + end + + context 'when given a UID with an escaped equal sign' do + let(:given) { 'uid\\=john' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized RDN attribute type name character "\\"') + end + end + end + end + + def assert_generic_test(test_description, got, expected) + test_failure_message = "Failed test description: '#{test_description}'\n\n expected: \"#{expected}\"\n got: \"#{got}\"" + expect(got).to eq(expected), test_failure_message + end +end diff --git a/spec/lib/gitlab/ldap/person_spec.rb b/spec/lib/gitlab/ldap/person_spec.rb index 087c4d8c92c..d204050ef66 100644 --- a/spec/lib/gitlab/ldap/person_spec.rb +++ b/spec/lib/gitlab/ldap/person_spec.rb @@ -16,6 +16,34 @@ describe Gitlab::LDAP::Person do ) end + describe '.normalize_dn' do + subject { described_class.normalize_dn(given) } + + it_behaves_like 'normalizes a DN' + + context 'with an exception during normalization' do + let(:given) { 'John "Smith,' } # just something that will cause an exception + + it 'returns the given DN unmodified' do + expect(subject).to eq(given) + end + end + end + + describe '.normalize_uid' do + subject { described_class.normalize_uid(given) } + + it_behaves_like 'normalizes a DN attribute value' + + context 'with an exception during normalization' do + let(:given) { 'John "Smith,' } # just something that will cause an exception + + it 'returns the given UID unmodified' do + expect(subject).to eq(given) + end + end + end + describe '#name' do it 'uses the configured name attribute and handles values as an array' do name = 'John Doe' @@ -43,4 +71,9 @@ describe Gitlab::LDAP::Person do expect(person.email).to eq([user_principal_name]) end end + + def assert_generic_test(test_description, got, expected) + test_failure_message = "Failed test description: '#{test_description}'\n\n expected: #{expected}\n got: #{got}" + expect(got).to eq(expected), test_failure_message + end end diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb index 6a6e465cea2..9a4705d1cee 100644 --- a/spec/lib/gitlab/ldap/user_spec.rb +++ b/spec/lib/gitlab/ldap/user_spec.rb @@ -11,7 +11,7 @@ describe Gitlab::LDAP::User do } end let(:auth_hash) do - OmniAuth::AuthHash.new(uid: 'my-uid', provider: 'ldapmain', info: info) + OmniAuth::AuthHash.new(uid: 'uid=John Smith,ou=People,dc=example,dc=com', provider: 'ldapmain', info: info) end let(:ldap_user_upper_case) { described_class.new(auth_hash_upper_case) } let(:info_upper_case) do @@ -22,12 +22,12 @@ describe Gitlab::LDAP::User do } end let(:auth_hash_upper_case) do - OmniAuth::AuthHash.new(uid: 'my-uid', provider: 'ldapmain', info: info_upper_case) + OmniAuth::AuthHash.new(uid: 'uid=John Smith,ou=People,dc=example,dc=com', provider: 'ldapmain', info: info_upper_case) end describe '#changed?' do it "marks existing ldap user as changed" do - create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain') + create(:omniauth_user, extern_uid: 'uid=John Smith,ou=People,dc=example,dc=com', provider: 'ldapmain') expect(ldap_user.changed?).to be_truthy end @@ -37,7 +37,7 @@ describe Gitlab::LDAP::User do end it "does not mark existing ldap user as changed" do - create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain') + create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', provider: 'ldapmain') ldap_user.gl_user.user_synced_attributes_metadata(provider: 'ldapmain', email: true) expect(ldap_user.changed?).to be_falsey end @@ -60,7 +60,7 @@ describe Gitlab::LDAP::User do describe 'find or create' do it "finds the user if already existing" do - create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain') + create(:omniauth_user, extern_uid: 'uid=John Smith,ou=People,dc=example,dc=com', provider: 'ldapmain') expect { ldap_user.save }.not_to change { User.count } end @@ -70,7 +70,7 @@ describe Gitlab::LDAP::User do expect { ldap_user.save }.not_to change { User.count } existing_user.reload - expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid' + expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com' expect(existing_user.ldap_identity.provider).to eql 'ldapmain' end @@ -79,7 +79,7 @@ describe Gitlab::LDAP::User do expect { ldap_user.save }.not_to change { User.count } existing_user.reload - expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid' + expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com' expect(existing_user.ldap_identity.provider).to eql 'ldapmain' expect(existing_user.id).to eql ldap_user.gl_user.id end @@ -89,7 +89,7 @@ describe Gitlab::LDAP::User do expect { ldap_user_upper_case.save }.not_to change { User.count } existing_user.reload - expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid' + expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com' expect(existing_user.ldap_identity.provider).to eql 'ldapmain' expect(existing_user.id).to eql ldap_user.gl_user.id end diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb new file mode 100644 index 00000000000..742a792a1af --- /dev/null +++ b/spec/lib/gitlab/middleware/read_only_spec.rb @@ -0,0 +1,142 @@ +require 'spec_helper' + +describe Gitlab::Middleware::ReadOnly do + include Rack::Test::Methods + + RSpec::Matchers.define :be_a_redirect do + match do |response| + response.status == 301 + end + end + + RSpec::Matchers.define :disallow_request do + match do |middleware| + flash = middleware.send(:rack_flash) + flash['alert'] && flash['alert'].include?('You cannot do writing operations') + end + end + + RSpec::Matchers.define :disallow_request_in_json do + match do |response| + json_response = JSON.parse(response.body) + response.body.include?('You cannot do writing operations') && json_response.key?('message') + end + end + + let(:rack_stack) do + rack = Rack::Builder.new do + use ActionDispatch::Session::CacheStore + use ActionDispatch::Flash + use ActionDispatch::ParamsParser + end + + rack.run(subject) + rack.to_app + end + + subject { described_class.new(fake_app) } + + let(:request) { Rack::MockRequest.new(rack_stack) } + + context 'normal requests to a read-only Gitlab instance' do + let(:fake_app) { lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ['OK']] } } + + before do + allow(Gitlab::Database).to receive(:read_only?) { true } + end + + it 'expects PATCH requests to be disallowed' do + response = request.patch('/test_request') + + expect(response).to be_a_redirect + expect(subject).to disallow_request + end + + it 'expects PUT requests to be disallowed' do + response = request.put('/test_request') + + expect(response).to be_a_redirect + expect(subject).to disallow_request + end + + it 'expects POST requests to be disallowed' do + response = request.post('/test_request') + + expect(response).to be_a_redirect + expect(subject).to disallow_request + end + + it 'expects a internal POST request to be allowed after a disallowed request' do + response = request.post('/test_request') + + expect(response).to be_a_redirect + + response = request.post("/api/#{API::API.version}/internal") + + expect(response).not_to be_a_redirect + end + + it 'expects DELETE requests to be disallowed' do + response = request.delete('/test_request') + + expect(response).to be_a_redirect + expect(subject).to disallow_request + end + + context 'whitelisted requests' do + it 'expects DELETE request to logout to be allowed' do + response = request.delete('/users/sign_out') + + expect(response).not_to be_a_redirect + expect(subject).not_to disallow_request + end + + it 'expects a POST internal request to be allowed' do + response = request.post("/api/#{API::API.version}/internal") + + expect(response).not_to be_a_redirect + expect(subject).not_to disallow_request + end + + it 'expects a POST LFS request to batch URL to be allowed' do + response = request.post('/root/rouge.git/info/lfs/objects/batch') + + expect(response).not_to be_a_redirect + expect(subject).not_to disallow_request + end + end + end + + context 'json requests to a read-only GitLab instance' do + let(:fake_app) { lambda { |env| [200, { 'Content-Type' => 'application/json' }, ['OK']] } } + let(:content_json) { { 'CONTENT_TYPE' => 'application/json' } } + + before do + allow(Gitlab::Database).to receive(:read_only?) { true } + end + + it 'expects PATCH requests to be disallowed' do + response = request.patch('/test_request', content_json) + + expect(response).to disallow_request_in_json + end + + it 'expects PUT requests to be disallowed' do + response = request.put('/test_request', content_json) + + expect(response).to disallow_request_in_json + end + + it 'expects POST requests to be disallowed' do + response = request.post('/test_request', content_json) + + expect(response).to disallow_request_in_json + end + + it 'expects DELETE requests to be disallowed' do + response = request.delete('/test_request', content_json) + + expect(response).to disallow_request_in_json + end + end +end diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index 4c5efbde69a..e44a7c23452 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::SearchResults do + include ProjectForksHelper + let(:user) { create(:user) } let!(:project) { create(:project, name: 'foo') } let!(:issue) { create(:issue, project: project, title: 'foo') } @@ -42,7 +44,7 @@ describe Gitlab::SearchResults do end it 'includes merge requests from source and target projects' do - forked_project = create(:project, forked_from_project: project) + forked_project = fork_project(project, user) merge_request_2 = create(:merge_request, target_project: project, source_project: forked_project, title: 'foo') results = described_class.new(user, Project.where(id: forked_project.id), 'foo') diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index 139afa22d01..2158b2837e2 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -156,7 +156,7 @@ describe Gitlab::Shell do it_behaves_like '#add_repository' end - context 'without gitaly', skip_gitaly_mock: true do + context 'without gitaly', :skip_gitaly_mock do it_behaves_like '#add_repository' end end @@ -333,7 +333,7 @@ describe Gitlab::Shell do end end - describe '#fetch_remote local', skip_gitaly_mock: true do + describe '#fetch_remote local', :skip_gitaly_mock do it_should_behave_like 'fetch_remote', false end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 4dffe2bd82f..9230d58012f 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -41,7 +41,7 @@ describe Gitlab::Workhorse do end end - context 'when Gitaly workhorse_archive feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly workhorse_archive feature is disabled', :skip_gitaly_mock do it 'sets the header correctly' do key, command, params = decode_workhorse_header(subject) @@ -383,7 +383,7 @@ describe Gitlab::Workhorse do end end - context 'when Gitaly workhorse_raw_show feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly workhorse_raw_show feature is disabled', :skip_gitaly_mock do it 'sets the header correctly' do key, command, params = decode_workhorse_header(subject) |