Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
ae566b89cc
commit
c8d44b1e3b
|
|
@ -31,14 +31,14 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['setCurrentFileHash']),
|
||||
...mapActions('diffs', ['goToFile']),
|
||||
...mapActions('batchComments', ['scrollToDraft']),
|
||||
isOnLatestDiff(draft) {
|
||||
return draft.position?.head_sha === this.getNoteableData.diff_head_sha;
|
||||
},
|
||||
async onClickDraft(draft) {
|
||||
if (this.viewDiffsFileByFile && draft.file_hash) {
|
||||
await this.setCurrentFileHash(draft.file_hash);
|
||||
if (this.viewDiffsFileByFile) {
|
||||
await this.goToFile({ path: draft.file_path });
|
||||
}
|
||||
|
||||
if (draft.position && !this.isOnLatestDiff(draft)) {
|
||||
|
|
|
|||
|
|
@ -201,8 +201,8 @@ export default {
|
|||
})
|
||||
);
|
||||
},
|
||||
totalWeight() {
|
||||
return this.boardList?.totalWeight;
|
||||
totalIssueWeight() {
|
||||
return this.boardList?.totalIssueWeight;
|
||||
},
|
||||
canShowTotalWeight() {
|
||||
return this.weightFeatureAvailable && !this.isLoading;
|
||||
|
|
@ -473,8 +473,8 @@ export default {
|
|||
<div v-else>• {{ itemsTooltipLabel }}</div>
|
||||
<div v-if="weightFeatureAvailable && !isLoading">
|
||||
•
|
||||
<gl-sprintf :message="__('%{totalWeight} total weight')">
|
||||
<template #totalWeight>{{ totalWeight }}</template>
|
||||
<gl-sprintf :message="__('%{totalIssueWeight} total weight')">
|
||||
<template #totalIssueWeight>{{ totalIssueWeight }}</template>
|
||||
</gl-sprintf>
|
||||
</div>
|
||||
</gl-tooltip>
|
||||
|
|
@ -507,7 +507,7 @@ export default {
|
|||
<gl-tooltip :target="() => $refs.weightTooltip" :title="weightCountToolTip" />
|
||||
<span ref="weightTooltip" class="gl-display-inline-flex gl-ml-3" data-testid="weight">
|
||||
<gl-icon class="gl-mr-2" name="weight" :size="14" />
|
||||
{{ totalWeight }}
|
||||
{{ totalIssueWeight }}
|
||||
</span>
|
||||
</template>
|
||||
<!-- EE end -->
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export function updateIssueCountAndWeight({
|
|||
boardList: {
|
||||
...boardList,
|
||||
issuesCount: boardList.issuesCount - 1,
|
||||
totalWeight: boardList.totalWeight - issue.weight,
|
||||
totalIssueWeight: boardList.totalIssueWeight - issue.weight,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
|
@ -83,7 +83,7 @@ export function updateIssueCountAndWeight({
|
|||
boardList: {
|
||||
...boardList,
|
||||
issuesCount: boardList.issuesCount + 1,
|
||||
totalWeight: boardList.totalWeight + issue.weight,
|
||||
totalIssueWeight: boardList.totalIssueWeight + issue.weight,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -621,7 +621,7 @@ export default {
|
|||
__typename: 'BoardList',
|
||||
id: fromList.boardList.id,
|
||||
issuesCount: fromList.boardList.issuesCount - 1,
|
||||
totalWeight: fromList.boardList.totalWeight - Number(weight),
|
||||
totalIssueWeight: fromList.boardList.totalIssueWeight - Number(weight),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -645,7 +645,7 @@ export default {
|
|||
__typename: 'BoardList',
|
||||
id: toList.boardList.id,
|
||||
issuesCount: toList.boardList.issuesCount + 1,
|
||||
totalWeight: toList.boardList.totalWeight + Number(weight),
|
||||
totalIssueWeight: toList.boardList.totalIssueWeight + Number(weight),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -731,7 +731,7 @@ export default {
|
|||
__typename: 'BoardList',
|
||||
id: fromList.boardList.id,
|
||||
issuesCount: fromList.boardList.issuesCount + 1,
|
||||
totalWeight: fromList.boardList.totalWeight,
|
||||
totalIssueWeight: fromList.boardList.totalIssueWeight,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ module SafeFormatHelper
|
|||
|
||||
# Use `Kernel.format` to avoid conflicts with ViewComponent's `format`.
|
||||
Kernel.format(
|
||||
html_escape_once(format),
|
||||
args.transform_values { |value| html_escape(value) }
|
||||
ERB::Util.html_escape_once(format),
|
||||
args.transform_values { |value| ERB::Util.html_escape(value) }
|
||||
).html_safe
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -166,6 +166,10 @@ module TodosHelper
|
|||
todos_filter_params.values.none?
|
||||
end
|
||||
|
||||
def todos_has_filtered_results?
|
||||
params[:group_id] || params[:project_id] || params[:author_id] || params[:type] || params[:action_id]
|
||||
end
|
||||
|
||||
def no_todos_messages
|
||||
[
|
||||
s_('Todos|Good job! Looks like you don\'t have anything left on your To-Do List'),
|
||||
|
|
|
|||
|
|
@ -76,4 +76,8 @@ class BulkImport < ApplicationRecord
|
|||
def supports_batched_export?
|
||||
source_version_info >= self.class.min_gl_version_for_migration_in_batches
|
||||
end
|
||||
|
||||
def completed?
|
||||
finished? || failed? || timeout?
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -76,6 +76,10 @@ module Integrations
|
|||
{ build_page: read_build_page(response), commit_status: read_commit_status(response) }
|
||||
end
|
||||
|
||||
def avatar_url
|
||||
ActionController::Base.helpers.image_path('illustrations/third-party-logos/integrations-logos/atlassian-bamboo.svg')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_build_result(response)
|
||||
|
|
|
|||
|
|
@ -642,7 +642,6 @@ class ProjectPolicy < BasePolicy
|
|||
prevent(*create_read_update_admin_destroy(:build))
|
||||
prevent(*create_read_update_admin_destroy(:pipeline_schedule))
|
||||
prevent(*create_read_update_admin_destroy(:environment))
|
||||
prevent(*create_read_update_admin_destroy(:cluster))
|
||||
prevent(*create_read_update_admin_destroy(:deployment))
|
||||
end
|
||||
|
||||
|
|
@ -666,6 +665,7 @@ class ProjectPolicy < BasePolicy
|
|||
prevent :read_pipeline_schedule
|
||||
prevent(*create_read_update_admin_destroy(:feature_flag))
|
||||
prevent(:admin_feature_flags_user_lists)
|
||||
prevent(*create_read_update_admin_destroy(:cluster))
|
||||
end
|
||||
|
||||
rule { container_registry_disabled }.policy do
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ module ChatNames
|
|||
chat_name = find_chat_name
|
||||
return unless chat_name
|
||||
|
||||
chat_name.update_last_used_at
|
||||
record_chat_activity(chat_name)
|
||||
chat_name
|
||||
end
|
||||
|
||||
|
|
@ -27,5 +27,10 @@ module ChatNames
|
|||
)
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def record_chat_activity(chat_name)
|
||||
chat_name.update_last_used_at
|
||||
Users::ActivityService.new(author: chat_name.user).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class SignupService < BaseService
|
||||
def initialize(current_user, params = {})
|
||||
@user = current_user
|
||||
@params = params.dup
|
||||
end
|
||||
|
||||
def execute
|
||||
assign_attributes
|
||||
inject_validators
|
||||
|
||||
if @user.save
|
||||
ServiceResponse.success
|
||||
else
|
||||
ServiceResponse.error(message: @user.errors.full_messages.join('. '))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assign_attributes
|
||||
@user.assign_attributes(params) unless params.empty?
|
||||
end
|
||||
|
||||
def inject_validators
|
||||
class << @user
|
||||
validates :role, presence: true
|
||||
validates :setup_for_company, inclusion: { in: [true, false], message: :blank }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -6,8 +6,8 @@
|
|||
- add_page_specific_style 'page_bundles/todos'
|
||||
- add_page_specific_style 'page_bundles/issuable'
|
||||
- filter_by_done = params[:state] == 'done'
|
||||
- open_todo_count = !filter_by_done ? @allowed_todos.count : todos_pending_count
|
||||
- done_todo_count = filter_by_done ? @allowed_todos.count : todos_done_count
|
||||
- open_todo_count = todos_has_filtered_results? && !filter_by_done ? @allowed_todos.count : todos_pending_count
|
||||
- done_todo_count = todos_has_filtered_results? && filter_by_done ? @allowed_todos.count : todos_done_count
|
||||
|
||||
.page-title-holder.d-flex.align-items-center
|
||||
%h1.page-title.gl-font-size-h-display= _("To-Do List")
|
||||
|
|
@ -83,31 +83,38 @@
|
|||
%ul.content-list.todos-list
|
||||
= render @allowed_todos
|
||||
= paginate @todos, theme: "gitlab"
|
||||
.js-nothing-here-container.empty-state.hidden
|
||||
.js-nothing-here-container.gl-empty-state.gl-text-center.hidden
|
||||
.svg-content.svg-150
|
||||
= image_tag 'illustrations/empty-todos-all-done-md.svg'
|
||||
.text-content.gl-text-center
|
||||
%h4
|
||||
%h1.gl-font-size-h-display.gl-line-height-36.gl-mt-0
|
||||
= s_("Todos|You're all done!")
|
||||
- elsif current_user.todos.any?
|
||||
.col.todos-all-done.empty-state
|
||||
.col.todos-all-done.gl-empty-state.gl-text-center
|
||||
.svg-content.svg-150
|
||||
= image_tag 'illustrations/empty-todos-all-done-md.svg'
|
||||
.text-content.gl-text-center
|
||||
- if todos_filter_empty?
|
||||
%h4
|
||||
= image_tag (!todos_filter_empty? && !todos_has_filtered_results?) ? 'illustrations/empty-todos-all-done-md.svg' : 'illustrations/empty-todos-md.svg'
|
||||
.text-content.gl-text-center.gl-m-auto{ class: "gl-max-w-88!" }
|
||||
%h1.gl-font-size-h-display.gl-line-height-36.gl-mt-0
|
||||
- if todos_filter_empty?
|
||||
= no_todos_messages.sample
|
||||
- elsif todos_has_filtered_results?
|
||||
= _("Sorry, your filter produced no results")
|
||||
- else
|
||||
= s_("Todos|Nothing is on your to-do list. Nice work!")
|
||||
|
||||
- if todos_filter_empty?
|
||||
%p
|
||||
= (s_("Todos|Are you looking for things to do? Take a look at %{strongStart}%{openIssuesLinkStart}open issues%{openIssuesLinkEnd}%{strongEnd}, contribute to %{strongStart}%{mergeRequestLinkStart}a merge request%{mergeRequestLinkEnd}%{mergeRequestLinkEnd}%{strongEnd}, or mention someone in a comment to automatically assign them a new to-do item.") % { strongStart: '<strong>', strongEnd: '</strong>', openIssuesLinkStart: "<a href=\"#{issues_dashboard_path}\">", openIssuesLinkEnd: '</a>', mergeRequestLinkStart: "<a href=\"#{merge_requests_dashboard_path}\">", mergeRequestLinkEnd: '</a>' }).html_safe
|
||||
- else
|
||||
%h4
|
||||
= s_("Todos|Nothing is on your to-do list. Nice work!")
|
||||
- elsif todos_has_filtered_results?
|
||||
%p
|
||||
= link_to s_("Todos|Do you want to remove the filters?"), todos_filter_path(without: [:project_id, :author_id, :type, :action_id])
|
||||
|
||||
- else
|
||||
.col.empty-state
|
||||
.col.gl-empty-state.gl-text-center
|
||||
.svg-content.svg-150
|
||||
= image_tag 'illustrations/empty-todos-md.svg'
|
||||
.text-content.gl-text-center
|
||||
%h4
|
||||
.text-content.gl-text-center.gl-m-auto{ class: "gl-max-w-88!" }
|
||||
%h1.gl-font-size-h-display.gl-line-height-36.gl-mt-0
|
||||
= s_("Todos|Your To-Do List shows what to work on next")
|
||||
%p
|
||||
= (s_("Todos|When an issue or merge request is assigned to you, or when you receive a %{strongStart}@mention%{strongEnd} in a comment, this automatically triggers a new item in your To-Do List.") % { strongStart: '<strong>', strongEnd: '</strong>' }).html_safe
|
||||
|
|
|
|||
|
|
@ -10,6 +10,6 @@
|
|||
- if show_super_sidebar?
|
||||
= render Pajamas::ButtonComponent.new(icon: 'sidebar', category: :tertiary, button_options: { class: 'js-super-sidebar-toggle-expand super-sidebar-toggle gl-ml-n3', aria: { controls: 'super-sidebar', expanded: 'false', label: _('Primary navigation sidebar') } })
|
||||
- elsif defined?(@left_sidebar)
|
||||
= render Pajamas::ButtonComponent.new(icon: 'sidebar', category: :tertiary, button_options: { class: 'toggle-mobile-nav gl-ml-n3', data: { testid: 'toggle_mobile_nav_button' }, aria: { label: _('Open sidebar') } })
|
||||
= render Pajamas::ButtonComponent.new(icon: 'sidebar', category: :tertiary, button_options: { class: 'toggle-mobile-nav gl-ml-n3', data: { testid: 'toggle-mobile-nav-button' }, aria: { label: _('Open sidebar') } })
|
||||
= render "layouts/nav/breadcrumbs/breadcrumbs"
|
||||
= render "layouts/nav/ask_duo_button"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class BulkImportWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
@bulk_import = BulkImport.find_by_id(bulk_import_id)
|
||||
|
||||
return unless @bulk_import
|
||||
return if @bulk_import.finished? || @bulk_import.failed?
|
||||
return if @bulk_import.completed?
|
||||
return @bulk_import.fail_op! if all_entities_failed?
|
||||
return @bulk_import.finish! if all_entities_processed? && @bulk_import.started?
|
||||
return re_enqueue if max_batch_size_exceeded? # Do not start more jobs if max allowed are already running
|
||||
|
|
|
|||
|
|
@ -93,11 +93,11 @@ The first 2-3 quarters are required to define a general split of data and build
|
|||
|
||||
The Admin Area section for the most part is shared across a cluster.
|
||||
|
||||
1. **User accounts are shared across cluster.**
|
||||
1. **User accounts are shared across cluster.** ✓
|
||||
|
||||
The purpose is to make `users` cluster-wide.
|
||||
|
||||
1. **User can create Group.**
|
||||
1. **User can create Group.** ✓ ([demo](https://www.youtube.com/watch?v=LUyV0ncfdRs))
|
||||
|
||||
The purpose is to perform a targeted decomposition of `users` and `namespaces`, because `namespaces` will be stored locally in the Cell.
|
||||
|
||||
|
|
@ -323,6 +323,7 @@ Below is a list of known affected features with preliminary proposed solutions.
|
|||
|
||||
- [Cells: Admin Area](impacted_features/admin-area.md)
|
||||
- [Cells: Backups](impacted_features/backups.md)
|
||||
- [Cells: CI/CD Catalog](impacted_features/ci-cd-catalog.md)
|
||||
- [Cells: CI Runners](impacted_features/ci-runners.md)
|
||||
- [Cells: Container Registry](impacted_features/container-registry.md)
|
||||
- [Cells: Contributions: Forks](impacted_features/contributions-forks.md)
|
||||
|
|
@ -344,7 +345,6 @@ Below is a list of known affected features with preliminary proposed solutions.
|
|||
The following list of impacted features only represents placeholders that still require work to estimate the impact of Cells and develop solution proposals.
|
||||
|
||||
- [Cells: Agent for Kubernetes](impacted_features/agent-for-kubernetes.md)
|
||||
- [Cells: CI/CD Catalog](impacted_features/ci-cd-catalog.md)
|
||||
- [Cells: Data pipeline ingestion](impacted_features/data-pipeline-ingestion.md)
|
||||
- [Cells: GitLab Pages](impacted_features/gitlab-pages.md)
|
||||
- [Cells: Personal Access Tokens](impacted_features/personal-access-tokens.md)
|
||||
|
|
|
|||
|
|
@ -93,9 +93,9 @@ Project maintainers and owners can add or enable a deploy key for a project repo
|
|||
|
||||
## Runner authentication tokens
|
||||
|
||||
In GitLab 16.0 and later, you can use a runner authentication token to register
|
||||
runners instead of a runner registration token. Runner registration tokens have
|
||||
been [deprecated](../update/deprecations.md#registration-tokens-and-server-side-runner-arguments-in-gitlab-runner-register-command).
|
||||
In GitLab 16.0 and later, to register a runner, you can use a runner authentication token
|
||||
instead of a runner registration token. Runner registration tokens have
|
||||
been [deprecated](../ci/runners/new_creation_workflow.md).
|
||||
|
||||
After you create a runner and its configuration, you receive a runner authentication token
|
||||
that you use to register the runner. The runner authentication token is stored locally in the
|
||||
|
|
@ -117,7 +117,7 @@ for the following executors only have access to the job token and not the runner
|
|||
- SSH
|
||||
|
||||
Malicious access to a runner's file system may expose the `config.toml` file and the
|
||||
runner authentication token. The attacker could use the runner authentication
|
||||
runner authentication token. The attacker could use the runner authentication token
|
||||
to [clone the runner](https://docs.gitlab.com/runner/security/#cloning-a-runner).
|
||||
|
||||
You can use the `runners` API to
|
||||
|
|
@ -126,7 +126,7 @@ programmatically [rotate or revoke a runner authentication token](../api/runners
|
|||
## Runner registration tokens (deprecated)
|
||||
|
||||
WARNING:
|
||||
The ability to pass a runner registration token has been [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/380872) and is
|
||||
The ability to pass a runner registration token has been [deprecated](../ci/runners/new_creation_workflow.md) and is
|
||||
planned for removal in GitLab 18.0, along with support for certain configuration arguments. This change is a breaking change. GitLab has implemented a new
|
||||
[GitLab Runner token architecture](../ci/runners/new_creation_workflow.md), which introduces
|
||||
a new method for registering runners and eliminates the
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ To enforce 2FA only for certain groups:
|
|||
and projects, the shortest grace period is used.
|
||||
1. Select **Save changes**.
|
||||
|
||||
Enforcement affects all [direct and inherited members](../user/project/members/index.md#membership-types) in the group.
|
||||
|
||||
Access tokens are not required to provide a second factor for authentication because
|
||||
they are API-based. Tokens generated before 2FA is enforced remain valid.
|
||||
|
||||
|
|
|
|||
|
|
@ -259,6 +259,14 @@ If you receive a `404` during setup when using "verify configuration", make sure
|
|||
If a user is trying to sign in for the first time and the GitLab single sign-on URL has not [been configured](index.md#set-up-your-identity-provider), they may see a 404.
|
||||
As outlined in the [user access section](index.md#link-saml-to-your-existing-gitlabcom-account), a group Owner needs to provide the URL to users.
|
||||
|
||||
If the top-level group has [restricted membership by email domain](../access_and_permissions.md#restrict-group-access-by-domain), and a user with an email domain that is not allowed tries to sign in with SSO, that user might receive a 404. Users might have multiple accounts, and their SAML identity might be linked to their personal account which has an email address that is different than the company domain. To check this, verify the following:
|
||||
|
||||
- That the top-level group has restricted membership by email domain.
|
||||
- That, in [Audit Events](../../../administration/audit_events.md) for the top-level group:
|
||||
- You can see **Signed in with GROUP_SAML authentication** action for that user.
|
||||
- That the user's username is the same as the username you configured for SAML SSO, by selecting the **Author** name.
|
||||
- If the username is different to the username you configured for SAML SSO, ask the user to [unlink the SAML identity](index.md#unlink-accounts) from their personal account.
|
||||
|
||||
If all users are receiving a `404` after signing in to the identity provider (IdP):
|
||||
|
||||
- Verify the `assertion_consumer_service_url`:
|
||||
|
|
|
|||
|
|
@ -9,10 +9,12 @@ module API
|
|||
params :path_and_file_name do
|
||||
requires :path,
|
||||
type: String,
|
||||
file_path: true,
|
||||
desc: 'Package path',
|
||||
documentation: { example: 'foo/bar/mypkg/1.0-SNAPSHOT' }
|
||||
requires :file_name,
|
||||
type: String,
|
||||
file_path: true,
|
||||
desc: 'Package file name',
|
||||
documentation: { example: 'mypkg-1.0-SNAPSHOT.jar' }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1268,10 +1268,10 @@ msgstr ""
|
|||
msgid "%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
|
||||
msgid "%{totalIssueWeight} total weight"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{totalWeight} total weight"
|
||||
msgid "%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{total_warnings} warning(s) found:"
|
||||
|
|
@ -49497,6 +49497,9 @@ msgstr ""
|
|||
msgid "Todos|Design"
|
||||
msgstr ""
|
||||
|
||||
msgid "Todos|Do you want to remove the filters?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Todos|Due %{due_date}"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ module QA
|
|||
def open_mobile_nav_sidebar
|
||||
unless has_css?('.sidebar-expanded-mobile')
|
||||
Support::Retrier.retry_until do
|
||||
click_element(:toggle_mobile_nav_button)
|
||||
click_element('toggle-mobile-nav-button')
|
||||
has_css?('.sidebar-expanded-mobile')
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ module QA
|
|||
include QA::Page::SubMenus::Common
|
||||
|
||||
view 'app/views/layouts/nav/_top_bar.html.haml' do
|
||||
element :toggle_mobile_nav_button
|
||||
element 'toggle-mobile-nav-button'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -22,5 +22,9 @@ FactoryBot.define do
|
|||
trait :failed do
|
||||
status { -1 }
|
||||
end
|
||||
|
||||
trait :timeout do
|
||||
status { 3 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ RSpec.describe 'Dashboard Todos', feature_category: :team_planning do
|
|||
|
||||
let_it_be(:user) { create(:user, :no_super_sidebar, username: 'john') }
|
||||
let_it_be(:user2) { create(:user, :no_super_sidebar, username: 'diane') }
|
||||
let_it_be(:user3) { create(:user) }
|
||||
let_it_be(:author) { create(:user, :no_super_sidebar) }
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
let_it_be(:issue) { create(:issue, project: project, due_date: Date.today, title: "Fix bug") }
|
||||
|
|
@ -424,6 +425,25 @@ RSpec.describe 'Dashboard Todos', feature_category: :team_planning do
|
|||
wait_for_requests
|
||||
end
|
||||
end
|
||||
|
||||
describe 'shows a count of todos' do
|
||||
before do
|
||||
allow(Todo).to receive(:default_per_page).and_return(1)
|
||||
create_list(:todo, 2, :mentioned, user: user3, project: project, target: issue, author: author, state: :pending)
|
||||
create_list(:todo, 2, :mentioned, user: user3, project: project, target: issue, author: author, state: :done)
|
||||
sign_in(user3)
|
||||
end
|
||||
|
||||
it 'displays a count of all pending todos' do
|
||||
visit dashboard_todos_path
|
||||
expect(find('.js-todos-pending')).to have_content('2')
|
||||
end
|
||||
|
||||
it 'displays a count of all done todos' do
|
||||
visit dashboard_todos_path(state: 'done')
|
||||
expect(find('.js-todos-done')).to have_content('2')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'User has a Build Failed todo' do
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Vue.use(Vuex);
|
|||
|
||||
let wrapper;
|
||||
|
||||
const setCurrentFileHash = jest.fn();
|
||||
const goToFile = jest.fn();
|
||||
const scrollToDraft = jest.fn();
|
||||
|
||||
const findPreviewItem = () => wrapper.findComponent(PreviewItem);
|
||||
|
|
@ -27,7 +27,7 @@ function factory({ viewDiffsFileByFile = false, draftsCount = 1, sortedDrafts =
|
|||
diffs: {
|
||||
namespaced: true,
|
||||
actions: {
|
||||
setCurrentFileHash,
|
||||
goToFile,
|
||||
},
|
||||
state: {
|
||||
viewDiffsFileByFile,
|
||||
|
|
@ -59,12 +59,12 @@ describe('Batch comments preview dropdown', () => {
|
|||
it('toggles active file when viewDiffsFileByFile is true', async () => {
|
||||
factory({
|
||||
viewDiffsFileByFile: true,
|
||||
sortedDrafts: [{ id: 1, file_hash: 'hash' }],
|
||||
sortedDrafts: [{ id: 1, file_hash: 'hash', file_path: 'foo' }],
|
||||
});
|
||||
findPreviewItem().trigger('click');
|
||||
await nextTick();
|
||||
|
||||
expect(setCurrentFileHash).toHaveBeenCalledWith(expect.anything(), 'hash');
|
||||
expect(goToFile).toHaveBeenCalledWith(expect.anything(), { path: 'foo' });
|
||||
|
||||
await nextTick();
|
||||
expect(scrollToDraft).toHaveBeenCalledWith(
|
||||
|
|
|
|||
|
|
@ -973,7 +973,7 @@ export const boardListQueryResponse = ({
|
|||
boardList: {
|
||||
__typename: 'BoardList',
|
||||
id: listId,
|
||||
totalWeight: 5,
|
||||
totalIssueWeight: '5',
|
||||
issuesCount,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,155 +7,291 @@ RSpec.describe Gitlab::Ci::Config::Interpolation::Inputs, feature_category: :pip
|
|||
let(:specs) { { foo: { default: 'bar' } } }
|
||||
let(:args) { {} }
|
||||
|
||||
context 'when inputs are valid' do
|
||||
where(:specs, :args, :merged) do
|
||||
[
|
||||
[
|
||||
{ foo: { default: 'bar' } }, {},
|
||||
{ foo: 'bar' }
|
||||
],
|
||||
[
|
||||
{ foo: { default: 'bar' } }, { foo: 'test' },
|
||||
{ foo: 'test' }
|
||||
],
|
||||
[
|
||||
{ foo: nil }, { foo: 'bar' },
|
||||
{ foo: 'bar' }
|
||||
],
|
||||
[
|
||||
{ foo: { type: 'string' } }, { foo: 'bar' },
|
||||
{ foo: 'bar' }
|
||||
],
|
||||
[
|
||||
{ foo: { type: 'string', default: 'bar' } }, { foo: 'test' },
|
||||
{ foo: 'test' }
|
||||
],
|
||||
[
|
||||
{ foo: { type: 'string', default: 'bar' } }, {},
|
||||
{ foo: 'bar' }
|
||||
],
|
||||
[
|
||||
{ foo: { default: 'bar' }, baz: nil }, { baz: 'test' },
|
||||
{ foo: 'bar', baz: 'test' }
|
||||
],
|
||||
[
|
||||
{ number_input: { type: 'number' } },
|
||||
{ number_input: 8 },
|
||||
{ number_input: 8 }
|
||||
],
|
||||
[
|
||||
{ default_number_input: { default: 9, type: 'number' } },
|
||||
{},
|
||||
{ default_number_input: 9 }
|
||||
],
|
||||
[
|
||||
{ true_input: { type: 'boolean' }, false_input: { type: 'boolean' } },
|
||||
{ true_input: true, false_input: false },
|
||||
{ true_input: true, false_input: false }
|
||||
],
|
||||
[
|
||||
{ default_boolean_input: { default: true, type: 'boolean' } },
|
||||
{},
|
||||
{ default_boolean_input: true }
|
||||
],
|
||||
[
|
||||
{ test_input: { regex: '^input_value$' } },
|
||||
{ test_input: 'input_value' },
|
||||
{ test_input: 'input_value' }
|
||||
],
|
||||
[
|
||||
{ test_input: { regex: '^input_value$', default: 'input_value' } },
|
||||
{},
|
||||
{ test_input: 'input_value' }
|
||||
]
|
||||
]
|
||||
context 'when given unrecognized inputs' do
|
||||
let(:specs) { { foo: nil } }
|
||||
let(:args) { { foo: 'bar', test: 'bar' } }
|
||||
|
||||
it 'is invalid' do
|
||||
expect(inputs).not_to be_valid
|
||||
expect(inputs.errors).to contain_exactly('unknown input arguments: test')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when given unrecognized configuration keywords' do
|
||||
let(:specs) { { foo: 123 } }
|
||||
let(:args) { {} }
|
||||
|
||||
it 'is invalid' do
|
||||
expect(inputs).not_to be_valid
|
||||
expect(inputs.errors).to contain_exactly(
|
||||
'unknown input specification for `foo` (valid types: boolean, number, string)'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the inputs have multiple errors' do
|
||||
let(:specs) { { foo: nil } }
|
||||
let(:args) { { test: 'bar', gitlab: '1' } }
|
||||
|
||||
it 'reports all of them' do
|
||||
expect(inputs).not_to be_valid
|
||||
expect(inputs.errors).to contain_exactly(
|
||||
'unknown input arguments: test, gitlab',
|
||||
'`foo` input: required value has not been provided'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'required inputs' do
|
||||
let(:specs) { { foo: nil } }
|
||||
|
||||
context 'when a value is given' do
|
||||
let(:args) { { foo: 'bar' } }
|
||||
|
||||
it 'is valid' do
|
||||
expect(inputs).to be_valid
|
||||
expect(inputs.to_hash).to eq(foo: 'bar')
|
||||
end
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'contains the merged inputs' do
|
||||
expect(inputs).to be_valid
|
||||
expect(inputs.to_hash).to eq(merged)
|
||||
context 'when no value is given' do
|
||||
let(:args) { {} }
|
||||
|
||||
it 'is invalid' do
|
||||
expect(inputs).not_to be_valid
|
||||
expect(inputs.errors).to contain_exactly('`foo` input: required value has not been provided')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when inputs are invalid' do
|
||||
where(:specs, :args, :errors) do
|
||||
[
|
||||
[
|
||||
{ foo: nil }, { foo: 'bar', test: 'bar' },
|
||||
['unknown input arguments: test']
|
||||
],
|
||||
[
|
||||
{ foo: nil }, { test: 'bar', gitlab: '1' },
|
||||
['unknown input arguments: test, gitlab', '`foo` input: required value has not been provided']
|
||||
],
|
||||
[
|
||||
{ foo: 123 }, {},
|
||||
['unknown input specification for `foo` (valid types: boolean, number, string)']
|
||||
],
|
||||
[
|
||||
{ a: nil, foo: 123 }, { a: '123' },
|
||||
['unknown input specification for `foo` (valid types: boolean, number, string)']
|
||||
],
|
||||
[
|
||||
{ foo: nil }, {},
|
||||
['`foo` input: required value has not been provided']
|
||||
],
|
||||
[
|
||||
{ foo: { default: 123 } }, { foo: 'test' },
|
||||
['`foo` input: default value is not a string']
|
||||
],
|
||||
[
|
||||
{ foo: { default: 'test' } }, { foo: 123 },
|
||||
['`foo` input: provided value is not a string']
|
||||
],
|
||||
[
|
||||
{ foo: nil }, { foo: 123 },
|
||||
['`foo` input: provided value is not a string']
|
||||
],
|
||||
[
|
||||
{ number_input: { type: 'number' } },
|
||||
{ number_input: 'NaN' },
|
||||
['`number_input` input: provided value is not a number']
|
||||
],
|
||||
[
|
||||
{ default_number_input: { default: 'NaN', type: 'number' } },
|
||||
{},
|
||||
['`default_number_input` input: default value is not a number']
|
||||
],
|
||||
[
|
||||
{ boolean_input: { type: 'boolean' } },
|
||||
{ boolean_input: 'string' },
|
||||
['`boolean_input` input: provided value is not a boolean']
|
||||
],
|
||||
[
|
||||
{ default_boolean_input: { default: 'string', type: 'boolean' } },
|
||||
{},
|
||||
['`default_boolean_input` input: default value is not a boolean']
|
||||
],
|
||||
[
|
||||
{ test_input: { regex: '^input_value$' } },
|
||||
{ test_input: 'input' },
|
||||
['`test_input` input: provided value does not match required RegEx pattern']
|
||||
],
|
||||
[
|
||||
{ test_input: { regex: '^input_value$', default: 'default' } },
|
||||
{},
|
||||
['`test_input` input: default value does not match required RegEx pattern']
|
||||
],
|
||||
[
|
||||
{ test_input: { regex: '^input_value$', type: 'number' } },
|
||||
{ test_input: 999 },
|
||||
['`test_input` input: RegEx validation can only be used with string inputs']
|
||||
]
|
||||
]
|
||||
describe 'inputs with a default value' do
|
||||
let(:specs) { { foo: { default: 'bar' } } }
|
||||
|
||||
context 'when a value is given' do
|
||||
let(:args) { { foo: 'test' } }
|
||||
|
||||
it 'uses the given value' do
|
||||
expect(inputs).to be_valid
|
||||
expect(inputs.to_hash).to eq(foo: 'test')
|
||||
end
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'contains the merged inputs', :aggregate_failures do
|
||||
context 'when no value is given' do
|
||||
let(:args) { {} }
|
||||
|
||||
it 'uses the default value' do
|
||||
expect(inputs).to be_valid
|
||||
expect(inputs.to_hash).to eq(foo: 'bar')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'inputs with type validation' do
|
||||
describe 'string validation' do
|
||||
let(:specs) { { a_input: nil, b_input: { default: 'test' }, c_input: { default: 123 } } }
|
||||
let(:args) { { a_input: 123, b_input: 123, c_input: 'test' } }
|
||||
|
||||
it 'is the default type' do
|
||||
expect(inputs).not_to be_valid
|
||||
expect(inputs.errors).to contain_exactly(*errors)
|
||||
expect(inputs.errors).to contain_exactly(
|
||||
'`a_input` input: provided value is not a string',
|
||||
'`b_input` input: provided value is not a string',
|
||||
'`c_input` input: default value is not a string'
|
||||
)
|
||||
end
|
||||
|
||||
context 'when the value is a string' do
|
||||
let(:specs) { { foo: { type: 'string' } } }
|
||||
let(:args) { { foo: 'bar' } }
|
||||
|
||||
it 'is valid' do
|
||||
expect(inputs).to be_valid
|
||||
expect(inputs.to_hash).to eq(foo: 'bar')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the default is a string' do
|
||||
let(:specs) { { foo: { type: 'string', default: 'bar' } } }
|
||||
let(:args) { {} }
|
||||
|
||||
it 'is valid' do
|
||||
expect(inputs).to be_valid
|
||||
expect(inputs.to_hash).to eq(foo: 'bar')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the value is not a string' do
|
||||
let(:specs) { { foo: { type: 'string' } } }
|
||||
let(:args) { { foo: 123 } }
|
||||
|
||||
it 'is invalid' do
|
||||
expect(inputs).not_to be_valid
|
||||
expect(inputs.errors).to contain_exactly('`foo` input: provided value is not a string')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the default is not a string' do
|
||||
let(:specs) { { foo: { default: 123, type: 'string' } } }
|
||||
let(:args) { {} }
|
||||
|
||||
it 'is invalid' do
|
||||
expect(inputs).not_to be_valid
|
||||
expect(inputs.errors).to contain_exactly('`foo` input: default value is not a string')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'number validation' do
|
||||
let(:specs) { { integer: { type: 'number' }, float: { type: 'number' } } }
|
||||
|
||||
context 'when the value is a float or integer' do
|
||||
let(:args) { { integer: 6, float: 6.6 } }
|
||||
|
||||
it 'is valid' do
|
||||
expect(inputs).to be_valid
|
||||
expect(inputs.to_hash).to eq(integer: 6, float: 6.6)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the default is a float or integer' do
|
||||
let(:specs) { { integer: { default: 6, type: 'number' }, float: { default: 6.6, type: 'number' } } }
|
||||
|
||||
it 'is valid' do
|
||||
expect(inputs).to be_valid
|
||||
expect(inputs.to_hash).to eq(integer: 6, float: 6.6)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the value is not a number' do
|
||||
let(:specs) { { number_input: { type: 'number' } } }
|
||||
let(:args) { { number_input: 'NaN' } }
|
||||
|
||||
it 'is invalid' do
|
||||
expect(inputs).not_to be_valid
|
||||
expect(inputs.errors).to contain_exactly('`number_input` input: provided value is not a number')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the default is not a number' do
|
||||
let(:specs) { { number_input: { default: 'NaN', type: 'number' } } }
|
||||
let(:args) { {} }
|
||||
|
||||
it 'is invalid' do
|
||||
expect(inputs).not_to be_valid
|
||||
expect(inputs.errors).to contain_exactly('`number_input` input: default value is not a number')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'boolean validation' do
|
||||
context 'when the value is true or false' do
|
||||
let(:specs) { { truthy: { type: 'boolean' }, falsey: { type: 'boolean' } } }
|
||||
let(:args) { { truthy: true, falsey: false } }
|
||||
|
||||
it 'is valid' do
|
||||
expect(inputs).to be_valid
|
||||
expect(inputs.to_hash).to eq(truthy: true, falsey: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the default is true or false' do
|
||||
let(:specs) { { truthy: { default: true, type: 'boolean' }, falsey: { default: false, type: 'boolean' } } }
|
||||
let(:args) { {} }
|
||||
|
||||
it 'is valid' do
|
||||
expect(inputs).to be_valid
|
||||
expect(inputs.to_hash).to eq(truthy: true, falsey: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the value is not a boolean' do
|
||||
let(:specs) { { boolean_input: { type: 'boolean' } } }
|
||||
let(:args) { { boolean_input: 'string' } }
|
||||
|
||||
it 'is invalid' do
|
||||
expect(inputs).not_to be_valid
|
||||
expect(inputs.errors).to contain_exactly('`boolean_input` input: provided value is not a boolean')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the default is not a boolean' do
|
||||
let(:specs) { { boolean_input: { default: 'string', type: 'boolean' } } }
|
||||
let(:args) { {} }
|
||||
|
||||
it 'is invalid' do
|
||||
expect(inputs).not_to be_valid
|
||||
expect(inputs.errors).to contain_exactly('`boolean_input` input: default value is not a boolean')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when given an unknown type' do
|
||||
let(:specs) { { unknown: { type: 'datetime' } } }
|
||||
let(:args) { { unknown: '2023-10-31' } }
|
||||
|
||||
it 'is invalid' do
|
||||
expect(inputs).not_to be_valid
|
||||
expect(inputs.errors).to contain_exactly(
|
||||
'unknown input specification for `unknown` (valid types: boolean, number, string)'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'inputs with RegEx validation' do
|
||||
context 'when given a value that matches the pattern' do
|
||||
let(:specs) { { test_input: { regex: '^input_value$' } } }
|
||||
let(:args) { { test_input: 'input_value' } }
|
||||
|
||||
it 'is valid' do
|
||||
expect(inputs).to be_valid
|
||||
expect(inputs.to_hash).to eq(test_input: 'input_value')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when given a default that matches the pattern' do
|
||||
let(:specs) { { test_input: { default: 'input_value', regex: '^input_value$' } } }
|
||||
let(:args) { {} }
|
||||
|
||||
it 'is valid' do
|
||||
expect(inputs).to be_valid
|
||||
expect(inputs.to_hash).to eq(test_input: 'input_value')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when given a value that does not match the pattern' do
|
||||
let(:specs) { { test_input: { regex: '^input_value$' } } }
|
||||
let(:args) { { test_input: 'input' } }
|
||||
|
||||
it 'is invalid' do
|
||||
expect(inputs).not_to be_valid
|
||||
expect(inputs.errors).to contain_exactly(
|
||||
'`test_input` input: provided value does not match required RegEx pattern'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when given a default that does not match the pattern' do
|
||||
let(:specs) { { test_input: { default: 'input', regex: '^input_value$' } } }
|
||||
let(:args) { {} }
|
||||
|
||||
it 'is invalid' do
|
||||
expect(inputs).not_to be_valid
|
||||
expect(inputs.errors).to contain_exactly(
|
||||
'`test_input` input: default value does not match required RegEx pattern'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when used with any type other than `string`' do
|
||||
let(:specs) { { test_input: { regex: '^input_value$', type: 'number' } } }
|
||||
let(:args) { { test_input: 999 } }
|
||||
|
||||
it 'is invalid' do
|
||||
expect(inputs).not_to be_valid
|
||||
expect(inputs.errors).to contain_exactly(
|
||||
'`test_input` input: RegEx validation can only be used with string inputs'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -40,6 +40,14 @@ RSpec.describe BulkImport, type: :model, feature_category: :importers do
|
|||
it { expect(described_class.min_gl_version_for_project_migration.to_s).to eq('14.4.0') }
|
||||
end
|
||||
|
||||
describe '#completed?' do
|
||||
it { expect(described_class.new(status: -1)).to be_completed }
|
||||
it { expect(described_class.new(status: 0)).not_to be_completed }
|
||||
it { expect(described_class.new(status: 1)).not_to be_completed }
|
||||
it { expect(described_class.new(status: 2)).to be_completed }
|
||||
it { expect(described_class.new(status: 3)).to be_completed }
|
||||
end
|
||||
|
||||
describe '#source_version_info' do
|
||||
it 'returns source_version as Gitlab::VersionInfo' do
|
||||
bulk_import = build(:bulk_import, source_version: '9.13.2')
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ChatName, feature_category: :integrations do
|
||||
let_it_be(:chat_name) { create(:chat_name) }
|
||||
let_it_be_with_reload(:chat_name) { create(:chat_name) }
|
||||
|
||||
subject { chat_name }
|
||||
|
||||
|
|
|
|||
|
|
@ -205,6 +205,12 @@ RSpec.describe Integrations::Bamboo, :use_clean_rails_memory_store_caching, feat
|
|||
end
|
||||
end
|
||||
|
||||
describe '#avatar_url' do
|
||||
it 'returns the avatar image path' do
|
||||
expect(subject.avatar_url).to eq(ActionController::Base.helpers.image_path('illustrations/third-party-logos/integrations-logos/atlassian-bamboo.svg'))
|
||||
end
|
||||
end
|
||||
|
||||
def stub_update_and_build_request(status: 200, body: nil)
|
||||
bamboo_full_url = 'http://gitlab.com/bamboo/updateAndBuild.action?buildKey=foo&os_authType=basic'
|
||||
|
||||
|
|
|
|||
|
|
@ -288,7 +288,6 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
|||
:create_build, :read_build, :update_build, :admin_build, :destroy_build,
|
||||
:create_pipeline_schedule, :read_pipeline_schedule_variables, :update_pipeline_schedule, :admin_pipeline_schedule, :destroy_pipeline_schedule,
|
||||
:create_environment, :read_environment, :update_environment, :admin_environment, :destroy_environment,
|
||||
:create_cluster, :read_cluster, :update_cluster, :admin_cluster, :destroy_cluster,
|
||||
:create_deployment, :read_deployment, :update_deployment, :admin_deployment, :destroy_deployment
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -377,6 +377,20 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'rejecting request with invalid params' do
|
||||
context 'with invalid maven path' do
|
||||
subject { download_file(file_name: package_file.file_name, path: 'foo/bar/%0d%0ahttp:/%2fexample.com') }
|
||||
|
||||
it_behaves_like 'returning response status with error', status: :bad_request, error: 'path should be a valid file path'
|
||||
end
|
||||
|
||||
context 'with invalid file name' do
|
||||
subject { download_file(file_name: '%0d%0ahttp:/%2fexample.com') }
|
||||
|
||||
it_behaves_like 'returning response status with error', status: :bad_request, error: 'file_name should be a valid file path'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v4/packages/maven/*path/:file_name' do
|
||||
context 'a public project' do
|
||||
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_maven_user' } }
|
||||
|
|
@ -403,6 +417,8 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do
|
|||
it_behaves_like 'returning response status', :forbidden
|
||||
end
|
||||
|
||||
it_behaves_like 'rejecting request with invalid params'
|
||||
|
||||
it 'returns not found when a package is not found' do
|
||||
finder = double('finder', execute: nil)
|
||||
expect(::Packages::Maven::PackageFinder).to receive(:new).and_return(finder)
|
||||
|
|
@ -444,6 +460,8 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do
|
|||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'rejecting request with invalid params'
|
||||
|
||||
it_behaves_like 'handling groups, subgroups and user namespaces for', 'getting a file', visibilities: { public: :redirect, internal: :not_found }
|
||||
end
|
||||
|
||||
|
|
@ -501,6 +519,8 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do
|
|||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'rejecting request with invalid params'
|
||||
|
||||
it_behaves_like 'handling groups, subgroups and user namespaces for', 'getting a file', visibilities: { public: :redirect, internal: :not_found, private: :not_found }
|
||||
end
|
||||
|
||||
|
|
@ -566,6 +586,8 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do
|
|||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'rejecting request with invalid params'
|
||||
|
||||
it_behaves_like 'handling groups and subgroups for', 'getting a file for a group'
|
||||
end
|
||||
|
||||
|
|
@ -597,6 +619,8 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do
|
|||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'rejecting request with invalid params'
|
||||
|
||||
it_behaves_like 'handling groups and subgroups for', 'getting a file for a group', visibilities: { internal: :unauthorized, public: :redirect }
|
||||
end
|
||||
|
||||
|
|
@ -634,6 +658,8 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do
|
|||
it_behaves_like 'returning response status', :redirect
|
||||
end
|
||||
|
||||
it_behaves_like 'rejecting request with invalid params'
|
||||
|
||||
context 'with group deploy token' do
|
||||
subject { download_file_with_token(file_name: package_file.file_name, request_headers: group_deploy_token_headers) }
|
||||
|
||||
|
|
@ -786,6 +812,8 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do
|
|||
|
||||
it_behaves_like 'returning response status', :redirect
|
||||
end
|
||||
|
||||
it_behaves_like 'rejecting request with invalid params'
|
||||
end
|
||||
|
||||
context 'private project' do
|
||||
|
|
@ -830,6 +858,8 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do
|
|||
|
||||
it_behaves_like 'returning response status', :redirect
|
||||
end
|
||||
|
||||
it_behaves_like 'rejecting request with invalid params'
|
||||
end
|
||||
|
||||
it_behaves_like 'forwarding package requests'
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ RSpec.describe ChatNames::FindUserService, :clean_gitlab_redis_shared_state, fea
|
|||
|
||||
context 'find user mapping' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:chat_name) { create(:chat_name, user: user) }
|
||||
let(:chat_name) { create(:chat_name, user: user) }
|
||||
|
||||
let(:team_id) { chat_name.team_id }
|
||||
let(:user_id) { chat_name.chat_id }
|
||||
|
|
@ -19,26 +19,20 @@ RSpec.describe ChatNames::FindUserService, :clean_gitlab_redis_shared_state, fea
|
|||
end
|
||||
|
||||
it 'updates the last used timestamp if one is not already set' do
|
||||
expect(chat_name.last_used_at).to be_nil
|
||||
|
||||
subject
|
||||
|
||||
expect(chat_name.reload.last_used_at).to be_present
|
||||
expect { subject }.to change { chat_name.reload.last_used_at }.from(nil)
|
||||
end
|
||||
|
||||
it 'only updates an existing timestamp once within a certain time frame' do
|
||||
chat_name = create(:chat_name, user: user)
|
||||
service = described_class.new(team_id, user_id)
|
||||
expect { described_class.new(team_id, user_id).execute }.to change { chat_name.reload.last_used_at }.from(nil)
|
||||
expect { described_class.new(team_id, user_id).execute }.not_to change { chat_name.reload.last_used_at }
|
||||
end
|
||||
|
||||
expect(chat_name.last_used_at).to be_nil
|
||||
it 'records activity for the related user' do
|
||||
expect_next_instance_of(Users::ActivityService, author: user) do |activity_service|
|
||||
expect(activity_service).to receive(:execute)
|
||||
end
|
||||
|
||||
service.execute
|
||||
|
||||
time = chat_name.reload.last_used_at
|
||||
|
||||
service.execute
|
||||
|
||||
expect(chat_name.reload.last_used_at).to eq(time)
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Users::SignupService, feature_category: :system_access do
|
||||
let_it_be(:user) { create(:user, setup_for_company: true) }
|
||||
|
||||
describe '#execute' do
|
||||
context 'when updating name' do
|
||||
it 'updates the name attribute' do
|
||||
result = update_user(user, name: 'New Name')
|
||||
|
||||
expect(result.success?).to be(true)
|
||||
expect(user.reload.name).to eq('New Name')
|
||||
end
|
||||
|
||||
it 'returns an error result when name is missing' do
|
||||
result = update_user(user, name: '')
|
||||
|
||||
expect(user.reload.name).not_to be_blank
|
||||
expect(result.success?).to be(false)
|
||||
expect(result.message).to include("Name can't be blank")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when updating role' do
|
||||
it 'updates the role attribute' do
|
||||
result = update_user(user, role: 'development_team_lead')
|
||||
|
||||
expect(result.success?).to be(true)
|
||||
expect(user.reload.role).to eq('development_team_lead')
|
||||
end
|
||||
|
||||
it 'returns an error result when role is missing' do
|
||||
result = update_user(user, role: '')
|
||||
|
||||
expect(user.reload.role).not_to be_blank
|
||||
expect(result.success?).to be(false)
|
||||
expect(result.message).to eq("Role can't be blank")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when updating setup_for_company' do
|
||||
it 'updates the setup_for_company attribute' do
|
||||
result = update_user(user, setup_for_company: 'false')
|
||||
|
||||
expect(result.success?).to be(true)
|
||||
expect(user.reload.setup_for_company).to be(false)
|
||||
end
|
||||
|
||||
it 'returns an error result when setup_for_company is missing' do
|
||||
result = update_user(user, setup_for_company: '')
|
||||
|
||||
expect(user.reload.setup_for_company).not_to be_blank
|
||||
expect(result.success?).to be(false)
|
||||
expect(result.message).to eq("Setup for company can't be blank")
|
||||
end
|
||||
end
|
||||
|
||||
def update_user(user, opts)
|
||||
described_class.new(user, opts).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -32,6 +32,16 @@ RSpec.describe BulkImportWorker, feature_category: :importers do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when bulk import is timeout' do
|
||||
it 'does nothing' do
|
||||
bulk_import = create(:bulk_import, :timeout)
|
||||
|
||||
expect(described_class).not_to receive(:perform_in)
|
||||
|
||||
subject.perform(bulk_import.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when all entities are processed' do
|
||||
it 'marks bulk import as finished' do
|
||||
bulk_import = create(:bulk_import, :started)
|
||||
|
|
|
|||
Loading…
Reference in New Issue