diff options
27 files changed, 536 insertions, 1 deletions
diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/index.js b/app/assets/javascripts/pages/projects/settings/repository/show/index.js index 788d86d1192..5bcdfc26a4b 100644 --- a/app/assets/javascripts/pages/projects/settings/repository/show/index.js +++ b/app/assets/javascripts/pages/projects/settings/repository/show/index.js @@ -6,6 +6,7 @@ import initSettingsPanels from '~/settings_panels'; import initDeployKeys from '~/deploy_keys'; import ProtectedBranchCreate from '~/protected_branches/protected_branch_create'; import ProtectedBranchEditList from '~/protected_branches/protected_branch_edit_list'; +import DueDateSelectors from '~/due_date_select'; document.addEventListener('DOMContentLoaded', () => { new ProtectedTagCreate(); @@ -14,4 +15,5 @@ document.addEventListener('DOMContentLoaded', () => { initSettingsPanels(); new ProtectedBranchCreate(); // eslint-disable-line no-new new ProtectedBranchEditList(); // eslint-disable-line no-new + new DueDateSelectors(); }); diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index a6ca8ed5016..9db6386b86a 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -284,3 +284,22 @@ .deprecated-service { cursor: default; } + +.personal-access-tokens-never-expires-label { + color: $note-disabled-comment-color; +} + +.created-deploy-token-container { + .deploy-token-field { + width: 90%; + display: inline; + } + + .btn-clipboard { + margin-left: 5px; + } + + .help-block { + margin-top: 4px; + } +} diff --git a/app/controllers/projects/deploy_tokens_controller.rb b/app/controllers/projects/deploy_tokens_controller.rb new file mode 100644 index 00000000000..ecc6db50f2f --- /dev/null +++ b/app/controllers/projects/deploy_tokens_controller.rb @@ -0,0 +1,34 @@ +class Projects::DeployTokensController < Projects::ApplicationController + before_action :authorize_admin_project! + + def create + @token = DeployTokens::CreateService.new(@project, current_user, deploy_token_params).execute + token_params = {} + + if @token.valid? + flash[:notice] = 'Your new project deploy token has been created.' + else + token_params = @token.attributes.slice("name", "scopes", "expires_at") + flash[:alert] = @token.errors.full_messages.join(', ').html_safe + end + + redirect_to project_settings_repository_path(project, deploy_token: token_params) + end + + def revoke + @token = @project.deploy_tokens.find(params[:id]) + @token.revoke! + + redirect_to project_settings_repository_path(project) + end + + private + + def deploy_token_params + params.require(:deploy_token).permit(:name, :expires_at, scopes: []) + end + + def authorize_admin_project! + return render_404 unless can?(current_user, :admin_project, @project) + end +end diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb index dd9e4a2af3e..28897cc5946 100644 --- a/app/controllers/projects/settings/repository_controller.rb +++ b/app/controllers/projects/settings/repository_controller.rb @@ -5,7 +5,9 @@ module Projects def show @deploy_keys = DeployKeysPresenter.new(@project, current_user: current_user) + @deploy_tokens = DeployTokensPresenter.new(@project.deploy_tokens.active, current_user: current_user, project: project) + define_deploy_token define_protected_refs end @@ -51,6 +53,14 @@ module Projects gon.push(protectable_branches_for_dropdown) gon.push(access_levels_options) end + + def define_deploy_token + @deploy_token = @project.deploy_tokens.build(deploy_token_attributes) + end + + def deploy_token_attributes + params.fetch(:deploy_token, {}).permit(:name, :expires_at, scopes: []) + end end end end diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb new file mode 100644 index 00000000000..185bd806b18 --- /dev/null +++ b/app/models/deploy_token.rb @@ -0,0 +1,25 @@ +class DeployToken < ActiveRecord::Base + include Expirable + include TokenAuthenticatable + add_authentication_token_field :token + + AVAILABLE_SCOPES = %w(read_repo read_registry).freeze + + serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize + + validates :scopes, presence: true + + belongs_to :project + + before_save :ensure_token + + scope :active, -> { where("revoked = false AND (expires_at >= NOW() OR expires_at IS NULL)") } + + def revoke! + update!(revoked: true) + end + + def self.redis_shared_state_key(user_id) + "gitlab:personal_access_token:#{user_id}" + end +end diff --git a/app/models/project.rb b/app/models/project.rb index 96907f3b23d..3cfb163abf4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -222,6 +222,7 @@ class Project < ActiveRecord::Base has_many :environments has_many :deployments has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule' + has_many :deploy_tokens has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' diff --git a/app/presenters/projects/settings/deploy_tokens_presenter.rb b/app/presenters/projects/settings/deploy_tokens_presenter.rb new file mode 100644 index 00000000000..cbad35431b3 --- /dev/null +++ b/app/presenters/projects/settings/deploy_tokens_presenter.rb @@ -0,0 +1,48 @@ +module Projects + module Settings + class DeployTokensPresenter < Gitlab::View::Presenter::Simple + include Enumerable + + presents :deploy_tokens + + def available_scopes + DeployToken::AVAILABLE_SCOPES + end + + def length + deploy_tokens.length + end + + def scope_description(scope) + scope_descriptions[scope] + end + + def each + deploy_tokens.each do |deploy_token| + yield deploy_token + end + end + + def new_deploy_token + @new_deploy_token ||= Gitlab::Redis::SharedState.with do |redis| + token = redis.get(deploy_token_key) + redis.del(deploy_token_key) + token + end + end + + private + + def scope_descriptions + { + 'read_repo' => 'Allows read-only access to the repository', + 'read_registry' => 'Allows read-only access to the registry images' + } + end + + def deploy_token_key + DeployToken.redis_shared_state_key(current_user.id) + end + end + end +end diff --git a/app/services/deploy_tokens/create_service.rb b/app/services/deploy_tokens/create_service.rb new file mode 100644 index 00000000000..e93c021557e --- /dev/null +++ b/app/services/deploy_tokens/create_service.rb @@ -0,0 +1,24 @@ +module DeployTokens + class CreateService < BaseService + REDIS_EXPIRY_TIME = 3.minutes + + def execute + @deploy_token = @project.deploy_tokens.create(params) + store_in_redis if @deploy_token.persisted? + + @deploy_token + end + + private + + def store_in_redis + Gitlab::Redis::SharedState.with do |redis| + redis.set(deploy_token_key, @deploy_token.token, ex: REDIS_EXPIRY_TIME) + end + end + + def deploy_token_key + DeployToken.redis_shared_state_key(current_user.id) + end + end +end diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml index b96251cd982..9b87a7aaca8 100644 --- a/app/views/profiles/personal_access_tokens/index.html.haml +++ b/app/views/profiles/personal_access_tokens/index.html.haml @@ -2,7 +2,6 @@ - page_title "Personal Access Tokens" - @content_class = "limit-container-width" unless fluid_layout - .row.prepend-top-default .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 diff --git a/app/views/projects/deploy_tokens/_form.html.haml b/app/views/projects/deploy_tokens/_form.html.haml new file mode 100644 index 00000000000..6f1c892bce0 --- /dev/null +++ b/app/views/projects/deploy_tokens/_form.html.haml @@ -0,0 +1,21 @@ +%p.profile-settings-content + Pick a name for the application, and we'll give you a unique deploy token. + += form_for token, url: project_deploy_tokens_path(project), method: :post do |f| + = form_errors(token) + + .form-group + = f.label :name, class: 'label-light' + = f.text_field :name, class: "form-control", required: true + + .form-group + = f.label :expires_at, class: 'label-light' + = f.text_field :expires_at, class: "datepicker form-control" + + .form-group + = f.label :scopes, class: 'label-light' + - presenter.available_scopes.each do |scope| + = render 'projects/deploy_tokens/scope_form', token: token, scope: scope, presenter: presenter + + .prepend-top-default + = f.submit "Create deploy token", class: "btn btn-create" diff --git a/app/views/projects/deploy_tokens/_index.html.haml b/app/views/projects/deploy_tokens/_index.html.haml new file mode 100644 index 00000000000..7d2bfe291c3 --- /dev/null +++ b/app/views/projects/deploy_tokens/_index.html.haml @@ -0,0 +1,19 @@ +- expanded = Rails.env.test? + +%section.settings.no-animate{ class: ('expanded' if expanded) } + .settings-header + %h4 + Deploy Tokens + %button.btn.js-settings-toggle.qa-expand-deploy-keys + = expanded ? 'Collapse' : 'Expand' + %p + Deploy tokens allow read-only access to your repository and registry images. + .settings-content + - if @deploy_tokens.new_deploy_token + = render 'projects/deploy_tokens/new_deploy_token', new_token: @deploy_tokens.new_deploy_token + + %h5.prepend-top-0 + Add a deploy token + = render 'projects/deploy_tokens/form', project: @project, token: @deploy_token, presenter: @deploy_tokens + %hr + = render 'projects/deploy_tokens/table', project: @project, active_tokens: @deploy_tokens diff --git a/app/views/projects/deploy_tokens/_new_deploy_token.html.haml b/app/views/projects/deploy_tokens/_new_deploy_token.html.haml new file mode 100644 index 00000000000..8f5e669c815 --- /dev/null +++ b/app/views/projects/deploy_tokens/_new_deploy_token.html.haml @@ -0,0 +1,9 @@ +.created-deploy-token-container + %h5.prepend-top-0 + Your New Deploy Token + .form-group + = text_field_tag 'deploy-token', new_token, readonly: true, class: "deploy-token-field form-control js-select-on-focus", 'aria-describedby' => "deploy-token-help-block" + = clipboard_button(text: new_token, title: "Copy deploy token to clipboard", placement: "left") + %span.deploy-token.help-block.text-danger Make sure you save it - you won't be able to access it again. + +%hr diff --git a/app/views/projects/deploy_tokens/_revoke_modal.html.haml b/app/views/projects/deploy_tokens/_revoke_modal.html.haml new file mode 100644 index 00000000000..a859aac015d --- /dev/null +++ b/app/views/projects/deploy_tokens/_revoke_modal.html.haml @@ -0,0 +1,15 @@ +.modal{ id: "revoke-modal-#{token.id}" } + .modal-dialog + .modal-content + .modal-header + %h4.modal-title.pull-left + Revoke + %b #{token.name}? + %button.close.pull-right{ "aria-label" => "Close", data: { dismiss: "modal"} } + %span{ "aria-hidden" => "true" } × + .modal-body + %p + Are you sure you want to revoke this Deploy Token? This action cannot be undone + .modal-footer + %a{ href: '#', data: { dismiss: 'modal' }, class: 'btn btn-default' } Cancel + = link_to "Revoke #{token.name}", revoke_project_deploy_token_path(project, token), method: :put, class: 'btn btn-danger' diff --git a/app/views/projects/deploy_tokens/_scope_form.html.haml b/app/views/projects/deploy_tokens/_scope_form.html.haml new file mode 100644 index 00000000000..f67701c8ee1 --- /dev/null +++ b/app/views/projects/deploy_tokens/_scope_form.html.haml @@ -0,0 +1,4 @@ +%fieldset + = check_box_tag "deploy_token[scopes][]", scope, token.scopes.include?(scope), id: "deploy_token_scopes_#{scope}" + = label_tag ("deploy_token_scopes_#{scope}"), scope + %span= presenter.scope_description(scope) diff --git a/app/views/projects/deploy_tokens/_table.html.haml b/app/views/projects/deploy_tokens/_table.html.haml new file mode 100644 index 00000000000..5ea9e86020f --- /dev/null +++ b/app/views/projects/deploy_tokens/_table.html.haml @@ -0,0 +1,29 @@ +%h5 Active Deploy Tokens (#{active_tokens.length}) + +- if active_tokens.present? + .table-responsive.deploy-tokens + %table.table + %thead + %tr + %th Name + %th Created + %th Expires + %th Scopes + %th + %tbody + - active_tokens.each do |token| + %tr + %td= token.name + %td= token.created_at.to_date.to_s(:medium) + %td + - if token.expires? + %span{ class: ('text-warning' if token.expires_soon?) } + In #{distance_of_time_in_words_to_now(token.expires_at)} + - else + %span.token-never-expires-label Never + %td= token.scopes.present? ? token.scopes.join(", ") : "<no scopes selected>" + %td= link_to "Revoke", "#", class: "btn btn-danger pull-right", data: { toggle: "modal", target: "#revoke-modal-#{token.id}"} + = render 'projects/deploy_tokens/revoke_modal', token: token, project: project +- else + .settings-message.text-center + This project has no active Deploy Tokens. diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml index 6bef4d19434..f57590a908f 100644 --- a/app/views/projects/settings/repository/show.html.haml +++ b/app/views/projects/settings/repository/show.html.haml @@ -9,3 +9,4 @@ = render "projects/protected_branches/index" = render "projects/protected_tags/index" = render @deploy_keys += render "projects/deploy_tokens/index" diff --git a/changelogs/unreleased/31591-project-deploy-tokens-to-allow-permanent-access.yml b/changelogs/unreleased/31591-project-deploy-tokens-to-allow-permanent-access.yml new file mode 100644 index 00000000000..b9b19eb20ad --- /dev/null +++ b/changelogs/unreleased/31591-project-deploy-tokens-to-allow-permanent-access.yml @@ -0,0 +1,5 @@ +--- +title: Creates Deploy Tokens to allow permanent access to repo and registry +merge_request: 17894 +author: +type: added diff --git a/config/routes/project.rb b/config/routes/project.rb index 618c7897060..27d3569829f 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -88,6 +88,12 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do end end + resources :deploy_tokens, constraints: { id: /\d+/ }, only: :create do + member do + put :revoke + end + end + resources :forks, only: [:index, :new, :create] resource :import, only: [:new, :create, :show] diff --git a/db/migrate/20180319190020_create_deploy_tokens.rb b/db/migrate/20180319190020_create_deploy_tokens.rb new file mode 100644 index 00000000000..53808300fc1 --- /dev/null +++ b/db/migrate/20180319190020_create_deploy_tokens.rb @@ -0,0 +1,16 @@ +class CreateDeployTokens < ActiveRecord::Migration + DOWNTIME = false + + def change + create_table :deploy_tokens do |t| + t.references :project, index: true, foreign_key: true, null: false + t.string :name, null: false + t.string :token, index: { unique: true }, null: false + t.string :scopes + t.boolean :revoked, default: false + t.datetime :expires_at + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 2cd51b200b3..333baa245b7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -683,6 +683,20 @@ ActiveRecord::Schema.define(version: 20180405101928) do add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree + create_table "deploy_tokens", force: :cascade do |t| + t.integer "project_id", null: false + t.string "name", null: false + t.string "token", null: false + t.string "scopes" + t.boolean "revoked", default: false + t.datetime "expires_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "deploy_tokens", ["project_id"], name: "index_deploy_tokens_on_project_id", using: :btree + add_index "deploy_tokens", ["token"], name: "index_deploy_tokens_on_token", unique: true, using: :btree + create_table "deployments", force: :cascade do |t| t.integer "iid", null: false t.integer "project_id", null: false @@ -2072,6 +2086,7 @@ ActiveRecord::Schema.define(version: 20180405101928) do add_foreign_key "clusters_applications_runners", "clusters", on_delete: :cascade add_foreign_key "container_repositories", "projects" add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade + add_foreign_key "deploy_tokens", "projects" add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade add_foreign_key "environments", "projects", name: "fk_d1c8c1da6a", on_delete: :cascade add_foreign_key "events", "projects", on_delete: :cascade diff --git a/spec/controllers/projects/deploy_tokens_controller_spec.rb b/spec/controllers/projects/deploy_tokens_controller_spec.rb new file mode 100644 index 00000000000..0ade61f4380 --- /dev/null +++ b/spec/controllers/projects/deploy_tokens_controller_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe Projects::DeployTokensController do + let(:project) { create(:project) } + let(:user) { create(:user) } + let!(:member) { project.add_master(user) } + + before do + sign_in(user) + end + + describe 'POST #create' do + let(:deploy_token_params) { attributes_for(:deploy_token) } + subject do + post :create, + namespace_id: project.namespace, + project_id: project, + deploy_token: deploy_token_params + end + + context 'with valid params' do + it 'should create a new DeployToken' do + expect { subject }.to change(DeployToken, :count).by(1) + end + + it 'should include a flash notice' do + subject + expect(flash[:notice]).to eq('Your new project deploy token has been created.') + end + end + + context 'with invalid params' do + let(:deploy_token_params) { attributes_for(:deploy_token, scopes: []) } + + it 'should not create a new DeployToken' do + expect { subject }.not_to change(DeployToken, :count) + end + + it 'should include a flash alert with the error message' do + subject + expect(flash[:alert]).to eq("Scopes can't be blank") + end + end + + context 'when user does not have enough permissions' do + let!(:member) { project.add_developer(user) } + + it 'responds with status 404' do + subject + + expect(response).to have_gitlab_http_status(404) + end + end + end +end diff --git a/spec/controllers/projects/settings/repository_controller_spec.rb b/spec/controllers/projects/settings/repository_controller_spec.rb index 3a4014b7768..03867661483 100644 --- a/spec/controllers/projects/settings/repository_controller_spec.rb +++ b/spec/controllers/projects/settings/repository_controller_spec.rb @@ -16,5 +16,31 @@ describe Projects::Settings::RepositoryController do expect(response).to have_gitlab_http_status(200) expect(response).to render_template(:show) end + + context 'with no deploy token params' do + it 'should build an empty instance of DeployToken' do + get :show, namespace_id: project.namespace, project_id: project + + deploy_token = assigns(:deploy_token) + expect(deploy_token).to be_an_instance_of(DeployToken) + expect(deploy_token.name).to be_nil + expect(deploy_token.expires_at).to be_nil + expect(deploy_token.scopes).to eq([]) + end + end + + context 'when rendering an invalid deploy token' do + let(:deploy_token_attributes) { attributes_for(:deploy_token, project_id: project.id) } + + it 'should build an instance of DeployToken' do + get :show, namespace_id: project.namespace, project_id: project, deploy_token: deploy_token_attributes + + deploy_token = assigns(:deploy_token) + expect(deploy_token).to be_an_instance_of(DeployToken) + expect(deploy_token.name).to eq(deploy_token_attributes[:name]) + expect(deploy_token.expires_at.to_date).to eq(deploy_token_attributes[:expires_at].to_date) + expect(deploy_token.scopes).to match_array(deploy_token_attributes[:scopes]) + end + end end end diff --git a/spec/factories/deploy_tokens.rb b/spec/factories/deploy_tokens.rb new file mode 100644 index 00000000000..fa349e10ddc --- /dev/null +++ b/spec/factories/deploy_tokens.rb @@ -0,0 +1,22 @@ +FactoryBot.define do + factory :deploy_token do + project + token { SecureRandom.hex(50) } + sequence(:name) { |n| "PDT #{n}" } + revoked false + expires_at { 5.days.from_now } + scopes %w(read_repo read_registry) + + trait :revoked do + revoked true + end + + trait :read_repo do + scopes ['read_repo'] + end + + trait :read_registry do + scopes ['read_registry'] + end + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index b675d5dc031..d38e665436f 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -281,6 +281,7 @@ project: - project_badges - source_of_merge_requests - internal_ids +- deploy_tokens award_emoji: - awardable - user diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb new file mode 100644 index 00000000000..bd27da63dfe --- /dev/null +++ b/spec/models/deploy_token_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe DeployToken do + it { is_expected.to belong_to :project } + + describe 'validations' do + let(:project_deploy_token) { build(:deploy_token) } + + context 'with no scopes defined' do + it 'should not be valid' do + project_deploy_token.scopes = [] + + expect(project_deploy_token).not_to be_valid + expect(project_deploy_token.errors[:scopes].first).to eq("can't be blank") + end + end + end + + describe '#ensure_token' do + let(:project_deploy_token) { build(:deploy_token) } + + it 'should ensure a token' do + project_deploy_token.token = nil + project_deploy_token.save + + expect(project_deploy_token.token).not_to be_empty + end + end + + describe '#revoke!' do + subject { create(:deploy_token) } + + it 'should update revoke attribute' do + subject.revoke! + expect(subject.revoked?).to be_truthy + end + end +end diff --git a/spec/presenters/projects/settings/deploy_tokens_presenter_spec.rb b/spec/presenters/projects/settings/deploy_tokens_presenter_spec.rb new file mode 100644 index 00000000000..d3210439b05 --- /dev/null +++ b/spec/presenters/projects/settings/deploy_tokens_presenter_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe Projects::Settings::DeployTokensPresenter do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:deploy_tokens) { create_list(:deploy_token, 3, project: project) } + + subject(:presenter) { described_class.new(deploy_tokens, current_user: user, project: project) } + + describe '#available_scopes' do + it 'returns the all the deploy token scopes' do + expect(presenter.available_scopes).to match_array(%w(read_repo read_registry)) + end + end + + describe '#scope_description' do + let(:deploy_token) { create(:deploy_token, project: project, scopes: [:read_registry]) } + + it 'returns the description for a given scope' do + description = 'Allows read-only access to the registry images' + expect(presenter.scope_description('read_registry')).to eq(description) + end + end + + describe '#length' do + it 'returns the size of deploy tokens presented' do + expect(presenter.length).to eq(3) + end + end + + describe '#new_deploy_token' do + context 'when a deploy token has been created recently' do + it 'returns the token of the deploy' do + deploy_token = ::DeployTokens::CreateService.new(project, user, attributes_for(:deploy_token)).execute + + expect(presenter.new_deploy_token).to eq(deploy_token.token) + end + end + + context 'when a deploy token has not been created recently' do + it 'does returns nil' do + expect(presenter.new_deploy_token).to be_nil + end + end + end +end diff --git a/spec/services/deploy_tokens/create_service_spec.rb b/spec/services/deploy_tokens/create_service_spec.rb new file mode 100644 index 00000000000..84aa17971d6 --- /dev/null +++ b/spec/services/deploy_tokens/create_service_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +describe DeployTokens::CreateService, :clean_gitlab_redis_shared_state do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:deploy_token_params) { attributes_for(:deploy_token) } + + describe '#execute' do + subject { described_class.new(project, user, deploy_token_params) } + + context 'when the deploy token is valid' do + it 'should create a new DeployToken' do + expect { subject.execute }.to change { DeployToken.count }.by(1) + end + + it 'should assign the DeployToken to the project' do + subject.execute + + expect(subject.project).to eq(project) + end + + it 'should store the token on redis' do + subject.execute + redis_key = DeployToken.redis_shared_state_key(user.id) + + expect(Gitlab::Redis::SharedState.with { |redis| redis.get(redis_key) }).not_to be_nil + end + end + + context 'when the deploy token is invalid' do + let(:deploy_token_params) { attributes_for(:deploy_token, scopes: []) } + + it 'it should not create a new DeployToken' do + expect { subject.execute }.not_to change { DeployToken.count } + end + + it 'should not store the token on redis' do + subject.execute + redis_key = DeployToken.redis_shared_state_key(user.id) + + expect(Gitlab::Redis::SharedState.with { |redis| redis.get(redis_key) }).to be_nil + end + end + end +end |