Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-05-21 09:21:10 +00:00
parent 6c4f790271
commit aa72e920ce
38 changed files with 422 additions and 144 deletions

View File

@ -154,19 +154,6 @@ Layout/ArgumentAlignment:
- 'ee/lib/gitlab/geo/git_ssh_proxy.rb'
- 'ee/lib/gitlab/geo/replicator.rb'
- 'ee/lib/gitlab/graphql/aggregations/epics/epic_node.rb'
- 'ee/lib/gitlab/insights/executors/dora_executor.rb'
- 'ee/lib/gitlab/insights/executors/issuable_executor.rb'
- 'ee/lib/gitlab/status_page/pipeline/post_process_pipeline.rb'
- 'ee/lib/gitlab/subscription_portal/clients/graphql.rb'
- 'ee/lib/gitlab/zoekt/search_results.rb'
- 'ee/spec/components/billing/plan_component_spec.rb'
- 'ee/spec/graphql/ee/mutations/boards/lists/create_spec.rb'
- 'ee/spec/graphql/ee/mutations/ci/runner/update_spec.rb'
- 'ee/spec/lib/analytics/group_activity_calculator_spec.rb'
- 'ee/spec/lib/analytics/merge_request_metrics_calculator_spec.rb'
- 'ee/spec/lib/api/entities/protected_environments/approval_rule_for_summary_spec.rb'
- 'ee/spec/lib/api/entities/protected_environments/approval_rule_spec.rb'
- 'ee/spec/lib/api/entities/protected_environments/deploy_access_level_spec.rb'
- 'ee/spec/lib/ee/gitlab/scim/params_parser_spec.rb'
- 'ee/spec/lib/ee/gitlab/scim/provisioning_service_spec.rb'
- 'ee/spec/lib/ee/gitlab/usage/service_ping_report_spec.rb'

View File

@ -8,17 +8,14 @@ import {
EDITOR_APP_DRAWER_JOB_ASSISTANT,
EDITOR_APP_DRAWER_NONE,
pipelineEditorTrackingOptions,
TEMPLATE_REPOSITORY_URL,
} from '../../constants';
export default {
i18n: {
browseCatalog: __('Browse CI/CD Catalog'),
browseTemplates: __('Browse templates'),
help: __('Help'),
jobAssistant: s__('JobAssistant|Job assistant'),
},
TEMPLATE_REPOSITORY_URL,
components: {
GlButton,
},
@ -58,11 +55,6 @@ export default {
const { label, actions } = pipelineEditorTrackingOptions;
this.track(actions.openHelpDrawer, { label });
},
trackTemplateBrowsing() {
const { label, actions } = pipelineEditorTrackingOptions;
this.track(actions.browseTemplates, { label });
},
},
};
</script>
@ -82,16 +74,6 @@ export default {
>
{{ $options.i18n.browseCatalog }}
</gl-button>
<gl-button
:href="$options.TEMPLATE_REPOSITORY_URL"
size="small"
icon="external-link"
target="_blank"
data-testid="template-repo-link"
@click="trackTemplateBrowsing"
>
{{ $options.i18n.browseTemplates }}
</gl-button>
<gl-button
icon="information-o"
size="small"

View File

@ -88,8 +88,6 @@ export const pipelineEditorTrackingOptions = {
},
};
export const TEMPLATE_REPOSITORY_URL =
'https://gitlab.com/gitlab-org/gitlab-foss/tree/master/lib/gitlab/ci/templates';
export const VALIDATE_TAB_FEEDBACK_URL = 'https://gitlab.com/gitlab-org/gitlab/-/issues/346687';
export const COMMIT_SHA_POLL_INTERVAL = 1000;

View File

@ -0,0 +1,23 @@
<script>
import { GlSprintf } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
name: 'OrganizationGroupsEditApp',
components: { GlSprintf },
i18n: {
pageTitle: __('Edit group: %{group_name}'),
},
inject: ['group'],
};
</script>
<template>
<div class="gl-py-6">
<h1 class="gl-mt-0 gl-font-size-h-display">
<gl-sprintf :message="$options.i18n.pageTitle">
<template #group_name>{{ group.fullName }}</template>
</gl-sprintf>
</h1>
</div>
</template>

