Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-11-03 21:10:35 +00:00
parent bbc36645d3
commit 16f41a5b04
43 changed files with 778 additions and 454 deletions

View File

@ -1263,16 +1263,12 @@
.rails:rules:detect-previous-failed-tests: .rails:rules:detect-previous-failed-tests:
rules: rules:
- <<: *if-not-canonical-namespace
when: never
- <<: *if-merge-request-labels-run-all-rspec - <<: *if-merge-request-labels-run-all-rspec
- <<: *if-merge-request - <<: *if-merge-request
changes: *code-backstage-patterns changes: *code-backstage-patterns
.rails:rules:rerun-previous-failed-tests: .rails:rules:rerun-previous-failed-tests:
rules: rules:
- <<: *if-not-canonical-namespace
when: never
- <<: *if-merge-request-labels-run-all-rspec - <<: *if-merge-request-labels-run-all-rspec
- <<: *if-merge-request - <<: *if-merge-request
changes: *code-backstage-patterns changes: *code-backstage-patterns

View File

@ -39,7 +39,6 @@ Rails/SaveBang:
- 'ee/spec/models/license_spec.rb' - 'ee/spec/models/license_spec.rb'
- 'ee/spec/models/merge_request_spec.rb' - 'ee/spec/models/merge_request_spec.rb'
- 'ee/spec/models/merge_train_spec.rb' - 'ee/spec/models/merge_train_spec.rb'
- 'spec/models/packages/package_spec.rb'
- 'ee/spec/models/project_ci_cd_setting_spec.rb' - 'ee/spec/models/project_ci_cd_setting_spec.rb'
- 'ee/spec/models/project_spec.rb' - 'ee/spec/models/project_spec.rb'
- 'ee/spec/models/protected_environment_spec.rb' - 'ee/spec/models/protected_environment_spec.rb'
@ -135,32 +134,6 @@ Rails/SaveBang:
- 'spec/lib/gitlab/middleware/go_spec.rb' - 'spec/lib/gitlab/middleware/go_spec.rb'
- 'spec/lib/gitlab/shard_health_cache_spec.rb' - 'spec/lib/gitlab/shard_health_cache_spec.rb'
- 'spec/mailers/notify_spec.rb' - 'spec/mailers/notify_spec.rb'
- 'spec/models/clusters/applications/helm_spec.rb'
- 'spec/models/design_management/version_spec.rb'
- 'spec/models/environment_spec.rb'
- 'spec/models/event_spec.rb'
- 'spec/models/fork_network_spec.rb'
- 'spec/models/generic_commit_status_spec.rb'
- 'spec/models/grafana_integration_spec.rb'
- 'spec/models/group_spec.rb'
- 'spec/models/identity_spec.rb'
- 'spec/models/jira_import_state_spec.rb'
- 'spec/models/namespace_spec.rb'
- 'spec/models/note_spec.rb'
- 'spec/models/notification_setting_spec.rb'
- 'spec/models/operations/feature_flag_scope_spec.rb'
- 'spec/models/operations/feature_flags/strategy_spec.rb'
- 'spec/models/operations/feature_flags/user_list_spec.rb'
- 'spec/models/pages_domain_spec.rb'
- 'spec/models/protectable_dropdown_spec.rb'
- 'spec/models/redirect_route_spec.rb'
- 'spec/models/release_spec.rb'
- 'spec/models/remote_mirror_spec.rb'
- 'spec/models/resource_milestone_event_spec.rb'
- 'spec/models/route_spec.rb'
- 'spec/models/sentry_issue_spec.rb'
- 'spec/models/snippet_spec.rb'
- 'spec/models/upload_spec.rb'
Rails/TimeZone: Rails/TimeZone:
Enabled: true Enabled: true

View File

@ -3064,6 +3064,10 @@ No changes.
- [Add missing metrics information](gitlab-org/gitlab@89cd7fe3b95323e635b2d73e08549b2e6153dc4d) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61772/edit)) - [Add missing metrics information](gitlab-org/gitlab@89cd7fe3b95323e635b2d73e08549b2e6153dc4d) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61772/edit))
- [Track usage of the resolve UI](gitlab-org/gitlab@35c8e30fce288cecefcf2f7c0077d4608e696519) ([merge request](gitlab-org/gitlab!61654)) - [Track usage of the resolve UI](gitlab-org/gitlab@35c8e30fce288cecefcf2f7c0077d4608e696519) ([merge request](gitlab-org/gitlab!61654))
## 13.12.15 (2021-11-03)
No changes.
## 13.12.14 (2021-11-03) ## 13.12.14 (2021-11-03)
### Fixed (2 changes) ### Fixed (2 changes)

View File

@ -0,0 +1,69 @@
<script>
import { GlCollapse, GlIcon, GlBadge, GlLink } from '@gitlab/ui';
import folderQuery from '../graphql/queries/folder.query.graphql';
export default {
components: {
GlCollapse,
GlIcon,
GlBadge,
GlLink,
},
props: {
nestedEnvironment: {
type: Object,
required: true,
},
},
data() {
return { visible: false };
},
apollo: {
folder: {
query: folderQuery,
variables() {
return { environment: this.nestedEnvironment.latest };
},
},
},
computed: {
icons() {
return this.visible
? { caret: 'angle-down', folder: 'folder-open' }
: { caret: 'angle-right', folder: 'folder-o' };
},
count() {
return this.folder?.availableCount ?? 0;
},
folderClass() {
return { 'gl-font-weight-bold': this.visible };
},
folderPath() {
return this.nestedEnvironment.latest.folderPath;
},
},
methods: {
toggleCollapse() {
this.visible = !this.visible;
},
},
};
</script>
<template>
<div class="gl-border-b-solid gl-border-gray-100 gl-border-1 gl-px-3 gl-pt-3 gl-pb-5">
<div class="gl-w-full gl-display-flex gl-align-items-center" @click="toggleCollapse">
<gl-icon
class="gl-mr-2 gl-fill-current-color gl-text-gray-500"
:name="icons.caret"
:size="12"
/>
<gl-icon class="gl-mr-2 gl-fill-current-color gl-text-gray-500" :name="icons.folder" />
<div class="gl-mr-2 gl-text-gray-500" :class="folderClass">
{{ nestedEnvironment.name }}
</div>
<gl-badge size="sm" class="gl-mr-auto">{{ count }}</gl-badge>
<gl-link v-if="visible" :href="folderPath">{{ s__('Environments|Show all') }}</gl-link>
</div>
<gl-collapse :visible="visible" />
</div>
</template>

View File

@ -1,6 +1,47 @@
<script> <script>
export default {}; import { GlBadge, GlTab, GlTabs } from '@gitlab/ui';
import environmentAppQuery from '../graphql/queries/environmentApp.query.graphql';
import EnvironmentFolder from './new_environment_folder.vue';
export default {
components: {
EnvironmentFolder,
GlBadge,
GlTab,
GlTabs,
},
apollo: {
environmentApp: {
query: environmentAppQuery,
},
},
computed: {
folders() {
return this.environmentApp?.environments.filter((e) => e.size > 1) ?? [];
},
availableCount() {
return this.environmentApp?.availableCount;
},
},
};
</script> </script>
<template> <template>
<div></div> <div>
<gl-tabs>
<gl-tab>
<template #title>
<span>{{ __('Available') }}</span>
<gl-badge size="sm" class="gl-tab-counter-badge">
{{ availableCount }}
</gl-badge>
</template>
<environment-folder
v-for="folder in folders"
:key="folder.name"
class="gl-mb-3"
:nested-environment="folder"
/>
</gl-tab>
</gl-tabs>
</div>
</template> </template>

View File

