summaryrefslogtreecommitdiff
path: root/spec/lib
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib')
-rw-r--r--spec/lib/banzai/renderer_spec.rb9
-rw-r--r--spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb117
-rw-r--r--spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb32
-rw-r--r--spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb36
-rw-r--r--spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb93
-rw-r--r--spec/lib/gitlab/checks/force_push_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/stage/seed_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/trace/section_parser_spec.rb87
-rw-r--r--spec/lib/gitlab/ci/trace_spec.rb87
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb4
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/formatters/image_formatter_spec.rb20
-rw-r--r--spec/lib/gitlab/diff/formatters/text_formatter_spec.rb42
-rw-r--r--spec/lib/gitlab/diff/position_spec.rb85
-rw-r--r--spec/lib/gitlab/diff/position_tracer_spec.rb16
-rw-r--r--spec/lib/gitlab/git/blame_spec.rb2
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb4
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb6
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb24
-rw-r--r--spec/lib/gitlab/git/tag_spec.rb2
-rw-r--r--spec/lib/gitlab/git_access_spec.rb27
-rw-r--r--spec/lib/gitlab/git_access_wiki_spec.rb29
-rw-r--r--spec/lib/gitlab/git_ref_validator_spec.rb5
-rw-r--r--spec/lib/gitlab/gitaly_client/ref_service_spec.rb4
-rw-r--r--spec/lib/gitlab/gpg/commit_spec.rb39
-rw-r--r--spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb49
-rw-r--r--spec/lib/gitlab/gpg_spec.rb17
-rw-r--r--spec/lib/gitlab/health_checks/fs_shards_check_spec.rb2
-rw-r--r--spec/lib/gitlab/hook_data/issuable_builder_spec.rb105
-rw-r--r--spec/lib/gitlab/hook_data/issue_builder_spec.rb46
-rw-r--r--spec/lib/gitlab/hook_data/merge_request_builder_spec.rb62
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml9
-rw-r--r--spec/lib/gitlab/import_export/fork_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/merge_request_parser_spec.rb7
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml29
-rw-r--r--spec/lib/gitlab/ldap/auth_hash_spec.rb24
-rw-r--r--spec/lib/gitlab/ldap/dn_spec.rb224
-rw-r--r--spec/lib/gitlab/ldap/person_spec.rb33
-rw-r--r--spec/lib/gitlab/ldap/user_spec.rb16
-rw-r--r--spec/lib/gitlab/middleware/read_only_spec.rb142
-rw-r--r--spec/lib/gitlab/search_results_spec.rb4
-rw-r--r--spec/lib/gitlab/shell_spec.rb56
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb1
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb70
-rw-r--r--spec/lib/google_api/auth_spec.rb41
-rw-r--r--spec/lib/google_api/cloud_platform/client_spec.rb128
-rw-r--r--spec/lib/rspec_flaky/config_spec.rb102
-rw-r--r--spec/lib/rspec_flaky/flaky_example_spec.rb129
-rw-r--r--spec/lib/rspec_flaky/flaky_examples_collection_spec.rb79
-rw-r--r--spec/lib/rspec_flaky/listener_spec.rb173
-rw-r--r--spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb8
54 files changed, 2162 insertions, 188 deletions
diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb
index da42272bbef..81a04a2d46d 100644
--- a/spec/lib/banzai/renderer_spec.rb
+++ b/spec/lib/banzai/renderer_spec.rb
@@ -31,7 +31,14 @@ describe Banzai::Renderer do
let(:object) { fake_object(fresh: false) }
it 'caches and returns the result' do
- expect(object).to receive(:refresh_markdown_cache!).with(do_update: true)
+ expect(object).to receive(:refresh_markdown_cache!)
+
+ is_expected.to eq('field_html')
+ end
+
+ it "skips database caching on a GitLab read-only instance" do
+ allow(Gitlab::Database).to receive(:read_only?).and_return(true)
+ expect(object).to receive(:refresh_markdown_cache!)
is_expected.to eq('field_html')
end
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/create_gpg_key_subkeys_from_gpg_keys_spec.rb b/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb
new file mode 100644
index 00000000000..26d48cc8201
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::CreateGpgKeySubkeysFromGpgKeys, :migration, schema: 20171005130944 do
+ context 'when GpgKey exists' do
+ let!(:gpg_key) { create(:gpg_key, key: GpgHelpers::User3.public_key) }
+
+ before do
+ GpgKeySubkey.destroy_all
+ end
+
+ it 'generate the subkeys' do
+ expect do
+ described_class.new.perform(gpg_key.id)
+ end.to change { gpg_key.subkeys.count }.from(0).to(2)
+ end
+
+ it 'schedules the signature update worker' do
+ expect(InvalidGpgSignatureUpdateWorker).to receive(:perform_async).with(gpg_key.id)
+
+ described_class.new.perform(gpg_key.id)
+ end
+ end
+
+ context 'when GpgKey does not exist' do
+ it 'does not do anything' do
+ expect(Gitlab::Gpg).not_to receive(:subkeys_from_key)
+ expect(InvalidGpgSignatureUpdateWorker).not_to receive(:perform_async)
+
+ described_class.new.perform(123)
+ 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/pipeline/chain/validate/config_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
index 3740df88f42..8357af38f92 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
@@ -55,6 +55,10 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Config do
it 'fails the pipeline' do
expect(pipeline.reload).to be_failed
end
+
+ it 'sets a config error failure reason' do
+ expect(pipeline.reload.config_error?).to eq true
+ end
end
context 'when saving incomplete pipeline is not allowed' do
diff --git a/spec/lib/gitlab/ci/stage/seed_spec.rb b/spec/lib/gitlab/ci/stage/seed_spec.rb
index 9ecd128faca..3fe8d50c49a 100644
--- a/spec/lib/gitlab/ci/stage/seed_spec.rb
+++ b/spec/lib/gitlab/ci/stage/seed_spec.rb
@@ -11,6 +11,12 @@ describe Gitlab::Ci::Stage::Seed do
described_class.new(pipeline, 'test', builds)
end
+ describe '#size' do
+ it 'returns a number of jobs in the stage' do
+ expect(subject.size).to eq 2
+ end
+ end
+
describe '#stage' do
it 'returns hash attributes of a stage' do
expect(subject.stage).to be_a Hash
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/git_ref_validator_spec.rb b/spec/lib/gitlab/git_ref_validator_spec.rb
index e1fa8ae03f8..ba7fb168a3b 100644
--- a/spec/lib/gitlab/git_ref_validator_spec.rb
+++ b/spec/lib/gitlab/git_ref_validator_spec.rb
@@ -4,6 +4,7 @@ describe Gitlab::GitRefValidator do
it { expect(described_class.validate('feature/new')).to be_truthy }
it { expect(described_class.validate('implement_@all')).to be_truthy }
it { expect(described_class.validate('my_new_feature')).to be_truthy }
+ it { expect(described_class.validate('my-branch')).to be_truthy }
it { expect(described_class.validate('#1')).to be_truthy }
it { expect(described_class.validate('feature/refs/heads/foo')).to be_truthy }
it { expect(described_class.validate('feature/~new/')).to be_falsey }
@@ -22,4 +23,8 @@ describe Gitlab::GitRefValidator do
it { expect(described_class.validate('refs/remotes/')).to be_falsey }
it { expect(described_class.validate('refs/heads/feature')).to be_falsey }
it { expect(described_class.validate('refs/remotes/origin')).to be_falsey }
+ it { expect(described_class.validate('-')).to be_falsey }
+ it { expect(described_class.validate('-branch')).to be_falsey }
+ it { expect(described_class.validate('.tag')).to be_falsey }
+ it { expect(described_class.validate('my branch')).to be_falsey }
end
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/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb
index b07462e4978..a6c99bc07d4 100644
--- a/spec/lib/gitlab/gpg/commit_spec.rb
+++ b/spec/lib/gitlab/gpg/commit_spec.rb
@@ -63,6 +63,45 @@ describe Gitlab::Gpg::Commit do
it_behaves_like 'returns the cached signature on second call'
end
+ context 'commit signed with a subkey' do
+ let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User3.emails.first }
+
+ let!(:user) { create(:user, email: GpgHelpers::User3.emails.first) }
+
+ let!(:gpg_key) do
+ create :gpg_key, key: GpgHelpers::User3.public_key, user: user
+ end
+
+ let(:gpg_key_subkey) do
+ gpg_key.subkeys.find_by(fingerprint: '0522DD29B98F167CD8421752E38FFCAF75ABD92A')
+ end
+
+ before do
+ allow(Rugged::Commit).to receive(:extract_signature)
+ .with(Rugged::Repository, commit_sha)
+ .and_return(
+ [
+ GpgHelpers::User3.signed_commit_signature,
+ GpgHelpers::User3.signed_commit_base_data
+ ]
+ )
+ end
+
+ it 'returns a valid signature' do
+ expect(described_class.new(commit).signature).to have_attributes(
+ commit_sha: commit_sha,
+ project: project,
+ gpg_key: gpg_key_subkey,
+ gpg_key_primary_keyid: gpg_key_subkey.keyid,
+ gpg_key_user_name: GpgHelpers::User3.names.first,
+ gpg_key_user_email: GpgHelpers::User3.emails.first,
+ verification_status: 'verified'
+ )
+ end
+
+ it_behaves_like 'returns the cached signature on second call'
+ end
+
context 'user email does not match the committer email, but is the same user' do
let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User2.emails.first }
diff --git a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
index b9fd4d02156..d6000af0ecd 100644
--- a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
+++ b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
@@ -2,17 +2,16 @@ require 'rails_helper'
RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
describe '#run' do
- let!(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
- let!(:project) { create :project, :repository, path: 'sample-project' }
+ let(:signature) { [GpgHelpers::User1.signed_commit_signature, GpgHelpers::User1.signed_commit_base_data] }
+ let(:committer_email) { GpgHelpers::User1.emails.first }
+ let!(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
+ let!(:project) { create :project, :repository, path: 'sample-project' }
let!(:raw_commit) do
raw_commit = double(
:raw_commit,
- signature: [
- GpgHelpers::User1.signed_commit_signature,
- GpgHelpers::User1.signed_commit_base_data
- ],
+ signature: signature,
sha: commit_sha,
- committer_email: GpgHelpers::User1.emails.first
+ committer_email: committer_email
)
allow(raw_commit).to receive :save!
@@ -29,12 +28,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
allow(Rugged::Commit).to receive(:extract_signature)
.with(Rugged::Repository, commit_sha)
- .and_return(
- [
- GpgHelpers::User1.signed_commit_signature,
- GpgHelpers::User1.signed_commit_base_data
- ]
- )
+ .and_return(signature)
end
context 'gpg signature did have an associated gpg key which was removed later' do
@@ -183,5 +177,34 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
)
end
end
+
+ context 'gpg signature did not have an associated gpg subkey' do
+ let(:signature) { [GpgHelpers::User3.signed_commit_signature, GpgHelpers::User3.signed_commit_base_data] }
+ let(:committer_email) { GpgHelpers::User3.emails.first }
+ let!(:user) { create :user, email: GpgHelpers::User3.emails.first }
+
+ let!(:invalid_gpg_signature) do
+ create :gpg_signature,
+ project: project,
+ commit_sha: commit_sha,
+ gpg_key: nil,
+ gpg_key_primary_keyid: GpgHelpers::User3.subkey_fingerprints.last[24..-1],
+ verification_status: 'unknown_key'
+ end
+
+ it 'updates the signature to being valid when the missing gpg key is added' do
+ # InvalidGpgSignatureUpdater is called by the after_create hook
+ gpg_key = create(:gpg_key, key: GpgHelpers::User3.public_key, user: user)
+ subkey = gpg_key.subkeys.last
+
+ expect(invalid_gpg_signature.reload).to have_attributes(
+ project: project,
+ commit_sha: commit_sha,
+ gpg_key_subkey_id: subkey.id,
+ gpg_key_primary_keyid: subkey.keyid,
+ verification_status: 'verified'
+ )
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb
index 11a2aea1915..ab9a166db00 100644
--- a/spec/lib/gitlab/gpg_spec.rb
+++ b/spec/lib/gitlab/gpg_spec.rb
@@ -28,6 +28,23 @@ describe Gitlab::Gpg do
end
end
+ describe '.subkeys_from_key' do
+ it 'returns the subkeys by primary key' do
+ all_subkeys = described_class.subkeys_from_key(GpgHelpers::User1.public_key)
+ subkeys = all_subkeys[GpgHelpers::User1.primary_keyid]
+
+ expect(subkeys).to be_present
+ expect(subkeys.first[:keyid]).to be_present
+ expect(subkeys.first[:fingerprint]).to be_present
+ end
+
+ it 'returns an empty array when there are not subkeys' do
+ all_subkeys = described_class.subkeys_from_key(GpgHelpers::User4.public_key)
+
+ expect(all_subkeys[GpgHelpers::User4.primary_keyid]).to be_empty
+ end
+ end
+
describe '.user_infos_from_key' do
it 'returns the names and emails' do
user_infos = described_class.user_infos_from_key(GpgHelpers::User1.public_key)
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 3fb8edeb701..29baa70d5ae 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -147,6 +147,10 @@ deploy_keys:
- user
- deploy_keys_projects
- projects
+cluster:
+- project
+- user
+- service
services:
- project
- service_hook
@@ -177,6 +181,7 @@ project:
- tag_taggings
- tags
- chat_services
+- cluster
- creator
- group
- namespace
@@ -266,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/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 7268226112c..80d92b2e6a3 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -25,6 +25,7 @@ Issue:
- relative_position
- last_edited_at
- last_edited_by_id
+- discussion_locked
Event:
- id
- target_type
@@ -168,6 +169,7 @@ MergeRequest:
- last_edited_at
- last_edited_by_id
- head_pipeline_id
+- discussion_locked
MergeRequestDiff:
- id
- state
@@ -224,6 +226,7 @@ Ci::Pipeline:
- auto_canceled_by_id
- pipeline_schedule_id
- config_source
+- failure_reason
- protected
Ci::Stage:
- id
@@ -310,6 +313,32 @@ Ci::PipelineSchedule:
- deleted_at
- created_at
- updated_at
+Gcp::Cluster:
+- id
+- project_id
+- user_id
+- service_id
+- enabled
+- status
+- status_reason
+- project_namespace
+- endpoint
+- ca_cert
+- encrypted_kubernetes_token
+- encrypted_kubernetes_token_iv
+- username
+- encrypted_password
+- encrypted_password_iv
+- gcp_project_id
+- gcp_cluster_zone
+- gcp_cluster_name
+- gcp_cluster_size
+- gcp_machine_type
+- gcp_operation_id
+- encrypted_gcp_token
+- encrypted_gcp_token_iv
+- created_at
+- updated_at
DeployKey:
- id
- user_id
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 9efdd7940ca..2158b2837e2 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -15,10 +15,6 @@ describe Gitlab::Shell do
it { is_expected.to respond_to :add_repository }
it { is_expected.to respond_to :remove_repository }
it { is_expected.to respond_to :fork_repository }
- it { is_expected.to respond_to :add_namespace }
- it { is_expected.to respond_to :rm_namespace }
- it { is_expected.to respond_to :mv_namespace }
- it { is_expected.to respond_to :exists? }
it { expect(gitlab_shell.url_to_repo('diaspora')).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git") }
@@ -160,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
@@ -337,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
@@ -363,4 +359,52 @@ describe Gitlab::Shell do
end
end
end
+
+ describe 'namespace actions' do
+ subject { described_class.new }
+ let(:storage_path) { Gitlab.config.repositories.storages.default.path }
+
+ describe '#add_namespace' do
+ it 'creates a namespace' do
+ subject.add_namespace(storage_path, "mepmep")
+
+ expect(subject.exists?(storage_path, "mepmep")).to be(true)
+ end
+ end
+
+ describe '#exists?' do
+ context 'when the namespace does not exist' do
+ it 'returns false' do
+ expect(subject.exists?(storage_path, "non-existing")).to be(false)
+ end
+ end
+
+ context 'when the namespace exists' do
+ it 'returns true' do
+ subject.add_namespace(storage_path, "mepmep")
+
+ expect(subject.exists?(storage_path, "mepmep")).to be(true)
+ end
+ end
+ end
+
+ describe '#remove' do
+ it 'removes the namespace' do
+ subject.add_namespace(storage_path, "mepmep")
+ subject.rm_namespace(storage_path, "mepmep")
+
+ expect(subject.exists?(storage_path, "mepmep")).to be(false)
+ end
+ end
+
+ describe '#mv_namespace' do
+ it 'renames the namespace' do
+ subject.add_namespace(storage_path, "mepmep")
+ subject.mv_namespace(storage_path, "mepmep", "2mep")
+
+ expect(subject.exists?(storage_path, "mepmep")).to be(false)
+ expect(subject.exists?(storage_path, "2mep")).to be(true)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index ee152872acc..777e9c8e21d 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -60,6 +60,7 @@ describe Gitlab::UsageData do
deploy_keys
deployments
environments
+ gcp_clusters
in_review_folder
groups
issues
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 4dffe2bd82f..80bf7986ee0 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)
@@ -66,12 +66,34 @@ describe Gitlab::Workhorse do
let(:diff_refs) { double(base_sha: "base", head_sha: "head") }
subject { described_class.send_git_patch(repository, diff_refs) }
- it 'sets the header correctly' do
- key, command, params = decode_workhorse_header(subject)
+ context 'when Gitaly workhorse_send_git_patch feature is enabled' do
+ it 'sets the header correctly' do
+ key, command, params = decode_workhorse_header(subject)
+
+ expect(key).to eq("Gitlab-Workhorse-Send-Data")
+ expect(command).to eq("git-format-patch")
+ expect(params).to eq({
+ 'GitalyServer' => {
+ address: Gitlab::GitalyClient.address(project.repository_storage),
+ token: Gitlab::GitalyClient.token(project.repository_storage)
+ },
+ 'RawPatchRequest' => Gitaly::RawPatchRequest.new(
+ repository: repository.gitaly_repository,
+ left_commit_id: 'base',
+ right_commit_id: 'head'
+ ).to_json
+ }.deep_stringify_keys)
+ end
+ end
+
+ context 'when Gitaly workhorse_send_git_patch feature is disabled', :skip_gitaly_mock do
+ it 'sets the header correctly' do
+ key, command, params = decode_workhorse_header(subject)
- expect(key).to eq("Gitlab-Workhorse-Send-Data")
- expect(command).to eq("git-format-patch")
- expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head")
+ expect(key).to eq("Gitlab-Workhorse-Send-Data")
+ expect(command).to eq("git-format-patch")
+ expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head")
+ end
end
end
@@ -115,14 +137,36 @@ describe Gitlab::Workhorse do
describe '.send_git_diff' do
let(:diff_refs) { double(base_sha: "base", head_sha: "head") }
- subject { described_class.send_git_patch(repository, diff_refs) }
+ subject { described_class.send_git_diff(repository, diff_refs) }
- it 'sets the header correctly' do
- key, command, params = decode_workhorse_header(subject)
+ context 'when Gitaly workhorse_send_git_diff feature is enabled' do
+ it 'sets the header correctly' do
+ key, command, params = decode_workhorse_header(subject)
- expect(key).to eq("Gitlab-Workhorse-Send-Data")
- expect(command).to eq("git-format-patch")
- expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head")
+ expect(key).to eq("Gitlab-Workhorse-Send-Data")
+ expect(command).to eq("git-diff")
+ expect(params).to eq({
+ 'GitalyServer' => {
+ address: Gitlab::GitalyClient.address(project.repository_storage),
+ token: Gitlab::GitalyClient.token(project.repository_storage)
+ },
+ 'RawDiffRequest' => Gitaly::RawDiffRequest.new(
+ repository: repository.gitaly_repository,
+ left_commit_id: 'base',
+ right_commit_id: 'head'
+ ).to_json
+ }.deep_stringify_keys)
+ end
+ end
+
+ context 'when Gitaly workhorse_send_git_diff feature is disabled', :skip_gitaly_mock do
+ it 'sets the header correctly' do
+ key, command, params = decode_workhorse_header(subject)
+
+ expect(key).to eq("Gitlab-Workhorse-Send-Data")
+ expect(command).to eq("git-diff")
+ expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head")
+ end
end
end
@@ -383,7 +427,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)
diff --git a/spec/lib/google_api/auth_spec.rb b/spec/lib/google_api/auth_spec.rb
new file mode 100644
index 00000000000..87a3f43274f
--- /dev/null
+++ b/spec/lib/google_api/auth_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe GoogleApi::Auth do
+ let(:redirect_uri) { 'http://localhost:3000/google_api/authorizations/callback' }
+ let(:redirect_to) { 'http://localhost:3000/namaspace/project/clusters' }
+
+ let(:client) do
+ GoogleApi::CloudPlatform::Client
+ .new(nil, redirect_uri, state: redirect_to)
+ end
+
+ describe '#authorize_url' do
+ subject { client.authorize_url }
+
+ it 'returns authorize_url' do
+ is_expected.to start_with('https://accounts.google.com/o/oauth2')
+ is_expected.to include(URI.encode(redirect_uri, URI::PATTERN::RESERVED))
+ is_expected.to include(URI.encode(redirect_to, URI::PATTERN::RESERVED))
+ end
+ end
+
+ describe '#get_token' do
+ let(:token) do
+ double.tap do |dbl|
+ allow(dbl).to receive(:token).and_return('token')
+ allow(dbl).to receive(:expires_at).and_return('expires_at')
+ end
+ end
+
+ before do
+ allow_any_instance_of(OAuth2::Strategy::AuthCode)
+ .to receive(:get_token).and_return(token)
+ end
+
+ it 'returns token and expires_at' do
+ token, expires_at = client.get_token('xxx')
+ expect(token).to eq('token')
+ expect(expires_at).to eq('expires_at')
+ end
+ end
+end
diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb
new file mode 100644
index 00000000000..acc5bd1da35
--- /dev/null
+++ b/spec/lib/google_api/cloud_platform/client_spec.rb
@@ -0,0 +1,128 @@
+require 'spec_helper'
+
+describe GoogleApi::CloudPlatform::Client do
+ let(:token) { 'token' }
+ let(:client) { described_class.new(token, nil) }
+
+ describe '.session_key_for_redirect_uri' do
+ let(:state) { 'random_string' }
+
+ subject { described_class.session_key_for_redirect_uri(state) }
+
+ it 'creates a new session key' do
+ is_expected.to eq('cloud_platform_second_redirect_uri_random_string')
+ end
+ end
+
+ describe '.new_session_key_for_redirect_uri' do
+ it 'generates a new session key' do
+ expect { |b| described_class.new_session_key_for_redirect_uri(&b) }
+ .to yield_with_args(String)
+ end
+ end
+
+ describe '#validate_token' do
+ subject { client.validate_token(expires_at) }
+
+ let(:expires_at) { 1.hour.since.utc.strftime('%s') }
+
+ context 'when token is nil' do
+ let(:token) { nil }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when expires_at is nil' do
+ let(:expires_at) { nil }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when expires in 1 hour' do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when expires in 10 minutes' do
+ let(:expires_at) { 5.minutes.since.utc.strftime('%s') }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '#projects_zones_clusters_get' do
+ subject { client.projects_zones_clusters_get(spy, spy, spy) }
+ let(:gke_cluster) { double }
+
+ before do
+ allow_any_instance_of(Google::Apis::ContainerV1::ContainerService)
+ .to receive(:get_zone_cluster).and_return(gke_cluster)
+ end
+
+ it { is_expected.to eq(gke_cluster) }
+ end
+
+ describe '#projects_zones_clusters_create' do
+ subject do
+ client.projects_zones_clusters_create(
+ spy, spy, cluster_name, cluster_size, machine_type: machine_type)
+ end
+
+ let(:cluster_name) { 'test-cluster' }
+ let(:cluster_size) { 1 }
+ let(:machine_type) { 'n1-standard-4' }
+ let(:operation) { double }
+
+ before do
+ allow_any_instance_of(Google::Apis::ContainerV1::ContainerService)
+ .to receive(:create_cluster).and_return(operation)
+ end
+
+ it { is_expected.to eq(operation) }
+
+ it 'sets corresponded parameters' do
+ expect_any_instance_of(Google::Apis::ContainerV1::CreateClusterRequest)
+ .to receive(:initialize).with(
+ {
+ "cluster": {
+ "name": cluster_name,
+ "initial_node_count": cluster_size,
+ "node_config": {
+ "machine_type": machine_type
+ }
+ }
+ } )
+
+ subject
+ end
+ end
+
+ describe '#projects_zones_operations' do
+ subject { client.projects_zones_operations(spy, spy, spy) }
+ let(:operation) { double }
+
+ before do
+ allow_any_instance_of(Google::Apis::ContainerV1::ContainerService)
+ .to receive(:get_zone_operation).and_return(operation)
+ end
+
+ it { is_expected.to eq(operation) }
+ end
+
+ describe '#parse_operation_id' do
+ subject { client.parse_operation_id(self_link) }
+
+ context 'when expected url' do
+ let(:self_link) do
+ 'projects/gcp-project-12345/zones/us-central1-a/operations/ope-123'
+ end
+
+ it { is_expected.to eq('ope-123') }
+ end
+
+ context 'when unexpected url' do
+ let(:self_link) { '???' }
+
+ it { is_expected.to be_nil }
+ end
+ end
+end
diff --git a/spec/lib/rspec_flaky/config_spec.rb b/spec/lib/rspec_flaky/config_spec.rb
new file mode 100644
index 00000000000..83556787e85
--- /dev/null
+++ b/spec/lib/rspec_flaky/config_spec.rb
@@ -0,0 +1,102 @@
+require 'spec_helper'
+
+describe RspecFlaky::Config, :aggregate_failures do
+ before do
+ # Stub these env variables otherwise specs don't behave the same on the CI
+ stub_env('FLAKY_RSPEC_GENERATE_REPORT', nil)
+ stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', nil)
+ stub_env('FLAKY_RSPEC_REPORT_PATH', nil)
+ stub_env('NEW_FLAKY_RSPEC_REPORT_PATH', nil)
+ end
+
+ describe '.generate_report?' do
+ context "when ENV['FLAKY_RSPEC_GENERATE_REPORT'] is not set" do
+ it 'returns false' do
+ expect(described_class).not_to be_generate_report
+ end
+ end
+
+ context "when ENV['FLAKY_RSPEC_GENERATE_REPORT'] is set to 'false'" do
+ before do
+ stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'false')
+ end
+
+ it 'returns false' do
+ expect(described_class).not_to be_generate_report
+ end
+ end
+
+ context "when ENV['FLAKY_RSPEC_GENERATE_REPORT'] is set to 'true'" do
+ before do
+ stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'true')
+ end
+
+ it 'returns true' do
+ expect(described_class).to be_generate_report
+ end
+ end
+ end
+
+ describe '.suite_flaky_examples_report_path' do
+ context "when ENV['SUITE_FLAKY_RSPEC_REPORT_PATH'] is not set" do
+ it 'returns the default path' do
+ expect(Rails.root).to receive(:join).with('rspec_flaky/suite-report.json')
+ .and_return('root/rspec_flaky/suite-report.json')
+
+ expect(described_class.suite_flaky_examples_report_path).to eq('root/rspec_flaky/suite-report.json')
+ end
+ end
+
+ context "when ENV['SUITE_FLAKY_RSPEC_REPORT_PATH'] is set" do
+ before do
+ stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', 'foo/suite-report.json')
+ end
+
+ it 'returns the value of the env variable' do
+ expect(described_class.suite_flaky_examples_report_path).to eq('foo/suite-report.json')
+ end
+ end
+ end
+
+ describe '.flaky_examples_report_path' do
+ context "when ENV['FLAKY_RSPEC_REPORT_PATH'] is not set" do
+ it 'returns the default path' do
+ expect(Rails.root).to receive(:join).with('rspec_flaky/report.json')
+ .and_return('root/rspec_flaky/report.json')
+
+ expect(described_class.flaky_examples_report_path).to eq('root/rspec_flaky/report.json')
+ end
+ end
+
+ context "when ENV['FLAKY_RSPEC_REPORT_PATH'] is set" do
+ before do
+ stub_env('FLAKY_RSPEC_REPORT_PATH', 'foo/report.json')
+ end
+
+ it 'returns the value of the env variable' do
+ expect(described_class.flaky_examples_report_path).to eq('foo/report.json')
+ end
+ end
+ end
+
+ describe '.new_flaky_examples_report_path' do
+ context "when ENV['NEW_FLAKY_RSPEC_REPORT_PATH'] is not set" do
+ it 'returns the default path' do
+ expect(Rails.root).to receive(:join).with('rspec_flaky/new-report.json')
+ .and_return('root/rspec_flaky/new-report.json')
+
+ expect(described_class.new_flaky_examples_report_path).to eq('root/rspec_flaky/new-report.json')
+ end
+ end
+
+ context "when ENV['NEW_FLAKY_RSPEC_REPORT_PATH'] is set" do
+ before do
+ stub_env('NEW_FLAKY_RSPEC_REPORT_PATH', 'foo/new-report.json')
+ end
+
+ it 'returns the value of the env variable' do
+ expect(described_class.new_flaky_examples_report_path).to eq('foo/new-report.json')
+ end
+ end
+ end
+end
diff --git a/spec/lib/rspec_flaky/flaky_example_spec.rb b/spec/lib/rspec_flaky/flaky_example_spec.rb
index cbfc1e538ab..d19c34bebb3 100644
--- a/spec/lib/rspec_flaky/flaky_example_spec.rb
+++ b/spec/lib/rspec_flaky/flaky_example_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe RspecFlaky::FlakyExample do
+describe RspecFlaky::FlakyExample, :aggregate_failures do
let(:flaky_example_attrs) do
{
example_id: 'spec/foo/bar_spec.rb:2',
@@ -9,6 +9,7 @@ describe RspecFlaky::FlakyExample do
description: 'hello world',
first_flaky_at: 1234,
last_flaky_at: 2345,
+ last_flaky_job: 'https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/12',
last_attempts_count: 2,
flaky_reports: 1
}
@@ -27,57 +28,78 @@ describe RspecFlaky::FlakyExample do
end
let(:example) { double(example_attrs) }
+ before do
+ # Stub these env variables otherwise specs don't behave the same on the CI
+ stub_env('CI_PROJECT_URL', nil)
+ stub_env('CI_JOB_ID', nil)
+ end
+
describe '#initialize' do
shared_examples 'a valid FlakyExample instance' do
- it 'returns valid attributes' do
- flaky_example = described_class.new(args)
+ let(:flaky_example) { described_class.new(args) }
+ it 'returns valid attributes' do
expect(flaky_example.uid).to eq(flaky_example_attrs[:uid])
- expect(flaky_example.example_id).to eq(flaky_example_attrs[:example_id])
+ expect(flaky_example.file).to eq(flaky_example_attrs[:file])
+ expect(flaky_example.line).to eq(flaky_example_attrs[:line])
+ expect(flaky_example.description).to eq(flaky_example_attrs[:description])
+ expect(flaky_example.first_flaky_at).to eq(expected_first_flaky_at)
+ expect(flaky_example.last_flaky_at).to eq(expected_last_flaky_at)
+ expect(flaky_example.last_attempts_count).to eq(flaky_example_attrs[:last_attempts_count])
+ expect(flaky_example.flaky_reports).to eq(expected_flaky_reports)
end
end
context 'when given an Rspec::Example' do
- let(:args) { example }
-
- it_behaves_like 'a valid FlakyExample instance'
+ it_behaves_like 'a valid FlakyExample instance' do
+ let(:args) { example }
+ let(:expected_first_flaky_at) { nil }
+ let(:expected_last_flaky_at) { nil }
+ let(:expected_flaky_reports) { 0 }
+ end
end
context 'when given a hash' do
- let(:args) { flaky_example_attrs }
-
- it_behaves_like 'a valid FlakyExample instance'
+ it_behaves_like 'a valid FlakyExample instance' do
+ let(:args) { flaky_example_attrs }
+ let(:expected_flaky_reports) { flaky_example_attrs[:flaky_reports] }
+ let(:expected_first_flaky_at) { flaky_example_attrs[:first_flaky_at] }
+ let(:expected_last_flaky_at) { flaky_example_attrs[:last_flaky_at] }
+ end
end
end
- describe '#to_h' do
- before do
- # Stub these env variables otherwise specs don't behave the same on the CI
- stub_env('CI_PROJECT_URL', nil)
- stub_env('CI_JOB_ID', nil)
- end
+ describe '#update_flakiness!' do
+ shared_examples 'an up-to-date FlakyExample instance' do
+ let(:flaky_example) { described_class.new(args) }
- shared_examples 'a valid FlakyExample hash' do
- let(:additional_attrs) { {} }
+ it 'updates the first_flaky_at' do
+ now = Time.now
+ expected_first_flaky_at = flaky_example.first_flaky_at ? flaky_example.first_flaky_at : now
+ Timecop.freeze(now) { flaky_example.update_flakiness! }
- it 'returns a valid hash' do
- flaky_example = described_class.new(args)
- final_hash = flaky_example_attrs
- .merge(last_flaky_at: instance_of(Time), last_flaky_job: nil)
- .merge(additional_attrs)
+ expect(flaky_example.first_flaky_at).to eq(expected_first_flaky_at)
+ end
+
+ it 'updates the last_flaky_at' do
+ now = Time.now
+ Timecop.freeze(now) { flaky_example.update_flakiness! }
- expect(flaky_example.to_h).to match(hash_including(final_hash))
+ expect(flaky_example.last_flaky_at).to eq(now)
end
- end
- context 'when given an Rspec::Example' do
- let(:args) { example }
+ it 'updates the flaky_reports' do
+ expected_flaky_reports = flaky_example.first_flaky_at ? flaky_example.flaky_reports + 1 : 1
+
+ expect { flaky_example.update_flakiness! }.to change { flaky_example.flaky_reports }.by(1)
+ expect(flaky_example.flaky_reports).to eq(expected_flaky_reports)
+ end
+
+ context 'when passed a :last_attempts_count' do
+ it 'updates the last_attempts_count' do
+ flaky_example.update_flakiness!(last_attempts_count: 42)
- context 'when run locally' do
- it_behaves_like 'a valid FlakyExample hash' do
- let(:additional_attrs) do
- { first_flaky_at: instance_of(Time) }
- end
+ expect(flaky_example.last_attempts_count).to eq(42)
end
end
@@ -87,10 +109,45 @@ describe RspecFlaky::FlakyExample do
stub_env('CI_JOB_ID', 42)
end
- it_behaves_like 'a valid FlakyExample hash' do
- let(:additional_attrs) do
- { first_flaky_at: instance_of(Time), last_flaky_job: "https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/42" }
- end
+ it 'updates the last_flaky_job' do
+ flaky_example.update_flakiness!
+
+ expect(flaky_example.last_flaky_job).to eq('https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/42')
+ end
+ end
+ end
+
+ context 'when given an Rspec::Example' do
+ it_behaves_like 'an up-to-date FlakyExample instance' do
+ let(:args) { example }
+ end
+ end
+
+ context 'when given a hash' do
+ it_behaves_like 'an up-to-date FlakyExample instance' do
+ let(:args) { flaky_example_attrs }
+ end
+ end
+ end
+
+ describe '#to_h' do
+ shared_examples 'a valid FlakyExample hash' do
+ let(:additional_attrs) { {} }
+
+ it 'returns a valid hash' do
+ flaky_example = described_class.new(args)
+ final_hash = flaky_example_attrs.merge(additional_attrs)
+
+ expect(flaky_example.to_h).to eq(final_hash)
+ end
+ end
+
+ context 'when given an Rspec::Example' do
+ let(:args) { example }
+
+ it_behaves_like 'a valid FlakyExample hash' do
+ let(:additional_attrs) do
+ { first_flaky_at: nil, last_flaky_at: nil, last_flaky_job: nil, flaky_reports: 0 }
end
end
end
diff --git a/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
new file mode 100644
index 00000000000..06a8ba0d02e
--- /dev/null
+++ b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+
+describe RspecFlaky::FlakyExamplesCollection, :aggregate_failures do
+ let(:collection_hash) do
+ {
+ a: { example_id: 'spec/foo/bar_spec.rb:2' },
+ b: { example_id: 'spec/foo/baz_spec.rb:3' }
+ }
+ end
+ let(:collection_report) do
+ {
+ a: {
+ example_id: 'spec/foo/bar_spec.rb:2',
+ first_flaky_at: nil,
+ last_flaky_at: nil,
+ last_flaky_job: nil
+ },
+ b: {
+ example_id: 'spec/foo/baz_spec.rb:3',
+ first_flaky_at: nil,
+ last_flaky_at: nil,
+ last_flaky_job: nil
+ }
+ }
+ end
+
+ describe '.from_json' do
+ it 'accepts a JSON' do
+ collection = described_class.from_json(JSON.pretty_generate(collection_hash))
+
+ expect(collection.to_report).to eq(described_class.new(collection_hash).to_report)
+ end
+ end
+
+ describe '#initialize' do
+ it 'accepts no argument' do
+ expect { described_class.new }.not_to raise_error
+ end
+
+ it 'accepts a hash' do
+ expect { described_class.new(collection_hash) }.not_to raise_error
+ end
+
+ it 'does not accept anything else' do
+ expect { described_class.new([1, 2, 3]) }.to raise_error(ArgumentError, "`collection` must be a Hash, Array given!")
+ end
+ end
+
+ describe '#to_report' do
+ it 'calls #to_h on the values' do
+ collection = described_class.new(collection_hash)
+
+ expect(collection.to_report).to eq(collection_report)
+ end
+ end
+
+ describe '#-' do
+ it 'returns only examples that are not present in the given collection' do
+ collection1 = described_class.new(collection_hash)
+ collection2 = described_class.new(
+ a: { example_id: 'spec/foo/bar_spec.rb:2' },
+ c: { example_id: 'spec/bar/baz_spec.rb:4' })
+
+ expect((collection2 - collection1).to_report).to eq(
+ c: {
+ example_id: 'spec/bar/baz_spec.rb:4',
+ first_flaky_at: nil,
+ last_flaky_at: nil,
+ last_flaky_job: nil
+ })
+ end
+
+ it 'fails if the given collection does not respond to `#key?`' do
+ collection = described_class.new(collection_hash)
+
+ expect { collection - [1, 2, 3] }.to raise_error(ArgumentError, "`other` must respond to `#key?`, Array does not!")
+ end
+ end
+end
diff --git a/spec/lib/rspec_flaky/listener_spec.rb b/spec/lib/rspec_flaky/listener_spec.rb
index 0e193bf408b..bfb7648b486 100644
--- a/spec/lib/rspec_flaky/listener_spec.rb
+++ b/spec/lib/rspec_flaky/listener_spec.rb
@@ -1,22 +1,35 @@
require 'spec_helper'
-describe RspecFlaky::Listener do
- let(:flaky_example_report) do
+describe RspecFlaky::Listener, :aggregate_failures do
+ let(:already_flaky_example_uid) { '6e869794f4cfd2badd93eb68719371d1' }
+ let(:suite_flaky_example_report) do
{
- 'abc123' => {
+ already_flaky_example_uid => {
example_id: 'spec/foo/bar_spec.rb:2',
file: 'spec/foo/bar_spec.rb',
line: 2,
description: 'hello world',
first_flaky_at: 1234,
- last_flaky_at: instance_of(Time),
- last_attempts_count: 2,
+ last_flaky_at: 4321,
+ last_attempts_count: 3,
flaky_reports: 1,
last_flaky_job: nil
}
}
end
- let(:example_attrs) do
+ let(:already_flaky_example_attrs) do
+ {
+ id: 'spec/foo/bar_spec.rb:2',
+ metadata: {
+ file_path: 'spec/foo/bar_spec.rb',
+ line_number: 2,
+ full_description: 'hello world'
+ },
+ execution_result: double(status: 'passed', exception: nil)
+ }
+ end
+ let(:already_flaky_example) { RspecFlaky::FlakyExample.new(suite_flaky_example_report[already_flaky_example_uid]) }
+ let(:new_example_attrs) do
{
id: 'spec/foo/baz_spec.rb:3',
metadata: {
@@ -32,18 +45,19 @@ describe RspecFlaky::Listener do
# Stub these env variables otherwise specs don't behave the same on the CI
stub_env('CI_PROJECT_URL', nil)
stub_env('CI_JOB_ID', nil)
+ stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', nil)
end
describe '#initialize' do
shared_examples 'a valid Listener instance' do
- let(:expected_all_flaky_examples) { {} }
+ let(:expected_suite_flaky_examples) { {} }
it 'returns a valid Listener instance' do
listener = described_class.new
- expect(listener.to_report(listener.all_flaky_examples))
- .to match(hash_including(expected_all_flaky_examples))
- expect(listener.new_flaky_examples).to eq({})
+ expect(listener.to_report(listener.suite_flaky_examples))
+ .to eq(expected_suite_flaky_examples)
+ expect(listener.flaky_examples).to eq({})
end
end
@@ -51,16 +65,16 @@ describe RspecFlaky::Listener do
it_behaves_like 'a valid Listener instance'
end
- context 'when a report file exists and set by ALL_FLAKY_RSPEC_REPORT_PATH' do
+ context 'when a report file exists and set by SUITE_FLAKY_RSPEC_REPORT_PATH' do
let(:report_file) do
Tempfile.new(%w[rspec_flaky_report .json]).tap do |f|
- f.write(JSON.pretty_generate(flaky_example_report))
+ f.write(JSON.pretty_generate(suite_flaky_example_report))
f.rewind
end
end
before do
- stub_env('ALL_FLAKY_RSPEC_REPORT_PATH', report_file.path)
+ stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', report_file.path)
end
after do
@@ -69,74 +83,122 @@ describe RspecFlaky::Listener do
end
it_behaves_like 'a valid Listener instance' do
- let(:expected_all_flaky_examples) { flaky_example_report }
+ let(:expected_suite_flaky_examples) { suite_flaky_example_report }
end
end
end
describe '#example_passed' do
- let(:rspec_example) { double(example_attrs) }
+ let(:rspec_example) { double(new_example_attrs) }
let(:notification) { double(example: rspec_example) }
+ let(:listener) { described_class.new(suite_flaky_example_report.to_json) }
shared_examples 'a non-flaky example' do
it 'does not change the flaky examples hash' do
- expect { subject.example_passed(notification) }
- .not_to change { subject.all_flaky_examples }
+ expect { listener.example_passed(notification) }
+ .not_to change { listener.flaky_examples }
end
end
- describe 'when the RSpec example does not respond to attempts' do
- it_behaves_like 'a non-flaky example'
- end
+ shared_examples 'an existing flaky example' do
+ let(:expected_flaky_example) do
+ {
+ example_id: 'spec/foo/bar_spec.rb:2',
+ file: 'spec/foo/bar_spec.rb',
+ line: 2,
+ description: 'hello world',
+ first_flaky_at: 1234,
+ last_attempts_count: 2,
+ flaky_reports: 2,
+ last_flaky_job: nil
+ }
+ end
- describe 'when the RSpec example has 1 attempt' do
- let(:rspec_example) { double(example_attrs.merge(attempts: 1)) }
+ it 'changes the flaky examples hash' do
+ new_example = RspecFlaky::Example.new(rspec_example)
- it_behaves_like 'a non-flaky example'
+ now = Time.now
+ Timecop.freeze(now) do
+ expect { listener.example_passed(notification) }
+ .to change { listener.flaky_examples[new_example.uid].to_h }
+ end
+
+ expect(listener.flaky_examples[new_example.uid].to_h)
+ .to eq(expected_flaky_example.merge(last_flaky_at: now))
+ end
end
- describe 'when the RSpec example has 2 attempts' do
- let(:rspec_example) { double(example_attrs.merge(attempts: 2)) }
- let(:expected_new_flaky_example) do
+ shared_examples 'a new flaky example' do
+ let(:expected_flaky_example) do
{
example_id: 'spec/foo/baz_spec.rb:3',
file: 'spec/foo/baz_spec.rb',
line: 3,
description: 'hello GitLab',
- first_flaky_at: instance_of(Time),
- last_flaky_at: instance_of(Time),
last_attempts_count: 2,
flaky_reports: 1,
last_flaky_job: nil
}
end
- it 'does not change the flaky examples hash' do
- expect { subject.example_passed(notification) }
- .to change { subject.all_flaky_examples }
-
+ it 'changes the all flaky examples hash' do
new_example = RspecFlaky::Example.new(rspec_example)
- expect(subject.all_flaky_examples[new_example.uid].to_h)
- .to match(hash_including(expected_new_flaky_example))
+ now = Time.now
+ Timecop.freeze(now) do
+ expect { listener.example_passed(notification) }
+ .to change { listener.flaky_examples[new_example.uid].to_h }
+ end
+
+ expect(listener.flaky_examples[new_example.uid].to_h)
+ .to eq(expected_flaky_example.merge(first_flaky_at: now, last_flaky_at: now))
+ end
+ end
+
+ describe 'when the RSpec example does not respond to attempts' do
+ it_behaves_like 'a non-flaky example'
+ end
+
+ describe 'when the RSpec example has 1 attempt' do
+ let(:rspec_example) { double(new_example_attrs.merge(attempts: 1)) }
+
+ it_behaves_like 'a non-flaky example'
+ end
+
+ describe 'when the RSpec example has 2 attempts' do
+ let(:rspec_example) { double(new_example_attrs.merge(attempts: 2)) }
+
+ it_behaves_like 'a new flaky example'
+
+ context 'with an existing flaky example' do
+ let(:rspec_example) { double(already_flaky_example_attrs.merge(attempts: 2)) }
+
+ it_behaves_like 'an existing flaky example'
end
end
end
describe '#dump_summary' do
- let(:rspec_example) { double(example_attrs) }
- let(:notification) { double(example: rspec_example) }
+ let(:listener) { described_class.new(suite_flaky_example_report.to_json) }
+ let(:new_flaky_rspec_example) { double(new_example_attrs.merge(attempts: 2)) }
+ let(:already_flaky_rspec_example) { double(already_flaky_example_attrs.merge(attempts: 2)) }
+ let(:notification_new_flaky_rspec_example) { double(example: new_flaky_rspec_example) }
+ let(:notification_already_flaky_rspec_example) { double(example: already_flaky_rspec_example) }
- context 'when a report file path is set by ALL_FLAKY_RSPEC_REPORT_PATH' do
+ context 'when a report file path is set by FLAKY_RSPEC_REPORT_PATH' do
let(:report_file_path) { Rails.root.join('tmp', 'rspec_flaky_report.json') }
+ let(:new_report_file_path) { Rails.root.join('tmp', 'rspec_flaky_new_report.json') }
before do
- stub_env('ALL_FLAKY_RSPEC_REPORT_PATH', report_file_path)
+ stub_env('FLAKY_RSPEC_REPORT_PATH', report_file_path)
+ stub_env('NEW_FLAKY_RSPEC_REPORT_PATH', new_report_file_path)
FileUtils.rm(report_file_path) if File.exist?(report_file_path)
+ FileUtils.rm(new_report_file_path) if File.exist?(new_report_file_path)
end
after do
FileUtils.rm(report_file_path) if File.exist?(report_file_path)
+ FileUtils.rm(new_report_file_path) if File.exist?(new_report_file_path)
end
context 'when FLAKY_RSPEC_GENERATE_REPORT == "false"' do
@@ -144,12 +206,13 @@ describe RspecFlaky::Listener do
stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'false')
end
- it 'does not write the report file' do
- subject.example_passed(notification)
+ it 'does not write any report file' do
+ listener.example_passed(notification_new_flaky_rspec_example)
- subject.dump_summary(nil)
+ listener.dump_summary(nil)
expect(File.exist?(report_file_path)).to be(false)
+ expect(File.exist?(new_report_file_path)).to be(false)
end
end
@@ -158,21 +221,39 @@ describe RspecFlaky::Listener do
stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'true')
end
- it 'writes the report file' do
- subject.example_passed(notification)
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'writes the report files' do
+ listener.example_passed(notification_new_flaky_rspec_example)
+ listener.example_passed(notification_already_flaky_rspec_example)
- subject.dump_summary(nil)
+ listener.dump_summary(nil)
expect(File.exist?(report_file_path)).to be(true)
+ expect(File.exist?(new_report_file_path)).to be(true)
+
+ expect(File.read(report_file_path))
+ .to eq(JSON.pretty_generate(listener.to_report(listener.flaky_examples)))
+
+ new_example = RspecFlaky::Example.new(notification_new_flaky_rspec_example)
+ new_flaky_example = RspecFlaky::FlakyExample.new(new_example)
+ new_flaky_example.update_flakiness!
+
+ expect(File.read(new_report_file_path))
+ .to eq(JSON.pretty_generate(listener.to_report(new_example.uid => new_flaky_example)))
end
end
end
end
describe '#to_report' do
+ let(:listener) { described_class.new(suite_flaky_example_report.to_json) }
+
it 'transforms the internal hash to a JSON-ready hash' do
- expect(subject.to_report('abc123' => RspecFlaky::FlakyExample.new(flaky_example_report['abc123'])))
- .to match(hash_including(flaky_example_report))
+ expect(listener.to_report(already_flaky_example_uid => already_flaky_example))
+ .to match(hash_including(suite_flaky_example_report))
end
end
end
diff --git a/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
index a0fb86345f3..b4b83b70d1c 100644
--- a/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
+++ b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
@@ -39,6 +39,14 @@ describe SystemCheck::App::GitUserDefaultSSHConfigCheck do
it { is_expected.to eq(expected_result) }
end
+
+ it 'skips GitLab read-only instances' do
+ stub_user
+ stub_home_dir
+ allow(Gitlab::Database).to receive(:read_only?).and_return(true)
+
+ is_expected.to be_truthy
+ end
end
describe '#check?' do