diff options
Diffstat (limited to 'spec/lib')
34 files changed, 1140 insertions, 556 deletions
diff --git a/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb b/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb new file mode 100644 index 00000000000..9d4921968b3 --- /dev/null +++ b/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::EncryptRunnersTokens, :migration, schema: 20181121111200 do + let(:settings) { table(:application_settings) } + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:runners) { table(:ci_runners) } + + context 'when migrating application settings' do + before do + settings.create!(id: 1, runners_registration_token: 'plain-text-token1') + end + + it 'migrates runners registration tokens' do + migrate!(:settings, 1, 1) + + encrypted_token = settings.first.runners_registration_token_encrypted + decrypted_token = ::Gitlab::CryptoHelper.aes256_gcm_decrypt(encrypted_token) + + expect(decrypted_token).to eq 'plain-text-token1' + expect(settings.first.runners_registration_token).to eq 'plain-text-token1' + end + end + + context 'when migrating namespaces' do + before do + namespaces.create!(id: 11, name: 'gitlab', path: 'gitlab-org', runners_token: 'my-token1') + namespaces.create!(id: 12, name: 'gitlab', path: 'gitlab-org', runners_token: 'my-token2') + namespaces.create!(id: 22, name: 'gitlab', path: 'gitlab-org', runners_token: 'my-token3') + end + + it 'migrates runners registration tokens' do + migrate!(:namespace, 11, 22) + + expect(namespaces.all.reload).to all( + have_attributes(runners_token: be_a(String), runners_token_encrypted: be_a(String)) + ) + end + end + + context 'when migrating projects' do + before do + namespaces.create!(id: 11, name: 'gitlab', path: 'gitlab-org') + projects.create!(id: 111, namespace_id: 11, name: 'gitlab', path: 'gitlab-ce', runners_token: 'my-token1') + projects.create!(id: 114, namespace_id: 11, name: 'gitlab', path: 'gitlab-ce', runners_token: 'my-token2') + projects.create!(id: 116, namespace_id: 11, name: 'gitlab', path: 'gitlab-ce', runners_token: 'my-token3') + end + + it 'migrates runners registration tokens' do + migrate!(:project, 111, 116) + + expect(projects.all.reload).to all( + have_attributes(runners_token: be_a(String), runners_token_encrypted: be_a(String)) + ) + end + end + + context 'when migrating runners' do + before do + runners.create!(id: 201, runner_type: 1, token: 'plain-text-token1') + runners.create!(id: 202, runner_type: 1, token: 'plain-text-token2') + runners.create!(id: 203, runner_type: 1, token: 'plain-text-token3') + end + + it 'migrates runners communication tokens' do + migrate!(:runner, 201, 203) + + expect(runners.all.reload).to all( + have_attributes(token: be_a(String), token_encrypted: be_a(String)) + ) + end + end + + def migrate!(model, from, to) + subject.perform(model, from, to) + end +end diff --git a/spec/lib/gitlab/checks/branch_check_spec.rb b/spec/lib/gitlab/checks/branch_check_spec.rb new file mode 100644 index 00000000000..77366e91dca --- /dev/null +++ b/spec/lib/gitlab/checks/branch_check_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Checks::BranchCheck do + include_context 'change access checks context' + + describe '#validate!' do + it 'does not raise any error' do + expect { subject.validate! }.not_to raise_error + end + + context 'trying to delete the default branch' do + let(:newrev) { '0000000000000000000000000000000000000000' } + let(:ref) { 'refs/heads/master' } + + it 'raises an error' do + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'The default branch of a project cannot be deleted.') + end + end + + context 'protected branches check' do + before do + allow(ProtectedBranch).to receive(:protected?).with(project, 'master').and_return(true) + allow(ProtectedBranch).to receive(:protected?).with(project, 'feature').and_return(true) + end + + it 'raises an error if the user is not allowed to do forced pushes to protected branches' do + expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true) + + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to force push code to a protected branch on this project.') + end + + it 'raises an error if the user is not allowed to merge to protected branches' do + expect_any_instance_of(Gitlab::Checks::MatchingMergeRequest).to receive(:match?).and_return(true) + expect(user_access).to receive(:can_merge_to_branch?).and_return(false) + expect(user_access).to receive(:can_push_to_branch?).and_return(false) + + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to merge code into protected branches on this project.') + end + + it 'raises an error if the user is not allowed to push to protected branches' do + expect(user_access).to receive(:can_push_to_branch?).and_return(false) + + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to protected branches on this project.') + end + + context 'when project repository is empty' do + let(:project) { create(:project) } + + it 'raises an error if the user is not allowed to push to protected branches' do + expect(user_access).to receive(:can_push_to_branch?).and_return(false) + + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /Ask a project Owner or Maintainer to create a default branch/) + end + end + + context 'branch deletion' do + let(:newrev) { '0000000000000000000000000000000000000000' } + let(:ref) { 'refs/heads/feature' } + + context 'if the user is not allowed to delete protected branches' do + it 'raises an error' do + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to delete protected branches from this project. Only a project maintainer or owner can delete a protected branch.') + end + end + + context 'if the user is allowed to delete protected branches' do + before do + project.add_maintainer(user) + end + + context 'through the web interface' do + let(:protocol) { 'web' } + + it 'allows branch deletion' do + expect { subject.validate! }.not_to raise_error + end + end + + context 'over SSH or HTTP' do + it 'raises an error' do + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only delete protected branches using the web interface.') + end + end + end + end + end + end +end diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb index 81804ba5c76..45fb33e9e4a 100644 --- a/spec/lib/gitlab/checks/change_access_spec.rb +++ b/spec/lib/gitlab/checks/change_access_spec.rb @@ -2,245 +2,56 @@ require 'spec_helper' describe Gitlab::Checks::ChangeAccess do describe '#exec' do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - let(:user_access) { Gitlab::UserAccess.new(user, project: project) } - let(:oldrev) { 'be93687618e4b132087f430a4d8fc3a609c9b77c' } - let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' } - let(:ref) { 'refs/heads/master' } - let(:changes) { { oldrev: oldrev, newrev: newrev, ref: ref } } - let(:protocol) { 'ssh' } - let(:timeout) { Gitlab::GitAccess::INTERNAL_TIMEOUT } - let(:logger) { Gitlab::Checks::TimedLogger.new(timeout: timeout) } + include_context 'change access checks context' - subject(:change_access) do - described_class.new( - changes, - project: project, - user_access: user_access, - protocol: protocol, - logger: logger - ) - end - - before do - project.add_developer(user) - end + subject { change_access } context 'without failed checks' do it "doesn't raise an error" do expect { subject.exec }.not_to raise_error end - end - context 'when time limit was reached' do - it 'raises a TimeoutError' do - logger = Gitlab::Checks::TimedLogger.new(start_time: timeout.ago, timeout: timeout) - access = described_class.new(changes, - project: project, - user_access: user_access, - protocol: protocol, - logger: logger) + it 'calls pushes checks' do + expect_any_instance_of(Gitlab::Checks::PushCheck).to receive(:validate!) - expect { access.exec }.to raise_error(Gitlab::Checks::TimedLogger::TimeoutError) + subject.exec end - end - context 'when the user is not allowed to push to the repo' do - it 'raises an error' do - expect(user_access).to receive(:can_do_action?).with(:push_code).and_return(false) - expect(user_access).to receive(:can_push_to_branch?).with('master').and_return(false) + it 'calls branches checks' do + expect_any_instance_of(Gitlab::Checks::BranchCheck).to receive(:validate!) - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to this project.') + subject.exec end - end - context 'tags check' do - let(:ref) { 'refs/tags/v1.0.0' } + it 'calls tags checks' do + expect_any_instance_of(Gitlab::Checks::TagCheck).to receive(:validate!) - it 'raises an error if the user is not allowed to update tags' do - allow(user_access).to receive(:can_do_action?).with(:push_code).and_return(true) - expect(user_access).to receive(:can_do_action?).with(:admin_project).and_return(false) - - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to change existing tags on this project.') + subject.exec end - context 'with protected tag' do - let!(:protected_tag) { create(:protected_tag, project: project, name: 'v*') } - - context 'as maintainer' do - before do - project.add_maintainer(user) - end + it 'calls lfs checks' do + expect_any_instance_of(Gitlab::Checks::LfsCheck).to receive(:validate!) - context 'deletion' do - let(:oldrev) { 'be93687618e4b132087f430a4d8fc3a609c9b77c' } - let(:newrev) { '0000000000000000000000000000000000000000' } - - it 'is prevented' do - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /cannot be deleted/) - end - end - - context 'update' do - let(:oldrev) { 'be93687618e4b132087f430a4d8fc3a609c9b77c' } - let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' } - - it 'is prevented' do - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /cannot be updated/) - end - end - end - - context 'creation' do - let(:oldrev) { '0000000000000000000000000000000000000000' } - let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' } - let(:ref) { 'refs/tags/v9.1.0' } - - it 'prevents creation below access level' do - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /allowed to create this tag as it is protected/) - end - - context 'when user has access' do - let!(:protected_tag) { create(:protected_tag, :developers_can_create, project: project, name: 'v*') } - - it 'allows tag creation' do - expect { subject.exec }.not_to raise_error - end - end - end + subject.exec end - end - context 'branches check' do - context 'trying to delete the default branch' do - let(:newrev) { '0000000000000000000000000000000000000000' } - let(:ref) { 'refs/heads/master' } + it 'calls diff checks' do + expect_any_instance_of(Gitlab::Checks::DiffCheck).to receive(:validate!) - it 'raises an error' do - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'The default branch of a project cannot be deleted.') - end - end - - context 'protected branches check' do - before do - allow(ProtectedBranch).to receive(:protected?).with(project, 'master').and_return(true) - allow(ProtectedBranch).to receive(:protected?).with(project, 'feature').and_return(true) - end - - it 'raises an error if the user is not allowed to do forced pushes to protected branches' do - expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true) - - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to force push code to a protected branch on this project.') - end - - it 'raises an error if the user is not allowed to merge to protected branches' do - expect_any_instance_of(Gitlab::Checks::MatchingMergeRequest).to receive(:match?).and_return(true) - expect(user_access).to receive(:can_merge_to_branch?).and_return(false) - expect(user_access).to receive(:can_push_to_branch?).and_return(false) - - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to merge code into protected branches on this project.') - end - - it 'raises an error if the user is not allowed to push to protected branches' do - expect(user_access).to receive(:can_push_to_branch?).and_return(false) - - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to protected branches on this project.') - end - - context 'when project repository is empty' do - let(:project) { create(:project) } - - it 'raises an error if the user is not allowed to push to protected branches' do - expect(user_access).to receive(:can_push_to_branch?).and_return(false) - - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /Ask a project Owner or Maintainer to create a default branch/) - end - end - - context 'branch deletion' do - let(:newrev) { '0000000000000000000000000000000000000000' } - let(:ref) { 'refs/heads/feature' } - - context 'if the user is not allowed to delete protected branches' do - it 'raises an error' do - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to delete protected branches from this project. Only a project maintainer or owner can delete a protected branch.') - end - end - - context 'if the user is allowed to delete protected branches' do - before do - project.add_maintainer(user) - end - - context 'through the web interface' do - let(:protocol) { 'web' } - - it 'allows branch deletion' do - expect { subject.exec }.not_to raise_error - end - end - - context 'over SSH or HTTP' do - it 'raises an error' do - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only delete protected branches using the web interface.') - end - end - end - end + subject.exec end end - context 'LFS integrity check' do - it 'fails if any LFS blobs are missing' do - allow_any_instance_of(Gitlab::Checks::LfsIntegrity).to receive(:objects_missing?).and_return(true) - - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /LFS objects are missing/) - end - - it 'succeeds if LFS objects have already been uploaded' do - allow_any_instance_of(Gitlab::Checks::LfsIntegrity).to receive(:objects_missing?).and_return(false) - - expect { subject.exec }.not_to raise_error - end - end - - context 'LFS file lock check' do - let(:owner) { create(:user) } - let!(:lock) { create(:lfs_file_lock, user: owner, project: project, path: 'README') } - - before do - allow(project.repository).to receive(:new_commits).and_return( - project.repository.commits_between('be93687618e4b132087f430a4d8fc3a609c9b77c', '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51') - ) - end - - context 'with LFS not enabled' do - it 'skips the validation' do - expect_any_instance_of(Gitlab::Checks::CommitCheck).not_to receive(:validate) - - subject.exec - end - end - - context 'with LFS enabled' do - before do - allow(project).to receive(:lfs_enabled?).and_return(true) - end - - context 'when change is sent by a different user' do - it 'raises an error if the user is not allowed to update the file' do - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "The path 'README' is locked in Git LFS by #{lock.user.name}") - end - end - - context 'when change is sent by the author of the lock' do - let(:user) { owner } + context 'when time limit was reached' do + it 'raises a TimeoutError' do + logger = Gitlab::Checks::TimedLogger.new(start_time: timeout.ago, timeout: timeout) + access = described_class.new(changes, + project: project, + user_access: user_access, + protocol: protocol, + logger: logger) - it "doesn't raise any error" do - expect { subject.exec }.not_to raise_error - end - end + expect { access.exec }.to raise_error(Gitlab::Checks::TimedLogger::TimeoutError) end end end diff --git a/spec/lib/gitlab/checks/diff_check_spec.rb b/spec/lib/gitlab/checks/diff_check_spec.rb new file mode 100644 index 00000000000..eeec1e83179 --- /dev/null +++ b/spec/lib/gitlab/checks/diff_check_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Checks::DiffCheck do + include_context 'change access checks context' + + describe '#validate!' do + let(:owner) { create(:user) } + let!(:lock) { create(:lfs_file_lock, user: owner, project: project, path: 'README') } + + before do + allow(project.repository).to receive(:new_commits).and_return( + project.repository.commits_between('be93687618e4b132087f430a4d8fc3a609c9b77c', '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51') + ) + end + + context 'with LFS not enabled' do + before do + allow(project).to receive(:lfs_enabled?).and_return(false) + end + + it 'skips the validation' do + expect(subject).not_to receive(:validate_diff) + expect(subject).not_to receive(:validate_file_paths) + + subject.validate! + end + end + + context 'with LFS enabled' do + before do + allow(project).to receive(:lfs_enabled?).and_return(true) + end + + context 'when change is sent by a different user' do + it 'raises an error if the user is not allowed to update the file' do + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "The path 'README' is locked in Git LFS by #{lock.user.name}") + end + end + + context 'when change is sent by the author of the lock' do + let(:user) { owner } + + it "doesn't raise any error" do + expect { subject.validate! }.not_to raise_error + end + end + end + end +end diff --git a/spec/lib/gitlab/checks/lfs_check_spec.rb b/spec/lib/gitlab/checks/lfs_check_spec.rb new file mode 100644 index 00000000000..35f8069c8a4 --- /dev/null +++ b/spec/lib/gitlab/checks/lfs_check_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Checks::LfsCheck do + include_context 'change access checks context' + + let(:blob_object) { project.repository.blob_at_branch('lfs', 'files/lfs/lfs_object.iso') } + + before do + allow_any_instance_of(Gitlab::Git::LfsChanges).to receive(:new_pointers) do + [blob_object] + end + end + + describe '#validate!' do + context 'with LFS not enabled' do + it 'skips integrity check' do + expect_any_instance_of(Gitlab::Git::LfsChanges).not_to receive(:new_pointers) + + subject.validate! + end + end + + context 'with LFS enabled' do + before do + allow(project).to receive(:lfs_enabled?).and_return(true) + end + + context 'deletion' do + let(:changes) { { oldrev: oldrev, ref: ref } } + + it 'skips integrity check' do + expect(project.repository).not_to receive(:new_objects) + + subject.validate! + end + end + + it 'fails if any LFS blobs are missing' do + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /LFS objects are missing/) + end + + it 'succeeds if LFS objects have already been uploaded' do + lfs_object = create(:lfs_object, oid: blob_object.lfs_oid) + create(:lfs_objects_project, project: project, lfs_object: lfs_object) + + expect { subject.validate! }.not_to raise_error + end + end + end +end diff --git a/spec/lib/gitlab/checks/push_check_spec.rb b/spec/lib/gitlab/checks/push_check_spec.rb new file mode 100644 index 00000000000..25f0d428cb9 --- /dev/null +++ b/spec/lib/gitlab/checks/push_check_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Checks::PushCheck do + include_context 'change access checks context' + + describe '#validate!' do + it 'does not raise any error' do + expect { subject.validate! }.not_to raise_error + end + + context 'when the user is not allowed to push to the repo' do + it 'raises an error' do + expect(user_access).to receive(:can_do_action?).with(:push_code).and_return(false) + expect(user_access).to receive(:can_push_to_branch?).with('master').and_return(false) + + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to this project.') + end + end + end +end diff --git a/spec/lib/gitlab/checks/tag_check_spec.rb b/spec/lib/gitlab/checks/tag_check_spec.rb new file mode 100644 index 00000000000..b1258270611 --- /dev/null +++ b/spec/lib/gitlab/checks/tag_check_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Checks::TagCheck do + include_context 'change access checks context' + + describe '#validate!' do + let(:ref) { 'refs/tags/v1.0.0' } + + it 'raises an error' do + allow(user_access).to receive(:can_do_action?).with(:push_code).and_return(true) + expect(user_access).to receive(:can_do_action?).with(:admin_project).and_return(false) + + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to change existing tags on this project.') + end + + context 'with protected tag' do + let!(:protected_tag) { create(:protected_tag, project: project, name: 'v*') } + + context 'as maintainer' do + before do + project.add_maintainer(user) + end + + context 'deletion' do + let(:oldrev) { 'be93687618e4b132087f430a4d8fc3a609c9b77c' } + let(:newrev) { '0000000000000000000000000000000000000000' } + + it 'is prevented' do + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /cannot be deleted/) + end + end + + context 'update' do + let(:oldrev) { 'be93687618e4b132087f430a4d8fc3a609c9b77c' } + let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' } + + it 'is prevented' do + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /cannot be updated/) + end + end + end + + context 'creation' do + let(:oldrev) { '0000000000000000000000000000000000000000' } + let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' } + let(:ref) { 'refs/tags/v9.1.0' } + + it 'prevents creation below access level' do + expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /allowed to create this tag as it is protected/) + end + + context 'when user has access' do + let!(:protected_tag) { create(:protected_tag, :developers_can_create, project: project, name: 'v*') } + + it 'allows tag creation' do + expect { subject.validate! }.not_to raise_error + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/except_policy_spec.rb b/spec/lib/gitlab/ci/config/entry/except_policy_spec.rb new file mode 100644 index 00000000000..d036bf2f4d1 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/except_policy_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Config::Entry::ExceptPolicy do + let(:entry) { described_class.new(config) } + + it_behaves_like 'correct only except policy' + + describe '.default' do + it 'does not have a default value' do + expect(described_class.default).to be_nil + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb index 7c18514934e..12f4b9dc624 100644 --- a/spec/lib/gitlab/ci/config/entry/global_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb @@ -160,7 +160,8 @@ describe Gitlab::Ci::Config::Entry::Global do cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' }, variables: { 'VAR' => 'value' }, ignore: false, - after_script: ['make clean'] }, + after_script: ['make clean'], + only: { refs: %w[branches tags] } }, spinach: { name: :spinach, before_script: [], script: %w[spinach], @@ -171,7 +172,8 @@ describe Gitlab::Ci::Config::Entry::Global do cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' }, variables: {}, ignore: false, - after_script: ['make clean'] } + after_script: ['make clean'], + only: { refs: %w[branches tags] } } ) end end diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index 57d4577a90c..c1f4a060063 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -258,7 +258,8 @@ describe Gitlab::Ci::Config::Entry::Job do commands: "ls\npwd\nrspec", stage: 'test', ignore: false, - after_script: %w[cleanup]) + after_script: %w[cleanup], + only: { refs: %w[branches tags] }) end end end diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb index c0a2b6517e3..2a753408f54 100644 --- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb @@ -67,12 +67,14 @@ describe Gitlab::Ci::Config::Entry::Jobs do script: %w[rspec], commands: 'rspec', ignore: false, - stage: 'test' }, + stage: 'test', + only: { refs: %w[branches tags] } }, spinach: { name: :spinach, script: %w[spinach], commands: 'spinach', ignore: false, - stage: 'test' }) + stage: 'test', + only: { refs: %w[branches tags] } }) end end diff --git a/spec/lib/gitlab/ci/config/entry/only_policy_spec.rb b/spec/lib/gitlab/ci/config/entry/only_policy_spec.rb new file mode 100644 index 00000000000..5518b68e51a --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/only_policy_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Config::Entry::OnlyPolicy do + let(:entry) { described_class.new(config) } + + it_behaves_like 'correct only except policy' + + describe '.default' do + it 'haa a default value' do + expect(described_class.default).to eq( { refs: %w[branches tags] } ) + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index 83001b7fdd8..cf40a22af2e 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -1,173 +1,8 @@ -require 'fast_spec_helper' -require_dependency 'active_model' +require 'spec_helper' describe Gitlab::Ci::Config::Entry::Policy do let(:entry) { described_class.new(config) } - context 'when using simplified policy' do - describe 'validations' do - context 'when entry config value is valid' do - context 'when config is a branch or tag name' do - let(:config) { %w[master feature/branch] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - - describe '#value' do - it 'returns refs hash' do - expect(entry.value).to eq(refs: config) - end - end - end - - context 'when config is a regexp' do - let(:config) { ['/^issue-.*$/'] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - - context 'when config is a special keyword' do - let(:config) { %w[tags triggers branches] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - end - - context 'when entry value is not valid' do - let(:config) { [1] } - - describe '#errors' do - it 'saves errors' do - expect(entry.errors) - .to include /policy config should be an array of strings or regexps/ - end - end - end - end - end - - context 'when using complex policy' do - context 'when specifying refs policy' do - let(:config) { { refs: ['master'] } } - - it 'is a correct configuraton' do - expect(entry).to be_valid - expect(entry.value).to eq(refs: %w[master]) - end - end - - context 'when specifying kubernetes policy' do - let(:config) { { kubernetes: 'active' } } - - it 'is a correct configuraton' do - expect(entry).to be_valid - expect(entry.value).to eq(kubernetes: 'active') - end - end - - context 'when specifying invalid kubernetes policy' do - let(:config) { { kubernetes: 'something' } } - - it 'reports an error about invalid policy' do - expect(entry.errors).to include /unknown value: something/ - end - end - - context 'when specifying valid variables expressions policy' do - let(:config) { { variables: ['$VAR == null'] } } - - it 'is a correct configuraton' do - expect(entry).to be_valid - expect(entry.value).to eq(config) - end - end - - context 'when specifying variables expressions in invalid format' do - let(:config) { { variables: '$MY_VAR' } } - - it 'reports an error about invalid format' do - expect(entry.errors).to include /should be an array of strings/ - end - end - - context 'when specifying invalid variables expressions statement' do - let(:config) { { variables: ['$MY_VAR =='] } } - - it 'reports an error about invalid statement' do - expect(entry.errors).to include /invalid expression syntax/ - end - end - - context 'when specifying invalid variables expressions token' do - let(:config) { { variables: ['$MY_VAR == 123'] } } - - it 'reports an error about invalid expression' do - expect(entry.errors).to include /invalid expression syntax/ - end - end - - context 'when using invalid variables expressions regexp' do - let(:config) { { variables: ['$MY_VAR =~ /some ( thing/'] } } - - it 'reports an error about invalid expression' do - expect(entry.errors).to include /invalid expression syntax/ - end - end - - context 'when specifying a valid changes policy' do - let(:config) { { changes: %w[some/* paths/**/*.rb] } } - - it 'is a correct configuraton' do - expect(entry).to be_valid - expect(entry.value).to eq(config) - end - end - - context 'when changes policy is invalid' do - let(:config) { { changes: [1, 2] } } - - it 'returns errors' do - expect(entry.errors).to include /changes should be an array of strings/ - end - end - - context 'when specifying unknown policy' do - let(:config) { { refs: ['master'], invalid: :something } } - - it 'returns error about invalid key' do - expect(entry.errors).to include /unknown keys: invalid/ - end - end - - context 'when policy is empty' do - let(:config) { {} } - - it 'is not a valid configuration' do - expect(entry.errors).to include /can't be blank/ - end - end - end - - context 'when policy strategy does not match' do - let(:config) { 'string strategy' } - - it 'returns information about errors' do - expect(entry.errors) - .to include /has to be either an array of conditions or a hash/ - end - end - describe '.default' do it 'does not have a default value' do expect(described_class.default).to be_nil diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb index 85d73e5c382..fab071405df 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb @@ -18,6 +18,7 @@ describe Gitlab::Ci::Pipeline::Chain::Build do before_sha: nil, trigger_request: nil, schedule: nil, + merge_request: nil, project: project, current_user: user, variables_attributes: variables_attributes) @@ -76,6 +77,7 @@ describe Gitlab::Ci::Pipeline::Chain::Build do before_sha: nil, trigger_request: nil, schedule: nil, + merge_request: nil, project: project, current_user: user) end @@ -90,4 +92,31 @@ describe Gitlab::Ci::Pipeline::Chain::Build do expect(pipeline).to be_tag end end + + context 'when pipeline is running for a merge request' do + let(:command) do + Gitlab::Ci::Pipeline::Chain::Command.new( + source: :merge_request, + origin_ref: 'feature', + checkout_sha: project.commit.id, + after_sha: nil, + before_sha: nil, + trigger_request: nil, + schedule: nil, + merge_request: merge_request, + project: project, + current_user: user) + end + + let(:merge_request) { build(:merge_request, target_project: project) } + + before do + step.perform! + end + + it 'correctly indicated that this is a merge request pipeline' do + expect(pipeline).to be_merge_request + expect(pipeline.merge_request).to eq(merge_request) + end + end end 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 a8dc5356413..053bc421649 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb @@ -106,4 +106,34 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Config do expect(step.break?).to be false end end + + context 'when pipeline source is merge request' do + before do + stub_ci_pipeline_yaml_file(YAML.dump(config)) + end + + let(:pipeline) { build_stubbed(:ci_pipeline, project: project) } + + let(:merge_request_pipeline) do + build(:ci_pipeline, source: :merge_request, project: project) + end + + let(:chain) { described_class.new(merge_request_pipeline, command).tap(&:perform!) } + + context "when config contains 'merge_requests' keyword" do + let(:config) { { rspec: { script: 'echo', only: ['merge_requests'] } } } + + it 'does not break the chain' do + expect(chain).not_to be_break + end + end + + context "when config contains 'merge_request' keyword" do + let(:config) { { rspec: { script: 'echo', only: ['merge_request'] } } } + + it 'does not break the chain' do + expect(chain).not_to be_break + end + end + end end diff --git a/spec/lib/gitlab/crypto_helper_spec.rb b/spec/lib/gitlab/crypto_helper_spec.rb new file mode 100644 index 00000000000..05cc6cf15de --- /dev/null +++ b/spec/lib/gitlab/crypto_helper_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe Gitlab::CryptoHelper do + describe '.sha256' do + it 'generates SHA256 digest Base46 encoded' do + digest = described_class.sha256('some-value') + + expect(digest).to match %r{\A[A-Za-z0-9+/=]+\z} + expect(digest).to eq digest.strip + end + end + + describe '.aes256_gcm_encrypt' do + it 'is Base64 encoded string without new line character' do + encrypted = described_class.aes256_gcm_encrypt('some-value') + + expect(encrypted).to match %r{\A[A-Za-z0-9+/=]+\z} + expect(encrypted).not_to include "\n" + end + end + + describe '.aes256_gcm_decrypt' do + let(:encrypted) { described_class.aes256_gcm_encrypt('some-value') } + + it 'correctly decrypts encrypted string' do + decrypted = described_class.aes256_gcm_decrypt(encrypted) + + expect(decrypted).to eq 'some-value' + end + + it 'decrypts a value when it ends with a new line character' do + decrypted = described_class.aes256_gcm_decrypt(encrypted + "\n") + + expect(decrypted).to eq 'some-value' + end + end +end diff --git a/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb b/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb index f518bb3dc3e..3991c737a26 100644 --- a/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb +++ b/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb @@ -16,6 +16,12 @@ describe Gitlab::Database::Count::ExactCountStrategy do expect(subject).to eq({ Project => 3, Identity => 1 }) end + + it 'returns default value if count times out' do + allow(models.first).to receive(:count).and_raise(ActiveRecord::StatementInvalid.new('')) + + expect(subject).to eq({}) + end end describe '.enabled?' do diff --git a/spec/lib/gitlab/diff/file_collection/commit_spec.rb b/spec/lib/gitlab/diff/file_collection/commit_spec.rb index 6d1b66deb6a..34ed22b8941 100644 --- a/spec/lib/gitlab/diff/file_collection/commit_spec.rb +++ b/spec/lib/gitlab/diff/file_collection/commit_spec.rb @@ -12,4 +12,8 @@ describe Gitlab::Diff::FileCollection::Commit do let(:diffable) { project.commit } let(:stub_path) { 'bar/branch-test.txt' } end + + it_behaves_like 'unfoldable diff' do + let(:diffable) { project.commit } + end end diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb index fbcf515281e..256166dbad3 100644 --- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb +++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb @@ -2,22 +2,29 @@ require 'spec_helper' describe Gitlab::Diff::FileCollection::MergeRequestDiff do let(:merge_request) { create(:merge_request) } - let(:diff_files) { described_class.new(merge_request.merge_request_diff, diff_options: nil).diff_files } + let(:subject) { described_class.new(merge_request.merge_request_diff, diff_options: nil) } + let(:diff_files) { subject.diff_files } - it 'does not highlight binary files' do - allow_any_instance_of(Gitlab::Diff::File).to receive(:text?).and_return(false) + describe '#diff_files' do + it 'does not highlight binary files' do + allow_any_instance_of(Gitlab::Diff::File).to receive(:text?).and_return(false) - expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines) + expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines) - diff_files - end + diff_files + end + + it 'does not highlight files marked as undiffable in .gitattributes' do + allow_any_instance_of(Gitlab::Diff::File).to receive(:diffable?).and_return(false) - it 'does not highlight files marked as undiffable in .gitattributes' do - allow_any_instance_of(Gitlab::Diff::File).to receive(:diffable?).and_return(false) + expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines) - expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines) + diff_files + end + end - diff_files + it_behaves_like 'unfoldable diff' do + let(:diffable) { merge_request.merge_request_diff } end it 'it uses a different cache key if diff line keys change' do diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb index 8c6d673391b..8229f0eb794 100644 --- a/spec/lib/gitlab/gpg/commit_spec.rb +++ b/spec/lib/gitlab/gpg/commit_spec.rb @@ -26,6 +26,28 @@ describe Gitlab::Gpg::Commit do end end + context 'invalid signature' do + let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User1.emails.first } + + let!(:user) { create(:user, email: GpgHelpers::User1.emails.first) } + + before do + allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily) + .with(Gitlab::Git::Repository, commit_sha) + .and_return( + [ + # Corrupt the key + GpgHelpers::User1.signed_commit_signature.tr('=', 'a'), + GpgHelpers::User1.signed_commit_base_data + ] + ) + end + + it 'returns nil' do + expect(described_class.new(commit).signature).to be_nil + end + end + context 'known key' do context 'user matches the key uid' do context 'user email matches the email committer' do diff --git a/spec/lib/gitlab/group_hierarchy_spec.rb b/spec/lib/gitlab/group_hierarchy_spec.rb index 30686634af4..f3de7adcec7 100644 --- a/spec/lib/gitlab/group_hierarchy_spec.rb +++ b/spec/lib/gitlab/group_hierarchy_spec.rb @@ -34,6 +34,28 @@ describe Gitlab::GroupHierarchy, :postgresql do expect { relation.update_all(share_with_group_lock: false) } .to raise_error(ActiveRecord::ReadOnlyRecord) end + + describe 'hierarchy_order option' do + let(:relation) do + described_class.new(Group.where(id: child2.id)).base_and_ancestors(hierarchy_order: hierarchy_order) + end + + context ':asc' do + let(:hierarchy_order) { :asc } + + it 'orders by child to parent' do + expect(relation).to eq([child2, child1, parent]) + end + end + + context ':desc' do + let(:hierarchy_order) { :desc } + + it 'orders by parent to child' do + expect(relation).to eq([parent, child1, child2]) + end + end + end end describe '#base_and_descendants' do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 31ab11bbf8d..7df129da95a 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -94,6 +94,7 @@ merge_requests: - timelogs - head_pipeline - latest_merge_request_diff +- merge_request_pipelines merge_request_diff: - merge_request - merge_request_diff_commits @@ -102,7 +103,7 @@ merge_request_diff_commits: - merge_request_diff merge_request_diff_files: - merge_request_diff -pipelines: +ci_pipelines: - project - user - stages @@ -121,6 +122,7 @@ pipelines: - artifacts - pipeline_schedule - merge_requests +- merge_request - deployments - environments pipeline_variables: @@ -263,7 +265,8 @@ project: - notification_settings - import_data - commit_statuses -- pipelines +- ci_pipelines +- all_pipelines - stages - builds - runner_projects @@ -304,6 +307,7 @@ project: - import_export_upload - repository_languages - pool_repository +- kubernetes_namespaces award_emoji: - awardable - user diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 7171e12a849..242c16c4bdc 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -197,9 +197,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end it 'has the correct number of pipelines and statuses' do - expect(@project.pipelines.size).to eq(5) + expect(@project.ci_pipelines.size).to eq(5) - @project.pipelines.zip([2, 2, 2, 2, 2]) + @project.ci_pipelines.zip([2, 2, 2, 2, 2]) .each do |(pipeline, expected_status_size)| expect(pipeline.statuses.size).to eq(expected_status_size) end diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index 5dc372263ad..46fdfba953b 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -119,16 +119,16 @@ describe Gitlab::ImportExport::ProjectTreeSaver do end it 'has pipeline stages' do - expect(saved_project_json.dig('pipelines', 0, 'stages')).not_to be_empty + expect(saved_project_json.dig('ci_pipelines', 0, 'stages')).not_to be_empty end it 'has pipeline statuses' do - expect(saved_project_json.dig('pipelines', 0, 'stages', 0, 'statuses')).not_to be_empty + expect(saved_project_json.dig('ci_pipelines', 0, 'stages', 0, 'statuses')).not_to be_empty end it 'has pipeline builds' do builds_count = saved_project_json - .dig('pipelines', 0, 'stages', 0, 'statuses') + .dig('ci_pipelines', 0, 'stages', 0, 'statuses') .count { |hash| hash['type'] == 'Ci::Build' } expect(builds_count).to eq(1) @@ -142,11 +142,11 @@ describe Gitlab::ImportExport::ProjectTreeSaver do end it 'has pipeline commits' do - expect(saved_project_json['pipelines']).not_to be_empty + expect(saved_project_json['ci_pipelines']).not_to be_empty end it 'has ci pipeline notes' do - expect(saved_project_json['pipelines'].first['notes']).not_to be_empty + expect(saved_project_json['ci_pipelines'].first['notes']).not_to be_empty end it 'has labels with no associations' do diff --git a/spec/lib/gitlab/import_export/relation_rename_service_spec.rb b/spec/lib/gitlab/import_export/relation_rename_service_spec.rb new file mode 100644 index 00000000000..a20a844a492 --- /dev/null +++ b/spec/lib/gitlab/import_export/relation_rename_service_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::ImportExport::RelationRenameService do + let(:renames) do + { + 'example_relation1' => 'new_example_relation1', + 'example_relation2' => 'new_example_relation2' + } + end + + let(:user) { create(:admin) } + let(:group) { create(:group, :nested) } + let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') } + let(:shared) { project.import_export_shared } + + before do + stub_const("#{described_class}::RENAMES", renames) + end + + context 'when importing' do + let(:project_tree_restorer) { Gitlab::ImportExport::ProjectTreeRestorer.new(user: user, shared: shared, project: project) } + let(:import_path) { 'spec/lib/gitlab/import_export' } + let(:file_content) { IO.read("#{import_path}/project.json") } + let!(:json_file) { ActiveSupport::JSON.decode(file_content) } + let(:tree_hash) { project_tree_restorer.instance_variable_get(:@tree_hash) } + + before do + allow(shared).to receive(:export_path).and_return(import_path) + allow(ActiveSupport::JSON).to receive(:decode).with(file_content).and_return(json_file) + end + + context 'when the file has only old relationship names' do + # Configuring the json as an old version exported file, with only + # the previous association with the old name + before do + renames.each do |old_name, _| + json_file[old_name.to_s] = [] + end + end + + it 'renames old relationships to the new name' do + expect(json_file.keys).to include(*renames.keys) + + project_tree_restorer.restore + + expect(json_file.keys).to include(*renames.values) + expect(json_file.keys).not_to include(*renames.keys) + end + end + + context 'when the file has both the old and new relationships' do + # Configuring the json as the new version exported file, with both + # the old association name and the new one + before do + renames.each do |old_name, new_name| + json_file[old_name.to_s] = [1] + json_file[new_name.to_s] = [2] + end + end + + it 'uses the new relationships and removes the old ones from the hash' do + expect(json_file.keys).to include(*renames.keys) + + project_tree_restorer.restore + + expect(json_file.keys).to include(*renames.values) + expect(json_file.values_at(*renames.values).flatten.uniq.first).to eq 2 + expect(json_file.keys).not_to include(*renames.keys) + end + end + + context 'when the file has only new relationship names' do + # Configuring the json as the future version exported file, with only + # the new association name + before do + renames.each do |_, new_name| + json_file[new_name.to_s] = [] + end + end + + it 'uses the new relationships' do + expect(json_file.keys).not_to include(*renames.keys) + + project_tree_restorer.restore + + expect(json_file.keys).to include(*renames.values) + end + end + end + + context 'when exporting' do + let(:project_tree_saver) { Gitlab::ImportExport::ProjectTreeSaver.new(project: project, current_user: user, shared: shared) } + let(:project_tree) { project_tree_saver.send(:project_json) } + + it 'adds old relationships to the exported file' do + project_tree.merge!(renames.values.map { |new_name| [new_name, []] }.to_h) + + allow(project_tree_saver).to receive(:save) do |arg| + project_tree_saver.send(:project_json_tree) + end + + result = project_tree_saver.save + + saved_data = ActiveSupport::JSON.decode(result) + + expect(saved_data.keys).to include(*(renames.keys + renames.values)) + end + end +end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index f7935149b23..d3bfde181bc 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -243,6 +243,7 @@ Ci::Pipeline: - failure_reason - protected - iid +- merge_request_id Ci::Stage: - id - name diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb index 3979a43216c..8fc85301304 100644 --- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb +++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb @@ -99,6 +99,7 @@ describe Gitlab::Kubernetes::KubeClient do :create_secret, :create_service_account, :update_config_map, + :update_secret, :update_service_account ].each do |method| describe "##{method}" do @@ -174,6 +175,84 @@ describe Gitlab::Kubernetes::KubeClient do end end + shared_examples 'create_or_update method' do + let(:get_method) { "get_#{resource_type}" } + let(:update_method) { "update_#{resource_type}" } + let(:create_method) { "create_#{resource_type}" } + + context 'resource exists' do + before do + expect(client).to receive(get_method).and_return(resource) + end + + it 'calls the update method' do + expect(client).to receive(update_method).with(resource) + + subject + end + end + + context 'resource does not exist' do + before do + expect(client).to receive(get_method).and_raise(Kubeclient::ResourceNotFoundError.new(404, 'Not found', nil)) + end + + it 'calls the create method' do + expect(client).to receive(create_method).with(resource) + + subject + end + end + end + + describe '#create_or_update_cluster_role_binding' do + let(:resource_type) { 'cluster_role_binding' } + + let(:resource) do + ::Kubeclient::Resource.new(metadata: { name: 'name', namespace: 'namespace' }) + end + + subject { client.create_or_update_cluster_role_binding(resource) } + + it_behaves_like 'create_or_update method' + end + + describe '#create_or_update_role_binding' do + let(:resource_type) { 'role_binding' } + + let(:resource) do + ::Kubeclient::Resource.new(metadata: { name: 'name', namespace: 'namespace' }) + end + + subject { client.create_or_update_role_binding(resource) } + + it_behaves_like 'create_or_update method' + end + + describe '#create_or_update_service_account' do + let(:resource_type) { 'service_account' } + + let(:resource) do + ::Kubeclient::Resource.new(metadata: { name: 'name', namespace: 'namespace' }) + end + + subject { client.create_or_update_service_account(resource) } + + it_behaves_like 'create_or_update method' + end + + describe '#create_or_update_secret' do + let(:resource_type) { 'secret' } + + let(:resource) do + ::Kubeclient::Resource.new(metadata: { name: 'name', namespace: 'namespace' }) + end + + subject { client.create_or_update_secret(resource) } + + it_behaves_like 'create_or_update method' + end + describe 'methods that do not exist on any client' do it 'throws an error' do expect { client.non_existent_method }.to raise_error(NoMethodError) diff --git a/spec/lib/gitlab/kubernetes_spec.rb b/spec/lib/gitlab/kubernetes_spec.rb index 5c03a2ce7d3..f326d57e9c6 100644 --- a/spec/lib/gitlab/kubernetes_spec.rb +++ b/spec/lib/gitlab/kubernetes_spec.rb @@ -48,26 +48,30 @@ describe Gitlab::Kubernetes do end describe '#to_kubeconfig' do + let(:token) { 'TOKEN' } + let(:ca_pem) { 'PEM' } + subject do to_kubeconfig( url: 'https://kube.domain.com', namespace: 'NAMESPACE', - token: 'TOKEN', - ca_pem: ca_pem) + token: token, + ca_pem: ca_pem + ) end - context 'when CA PEM is provided' do - let(:ca_pem) { 'PEM' } - let(:path) { expand_fixture_path('config/kubeconfig.yml') } - - it { is_expected.to eq(YAML.load_file(path)) } - end + it { expect(YAML.safe_load(subject)).to eq(YAML.load_file(expand_fixture_path('config/kubeconfig.yml'))) } context 'when CA PEM is not provided' do let(:ca_pem) { nil } - let(:path) { expand_fixture_path('config/kubeconfig-without-ca.yml') } - it { is_expected.to eq(YAML.load_file(path)) } + it { expect(YAML.safe_load(subject)).to eq(YAML.load_file(expand_fixture_path('config/kubeconfig-without-ca.yml'))) } + end + + context 'when token is not provided' do + let(:token) { nil } + + it { is_expected.to be_nil } end end diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb index 3a20dad16d0..77ee30264bf 100644 --- a/spec/lib/gitlab/lfs_token_spec.rb +++ b/spec/lib/gitlab/lfs_token_spec.rb @@ -48,4 +48,59 @@ describe Gitlab::LfsToken do end end end + + describe '#deploy_key_pushable?' do + let(:lfs_token) { described_class.new(actor) } + + context 'when actor is not a DeployKey' do + let(:actor) { create(:user) } + let(:project) { create(:project) } + + it 'returns false' do + expect(lfs_token.deploy_key_pushable?(project)).to be_falsey + end + end + + context 'when actor is a DeployKey' do + let(:deploy_keys_project) { create(:deploy_keys_project, can_push: can_push) } + let(:project) { deploy_keys_project.project } + let(:actor) { deploy_keys_project.deploy_key } + + context 'but the DeployKey cannot push to the project' do + let(:can_push) { false } + + it 'returns false' do + expect(lfs_token.deploy_key_pushable?(project)).to be_falsey + end + end + + context 'and the DeployKey can push to the project' do + let(:can_push) { true } + + it 'returns true' do + expect(lfs_token.deploy_key_pushable?(project)).to be_truthy + end + end + end + end + + describe '#type' do + let(:lfs_token) { described_class.new(actor) } + + context 'when actor is not a User' do + let(:actor) { create(:deploy_key) } + + it 'returns false' do + expect(lfs_token.type).to eq(:lfs_deploy_token) + end + end + + context 'when actor is a User' do + let(:actor) { create(:user) } + + it 'returns false' do + expect(lfs_token.type).to eq(:lfs_token) + end + end + end end diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index 4a0dc3686ec..6831274d37c 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -54,11 +54,18 @@ describe Gitlab::ProjectSearchResults do end it 'finds by name' do - expect(results.map(&:first)).to include(expected_file_by_name) + expect(results.map(&:filename)).to include(expected_file_by_name) + end + + it "loads all blobs for filename matches in single batch" do + expect(Gitlab::Git::Blob).to receive(:batch).once.and_call_original + + expected = project.repository.search_files_by_name(query, 'master') + expect(results.map(&:filename)).to include(*expected) end it 'finds by content' do - blob = results.select { |result| result.first == expected_file_by_content }.flatten.last + blob = results.select { |result| result.filename == expected_file_by_content }.flatten.last expect(blob.filename).to eq(expected_file_by_content) end @@ -122,126 +129,6 @@ describe Gitlab::ProjectSearchResults do let(:blob_type) { 'blobs' } let(:entity) { project } end - - describe 'parsing results' do - let(:results) { project.repository.search_files_by_content('feature', 'master') } - let(:search_result) { results.first } - - subject { described_class.parse_search_result(search_result) } - - it "returns a valid FoundBlob" do - is_expected.to be_an Gitlab::SearchResults::FoundBlob - expect(subject.id).to be_nil - expect(subject.path).to eq('CHANGELOG') - expect(subject.filename).to eq('CHANGELOG') - expect(subject.basename).to eq('CHANGELOG') - expect(subject.ref).to eq('master') - expect(subject.startline).to eq(188) - expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") - end - - context 'when the matching filename contains a colon' do - let(:search_result) { "master:testdata/project::function1.yaml\x001\x00---\n" } - - it 'returns a valid FoundBlob' do - expect(subject.filename).to eq('testdata/project::function1.yaml') - expect(subject.basename).to eq('testdata/project::function1') - expect(subject.ref).to eq('master') - expect(subject.startline).to eq(1) - expect(subject.data).to eq("---\n") - end - end - - context 'when the matching content contains a number surrounded by colons' do - let(:search_result) { "master:testdata/foo.txt\x001\x00blah:9:blah" } - - it 'returns a valid FoundBlob' do - expect(subject.filename).to eq('testdata/foo.txt') - expect(subject.basename).to eq('testdata/foo') - expect(subject.ref).to eq('master') - expect(subject.startline).to eq(1) - expect(subject.data).to eq('blah:9:blah') - end - end - - context 'when the matching content contains multiple null bytes' do - let(:search_result) { "master:testdata/foo.txt\x001\x00blah\x001\x00foo" } - - it 'returns a valid FoundBlob' do - expect(subject.filename).to eq('testdata/foo.txt') - expect(subject.basename).to eq('testdata/foo') - expect(subject.ref).to eq('master') - expect(subject.startline).to eq(1) - expect(subject.data).to eq("blah\x001\x00foo") - end - end - - context 'when the search result ends with an empty line' do - let(:results) { project.repository.search_files_by_content('Role models', 'master') } - - it 'returns a valid FoundBlob that ends with an empty line' do - expect(subject.filename).to eq('files/markdown/ruby-style-guide.md') - expect(subject.basename).to eq('files/markdown/ruby-style-guide') - expect(subject.ref).to eq('master') - expect(subject.startline).to eq(1) - expect(subject.data).to eq("# Prelude\n\n> Role models are important. <br/>\n> -- Officer Alex J. Murphy / RoboCop\n\n") - end - end - - context 'when the search returns non-ASCII data' do - context 'with UTF-8' do - let(:results) { project.repository.search_files_by_content('файл', 'master') } - - it 'returns results as UTF-8' do - expect(subject.filename).to eq('encoding/russian.rb') - expect(subject.basename).to eq('encoding/russian') - expect(subject.ref).to eq('master') - expect(subject.startline).to eq(1) - expect(subject.data).to eq("Хороший файл\n") - end - end - - context 'with UTF-8 in the filename' do - let(:results) { project.repository.search_files_by_content('webhook', 'master') } - - it 'returns results as UTF-8' do - expect(subject.filename).to eq('encoding/テスト.txt') - expect(subject.basename).to eq('encoding/テスト') - expect(subject.ref).to eq('master') - expect(subject.startline).to eq(3) - expect(subject.data).to include('WebHookの確認') - end - end - - context 'with ISO-8859-1' do - let(:search_result) { "master:encoding/iso8859.txt\x001\x00\xC4\xFC\nmaster:encoding/iso8859.txt\x002\x00\nmaster:encoding/iso8859.txt\x003\x00foo\n".force_encoding(Encoding::ASCII_8BIT) } - - it 'returns results as UTF-8' do - expect(subject.filename).to eq('encoding/iso8859.txt') - expect(subject.basename).to eq('encoding/iso8859') - expect(subject.ref).to eq('master') - expect(subject.startline).to eq(1) - expect(subject.data).to eq("Äü\n\nfoo\n") - end - end - end - - context "when filename has extension" do - let(:search_result) { "master:CONTRIBUTE.md\x005\x00- [Contribute to GitLab](#contribute-to-gitlab)\n" } - - it { expect(subject.path).to eq('CONTRIBUTE.md') } - it { expect(subject.filename).to eq('CONTRIBUTE.md') } - it { expect(subject.basename).to eq('CONTRIBUTE') } - end - - context "when file under directory" do - let(:search_result) { "master:a/b/c.md\x005\x00a b c\n" } - - it { expect(subject.path).to eq('a/b/c.md') } - it { expect(subject.filename).to eq('a/b/c.md') } - it { expect(subject.basename).to eq('a/b/c') } - end - end end describe 'wiki search' do diff --git a/spec/lib/gitlab/search/found_blob_spec.rb b/spec/lib/gitlab/search/found_blob_spec.rb new file mode 100644 index 00000000000..74157e5c67c --- /dev/null +++ b/spec/lib/gitlab/search/found_blob_spec.rb @@ -0,0 +1,138 @@ +# coding: utf-8 + +require 'spec_helper' + +describe Gitlab::Search::FoundBlob do + describe 'parsing results' do + let(:project) { create(:project, :public, :repository) } + let(:results) { project.repository.search_files_by_content('feature', 'master') } + let(:search_result) { results.first } + + subject { described_class.new(content_match: search_result, project: project) } + + it "returns a valid FoundBlob" do + is_expected.to be_an described_class + expect(subject.id).to be_nil + expect(subject.path).to eq('CHANGELOG') + expect(subject.filename).to eq('CHANGELOG') + expect(subject.basename).to eq('CHANGELOG') + expect(subject.ref).to eq('master') + expect(subject.startline).to eq(188) + expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") + end + + it "doesn't parses content if not needed" do + expect(subject).not_to receive(:parse_search_result) + expect(subject.project_id).to eq(project.id) + expect(subject.binary_filename).to eq('CHANGELOG') + end + + it "parses content only once when needed" do + expect(subject).to receive(:parse_search_result).once.and_call_original + expect(subject.filename).to eq('CHANGELOG') + expect(subject.startline).to eq(188) + end + + context 'when the matching filename contains a colon' do + let(:search_result) { "master:testdata/project::function1.yaml\x001\x00---\n" } + + it 'returns a valid FoundBlob' do + expect(subject.filename).to eq('testdata/project::function1.yaml') + expect(subject.basename).to eq('testdata/project::function1') + expect(subject.ref).to eq('master') + expect(subject.startline).to eq(1) + expect(subject.data).to eq("---\n") + end + end + + context 'when the matching content contains a number surrounded by colons' do + let(:search_result) { "master:testdata/foo.txt\x001\x00blah:9:blah" } + + it 'returns a valid FoundBlob' do + expect(subject.filename).to eq('testdata/foo.txt') + expect(subject.basename).to eq('testdata/foo') + expect(subject.ref).to eq('master') + expect(subject.startline).to eq(1) + expect(subject.data).to eq('blah:9:blah') + end + end + + context 'when the matching content contains multiple null bytes' do + let(:search_result) { "master:testdata/foo.txt\x001\x00blah\x001\x00foo" } + + it 'returns a valid FoundBlob' do + expect(subject.filename).to eq('testdata/foo.txt') + expect(subject.basename).to eq('testdata/foo') + expect(subject.ref).to eq('master') + expect(subject.startline).to eq(1) + expect(subject.data).to eq("blah\x001\x00foo") + end + end + + context 'when the search result ends with an empty line' do + let(:results) { project.repository.search_files_by_content('Role models', 'master') } + + it 'returns a valid FoundBlob that ends with an empty line' do + expect(subject.filename).to eq('files/markdown/ruby-style-guide.md') + expect(subject.basename).to eq('files/markdown/ruby-style-guide') + expect(subject.ref).to eq('master') + expect(subject.startline).to eq(1) + expect(subject.data).to eq("# Prelude\n\n> Role models are important. <br/>\n> -- Officer Alex J. Murphy / RoboCop\n\n") + end + end + + context 'when the search returns non-ASCII data' do + context 'with UTF-8' do + let(:results) { project.repository.search_files_by_content('файл', 'master') } + + it 'returns results as UTF-8' do + expect(subject.filename).to eq('encoding/russian.rb') + expect(subject.basename).to eq('encoding/russian') + expect(subject.ref).to eq('master') + expect(subject.startline).to eq(1) + expect(subject.data).to eq("Хороший файл\n") + end + end + + context 'with UTF-8 in the filename' do + let(:results) { project.repository.search_files_by_content('webhook', 'master') } + + it 'returns results as UTF-8' do + expect(subject.filename).to eq('encoding/テスト.txt') + expect(subject.basename).to eq('encoding/テスト') + expect(subject.ref).to eq('master') + expect(subject.startline).to eq(3) + expect(subject.data).to include('WebHookの確認') + end + end + + context 'with ISO-8859-1' do + let(:search_result) { "master:encoding/iso8859.txt\x001\x00\xC4\xFC\nmaster:encoding/iso8859.txt\x002\x00\nmaster:encoding/iso8859.txt\x003\x00foo\n".force_encoding(Encoding::ASCII_8BIT) } + + it 'returns results as UTF-8' do + expect(subject.filename).to eq('encoding/iso8859.txt') + expect(subject.basename).to eq('encoding/iso8859') + expect(subject.ref).to eq('master') + expect(subject.startline).to eq(1) + expect(subject.data).to eq("Äü\n\nfoo\n") + end + end + end + + context "when filename has extension" do + let(:search_result) { "master:CONTRIBUTE.md\x005\x00- [Contribute to GitLab](#contribute-to-gitlab)\n" } + + it { expect(subject.path).to eq('CONTRIBUTE.md') } + it { expect(subject.filename).to eq('CONTRIBUTE.md') } + it { expect(subject.basename).to eq('CONTRIBUTE') } + end + + context "when file under directory" do + let(:search_result) { "master:a/b/c.md\x005\x00a b c\n" } + + it { expect(subject.path).to eq('a/b/c.md') } + it { expect(subject.filename).to eq('a/b/c.md') } + it { expect(subject.basename).to eq('a/b/c') } + end + end +end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 5390f237073..deb19fe1a4b 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -17,6 +17,9 @@ describe Gitlab::UsageData do gcp_cluster = create(:cluster, :provided_by_gcp) create(:cluster, :provided_by_user) create(:cluster, :provided_by_user, :disabled) + create(:cluster, :group) + create(:cluster, :group, :disabled) + create(:cluster, :group, :disabled) create(:clusters_applications_helm, :installed, cluster: gcp_cluster) create(:clusters_applications_ingress, :installed, cluster: gcp_cluster) create(:clusters_applications_cert_managers, :installed, cluster: gcp_cluster) @@ -77,7 +80,11 @@ describe Gitlab::UsageData do environments clusters clusters_enabled + project_clusters_enabled + group_clusters_enabled clusters_disabled + project_clusters_disabled + group_clusters_disabled clusters_platforms_gke clusters_platforms_user clusters_applications_helm @@ -127,8 +134,13 @@ describe Gitlab::UsageData do expect(count_data[:projects_slack_notifications_active]).to eq(2) expect(count_data[:projects_slack_slash_active]).to eq(1) - expect(count_data[:clusters_enabled]).to eq(6) - expect(count_data[:clusters_disabled]).to eq(1) + expect(count_data[:clusters_enabled]).to eq(7) + expect(count_data[:project_clusters_enabled]).to eq(6) + expect(count_data[:group_clusters_enabled]).to eq(1) + expect(count_data[:clusters_disabled]).to eq(3) + expect(count_data[:project_clusters_disabled]).to eq(1) + expect(count_data[:group_clusters_disabled]).to eq(2) + expect(count_data[:group_clusters_enabled]).to eq(1) expect(count_data[:clusters_platforms_gke]).to eq(1) expect(count_data[:clusters_platforms_user]).to eq(1) expect(count_data[:clusters_applications_helm]).to eq(1) @@ -201,4 +213,29 @@ describe Gitlab::UsageData do expect(described_class.count(relation, fallback: 15)).to eq(15) end end + + describe '#approximate_counts' do + it 'gets approximate counts for selected models' do + create(:label) + + expect(Gitlab::Database::Count).to receive(:approximate_counts) + .with(described_class::APPROXIMATE_COUNT_MODELS).once.and_call_original + + counts = described_class.approximate_counts.values + + expect(counts.count).to eq(described_class::APPROXIMATE_COUNT_MODELS.count) + expect(counts.any? { |count| count < 0 }).to be_falsey + end + + it 'returns default values if counts can not be retrieved' do + described_class::APPROXIMATE_COUNT_MODELS.map do |model| + model.name.underscore.pluralize.to_sym + end + + expect(Gitlab::Database::Count).to receive(:approximate_counts) + .and_return({}) + + expect(described_class.approximate_counts.values.uniq).to eq([-1]) + end + end end diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb index fae32cff781..47a5fd0bdb4 100644 --- a/spec/lib/gitlab/utils_spec.rb +++ b/spec/lib/gitlab/utils_spec.rb @@ -153,4 +153,42 @@ describe Gitlab::Utils do end end end + + describe '.ensure_utf8_size' do + context 'string is has less bytes than expected' do + it 'backfills string with null characters' do + transformed = described_class.ensure_utf8_size('a' * 10, bytes: 32) + + expect(transformed.bytesize).to eq 32 + expect(transformed).to eq(('a' * 10) + ('0' * 22)) + end + end + + context 'string size is exactly the one that is expected' do + it 'returns original value' do + transformed = described_class.ensure_utf8_size('a' * 32, bytes: 32) + + expect(transformed).to eq 'a' * 32 + expect(transformed.bytesize).to eq 32 + end + end + + context 'when string contains a few multi-byte UTF characters' do + it 'backfills string with null characters' do + transformed = described_class.ensure_utf8_size('❤' * 6, bytes: 32) + + expect(transformed).to eq '❤❤❤❤❤❤' + ('0' * 14) + expect(transformed.bytesize).to eq 32 + end + end + + context 'when string has multiple multi-byte UTF chars exceeding 32 bytes' do + it 'truncates string to 32 characters and backfills it if needed' do + transformed = described_class.ensure_utf8_size('❤' * 18, bytes: 32) + + expect(transformed).to eq(('❤' * 10) + ('0' * 2)) + expect(transformed.bytesize).to eq 32 + end + end + end end diff --git a/spec/lib/omni_auth/strategies/jwt_spec.rb b/spec/lib/omni_auth/strategies/jwt_spec.rb index 88d6d0b559a..c2e2db27362 100644 --- a/spec/lib/omni_auth/strategies/jwt_spec.rb +++ b/spec/lib/omni_auth/strategies/jwt_spec.rb @@ -4,12 +4,10 @@ describe OmniAuth::Strategies::Jwt do include Rack::Test::Methods include DeviseHelpers - context '.decoded' do - let(:strategy) { described_class.new({}) } + context '#decoded' do + subject { described_class.new({}) } let(:timestamp) { Time.now.to_i } let(:jwt_config) { Devise.omniauth_configs[:jwt] } - let(:key) { JWT.encode(claims, jwt_config.strategy.secret) } - let(:claims) do { id: 123, @@ -18,19 +16,55 @@ describe OmniAuth::Strategies::Jwt do iat: timestamp } end + let(:algorithm) { 'HS256' } + let(:secret) { jwt_config.strategy.secret } + let(:private_key) { secret } + let(:payload) { JWT.encode(claims, private_key, algorithm) } before do - allow_any_instance_of(OmniAuth::Strategy).to receive(:options).and_return(jwt_config.strategy) - allow_any_instance_of(Rack::Request).to receive(:params).and_return({ 'jwt' => key }) + subject.options[:secret] = secret + subject.options[:algorithm] = algorithm + + expect_next_instance_of(Rack::Request) do |rack_request| + expect(rack_request).to receive(:params).and_return('jwt' => payload) + end end - it 'decodes the user information' do - result = strategy.decoded + ECDSA_NAMED_CURVES = { + 'ES256' => 'prime256v1', + 'ES384' => 'secp384r1', + 'ES512' => 'secp521r1' + }.freeze - expect(result["id"]).to eq(123) - expect(result["name"]).to eq("user_example") - expect(result["email"]).to eq("user@example.com") - expect(result["iat"]).to eq(timestamp) + { + OpenSSL::PKey::RSA => %w[RS256 RS384 RS512], + OpenSSL::PKey::EC => %w[ES256 ES384 ES512], + String => %w[HS256 HS384 HS512] + }.each do |private_key_class, algorithms| + algorithms.each do |algorithm| + context "when the #{algorithm} algorithm is used" do + let(:algorithm) { algorithm } + let(:secret) do + if private_key_class == OpenSSL::PKey::RSA + private_key_class.generate(2048) + .to_pem + elsif private_key_class == OpenSSL::PKey::EC + private_key_class.new(ECDSA_NAMED_CURVES[algorithm]) + .tap { |key| key.generate_key! } + .to_pem + else + private_key_class.new(jwt_config.strategy.secret) + end + end + let(:private_key) { private_key_class ? private_key_class.new(secret) : secret } + + it 'decodes the user information' do + result = subject.decoded + + expect(result).to eq(claims.stringify_keys) + end + end + end end context 'required claims is missing' do @@ -43,7 +77,7 @@ describe OmniAuth::Strategies::Jwt do end it 'raises error' do - expect { strategy.decoded }.to raise_error(OmniAuth::Strategies::Jwt::ClaimInvalid) + expect { subject.decoded }.to raise_error(OmniAuth::Strategies::Jwt::ClaimInvalid) end end @@ -57,11 +91,12 @@ describe OmniAuth::Strategies::Jwt do end before do - jwt_config.strategy.valid_within = Time.now.to_i + # Omniauth config values are always strings! + subject.options[:valid_within] = 2.days.to_s end it 'raises error' do - expect { strategy.decoded }.to raise_error(OmniAuth::Strategies::Jwt::ClaimInvalid) + expect { subject.decoded }.to raise_error(OmniAuth::Strategies::Jwt::ClaimInvalid) end end @@ -76,11 +111,12 @@ describe OmniAuth::Strategies::Jwt do end before do - jwt_config.strategy.valid_within = 2.seconds + # Omniauth config values are always strings! + subject.options[:valid_within] = 2.seconds.to_s end it 'raises error' do - expect { strategy.decoded }.to raise_error(OmniAuth::Strategies::Jwt::ClaimInvalid) + expect { subject.decoded }.to raise_error(OmniAuth::Strategies::Jwt::ClaimInvalid) end end end |