View File

@ -0,0 +1,26 @@
import Vue from 'vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import App from './components/app.vue';
export const initOrganizationsGroupsEdit = () => {
const el = document.getElementById('js-organizations-groups-edit');
if (!el) return false;
const {
dataset: { appData },
} = el;
const { group } = convertObjectPropsToCamelCase(JSON.parse(appData), { deep: true });
return new Vue({
el,
name: 'OrganizationGroupsEditRoot',
provide: {
group,
},
render(createElement) {
return createElement(App);
},
});
};

View File

@ -0,0 +1,3 @@
import { initOrganizationsGroupsEdit } from '~/organizations/groups/edit';
initOrganizationsGroupsEdit();

View File

@ -81,12 +81,20 @@ export default {
primaryModalProps() {
return {
text: this.$options.i18n.modalRemove,
attributes: { disabled: this.disableModalSubmit, variant: 'danger' },
attributes: {
disabled: this.disableModalSubmit,
variant: 'danger',
type: 'submit',
form: this.$options.removeFormId,
},
};
},
commandModalId() {
return `init-command-modal-${this.state.name}`;
},
modalInputId() {
return `terraform-state-remove-input-${this.state.name}`;
},
},
methods: {
hideModal() {
@ -188,6 +196,7 @@ export default {
this.showCommandModal = true;
},
},
removeFormId: 'remove-state-form',
};
</script>
@ -255,30 +264,26 @@ export default {
</gl-sprintf>
</template>
<p>
<gl-sprintf :message="$options.i18n.modalBody">
<template #name>
<span>{{ state.name }}</span>
</template>
</gl-sprintf>
</p>
<gl-form-group>
<template #label>
<gl-sprintf :message="$options.i18n.modalInputLabel">
<form :id="$options.removeFormId" @submit.prevent="remove">
<p>
<gl-sprintf :message="$options.i18n.modalBody">
<template #name>
<code>{{ state.name }}</code>
</template>
</gl-sprintf>
</template>
<gl-form-input
:id="`terraform-state-remove-input-${state.name}`"
ref="input"
v-model="removeConfirmText"
type="text"
@keyup.enter="remove"
/>
</gl-form-group>
</p>
<gl-form-group :label-for="modalInputId">
<template #label>
<gl-sprintf :message="$options.i18n.modalInputLabel">
<template #name>
<code>{{ state.name }}</code>
</template>
</gl-sprintf>
</template>
<gl-form-input :id="modalInputId" ref="input" v-model="removeConfirmText" type="text" />
</gl-form-group>
</form>
</gl-modal>
<init-command-modal

View File

@ -34,8 +34,8 @@
width: $chip-size;
height: $chip-size;
background: $white;
background-image: linear-gradient(135deg, $gray-dark 25%, transparent 0%, transparent 75%, $gray-dark 0%),
linear-gradient(135deg, $gray-dark 25%, transparent 0%, transparent 75%, $gray-dark 0%);
background-image: linear-gradient(135deg, $gray-100 25%, transparent 0%, transparent 75%, $gray-100 0%),
linear-gradient(135deg, $gray-100 25%, transparent 0%, transparent 75%, $gray-100 0%);
background-size: $bg-size $bg-size;
background-position: 0 0, $bg-pos $bg-pos;

View File

@ -83,37 +83,17 @@ $size-scale: (
);
// Color schema
$darken-normal-factor: 7% !default;
$darken-dark-factor: 10% !default;
$purple: #6d49cb !default;
$purple-light: #ede8fb !default;
$gray-light: $gray-10 !default;
$gray-lighter: lighten($gray-50, 4) !default;
$gray-normal: lighten($gray-50, 2) !default;
$gray-dark: darken($gray-light, $darken-dark-factor) !default;
$t-white-a-02: rgba(255, 255, 255, 0.02) !default;
$t-white-a-04: rgba(255, 255, 255, 0.04) !default;
$t-white-a-06: rgba(255, 255, 255, 0.06) !default;
$t-white-a-08: rgba(255, 255, 255, 0.08) !default;
$t-white-a-16: rgba(255, 255, 255, 0.16) !default;
$t-white-a-24: rgba(255, 255, 255, 0.24) !default;
$t-white-a-36: rgba(255, 255, 255, 0.36) !default;
$t-gray-a-02: rgba(31, 30, 36, 0.02) !default;
$t-gray-a-04: rgba(31, 30, 36, 0.04) !default;
$t-gray-a-06: rgba(31, 30, 36, 0.06) !default;
$t-gray-a-08: rgba(31, 30, 36, 0.08) !default;
$t-gray-a-16: rgba(31, 30, 36, 0.16) !default;
$t-gray-a-24: rgba(31, 30, 36, 0.24) !default;
/*
* UI elements
*/
$border-color: $gray-100;
$shadow-color: $t-gray-a-08;
$well-expand-item: #e8f2f7 !default;
/*

View File

@ -27,8 +27,8 @@ $dropdown-item-padding-x: 12px;
$popover-max-width: 300px;
$popover-border-width: 1px;
$popover-border-color: $border-color;
$popover-box-shadow: 0 $border-radius-small $gl-border-radius-base 0 $shadow-color;
$popover-arrow-outer-color: $shadow-color;
$popover-box-shadow: 0 $border-radius-small $gl-border-radius-base 0 $t-gray-a-08;
$popover-arrow-outer-color: $t-gray-a-08;
$h1-font-size: 14px * 2.5;
$h2-font-size: 14px * 2;
$h3-font-size: 14px * 1.75;

View File

@ -3,11 +3,6 @@
$gray-light: lighten($gray-10, 2);
$gray-lighter: darken($gray-50, 4);
$gray-normal: $gray-50;
$gray-dark: darken($gray-100, 2);
// Used for border and background in a couple instances where inverting between modes is desirable
// once migrated to suitable color values this can be removed
$t-gray-a-08: rgba($gray-950, 0.08);
$black-normal: $gray-900;

View File

@ -8,9 +8,13 @@ module Organizations
urgency :low, [:create, :new]
before_action :authorize_create_group!, only: [:new]
before_action :authorize_read_organization!, only: [:edit]
before_action :authorize_view_edit_page!, only: [:edit]
def new; end
def edit; end
def create
response = create_group
@group = response[:group]
@ -24,10 +28,20 @@ module Organizations
private
def group
@group ||= Group.in_organization(organization).find_by_full_path(params[:id])
end
def create_group
create_service_params = group_params.merge(organization_id: organization.id)
Groups::CreateService.new(current_user, create_service_params).execute
end
def authorize_view_edit_page!
return render_404 if group.nil?
access_denied! unless can?(current_user, :view_edit_page, group)
end
end
end

View File

@ -65,6 +65,12 @@ module Organizations
}.to_json
end
def organization_groups_edit_app_data(group)
{
group: group.slice(:full_name)
}.to_json
end
def admin_organizations_index_app_data
shared_organization_index_app_data.to_json
end

View File

@ -4,6 +4,7 @@ class DraftNote < ApplicationRecord
include Gitlab::Utils::StrongMemoize
include Sortable
include ShaAttribute
include BulkInsertSafe
PUBLISH_ATTRS = %i[noteable type note internal].freeze
DIFF_ATTRS = %i[position original_position change_position commit_id].freeze

View File

@ -21,6 +21,7 @@ class RemoteMirror < ApplicationRecord
belongs_to :project, inverse_of: :remote_mirrors
validates :url, presence: true, public_url: { schemes: Project::VALID_MIRROR_PROTOCOLS, allow_blank: true, enforce_user: true }
validates :only_protected_branches, inclusion: { in: [true, false], message: :blank }
before_validation :store_credentials
after_update :reset_fields, if: :saved_change_to_mirror_url?

View File

@ -0,0 +1,5 @@
- page_title _("Edit"), @group.name, _("Edit")
- add_to_breadcrumbs _('Groups and projects'), groups_and_projects_organization_path(@organization, { display: 'groups' })
- add_to_breadcrumbs @group.name, group_path(@group)
#js-organizations-groups-edit{ data: { app_data: organization_groups_edit_app_data(@group) } }

View File

@ -16,6 +16,18 @@ resources(:organizations, only: [:show, :index, :new, :create], param: :organiza
resource :groups, only: [:new, :create], as: :groups_organization
scope(
path: 'groups/*id',
constraints: { id: Gitlab::PathRegex.full_namespace_route_regex }
) do
resource(
:groups,
path: '/',
only: [:edit],
as: :groups_organization
)
end
scope(
path: 'projects/*namespace_id',
as: :namespace,

View File

@ -23,3 +23,4 @@ tokens:
- "GitLab (v)?11."
- "GitLab (v)?12."
- "GitLab (v)?13."
- "GitLab (v)?14."

View File

@ -15,7 +15,7 @@ swap:
active users: "billable users"
air(?:-| )?gapped: "offline environment"
bullet: "list item"
click: "select"
(?<!right-)click(?!-through): "select"
code base: "codebase"
config: "configuration"
confirmation box: "confirmation dialog"

View File

@ -133,7 +133,6 @@ Choose the one or three step method according to your registry installation.
#### One-step migration
WARNING:
WARNING:
The registry must be shut down or remain in `read-only` mode during the migration.
Only choose this method if you do not need to write to the registry during the migration

View File

@ -160,7 +160,7 @@ the feature must request to the [AI Gateway](../../architecture/blueprints/ai_ga
1. Verify AI feature by calling the following in the rails console:
```ruby
Gitlab::Llm::AiGateway::Client.new(User.first).stream(prompt: "\n\nHuman: Hi, how are you?\n\nAssistant:")
Gitlab::Llm::AiGateway::Client.new(User.first).stream(prompt: [{role: "user", content: "Hi, how are you?"}])
```
### Verify the setup with GraphQL
@ -218,7 +218,7 @@ it will print useful error messages with links to the docs on how to resolve the
GITLAB_SIMULATE_SAAS=1 RAILS_ENV=development bundle exec rake 'gitlab:duo:setup[<test-group-name>]'
```
[AI Gateway](#set-up) still needs to be setup when using the automated setup.
[AI Gateway](#test-ai-features-with-ai-gateway-locally) still needs to be setup when using the automated setup.
**Manual way**
@ -241,7 +241,7 @@ GITLAB_SIMULATE_SAAS=1 RAILS_ENV=development bundle exec rake 'gitlab:duo:setup[
1. Enable **Experiment & Beta features**.
1. Enable the specific feature flag for the feature you want to test.
1. You can use Rake task `rake gitlab:duo:enable_feature_flags` to enable all feature flags that are assigned to group AI Framework.
1. Setup [AI Gateway](#set-up).
1. Setup [AI Gateway](#test-ai-features-with-ai-gateway-locally).
### Help

View File

@ -26,7 +26,7 @@ For each scanner, an analyzer:
- Handles its execution.
- Converts its output to a [standard format](../terminology/index.md#secure-report-format).
## SAST analyzers
## Official analyzers
SAST supports the following official analyzers:
@ -36,8 +36,9 @@ SAST supports the following official analyzers:
- [`sobelow`](https://gitlab.com/gitlab-org/security-products/analyzers/sobelow) (Sobelow (Elixir Phoenix))
- [`spotbugs`](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs) (SpotBugs with the Find Sec Bugs plugin (Ant, Gradle and wrapper, Grails, Maven and wrapper, SBT))
The following analyzers reached End of Support status and do not receive updates. They were replaced
by the `semgrep` analyzer with GitLab-managed rules.
The following GitLab analyzers have reached [End of Support](../../../update/terminology.md#end-of-support)
status and do not receive updates. They were replaced by the Semgrep-based analyzer with
GitLab-managed rules.
- [`bandit`](https://gitlab.com/gitlab-org/security-products/analyzers/bandit) (Bandit); [End of Support](https://gitlab.com/gitlab-org/gitlab/-/issues/352554) in GitLab 15.4.
- [`brakeman`](https://gitlab.com/gitlab-org/security-products/analyzers/brakeman) (Brakeman); [End of Support](https://gitlab.com/gitlab-org/gitlab/-/issues/412060) in GitLab 17.0.

View File

@ -91,7 +91,9 @@ For more information about our plans for language support in SAST, see the [cate
## End of supported analyzers
GitLab has reached End of Support for the below analyzers. These analyzers have been replaced by the Semgrep-based analyzer.
The following GitLab analyzers have reached [End of Support](../../../update/terminology.md#end-of-support)
status and do not receive updates. They were replaced by the Semgrep-based analyzer with
GitLab-managed rules.
| Language / framework | [Analyzer](analyzers.md) used for scanning | Minimum supported GitLab version | End Of Support GitLab version |
|------------------------------|--------------------------------------------------------------------------------------------------------------| --------------------------------- | ------------------------------------------------------------- |

View File

@ -285,7 +285,7 @@ Configure FortiToken Cloud in GitLab. On your GitLab server:
### Set up a WebAuthn device
> - Optional one-time password authentication for WebAuthn devices [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/378844) in GitLab 15.10 [with a flag](../../../administration/feature_flags.md) named `webauthn_without_topt`. [Enabled on GitLab.com and self-managed by default](https://gitlab.com/gitlab-org/gitlab/-/issues/232671).
> - Optional one-time password authentication for WebAuthn devices [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/378844) in GitLab 15.10 [with a flag](../../../administration/feature_flags.md) named `webauthn_without_totp`. [Enabled on GitLab.com and self-managed by default](https://gitlab.com/gitlab-org/gitlab/-/issues/232671).
FLAG:
On self-managed GitLab, by default, optional one-time password authentication for WebAuthn devices is not available. To enable the feature, an administrator can [enable the feature flag](../../../administration/feature_flags.md) named `webauthn_without_totp`.

View File

@ -82,7 +82,6 @@ module API
optional :squash_option, type: String, values: %w[never always default_on default_off], desc: 'Squash default for project. One of `never`, `always`, `default_on`, or `default_off`.'
optional :mr_default_target_self, type: Boolean, desc: 'Merge requests of this forked project targets itself by default'
optional :warn_about_potentially_unwanted_characters, type: Boolean, desc: 'Warn about potentially unwanted characters'
optional :repository_object_format, type: String, values: %w[sha1 sha256], desc: 'The object format of the project repository'
end
params :optional_project_params_ee do
@ -93,11 +92,16 @@ module API
use :optional_project_params_ee
end
params :optional_create_project_params_ce do
optional :repository_object_format, type: String, values: %w[sha1 sha256], desc: 'The object format of the project repository'
end
params :optional_create_project_params_ee do
end
params :optional_create_project_params do
use :optional_project_params
use :optional_create_project_params_ce
use :optional_create_project_params_ee
end

View File

@ -11,6 +11,12 @@ module API
unauthorized! unless can?(current_user, :admin_remote_mirror, user_project)
end
helpers do
def find_remote_mirror
user_project.remote_mirrors.find(params[:mirror_id])
end
end
params do
requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'
end
@ -44,7 +50,7 @@ module API
requires :mirror_id, type: String, desc: 'The ID of a remote mirror'
end
get ':id/remote_mirrors/:mirror_id' do
mirror = user_project.remote_mirrors.find(params[:mirror_id])
mirror = find_remote_mirror
present mirror, with: Entities::RemoteMirror
end
@ -62,7 +68,7 @@ module API
requires :mirror_id, type: String, desc: 'The ID of a remote mirror'
end
post ':id/remote_mirrors/:mirror_id/sync' do
mirror = user_project.remote_mirrors.find(params[:mirror_id])
mirror = find_remote_mirror
result = ::RemoteMirrors::SyncService.new(user_project, current_user).execute(mirror)
@ -137,7 +143,7 @@ module API
use :mirror_branches_setting
end
put ':id/remote_mirrors/:mirror_id' do
mirror = user_project.remote_mirrors.find(params[:mirror_id])
mirror = find_remote_mirror
mirror_params = declared_params(include_missing: false)
mirror_params[:id] = mirror_params.delete(:mirror_id)
@ -152,7 +158,7 @@ module API
if result[:status] == :success
present mirror.reset, with: Entities::RemoteMirror
else
render_api_error!(result[:message], result[:http_status])
render_api_error!(result[:message], 400)
end
end
@ -170,7 +176,7 @@ module API
requires :mirror_id, type: String, desc: 'The ID of a remote mirror'
end
delete ':id/remote_mirrors/:mirror_id' do
mirror = user_project.remote_mirrors.find(params[:mirror_id])
mirror = find_remote_mirror
destroy_conditionally!(mirror) do
mirror_params = declared_params(include_missing: false).merge(_destroy: 1)

View File

@ -51,6 +51,7 @@ module Sidebars
organizations/organizations#groups_and_projects
organizations/groups#new
organizations/projects#edit
organizations/groups#edit
]
},
item_id: :organization_groups_and_projects

View File

@ -9488,9 +9488,6 @@ msgstr ""
msgid "Browse files"
msgstr ""
msgid "Browse templates"
msgstr ""
msgid "Build cannot be erased"
msgstr ""

View File

@ -4,7 +4,6 @@ import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import CiEditorHeader from '~/ci/pipeline_editor/components/editor/ci_editor_header.vue';
import {
pipelineEditorTrackingOptions,
TEMPLATE_REPOSITORY_URL,
EDITOR_APP_DRAWER_HELP,
EDITOR_APP_DRAWER_NONE,
} from '~/ci/pipeline_editor/constants';
@ -37,7 +36,6 @@ describe('CI Editor Header', () => {
);
};
const findLinkBtn = () => wrapper.findByTestId('template-repo-link');
const findHelpBtn = () => wrapper.findByTestId('drawer-toggle');
const findCatalogRepoLinkButton = () => wrapper.findByTestId('catalog-repo-link');
@ -70,10 +68,6 @@ describe('CI Editor Header', () => {
expect(findCatalogRepoLinkButton().exists()).toBe(true);
});
it('has the external-link icon', () => {
expect(findCatalogRepoLinkButton().props('icon')).toBe('external-link');
});
it('tracks the click on the Catalog button', () => {
const { browseCatalog } = pipelineEditorTrackingOptions.actions;
@ -81,31 +75,6 @@ describe('CI Editor Header', () => {
});
});
describe('link button', () => {
beforeEach(() => {
createComponent();
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
});
it('finds the browse template button', () => {
expect(findLinkBtn().exists()).toBe(true);
});
it('contains the link to the template repo', () => {
expect(findLinkBtn().attributes('href')).toBe(TEMPLATE_REPOSITORY_URL);
});
it('has the external-link icon', () => {
expect(findLinkBtn().props('icon')).toBe('external-link');
});
it('tracks the click on the browse button', () => {
const { browseTemplates } = pipelineEditorTrackingOptions.actions;
testTracker(findLinkBtn(), browseTemplates);
});
});
describe('help button', () => {
beforeEach(() => {
createComponent();

View File

@ -0,0 +1,30 @@
import { GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import App from '~/organizations/groups/edit/components/app.vue';
describe('OrganizationGroupsEditApp', () => {
let wrapper;
const defaultProvide = {
group: {
fullName: 'Mock namespace / Foo bar',
},
};
const createComponent = () => {
wrapper = shallowMountExtended(App, {
provide: defaultProvide,
stubs: {
GlSprintf,
},
});
};
it('renders page title', () => {
createComponent();
expect(
wrapper.findByRole('heading', { name: 'Edit group: Mock namespace / Foo bar' }).exists(),
).toBe(true);
});
});

View File

@ -310,6 +310,20 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
end
end
describe '#organization_groups_edit_app_data' do
let_it_be(:group) { build_stubbed(:group, organization: organization) }
it 'returns expected json' do
expect(Gitlab::Json.parse(helper.organization_groups_edit_app_data(group))).to eq(
{
'group' => {
'full_name' => group.full_name
}
}
)
end
end
describe '#admin_organizations_index_app_data' do
it 'returns expected json' do
expect(Gitlab::Json.parse(helper.admin_organizations_index_app_data)).to eq(

View File

@ -2,11 +2,16 @@
require 'spec_helper'
RSpec.describe RemoteMirror, :mailer do
RSpec.describe RemoteMirror, :mailer, feature_category: :source_code_management do
before do
stub_feature_flags(remote_mirror_no_delay: false)
end
describe 'validations' do
it { is_expected.to allow_value(true, false).for(:only_protected_branches) }
it { is_expected.not_to allow_value(nil).for(:only_protected_branches) }
end
describe 'URL validation' do
context 'with a valid URL' do
it 'is valid' do

View File

@ -260,16 +260,6 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do
expect(json_response['pipelines']).to be_empty
end
it 'does not result in additional queries', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/439528' do # rubocop:disable Layout/LineLength -- We prefer to keep it on a single line, for simplicity sake
control = ActiveRecord::QueryRecorder.new do
get api(package_url, user)
end
expect do
get api(package_url, user)
end.not_to exceed_query_limit(control).with_threshold(4)
end
end
context 'project is public' do

View File

@ -4524,6 +4524,15 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
end
end
context 'with repository_object_format' do
it 'ignores repositotory object format field' do
put api(path, user), params: { name: 'new', repository_object_format: 'sha256' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['repository_object_format']).to eq 'sha1'
end
end
context 'when authenticated as project developer' do
it 'does not update other attributes' do
project_param = { path: 'bar',

View File

@ -201,6 +201,19 @@ RSpec.describe API::RemoteMirrors, feature_category: :source_code_management do
expect(json_response['error']).to eq('auth_method does not have a valid value')
end
end
context 'when only_protected_branches is not set' do
let(:params) { { url: 'https://foo:bar@test.com', enabled: true, only_protected_branches: nil } }
it 'returns an error' do
project.add_maintainer(user)
post api(route, user), params: params
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']['only_protected_branches']).to match_array(["can't be blank"])
end
end
end
describe 'PUT /projects/:id/remote_mirrors/:mirror_id' do
@ -227,6 +240,32 @@ RSpec.describe API::RemoteMirrors, feature_category: :source_code_management do
expect(json_response['only_protected_branches']).to eq(true)
expect(json_response['keep_divergent_refs']).to eq(true)
end
context 'when auth method is invalid' do
let(:params) { { enabled: true, auth_method: 'invalid' } }
it 'returns an error' do
project.add_maintainer(user)
put api(route, user), params: params
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq('Remote mirrors auth method is not included in the list')
end
end
context 'when only_protected_branches is not set' do
let(:params) { { enabled: true, only_protected_branches: nil } }
it 'returns an error' do
project.add_maintainer(user)
put api(route, user), params: params
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq("Remote mirrors only protected branches can't be blank")
end
end
end
describe 'DELETE /projects/:id/remote_mirrors/:mirror_id' do

View File

@ -93,4 +93,105 @@ RSpec.describe Organizations::GroupsController, feature_category: :cell do
end
end
end
describe 'GET #edit' do
let_it_be(:group) { create(:group, organization: organization) }
context 'when group exists' do
subject(:gitlab_request) do
get edit_groups_organization_path(organization, id: group.to_param)
end
context 'when the user is not signed in' do
it_behaves_like 'organization - redirects to sign in page'
context 'when `ui_for_organizations` feature flag is disabled' do
before do
stub_feature_flags(ui_for_organizations: false)
end
it_behaves_like 'organization - redirects to sign in page'
end
end
context 'when the user is signed in' do
let_it_be(:user) { create(:user) }
before do
sign_in(user)
end
context 'as as admin', :enable_admin_mode do
let_it_be(:user) { create(:admin) }
it_behaves_like 'organization - successful response'
it_behaves_like 'organization - action disabled by `ui_for_organizations` feature flag'
end
context 'as a group owner' do
before_all do
group.add_owner(user)
end
it_behaves_like 'organization - successful response'
it_behaves_like 'organization - action disabled by `ui_for_organizations` feature flag'
end
context 'as a user that is not an owner' do
it_behaves_like 'organization - not found response'
it_behaves_like 'organization - action disabled by `ui_for_organizations` feature flag'
end
context 'as an organization owner' do
let_it_be(:user) do
organization_user = create(:organization_owner, organization: organization)
organization_user.user
end
it_behaves_like 'organization - successful response'
it_behaves_like 'organization - action disabled by `ui_for_organizations` feature flag'
end
end
end
context 'when group is not in organization' do
let_it_be(:user) { create(:user) }
let_it_be(:organization_2) { create(:organization) }
subject(:gitlab_request) do
get edit_groups_organization_path(organization_2, id: group.to_param)
end
before_all do
group.add_owner(user)
end
before do
sign_in(user)
end
it_behaves_like 'organization - not found response'
it_behaves_like 'organization - action disabled by `ui_for_organizations` feature flag'
end
context 'when group does not exist' do
subject(:gitlab_request) do
get edit_groups_organization_path(organization, id: 'group-that-does-not-exist')
end
context 'when the user is not signed in' do
it_behaves_like 'organization - redirects to sign in page'
end
context 'when the user is signed in' do
let_it_be(:user) { create(:user) }
before do
sign_in(user)
end
it_behaves_like 'organization - not found response'
end
end
end
end

View File

@ -4,9 +4,19 @@ require 'spec_helper'
RSpec.describe Organizations::GroupsController, :routing, feature_category: :cell do
let_it_be(:organization) { build(:organization) }
let_it_be(:group) { build(:group, organization: organization) }
it 'routes to groups#new' do
expect(get("/-/organizations/#{organization.path}/groups/new"))
.to route_to('organizations/groups#new', organization_path: organization.path)
end
it 'routes to groups#edit' do
expect(get("/-/organizations/#{organization.path}/groups/#{group.full_path}/edit"))
.to route_to(
'organizations/groups#edit',
organization_path: organization.path,
id: group.to_param
)
end
end

View File

@ -0,0 +1,62 @@
package senddata
import (
"encoding/base64"
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
const testPrefix = "test:"
func TestPrefixMatch(t *testing.T) {
tests := []struct {
name string
input string
expectedMatch bool
}{
{"Match with correct prefix", "test:sendData", true},
{"Match with correct prefix and nested data", "test:otherData:nestedData", true},
{"Does not match with wrong prefix", "another:sendData", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := require.New(t)
prefix := Prefix(testPrefix)
name := prefix.Name()
r.Contains(testPrefix, name)
r.Equal(tt.expectedMatch, prefix.Match(tt.input))
})
}
}
func TestPrefixUnpack(t *testing.T) {
tests := []struct {
name string
inputData string
expectedResult string
}{
{"Valid JSON data encoded with base64", "test data", "test data"},
{"Invalid base64 encoded data", "invalid_base64_encoded_data", "invalid_base64_encoded_data"},
{"Invalid JSON data encoded with base64", base64.URLEncoding.EncodeToString([]byte("invalid_json")), "aW52YWxpZF9qc29u"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := require.New(t)
jsonBytes, err := json.Marshal(tt.inputData)
r.NoError(err)
sendData := base64.URLEncoding.EncodeToString(jsonBytes)
prefix := Prefix(testPrefix)
var result string
err = prefix.Unpack(&result, testPrefix+sendData)
r.NoError(err)
r.Equal(tt.expectedResult, result)
})
}
}