Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
f6dad4cfc6
commit
2000704b7a
|
@ -65,7 +65,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapState(['isShowingLabels', 'issuableType', 'allowSubEpics']),
|
||||
...mapGetters(['isEpicBoard']),
|
||||
...mapGetters(['isEpicBoard', 'isProjectBoard']),
|
||||
cappedAssignees() {
|
||||
// e.g. maxRender is 4,
|
||||
// Render up to all 4 assignees if there are only 4 assigness
|
||||
|
@ -144,6 +144,9 @@ export default {
|
|||
totalProgress() {
|
||||
return Math.round((this.item.descendantWeightSum.closedIssues / this.totalWeight) * 100);
|
||||
},
|
||||
showReferencePath() {
|
||||
return !this.isProjectBoard && this.itemReferencePath;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['performSearch', 'setError']),
|
||||
|
@ -247,7 +250,7 @@ export default {
|
|||
:class="{ 'gl-font-base': isEpicBoard }"
|
||||
>
|
||||
<tooltip-on-truncate
|
||||
v-if="itemReferencePath"
|
||||
v-if="showReferencePath"
|
||||
:title="itemReferencePath"
|
||||
placement="bottom"
|
||||
class="board-item-path gl-text-truncate gl-font-weight-bold"
|
||||
|
|
|
@ -4,6 +4,9 @@ module IssuableActions
|
|||
extend ActiveSupport::Concern
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
include Gitlab::Cache::Helpers
|
||||
include SpammableActions::AkismetMarkAsSpamAction
|
||||
include SpammableActions::CaptchaCheck::HtmlFormatActionsSupport
|
||||
include SpammableActions::CaptchaCheck::JsonFormatActionsSupport
|
||||
|
||||
included do
|
||||
before_action :authorize_destroy_issuable!, only: :destroy
|
||||
|
@ -25,17 +28,42 @@ module IssuableActions
|
|||
end
|
||||
|
||||
def update
|
||||
@issuable = update_service.execute(issuable) # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
recaptcha_check_if_spammable { render :edit }
|
||||
end
|
||||
updated_issuable = update_service.execute(issuable)
|
||||
# NOTE: We only assign the instance variable on this line, and use the local variable
|
||||
# everywhere else in the method, to avoid having to add multiple `rubocop:disable` comments.
|
||||
@issuable = updated_issuable # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
|
||||
format.json do
|
||||
recaptcha_check_if_spammable(false) { render_entity_json }
|
||||
# NOTE: This check for `is_a?(Spammable)` is necessary because not all
|
||||
# possible `issuable` types implement Spammable. Once they all implement Spammable,
|
||||
# this check can be removed.
|
||||
if updated_issuable.is_a?(Spammable)
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
# NOTE: This redirect is intentionally only performed in the case where the updated
|
||||
# issuable is a spammable, and intentionally is not performed in the non-spammable case.
|
||||
# This preserves the legacy behavior of this action.
|
||||
if updated_issuable.valid?
|
||||
redirect_to spammable_path
|
||||
else
|
||||
with_captcha_check_html_format { render :edit }
|
||||
end
|
||||
end
|
||||
|
||||
format.json do
|
||||
with_captcha_check_json_format { render_entity_json }
|
||||
end
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
render :edit
|
||||
end
|
||||
|
||||
format.json do
|
||||
render_entity_json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
render_conflict_response
|
||||
end
|
||||
|
@ -171,12 +199,6 @@ module IssuableActions
|
|||
DiscussionSerializer.new(project: project, noteable: issuable, current_user: current_user, note_entity: ProjectNoteEntity)
|
||||
end
|
||||
|
||||
def recaptcha_check_if_spammable(should_redirect = true, &block)
|
||||
return yield unless issuable.is_a? Spammable
|
||||
|
||||
recaptcha_check_with_fallback(should_redirect, &block)
|
||||
end
|
||||
|
||||
def render_conflict_response
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module SpammableActions
|
||||
extend ActiveSupport::Concern
|
||||
include Spam::Concerns::HasSpamActionResponseFields
|
||||
|
||||
included do
|
||||
before_action :authorize_submit_spammable!, only: :mark_as_spam
|
||||
end
|
||||
|
||||
def mark_as_spam
|
||||
if Spam::MarkAsSpamService.new(target: spammable).execute
|
||||
redirect_to spammable_path, notice: _("%{spammable_titlecase} was submitted to Akismet successfully.") % { spammable_titlecase: spammable.spammable_entity_type.titlecase }
|
||||
else
|
||||
redirect_to spammable_path, alert: _('Error with Akismet. Please check the logs for more info.')
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def recaptcha_check_with_fallback(should_redirect = true, &fallback)
|
||||
if should_redirect && spammable.valid?
|
||||
redirect_to spammable_path
|
||||
elsif spammable.render_recaptcha?
|
||||
Gitlab::Recaptcha.load_configurations!
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
# NOTE: format.html is still used by issue create, and uses the legacy HAML
|
||||
# `_recaptcha_form.html.haml` rendered via the `projects/issues/verify` template.
|
||||
render :verify
|
||||
end
|
||||
|
||||
format.json do
|
||||
# format.json is used by all new Vue-based CAPTCHA implementations, which
|
||||
# handle all of the CAPTCHA form rendering on the client via the Pajamas-based
|
||||
# app/assets/javascripts/captcha/captcha_modal.vue
|
||||
|
||||
# NOTE: "409 - Conflict" seems to be the most appropriate HTTP status code for a response
|
||||
# which requires a CAPTCHA to be solved in order for the request to be resubmitted.
|
||||
# See https://stackoverflow.com/q/26547466/25192
|
||||
render json: spam_action_response_fields(spammable), status: :conflict
|
||||
end
|
||||
end
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: This method is currently only needed for issue create, to convert spam/CAPTCHA values from
|
||||
# params, and instead be passed as headers, as the spam services now all expect. It can be removed
|
||||
# when issue create is is converted to a client/JS based approach instead of the legacy HAML
|
||||
# `_recaptcha_form.html.haml` which is rendered via the `projects/issues/verify` template.
|
||||
# In that case, which is based on the legacy reCAPTCHA implementation using the HTML/HAML form,
|
||||
# the 'g-recaptcha-response' field name comes from `Recaptcha::ClientHelper#recaptcha_tags` in the
|
||||
# recaptcha gem, which is called from the HAML `_recaptcha_form.html.haml` form.
|
||||
def extract_legacy_spam_params_to_headers
|
||||
request.headers['X-GitLab-Captcha-Response'] = params['g-recaptcha-response'] || params[:captcha_response]
|
||||
request.headers['X-GitLab-Spam-Log-Id'] = params[:spam_log_id]
|
||||
end
|
||||
|
||||
def spammable
|
||||
raise NotImplementedError, "#{self.class} does not implement #{__method__}"
|
||||
end
|
||||
|
||||
def spammable_path
|
||||
raise NotImplementedError, "#{self.class} does not implement #{__method__}"
|
||||
end
|
||||
|
||||
def authorize_submit_spammable!
|
||||
access_denied! unless current_user.admin?
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module SpammableActions::AkismetMarkAsSpamAction
|
||||
extend ActiveSupport::Concern
|
||||
include SpammableActions::Attributes
|
||||
|
||||
included do
|
||||
before_action :authorize_submit_spammable!, only: :mark_as_spam
|
||||
end
|
||||
|
||||
def mark_as_spam
|
||||
if Spam::AkismetMarkAsSpamService.new(target: spammable).execute
|
||||
redirect_to spammable_path, notice: _("%{spammable_titlecase} was submitted to Akismet successfully.") % { spammable_titlecase: spammable.spammable_entity_type.titlecase }
|
||||
else
|
||||
redirect_to spammable_path, alert: _('Error with Akismet. Please check the logs for more info.')
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorize_submit_spammable!
|
||||
access_denied! unless current_user.can_admin_all_resources?
|
||||
end
|
||||
|
||||
def spammable_path
|
||||
raise NotImplementedError, "#{self.class} does not implement #{__method__}"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module SpammableActions
|
||||
module Attributes
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
private
|
||||
|
||||
def spammable
|
||||
raise NotImplementedError, "#{self.class} does not implement #{__method__}"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module SpammableActions::CaptchaCheck
|
||||
module Common
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
private
|
||||
|
||||
def with_captcha_check_common(captcha_render_lambda:, &block)
|
||||
# If the Spammable indicates that CAPTCHA is not necessary (either due to it not being flagged
|
||||
# as spam, or if spam/captcha is disabled for some reason), then we will go ahead and
|
||||
# yield to the block containing the action's original behavior, then return.
|
||||
return yield unless spammable.render_recaptcha?
|
||||
|
||||
# If we got here, we need to render the CAPTCHA instead of yielding to action's original
|
||||
# behavior. We will present a CAPTCHA to be solved by executing the lambda which was passed
|
||||
# as the `captcha_render_lambda:` argument. This lambda contains either the HTML-specific or
|
||||
# JSON-specific behavior to cause the CAPTCHA modal to be rendered.
|
||||
Gitlab::Recaptcha.load_configurations!
|
||||
captcha_render_lambda.call
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# This module should *ONLY* be included if needed to support forms submits with HTML MIME type.
|
||||
# In other words, forms handled by actions which use a `respond_to` of `format.html`.
|
||||
#
|
||||
# If the request is handled by actions via `format.json`, for example, for all Javascript based form
|
||||
# submissions and Vue components which use Apollo and Axios, then the corresponding module
|
||||
# which supports JSON format should be used instead.
|
||||
module SpammableActions::CaptchaCheck::HtmlFormatActionsSupport
|
||||
extend ActiveSupport::Concern
|
||||
include SpammableActions::Attributes
|
||||
include SpammableActions::CaptchaCheck::Common
|
||||
|
||||
included do
|
||||
before_action :convert_html_spam_params_to_headers, only: [:create, :update]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def with_captcha_check_html_format(&block)
|
||||
captcha_render_lambda = -> { render :verify }
|
||||
with_captcha_check_common(captcha_render_lambda: captcha_render_lambda, &block)
|
||||
end
|
||||
|
||||
# Convert spam/CAPTCHA values from form field params to headers, because all spam-related services
|
||||
# expect these values to be passed as headers.
|
||||
#
|
||||
# The 'g-recaptcha-response' field name comes from `Recaptcha::ClientHelper#recaptcha_tags` in the
|
||||
# recaptcha gem. This is a field which is automatically included by calling the
|
||||
# `#recaptcha_tags` method within a HAML template's form.
|
||||
def convert_html_spam_params_to_headers
|
||||
request.headers['X-GitLab-Captcha-Response'] = params['g-recaptcha-response'] if params['g-recaptcha-response']
|
||||
request.headers['X-GitLab-Spam-Log-Id'] = params[:spam_log_id] if params[:spam_log_id]
|
||||
end
|
||||
end
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# This module should be included to support forms submits with a 'js' or 'json' type of MIME type.
|
||||
# In other words, forms handled by actions which use a `respond_to` of `format.js` or `format.json`.
|
||||
#
|
||||
# For example, for all Javascript based form submissions and Vue components which use Apollo and Axios
|
||||
#
|
||||
# If the request is handled by actions via `format.html`, then the corresponding module which
|
||||
# supports HTML format should be used instead.
|
||||
module SpammableActions::CaptchaCheck::JsonFormatActionsSupport
|
||||
extend ActiveSupport::Concern
|
||||
include SpammableActions::Attributes
|
||||
include SpammableActions::CaptchaCheck::Common
|
||||
include Spam::Concerns::HasSpamActionResponseFields
|
||||
|
||||
private
|
||||
|
||||
def with_captcha_check_json_format(&block)
|
||||
# NOTE: "409 - Conflict" seems to be the most appropriate HTTP status code for a response
|
||||
# which requires a CAPTCHA to be solved in order for the request to be resubmitted.
|
||||
# See https://stackoverflow.com/q/26547466/25192
|
||||
captcha_render_lambda = -> { render json: spam_action_response_fields(spammable), status: :conflict }
|
||||
with_captcha_check_common(captcha_render_lambda: captcha_render_lambda, &block)
|
||||
end
|
||||
end
|
|
@ -7,7 +7,6 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
include ToggleAwardEmoji
|
||||
include IssuableCollections
|
||||
include IssuesCalendar
|
||||
include SpammableActions
|
||||
include RecordUserLastActivity
|
||||
|
||||
ISSUES_EXCEPT_ACTIONS = %i[index calendar new create bulk_update import_csv export_csv service_desk].freeze
|
||||
|
@ -129,7 +128,6 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def create
|
||||
extract_legacy_spam_params_to_headers
|
||||
create_params = issue_params.merge(
|
||||
merge_request_to_resolve_discussions_of: params[:merge_request_to_resolve_discussions_of],
|
||||
discussion_to_resolve: params[:discussion_to_resolve]
|
||||
|
@ -149,10 +147,11 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
recaptcha_check_with_fallback { render :new }
|
||||
end
|
||||
if @issue.valid?
|
||||
redirect_to project_issue_path(@project, @issue)
|
||||
else
|
||||
# NOTE: this CAPTCHA support method is indirectly included via IssuableActions
|
||||
with_captcha_check_html_format { render :new }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ class Projects::SnippetsController < Projects::Snippets::ApplicationController
|
|||
extend ::Gitlab::Utils::Override
|
||||
include SnippetsActions
|
||||
include ToggleAwardEmoji
|
||||
include SpammableActions
|
||||
include SpammableActions::AkismetMarkAsSpamAction
|
||||
|
||||
before_action :check_snippets_available!
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ class SnippetsController < Snippets::ApplicationController
|
|||
include SnippetsActions
|
||||
include PreviewMarkdown
|
||||
include ToggleAwardEmoji
|
||||
include SpammableActions
|
||||
include SpammableActions::AkismetMarkAsSpamAction
|
||||
|
||||
before_action :snippet, only: [:show, :edit, :raw, :toggle_award_emoji, :mark_as_spam]
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ module Mutations
|
|||
private
|
||||
|
||||
def mark_as_spam(snippet)
|
||||
Spam::MarkAsSpamService.new(target: snippet).execute
|
||||
Spam::AkismetMarkAsSpamService.new(target: snippet).execute
|
||||
end
|
||||
|
||||
def authorized_resource?(snippet)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Spam
|
||||
class MarkAsSpamService
|
||||
class AkismetMarkAsSpamService
|
||||
include ::AkismetMethods
|
||||
|
||||
attr_accessor :target, :options
|
||||
|
@ -9,12 +9,12 @@ module Spam
|
|||
def initialize(target:)
|
||||
@target = target
|
||||
@options = {}
|
||||
|
||||
@options[:ip_address] = @target.ip_address
|
||||
@options[:user_agent] = @target.user_agent
|
||||
end
|
||||
|
||||
def execute
|
||||
@options[:ip_address] = @target.ip_address
|
||||
@options[:user_agent] = @target.user_agent
|
||||
|
||||
return unless target.submittable_as_spam?
|
||||
return unless akismet.submit_spam
|
||||
|
|
@ -83,7 +83,7 @@ YYYY-MM-DD
|
|||
|
||||
### Event Time Period Limit
|
||||
|
||||
GitLab removes events older than 2 years from the events table for performance reasons.
|
||||
GitLab removes events older than 3 years from the events table for performance reasons.
|
||||
|
||||
## List currently authenticated user's events
|
||||
|
||||
|
|
1
qa/qa.rb
1
qa/qa.rb
|
@ -73,6 +73,7 @@ module QA
|
|||
autoload :GroupBase, 'qa/resource/group_base'
|
||||
autoload :Sandbox, 'qa/resource/sandbox'
|
||||
autoload :Group, 'qa/resource/group'
|
||||
autoload :BulkImportGroup, 'qa/resource/bulk_import_group'
|
||||
autoload :Issue, 'qa/resource/issue'
|
||||
autoload :ProjectIssueNote, 'qa/resource/project_issue_note'
|
||||
autoload :Project, 'qa/resource/project'
|
||||
|
|
|
@ -188,7 +188,7 @@ module QA
|
|||
end
|
||||
|
||||
def log_having_both_api_result_and_block(name, api_value)
|
||||
QA::Runtime::Logger.info(<<~MSG.strip)
|
||||
QA::Runtime::Logger.debug(<<~MSG.strip)
|
||||
<#{self.class}> Attribute #{name.inspect} has both API response `#{api_value}` and a block. API response will be picked. Block will be ignored.
|
||||
MSG
|
||||
end
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Resource
|
||||
class BulkImportGroup < Group
|
||||
attributes :source_group_path,
|
||||
:import_id
|
||||
|
||||
attribute :destination_group_path do
|
||||
source_group_path
|
||||
end
|
||||
|
||||
attribute :access_token do
|
||||
api_client.personal_access_token
|
||||
end
|
||||
|
||||
alias_method :path, :source_group_path
|
||||
|
||||
delegate :gitlab_address, to: 'QA::Runtime::Scenario'
|
||||
|
||||
def fabricate_via_browser_ui!
|
||||
Page::Main::Menu.perform(&:go_to_create_group)
|
||||
|
||||
Page::Group::New.perform do |group|
|
||||
group.switch_to_import_tab
|
||||
group.connect_gitlab_instance(gitlab_address, api_client.personal_access_token)
|
||||
end
|
||||
|
||||
Page::Group::BulkImport.perform do |import_page|
|
||||
import_page.import_group(path, sandbox.path)
|
||||
end
|
||||
|
||||
reload!
|
||||
visit!
|
||||
end
|
||||
|
||||
def fabricate_via_api!
|
||||
response = post(Runtime::API::Request.new(api_client, api_post_path).url, api_post_body)
|
||||
@import_id = parse_body(response)[:id]
|
||||
|
||||
"#{gitlab_address}/#{full_path}"
|
||||
end
|
||||
|
||||
def api_post_path
|
||||
'/bulk_imports'
|
||||
end
|
||||
|
||||
def api_post_body
|
||||
{
|
||||
configuration: {
|
||||
url: gitlab_address,
|
||||
access_token: access_token
|
||||
},
|
||||
entities: [
|
||||
{
|
||||
source_type: 'group_entity',
|
||||
source_full_path: source_group_path,
|
||||
destination_name: destination_group_path,
|
||||
destination_namespace: sandbox.path
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def import_status
|
||||
response = get(Runtime::API::Request.new(api_client, "/bulk_imports/#{import_id}").url)
|
||||
|
||||
unless response.code == HTTP_STATUS_OK
|
||||
raise ResourceQueryError, "Could not get import status. Request returned (#{response.code}): `#{response}`."
|
||||
end
|
||||
|
||||
parse_body(response)[:status]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,104 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Manage', :requires_admin do
|
||||
describe 'Bulk group import via api' do
|
||||
let!(:staging?) { Runtime::Scenario.gitlab_address.include?('staging.gitlab.com') }
|
||||
|
||||
let(:admin_api_client) { Runtime::API::Client.as_admin }
|
||||
let(:user) do
|
||||
Resource::User.fabricate_via_api! do |usr|
|
||||
usr.api_client = admin_api_client
|
||||
usr.hard_delete_on_api_removal = true
|
||||
end
|
||||
end
|
||||
|
||||
let(:api_client) { Runtime::API::Client.new(user: user) }
|
||||
let(:personal_access_token) { api_client.personal_access_token }
|
||||
|
||||
let(:sandbox) do
|
||||
Resource::Sandbox.fabricate_via_api! do |group|
|
||||
group.api_client = admin_api_client
|
||||
end
|
||||
end
|
||||
|
||||
let(:source_group) do
|
||||
Resource::Sandbox.fabricate_via_api! do |group|
|
||||
group.api_client = api_client
|
||||
group.path = "source-group-for-import-#{SecureRandom.hex(4)}"
|
||||
end
|
||||
end
|
||||
|
||||
let(:subgroup) do
|
||||
Resource::Group.fabricate_via_api! do |group|
|
||||
group.api_client = api_client
|
||||
group.sandbox = source_group
|
||||
group.path = "subgroup-for-import-#{SecureRandom.hex(4)}"
|
||||
end
|
||||
end
|
||||
|
||||
let(:imported_subgroup) do
|
||||
Resource::Group.init do |group|
|
||||
group.api_client = api_client
|
||||
group.sandbox = imported_group
|
||||
group.path = subgroup.path
|
||||
end
|
||||
end
|
||||
|
||||
let(:imported_group) do
|
||||
Resource::BulkImportGroup.fabricate_via_api! do |group|
|
||||
group.api_client = api_client
|
||||
group.sandbox = sandbox
|
||||
group.source_group_path = source_group.path
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
Runtime::Feature.enable(:bulk_import) unless staging?
|
||||
Runtime::Feature.enable(:top_level_group_creation_enabled) if staging?
|
||||
|
||||
sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
|
||||
|
||||
Resource::GroupLabel.fabricate_via_api! do |label|
|
||||
label.api_client = api_client
|
||||
label.group = source_group
|
||||
label.title = "source-group-#{SecureRandom.hex(4)}"
|
||||
end
|
||||
Resource::GroupLabel.fabricate_via_api! do |label|
|
||||
label.api_client = api_client
|
||||
label.group = subgroup
|
||||
label.title = "subgroup-#{SecureRandom.hex(4)}"
|
||||
end
|
||||
end
|
||||
|
||||
# Non blocking issues:
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/331252
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/333678 <- can cause 500 when creating user and group back to back
|
||||
it(
|
||||
'imports group with subgroups and labels',
|
||||
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1871'
|
||||
) do
|
||||
Page::Group::BulkImport.perform do |import_page|
|
||||
imported_group
|
||||
|
||||
expect { imported_group.import_status }.to eventually_eq('finished').within(duration: 300, interval: 2)
|
||||
|
||||
aggregate_failures do
|
||||
expect(imported_group.reload!).to eq(source_group)
|
||||
expect(imported_group.labels).to include(*source_group.labels)
|
||||
|
||||
expect(imported_subgroup.reload!).to eq(subgroup)
|
||||
expect(imported_subgroup.labels).to include(*subgroup.labels)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
user.remove_via_api!
|
||||
ensure
|
||||
Runtime::Feature.disable(:bulk_import) unless staging?
|
||||
Runtime::Feature.disable(:top_level_group_creation_enabled) if staging?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -29,27 +29,11 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
let(:subgroup) do
|
||||
Resource::Group.fabricate_via_api! do |group|
|
||||
group.api_client = api_client
|
||||
group.sandbox = source_group
|
||||
group.path = "subgroup-for-import-#{SecureRandom.hex(4)}"
|
||||
end
|
||||
end
|
||||
|
||||
let(:imported_group) do
|
||||
Resource::Group.init do |group|
|
||||
Resource::BulkImportGroup.init do |group|
|
||||
group.api_client = api_client
|
||||
group.sandbox = sandbox
|
||||
group.path = source_group.path
|
||||
end
|
||||
end
|
||||
|
||||
let(:imported_subgroup) do
|
||||
Resource::Group.init do |group|
|
||||
group.api_client = api_client
|
||||
group.sandbox = imported_group
|
||||
group.path = subgroup.path
|
||||
group.source_group_path = source_group.path
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -61,7 +45,6 @@ module QA
|
|||
|
||||
# create groups explicitly before connecting gitlab instance
|
||||
source_group
|
||||
subgroup
|
||||
|
||||
Flow::Login.sign_in(as: user)
|
||||
Page::Main::Menu.perform(&:go_to_create_group)
|
||||
|
@ -78,29 +61,14 @@ module QA
|
|||
'imports group with subgroups and labels',
|
||||
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1785'
|
||||
) do
|
||||
Resource::GroupLabel.fabricate_via_api! do |label|
|
||||
label.api_client = api_client
|
||||
label.group = source_group
|
||||
label.title = "source-group-#{SecureRandom.hex(4)}"
|
||||
end
|
||||
Resource::GroupLabel.fabricate_via_api! do |label|
|
||||
label.api_client = api_client
|
||||
label.group = subgroup
|
||||
label.title = "subgroup-#{SecureRandom.hex(4)}"
|
||||
end
|
||||
|
||||
Page::Group::BulkImport.perform do |import_page|
|
||||
import_page.import_group(source_group.path, sandbox.path)
|
||||
import_page.import_group(imported_group.path, imported_group.sandbox.path)
|
||||
|
||||
expect(import_page).to have_imported_group(source_group.path, wait: 300)
|
||||
expect(import_page).to have_imported_group(imported_group.path, wait: 300)
|
||||
|
||||
aggregate_failures do
|
||||
expect { imported_group.reload! }.to eventually_eq(source_group).within(duration: 10)
|
||||
expect { imported_group.labels }.to eventually_include(*source_group.labels).within(duration: 10)
|
||||
|
||||
# Do not validate subgroups until https://gitlab.com/gitlab-org/gitlab/-/issues/332818 is resolved
|
||||
# expect { imported_subgroup.reload! }.to eventually_eq(subgroup).within(duration: 30)
|
||||
# expect { imported_subgroup.labels }.to eventually_include(*subgroup.labels).within(duration: 30)
|
||||
imported_group.reload!.visit!
|
||||
Page::Group::Show.perform do |group|
|
||||
expect(group).to have_content(imported_group.path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -170,16 +170,16 @@ RSpec.describe QA::Resource::Base do
|
|||
let(:api_resource) { { test: 'api_with_block' } }
|
||||
|
||||
before do
|
||||
allow(QA::Runtime::Logger).to receive(:info)
|
||||
allow(QA::Runtime::Logger).to receive(:debug)
|
||||
end
|
||||
|
||||
it 'returns value from api and emits an INFO log entry' do
|
||||
it 'returns value from api and emits an debug log entry' do
|
||||
result = subject.fabricate!(resource: resource)
|
||||
|
||||
expect(result).to be_a(described_class)
|
||||
expect(result.test).to eq('api_with_block')
|
||||
expect(QA::Runtime::Logger)
|
||||
.to have_received(:info).with(/api_with_block/)
|
||||
.to have_received(:debug).with(/api_with_block/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe SpammableActions::AkismetMarkAsSpamAction do
|
||||
include AfterNextHelpers
|
||||
|
||||
controller(ActionController::Base) do
|
||||
include SpammableActions::AkismetMarkAsSpamAction
|
||||
|
||||
private
|
||||
|
||||
def spammable_path
|
||||
'/fake_spammable_path'
|
||||
end
|
||||
end
|
||||
|
||||
let(:spammable_type) { 'SpammableType' }
|
||||
let(:spammable) { double(:spammable, spammable_entity_type: double(:spammable_entity_type, titlecase: spammable_type)) }
|
||||
let(:current_user) { create(:admin) }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Recaptcha).to receive(:load_configurations!) { true }
|
||||
routes.draw { get 'mark_as_spam' => 'anonymous#mark_as_spam' }
|
||||
allow(controller).to receive(:spammable) { spammable }
|
||||
allow(controller).to receive(:current_user) { double(:current_user, admin?: admin) }
|
||||
allow(controller).to receive(:current_user).and_return(current_user)
|
||||
end
|
||||
|
||||
describe '#mark_as_spam' do
|
||||
subject { post :mark_as_spam }
|
||||
|
||||
before do
|
||||
expect_next(Spam::AkismetMarkAsSpamService, target: spammable)
|
||||
.to receive(:execute).and_return(execute_result)
|
||||
end
|
||||
|
||||
context 'when user is admin', :enable_admin_mode do
|
||||
let(:admin) { true }
|
||||
|
||||
context 'when service returns truthy' do
|
||||
let(:execute_result) { true }
|
||||
|
||||
it 'redirects with notice' do
|
||||
expect(subject).to redirect_to('/fake_spammable_path')
|
||||
expect(subject.request.flash[:notice]).to match(/#{spammable_type}.*submitted.*successfully/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when service returns falsey' do
|
||||
let(:execute_result) { false }
|
||||
|
||||
it 'redirects with notice' do
|
||||
expect(subject).to redirect_to('/fake_spammable_path')
|
||||
expect(subject.request.flash[:alert]).to match(/Error/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not admin' do
|
||||
let(:admin) { false }
|
||||
let(:execute_result) { true }
|
||||
|
||||
it 'calls #access_denied!' do
|
||||
expect(controller).to receive(:access_denied!) { false }
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,74 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe SpammableActions::CaptchaCheck::HtmlFormatActionsSupport do
|
||||
controller(ActionController::Base) do
|
||||
include SpammableActions::CaptchaCheck::HtmlFormatActionsSupport
|
||||
|
||||
def create
|
||||
with_captcha_check_html_format { render :some_rendered_view }
|
||||
end
|
||||
end
|
||||
|
||||
let(:spammable) { double(:spammable) }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Recaptcha).to receive(:load_configurations!) { true }
|
||||
routes.draw { get 'create' => 'anonymous#create' }
|
||||
allow(controller).to receive(:spammable) { spammable }
|
||||
expect(spammable).to receive(:render_recaptcha?).at_least(:once) { render_recaptcha }
|
||||
end
|
||||
|
||||
describe '#convert_html_spam_params_to_headers' do
|
||||
let(:render_recaptcha) { false }
|
||||
let(:g_recaptcha_response) { 'abc123' }
|
||||
let(:spam_log_id) { 42 }
|
||||
|
||||
let(:params) do
|
||||
{
|
||||
'g-recaptcha-response' => g_recaptcha_response,
|
||||
spam_log_id: spam_log_id
|
||||
}
|
||||
end
|
||||
|
||||
# NOTE: `:update` has an identical `before_action` behavior to ``:create``, but `before_action` is
|
||||
# declarative via the ``:only`` attribute, so there's little value in re-testing the behavior.
|
||||
subject { post :create, params: params }
|
||||
|
||||
before do
|
||||
allow(controller).to receive(:render).with(:some_rendered_view)
|
||||
end
|
||||
|
||||
it 'converts params to headers' do
|
||||
subject
|
||||
|
||||
expect(controller.request.headers['X-GitLab-Captcha-Response']).to eq(g_recaptcha_response)
|
||||
expect(controller.request.headers['X-GitLab-Spam-Log-Id']).to eq(spam_log_id.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#with_captcha_check_html_format' do
|
||||
subject { post :create }
|
||||
|
||||
context 'when spammable.render_recaptcha? is true' do
|
||||
let(:render_recaptcha) { true }
|
||||
|
||||
it 'renders :verify' do
|
||||
expect(controller).to receive(:render).with(:verify)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when spammable.render_recaptcha? is false' do
|
||||
let(:render_recaptcha) { false }
|
||||
|
||||
it 'yields to block' do
|
||||
expect(controller).to receive(:render).with(:some_rendered_view)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,60 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe SpammableActions::CaptchaCheck::JsonFormatActionsSupport do
|
||||
controller(ActionController::Base) do
|
||||
include SpammableActions::CaptchaCheck::JsonFormatActionsSupport
|
||||
|
||||
def some_action
|
||||
with_captcha_check_json_format { render :some_rendered_view }
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Gitlab::Recaptcha).to receive(:load_configurations!) { true }
|
||||
end
|
||||
|
||||
describe '#with_captcha_check_json_format' do
|
||||
subject { post :some_action }
|
||||
|
||||
let(:spammable) { double(:spammable) }
|
||||
|
||||
before do
|
||||
routes.draw { get 'some_action' => 'anonymous#some_action' }
|
||||
allow(controller).to receive(:spammable) { spammable }
|
||||
expect(spammable).to receive(:render_recaptcha?).at_least(:once) { render_recaptcha }
|
||||
end
|
||||
|
||||
context 'when spammable.render_recaptcha? is true' do
|
||||
let(:render_recaptcha) { true }
|
||||
let(:spam_log) { double(:spam_log, id: 1) }
|
||||
let(:spammable) { double(:spammable, spam?: true, render_recaptcha?: render_recaptcha, spam_log: spam_log) }
|
||||
let(:recaptcha_site_key) { 'abc123' }
|
||||
let(:spam_action_response_fields) do
|
||||
{
|
||||
spam: true,
|
||||
needs_captcha_response: render_recaptcha,
|
||||
spam_log_id: 1,
|
||||
captcha_site_key: recaptcha_site_key
|
||||
}
|
||||
end
|
||||
|
||||
it 'renders json containing spam_action_response_fields' do
|
||||
expect(controller).to receive(:render).with(json: spam_action_response_fields, status: :conflict)
|
||||
allow(Gitlab::CurrentSettings).to receive(:recaptcha_site_key) { recaptcha_site_key }
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when spammable.render_recaptcha? is false' do
|
||||
let(:render_recaptcha) { false }
|
||||
|
||||
it 'yields to block' do
|
||||
expect(controller).to receive(:render).with(:some_rendered_view)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,112 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe SpammableActions do
|
||||
controller(ActionController::Base) do
|
||||
include SpammableActions
|
||||
|
||||
# #update is used here to test #recaptcha_check_with_fallback, but it could be invoked
|
||||
# from #create or any other action which mutates a spammable via a controller.
|
||||
def update
|
||||
should_redirect = params[:should_redirect] == 'true'
|
||||
|
||||
recaptcha_check_with_fallback(should_redirect) { render json: :ok }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def spammable_path
|
||||
'/fake_spammable_path'
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Gitlab::Recaptcha).to receive(:load_configurations!) { true }
|
||||
end
|
||||
|
||||
describe '#recaptcha_check_with_fallback' do
|
||||
shared_examples 'yields to block' do
|
||||
it do
|
||||
subject
|
||||
|
||||
expect(json_response).to eq({ json: 'ok' })
|
||||
end
|
||||
end
|
||||
|
||||
let(:format) { :html }
|
||||
|
||||
subject { post :update, format: format, params: params }
|
||||
|
||||
let(:spammable) { double(:spammable) }
|
||||
let(:should_redirect) { nil }
|
||||
let(:params) do
|
||||
{
|
||||
should_redirect: should_redirect
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
routes.draw { get 'update' => 'anonymous#update' }
|
||||
allow(controller).to receive(:spammable) { spammable }
|
||||
end
|
||||
|
||||
context 'when should_redirect is true and spammable is valid' do
|
||||
let(:should_redirect) { true }
|
||||
|
||||
before do
|
||||
allow(spammable).to receive(:valid?) { true }
|
||||
end
|
||||
|
||||
it 'redirects to spammable_path' do
|
||||
expect(subject).to redirect_to('/fake_spammable_path')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when should_redirect is false or spammable is not valid' do
|
||||
before do
|
||||
allow(spammable).to receive(:valid?) { false }
|
||||
end
|
||||
|
||||
context 'when spammable.render_recaptcha? is true' do
|
||||
let(:spam_log) { instance_double(SpamLog, id: 123) }
|
||||
let(:captcha_site_key) { 'abc123' }
|
||||
|
||||
before do
|
||||
expect(spammable).to receive(:render_recaptcha?).at_least(:once) { true }
|
||||
end
|
||||
|
||||
context 'when format is :html' do
|
||||
it 'renders :verify' do
|
||||
expect(controller).to receive(:render).with(:verify)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when format is :json' do
|
||||
let(:format) { :json }
|
||||
|
||||
before do
|
||||
expect(spammable).to receive(:spam?) { false }
|
||||
expect(spammable).to receive(:spam_log) { spam_log }
|
||||
expect(Gitlab::CurrentSettings).to receive(:recaptcha_site_key) { captcha_site_key }
|
||||
end
|
||||
|
||||
it 'renders json with spam_action_response_fields' do
|
||||
subject
|
||||
|
||||
expected_json_response = HashWithIndifferentAccess.new(
|
||||
{
|
||||
spam: false,
|
||||
needs_captcha_response: true,
|
||||
spam_log_id: spam_log.id,
|
||||
captcha_site_key: captcha_site_key
|
||||
})
|
||||
expect(json_response).to eq(expected_json_response)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1464,7 +1464,7 @@ RSpec.describe Projects::IssuesController do
|
|||
}
|
||||
end
|
||||
|
||||
it 'updates issue' do
|
||||
it 'updates issue', :enable_admin_mode do
|
||||
post_spam
|
||||
expect(issue.submittable_as_spam?).to be_falsey
|
||||
end
|
||||
|
|
|
@ -110,7 +110,7 @@ RSpec.describe Projects::SnippetsController do
|
|||
}
|
||||
end
|
||||
|
||||
it 'updates the snippet' do
|
||||
it 'updates the snippet', :enable_admin_mode do
|
||||
mark_as_spam
|
||||
|
||||
expect(snippet.reload).not_to be_submittable_as_spam
|
||||
|
|
|
@ -231,7 +231,7 @@ RSpec.describe SnippetsController do
|
|||
post :mark_as_spam, params: { id: public_snippet.id }
|
||||
end
|
||||
|
||||
it 'updates the snippet' do
|
||||
it 'updates the snippet', :enable_admin_mode do
|
||||
mark_as_spam
|
||||
|
||||
expect(public_snippet.reload).not_to be_submittable_as_spam
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -9,7 +9,7 @@ import { issuableTypes } from '~/boards/constants';
|
|||
import eventHub from '~/boards/eventhub';
|
||||
import defaultStore from '~/boards/stores';
|
||||
import { updateHistory } from '~/lib/utils/url_utility';
|
||||
import { mockLabelList, mockIssue } from './mock_data';
|
||||
import { mockLabelList, mockIssue, mockIssueFullPath } from './mock_data';
|
||||
|
||||
jest.mock('~/lib/utils/url_utility');
|
||||
jest.mock('~/boards/eventhub');
|
||||
|
@ -45,7 +45,7 @@ describe('Board card component', () => {
|
|||
const findEpicCountablesTotalWeight = () => wrapper.findByTestId('epic-countables-total-weight');
|
||||
const findEpicProgressTooltip = () => wrapper.findByTestId('epic-progress-tooltip-content');
|
||||
|
||||
const createStore = ({ isEpicBoard = false } = {}) => {
|
||||
const createStore = ({ isEpicBoard = false, isProjectBoard = false } = {}) => {
|
||||
store = new Vuex.Store({
|
||||
...defaultStore,
|
||||
state: {
|
||||
|
@ -55,7 +55,7 @@ describe('Board card component', () => {
|
|||
getters: {
|
||||
isGroupBoard: () => true,
|
||||
isEpicBoard: () => isEpicBoard,
|
||||
isProjectBoard: () => false,
|
||||
isProjectBoard: () => isProjectBoard,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -134,6 +134,17 @@ describe('Board card component', () => {
|
|||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not render item reference path', () => {
|
||||
createStore({ isProjectBoard: true });
|
||||
createWrapper();
|
||||
|
||||
expect(wrapper.find('.board-card-number').text()).not.toContain(mockIssueFullPath);
|
||||
});
|
||||
|
||||
it('renders item reference path', () => {
|
||||
expect(wrapper.find('.board-card-number').text()).toContain(mockIssueFullPath);
|
||||
});
|
||||
|
||||
describe('blocked', () => {
|
||||
it('renders blocked icon if issue is blocked', async () => {
|
||||
createWrapper({
|
||||
|
|
|
@ -31,6 +31,7 @@ describe('Board card', () => {
|
|||
actions: mockActions,
|
||||
getters: {
|
||||
isEpicBoard: () => false,
|
||||
isProjectBoard: () => false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -58,7 +58,7 @@ RSpec.describe 'Mark snippet as spam' do
|
|||
end
|
||||
|
||||
it 'marks snippet as spam' do
|
||||
expect_next(Spam::MarkAsSpamService, target: snippet)
|
||||
expect_next(Spam::AkismetMarkAsSpamService, target: snippet)
|
||||
.to receive(:execute).and_return(true)
|
||||
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Spam::MarkAsSpamService do
|
||||
RSpec.describe Spam::AkismetMarkAsSpamService do
|
||||
let(:user_agent_detail) { build(:user_agent_detail) }
|
||||
let(:spammable) { build(:issue, user_agent_detail: user_agent_detail) }
|
||||
let(:fake_akismet_service) { double(:akismet_service, submit_spam: true) }
|
Loading…
Reference in New Issue