@ -87,7 +87,7 @@ export default {
v-if="!latestPipeline" v-if="!latestPipeline"
:empty-state-svg-path="pipelinesEmptyStateSvgPath" :empty-state-svg-path="pipelinesEmptyStateSvgPath"
:can-set-ci="true" :can-set-ci="true"
class="mb-auto mt-auto" class="gl-p-5"
/> />
<gl-alert <gl-alert
v-else-if="latestPipeline.yamlError" v-else-if="latestPipeline.yamlError"

View File

@ -76,7 +76,7 @@ export default {
</p> </p>
<div class="row gl-mb-8"> <div class="row gl-mb-8">
<div class="col-lg-3"> <div class="col-12">
<gl-card> <gl-card>
<div class="gl-flex-direction-row"> <div class="gl-flex-direction-row">
<div class="gl-py-5"><gl-emoji class="gl-font-size-h2-xl" data-name="wave" /></div> <div class="gl-py-5"><gl-emoji class="gl-font-size-h2-xl" data-name="wave" /></div>

View File

@ -58,7 +58,7 @@ To import bare repositories into a GitLab instance:
- Omnibus Installation - Omnibus Installation
```shell ```shell
sudo gitlab-rake gitlab:import:repos['/var/opt/gitlab/git-data/repository-import-$(date "+%Y-%m-%d")'] sudo gitlab-rake gitlab:import:repos["/var/opt/gitlab/git-data/repository-import-$(date "+%Y-%m-%d")"]
``` ```
- Installation from source. Before running this command you need to change to the directory where - Installation from source. Before running this command you need to change to the directory where
@ -66,7 +66,7 @@ To import bare repositories into a GitLab instance:
```shell ```shell
cd /home/git/gitlab cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:import:repos['/var/opt/gitlab/git-data/repository-import-$(date "+%Y-%m-%d")'] RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:import:repos["/var/opt/gitlab/git-data/repository-import-$(date "+%Y-%m-%d")"] RAILS_ENV=production
``` ```
## Example output ## Example output

View File

@ -9,9 +9,13 @@ type: reference, concepts
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/39060) in GitLab 12.8. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/39060) in GitLab 12.8.
Merge request approval rules prevent users from overriding certain settings on the project Merge request approval rules prevent users from overriding certain settings on the project level.
level. When enabled at the instance level, these settings are no longer editable on the When enabled at the instance level, these settings [cascade](../project/merge_requests/approvals/settings.md#settings-cascading)
project level. and can no longer be changed:
- In projects.
- In groups. Cascading to groups was [enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/285410)
in GitLab 14.5.
To enable merge request approval settings for an instance: To enable merge request approval settings for an instance:
@ -24,15 +28,14 @@ To enable merge request approval settings for an instance:
Merge request approval settings that can be set at an instance level are: Merge request approval settings that can be set at an instance level are:
- **Prevent approval by author**. Prevents project - **Prevent approval by author**. Prevents project maintainers from allowing request authors to
maintainers from allowing request authors to merge their own merge requests. merge their own merge requests.
- **Prevent approvals by users who add commits**. Prevents project - **Prevent approvals by users who add commits**. Prevents project maintainers from allowing users
maintainers from allowing users to approve merge requests if they have submitted to approve merge requests if they have submitted any commits to the source branch.
any commits to the source branch. - **Prevent editing approval rules in projects and merge requests**. Prevents users from modifying
- **Prevent editing approval rules in projects and merge requests**. Prevents users from the approvers list in project settings or in individual merge requests.
modifying the approvers list in project settings or in individual merge requests.
See also the following, which are affected by instance-level rules: See also the following, which are affected by instance-level rules:
- [Project-level merge request approval rules](../project/merge_requests/approvals/index.md). - [Project merge request approval rules](../project/merge_requests/approvals/index.md).
- [Group-level merge request approval rules](../group/index.md#group-approval-rules) available in GitLab 13.9 and later. - [Group merge request approval rules](../group/index.md#group-approval-rules) available in GitLab 13.9 and later.

View File

@ -7,9 +7,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Migrate groups from another instance of GitLab **(FREE)** # Migrate groups from another instance of GitLab **(FREE)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/249160) in GitLab 13.7. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/249160) in GitLab 13.7 [with a flag](../../feature_flags.md) named `bulk_import`. Disabled by default.
> - [Deployed behind a feature flag](../../feature_flags.md), disabled by default. > - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/338985) in GitLab 14.3.
> - Enabled on GitLab.com.
NOTE: NOTE:
The importer migrates **only** the group data listed on this page. To leave feedback on this The importer migrates **only** the group data listed on this page. To leave feedback on this
@ -77,14 +76,9 @@ Any other items are **not** migrated.
## Enable or disable GitLab Group Migration ## Enable or disable GitLab Group Migration
GitLab Migration is deployed behind a feature flag that is **enabled by default**. GitLab Migration is deployed behind the `bulk_import` feature flag, which is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) can enable it. [GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can disable it.
To enable it:
```ruby
Feature.enable(:bulk_import)
```
To disable it: To disable it:
@ -92,6 +86,12 @@ To disable it:
Feature.disable(:bulk_import) Feature.disable(:bulk_import)
``` ```
To enable it:
```ruby
Feature.enable(:bulk_import)
```
## Import your groups into GitLab ## Import your groups into GitLab
Before you begin, ensure that the target instance of GitLab can communicate with the source Before you begin, ensure that the target instance of GitLab can communicate with the source

View File

@ -742,12 +742,11 @@ The group's new subgroups have push rules set for them based on either:
FLAG: FLAG:
On self-managed GitLab, by default this feature is available. To hide the feature per group, ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `group_merge_request_approval_settings_feature_flag`. On GitLab.com, this feature is available. On self-managed GitLab, by default this feature is available. To hide the feature per group, ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `group_merge_request_approval_settings_feature_flag`. On GitLab.com, this feature is available.
Group approval rules provides an interface for managing Group approval rules manage [project merge request approval rules](../project/merge_requests/approvals/index.md)
[project merge request approval rules](../project/merge_requests/approvals/index.md) at the at the top-level group level. These rules [cascade to all projects](../project/merge_requests/approvals/settings.md#settings-cascading)
top-level group level. When rules are configured [at the instance level](../admin_area/merge_requests_approvals.md), that belong to the group.
you can't edit locked rules.
To view the merge request approval rules UI for a group: To view the merge request approval rules for a group:
1. Go to the top-level group's **Settings > General** page. 1. Go to the top-level group's **Settings > General** page.
1. Expand the **Merge request approvals** section. 1. Expand the **Merge request approvals** section.

View File

@ -139,7 +139,7 @@ coverage.
To learn more, see [Coverage check approval rule](../../../../ci/pipelines/settings.md#coverage-check-approval-rule). To learn more, see [Coverage check approval rule](../../../../ci/pipelines/settings.md#coverage-check-approval-rule).
## Merge request approval settings cascading ## Settings cascading
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/285410) in GitLab 14.4. [Deployed behind the `group_merge_request_approval_settings_feature_flag` flag](../../../../administration/feature_flags.md), disabled by default. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/285410) in GitLab 14.4. [Deployed behind the `group_merge_request_approval_settings_feature_flag` flag](../../../../administration/feature_flags.md), disabled by default.
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/285410) in GitLab 14.5. > - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/285410) in GitLab 14.5.
@ -149,11 +149,13 @@ On self-managed GitLab, by default this feature is available. To hide the featur
You can also enforce merge request approval settings: You can also enforce merge request approval settings:
- At the [instance level](../../../admin_area/merge_requests_approvals.md), which apply to all groups on an instance and, therefore, all - At the [instance level](../../../admin_area/merge_requests_approvals.md), which apply to all groups
projects. on an instance and, therefore, all projects.
- On a [top-level group](../../../group/index.md#group-approval-rules), which apply to all subgroups and projects. - On a [top-level group](../../../group/index.md#group-approval-rules), which apply to all subgroups
and projects.
If the settings are inherited by a group or project, they cannot be overridden by the group or project that inherited them. If the settings are inherited by a group or project, they cannot be changed in the group or project
that inherited them.
## Related links ## Related links

View File

@ -20,7 +20,7 @@ require_relative 'api/default_options'
# Push into expected format for failed tests # Push into expected format for failed tests
class PipelineTestReportBuilder class PipelineTestReportBuilder
def initialize(options) def initialize(options)
@project = options.delete(:project) @target_project = options.delete(:target_project)
@mr_id = options.delete(:mr_id) || Host::DEFAULT_OPTIONS[:mr_id] @mr_id = options.delete(:mr_id) || Host::DEFAULT_OPTIONS[:mr_id]
@instance_base_url = options.delete(:instance_base_url) || Host::DEFAULT_OPTIONS[:instance_base_url] @instance_base_url = options.delete(:instance_base_url) || Host::DEFAULT_OPTIONS[:instance_base_url]
@output_file_path = options.delete(:output_file_path) @output_file_path = options.delete(:output_file_path)
@ -40,38 +40,38 @@ class PipelineTestReportBuilder
end end
end end
private
attr_reader :project, :mr_id, :instance_base_url, :output_file_path
def project_api_base_url
"#{instance_base_url}/api/v4/projects/#{CGI.escape(project)}"
end
def project_base_url
"#{instance_base_url}/#{project}"
end
def previous_pipeline def previous_pipeline
# Top of the list will always be the current pipeline # Top of the list will always be the current pipeline
# Second from top will be the previous pipeline # Second from top will be the previous pipeline
pipelines_for_mr.sort_by { |a| -Time.parse(a['created_at']).to_i }[1] pipelines_for_mr.sort_by { |a| -Time.parse(a['created_at']).to_i }[1]
end end
def pipelines_for_mr private
fetch("#{project_api_base_url}/merge_requests/#{mr_id}/pipelines")
attr_reader :target_project, :mr_id, :instance_base_url, :output_file_path
def pipeline_project_api_base_url(pipeline)
"#{instance_base_url}/api/v4/projects/#{pipeline['project_id']}"
end end
def failed_builds_for_pipeline(pipeline_id) def target_project_api_base_url
fetch("#{project_api_base_url}/pipelines/#{pipeline_id}/jobs?scope=failed&per_page=100") "#{instance_base_url}/api/v4/projects/#{CGI.escape(target_project)}"
end
def pipelines_for_mr
fetch("#{target_project_api_base_url}/merge_requests/#{mr_id}/pipelines")
end
def failed_builds_for_pipeline(pipeline)
fetch("#{pipeline_project_api_base_url(pipeline)}/pipelines/#{pipeline['id']}/jobs?scope=failed&per_page=100")
end end
# Method uses the test suite endpoint to gather test results for a particular build. # Method uses the test suite endpoint to gather test results for a particular build.
# Here we request individual builds, even though it is possible to supply multiple build IDs. # Here we request individual builds, even though it is possible to supply multiple build IDs.
# The reason for this; it is possible to lose the job context and name when requesting multiple builds. # The reason for this; it is possible to lose the job context and name when requesting multiple builds.
# Please see for more info: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69053#note_709939709 # Please see for more info: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69053#note_709939709
def test_report_for_build(pipeline_id, build_id) def test_report_for_build(pipeline, build_id)
fetch("#{project_base_url}/-/pipelines/#{pipeline_id}/tests/suite.json?build_ids[]=#{build_id}") fetch("#{pipeline['web_url']}/tests/suite.json?build_ids[]=#{build_id}")
end end
def build_test_report_json_for_pipeline(pipeline) def build_test_report_json_for_pipeline(pipeline)
@ -82,7 +82,7 @@ class PipelineTestReportBuilder
puts "Discovered last failed pipeline (#{pipeline['id']}) for MR!#{mr_id}" puts "Discovered last failed pipeline (#{pipeline['id']}) for MR!#{mr_id}"
failed_builds_for_test_stage = failed_builds_for_pipeline(pipeline['id']).select do |failed_build| failed_builds_for_test_stage = failed_builds_for_pipeline(pipeline).select do |failed_build|
failed_build['stage'] == 'test' failed_build['stage'] == 'test'
end end
@ -92,7 +92,7 @@ class PipelineTestReportBuilder
test_report['suites'] ||= [] test_report['suites'] ||= []
failed_builds_for_test_stage.each do |failed_build| failed_builds_for_test_stage.each do |failed_build|
test_report['suites'] << test_report_for_build(pipeline['id'], failed_build['id']) test_report['suites'] << test_report_for_build(pipeline, failed_build['id'])
end end
end end
@ -127,8 +127,8 @@ if $0 == __FILE__
options = Host::DEFAULT_OPTIONS.dup options = Host::DEFAULT_OPTIONS.dup
OptionParser.new do |opts| OptionParser.new do |opts|
opts.on("-p", "--project PROJECT", String, "Project where to find the merge request(defaults to $CI_PROJECT_ID)") do |value| opts.on("-t", "--target-project TARGET_PROJECT", String, "Project where to find the merge request") do |value|
options[:project] = value options[:target_project] = value
end end
opts.on("-m", "--mr-id MR_ID", String, "A merge request ID") do |value| opts.on("-m", "--mr-id MR_ID", String, "A merge request ID") do |value|

View File

@ -94,11 +94,14 @@ function retrieve_previous_failed_tests() {
local rspec_pg_regex="${2}" local rspec_pg_regex="${2}"
local rspec_ee_pg_regex="${3}" local rspec_ee_pg_regex="${3}"
local pipeline_report_path="test_results/previous/test_reports.json" local pipeline_report_path="test_results/previous/test_reports.json"
local project_path="gitlab-org/gitlab"
# Used to query merge requests. This variable reflects where the merge request has been created
local target_project_path="${CI_MERGE_REQUEST_PROJECT_PATH}"
local instance_url="${CI_SERVER_URL}"
echo 'Attempting to build pipeline test report...' echo 'Attempting to build pipeline test report...'
scripts/pipeline_test_report_builder.rb --instance-base-url "https://gitlab.com" --project "${project_path}" --mr-id "${CI_MERGE_REQUEST_IID}" --output-file-path "${pipeline_report_path}" scripts/pipeline_test_report_builder.rb --instance-base-url "${instance_url}" --target-project "${target_project_path}" --mr-id "${CI_MERGE_REQUEST_IID}" --output-file-path "${pipeline_report_path}"
echo 'Generating failed tests lists...' echo 'Generating failed tests lists...'

View File

@ -5,5 +5,37 @@ FactoryBot.define do
association :feature_flag, factory: :operations_feature_flag association :feature_flag, factory: :operations_feature_flag
name { "default" } name { "default" }
parameters { {} } parameters { {} }
trait :default do
name { "default" }
parameters { {} }
end
trait :gitlab_userlist do
association :user_list, factory: :operations_feature_flag_user_list
name { "gitlabUserList" }
parameters { {} }
end
trait :flexible_rollout do
name { "flexibleRollout" }
parameters do
{
groupId: 'default',
rollout: '10',
stickiness: 'default'
}
end
end
trait :gradual_rollout do
name { "gradualRolloutUserId" }
parameters { { percentage: '10', groupId: 'default' } }
end
trait :userwithid do
name { "userWithId" }
parameters { { userIds: 'user1' } }
end
end end
end end

View File

@ -1,5 +1,34 @@
export const environmentsApp = { export const environmentsApp = {
environments: [ environments: [
{
name: 'review',
size: 2,
latest: {
id: 42,
global_id: 'gid://gitlab/Environment/42',
name: 'review/goodbye',
state: 'available',
external_url: 'https://example.org',
environment_type: 'review',
name_without_type: 'goodbye',
last_deployment: null,
has_stop_action: false,
rollout_status: null,
environment_path: '/h5bp/html5-boilerplate/-/environments/42',
stop_path: '/h5bp/html5-boilerplate/-/environments/42/stop',
cancel_auto_stop_path: '/h5bp/html5-boilerplate/-/environments/42/cancel_auto_stop',
delete_path: '/api/v4/projects/8/environments/42',
folder_path: '/h5bp/html5-boilerplate/-/environments/folders/review',
created_at: '2021-10-04T19:27:20.639Z',
updated_at: '2021-10-04T19:27:20.639Z',
can_stop: true,
logs_path: '/h5bp/html5-boilerplate/-/logs?environment_name=review%2Fgoodbye',
logs_api_path: '/h5bp/html5-boilerplate/-/logs/k8s.json?environment_name=review%2Fgoodbye',
enable_advanced_logs_querying: false,
can_delete: false,
has_opened_alert: false,
},
},
{ {
name: 'production', name: 'production',
size: 1, size: 1,
@ -134,35 +163,6 @@ export const environmentsApp = {
has_opened_alert: false, has_opened_alert: false,
}, },
}, },
{
name: 'review',
size: 2,
latest: {
id: 42,
global_id: 'gid://gitlab/Environment/42',
name: 'review/goodbye',
state: 'available',
external_url: 'https://example.org',
environment_type: 'review',
name_without_type: 'goodbye',
last_deployment: null,
has_stop_action: false,
rollout_status: null,
environment_path: '/h5bp/html5-boilerplate/-/environments/42',
stop_path: '/h5bp/html5-boilerplate/-/environments/42/stop',
cancel_auto_stop_path: '/h5bp/html5-boilerplate/-/environments/42/cancel_auto_stop',
delete_path: '/api/v4/projects/8/environments/42',
folder_path: '/h5bp/html5-boilerplate/-/environments/folders/review',
created_at: '2021-10-04T19:27:20.639Z',
updated_at: '2021-10-04T19:27:20.639Z',
can_stop: true,
logs_path: '/h5bp/html5-boilerplate/-/logs?environment_name=review%2Fgoodbye',
logs_api_path: '/h5bp/html5-boilerplate/-/logs/k8s.json?environment_name=review%2Fgoodbye',
enable_advanced_logs_querying: false,
can_delete: false,
has_opened_alert: false,
},
},
{ {
name: 'staging', name: 'staging',
size: 1, size: 1,
@ -206,6 +206,36 @@ export const environmentsApp = {
export const resolvedEnvironmentsApp = { export const resolvedEnvironmentsApp = {
availableCount: 4, availableCount: 4,
environments: [ environments: [
{
name: 'review',
size: 2,
latest: {
id: 42,
globalId: 'gid://gitlab/Environment/42',
name: 'review/goodbye',
state: 'available',
externalUrl: 'https://example.org',
environmentType: 'review',
nameWithoutType: 'goodbye',
lastDeployment: null,
hasStopAction: false,
rolloutStatus: null,
environmentPath: '/h5bp/html5-boilerplate/-/environments/42',
stopPath: '/h5bp/html5-boilerplate/-/environments/42/stop',
cancelAutoStopPath: '/h5bp/html5-boilerplate/-/environments/42/cancel_auto_stop',
deletePath: '/api/v4/projects/8/environments/42',
folderPath: '/h5bp/html5-boilerplate/-/environments/folders/review',
createdAt: '2021-10-04T19:27:20.639Z',
updatedAt: '2021-10-04T19:27:20.639Z',
canStop: true,
logsPath: '/h5bp/html5-boilerplate/-/logs?environment_name=review%2Fgoodbye',
logsApiPath: '/h5bp/html5-boilerplate/-/logs/k8s.json?environment_name=review%2Fgoodbye',
enableAdvancedLogsQuerying: false,
canDelete: false,
hasOpenedAlert: false,
},
__typename: 'NestedEnvironment',
},
{ {
name: 'production', name: 'production',
size: 1, size: 1,
@ -340,36 +370,6 @@ export const resolvedEnvironmentsApp = {
}, },
__typename: 'NestedEnvironment', __typename: 'NestedEnvironment',
}, },
{
name: 'review',
size: 2,
latest: {
id: 42,
globalId: 'gid://gitlab/Environment/42',
name: 'review/goodbye',
state: 'available',
externalUrl: 'https://example.org',
environmentType: 'review',
nameWithoutType: 'goodbye',
lastDeployment: null,
hasStopAction: false,
rolloutStatus: null,
environmentPath: '/h5bp/html5-boilerplate/-/environments/42',
stopPath: '/h5bp/html5-boilerplate/-/environments/42/stop',
cancelAutoStopPath: '/h5bp/html5-boilerplate/-/environments/42/cancel_auto_stop',
deletePath: '/api/v4/projects/8/environments/42',
folderPath: '/h5bp/html5-boilerplate/-/environments/folders/review',
createdAt: '2021-10-04T19:27:20.639Z',
updatedAt: '2021-10-04T19:27:20.639Z',
canStop: true,
logsPath: '/h5bp/html5-boilerplate/-/logs?environment_name=review%2Fgoodbye',
logsApiPath: '/h5bp/html5-boilerplate/-/logs/k8s.json?environment_name=review%2Fgoodbye',
enableAdvancedLogsQuerying: false,
canDelete: false,
hasOpenedAlert: false,
},
__typename: 'NestedEnvironment',
},
{ {
name: 'staging', name: 'staging',
size: 1, size: 1,

View File

@ -0,0 +1,74 @@
import VueApollo from 'vue-apollo';
import Vue from 'vue';
import { GlCollapse, GlIcon } from '@gitlab/ui';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue';
import { s__ } from '~/locale';
import { resolvedEnvironmentsApp, resolvedFolder } from './graphql/mock_data';
Vue.use(VueApollo);
describe('~/environments/components/new_environments_folder.vue', () => {
let wrapper;
let environmentFolderMock;
let nestedEnvironment;
let folderName;
const findLink = () => wrapper.findByRole('link', { name: s__('Environments|Show all') });
const createApolloProvider = () => {
const mockResolvers = { Query: { folder: environmentFolderMock } };
return createMockApollo([], mockResolvers);
};
const createWrapper = (propsData, apolloProvider) =>
mountExtended(EnvironmentsFolder, { apolloProvider, propsData });
beforeEach(() => {
environmentFolderMock = jest.fn();
[nestedEnvironment] = resolvedEnvironmentsApp.environments;
environmentFolderMock.mockReturnValue(resolvedFolder);
wrapper = createWrapper({ nestedEnvironment }, createApolloProvider());
folderName = wrapper.findByText(nestedEnvironment.name);
});
afterEach(() => {
wrapper?.destroy();
});
it('displays the name of the folder', () => {
expect(folderName.text()).toBe(nestedEnvironment.name);
});
describe('collapse', () => {
let icons;
let collapse;
beforeEach(() => {
collapse = wrapper.findComponent(GlCollapse);
icons = wrapper.findAllComponents(GlIcon);
});
it('is collapsed by default', () => {
const link = findLink();
expect(collapse.attributes('visible')).toBeUndefined();
expect(icons.wrappers.map((i) => i.props('name'))).toEqual(['angle-right', 'folder-o']);
expect(folderName.classes('gl-font-weight-bold')).toBe(false);
expect(link.exists()).toBe(false);
});
it('opens on click', async () => {
await folderName.trigger('click');
const link = findLink();
expect(collapse.attributes('visible')).toBe('true');
expect(icons.wrappers.map((i) => i.props('name'))).toEqual(['angle-down', 'folder-open']);
expect(folderName.classes('gl-font-weight-bold')).toBe(true);
expect(link.attributes('href')).toBe(nestedEnvironment.latest.folderPath);
});
});
});

View File

@ -0,0 +1,50 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { mount } from '@vue/test-utils';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import EnvironmentsApp from '~/environments/components/new_environments_app.vue';
import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue';
import { resolvedEnvironmentsApp, resolvedFolder } from './graphql/mock_data';
Vue.use(VueApollo);
describe('~/environments/components/new_environments_app.vue', () => {
let wrapper;
let environmentAppMock;
let environmentFolderMock;
const createApolloProvider = () => {
const mockResolvers = {
Query: { environmentApp: environmentAppMock, folder: environmentFolderMock },
};
return createMockApollo([], mockResolvers);
};
const createWrapper = (apolloProvider) => mount(EnvironmentsApp, { apolloProvider });
beforeEach(() => {
environmentAppMock = jest.fn();
environmentFolderMock = jest.fn();
});
afterEach(() => {
wrapper?.destroy();
});
it('should show all the folders that are fetched', async () => {
environmentAppMock.mockReturnValue(resolvedEnvironmentsApp);
environmentFolderMock.mockReturnValue(resolvedFolder);
const apolloProvider = createApolloProvider();
wrapper = createWrapper(apolloProvider);
await waitForPromises();
await Vue.nextTick();
const text = wrapper.findAllComponents(EnvironmentsFolder).wrappers.map((w) => w.text());
expect(text).toContainEqual(expect.stringMatching('review'));
expect(text).not.toContainEqual(expect.stringMatching('production'));
});
});

View File

@ -8,7 +8,7 @@ exports[`IDE pipelines list when loaded renders empty state when no latestPipeli
<empty-state-stub <empty-state-stub
cansetci="true" cansetci="true"
class="mb-auto mt-auto" class="gl-p-5"
emptystatesvgpath="http://test.host" emptystatesvgpath="http://test.host"
/> />
</div> </div>

View File

@ -283,7 +283,7 @@ RSpec.describe DesignManagement::Version do
it 'retrieves author from the Commit if author_id is nil and version has been persisted' do it 'retrieves author from the Commit if author_id is nil and version has been persisted' do
author = create(:user) author = create(:user)
version = create(:design_version, :committed, author: author) version = create(:design_version, :committed, author: author)
author.destroy author.destroy!
version.reload version.reload
commit = version.issue.project.design_repository.commit(version.sha) commit = version.issue.project.design_repository.commit(version.sha)
commit_user = create(:user, email: commit.author_email, name: commit.author_name) commit_user = create(:user, email: commit.author_email, name: commit.author_name)

View File

@ -39,7 +39,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
it 'ensures environment tier when a new object is created' do it 'ensures environment tier when a new object is created' do
environment = build(:environment, name: 'gprd', tier: nil) environment = build(:environment, name: 'gprd', tier: nil)
expect { environment.save }.to change { environment.tier }.from(nil).to('production') expect { environment.save! }.to change { environment.tier }.from(nil).to('production')
end end
it 'ensures environment tier when an existing object is updated' do it 'ensures environment tier when an existing object is updated' do
@ -418,7 +418,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
context 'not in the same branch' do context 'not in the same branch' do
before do before do
deployment.update(sha: project.commit('feature').id) deployment.update!(sha: project.commit('feature').id)
end end
it 'returns false' do it 'returns false' do
@ -496,7 +496,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
context 'when no other actions' do context 'when no other actions' do
context 'environment is available' do context 'environment is available' do
before do before do
environment.update(state: :available) environment.update!(state: :available)
end end
it do it do
@ -508,7 +508,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
context 'environment is already stopped' do context 'environment is already stopped' do
before do before do
environment.update(state: :stopped) environment.update!(state: :stopped)
end end
it do it do
@ -1502,7 +1502,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
deployment = create(:deployment, :success, environment: environment, project: project) deployment = create(:deployment, :success, environment: environment, project: project)
deployment.create_ref deployment.create_ref
expect { environment.destroy }.to change { project.commit(deployment.ref_path) }.to(nil) expect { environment.destroy! }.to change { project.commit(deployment.ref_path) }.to(nil)
end end
end end
@ -1517,7 +1517,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
end end
it 'returns the environments count grouped by state with zero value' do it 'returns the environments count grouped by state with zero value' do
environment2.update(state: 'stopped') environment2.update!(state: 'stopped')
expect(project.environments.count_by_state).to eq({ stopped: 3, available: 0 }) expect(project.environments.count_by_state).to eq({ stopped: 3, available: 0 })
end end
end end

View File

@ -32,7 +32,7 @@ RSpec.describe Event do
describe 'after_create :set_last_repository_updated_at' do describe 'after_create :set_last_repository_updated_at' do
context 'with a push event' do context 'with a push event' do
it 'updates the project last_repository_updated_at' do it 'updates the project last_repository_updated_at' do
project.update(last_repository_updated_at: 1.year.ago) project.update!(last_repository_updated_at: 1.year.ago)
create_push_event(project, project.owner) create_push_event(project, project.owner)
@ -44,7 +44,7 @@ RSpec.describe Event do
context 'without a push event' do context 'without a push event' do
it 'does not update the project last_repository_updated_at' do it 'does not update the project last_repository_updated_at' do
project.update(last_repository_updated_at: 1.year.ago) project.update!(last_repository_updated_at: 1.year.ago)
create(:closed_issue_event, project: project, author: project.owner) create(:closed_issue_event, project: project, author: project.owner)
@ -58,7 +58,7 @@ RSpec.describe Event do
describe '#set_last_repository_updated_at' do describe '#set_last_repository_updated_at' do
it 'only updates once every Event::REPOSITORY_UPDATED_AT_INTERVAL minutes' do it 'only updates once every Event::REPOSITORY_UPDATED_AT_INTERVAL minutes' do
last_known_timestamp = (Event::REPOSITORY_UPDATED_AT_INTERVAL - 1.minute).ago last_known_timestamp = (Event::REPOSITORY_UPDATED_AT_INTERVAL - 1.minute).ago
project.update(last_repository_updated_at: last_known_timestamp) project.update!(last_repository_updated_at: last_known_timestamp)
project.reload # a reload removes fractions of seconds project.reload # a reload removes fractions of seconds
expect do expect do
@ -73,7 +73,7 @@ RSpec.describe Event do
it 'passes event to UserInteractedProject.track' do it 'passes event to UserInteractedProject.track' do
expect(UserInteractedProject).to receive(:track).with(event) expect(UserInteractedProject).to receive(:track).with(event)
event.save event.save!
end end
end end
end end
@ -824,7 +824,7 @@ RSpec.describe Event do
context 'when a project was updated less than 1 hour ago' do context 'when a project was updated less than 1 hour ago' do
it 'does not update the project' do it 'does not update the project' do
project.update(last_activity_at: Time.current) project.update!(last_activity_at: Time.current)
expect(project).not_to receive(:update_column) expect(project).not_to receive(:update_column)
.with(:last_activity_at, a_kind_of(Time)) .with(:last_activity_at, a_kind_of(Time))
@ -835,7 +835,7 @@ RSpec.describe Event do
context 'when a project was updated more than 1 hour ago' do context 'when a project was updated more than 1 hour ago' do
it 'updates the project' do it 'updates the project' do
project.update(last_activity_at: 1.year.ago) project.update!(last_activity_at: 1.year.ago)
create_push_event(project, project.owner) create_push_event(project, project.owner)

View File

@ -8,7 +8,7 @@ RSpec.describe ForkNetwork do
describe '#add_root_as_member' do describe '#add_root_as_member' do
it 'adds the root project as a member when creating a new root network' do it 'adds the root project as a member when creating a new root network' do
project = create(:project) project = create(:project)
fork_network = described_class.create(root_project: project) fork_network = described_class.create!(root_project: project)
expect(fork_network.projects).to include(project) expect(fork_network.projects).to include(project)
end end
@ -54,8 +54,8 @@ RSpec.describe ForkNetwork do
first_fork = fork_project(first_project) first_fork = fork_project(first_project)
second_fork = fork_project(second_project) second_fork = fork_project(second_project)
first_project.destroy first_project.destroy!
second_project.destroy second_project.destroy!
expect(first_fork.fork_network).not_to be_nil expect(first_fork.fork_network).not_to be_nil
expect(first_fork.fork_network.root_project).to be_nil expect(first_fork.fork_network.root_project).to be_nil

View File

@ -133,7 +133,7 @@ RSpec.describe GenericCommitStatus do
before do before do
generic_commit_status.context = nil generic_commit_status.context = nil
generic_commit_status.stage = nil generic_commit_status.stage = nil
generic_commit_status.save generic_commit_status.save!
end end
describe '#context' do describe '#context' do

View File

@ -60,7 +60,7 @@ RSpec.describe GrafanaIntegration do
context 'with grafana integration enabled' do context 'with grafana integration enabled' do
it 'returns nil' do it 'returns nil' do
grafana_integration.update(enabled: false) grafana_integration.update!(enabled: false)
expect(grafana_integration.client).to be(nil) expect(grafana_integration.client).to be(nil)
end end
@ -81,8 +81,8 @@ RSpec.describe GrafanaIntegration do
end end
it 'prevents overriding token value with its encrypted or masked version', :aggregate_failures do it 'prevents overriding token value with its encrypted or masked version', :aggregate_failures do
expect { grafana_integration.update(token: grafana_integration.encrypted_token) }.not_to change { grafana_integration.reload.send(:token) } expect { grafana_integration.update!(token: grafana_integration.encrypted_token) }.not_to change { grafana_integration.reload.send(:token) }
expect { grafana_integration.update(token: grafana_integration.masked_token) }.not_to change { grafana_integration.reload.send(:token) } expect { grafana_integration.update!(token: grafana_integration.masked_token) }.not_to change { grafana_integration.reload.send(:token) }
end end
end end
end end

View File

@ -160,7 +160,7 @@ RSpec.describe Group do
context 'when sub group is deleted' do context 'when sub group is deleted' do
it 'does not delete parent notification settings' do it 'does not delete parent notification settings' do
expect do expect do
sub_group.destroy sub_group.destroy!
end.to change { NotificationSetting.count }.by(-1) end.to change { NotificationSetting.count }.by(-1)
end end
end end
@ -404,7 +404,7 @@ RSpec.describe Group do
subject do subject do
recorded_queries.record do recorded_queries.record do
group.update(parent: new_parent) group.update!(parent: new_parent)
end end
end end
@ -496,7 +496,7 @@ RSpec.describe Group do
let!(:group) { create(:group, parent: parent_group) } let!(:group) { create(:group, parent: parent_group) }
before do before do
parent_group.update(parent: new_grandparent) parent_group.update!(parent: new_grandparent)
end end
it 'updates traversal_ids for all descendants' do it 'updates traversal_ids for all descendants' do
@ -866,7 +866,7 @@ RSpec.describe Group do
before do before do
parent_group = create(:group) parent_group = create(:group)
create(:group_member, :owner, group: parent_group) create(:group_member, :owner, group: parent_group)
group.update(parent: parent_group) group.update!(parent: parent_group)
end end
it { expect(group.last_owner?(@members[:owner])).to be_falsy } it { expect(group.last_owner?(@members[:owner])).to be_falsy }
@ -923,7 +923,7 @@ RSpec.describe Group do
before do before do
parent_group = create(:group) parent_group = create(:group)
create(:group_member, :owner, group: parent_group) create(:group_member, :owner, group: parent_group)
group.update(parent: parent_group) group.update!(parent: parent_group)
end end
it { expect(group.member_last_blocked_owner?(member)).to be(false) } it { expect(group.member_last_blocked_owner?(member)).to be(false) }
@ -1971,7 +1971,7 @@ RSpec.describe Group do
let(:environment) { 'foo%bar/test' } let(:environment) { 'foo%bar/test' }
it 'matches literally for %' do it 'matches literally for %' do
ci_variable.update(environment_scope: 'foo%bar/*') ci_variable.update_attribute(:environment_scope, 'foo%bar/*')
is_expected.to contain_exactly(ci_variable) is_expected.to contain_exactly(ci_variable)
end end
@ -2112,7 +2112,7 @@ RSpec.describe Group do
let(:ancestor_group) { create(:group) } let(:ancestor_group) { create(:group) }
before do before do
group.update(parent: ancestor_group) group.update!(parent: ancestor_group)
end end
it 'returns all ancestor group ids' do it 'returns all ancestor group ids' do
@ -2629,7 +2629,7 @@ RSpec.describe Group do
let_it_be(:project) { create(:project, group: group, service_desk_enabled: false) } let_it_be(:project) { create(:project, group: group, service_desk_enabled: false) }
before do before do
project.update(service_desk_enabled: false) project.update!(service_desk_enabled: false)
end end
it { is_expected.to eq(false) } it { is_expected.to eq(false) }

View File

@ -118,19 +118,19 @@ RSpec.describe Identity do
it 'if extern_uid changes' do it 'if extern_uid changes' do
expect(ldap_identity).not_to receive(:ensure_normalized_extern_uid) expect(ldap_identity).not_to receive(:ensure_normalized_extern_uid)
ldap_identity.save ldap_identity.save!
end end
it 'if current_uid is nil' do it 'if current_uid is nil' do
expect(ldap_identity).to receive(:ensure_normalized_extern_uid) expect(ldap_identity).to receive(:ensure_normalized_extern_uid)
ldap_identity.update(extern_uid: nil) ldap_identity.update!(extern_uid: nil)
expect(ldap_identity.extern_uid).to be_nil expect(ldap_identity.extern_uid).to be_nil
end end
it 'if extern_uid changed and not nil' do it 'if extern_uid changed and not nil' do
ldap_identity.update(extern_uid: 'uid=john1,ou=PEOPLE,dc=example,dc=com') ldap_identity.update!(extern_uid: 'uid=john1,ou=PEOPLE,dc=example,dc=com')
expect(ldap_identity.extern_uid).to eq 'uid=john1,ou=people,dc=example,dc=com' expect(ldap_identity.extern_uid).to eq 'uid=john1,ou=people,dc=example,dc=com'
end end
@ -150,7 +150,7 @@ RSpec.describe Identity do
expect(user.user_synced_attributes_metadata.provider).to eq 'ldapmain' expect(user.user_synced_attributes_metadata.provider).to eq 'ldapmain'
ldap_identity.destroy ldap_identity.destroy!
expect(user.reload.user_synced_attributes_metadata).to be_nil expect(user.reload.user_synced_attributes_metadata).to be_nil
end end
@ -162,7 +162,7 @@ RSpec.describe Identity do
expect(user.user_synced_attributes_metadata.provider).to eq 'other' expect(user.user_synced_attributes_metadata.provider).to eq 'other'
ldap_identity.destroy ldap_identity.destroy!
expect(user.reload.user_synced_attributes_metadata.provider).to eq 'other' expect(user.reload.user_synced_attributes_metadata.provider).to eq 'other'
end end

View File

@ -175,7 +175,7 @@ RSpec.describe JiraImportState do
let(:jira_import) { build(:jira_import_state, project: project)} let(:jira_import) { build(:jira_import_state, project: project)}
it 'does not run the callback', :aggregate_failures do it 'does not run the callback', :aggregate_failures do
expect { jira_import.save }.to change { JiraImportState.count }.by(1) expect { jira_import.save! }.to change { JiraImportState.count }.by(1)
expect(jira_import.reload.error_message).to be_nil expect(jira_import.reload.error_message).to be_nil
end end
end end
@ -184,7 +184,7 @@ RSpec.describe JiraImportState do
let(:jira_import) { build(:jira_import_state, project: project, error_message: 'error')} let(:jira_import) { build(:jira_import_state, project: project, error_message: 'error')}
it 'does not run the callback', :aggregate_failures do it 'does not run the callback', :aggregate_failures do
expect { jira_import.save }.to change { JiraImportState.count }.by(1) expect { jira_import.save! }.to change { JiraImportState.count }.by(1)
expect(jira_import.reload.error_message).to eq('error') expect(jira_import.reload.error_message).to eq('error')
end end
end end

View File

@ -228,7 +228,7 @@ RSpec.describe Namespace do
expect(namespace.path).to eq('j') expect(namespace.path).to eq('j')
namespace.update(name: 'something new') namespace.update!(name: 'something new')
expect(namespace).to be_valid expect(namespace).to be_valid
expect(namespace.name).to eq('something new') expect(namespace.name).to eq('something new')
@ -240,7 +240,7 @@ RSpec.describe Namespace do
it 'allows to update path to single char' do it 'allows to update path to single char' do
namespace = create(:project_namespace) namespace = create(:project_namespace)
namespace.update(path: 'j') namespace.update!(path: 'j')
expect(namespace).to be_valid expect(namespace).to be_valid
end end
@ -740,7 +740,7 @@ RSpec.describe Namespace do
end end
it "moves dir if path changed" do it "moves dir if path changed" do
namespace.update(path: namespace.full_path + '_new') namespace.update!(path: namespace.full_path + '_new')
expect(gitlab_shell.repository_exists?(project.repository_storage, "#{namespace.path}/#{project.path}.git")).to be_truthy expect(gitlab_shell.repository_exists?(project.repository_storage, "#{namespace.path}/#{project.path}.git")).to be_truthy
end end
@ -751,7 +751,7 @@ RSpec.describe Namespace do
expect(namespace).to receive(:write_projects_repository_config).and_raise('foo') expect(namespace).to receive(:write_projects_repository_config).and_raise('foo')
expect do expect do
namespace.update(path: namespace.full_path + '_new') namespace.update!(path: namespace.full_path + '_new')
end.to raise_error('foo') end.to raise_error('foo')
end end
end end
@ -768,7 +768,7 @@ RSpec.describe Namespace do
end end
expect(Gitlab::ErrorTracking).to receive(:should_raise_for_dev?).and_return(false) # like prod expect(Gitlab::ErrorTracking).to receive(:should_raise_for_dev?).and_return(false) # like prod
namespace.update(path: namespace.full_path + '_new') namespace.update!(path: namespace.full_path + '_new')
end end
end end
end end
@ -998,7 +998,7 @@ RSpec.describe Namespace do
it "repository directory remains unchanged if path changed" do it "repository directory remains unchanged if path changed" do
before_disk_path = project.disk_path before_disk_path = project.disk_path
namespace.update(path: namespace.full_path + '_new') namespace.update!(path: namespace.full_path + '_new')
expect(before_disk_path).to eq(project.disk_path) expect(before_disk_path).to eq(project.disk_path)
expect(gitlab_shell.repository_exists?(project.repository_storage, "#{project.disk_path}.git")).to be_truthy expect(gitlab_shell.repository_exists?(project.repository_storage, "#{project.disk_path}.git")).to be_truthy
@ -1013,7 +1013,7 @@ RSpec.describe Namespace do
let!(:legacy_project_in_subgroup) { create(:project, :legacy_storage, :repository, namespace: subgroup, name: 'foo3') } let!(:legacy_project_in_subgroup) { create(:project, :legacy_storage, :repository, namespace: subgroup, name: 'foo3') }
it 'updates project full path in .git/config' do it 'updates project full path in .git/config' do
parent.update(path: 'mygroup_new') parent.update!(path: 'mygroup_new')
expect(project_rugged(project_in_parent_group).config['gitlab.fullpath']).to eq "mygroup_new/#{project_in_parent_group.path}" expect(project_rugged(project_in_parent_group).config['gitlab.fullpath']).to eq "mygroup_new/#{project_in_parent_group.path}"
expect(project_rugged(hashed_project_in_subgroup).config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{hashed_project_in_subgroup.path}" expect(project_rugged(hashed_project_in_subgroup).config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{hashed_project_in_subgroup.path}"
@ -1025,7 +1025,7 @@ RSpec.describe Namespace do
repository_hashed_project_in_subgroup = hashed_project_in_subgroup.project_repository repository_hashed_project_in_subgroup = hashed_project_in_subgroup.project_repository
repository_legacy_project_in_subgroup = legacy_project_in_subgroup.project_repository repository_legacy_project_in_subgroup = legacy_project_in_subgroup.project_repository
parent.update(path: 'mygroup_moved') parent.update!(path: 'mygroup_moved')
expect(repository_project_in_parent_group.reload.disk_path).to eq "mygroup_moved/#{project_in_parent_group.path}" expect(repository_project_in_parent_group.reload.disk_path).to eq "mygroup_moved/#{project_in_parent_group.path}"
expect(repository_hashed_project_in_subgroup.reload.disk_path).to eq hashed_project_in_subgroup.disk_path expect(repository_hashed_project_in_subgroup.reload.disk_path).to eq hashed_project_in_subgroup.disk_path
@ -1059,7 +1059,7 @@ RSpec.describe Namespace do
it 'renames its dirs when deleted' do it 'renames its dirs when deleted' do
allow(GitlabShellWorker).to receive(:perform_in) allow(GitlabShellWorker).to receive(:perform_in)
namespace.destroy namespace.destroy!
expect(File.exist?(deleted_path_in_dir)).to be(true) expect(File.exist?(deleted_path_in_dir)).to be(true)
end end
@ -1067,7 +1067,7 @@ RSpec.describe Namespace do
it 'schedules the namespace for deletion' do it 'schedules the namespace for deletion' do
expect(GitlabShellWorker).to receive(:perform_in).with(5.minutes, :rm_namespace, repository_storage, deleted_path) expect(GitlabShellWorker).to receive(:perform_in).with(5.minutes, :rm_namespace, repository_storage, deleted_path)
namespace.destroy namespace.destroy!
end end
context 'in sub-groups' do context 'in sub-groups' do
@ -1081,7 +1081,7 @@ RSpec.describe Namespace do
it 'renames its dirs when deleted' do it 'renames its dirs when deleted' do
allow(GitlabShellWorker).to receive(:perform_in) allow(GitlabShellWorker).to receive(:perform_in)
child.destroy child.destroy!
expect(File.exist?(deleted_path_in_dir)).to be(true) expect(File.exist?(deleted_path_in_dir)).to be(true)
end end
@ -1089,7 +1089,7 @@ RSpec.describe Namespace do
it 'schedules the namespace for deletion' do it 'schedules the namespace for deletion' do
expect(GitlabShellWorker).to receive(:perform_in).with(5.minutes, :rm_namespace, repository_storage, deleted_path) expect(GitlabShellWorker).to receive(:perform_in).with(5.minutes, :rm_namespace, repository_storage, deleted_path)
child.destroy child.destroy!
end end
end end
end end
@ -1102,7 +1102,7 @@ RSpec.describe Namespace do
expect(File.exist?(path_in_dir)).to be(false) expect(File.exist?(path_in_dir)).to be(false)
namespace.destroy namespace.destroy!
expect(File.exist?(deleted_path_in_dir)).to be(false) expect(File.exist?(deleted_path_in_dir)).to be(false)
end end
@ -1628,7 +1628,7 @@ RSpec.describe Namespace do
it 'returns the path before last save' do it 'returns the path before last save' do
group = create(:group) group = create(:group)
group.update(parent: nil) group.update!(parent: nil)
expect(group.full_path_before_last_save).to eq(group.path_before_last_save) expect(group.full_path_before_last_save).to eq(group.path_before_last_save)
end end
@ -1639,7 +1639,7 @@ RSpec.describe Namespace do
group = create(:group, parent: nil) group = create(:group, parent: nil)
parent = create(:group) parent = create(:group)
group.update(parent: parent) group.update!(parent: parent)
expect(group.full_path_before_last_save).to eq("#{group.path_before_last_save}") expect(group.full_path_before_last_save).to eq("#{group.path_before_last_save}")
end end
@ -1650,7 +1650,7 @@ RSpec.describe Namespace do
parent = create(:group) parent = create(:group)
group = create(:group, parent: parent) group = create(:group, parent: parent)
group.update(parent: nil) group.update!(parent: nil)
expect(group.full_path_before_last_save).to eq("#{parent.full_path}/#{group.path}") expect(group.full_path_before_last_save).to eq("#{parent.full_path}/#{group.path}")
end end
@ -1662,7 +1662,7 @@ RSpec.describe Namespace do
group = create(:group, parent: parent) group = create(:group, parent: parent)
new_parent = create(:group) new_parent = create(:group)
group.update(parent: new_parent) group.update!(parent: new_parent)
expect(group.full_path_before_last_save).to eq("#{parent.full_path}/#{group.path}") expect(group.full_path_before_last_save).to eq("#{parent.full_path}/#{group.path}")
end end

View File

@ -155,7 +155,7 @@ RSpec.describe Note do
expect(note).to receive(:notify_after_destroy).and_call_original expect(note).to receive(:notify_after_destroy).and_call_original
expect(note.noteable).to receive(:after_note_destroyed).with(note) expect(note.noteable).to receive(:after_note_destroyed).with(note)
note.destroy note.destroy!
end end
it 'does not error if noteable is nil' do it 'does not error if noteable is nil' do
@ -163,7 +163,7 @@ RSpec.describe Note do
expect(note).to receive(:notify_after_destroy).and_call_original expect(note).to receive(:notify_after_destroy).and_call_original
expect(note).to receive(:noteable).at_least(:once).and_return(nil) expect(note).to receive(:noteable).at_least(:once).and_return(nil)
expect { note.destroy }.not_to raise_error expect { note.destroy! }.not_to raise_error
end end
end end
end end
@ -226,8 +226,8 @@ RSpec.describe Note do
describe 'read' do describe 'read' do
before do before do
@p1.project_members.create(user: @u2, access_level: ProjectMember::GUEST) @p1.project_members.create!(user: @u2, access_level: ProjectMember::GUEST)
@p2.project_members.create(user: @u3, access_level: ProjectMember::GUEST) @p2.project_members.create!(user: @u3, access_level: ProjectMember::GUEST)
end end
it { expect(Ability.allowed?(@u1, :read_note, @p1)).to be_falsey } it { expect(Ability.allowed?(@u1, :read_note, @p1)).to be_falsey }
@ -237,8 +237,8 @@ RSpec.describe Note do
describe 'write' do describe 'write' do
before do before do
@p1.project_members.create(user: @u2, access_level: ProjectMember::DEVELOPER) @p1.project_members.create!(user: @u2, access_level: ProjectMember::DEVELOPER)
@p2.project_members.create(user: @u3, access_level: ProjectMember::DEVELOPER) @p2.project_members.create!(user: @u3, access_level: ProjectMember::DEVELOPER)
end end
it { expect(Ability.allowed?(@u1, :create_note, @p1)).to be_falsey } it { expect(Ability.allowed?(@u1, :create_note, @p1)).to be_falsey }
@ -248,9 +248,9 @@ RSpec.describe Note do
describe 'admin' do describe 'admin' do
before do before do
@p1.project_members.create(user: @u1, access_level: ProjectMember::REPORTER) @p1.project_members.create!(user: @u1, access_level: ProjectMember::REPORTER)
@p1.project_members.create(user: @u2, access_level: ProjectMember::MAINTAINER) @p1.project_members.create!(user: @u2, access_level: ProjectMember::MAINTAINER)
@p2.project_members.create(user: @u3, access_level: ProjectMember::MAINTAINER) @p2.project_members.create!(user: @u3, access_level: ProjectMember::MAINTAINER)
end end
it { expect(Ability.allowed?(@u1, :admin_note, @p1)).to be_falsey } it { expect(Ability.allowed?(@u1, :admin_note, @p1)).to be_falsey }
@ -1468,7 +1468,7 @@ RSpec.describe Note do
shared_examples 'assignee check' do shared_examples 'assignee check' do
context 'when the provided user is one of the assignees' do context 'when the provided user is one of the assignees' do
before do before do
note.noteable.update(assignees: [user, create(:user)]) note.noteable.update!(assignees: [user, create(:user)])
end end
it 'returns true' do it 'returns true' do
@ -1480,7 +1480,7 @@ RSpec.describe Note do
shared_examples 'author check' do shared_examples 'author check' do
context 'when the provided user is the author' do context 'when the provided user is the author' do
before do before do
note.noteable.update(author: user) note.noteable.update!(author: user)
end end
it 'returns true' do it 'returns true' do

View File

@ -36,7 +36,7 @@ RSpec.describe NotificationSetting do
notification_setting.merge_merge_request = "t" notification_setting.merge_merge_request = "t"
notification_setting.close_merge_request = "nil" notification_setting.close_merge_request = "nil"
notification_setting.reopen_merge_request = "false" notification_setting.reopen_merge_request = "false"
notification_setting.save notification_setting.save!
end end
it "parses boolean before saving" do it "parses boolean before saving" do
@ -52,12 +52,12 @@ RSpec.describe NotificationSetting do
context 'notification_email' do context 'notification_email' do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
subject { described_class.new(source_id: 1, source_type: 'Project', user_id: user.id) } subject { build(:notification_setting, user_id: user.id) }
it 'allows to change email to verified one' do it 'allows to change email to verified one' do
email = create(:email, :confirmed, user: user) email = create(:email, :confirmed, user: user)
subject.update(notification_email: email.email) subject.notification_email = email.email
expect(subject).to be_valid expect(subject).to be_valid
end end
@ -65,13 +65,13 @@ RSpec.describe NotificationSetting do
it 'does not allow to change email to not verified one' do it 'does not allow to change email to not verified one' do
email = create(:email, user: user) email = create(:email, user: user)
subject.update(notification_email: email.email) subject.notification_email = email.email
expect(subject).to be_invalid expect(subject).to be_invalid
end end
it 'allows to change email to empty one' do it 'allows to change email to empty one' do
subject.update(notification_email: '') subject.notification_email = ''
expect(subject).to be_valid expect(subject).to be_valid
end end
@ -85,7 +85,7 @@ RSpec.describe NotificationSetting do
1.upto(4) do |i| 1.upto(4) do |i|
setting = create(:notification_setting, user: user) setting = create(:notification_setting, user: user)
setting.project.update(pending_delete: true) if i.even? setting.project.update!(pending_delete: true) if i.even?
end end
end end

View File

@ -20,8 +20,12 @@ RSpec.describe Operations::FeatureFlags::Strategy do
end end
with_them do with_them do
it 'skips parameters validation' do it 'skips parameters validation' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: invalid_name, parameters: { bad: 'params' }) feature_flag: feature_flag,
name: invalid_name,
parameters: { bad: 'params' })
expect(strategy).to be_invalid
expect(strategy.errors[:name]).to eq(['strategy name is invalid']) expect(strategy.errors[:name]).to eq(['strategy name is invalid'])
expect(strategy.errors[:parameters]).to be_empty expect(strategy.errors[:parameters]).to be_empty
@ -36,19 +40,24 @@ RSpec.describe Operations::FeatureFlags::Strategy do
end end
with_them do with_them do
it 'must have valid parameters for the strategy' do it 'must have valid parameters for the strategy' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'gradualRolloutUserId', parameters: invalid_parameters) :gradual_rollout,
feature_flag: feature_flag,
parameters: invalid_parameters)
expect(strategy).to be_invalid
expect(strategy.errors[:parameters]).to eq(['parameters are invalid']) expect(strategy.errors[:parameters]).to eq(['parameters are invalid'])
end end
end end
it 'allows the parameters in any order' do it 'allows the parameters in any order' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'gradualRolloutUserId', :gradual_rollout,
parameters: { percentage: '10', groupId: 'mygroup' }) feature_flag: feature_flag,
parameters: { percentage: '10', groupId: 'mygroup' })
expect(strategy.errors[:parameters]).to be_empty expect(strategy).to be_valid
end end
describe 'percentage' do describe 'percentage' do
@ -59,9 +68,12 @@ RSpec.describe Operations::FeatureFlags::Strategy do
end end
with_them do with_them do
it 'must be a string value between 0 and 100 inclusive and without a percentage sign' do it 'must be a string value between 0 and 100 inclusive and without a percentage sign' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'gradualRolloutUserId', :gradual_rollout,
parameters: { groupId: 'mygroup', percentage: invalid_value }) feature_flag: feature_flag,
parameters: { groupId: 'mygroup', percentage: invalid_value })
expect(strategy).to be_invalid
expect(strategy.errors[:parameters]).to eq(['percentage must be a string between 0 and 100 inclusive']) expect(strategy.errors[:parameters]).to eq(['percentage must be a string between 0 and 100 inclusive'])
end end
@ -72,11 +84,12 @@ RSpec.describe Operations::FeatureFlags::Strategy do
end end
with_them do with_them do
it 'must be a string value between 0 and 100 inclusive and without a percentage sign' do it 'must be a string value between 0 and 100 inclusive and without a percentage sign' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'gradualRolloutUserId', :gradual_rollout,
parameters: { groupId: 'mygroup', percentage: valid_value }) feature_flag: feature_flag,
parameters: { groupId: 'mygroup', percentage: valid_value })
expect(strategy.errors[:parameters]).to eq([]) expect(strategy).to be_valid
end end
end end
end end
@ -88,9 +101,12 @@ RSpec.describe Operations::FeatureFlags::Strategy do
end end
with_them do with_them do
it 'must be a string value of up to 32 lowercase characters' do it 'must be a string value of up to 32 lowercase characters' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'gradualRolloutUserId', :gradual_rollout,
parameters: { groupId: invalid_value, percentage: '40' }) feature_flag: feature_flag,
parameters: { groupId: invalid_value, percentage: '40' })
expect(strategy).to be_invalid
expect(strategy.errors[:parameters]).to eq(['groupId parameter is invalid']) expect(strategy.errors[:parameters]).to eq(['groupId parameter is invalid'])
end end
@ -101,11 +117,12 @@ RSpec.describe Operations::FeatureFlags::Strategy do
end end
with_them do with_them do
it 'must be a string value of up to 32 lowercase characters' do it 'must be a string value of up to 32 lowercase characters' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'gradualRolloutUserId', :gradual_rollout,
parameters: { groupId: valid_value, percentage: '40' }) feature_flag: feature_flag,
parameters: { groupId: valid_value, percentage: '40' })
expect(strategy.errors[:parameters]).to eq([]) expect(strategy).to be_valid
end end
end end
end end
@ -123,9 +140,12 @@ RSpec.describe Operations::FeatureFlags::Strategy do
]) ])
with_them do with_them do
it 'must have valid parameters for the strategy' do it 'must have valid parameters for the strategy' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'flexibleRollout', :flexible_rollout,
parameters: invalid_parameters) feature_flag: feature_flag,
parameters: invalid_parameters)
expect(strategy).to be_invalid
expect(strategy.errors[:parameters]).to eq(['parameters are invalid']) expect(strategy.errors[:parameters]).to eq(['parameters are invalid'])
end end
@ -137,11 +157,12 @@ RSpec.describe Operations::FeatureFlags::Strategy do
[:groupId, 'mygroup'] [:groupId, 'mygroup']
].permutation(3).each do |parameters| ].permutation(3).each do |parameters|
it "allows the parameters in the order #{parameters.map { |p| p.first }.join(', ')}" do it "allows the parameters in the order #{parameters.map { |p| p.first }.join(', ')}" do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'flexibleRollout', :flexible_rollout,
parameters: Hash[parameters]) feature_flag: feature_flag,
parameters: Hash[parameters])
expect(strategy.errors[:parameters]).to be_empty expect(strategy).to be_valid
end end
end end
@ -152,9 +173,12 @@ RSpec.describe Operations::FeatureFlags::Strategy do
with_them do with_them do
it 'must be a string value between 0 and 100 inclusive and without a percentage sign' do it 'must be a string value between 0 and 100 inclusive and without a percentage sign' do
parameters = { stickiness: 'default', groupId: 'mygroup', rollout: invalid_value } parameters = { stickiness: 'default', groupId: 'mygroup', rollout: invalid_value }
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'flexibleRollout', :flexible_rollout,
parameters: parameters) feature_flag: feature_flag,
parameters: parameters)
expect(strategy).to be_invalid
expect(strategy.errors[:parameters]).to eq([ expect(strategy.errors[:parameters]).to eq([
'rollout must be a string between 0 and 100 inclusive' 'rollout must be a string between 0 and 100 inclusive'
@ -166,11 +190,12 @@ RSpec.describe Operations::FeatureFlags::Strategy do
with_them do with_them do
it 'must be a string value between 0 and 100 inclusive and without a percentage sign' do it 'must be a string value between 0 and 100 inclusive and without a percentage sign' do
parameters = { stickiness: 'default', groupId: 'mygroup', rollout: valid_value } parameters = { stickiness: 'default', groupId: 'mygroup', rollout: valid_value }
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'flexibleRollout', :flexible_rollout,
parameters: parameters) feature_flag: feature_flag,
parameters: parameters)
expect(strategy.errors[:parameters]).to eq([]) expect(strategy).to be_valid
end end
end end
end end
@ -181,9 +206,12 @@ RSpec.describe Operations::FeatureFlags::Strategy do
with_them do with_them do
it 'must be a string value of up to 32 lowercase characters' do it 'must be a string value of up to 32 lowercase characters' do
parameters = { stickiness: 'default', groupId: invalid_value, rollout: '40' } parameters = { stickiness: 'default', groupId: invalid_value, rollout: '40' }
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'flexibleRollout', :flexible_rollout,
parameters: parameters) feature_flag: feature_flag,
parameters: parameters)
expect(strategy).to be_invalid
expect(strategy.errors[:parameters]).to eq(['groupId parameter is invalid']) expect(strategy.errors[:parameters]).to eq(['groupId parameter is invalid'])
end end
@ -193,11 +221,12 @@ RSpec.describe Operations::FeatureFlags::Strategy do
with_them do with_them do
it 'must be a string value of up to 32 lowercase characters' do it 'must be a string value of up to 32 lowercase characters' do
parameters = { stickiness: 'default', groupId: valid_value, rollout: '40' } parameters = { stickiness: 'default', groupId: valid_value, rollout: '40' }
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'flexibleRollout', :flexible_rollout,
parameters: parameters) feature_flag: feature_flag,
parameters: parameters)
expect(strategy.errors[:parameters]).to eq([]) expect(strategy).to be_valid
end end
end end
end end
@ -207,9 +236,12 @@ RSpec.describe Operations::FeatureFlags::Strategy do
with_them do with_them do
it 'must be a string representing a supported stickiness setting' do it 'must be a string representing a supported stickiness setting' do
parameters = { stickiness: invalid_value, groupId: 'mygroup', rollout: '40' } parameters = { stickiness: invalid_value, groupId: 'mygroup', rollout: '40' }
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'flexibleRollout', :flexible_rollout,
parameters: parameters) feature_flag: feature_flag,
parameters: parameters)
expect(strategy).to be_invalid
expect(strategy.errors[:parameters]).to eq([ expect(strategy.errors[:parameters]).to eq([
'stickiness parameter must be default, userId, sessionId, or random' 'stickiness parameter must be default, userId, sessionId, or random'
@ -221,11 +253,12 @@ RSpec.describe Operations::FeatureFlags::Strategy do
with_them do with_them do
it 'must be a string representing a supported stickiness setting' do it 'must be a string representing a supported stickiness setting' do
parameters = { stickiness: valid_value, groupId: 'mygroup', rollout: '40' } parameters = { stickiness: valid_value, groupId: 'mygroup', rollout: '40' }
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'flexibleRollout', :flexible_rollout,
parameters: parameters) feature_flag: feature_flag,
parameters: parameters)
expect(strategy.errors[:parameters]).to eq([]) expect(strategy).to be_valid
end end
end end
end end
@ -237,8 +270,11 @@ RSpec.describe Operations::FeatureFlags::Strategy do
end end
with_them do with_them do
it 'must have valid parameters for the strategy' do it 'must have valid parameters for the strategy' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'userWithId', parameters: invalid_parameters) feature_flag: feature_flag,
name: 'userWithId', parameters: invalid_parameters)
expect(strategy).to be_invalid
expect(strategy.errors[:parameters]).to eq(['parameters are invalid']) expect(strategy.errors[:parameters]).to eq(['parameters are invalid'])
end end
@ -253,10 +289,12 @@ RSpec.describe Operations::FeatureFlags::Strategy do
end end
with_them do with_them do
it 'is valid with a string of comma separated values' do it 'is valid with a string of comma separated values' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'userWithId', parameters: { userIds: valid_value }) feature_flag: feature_flag,
name: 'userWithId',
parameters: { userIds: valid_value })
expect(strategy.errors[:parameters]).to be_empty expect(strategy).to be_valid
end end
end end
@ -267,8 +305,12 @@ RSpec.describe Operations::FeatureFlags::Strategy do
end end
with_them do with_them do
it 'is invalid' do it 'is invalid' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'userWithId', parameters: { userIds: invalid_value }) feature_flag: feature_flag,
name: 'userWithId',
parameters: { userIds: invalid_value })
expect(strategy).to be_invalid
expect(strategy.errors[:parameters]).to include( expect(strategy.errors[:parameters]).to include(
'userIds must be a string of unique comma separated values each 256 characters or less' 'userIds must be a string of unique comma separated values each 256 characters or less'
@ -284,43 +326,48 @@ RSpec.describe Operations::FeatureFlags::Strategy do
end end
with_them do with_them do
it 'must be empty' do it 'must be empty' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy, :default, feature_flag: feature_flag, parameters: invalid_value)
name: 'default',
parameters: invalid_value) expect(strategy).to be_invalid
expect(strategy.errors[:parameters]).to eq(['parameters are invalid']) expect(strategy.errors[:parameters]).to eq(['parameters are invalid'])
end end
end end
it 'must be empty' do it 'must be empty' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy, :default, feature_flag: feature_flag)
name: 'default',
parameters: {})
expect(strategy.errors[:parameters]).to be_empty expect(strategy).to be_valid
end end
end end
context 'when the strategy name is gitlabUserList' do context 'when the strategy name is gitlabUserList' do
let_it_be(:user_list) { create(:operations_feature_flag_user_list, project: project) }
where(:invalid_value) do where(:invalid_value) do
[{ groupId: "default", percentage: "7" }, "", "nothing", 7, nil, [], 2.5, { userIds: 'user1' }] [{ groupId: "default", percentage: "7" }, "", "nothing", 7, nil, [], 2.5, { userIds: 'user1' }]
end end
with_them do with_them do
it 'must be empty' do it 'is invalid' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'gitlabUserList', :gitlab_userlist,
parameters: invalid_value) user_list: user_list,
feature_flag: feature_flag,
parameters: invalid_value)
expect(strategy).to be_invalid
expect(strategy.errors[:parameters]).to eq(['parameters are invalid']) expect(strategy.errors[:parameters]).to eq(['parameters are invalid'])
end end
end end
it 'must be empty' do it 'is valid' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy,
name: 'gitlabUserList', :gitlab_userlist,
parameters: {}) user_list: user_list,
feature_flag: feature_flag)
expect(strategy.errors[:parameters]).to be_empty expect(strategy).to be_valid
end end
end end
end end
@ -329,18 +376,15 @@ RSpec.describe Operations::FeatureFlags::Strategy do
context 'when name is gitlabUserList' do context 'when name is gitlabUserList' do
it 'is valid when associated with a user list' do it 'is valid when associated with a user list' do
user_list = create(:operations_feature_flag_user_list, project: project) user_list = create(:operations_feature_flag_user_list, project: project)
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy, :gitlab_userlist, feature_flag: feature_flag, user_list: user_list)
name: 'gitlabUserList',
user_list: user_list,
parameters: {})
expect(strategy.errors[:user_list]).to be_empty expect(strategy).to be_valid
end end
it 'is invalid without a user list' do it 'is invalid without a user list' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy, :gitlab_userlist, feature_flag: feature_flag, user_list: nil)
name: 'gitlabUserList',
parameters: {}) expect(strategy).to be_invalid
expect(strategy.errors[:user_list]).to eq(["can't be blank"]) expect(strategy.errors[:user_list]).to eq(["can't be blank"])
end end
@ -348,10 +392,9 @@ RSpec.describe Operations::FeatureFlags::Strategy do
it 'is invalid when associated with a user list from another project' do it 'is invalid when associated with a user list from another project' do
other_project = create(:project) other_project = create(:project)
user_list = create(:operations_feature_flag_user_list, project: other_project) user_list = create(:operations_feature_flag_user_list, project: other_project)
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy, :gitlab_userlist, feature_flag: feature_flag, user_list: user_list)
name: 'gitlabUserList',
user_list: user_list, expect(strategy).to be_invalid
parameters: {})
expect(strategy.errors[:user_list]).to eq(['must belong to the same project']) expect(strategy.errors[:user_list]).to eq(['must belong to the same project'])
end end
@ -360,84 +403,68 @@ RSpec.describe Operations::FeatureFlags::Strategy do
context 'when name is default' do context 'when name is default' do
it 'is invalid when associated with a user list' do it 'is invalid when associated with a user list' do
user_list = create(:operations_feature_flag_user_list, project: project) user_list = create(:operations_feature_flag_user_list, project: project)
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy, :default, feature_flag: feature_flag, user_list: user_list)
name: 'default',
user_list: user_list, expect(strategy).to be_invalid
parameters: {})
expect(strategy.errors[:user_list]).to eq(['must be blank']) expect(strategy.errors[:user_list]).to eq(['must be blank'])
end end
it 'is valid without a user list' do it 'is valid without a user list' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy, :default, feature_flag: feature_flag)
name: 'default',
parameters: {})
expect(strategy.errors[:user_list]).to be_empty expect(strategy).to be_valid
end end
end end
context 'when name is userWithId' do context 'when name is userWithId' do
it 'is invalid when associated with a user list' do it 'is invalid when associated with a user list' do
user_list = create(:operations_feature_flag_user_list, project: project) user_list = create(:operations_feature_flag_user_list, project: project)
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy, :userwithid, feature_flag: feature_flag, user_list: user_list)
name: 'userWithId',
user_list: user_list, expect(strategy).to be_invalid
parameters: { userIds: 'user1' })
expect(strategy.errors[:user_list]).to eq(['must be blank']) expect(strategy.errors[:user_list]).to eq(['must be blank'])
end end
it 'is valid without a user list' do it 'is valid without a user list' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy, :userwithid, feature_flag: feature_flag)
name: 'userWithId',
parameters: { userIds: 'user1' })
expect(strategy.errors[:user_list]).to be_empty expect(strategy).to be_valid
end end
end end
context 'when name is gradualRolloutUserId' do context 'when name is gradualRolloutUserId' do
it 'is invalid when associated with a user list' do it 'is invalid when associated with a user list' do
user_list = create(:operations_feature_flag_user_list, project: project) user_list = create(:operations_feature_flag_user_list, project: project)
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy, :gradual_rollout, feature_flag: feature_flag, user_list: user_list)
name: 'gradualRolloutUserId',
user_list: user_list, expect(strategy).to be_invalid
parameters: { groupId: 'default', percentage: '10' })
expect(strategy.errors[:user_list]).to eq(['must be blank']) expect(strategy.errors[:user_list]).to eq(['must be blank'])
end end
it 'is valid without a user list' do it 'is valid without a user list' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy, :gradual_rollout, feature_flag: feature_flag)
name: 'gradualRolloutUserId',
parameters: { groupId: 'default', percentage: '10' })
expect(strategy.errors[:user_list]).to be_empty expect(strategy).to be_valid
end end
end end
context 'when name is flexibleRollout' do context 'when name is flexibleRollout' do
it 'is invalid when associated with a user list' do it 'is invalid when associated with a user list' do
user_list = create(:operations_feature_flag_user_list, project: project) user_list = create(:operations_feature_flag_user_list, project: project)
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy, :flexible_rollout, feature_flag: feature_flag, user_list: user_list)
name: 'flexibleRollout',
user_list: user_list, expect(strategy).to be_invalid
parameters: { groupId: 'default',
rollout: '10',
stickiness: 'default' })
expect(strategy.errors[:user_list]).to eq(['must be blank']) expect(strategy.errors[:user_list]).to eq(['must be blank'])
end end
it 'is valid without a user list' do it 'is valid without a user list' do
strategy = described_class.create(feature_flag: feature_flag, strategy = build(:operations_strategy, :flexible_rollout, feature_flag: feature_flag)
name: 'flexibleRollout',
parameters: { groupId: 'default',
rollout: '10',
stickiness: 'default' })
expect(strategy.errors[:user_list]).to be_empty expect(strategy).to be_valid
end end
end end
end end

View File

@ -20,9 +20,9 @@ RSpec.describe Operations::FeatureFlags::UserList do
end end
with_them do with_them do
it 'is valid with a string of comma separated values' do it 'is valid with a string of comma separated values' do
user_list = described_class.create(user_xids: valid_value) user_list = build(:operations_feature_flag_user_list, user_xids: valid_value)
expect(user_list.errors[:user_xids]).to be_empty expect(user_list).to be_valid
end end
end end
@ -31,9 +31,10 @@ RSpec.describe Operations::FeatureFlags::UserList do
end end
with_them do with_them do
it 'automatically casts values of other types' do it 'automatically casts values of other types' do
user_list = described_class.create(user_xids: typecast_value) user_list = build(:operations_feature_flag_user_list, user_xids: typecast_value)
expect(user_list).to be_valid
expect(user_list.errors[:user_xids]).to be_empty
expect(user_list.user_xids).to eq(typecast_value.to_s) expect(user_list.user_xids).to eq(typecast_value.to_s)
end end
end end
@ -45,7 +46,9 @@ RSpec.describe Operations::FeatureFlags::UserList do
end end
with_them do with_them do
it 'is invalid' do it 'is invalid' do
user_list = described_class.create(user_xids: invalid_value) user_list = build(:operations_feature_flag_user_list, user_xids: invalid_value)
expect(user_list).to be_invalid
expect(user_list.errors[:user_xids]).to include( expect(user_list.errors[:user_xids]).to include(
'user_xids must be a string of unique comma separated values each 256 characters or less' 'user_xids must be a string of unique comma separated values each 256 characters or less'
@ -70,20 +73,20 @@ RSpec.describe Operations::FeatureFlags::UserList do
describe '#destroy' do describe '#destroy' do
it 'deletes the model if it is not associated with any feature flag strategies' do it 'deletes the model if it is not associated with any feature flag strategies' do
project = create(:project) project = create(:project)
user_list = described_class.create(project: project, name: 'My User List', user_xids: 'user1,user2') user_list = described_class.create!(project: project, name: 'My User List', user_xids: 'user1,user2')
user_list.destroy user_list.destroy!
expect(described_class.count).to eq(0) expect(described_class.count).to eq(0)
end end
it 'does not delete the model if it is associated with a feature flag strategy' do it 'does not delete the model if it is associated with a feature flag strategy' do
project = create(:project) project = create(:project)
user_list = described_class.create(project: project, name: 'My User List', user_xids: 'user1,user2') user_list = described_class.create!(project: project, name: 'My User List', user_xids: 'user1,user2')
feature_flag = create(:operations_feature_flag, :new_version_flag, project: project) feature_flag = create(:operations_feature_flag, :new_version_flag, project: project)
strategy = create(:operations_strategy, feature_flag: feature_flag, name: 'gitlabUserList', user_list: user_list) strategy = create(:operations_strategy, feature_flag: feature_flag, name: 'gitlabUserList', user_list: user_list)
user_list.destroy user_list.destroy # rubocop:disable Rails/SaveBang
expect(described_class.count).to eq(1) expect(described_class.count).to eq(1)
expect(::Operations::FeatureFlags::StrategyUserList.count).to eq(1) expect(::Operations::FeatureFlags::StrategyUserList.count).to eq(1)

View File

@ -104,7 +104,7 @@ RSpec.describe PagesDomain do
let(:domain) { build(:pages_domain) } let(:domain) { build(:pages_domain) }
it 'saves validity time' do it 'saves validity time' do
domain.save domain.save!
expect(domain.certificate_valid_not_before).to be_like_time(Time.zone.parse("2020-03-16 14:20:34 UTC")) expect(domain.certificate_valid_not_before).to be_like_time(Time.zone.parse("2020-03-16 14:20:34 UTC"))
expect(domain.certificate_valid_not_after).to be_like_time(Time.zone.parse("2220-01-28 14:20:34 UTC")) expect(domain.certificate_valid_not_after).to be_like_time(Time.zone.parse("2220-01-28 14:20:34 UTC"))
@ -161,7 +161,7 @@ RSpec.describe PagesDomain do
context 'when certificate is already saved' do context 'when certificate is already saved' do
it "doesn't add error to certificate" do it "doesn't add error to certificate" do
domain.save(validate: false) domain.save!(validate: false)
domain.valid? domain.valid?

View File

@ -16,7 +16,7 @@ RSpec.describe ProtectableDropdown do
describe '#protectable_ref_names' do describe '#protectable_ref_names' do
context 'when project repository is not empty' do context 'when project repository is not empty' do
before do before do
project.protected_branches.create(name: 'master') create(:protected_branch, project: project, name: 'master')
end end
it { expect(subject.protectable_ref_names).to include('feature') } it { expect(subject.protectable_ref_names).to include('feature') }

View File

@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe RedirectRoute do RSpec.describe RedirectRoute do
let(:group) { create(:group) } let(:group) { create(:group) }
let!(:redirect_route) { group.redirect_routes.create(path: 'gitlabb') } let!(:redirect_route) { group.redirect_routes.create!(path: 'gitlabb') }
describe 'relationships' do describe 'relationships' do
it { is_expected.to belong_to(:source) } it { is_expected.to belong_to(:source) }
@ -17,10 +17,10 @@ RSpec.describe RedirectRoute do
end end
describe '.matching_path_and_descendants' do describe '.matching_path_and_descendants' do
let!(:redirect2) { group.redirect_routes.create(path: 'gitlabb/test') } let!(:redirect2) { group.redirect_routes.create!(path: 'gitlabb/test') }
let!(:redirect3) { group.redirect_routes.create(path: 'gitlabb/test/foo') } let!(:redirect3) { group.redirect_routes.create!(path: 'gitlabb/test/foo') }
let!(:redirect4) { group.redirect_routes.create(path: 'gitlabb/test/foo/bar') } let!(:redirect4) { group.redirect_routes.create!(path: 'gitlabb/test/foo/bar') }
let!(:redirect5) { group.redirect_routes.create(path: 'gitlabb/test/baz') } let!(:redirect5) { group.redirect_routes.create!(path: 'gitlabb/test/baz') }
context 'when the redirect route matches with same casing' do context 'when the redirect route matches with same casing' do
it 'returns correct routes' do it 'returns correct routes' do

View File

@ -26,10 +26,10 @@ RSpec.describe Release do
context 'when a release exists in the database without a name' do context 'when a release exists in the database without a name' do
it 'does not require name' do it 'does not require name' do
existing_release_without_name = build(:release, project: project, author: user, name: nil) existing_release_without_name = build(:release, project: project, author: user, name: nil)
existing_release_without_name.save(validate: false) existing_release_without_name.save!(validate: false)
existing_release_without_name.description = "change" existing_release_without_name.description = "change"
existing_release_without_name.save existing_release_without_name.save!
existing_release_without_name.reload existing_release_without_name.reload
expect(existing_release_without_name).to be_valid expect(existing_release_without_name).to be_valid
@ -88,7 +88,7 @@ RSpec.describe Release do
describe '.create' do describe '.create' do
it "fills released_at using created_at if it's not set" do it "fills released_at using created_at if it's not set" do
release = described_class.create(project: project, author: user) release = create(:release, project: project, author: user, released_at: nil)
expect(release.released_at).to eq(release.created_at) expect(release.released_at).to eq(release.created_at)
end end
@ -96,14 +96,14 @@ RSpec.describe Release do
it "does not change released_at if it's set explicitly" do it "does not change released_at if it's set explicitly" do
released_at = Time.zone.parse('2018-10-20T18:00:00Z') released_at = Time.zone.parse('2018-10-20T18:00:00Z')
release = described_class.create(project: project, author: user, released_at: released_at) release = create(:release, project: project, author: user, released_at: released_at)
expect(release.released_at).to eq(released_at) expect(release.released_at).to eq(released_at)
end end
end end
describe '#update' do describe '#update' do
subject { release.update(params) } subject { release.update!(params) }
context 'when links do not exist' do context 'when links do not exist' do
context 'when params are specified for creation' do context 'when params are specified for creation' do
@ -182,7 +182,7 @@ RSpec.describe Release do
it 'also deletes the associated evidence' do it 'also deletes the associated evidence' do
release_with_evidence release_with_evidence
expect { release_with_evidence.destroy }.to change(Releases::Evidence, :count).by(-1) expect { release_with_evidence.destroy! }.to change(Releases::Evidence, :count).by(-1)
end end
end end
end end
@ -190,7 +190,7 @@ RSpec.describe Release do
describe '#name' do describe '#name' do
context 'name is nil' do context 'name is nil' do
before do before do
release.update(name: nil) release.update!(name: nil)
end end
it 'returns tag' do it 'returns tag' do

View File

@ -289,7 +289,7 @@ RSpec.describe RemoteMirror, :mailer do
context 'with remote mirroring disabled' do context 'with remote mirroring disabled' do
it 'returns nil' do it 'returns nil' do
remote_mirror.update(enabled: false) remote_mirror.update!(enabled: false)
expect(remote_mirror.sync).to be_nil expect(remote_mirror.sync).to be_nil
end end
@ -354,7 +354,7 @@ RSpec.describe RemoteMirror, :mailer do
let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first } let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first }
it 'resets all the columns when URL changes' do it 'resets all the columns when URL changes' do
remote_mirror.update(last_error: Time.current, remote_mirror.update!(last_error: Time.current,
last_update_at: Time.current, last_update_at: Time.current,
last_successful_update_at: Time.current, last_successful_update_at: Time.current,
update_status: 'started', update_status: 'started',
@ -378,7 +378,7 @@ RSpec.describe RemoteMirror, :mailer do
end end
before do before do
remote_mirror.update(last_update_started_at: Time.current) remote_mirror.update!(last_update_started_at: Time.current)
end end
context 'when remote mirror does not have status failed' do context 'when remote mirror does not have status failed' do
@ -393,7 +393,7 @@ RSpec.describe RemoteMirror, :mailer do
context 'when remote mirror has status failed' do context 'when remote mirror has status failed' do
it 'returns false when last update started after the timestamp' do it 'returns false when last update started after the timestamp' do
remote_mirror.update(update_status: 'failed') remote_mirror.update!(update_status: 'failed')
expect(remote_mirror.updated_since?(timestamp)).to be false expect(remote_mirror.updated_since?(timestamp)).to be false
end end
@ -409,7 +409,7 @@ RSpec.describe RemoteMirror, :mailer do
updated_at: 25.hours.ago) updated_at: 25.hours.ago)
project = mirror.project project = mirror.project
project.pending_delete = true project.pending_delete = true
project.save project.save!
mirror.reload mirror.reload
expect(mirror.sync).to be_nil expect(mirror.sync).to be_nil

View File

@ -31,18 +31,18 @@ RSpec.describe Route do
context 'after update' do context 'after update' do
it 'calls #create_redirect_for_old_path' do it 'calls #create_redirect_for_old_path' do
expect(route).to receive(:create_redirect_for_old_path) expect(route).to receive(:create_redirect_for_old_path)
route.update(path: 'foo') route.update!(path: 'foo')
end end
it 'calls #delete_conflicting_redirects' do it 'calls #delete_conflicting_redirects' do
expect(route).to receive(:delete_conflicting_redirects) expect(route).to receive(:delete_conflicting_redirects)
route.update(path: 'foo') route.update!(path: 'foo')
end end
end end
context 'after create' do context 'after create' do
it 'calls #delete_conflicting_redirects' do it 'calls #delete_conflicting_redirects' do
route.destroy route.destroy!
new_route = described_class.new(source: group, path: group.path) new_route = described_class.new(source: group, path: group.path)
expect(new_route).to receive(:delete_conflicting_redirects) expect(new_route).to receive(:delete_conflicting_redirects)
new_route.save! new_route.save!
@ -81,7 +81,7 @@ RSpec.describe Route do
context 'path update' do context 'path update' do
context 'when route name is set' do context 'when route name is set' do
before do before do
route.update(path: 'bar') route.update!(path: 'bar')
end end
it 'updates children routes with new path' do it 'updates children routes with new path' do
@ -111,7 +111,7 @@ RSpec.describe Route do
let!(:conflicting_redirect3) { route.create_redirect('gitlab-org') } let!(:conflicting_redirect3) { route.create_redirect('gitlab-org') }
it 'deletes the conflicting redirects' do it 'deletes the conflicting redirects' do
route.update(path: 'bar') route.update!(path: 'bar')
expect(RedirectRoute.exists?(path: 'bar/test')).to be_falsey expect(RedirectRoute.exists?(path: 'bar/test')).to be_falsey
expect(RedirectRoute.exists?(path: 'bar/test/foo')).to be_falsey expect(RedirectRoute.exists?(path: 'bar/test/foo')).to be_falsey
@ -122,7 +122,7 @@ RSpec.describe Route do
context 'name update' do context 'name update' do
it 'updates children routes with new path' do it 'updates children routes with new path' do
route.update(name: 'bar') route.update!(name: 'bar')
expect(described_class.exists?(name: 'bar')).to be_truthy expect(described_class.exists?(name: 'bar')).to be_truthy
expect(described_class.exists?(name: 'bar / test')).to be_truthy expect(described_class.exists?(name: 'bar / test')).to be_truthy
@ -134,7 +134,7 @@ RSpec.describe Route do
# Note: using `update_columns` to skip all validation and callbacks # Note: using `update_columns` to skip all validation and callbacks
route.update_columns(name: nil) route.update_columns(name: nil)
expect { route.update(name: 'bar') } expect { route.update!(name: 'bar') }
.to change { route.name }.from(nil).to('bar') .to change { route.name }.from(nil).to('bar')
end end
end end

View File

@ -53,7 +53,7 @@ RSpec.describe SentryIssue do
create(:sentry_issue) create(:sentry_issue)
project = sentry_issue.issue.project project = sentry_issue.issue.project
sentry_issue_3 = build(:sentry_issue, issue: create(:issue, project: project), sentry_issue_identifier: sentry_issue.sentry_issue_identifier) sentry_issue_3 = build(:sentry_issue, issue: create(:issue, project: project), sentry_issue_identifier: sentry_issue.sentry_issue_identifier)
sentry_issue_3.save(validate: false) sentry_issue_3.save!(validate: false)
result = described_class.for_project_and_identifier(project, sentry_issue.sentry_issue_identifier) result = described_class.for_project_and_identifier(project, sentry_issue.sentry_issue_identifier)

View File

@ -98,7 +98,7 @@ RSpec.describe Snippet do
snippet = build(:snippet) snippet = build(:snippet)
expect(snippet.statistics).to be_nil expect(snippet.statistics).to be_nil
snippet.save snippet.save!
expect(snippet.statistics).to be_persisted expect(snippet.statistics).to be_persisted
end end
@ -289,7 +289,7 @@ RSpec.describe Snippet do
let(:access_level) { ProjectFeature::ENABLED } let(:access_level) { ProjectFeature::ENABLED }
before do before do
project.project_feature.update(snippets_access_level: access_level) project.project_feature.update!(snippets_access_level: access_level)
end end
it 'includes snippets for projects with snippets enabled' do it 'includes snippets for projects with snippets enabled' do
@ -623,7 +623,7 @@ RSpec.describe Snippet do
context 'when snippet_repository does not exist' do context 'when snippet_repository does not exist' do
it 'creates a snippet_repository' do it 'creates a snippet_repository' do
snippet.snippet_repository.destroy snippet.snippet_repository.destroy!
snippet.reload snippet.reload
expect do expect do

View File

@ -19,7 +19,7 @@ RSpec.describe Upload do
it 'schedules checksum calculation' do it 'schedules checksum calculation' do
stub_const('UploadChecksumWorker', spy) stub_const('UploadChecksumWorker', spy)
upload = described_class.create( upload = described_class.create!(
path: __FILE__, path: __FILE__,
size: described_class::CHECKSUM_THRESHOLD + 1.kilobyte, size: described_class::CHECKSUM_THRESHOLD + 1.kilobyte,
model: build_stubbed(:user), model: build_stubbed(:user),
@ -42,7 +42,7 @@ RSpec.describe Upload do
store: ObjectStorage::Store::LOCAL store: ObjectStorage::Store::LOCAL
) )
expect { upload.save } expect { upload.save! }
.to change { upload.checksum }.from(nil) .to change { upload.checksum }.from(nil)
.to(a_string_matching(/\A\h{64}\z/)) .to(a_string_matching(/\A\h{64}\z/))
end end
@ -55,7 +55,7 @@ RSpec.describe Upload do
it 'calls delete_file!' do it 'calls delete_file!' do
is_expected.to receive(:delete_file!) is_expected.to receive(:delete_file!)
subject.destroy subject.destroy!
end end
end end
end end

View File

@ -9,30 +9,39 @@ RSpec.describe PipelineTestReportBuilder do
subject do subject do
described_class.new( described_class.new(
project: 'gitlab-org/gitlab', target_project: 'gitlab-org/gitlab',
mr_id: '999', mr_id: '999',
instance_base_url: 'https://gitlab.com', instance_base_url: 'https://gitlab.com',
output_file_path: output_file_path output_file_path: output_file_path
) )
end end
let(:mr_pipelines) do let(:failed_pipeline_url) { 'pipeline2_url' }
[
{ let(:failed_pipeline) do
'status' => 'running', {
'created_at' => DateTime.now.to_s 'status' => 'failed',
}, 'created_at' => (DateTime.now - 5).to_s,
{ 'web_url' => failed_pipeline_url
'status' => 'failed', }
'created_at' => (DateTime.now - 5).to_s
}
]
end end
let(:current_pipeline) do
{
'status' => 'running',
'created_at' => DateTime.now.to_s,
'web_url' => 'pipeline1_url'
}
end
let(:mr_pipelines) { [current_pipeline, failed_pipeline] }
let(:failed_build_id) { 9999 }
let(:failed_builds_for_pipeline) do let(:failed_builds_for_pipeline) do
[ [
{ {
'id' => 9999, 'id' => failed_build_id,
'stage' => 'test' 'stage' => 'test'
} }
] ]
@ -62,75 +71,114 @@ RSpec.describe PipelineTestReportBuilder do
before do before do
allow(subject).to receive(:pipelines_for_mr).and_return(mr_pipelines) allow(subject).to receive(:pipelines_for_mr).and_return(mr_pipelines)
allow(subject).to receive(:failed_builds_for_pipeline).and_return(failed_builds_for_pipeline) allow(subject).to receive(:failed_builds_for_pipeline).and_return(failed_builds_for_pipeline)
allow(subject).to receive(:test_report_for_build).and_return(test_report_for_build) end
describe '#previous_pipeline' do
let(:fork_pipeline_url) { 'fork_pipeline_url' }
let(:fork_pipeline) do
{
'status' => 'failed',
'created_at' => (DateTime.now - 5).to_s,
'web_url' => fork_pipeline_url
}
end
before do
allow(subject).to receive(:test_report_for_build).and_return(test_report_for_build)
end
context 'pipeline in a fork project' do
let(:mr_pipelines) { [current_pipeline, fork_pipeline] }
it 'returns fork pipeline' do
expect(subject.previous_pipeline).to eq(fork_pipeline)
end
end
context 'pipeline in target project' do
it 'returns failed pipeline' do
expect(subject.previous_pipeline).to eq(failed_pipeline)
end
end
end end
describe '#test_report_for_latest_pipeline' do describe '#test_report_for_latest_pipeline' do
context 'no previous pipeline' do it 'fetches builds from pipeline related to MR' do
let(:mr_pipelines) { [] } expect(subject).to receive(:fetch).with("#{failed_pipeline_url}/tests/suite.json?build_ids[]=#{failed_build_id}").and_return(failed_builds_for_pipeline)
subject.test_report_for_latest_pipeline
it 'returns empty hash' do
expect(subject.test_report_for_latest_pipeline).to eq("{}")
end
end end
context 'first pipeline scenario' do context 'canonical pipeline' do
let(:mr_pipelines) do before do
[ allow(subject).to receive(:test_report_for_build).and_return(test_report_for_build)
{
'status' => 'running',
'created_at' => DateTime.now.to_s
}
]
end end
it 'returns empty hash' do context 'no previous pipeline' do
expect(subject.test_report_for_latest_pipeline).to eq("{}") let(:mr_pipelines) { [] }
end
end
context 'no previous failed pipeline' do it 'returns empty hash' do
let(:mr_pipelines) do expect(subject.test_report_for_latest_pipeline).to eq("{}")
[ end
{
'status' => 'running',
'created_at' => DateTime.now.to_s
},
{
'status' => 'success',
'created_at' => (DateTime.now - 5).to_s
}
]
end end
it 'returns empty hash' do context 'first pipeline scenario' do
expect(subject.test_report_for_latest_pipeline).to eq("{}") let(:mr_pipelines) do
end [
end {
'status' => 'running',
'created_at' => DateTime.now.to_s
}
]
end
context 'no failed test builds' do it 'returns empty hash' do
let(:failed_builds_for_pipeline) do expect(subject.test_report_for_latest_pipeline).to eq("{}")
[ end
{
'id' => 9999,
'stage' => 'prepare'
}
]
end end
it 'returns empty hash' do context 'no previous failed pipeline' do
expect(subject.test_report_for_latest_pipeline).to eq("{}") let(:mr_pipelines) do
[
{
'status' => 'running',
'created_at' => DateTime.now.to_s
},
{
'status' => 'success',
'created_at' => (DateTime.now - 5).to_s
}
]
end
it 'returns empty hash' do
expect(subject.test_report_for_latest_pipeline).to eq("{}")
end
end end
end
context 'failed pipeline and failed test builds' do context 'no failed test builds' do
it 'returns populated test list for suites' do let(:failed_builds_for_pipeline) do
actual = subject.test_report_for_latest_pipeline [
expected = { {
'suites' => [test_report_for_build] 'id' => 9999,
}.to_json 'stage' => 'prepare'
}
]
end
expect(actual).to eq(expected) it 'returns empty hash' do
expect(subject.test_report_for_latest_pipeline).to eq("{}")
end
end
context 'failed pipeline and failed test builds' do
it 'returns populated test list for suites' do
actual = subject.test_report_for_latest_pipeline
expected = {
'suites' => [test_report_for_build]
}.to_json
expect(actual).to eq(expected)
end
end end
end end
end end