Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-10-05 12:10:06 +00:00
parent ae566b89cc
commit c8d44b1e3b
35 changed files with 455 additions and 307 deletions

View File

@ -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)) {

View File

@ -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 -->

View File

@ -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,
},
}),
);

View File

@ -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,
},
};

View File

@ -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

View File

@ -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'),

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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`:

View File

@ -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

View File

@ -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 ""

View File

@ -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

View File

@ -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

View File

@ -22,5 +22,9 @@ FactoryBot.define do
trait :failed do
status { -1 }
end
trait :timeout do
status { 3 }
end
end
end

View File

@ -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

View File

@ -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(

View File

@ -973,7 +973,7 @@ export const boardListQueryResponse = ({
boardList: {
__typename: 'BoardList',
id: listId,
totalWeight: 5,
totalIssueWeight: '5',
issuesCount,
},
},

View File

@ -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

View File

@ -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')

View File

@ -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 }

View File

@ -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'

View File

@ -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
]

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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)