Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
eef37e3313
commit
99373ac711
|
@ -12,7 +12,7 @@ Set the title to: `Description of the original issue`
|
|||
- [ ] Make sure the issue really needs to follow the security release workflow.
|
||||
- Verify if the issue you're working on `gitlab-org/gitlab` is confidential, if it's public fix should be placed on GitLab canonical and no backports are required.
|
||||
- If the issue you're fixing doesn't appear to be something that can be exploited by a malicious person and is instead simply a security enhancement do not hesitate to ping `@gitlab-com/gl-security/appsec` to discuss if the issue can be fixed in the canonical repository.
|
||||
- [ ] **IMPORTANT**: Mark this [issue as linked] to the Security Release Tracking Issue. You can find it on the topic of the `#releases` Slack channel. This issue
|
||||
- [ ] **IMPORTANT**: Mark this [issue as linked] to the Security Release Tracking Issue. You can find it [here](https://gitlab.com/gitlab-org/gitlab/-/issues?sort=created_date&state=opened&label_name[]=upcoming+security+release). This issue
|
||||
MUST be linked for the release bot to know that the associated merge requests should be merged for this security release.
|
||||
- Fill out the [Links section](#links):
|
||||
- [ ] Next to **Issue on GitLab**, add a link to the `gitlab-org/gitlab` issue that describes the security vulnerability.
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<script>
|
||||
import { GlButton, GlModalDirective } from '@gitlab/ui';
|
||||
import { ADD_NAMESPACE_MODAL_ID } from '../constants';
|
||||
import AddNamespaceModal from './add_namespace_modal/add_namespace_modal.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlButton,
|
||||
AddNamespaceModal,
|
||||
},
|
||||
directives: {
|
||||
GlModal: GlModalDirective,
|
||||
},
|
||||
ADD_NAMESPACE_MODAL_ID,
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<gl-button v-gl-modal="$options.ADD_NAMESPACE_MODAL_ID" category="primary" variant="info">
|
||||
{{ s__('Integrations|Add namespace') }}
|
||||
</gl-button>
|
||||
<add-namespace-modal />
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,26 @@
|
|||
<script>
|
||||
import { GlModal } from '@gitlab/ui';
|
||||
import { __, s__ } from '~/locale';
|
||||
import { ADD_NAMESPACE_MODAL_ID } from '../../constants';
|
||||
import GroupsList from './groups_list.vue';
|
||||
|
||||
export default {
|
||||
components: { GlModal, GroupsList },
|
||||
modal: {
|
||||
id: ADD_NAMESPACE_MODAL_ID,
|
||||
title: s__('Integrations|Link namespaces'),
|
||||
cancelProps: {
|
||||
text: __('Cancel'),
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-modal
|
||||
:modal-id="$options.modal.id"
|
||||
:title="$options.modal.title"
|
||||
:action-cancel="$options.modal.cancelProps"
|
||||
>
|
||||
<groups-list />
|
||||
</gl-modal>
|
||||
</template>
|
|
@ -4,7 +4,7 @@ import { helpPagePath } from '~/helpers/help_page_helper';
|
|||
import { addSubscription } from '~/jira_connect/subscriptions/api';
|
||||
import { persistAlert, reloadPage } from '~/jira_connect/subscriptions/utils';
|
||||
import { s__ } from '~/locale';
|
||||
import GroupItemName from './group_item_name.vue';
|
||||
import GroupItemName from '../group_item_name.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
|
@ -1,11 +1,10 @@
|
|||
<script>
|
||||
import { GlAlert, GlButton, GlLink, GlModal, GlModalDirective, GlSprintf } from '@gitlab/ui';
|
||||
import { GlAlert, GlButton, GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import { mapState, mapMutations } from 'vuex';
|
||||
import { retrieveAlert, getLocation } from '~/jira_connect/subscriptions/utils';
|
||||
import { __ } from '~/locale';
|
||||
import { SET_ALERT } from '../store/mutation_types';
|
||||
import GroupsList from './groups_list.vue';
|
||||
import SubscriptionsList from './subscriptions_list.vue';
|
||||
import AddNamespaceButton from './add_namespace_button.vue';
|
||||
|
||||
export default {
|
||||
name: 'JiraConnectApp',
|
||||
|
@ -13,13 +12,9 @@ export default {
|
|||
GlAlert,
|
||||
GlButton,
|
||||
GlLink,
|
||||
GlModal,
|
||||
GlSprintf,
|
||||
GroupsList,
|
||||
SubscriptionsList,
|
||||
},
|
||||
directives: {
|
||||
GlModalDirective,
|
||||
AddNamespaceButton,
|
||||
},
|
||||
inject: {
|
||||
usersPath: {
|
||||
|
@ -44,11 +39,6 @@ export default {
|
|||
return Boolean(this.alert?.message);
|
||||
},
|
||||
},
|
||||
modal: {
|
||||
cancelProps: {
|
||||
text: __('Cancel'),
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.setInitialAlert();
|
||||
this.setLocation();
|
||||
|
@ -101,22 +91,7 @@ export default {
|
|||
target="_blank"
|
||||
>{{ s__('Integrations|Sign in to add namespaces') }}</gl-button
|
||||
>
|
||||
<template v-else>
|
||||
<gl-button
|
||||
v-gl-modal-directive="'add-namespace-modal'"
|
||||
category="primary"
|
||||
variant="info"
|
||||
class="gl-align-self-center"
|
||||
>{{ s__('Integrations|Add namespace') }}</gl-button
|
||||
>
|
||||
<gl-modal
|
||||
modal-id="add-namespace-modal"
|
||||
:title="s__('Integrations|Link namespaces')"
|
||||
:action-cancel="$options.modal.cancelProps"
|
||||
>
|
||||
<groups-list />
|
||||
</gl-modal>
|
||||
</template>
|
||||
<add-namespace-button v-else />
|
||||
</div>
|
||||
|
||||
<subscriptions-list />
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
export const DEFAULT_GROUPS_PER_PAGE = 10;
|
||||
export const ALERT_LOCALSTORAGE_KEY = 'gitlab_alert';
|
||||
export const MINIMUM_SEARCH_TERM_LENGTH = 3;
|
||||
|
||||
export const ADD_NAMESPACE_MODAL_ID = 'add-namespace-modal';
|
||||
|
|
|
@ -15,6 +15,7 @@ class Admin::ApplicationsController < Admin::ApplicationController
|
|||
end
|
||||
|
||||
def show
|
||||
@created = get_created_session
|
||||
end
|
||||
|
||||
def new
|
||||
|
@ -33,6 +34,8 @@ class Admin::ApplicationsController < Admin::ApplicationController
|
|||
if @application.persisted?
|
||||
flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
|
||||
|
||||
set_created_session
|
||||
|
||||
redirect_to admin_application_url(@application)
|
||||
else
|
||||
render :new
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
module OauthApplications
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
CREATED_SESSION_KEY = :oauth_applications_created
|
||||
|
||||
included do
|
||||
before_action :prepare_scopes, only: [:create, :update]
|
||||
end
|
||||
|
@ -15,6 +17,14 @@ module OauthApplications
|
|||
end
|
||||
end
|
||||
|
||||
def set_created_session
|
||||
session[CREATED_SESSION_KEY] = true
|
||||
end
|
||||
|
||||
def get_created_session
|
||||
session.delete(CREATED_SESSION_KEY) || false
|
||||
end
|
||||
|
||||
def load_scopes
|
||||
@scopes ||= Doorkeeper.configuration.scopes
|
||||
end
|
||||
|
|
|
@ -16,6 +16,7 @@ module Groups
|
|||
end
|
||||
|
||||
def show
|
||||
@created = get_created_session
|
||||
end
|
||||
|
||||
def edit
|
||||
|
@ -27,6 +28,8 @@ module Groups
|
|||
if @application.persisted?
|
||||
flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
|
||||
|
||||
set_created_session
|
||||
|
||||
redirect_to group_settings_application_url(@group, @application)
|
||||
else
|
||||
set_index_vars
|
||||
|
|
|
@ -24,12 +24,18 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
|
|||
set_index_vars
|
||||
end
|
||||
|
||||
def show
|
||||
@created = get_created_session
|
||||
end
|
||||
|
||||
def create
|
||||
@application = Applications::CreateService.new(current_user, application_params).execute(request)
|
||||
|
||||
if @application.persisted?
|
||||
flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
|
||||
|
||||
set_created_session
|
||||
|
||||
redirect_to oauth_application_url(@application)
|
||||
else
|
||||
set_index_vars
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Import
|
||||
module Github
|
||||
module Notes
|
||||
class CreateService < ::Notes::CreateService
|
||||
# Github does not have support to quick actions in notes (like /assign)
|
||||
# Therefore, when importing notes we skip the quick actions processing
|
||||
def quick_actions_supported?(_note)
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -43,7 +43,7 @@ module Notes
|
|||
private
|
||||
|
||||
def execute_quick_actions(note)
|
||||
return yield(false) unless quick_actions_service.supported?(note)
|
||||
return yield(false) unless quick_actions_supported?(note)
|
||||
|
||||
content, update_params, message = quick_actions_service.execute(note, quick_action_options)
|
||||
only_commands = content.empty?
|
||||
|
@ -54,6 +54,10 @@ module Notes
|
|||
do_commands(note, update_params, message, only_commands)
|
||||
end
|
||||
|
||||
def quick_actions_supported?(note)
|
||||
quick_actions_service.supported?(note)
|
||||
end
|
||||
|
||||
def quick_actions_service
|
||||
@quick_actions_service ||= QuickActionsService.new(project, current_user)
|
||||
end
|
||||
|
|
|
@ -6,4 +6,5 @@
|
|||
= render 'shared/doorkeeper/applications/show',
|
||||
edit_path: edit_admin_application_path(@application),
|
||||
delete_path: admin_application_path(@application),
|
||||
index_path: admin_applications_path,
|
||||
show_trusted_row: true
|
||||
|
|
|
@ -6,4 +6,7 @@
|
|||
%h3.page-title
|
||||
= _("Application: %{name}") % { name: @application.name }
|
||||
|
||||
= render 'shared/doorkeeper/applications/show', edit_path: edit_oauth_application_path(@application), delete_path: oauth_application_path(@application)
|
||||
= render 'shared/doorkeeper/applications/show',
|
||||
edit_path: edit_oauth_application_path(@application),
|
||||
delete_path: oauth_application_path(@application),
|
||||
index_path: oauth_applications_path
|
||||
|
|
|
@ -6,4 +6,7 @@
|
|||
%h3.page-title
|
||||
= _("Group application: %{name}") % { name: @application.name }
|
||||
|
||||
= render 'shared/doorkeeper/applications/show', edit_path: edit_group_settings_application_path(@group, @application), delete_path: group_settings_application_path(@group, @application)
|
||||
= render 'shared/doorkeeper/applications/show',
|
||||
edit_path: edit_group_settings_application_path(@group, @application),
|
||||
delete_path: group_settings_application_path(@group, @application),
|
||||
index_path: group_settings_applications_path
|
||||
|
|
|
@ -39,6 +39,9 @@
|
|||
|
||||
= render "shared/tokens/scopes_list", token: @application
|
||||
|
||||
.form-actions
|
||||
= link_to _('Edit'), edit_path, class: 'gl-button btn btn-confirm wide float-left'
|
||||
= render 'shared/doorkeeper/applications/delete_form', path: delete_path, submit_btn_css: 'gl-button btn btn-danger gl-ml-3'
|
||||
.form-actions.gl-display-flex.gl-justify-content-space-between
|
||||
%div
|
||||
- if @created
|
||||
= link_to _('Continue'), index_path, class: 'btn btn-confirm btn-md gl-button gl-mr-3'
|
||||
= link_to _('Edit'), edit_path, class: 'btn btn-default btn-md gl-button'
|
||||
= render 'shared/doorkeeper/applications/delete_form', path: delete_path, submit_btn_css: 'btn btn-danger btn-md gl-button btn-danger-secondary'
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: github_importer_use_diff_note_with_suggestions
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71765
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344309
|
||||
milestone: '14.5'
|
||||
type: development
|
||||
group: group::import
|
||||
default_enabled: false
|
|
@ -4,41 +4,61 @@ module Gitlab
|
|||
module GithubImport
|
||||
module Importer
|
||||
class DiffNoteImporter
|
||||
attr_reader :note, :project, :client, :user_finder
|
||||
|
||||
# note - An instance of `Gitlab::GithubImport::Representation::DiffNote`.
|
||||
# project - An instance of `Project`.
|
||||
# client - An instance of `Gitlab::GithubImport::Client`.
|
||||
# note - An instance of `Gitlab::GithubImport::Representation::DiffNote`
|
||||
# project - An instance of `Project`
|
||||
# client - An instance of `Gitlab::GithubImport::Client`
|
||||
def initialize(note, project, client)
|
||||
@note = note
|
||||
@project = project
|
||||
@client = client
|
||||
@user_finder = GithubImport::UserFinder.new(project, client)
|
||||
end
|
||||
|
||||
def execute
|
||||
return unless (mr_id = find_merge_request_id)
|
||||
return if merge_request_id.blank?
|
||||
|
||||
author_id, author_found = user_finder.author_id_for(note)
|
||||
build_author_attributes
|
||||
|
||||
note_body = MarkdownText.format(note.note, note.author, author_found)
|
||||
# Diff notes with suggestions are imported with DiffNote, which is
|
||||
# slower to import than LegacyDiffNote. Importing DiffNote is slower
|
||||
# because it cannot use the BulkImporting strategy, which skips
|
||||
# callbacks and validations. For this reason, notes that don't have
|
||||
# suggestions are still imported with LegacyDiffNote
|
||||
if import_with_diff_note?
|
||||
import_with_diff_note
|
||||
else
|
||||
import_with_legacy_diff_note
|
||||
end
|
||||
rescue ActiveRecord::InvalidForeignKey => e
|
||||
# It's possible the project and the issue have been deleted since
|
||||
# scheduling this job. In this case we'll just skip creating the note
|
||||
Logger.info(
|
||||
message: e.message,
|
||||
github_identifiers: note.github_identifiers
|
||||
)
|
||||
end
|
||||
|
||||
attributes = {
|
||||
discussion_id: Discussion.discussion_id(note),
|
||||
noteable_type: 'MergeRequest',
|
||||
noteable_id: mr_id,
|
||||
project_id: project.id,
|
||||
author_id: author_id,
|
||||
note: note_body,
|
||||
system: false,
|
||||
commit_id: note.original_commit_id,
|
||||
line_code: note.line_code,
|
||||
type: 'LegacyDiffNote',
|
||||
created_at: note.created_at,
|
||||
updated_at: note.updated_at,
|
||||
st_diff: note.diff_hash.to_yaml
|
||||
}
|
||||
private
|
||||
|
||||
attr_reader :note, :project, :client, :author_id, :author_found
|
||||
|
||||
def import_with_diff_note?
|
||||
note.contains_suggestion? && use_diff_note_with_suggestions_enabled?
|
||||
end
|
||||
|
||||
def use_diff_note_with_suggestions_enabled?
|
||||
Feature.enabled?(
|
||||
:github_importer_use_diff_note_with_suggestions,
|
||||
default_enabled: :yaml
|
||||
)
|
||||
end
|
||||
|
||||
def build_author_attributes
|
||||
@author_id, @author_found = user_finder.author_id_for(note)
|
||||
end
|
||||
|
||||
# rubocop:disable Gitlab/BulkInsert
|
||||
def import_with_legacy_diff_note
|
||||
log_diff_note_creation('LegacyDiffNote')
|
||||
# It's possible that during an import we'll insert tens of thousands
|
||||
# of diff notes. If we were to use the Note/LegacyDiffNote model here
|
||||
# we'd also have to run additional queries for both validations and
|
||||
|
@ -47,15 +67,69 @@ module Gitlab
|
|||
# To work around this we're using bulk_insert with a single row. This
|
||||
# allows us to efficiently insert data (even if it's just 1 row)
|
||||
# without having to use all sorts of hacks to disable callbacks.
|
||||
Gitlab::Database.main.bulk_insert(LegacyDiffNote.table_name, [attributes]) # rubocop:disable Gitlab/BulkInsert
|
||||
rescue ActiveRecord::InvalidForeignKey
|
||||
# It's possible the project and the issue have been deleted since
|
||||
# scheduling this job. In this case we'll just skip creating the note.
|
||||
Gitlab::Database.main.bulk_insert(LegacyDiffNote.table_name, [{
|
||||
noteable_type: 'MergeRequest',
|
||||
system: false,
|
||||
type: 'LegacyDiffNote',
|
||||
discussion_id: Discussion.discussion_id(note),
|
||||
noteable_id: merge_request_id,
|
||||
project_id: project.id,
|
||||
author_id: author_id,
|
||||
note: note_body,
|
||||
commit_id: note.original_commit_id,
|
||||
line_code: note.line_code,
|
||||
created_at: note.created_at,
|
||||
updated_at: note.updated_at,
|
||||
st_diff: note.diff_hash.to_yaml
|
||||
}])
|
||||
end
|
||||
# rubocop:enabled Gitlab/BulkInsert
|
||||
|
||||
def import_with_diff_note
|
||||
log_diff_note_creation('DiffNote')
|
||||
|
||||
::Import::Github::Notes::CreateService.new(project, author, {
|
||||
noteable_type: 'MergeRequest',
|
||||
system: false,
|
||||
type: 'DiffNote',
|
||||
noteable_id: merge_request_id,
|
||||
project_id: project.id,
|
||||
note: note_body,
|
||||
commit_id: note.original_commit_id,
|
||||
created_at: note.created_at,
|
||||
updated_at: note.updated_at,
|
||||
position: note.diff_position(merge_request)
|
||||
}).execute
|
||||
end
|
||||
|
||||
def note_body
|
||||
@note_body ||= MarkdownText.format(note.note, note.author, author_found)
|
||||
end
|
||||
|
||||
def author
|
||||
@author ||= User.find(author_id)
|
||||
end
|
||||
|
||||
def merge_request
|
||||
@merge_request ||= MergeRequest.find(merge_request_id)
|
||||
end
|
||||
|
||||
# Returns the ID of the merge request this note belongs to.
|
||||
def find_merge_request_id
|
||||
GithubImport::IssuableFinder.new(project, note).database_id
|
||||
def merge_request_id
|
||||
@merge_request_id ||= GithubImport::IssuableFinder.new(project, note).database_id
|
||||
end
|
||||
|
||||
def user_finder
|
||||
@user_finder ||= GithubImport::UserFinder.new(project, client)
|
||||
end
|
||||
|
||||
def log_diff_note_creation(model)
|
||||
Logger.info(
|
||||
project_id: project.id,
|
||||
importer: self.class.name,
|
||||
github_identifiers: note.github_identifiers,
|
||||
model: model
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,8 +10,9 @@ module Gitlab
|
|||
attr_reader :attributes
|
||||
|
||||
expose_attribute :noteable_type, :noteable_id, :commit_id, :file_path,
|
||||
:diff_hunk, :author, :note, :created_at, :updated_at,
|
||||
:original_commit_id, :note_id
|
||||
:diff_hunk, :author, :created_at, :updated_at,
|
||||
:original_commit_id, :note_id, :end_line, :start_line,
|
||||
:side
|
||||
|
||||
NOTEABLE_ID_REGEX = %r{/pull/(?<iid>\d+)}i.freeze
|
||||
|
||||
|
@ -42,7 +43,8 @@ module Gitlab
|
|||
updated_at: note.updated_at,
|
||||
note_id: note.id,
|
||||
end_line: note.line,
|
||||
start_line: note.start_line
|
||||
start_line: note.start_line,
|
||||
side: note.side
|
||||
}
|
||||
|
||||
new(hash)
|
||||
|
@ -60,17 +62,31 @@ module Gitlab
|
|||
# Hash must be Symbols.
|
||||
def initialize(attributes)
|
||||
@attributes = attributes
|
||||
|
||||
@note_formatter = DiffNotes::SuggestionFormatter.new(
|
||||
note: attributes[:note],
|
||||
start_line: attributes[:start_line],
|
||||
end_line: attributes[:end_line]
|
||||
)
|
||||
end
|
||||
|
||||
def contains_suggestion?
|
||||
@note_formatter.contains_suggestion?
|
||||
end
|
||||
|
||||
def note
|
||||
@note_formatter.formatted_note
|
||||
end
|
||||
|
||||
def line_code
|
||||
diff_line = Gitlab::Diff::Parser.new.parse(diff_hunk.lines).to_a.last
|
||||
|
||||
Gitlab::Git
|
||||
.diff_line_code(file_path, diff_line.new_pos, diff_line.old_pos)
|
||||
Gitlab::Git.diff_line_code(file_path, diff_line.new_pos, diff_line.old_pos)
|
||||
end
|
||||
|
||||
# Returns a Hash that can be used to populate `notes.st_diff`, removing
|
||||
# the need for requesting Git data for every diff note.
|
||||
# Used when importing with LegacyDiffNote
|
||||
def diff_hash
|
||||
{
|
||||
diff: diff_hunk,
|
||||
|
@ -85,12 +101,15 @@ module Gitlab
|
|||
}
|
||||
end
|
||||
|
||||
def note
|
||||
@note ||= DiffNotes::SuggestionFormatter.formatted_note_for(
|
||||
note: attributes[:note],
|
||||
start_line: attributes[:start_line],
|
||||
end_line: attributes[:end_line]
|
||||
)
|
||||
# Used when importing with DiffNote
|
||||
def diff_position(merge_request)
|
||||
position_params = {
|
||||
diff_refs: merge_request.diff_refs,
|
||||
old_path: file_path,
|
||||
new_path: file_path
|
||||
}
|
||||
|
||||
Gitlab::Diff::Position.new(position_params.merge(diff_line_params))
|
||||
end
|
||||
|
||||
def github_identifiers
|
||||
|
@ -100,6 +119,20 @@ module Gitlab
|
|||
noteable_type: noteable_type
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def diff_line_params
|
||||
if addition?
|
||||
{ new_line: end_line, old_line: nil }
|
||||
else
|
||||
{ new_line: nil, old_line: end_line }
|
||||
end
|
||||
end
|
||||
|
||||
def addition?
|
||||
side == 'RIGHT'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,30 +10,38 @@ module Gitlab
|
|||
module Representation
|
||||
module DiffNotes
|
||||
class SuggestionFormatter
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
# A github suggestion:
|
||||
# - the ```suggestion tag must be the first text of the line
|
||||
# - it might have up to 3 spaces before the ```suggestion tag
|
||||
# - extra text on the ```suggestion tag line will be ignored
|
||||
GITHUB_SUGGESTION = /^\ {,3}(?<suggestion>```suggestion\b).*(?<eol>\R)/.freeze
|
||||
|
||||
def self.formatted_note_for(...)
|
||||
new(...).formatted_note
|
||||
end
|
||||
|
||||
def initialize(note:, start_line: nil, end_line: nil)
|
||||
@note = note
|
||||
@start_line = start_line
|
||||
@end_line = end_line
|
||||
end
|
||||
|
||||
# Returns a tuple with:
|
||||
# - a boolean indicating if the note has suggestions
|
||||
# - the note with the suggestion formatted for Gitlab
|
||||
def formatted_note
|
||||
if contains_suggestion?
|
||||
note.gsub(
|
||||
GITHUB_SUGGESTION,
|
||||
"\\k<suggestion>:#{suggestion_range}\\k<eol>"
|
||||
)
|
||||
else
|
||||
note
|
||||
@formatted_note ||=
|
||||
if contains_suggestion?
|
||||
note.gsub(
|
||||
GITHUB_SUGGESTION,
|
||||
"\\k<suggestion>:#{suggestion_range}\\k<eol>"
|
||||
)
|
||||
else
|
||||
note
|
||||
end
|
||||
end
|
||||
|
||||
def contains_suggestion?
|
||||
strong_memoize(:contain_suggestion) do
|
||||
note.to_s.match?(GITHUB_SUGGESTION)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -41,10 +49,6 @@ module Gitlab
|
|||
|
||||
attr_reader :note, :start_line, :end_line
|
||||
|
||||
def contains_suggestion?
|
||||
note.to_s.match?(GITHUB_SUGGESTION)
|
||||
end
|
||||
|
||||
# Github always saves the comment on the _last_ line of the range.
|
||||
# Therefore, the diff hunk will always be related to lines before
|
||||
# the comment itself.
|
||||
|
|
|
@ -5,6 +5,7 @@ require 'spec_helper'
|
|||
RSpec.describe 'admin manage applications' do
|
||||
let_it_be(:new_application_path) { new_admin_application_path }
|
||||
let_it_be(:applications_path) { admin_applications_path }
|
||||
let_it_be(:index_path) { admin_applications_path }
|
||||
|
||||
before do
|
||||
admin = create(:admin)
|
||||
|
|
|
@ -6,6 +6,7 @@ RSpec.describe 'User manages applications' do
|
|||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:new_application_path) { group_settings_applications_path(group) }
|
||||
let_it_be(:index_path) { group_settings_applications_path(group) }
|
||||
|
||||
before do
|
||||
group.add_owner(user)
|
||||
|
|
|
@ -5,6 +5,7 @@ require 'spec_helper'
|
|||
RSpec.describe 'User manages applications' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:new_application_path) { applications_profile_path }
|
||||
let_it_be(:index_path) { oauth_applications_path }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import { GlButton } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import AddNamespaceButton from '~/jira_connect/subscriptions/components/add_namespace_button.vue';
|
||||
import AddNamespaceModal from '~/jira_connect/subscriptions/components/add_namespace_modal/add_namespace_modal.vue';
|
||||
import { ADD_NAMESPACE_MODAL_ID } from '~/jira_connect/subscriptions/constants';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
|
||||
describe('AddNamespaceButton', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = shallowMount(AddNamespaceButton, {
|
||||
directives: {
|
||||
glModal: createMockDirective(),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findButton = () => wrapper.findComponent(GlButton);
|
||||
const findModal = () => wrapper.findComponent(AddNamespaceModal);
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('displays a button', () => {
|
||||
expect(findButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('contains a modal', () => {
|
||||
expect(findModal().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('button is bound to the modal', () => {
|
||||
const { value } = getBinding(findButton().element, 'gl-modal');
|
||||
|
||||
expect(value).toBeTruthy();
|
||||
expect(value).toBe(ADD_NAMESPACE_MODAL_ID);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import AddNamespaceModal from '~/jira_connect/subscriptions/components/add_namespace_modal/add_namespace_modal.vue';
|
||||
import GroupsList from '~/jira_connect/subscriptions/components/add_namespace_modal/groups_list.vue';
|
||||
import { ADD_NAMESPACE_MODAL_ID } from '~/jira_connect/subscriptions/constants';
|
||||
|
||||
describe('AddNamespaceModal', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = shallowMount(AddNamespaceModal);
|
||||
};
|
||||
|
||||
const findModal = () => wrapper.findComponent(AddNamespaceModal);
|
||||
const findGroupsList = () => wrapper.findComponent(GroupsList);
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('displays modal with correct props', () => {
|
||||
const modal = findModal();
|
||||
expect(modal.exists()).toBe(true);
|
||||
expect(modal.attributes()).toMatchObject({
|
||||
modalid: ADD_NAMESPACE_MODAL_ID,
|
||||
title: AddNamespaceModal.modal.title,
|
||||
});
|
||||
});
|
||||
|
||||
it('displays GroupList', () => {
|
||||
expect(findGroupsList().exists()).toBe(true);
|
||||
});
|
||||
});
|
|
@ -4,9 +4,9 @@ import waitForPromises from 'helpers/wait_for_promises';
|
|||
|
||||
import * as JiraConnectApi from '~/jira_connect/subscriptions/api';
|
||||
import GroupItemName from '~/jira_connect/subscriptions/components/group_item_name.vue';
|
||||
import GroupsListItem from '~/jira_connect/subscriptions/components/groups_list_item.vue';
|
||||
import GroupsListItem from '~/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue';
|
||||
import { persistAlert, reloadPage } from '~/jira_connect/subscriptions/utils';
|
||||
import { mockGroup1 } from '../mock_data';
|
||||
import { mockGroup1 } from '../../mock_data';
|
||||
|
||||
jest.mock('~/jira_connect/subscriptions/utils');
|
||||
|
|
@ -3,10 +3,10 @@ import { shallowMount } from '@vue/test-utils';
|
|||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { fetchGroups } from '~/jira_connect/subscriptions/api';
|
||||
import GroupsList from '~/jira_connect/subscriptions/components/groups_list.vue';
|
||||
import GroupsListItem from '~/jira_connect/subscriptions/components/groups_list_item.vue';
|
||||
import GroupsList from '~/jira_connect/subscriptions/components/add_namespace_modal/groups_list.vue';
|
||||
import GroupsListItem from '~/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue';
|
||||
import { DEFAULT_GROUPS_PER_PAGE } from '~/jira_connect/subscriptions/constants';
|
||||
import { mockGroup1, mockGroup2 } from '../mock_data';
|
||||
import { mockGroup1, mockGroup2 } from '../../mock_data';
|
||||
|
||||
const createMockGroup = (groupId) => {
|
||||
return {
|
|
@ -1,7 +1,8 @@
|
|||
import { GlAlert, GlButton, GlModal, GlLink } from '@gitlab/ui';
|
||||
import { GlAlert, GlButton, GlLink } from '@gitlab/ui';
|
||||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
|
||||
import JiraConnectApp from '~/jira_connect/subscriptions/components/app.vue';
|
||||
import AddNamespaceButton from '~/jira_connect/subscriptions/components/add_namespace_button.vue';
|
||||
import createStore from '~/jira_connect/subscriptions/store';
|
||||
import { SET_ALERT } from '~/jira_connect/subscriptions/store/mutation_types';
|
||||
import { __ } from '~/locale';
|
||||
|
@ -18,7 +19,7 @@ describe('JiraConnectApp', () => {
|
|||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
const findAlertLink = () => findAlert().findComponent(GlLink);
|
||||
const findGlButton = () => wrapper.findComponent(GlButton);
|
||||
const findGlModal = () => wrapper.findComponent(GlModal);
|
||||
const findAddNamespaceButton = () => wrapper.findComponent(AddNamespaceButton);
|
||||
|
||||
const createComponent = ({ provide, mountFn = shallowMount } = {}) => {
|
||||
store = createStore();
|
||||
|
@ -45,7 +46,7 @@ describe('JiraConnectApp', () => {
|
|||
|
||||
it('renders "Sign in" button', () => {
|
||||
expect(findGlButton().text()).toBe('Sign in to add namespaces');
|
||||
expect(findGlModal().exists()).toBe(false);
|
||||
expect(findAddNamespaceButton().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -54,9 +55,8 @@ describe('JiraConnectApp', () => {
|
|||
createComponent();
|
||||
});
|
||||
|
||||
it('renders "Add" button and modal', () => {
|
||||
expect(findGlButton().text()).toBe('Add namespace');
|
||||
expect(findGlModal().exists()).toBe(true);
|
||||
it('renders "Add namespace" button ', () => {
|
||||
expect(findAddNamespaceButton().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -2,156 +2,224 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::GithubImport::Importer::DiffNoteImporter do
|
||||
let(:project) { create(:project) }
|
||||
let(:client) { double(:client) }
|
||||
let(:user) { create(:user) }
|
||||
let(:created_at) { Time.new(2017, 1, 1, 12, 00) }
|
||||
let(:updated_at) { Time.new(2017, 1, 1, 12, 15) }
|
||||
RSpec.describe Gitlab::GithubImport::Importer::DiffNoteImporter, :aggregate_failures do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:hunk) do
|
||||
'@@ -1 +1 @@
|
||||
let(:client) { double(:client) }
|
||||
let(:discussion_id) { 'b0fa404393eeebb4e82becb8104f238812bb1fe6' }
|
||||
let(:created_at) { Time.new(2017, 1, 1, 12, 00).utc }
|
||||
let(:updated_at) { Time.new(2017, 1, 1, 12, 15).utc }
|
||||
let(:note_body) { 'Hello' }
|
||||
let(:file_path) { 'files/ruby/popen.rb' }
|
||||
|
||||
let(:diff_hunk) do
|
||||
'@@ -14 +14 @@
|
||||
-Hello
|
||||
+Hello world'
|
||||
end
|
||||
|
||||
let(:note) do
|
||||
let(:note_representation) do
|
||||
Gitlab::GithubImport::Representation::DiffNote.new(
|
||||
noteable_type: 'MergeRequest',
|
||||
noteable_id: 1,
|
||||
commit_id: '123abc',
|
||||
original_commit_id: 'original123abc',
|
||||
file_path: 'README.md',
|
||||
diff_hunk: hunk,
|
||||
author: Gitlab::GithubImport::Representation::User
|
||||
.new(id: user.id, login: user.username),
|
||||
note: 'Hello',
|
||||
file_path: file_path,
|
||||
author: Gitlab::GithubImport::Representation::User.new(id: user.id, login: user.username),
|
||||
note: note_body,
|
||||
created_at: created_at,
|
||||
updated_at: updated_at,
|
||||
github_id: 1
|
||||
start_line: nil,
|
||||
end_line: 15,
|
||||
github_id: 1,
|
||||
diff_hunk: diff_hunk,
|
||||
side: 'RIGHT'
|
||||
)
|
||||
end
|
||||
|
||||
let(:importer) { described_class.new(note, project, client) }
|
||||
subject(:importer) { described_class.new(note_representation, project, client) }
|
||||
|
||||
shared_examples 'diff notes without suggestion' do
|
||||
it 'imports the note as legacy diff note' do
|
||||
stub_user_finder(user.id, true)
|
||||
|
||||
expect { subject.execute }
|
||||
.to change(LegacyDiffNote, :count)
|
||||
.by(1)
|
||||
|
||||
note = project.notes.diff_notes.take
|
||||
expect(note).to be_valid
|
||||
expect(note.author_id).to eq(user.id)
|
||||
expect(note.commit_id).to eq('original123abc')
|
||||
expect(note.created_at).to eq(created_at)
|
||||
expect(note.diff).to be_an_instance_of(Gitlab::Git::Diff)
|
||||
expect(note.discussion_id).to eq(discussion_id)
|
||||
expect(note.line_code).to eq(note_representation.line_code)
|
||||
expect(note.note).to eq('Hello')
|
||||
expect(note.noteable_id).to eq(merge_request.id)
|
||||
expect(note.noteable_type).to eq('MergeRequest')
|
||||
expect(note.project_id).to eq(project.id)
|
||||
expect(note.st_diff).to eq(note_representation.diff_hash)
|
||||
expect(note.system).to eq(false)
|
||||
expect(note.type).to eq('LegacyDiffNote')
|
||||
expect(note.updated_at).to eq(updated_at)
|
||||
end
|
||||
|
||||
it 'adds a "created by:" note when the author cannot be found' do
|
||||
stub_user_finder(project.creator_id, false)
|
||||
|
||||
expect { subject.execute }
|
||||
.to change(LegacyDiffNote, :count)
|
||||
.by(1)
|
||||
|
||||
note = project.notes.diff_notes.take
|
||||
expect(note).to be_valid
|
||||
expect(note.author_id).to eq(project.creator_id)
|
||||
expect(note.note).to eq("*Created by: #{user.username}*\n\nHello")
|
||||
end
|
||||
|
||||
it 'does not import the note when a foreign key error is raised' do
|
||||
stub_user_finder(project.creator_id, false)
|
||||
|
||||
expect(Gitlab::Database.main)
|
||||
.to receive(:bulk_insert)
|
||||
.and_raise(ActiveRecord::InvalidForeignKey, 'invalid foreign key')
|
||||
|
||||
expect { subject.execute }
|
||||
.not_to change(LegacyDiffNote, :count)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
context 'when the merge request no longer exists' do
|
||||
it 'does not import anything' do
|
||||
expect(Gitlab::Database.main).not_to receive(:bulk_insert)
|
||||
|
||||
importer.execute
|
||||
expect { subject.execute }
|
||||
.to not_change(DiffNote, :count)
|
||||
.and not_change(LegacyDiffNote, :count)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the merge request exists' do
|
||||
let!(:merge_request) do
|
||||
let_it_be(:merge_request) do
|
||||
create(:merge_request, source_project: project, target_project: project)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(importer)
|
||||
.to receive(:find_merge_request_id)
|
||||
.and_return(merge_request.id)
|
||||
expect_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
|
||||
expect(finder)
|
||||
.to receive(:database_id)
|
||||
.and_return(merge_request.id)
|
||||
end
|
||||
|
||||
expect(Discussion)
|
||||
.to receive(:discussion_id)
|
||||
.and_return(discussion_id)
|
||||
end
|
||||
|
||||
it 'imports the note' do
|
||||
allow(importer.user_finder)
|
||||
.to receive(:author_id_for)
|
||||
.and_return([user.id, true])
|
||||
context 'when github_importer_use_diff_note_with_suggestions is disabled' do
|
||||
before do
|
||||
stub_feature_flags(github_importer_use_diff_note_with_suggestions: false)
|
||||
end
|
||||
|
||||
expect(Gitlab::Database.main)
|
||||
.to receive(:bulk_insert)
|
||||
.with(
|
||||
LegacyDiffNote.table_name,
|
||||
[
|
||||
{
|
||||
discussion_id: anything,
|
||||
noteable_type: 'MergeRequest',
|
||||
noteable_id: merge_request.id,
|
||||
project_id: project.id,
|
||||
author_id: user.id,
|
||||
note: 'Hello',
|
||||
system: false,
|
||||
commit_id: 'original123abc',
|
||||
line_code: note.line_code,
|
||||
type: 'LegacyDiffNote',
|
||||
created_at: created_at,
|
||||
updated_at: updated_at,
|
||||
st_diff: note.diff_hash.to_yaml
|
||||
}
|
||||
]
|
||||
)
|
||||
.and_call_original
|
||||
it_behaves_like 'diff notes without suggestion'
|
||||
|
||||
importer.execute
|
||||
context 'when the note has suggestions' do
|
||||
let(:note_body) do
|
||||
<<~EOB
|
||||
Suggestion:
|
||||
```suggestion
|
||||
what do you think to do it like this
|
||||
```
|
||||
EOB
|
||||
end
|
||||
|
||||
it 'imports the note' do
|
||||
stub_user_finder(user.id, true)
|
||||
|
||||
expect { subject.execute }
|
||||
.to change(LegacyDiffNote, :count)
|
||||
.and not_change(DiffNote, :count)
|
||||
|
||||
note = project.notes.diff_notes.take
|
||||
expect(note).to be_valid
|
||||
expect(note.note)
|
||||
.to eq <<~NOTE
|
||||
Suggestion:
|
||||
```suggestion:-0+0
|
||||
what do you think to do it like this
|
||||
```
|
||||
NOTE
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'imports the note when the author could not be found' do
|
||||
allow(importer.user_finder)
|
||||
.to receive(:author_id_for)
|
||||
.and_return([project.creator_id, false])
|
||||
context 'when github_importer_use_diff_note_with_suggestions is enabled' do
|
||||
before do
|
||||
stub_feature_flags(github_importer_use_diff_note_with_suggestions: true)
|
||||
end
|
||||
|
||||
expect(Gitlab::Database.main)
|
||||
.to receive(:bulk_insert)
|
||||
.with(
|
||||
LegacyDiffNote.table_name,
|
||||
[
|
||||
{
|
||||
discussion_id: anything,
|
||||
noteable_type: 'MergeRequest',
|
||||
noteable_id: merge_request.id,
|
||||
project_id: project.id,
|
||||
author_id: project.creator_id,
|
||||
note: "*Created by: #{user.username}*\n\nHello",
|
||||
system: false,
|
||||
commit_id: 'original123abc',
|
||||
line_code: note.line_code,
|
||||
type: 'LegacyDiffNote',
|
||||
created_at: created_at,
|
||||
updated_at: updated_at,
|
||||
st_diff: note.diff_hash.to_yaml
|
||||
}
|
||||
]
|
||||
)
|
||||
.and_call_original
|
||||
it_behaves_like 'diff notes without suggestion'
|
||||
|
||||
importer.execute
|
||||
end
|
||||
context 'when the note has suggestions' do
|
||||
let(:note_body) do
|
||||
<<~EOB
|
||||
Suggestion:
|
||||
```suggestion
|
||||
what do you think to do it like this
|
||||
```
|
||||
EOB
|
||||
end
|
||||
|
||||
it 'produces a valid LegacyDiffNote' do
|
||||
allow(importer.user_finder)
|
||||
.to receive(:author_id_for)
|
||||
.and_return([user.id, true])
|
||||
it 'imports the note as diff note' do
|
||||
stub_user_finder(user.id, true)
|
||||
|
||||
importer.execute
|
||||
expect { subject.execute }
|
||||
.to change(DiffNote, :count)
|
||||
.by(1)
|
||||
|
||||
note = project.notes.diff_notes.take
|
||||
|
||||
expect(note).to be_valid
|
||||
expect(note.diff).to be_an_instance_of(Gitlab::Git::Diff)
|
||||
end
|
||||
|
||||
it 'does not import the note when a foreign key error is raised' do
|
||||
allow(importer.user_finder)
|
||||
.to receive(:author_id_for)
|
||||
.and_return([project.creator_id, false])
|
||||
|
||||
expect(Gitlab::Database.main)
|
||||
.to receive(:bulk_insert)
|
||||
.and_raise(ActiveRecord::InvalidForeignKey, 'invalid foreign key')
|
||||
|
||||
expect { importer.execute }.not_to raise_error
|
||||
note = project.notes.diff_notes.take
|
||||
expect(note).to be_valid
|
||||
expect(note.noteable_type).to eq('MergeRequest')
|
||||
expect(note.noteable_id).to eq(merge_request.id)
|
||||
expect(note.project_id).to eq(project.id)
|
||||
expect(note.author_id).to eq(user.id)
|
||||
expect(note.system).to eq(false)
|
||||
expect(note.discussion_id).to eq(discussion_id)
|
||||
expect(note.commit_id).to eq('original123abc')
|
||||
expect(note.line_code).to eq(note_representation.line_code)
|
||||
expect(note.type).to eq('DiffNote')
|
||||
expect(note.created_at).to eq(created_at)
|
||||
expect(note.updated_at).to eq(updated_at)
|
||||
expect(note.position.to_h).to eq({
|
||||
base_sha: merge_request.diffs.diff_refs.base_sha,
|
||||
head_sha: merge_request.diffs.diff_refs.head_sha,
|
||||
start_sha: merge_request.diffs.diff_refs.start_sha,
|
||||
new_line: 15,
|
||||
old_line: nil,
|
||||
new_path: file_path,
|
||||
old_path: file_path,
|
||||
position_type: 'text',
|
||||
line_range: nil
|
||||
})
|
||||
expect(note.note)
|
||||
.to eq <<~NOTE
|
||||
Suggestion:
|
||||
```suggestion:-0+0
|
||||
what do you think to do it like this
|
||||
```
|
||||
NOTE
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_merge_request_id' do
|
||||
it 'returns a merge request ID' do
|
||||
expect_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |instance|
|
||||
expect(instance).to receive(:database_id).and_return(10)
|
||||
end
|
||||
|
||||
expect(importer.find_merge_request_id).to eq(10)
|
||||
def stub_user_finder(user, found)
|
||||
expect_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
|
||||
expect(finder)
|
||||
.to receive(:author_id_for)
|
||||
.and_return([user, found])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,6 +20,7 @@ RSpec.describe Gitlab::GithubImport::Importer::DiffNotesImporter do
|
|||
line: 23,
|
||||
start_line: nil,
|
||||
id: 1,
|
||||
side: 'RIGHT',
|
||||
body: <<~BODY
|
||||
Hello World
|
||||
|
||||
|
|
|
@ -9,16 +9,21 @@ RSpec.describe Gitlab::GithubImport::Representation::DiffNote do
|
|||
+Hello world'
|
||||
end
|
||||
|
||||
let(:note_body) { 'Hello world' }
|
||||
let(:start_line) { nil }
|
||||
let(:end_line) { 23 }
|
||||
let(:user_data) { { 'id' => 4, 'login' => 'alice' } }
|
||||
let(:side) { 'RIGHT' }
|
||||
let(:created_at) { Time.new(2017, 1, 1, 12, 00) }
|
||||
let(:updated_at) { Time.new(2017, 1, 1, 12, 15) }
|
||||
|
||||
shared_examples 'a DiffNote' do
|
||||
shared_examples 'a DiffNote representation' do
|
||||
it 'returns an instance of DiffNote' do
|
||||
expect(note).to be_an_instance_of(described_class)
|
||||
end
|
||||
|
||||
context 'the returned DiffNote' do
|
||||
it 'includes the number of the note' do
|
||||
it 'includes the number of the merge request' do
|
||||
expect(note.noteable_id).to eq(42)
|
||||
end
|
||||
|
||||
|
@ -30,18 +35,6 @@ RSpec.describe Gitlab::GithubImport::Representation::DiffNote do
|
|||
expect(note.commit_id).to eq('123abc')
|
||||
end
|
||||
|
||||
it 'includes the user details' do
|
||||
expect(note.author)
|
||||
.to be_an_instance_of(Gitlab::GithubImport::Representation::User)
|
||||
|
||||
expect(note.author.id).to eq(4)
|
||||
expect(note.author.login).to eq('alice')
|
||||
end
|
||||
|
||||
it 'includes the note body' do
|
||||
expect(note.note).to eq('Hello world')
|
||||
end
|
||||
|
||||
it 'includes the created timestamp' do
|
||||
expect(note.created_at).to eq(created_at)
|
||||
end
|
||||
|
@ -57,203 +50,192 @@ RSpec.describe Gitlab::GithubImport::Representation::DiffNote do
|
|||
it 'returns the noteable type' do
|
||||
expect(note.noteable_type).to eq('MergeRequest')
|
||||
end
|
||||
|
||||
describe '#diff_hash' do
|
||||
it 'returns a Hash containing the diff details' do
|
||||
expect(note.diff_hash).to eq(
|
||||
diff: hunk,
|
||||
new_path: 'README.md',
|
||||
old_path: 'README.md',
|
||||
a_mode: '100644',
|
||||
b_mode: '100644',
|
||||
new_file: false
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#diff_position' do
|
||||
let(:diff_refs) { double(:refs, base_sha: 'base', start_sha: 'start', head_sha: 'head') }
|
||||
let(:merge_request) { double(:merge_request, diff_refs: diff_refs) }
|
||||
|
||||
context 'when the diff is an addition' do
|
||||
it 'returns a Gitlab::Diff::Position' do
|
||||
expect(note.diff_position(merge_request).to_h).to eq(
|
||||
base_sha: 'base',
|
||||
head_sha: 'head',
|
||||
line_range: nil,
|
||||
new_line: 23,
|
||||
new_path: 'README.md',
|
||||
old_line: nil,
|
||||
old_path: 'README.md',
|
||||
position_type: 'text',
|
||||
start_sha: 'start'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the diff is an deletion' do
|
||||
let(:side) { 'LEFT' }
|
||||
|
||||
it 'returns a Gitlab::Diff::Position' do
|
||||
expect(note.diff_position(merge_request).to_h).to eq(
|
||||
base_sha: 'base',
|
||||
head_sha: 'head',
|
||||
line_range: nil,
|
||||
old_line: 23,
|
||||
new_path: 'README.md',
|
||||
new_line: nil,
|
||||
old_path: 'README.md',
|
||||
position_type: 'text',
|
||||
start_sha: 'start'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#github_identifiers' do
|
||||
it 'returns a hash with needed identifiers' do
|
||||
expect(note.github_identifiers).to eq(
|
||||
noteable_id: 42,
|
||||
noteable_type: 'MergeRequest',
|
||||
note_id: 1
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#line_code' do
|
||||
it 'generates the proper line code' do
|
||||
note = described_class.new(diff_hunk: hunk, file_path: 'README.md')
|
||||
|
||||
expect(note.line_code).to eq('8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d_2_2')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#note and #contains_suggestion?' do
|
||||
it 'includes the note body' do
|
||||
expect(note.note).to eq('Hello world')
|
||||
expect(note.contains_suggestion?).to eq(false)
|
||||
end
|
||||
|
||||
context 'when the note have a suggestion' do
|
||||
let(:note_body) do
|
||||
<<~BODY
|
||||
```suggestion
|
||||
Hello World
|
||||
```
|
||||
BODY
|
||||
end
|
||||
|
||||
it 'returns the suggestion formatted in the note' do
|
||||
expect(note.note).to eq <<~BODY
|
||||
```suggestion:-0+0
|
||||
Hello World
|
||||
```
|
||||
BODY
|
||||
expect(note.contains_suggestion?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the note have a multiline suggestion' do
|
||||
let(:start_line) { 20 }
|
||||
let(:end_line) { 23 }
|
||||
let(:note_body) do
|
||||
<<~BODY
|
||||
```suggestion
|
||||
Hello World
|
||||
```
|
||||
BODY
|
||||
end
|
||||
|
||||
it 'returns the multi-line suggestion formatted in the note' do
|
||||
expect(note.note).to eq <<~BODY
|
||||
```suggestion:-3+0
|
||||
Hello World
|
||||
```
|
||||
BODY
|
||||
expect(note.contains_suggestion?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#author' do
|
||||
it 'includes the user details' do
|
||||
expect(note.author).to be_an_instance_of(
|
||||
Gitlab::GithubImport::Representation::User
|
||||
)
|
||||
|
||||
expect(note.author.id).to eq(4)
|
||||
expect(note.author.login).to eq('alice')
|
||||
end
|
||||
|
||||
context 'when the author is empty' do
|
||||
let(:user_data) { nil }
|
||||
|
||||
it 'does not set the user if the response did not include a user' do
|
||||
expect(note.author).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.from_api_response' do
|
||||
let(:response) do
|
||||
double(
|
||||
:response,
|
||||
html_url: 'https://github.com/foo/bar/pull/42',
|
||||
path: 'README.md',
|
||||
commit_id: '123abc',
|
||||
original_commit_id: 'original123abc',
|
||||
diff_hunk: hunk,
|
||||
user: double(:user, id: 4, login: 'alice'),
|
||||
body: 'Hello world',
|
||||
created_at: created_at,
|
||||
updated_at: updated_at,
|
||||
line: 23,
|
||||
start_line: nil,
|
||||
id: 1
|
||||
)
|
||||
end
|
||||
it_behaves_like 'a DiffNote representation' do
|
||||
let(:response) do
|
||||
double(
|
||||
:response,
|
||||
id: 1,
|
||||
html_url: 'https://github.com/foo/bar/pull/42',
|
||||
path: 'README.md',
|
||||
commit_id: '123abc',
|
||||
original_commit_id: 'original123abc',
|
||||
side: side,
|
||||
user: user_data && double(:user, user_data),
|
||||
diff_hunk: hunk,
|
||||
body: note_body,
|
||||
created_at: created_at,
|
||||
updated_at: updated_at,
|
||||
line: end_line,
|
||||
start_line: start_line
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'a DiffNote' do
|
||||
let(:note) { described_class.from_api_response(response) }
|
||||
end
|
||||
|
||||
it 'does not set the user if the response did not include a user' do
|
||||
allow(response)
|
||||
.to receive(:user)
|
||||
.and_return(nil)
|
||||
|
||||
note = described_class.from_api_response(response)
|
||||
|
||||
expect(note.author).to be_nil
|
||||
end
|
||||
|
||||
it 'formats a suggestion in the note body' do
|
||||
allow(response)
|
||||
.to receive(:body)
|
||||
.and_return <<~BODY
|
||||
```suggestion
|
||||
Hello World
|
||||
```
|
||||
BODY
|
||||
|
||||
note = described_class.from_api_response(response)
|
||||
|
||||
expect(note.note).to eq <<~BODY
|
||||
```suggestion:-0+0
|
||||
Hello World
|
||||
```
|
||||
BODY
|
||||
subject(:note) { described_class.from_api_response(response) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.from_json_hash' do
|
||||
let(:hash) do
|
||||
{
|
||||
'noteable_type' => 'MergeRequest',
|
||||
'noteable_id' => 42,
|
||||
'file_path' => 'README.md',
|
||||
'commit_id' => '123abc',
|
||||
'original_commit_id' => 'original123abc',
|
||||
'diff_hunk' => hunk,
|
||||
'author' => { 'id' => 4, 'login' => 'alice' },
|
||||
'note' => 'Hello world',
|
||||
'created_at' => created_at.to_s,
|
||||
'updated_at' => updated_at.to_s,
|
||||
'note_id' => 1
|
||||
}
|
||||
end
|
||||
it_behaves_like 'a DiffNote representation' do
|
||||
let(:hash) do
|
||||
{
|
||||
'note_id' => 1,
|
||||
'noteable_type' => 'MergeRequest',
|
||||
'noteable_id' => 42,
|
||||
'file_path' => 'README.md',
|
||||
'commit_id' => '123abc',
|
||||
'original_commit_id' => 'original123abc',
|
||||
'side' => side,
|
||||
'author' => user_data,
|
||||
'diff_hunk' => hunk,
|
||||
'note' => note_body,
|
||||
'created_at' => created_at.to_s,
|
||||
'updated_at' => updated_at.to_s,
|
||||
'end_line' => end_line,
|
||||
'start_line' => start_line
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'a DiffNote' do
|
||||
let(:note) { described_class.from_json_hash(hash) }
|
||||
end
|
||||
|
||||
it 'does not convert the author if it was not specified' do
|
||||
hash.delete('author')
|
||||
|
||||
note = described_class.from_json_hash(hash)
|
||||
|
||||
expect(note.author).to be_nil
|
||||
end
|
||||
|
||||
it 'formats a suggestion in the note body' do
|
||||
hash['note'] = <<~BODY
|
||||
```suggestion
|
||||
Hello World
|
||||
```
|
||||
BODY
|
||||
|
||||
note = described_class.from_json_hash(hash)
|
||||
|
||||
expect(note.note).to eq <<~BODY
|
||||
```suggestion:-0+0
|
||||
Hello World
|
||||
```
|
||||
BODY
|
||||
end
|
||||
end
|
||||
|
||||
describe '#line_code' do
|
||||
it 'returns a String' do
|
||||
note = described_class.new(diff_hunk: hunk, file_path: 'README.md')
|
||||
|
||||
expect(note.line_code).to be_an_instance_of(String)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#diff_hash' do
|
||||
it 'returns a Hash containing the diff details' do
|
||||
note = described_class.from_json_hash(
|
||||
'noteable_type' => 'MergeRequest',
|
||||
'noteable_id' => 42,
|
||||
'file_path' => 'README.md',
|
||||
'commit_id' => '123abc',
|
||||
'original_commit_id' => 'original123abc',
|
||||
'diff_hunk' => hunk,
|
||||
'author' => { 'id' => 4, 'login' => 'alice' },
|
||||
'note' => 'Hello world',
|
||||
'created_at' => created_at.to_s,
|
||||
'updated_at' => updated_at.to_s,
|
||||
'note_id' => 1
|
||||
)
|
||||
|
||||
expect(note.diff_hash).to eq(
|
||||
diff: hunk,
|
||||
new_path: 'README.md',
|
||||
old_path: 'README.md',
|
||||
a_mode: '100644',
|
||||
b_mode: '100644',
|
||||
new_file: false
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#github_identifiers' do
|
||||
it 'returns a hash with needed identifiers' do
|
||||
github_identifiers = {
|
||||
noteable_id: 42,
|
||||
noteable_type: 'MergeRequest',
|
||||
note_id: 1
|
||||
}
|
||||
other_attributes = { something_else: '_something_else_' }
|
||||
note = described_class.new(github_identifiers.merge(other_attributes))
|
||||
|
||||
expect(note.github_identifiers).to eq(github_identifiers)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#note' do
|
||||
it 'returns the given note' do
|
||||
hash = {
|
||||
'note': 'simple text'
|
||||
}
|
||||
|
||||
note = described_class.new(hash)
|
||||
|
||||
expect(note.note).to eq 'simple text'
|
||||
end
|
||||
|
||||
it 'returns the suggestion formatted in the note' do
|
||||
hash = {
|
||||
'note': <<~BODY
|
||||
```suggestion
|
||||
Hello World
|
||||
```
|
||||
BODY
|
||||
}
|
||||
|
||||
note = described_class.new(hash)
|
||||
|
||||
expect(note.note).to eq <<~BODY
|
||||
```suggestion:-0+0
|
||||
Hello World
|
||||
```
|
||||
BODY
|
||||
end
|
||||
|
||||
it 'returns the multi-line suggestion formatted in the note' do
|
||||
hash = {
|
||||
'start_line': 20,
|
||||
'end_line': 23,
|
||||
'note': <<~BODY
|
||||
```suggestion
|
||||
Hello World
|
||||
```
|
||||
BODY
|
||||
}
|
||||
|
||||
note = described_class.new(hash)
|
||||
|
||||
expect(note.note).to eq <<~BODY
|
||||
```suggestion:-3+0
|
||||
Hello World
|
||||
```
|
||||
BODY
|
||||
subject(:note) { described_class.from_json_hash(hash) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,13 +9,19 @@ RSpec.describe Gitlab::GithubImport::Representation::DiffNotes::SuggestionFormat
|
|||
```
|
||||
BODY
|
||||
|
||||
expect(described_class.formatted_note_for(note: note)).to eq(note)
|
||||
note_formatter = described_class.new(note: note)
|
||||
|
||||
expect(note_formatter.formatted_note).to eq(note)
|
||||
expect(note_formatter.contains_suggestion?).to eq(false)
|
||||
end
|
||||
|
||||
it 'handles nil value for note' do
|
||||
note = nil
|
||||
|
||||
expect(described_class.formatted_note_for(note: note)).to eq(note)
|
||||
note_formatter = described_class.new(note: note)
|
||||
|
||||
expect(note_formatter.formatted_note).to eq(note)
|
||||
expect(note_formatter.contains_suggestion?).to eq(false)
|
||||
end
|
||||
|
||||
it 'does not allow over 3 leading spaces for valid suggestion' do
|
||||
|
@ -26,7 +32,10 @@ RSpec.describe Gitlab::GithubImport::Representation::DiffNotes::SuggestionFormat
|
|||
```
|
||||
BODY
|
||||
|
||||
expect(described_class.formatted_note_for(note: note)).to eq(note)
|
||||
note_formatter = described_class.new(note: note)
|
||||
|
||||
expect(note_formatter.formatted_note).to eq(note)
|
||||
expect(note_formatter.contains_suggestion?).to eq(false)
|
||||
end
|
||||
|
||||
it 'allows up to 3 leading spaces' do
|
||||
|
@ -44,7 +53,10 @@ RSpec.describe Gitlab::GithubImport::Representation::DiffNotes::SuggestionFormat
|
|||
```
|
||||
BODY
|
||||
|
||||
expect(described_class.formatted_note_for(note: note)).to eq(expected)
|
||||
note_formatter = described_class.new(note: note)
|
||||
|
||||
expect(note_formatter.formatted_note).to eq(expected)
|
||||
expect(note_formatter.contains_suggestion?).to eq(true)
|
||||
end
|
||||
|
||||
it 'does nothing when there is any text without space after the suggestion tag' do
|
||||
|
@ -53,7 +65,10 @@ RSpec.describe Gitlab::GithubImport::Representation::DiffNotes::SuggestionFormat
|
|||
```
|
||||
BODY
|
||||
|
||||
expect(described_class.formatted_note_for(note: note)).to eq(note)
|
||||
note_formatter = described_class.new(note: note)
|
||||
|
||||
expect(note_formatter.formatted_note).to eq(note)
|
||||
expect(note_formatter.contains_suggestion?).to eq(false)
|
||||
end
|
||||
|
||||
it 'formats single-line suggestions' do
|
||||
|
@ -71,7 +86,10 @@ RSpec.describe Gitlab::GithubImport::Representation::DiffNotes::SuggestionFormat
|
|||
```
|
||||
BODY
|
||||
|
||||
expect(described_class.formatted_note_for(note: note)).to eq(expected)
|
||||
note_formatter = described_class.new(note: note)
|
||||
|
||||
expect(note_formatter.formatted_note).to eq(expected)
|
||||
expect(note_formatter.contains_suggestion?).to eq(true)
|
||||
end
|
||||
|
||||
it 'ignores text after suggestion tag on the same line' do
|
||||
|
@ -89,7 +107,10 @@ RSpec.describe Gitlab::GithubImport::Representation::DiffNotes::SuggestionFormat
|
|||
```
|
||||
BODY
|
||||
|
||||
expect(described_class.formatted_note_for(note: note)).to eq(expected)
|
||||
note_formatter = described_class.new(note: note)
|
||||
|
||||
expect(note_formatter.formatted_note).to eq(expected)
|
||||
expect(note_formatter.contains_suggestion?).to eq(true)
|
||||
end
|
||||
|
||||
it 'formats multiple single-line suggestions' do
|
||||
|
@ -115,7 +136,10 @@ RSpec.describe Gitlab::GithubImport::Representation::DiffNotes::SuggestionFormat
|
|||
```
|
||||
BODY
|
||||
|
||||
expect(described_class.formatted_note_for(note: note)).to eq(expected)
|
||||
note_formatter = described_class.new(note: note)
|
||||
|
||||
expect(note_formatter.formatted_note).to eq(expected)
|
||||
expect(note_formatter.contains_suggestion?).to eq(true)
|
||||
end
|
||||
|
||||
it 'formats multi-line suggestions' do
|
||||
|
@ -133,7 +157,10 @@ RSpec.describe Gitlab::GithubImport::Representation::DiffNotes::SuggestionFormat
|
|||
```
|
||||
BODY
|
||||
|
||||
expect(described_class.formatted_note_for(note: note, start_line: 6, end_line: 8)).to eq(expected)
|
||||
note_formatter = described_class.new(note: note, start_line: 6, end_line: 8)
|
||||
|
||||
expect(note_formatter.formatted_note).to eq(expected)
|
||||
expect(note_formatter.contains_suggestion?).to eq(true)
|
||||
end
|
||||
|
||||
it 'formats multiple multi-line suggestions' do
|
||||
|
@ -159,6 +186,9 @@ RSpec.describe Gitlab::GithubImport::Representation::DiffNotes::SuggestionFormat
|
|||
```
|
||||
BODY
|
||||
|
||||
expect(described_class.formatted_note_for(note: note, start_line: 6, end_line: 8)).to eq(expected)
|
||||
note_formatter = described_class.new(note: note, start_line: 6, end_line: 8)
|
||||
|
||||
expect(note_formatter.formatted_note).to eq(expected)
|
||||
expect(note_formatter.contains_suggestion?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Admin::ApplicationsController, :enable_admin_mode do
|
||||
let_it_be(:admin) { create(:admin) }
|
||||
let_it_be(:application) { create(:oauth_application, owner_id: nil, owner_type: nil) }
|
||||
let_it_be(:show_path) { admin_application_path(application) }
|
||||
let_it_be(:create_path) { admin_applications_path }
|
||||
|
||||
before do
|
||||
sign_in(admin)
|
||||
end
|
||||
|
||||
include_examples 'applications controller - GET #show'
|
||||
|
||||
include_examples 'applications controller - POST #create'
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Groups::Settings::ApplicationsController do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:application) { create(:oauth_application, owner_id: group.id, owner_type: 'Namespace') }
|
||||
let_it_be(:show_path) { group_settings_application_path(group, application) }
|
||||
let_it_be(:create_path) { group_settings_applications_path(group) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
group.add_owner(user)
|
||||
end
|
||||
|
||||
include_examples 'applications controller - GET #show'
|
||||
|
||||
include_examples 'applications controller - POST #create'
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Oauth::ApplicationsController do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:application) { create(:oauth_application, owner: user) }
|
||||
let_it_be(:show_path) { oauth_application_path(application) }
|
||||
let_it_be(:create_path) { oauth_applications_path }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
include_examples 'applications controller - GET #show'
|
||||
|
||||
include_examples 'applications controller - POST #create'
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Import::Github::Notes::CreateService do
|
||||
it 'does not support quick actions' do
|
||||
project = create(:project, :repository)
|
||||
user = create(:user)
|
||||
merge_request = create(:merge_request, source_project: project)
|
||||
|
||||
project.add_maintainer(user)
|
||||
|
||||
note = described_class.new(
|
||||
project,
|
||||
user,
|
||||
note: '/close',
|
||||
noteable_type: 'MergeRequest',
|
||||
noteable_id: merge_request.id
|
||||
).execute
|
||||
|
||||
expect(note.note).to eq('/close')
|
||||
expect(note.noteable.closed?).to be(false)
|
||||
end
|
||||
end
|
|
@ -18,6 +18,7 @@ RSpec.shared_examples 'manage applications' do
|
|||
click_on 'Save application'
|
||||
|
||||
validate_application(application_name, 'Yes')
|
||||
expect(page).to have_link('Continue', href: index_path)
|
||||
|
||||
application = Doorkeeper::Application.find_by(name: application_name)
|
||||
expect(page).to have_css("button[title=\"Copy secret\"][data-clipboard-text=\"#{application.secret}\"]", text: 'Copy')
|
||||
|
@ -33,6 +34,7 @@ RSpec.shared_examples 'manage applications' do
|
|||
click_on 'Save application'
|
||||
|
||||
validate_application(application_name_changed, 'No')
|
||||
expect(page).not_to have_link('Continue')
|
||||
|
||||
visit_applications_path
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'applications controller - GET #show' do
|
||||
describe 'GET #show' do
|
||||
it 'renders template' do
|
||||
get show_path
|
||||
|
||||
expect(response).to render_template :show
|
||||
end
|
||||
|
||||
context 'when application is viewed after being created' do
|
||||
before do
|
||||
create_application
|
||||
end
|
||||
|
||||
it 'sets `@created` instance variable to `true`' do
|
||||
get show_path
|
||||
|
||||
expect(assigns[:created]).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when application is reviewed' do
|
||||
it 'sets `@created` instance variable to `false`' do
|
||||
get show_path
|
||||
|
||||
expect(assigns[:created]).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'applications controller - POST #create' do
|
||||
it "sets `#{OauthApplications::CREATED_SESSION_KEY}` session key to `true`" do
|
||||
create_application
|
||||
|
||||
expect(session[OauthApplications::CREATED_SESSION_KEY]).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
def create_application
|
||||
create_params = attributes_for(:application, trusted: true, confidential: false, scopes: ['api'])
|
||||
post create_path, params: { doorkeeper_application: create_params }
|
||||
end
|
Loading…
Reference in New Issue