diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-04 21:08:41 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-04 21:08:41 +0000 |
commit | 9e19896fb55fe053414109dcfb6e4f3142918e08 (patch) | |
tree | ea488213c66a9adc11496e63cdb658e075af48d0 | |
parent | b3555357704e2776fc0c960eaf931b0e9b0f0ddf (diff) | |
download | gitlab-ce-9e19896fb55fe053414109dcfb6e4f3142918e08.tar.gz |
Add latest changes from gitlab-org/gitlab@master
36 files changed, 348 insertions, 233 deletions
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue index 1bf705dcda2..c06ab265915 100644 --- a/app/assets/javascripts/environments/components/environments_table.vue +++ b/app/assets/javascripts/environments/components/environments_table.vue @@ -14,6 +14,7 @@ export default { DeployBoard: () => import('ee_component/environments/components/deploy_board_component.vue'), CanaryDeploymentCallout: () => import('ee_component/environments/components/canary_deployment_callout.vue'), + EnvironmentAlert: () => import('ee_component/environments/components/environment_alert.vue'), }, props: { environments: { @@ -111,6 +112,9 @@ export default { shouldShowCanaryCallout(env) { return env.showCanaryCallout && this.showCanaryDeploymentCallout; }, + shouldRenderAlert(env) { + return env?.has_opened_alert; + }, sortEnvironments(environments) { /* * The sorting algorithm should sort in the following priorities: @@ -185,6 +189,11 @@ export default { /> </div> </div> + <environment-alert + v-if="shouldRenderAlert(model)" + :key="`alert-row-${i}`" + :environment="model" + /> <template v-if="shouldRenderFolderContent(model)"> <div v-if="model.isLoadingFolderContent" :key="`loading-item-${i}`"> diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js index 56896ac4d43..6c547c3713a 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js +++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js @@ -1,20 +1,33 @@ import Vue from 'vue'; +import VueApollo from 'vue-apollo'; import canaryCalloutMixin from '../mixins/canary_callout_mixin'; import environmentsFolderApp from './environments_folder_view.vue'; import { parseBoolean } from '../../lib/utils/common_utils'; import Translate from '../../vue_shared/translate'; +import createDefaultClient from '~/lib/graphql'; Vue.use(Translate); +Vue.use(VueApollo); -export default () => - new Vue({ - el: '#environments-folder-list-view', +const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), +}); + +export default () => { + const el = document.getElementById('environments-folder-list-view'); + + return new Vue({ + el, components: { environmentsFolderApp, }, mixins: [canaryCalloutMixin], + apolloProvider, + provide: { + projectPath: el.dataset.projectPath, + }, data() { - const environmentsData = document.querySelector(this.$options.el).dataset; + const environmentsData = el.dataset; return { endpoint: environmentsData.environmentsDataEndpoint, @@ -35,3 +48,4 @@ export default () => }); }, }); +}; diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue index e1e356a977f..16d25615779 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.vue +++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue @@ -23,7 +23,8 @@ export default { }, cssContainerClass: { type: String, - required: true, + required: false, + default: '', }, canReadEnvironment: { type: Boolean, diff --git a/app/assets/javascripts/environments/index.js b/app/assets/javascripts/environments/index.js index 4848cb0f13d..8e8af3f32f7 100644 --- a/app/assets/javascripts/environments/index.js +++ b/app/assets/javascripts/environments/index.js @@ -1,20 +1,32 @@ import Vue from 'vue'; +import VueApollo from 'vue-apollo'; import canaryCalloutMixin from './mixins/canary_callout_mixin'; import environmentsComponent from './components/environments_app.vue'; import { parseBoolean } from '../lib/utils/common_utils'; import Translate from '../vue_shared/translate'; +import createDefaultClient from '~/lib/graphql'; Vue.use(Translate); +Vue.use(VueApollo); -export default () => - new Vue({ - el: '#environments-list-view', +const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), +}); + +export default () => { + const el = document.getElementById('environments-list-view'); + return new Vue({ + el, components: { environmentsComponent, }, mixins: [canaryCalloutMixin], + apolloProvider, + provide: { + projectPath: el.dataset.projectPath, + }, data() { - const environmentsData = document.querySelector(this.$options.el).dataset; + const environmentsData = el.dataset; return { endpoint: environmentsData.environmentsDataEndpoint, @@ -39,3 +51,4 @@ export default () => }); }, }); +}; diff --git a/app/assets/javascripts/environments/mixins/canary_callout_mixin.js b/app/assets/javascripts/environments/mixins/canary_callout_mixin.js index 398576a31cb..e9f1a144cb3 100644 --- a/app/assets/javascripts/environments/mixins/canary_callout_mixin.js +++ b/app/assets/javascripts/environments/mixins/canary_callout_mixin.js @@ -2,7 +2,7 @@ import { parseBoolean } from '~/lib/utils/common_utils'; export default { data() { - const data = document.querySelector(this.$options.el).dataset; + const data = this.$options.el.dataset; return { canaryDeploymentFeatureId: data.environmentsDataCanaryDeploymentFeatureId, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue index 3e2c119fa7c..5066a88b52b 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue @@ -1,8 +1,15 @@ <script> /* eslint-disable vue/require-default-prop, vue/no-v-html */ -import { GlIcon, GlLink, GlLoadingIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui'; +import { + GlIcon, + GlLink, + GlLoadingIcon, + GlSprintf, + GlTooltip, + GlTooltipDirective, +} from '@gitlab/ui'; import mrWidgetPipelineMixin from 'ee_else_ce/vue_merge_request_widget/mixins/mr_widget_pipeline'; -import { s__ } from '~/locale'; +import { s__, n__ } from '~/locale'; import PipelineStage from '~/pipelines/components/pipelines_list/stage.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; @@ -15,6 +22,7 @@ export default { GlLoadingIcon, GlIcon, GlSprintf, + GlTooltip, PipelineStage, TooltipOnTruncate, LinkedPipelinesMiniList: () => @@ -33,6 +41,11 @@ export default { type: String, required: false, }, + buildsWithCoverage: { + type: Array, + required: false, + default: () => [], + }, // This prop needs to be camelCase, html attributes are case insensive // https://vuejs.org/v2/guide/components.html#camelCase-vs-kebab-case hasCi: { @@ -100,6 +113,16 @@ export default { } return ''; }, + pipelineCoverageJobNumberText() { + return n__('from %d job', 'from %d jobs', this.buildsWithCoverage.length); + }, + pipelineCoverageTooltipDescription() { + return n__( + 'Coverage value for this pipeline was calculated by the coverage value of %d job.', + 'Coverage value for this pipeline was calculated by averaging the resulting coverage values of %d jobs.', + this.buildsWithCoverage.length, + ); + }, }, errorText: s__( 'Pipeline|Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation%{linkEnd}.', @@ -139,7 +162,7 @@ export default { > <gl-icon name="question" - :small="12" + :size="12" tabindex="0" role="text" :aria-label="__('Link to go to GitLab pipeline documentation')" @@ -189,14 +212,30 @@ export default { </div> <div v-if="pipeline.coverage" class="coverage" data-testid="pipeline-coverage"> {{ s__('Pipeline|Coverage') }} {{ pipeline.coverage }}% - <span v-if="pipelineCoverageDelta" :class="coverageDeltaClass" data-testid="pipeline-coverage-delta" + >({{ pipelineCoverageDelta }}%)</span > - ({{ pipelineCoverageDelta }}%) + + {{ pipelineCoverageJobNumberText }} + <span ref="pipelineCoverageQuestion"> + <gl-icon name="question" :size="12" /> </span> + <gl-tooltip + :target="() => $refs.pipelineCoverageQuestion" + data-testid="pipeline-coverage-tooltip" + > + {{ pipelineCoverageTooltipDescription }} + <div + v-for="(build, index) in buildsWithCoverage" + :key="`${build.name}-${index}`" + class="gl-mt-3 gl-text-left gl-px-4" + > + {{ build.name }} ({{ build.coverage }}%) + </div> + </gl-tooltip> </div> </div> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue index 5c307b5ff0c..55efd7e7d3b 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue @@ -77,6 +77,7 @@ export default { <mr-widget-pipeline :pipeline="pipeline" :pipeline-coverage-delta="mr.pipelineCoverageDelta" + :builds-with-coverage="mr.buildsWithCoverage" :ci-status="mr.ciStatus" :has-ci="mr.hasCI" :pipeline-must-succeed="mr.onlyAllowMergeIfPipelineSucceeds" diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index 8c98ba1b023..5695e02bbab 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -52,6 +52,7 @@ export default class MergeRequestStore { this.divergedCommitsCount = data.diverged_commits_count; this.pipeline = data.pipeline || {}; this.pipelineCoverageDelta = data.pipeline_coverage_delta; + this.buildsWithCoverage = data.builds_with_coverage; this.mergePipeline = data.merge_pipeline || {}; this.deployments = this.deployments || data.deployments || []; this.postMergeDeployments = this.postMergeDeployments || []; diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index aa02bc132f9..e65b27db3ae 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -76,8 +76,12 @@ class InvitesController < ApplicationController notice << "or create an account" if Gitlab::CurrentSettings.allow_signup? notice = notice.join(' ') + "." + initial_member = Member.find_by_invite_token(params[:id]) + redirect_params = initial_member ? { invite_email: member.invite_email } : {} + store_location_for :user, request.fullpath - redirect_to new_user_session_path(invite_email: member.invite_email), notice: notice + + redirect_to new_user_session_path(redirect_params), notice: notice end def invite_details diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index a6986029f0d..78161a3b446 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -177,10 +177,6 @@ module Issuable assignees.count > 1 end - def supports_weight? - false - end - def supports_time_tracking? is_a?(TimeTrackable) && !incident? end diff --git a/app/models/member.rb b/app/models/member.rb index 1913df61614..55bb78b7f94 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -161,8 +161,8 @@ class Member < ApplicationRecord where(user_id: user_ids).has_access.pluck(:user_id, :access_level).to_h end - def find_by_invite_token(invite_token) - invite_token = Devise.token_generator.digest(self, :invite_token, invite_token) + def find_by_invite_token(raw_invite_token) + invite_token = Devise.token_generator.digest(self, :invite_token, raw_invite_token) find_by(invite_token: invite_token) end diff --git a/app/services/snippets/base_service.rb b/app/services/snippets/base_service.rb index d9e8326f159..53a04e5a398 100644 --- a/app/services/snippets/base_service.rb +++ b/app/services/snippets/base_service.rb @@ -46,7 +46,7 @@ module Snippets snippet.errors.add(:snippet_actions, 'have invalid data') end - snippet_error_response(snippet, 403) + snippet_error_response(snippet, 422) end def snippet_error_response(snippet, http_status) diff --git a/app/views/projects/environments/folder.html.haml b/app/views/projects/environments/folder.html.haml index cd24c30e46f..554cb4323f7 100644 --- a/app/views/projects/environments/folder.html.haml +++ b/app/views/projects/environments/folder.html.haml @@ -2,4 +2,4 @@ - breadcrumb_title _("Folder/%{name}") % { name: @folder } - page_title _("Environments in %{name}") % { name: @folder } -#environments-folder-list-view{ data: { environments_data: environments_folder_list_view_data } } +#environments-folder-list-view{ data: { environments_data: environments_folder_list_view_data, project_path: @project.full_path } } diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 445196ed449..9abc1a5a925 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -5,4 +5,5 @@ "can-create-environment" => can?(current_user, :create_environment, @project).to_s, "new-environment-path" => new_project_environment_path(@project), "help-page-path" => help_page_path("ci/environments/index.md"), - "deploy-boards-help-path" => help_page_path("user/project/deploy_boards", anchor: "enabling-deploy-boards") } } + "deploy-boards-help-path" => help_page_path("user/project/deploy_boards", anchor: "enabling-deploy-boards"), + "project-path" => @project.full_path } } diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml index 82e435fe0d3..79e6f043b64 100644 --- a/app/views/shared/issuable/form/_metadata.html.haml +++ b/app/views/shared/issuable/form/_metadata.html.haml @@ -30,7 +30,7 @@ = render_if_exists "shared/issuable/form/merge_request_blocks", issuable: issuable, form: form - - if has_due_date || issuable.supports_weight? + - if has_due_date .col-lg-6 - if @issue[:issue_type] != 'incident' = render_if_exists "shared/issuable/form/weight", issuable: issuable, form: form diff --git a/changelogs/unreleased/15399-show-multiple-jobs-for-coverage.yml b/changelogs/unreleased/15399-show-multiple-jobs-for-coverage.yml new file mode 100644 index 00000000000..3dbbd22e002 --- /dev/null +++ b/changelogs/unreleased/15399-show-multiple-jobs-for-coverage.yml @@ -0,0 +1,5 @@ +--- +title: Show multiple jobs contributing to code coverage +merge_request: 41217 +author: +type: added diff --git a/changelogs/unreleased/220540-drop-ds-sast-dind.yml b/changelogs/unreleased/220540-drop-ds-sast-dind.yml new file mode 100644 index 00000000000..d1c2818aa94 --- /dev/null +++ b/changelogs/unreleased/220540-drop-ds-sast-dind.yml @@ -0,0 +1,5 @@ +--- +title: Drop Docker-in-Docker mode for SAST and Dependency Scanning +merge_request: 41260 +author: +type: removed diff --git a/changelogs/unreleased/241359-nomethoderror-undefined-method-invite_email-for-actionview-outputb.yml b/changelogs/unreleased/241359-nomethoderror-undefined-method-invite_email-for-actionview-outputb.yml new file mode 100644 index 00000000000..36c5c819f00 --- /dev/null +++ b/changelogs/unreleased/241359-nomethoderror-undefined-method-invite_email-for-actionview-outputb.yml @@ -0,0 +1,5 @@ +--- +title: 'Resolve NoMethodError: undefined method invite_email' +merge_request: 41587 +author: +type: fixed diff --git a/changelogs/unreleased/vij-snippet-status-codes.yml b/changelogs/unreleased/vij-snippet-status-codes.yml new file mode 100644 index 00000000000..49ce9a00e33 --- /dev/null +++ b/changelogs/unreleased/vij-snippet-status-codes.yml @@ -0,0 +1,5 @@ +--- +title: Change invalid Snippet params status code from 403 to 422 +merge_request: 40619 +author: +type: changed diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md index 92a4ce860c3..797721aca0d 100644 --- a/doc/install/aws/index.md +++ b/doc/install/aws/index.md @@ -68,28 +68,32 @@ As we'll be using [Amazon S3 object storage](#amazon-s3-object-storage), our EC2 1. Click **Create policy**, select the `JSON` tab, and add a policy. We want to [follow security best practices and grant _least privilege_](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege), giving our role only the permissions needed to perform the required actions. 1. Assuming you prefix the S3 bucket names with `gl-` as shown in the diagram, add the following policy: -```json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "s3:AbortMultipartUpload", - "s3:CompleteMultipartUpload", - "s3:ListBucket", - "s3:PutObject", - "s3:GetObject", - "s3:DeleteObject", - "s3:PutObjectAcl" - ], - "Resource": [ - "arn:aws:s3:::gl-*/*" - ] - } - ] -} -``` + ```json + { "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:DeleteObject", + "s3:PutObjectAcl" + ], + "Resource": "arn:aws:s3:::gl-*/*" + }, + { + "Effect": "Allow", + "Action": [ + "s3:ListBucket", + "s3:AbortMultipartUpload", + "s3:ListMultipartUploadParts", + "s3:ListBucketMultipartUploads" + ], + "Resource": "arn:aws:s3:::gl-*" + } + ] + } + ``` 1. Click **Review policy**, give your policy a name (we'll use `gl-s3-policy`), and click **Create policy**. diff --git a/doc/user/application_security/img/adding_a_dismissal_reason_v13_0.png b/doc/user/application_security/img/adding_a_dismissal_reason_v13_0.png Binary files differdeleted file mode 100644 index cb8911b14b1..00000000000 --- a/doc/user/application_security/img/adding_a_dismissal_reason_v13_0.png +++ /dev/null diff --git a/doc/user/application_security/img/adding_a_dismissal_reason_v13_4.png b/doc/user/application_security/img/adding_a_dismissal_reason_v13_4.png Binary files differnew file mode 100644 index 00000000000..8e7bcf09428 --- /dev/null +++ b/doc/user/application_security/img/adding_a_dismissal_reason_v13_4.png diff --git a/doc/user/application_security/img/create_issue_with_list_hover.png b/doc/user/application_security/img/create_issue_with_list_hover.png Binary files differdeleted file mode 100644 index 4c38862e68f..00000000000 --- a/doc/user/application_security/img/create_issue_with_list_hover.png +++ /dev/null diff --git a/doc/user/application_security/img/create_mr_from_vulnerability_v13_4.png b/doc/user/application_security/img/create_mr_from_vulnerability_v13_4.png Binary files differnew file mode 100644 index 00000000000..a914c2996f7 --- /dev/null +++ b/doc/user/application_security/img/create_mr_from_vulnerability_v13_4.png diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md index 7d0b14a8326..da348cfc9c4 100644 --- a/doc/user/application_security/index.md +++ b/doc/user/application_security/index.md @@ -182,7 +182,7 @@ vulnerability's status to Dismissed, a text box appears for you to add a comment dismissal. Once added, you can edit or delete it. This allows you to add and update context for a vulnerability as you learn more over time. -![Dismissed vulnerability comment](img/adding_a_dismissal_reason_v13_0.png) +![Dismissed vulnerability comment](img/adding_a_dismissal_reason_v13_4.png) #### Dismissing multiple vulnerabilities @@ -197,23 +197,6 @@ Pressing the "Dismiss Selected" button will dismiss all the selected vulnerabili ![Multiple vulnerability dismissal](img/multi_select_v12_9.png) -### Creating an issue for a vulnerability - -You can create an issue for a vulnerability by visiting the vulnerability's page and clicking -**Create issue**, which you can find in the **Related issues** section. - -![Create issue from vulnerability](img/create_issue_from_vulnerability_v13_3.png) - -This creates a [confidential issue](../project/issues/confidential_issues.md) in the project the -vulnerability came from, and pre-populates it with some useful information taken from the vulnerability -report. Once the issue is created, you are redirected to it so you can edit, assign, or comment on -it. - -Upon returning to the group security dashboard, the vulnerability now has an associated issue next -to the name. - -![Linked issue in the group security dashboard](img/issue.png) - ### Solutions for vulnerabilities (auto-remediation) > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/5656) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.7. @@ -248,10 +231,27 @@ vulnerability. Any vulnerability that has a [solution](#solutions-for-vulnerabilities-auto-remediation) can have a merge request created to automatically solve the issue. -If this action is available, the vulnerability modal contains a **Create merge request** button. +If this action is available, the vulnerability page or modal contains a **Create merge request** button. Click this button to create a merge request to apply the solution onto the source branch. -![Create merge request from vulnerability](img/create_issue_with_list_hover.png) +![Create merge request from vulnerability](img/create_mr_from_vulnerability_v13_4.png) + +### Creating an issue for a vulnerability + +You can create an issue for a vulnerability by visiting the vulnerability's page and clicking +**Create issue**, which you can find in the **Related issues** section. + +![Create issue from vulnerability](img/create_issue_from_vulnerability_v13_3.png) + +This creates a [confidential issue](../project/issues/confidential_issues.md) in the project the +vulnerability came from, and pre-populates it with some useful information taken from the vulnerability +report. Once the issue is created, you are redirected to it so you can edit, assign, or comment on +it. + +Upon returning to the group security dashboard, the vulnerability now has an associated issue next +to the name. + +![Linked issue in the group security dashboard](img/issue.png) ### Managing related issues for a vulnerability diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml index 6e6dc023e79..3789f0edc1c 100644 --- a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml @@ -12,81 +12,24 @@ variables: DS_DEFAULT_ANALYZERS: "bundler-audit, retire.js, gemnasium, gemnasium-maven, gemnasium-python" DS_EXCLUDED_PATHS: "spec, test, tests, tmp" DS_MAJOR_VERSION: 2 - DS_DISABLE_DIND: "true" dependency_scanning: stage: test - image: docker:stable - variables: - DOCKER_DRIVER: overlay2 - DOCKER_TLS_CERTDIR: "" - allow_failure: true - services: - - docker:stable-dind script: - - | - if ! docker info &>/dev/null; then - if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then - export DOCKER_HOST='tcp://localhost:2375' - fi - fi - - | # this is required to avoid undesirable reset of Docker image ENV variables being set on build stage - function propagate_env_vars() { - CURRENT_ENV=$(printenv) - - for VAR_NAME; do - echo $CURRENT_ENV | grep "${VAR_NAME}=" > /dev/null && echo "--env $VAR_NAME " - done - } - - | - docker run \ - $(propagate_env_vars \ - DS_ANALYZER_IMAGES \ - SECURE_ANALYZERS_PREFIX \ - DS_ANALYZER_IMAGE_TAG \ - DS_DEFAULT_ANALYZERS \ - DS_EXCLUDED_PATHS \ - DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \ - DS_PULL_ANALYZER_IMAGE_TIMEOUT \ - DS_RUN_ANALYZER_TIMEOUT \ - DS_PYTHON_VERSION \ - DS_PIP_VERSION \ - DS_PIP_DEPENDENCY_PATH \ - DS_JAVA_VERSION \ - GEMNASIUM_DB_LOCAL_PATH \ - GEMNASIUM_DB_REMOTE_URL \ - GEMNASIUM_DB_REF_NAME \ - PIP_INDEX_URL \ - PIP_EXTRA_INDEX_URL \ - PIP_REQUIREMENTS_FILE \ - MAVEN_CLI_OPTS \ - GRADLE_CLI_OPTS \ - SBT_CLI_OPTS \ - BUNDLER_AUDIT_UPDATE_DISABLED \ - BUNDLER_AUDIT_ADVISORY_DB_URL \ - BUNDLER_AUDIT_ADVISORY_DB_REF_NAME \ - RETIREJS_JS_ADVISORY_DB \ - RETIREJS_NODE_ADVISORY_DB \ - DS_REMEDIATE \ - ) \ - --volume "$PWD:/code" \ - --volume /var/run/docker.sock:/var/run/docker.sock \ - "registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$DS_MAJOR_VERSION" /code + - echo "$CI_JOB_NAME is used for configuration only, and its script should not be executed" + - exit 1 artifacts: reports: dependency_scanning: gl-dependency-scanning-report.json dependencies: [] rules: - - if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'true' - when: never - - if: $CI_COMMIT_BRANCH && - $GITLAB_FEATURES =~ /\bdependency_scanning\b/ + - when: never .ds-analyzer: extends: dependency_scanning - services: [] + allow_failure: true rules: - - if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'false' + - if: $DEPENDENCY_SCANNING_DISABLED when: never - if: $CI_COMMIT_BRANCH && $GITLAB_FEATURES =~ /\bdependency_scanning\b/ @@ -100,7 +43,7 @@ gemnasium-dependency_scanning: variables: DS_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/gemnasium:$DS_MAJOR_VERSION" rules: - - if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'false' + - if: $DEPENDENCY_SCANNING_DISABLED when: never - if: $CI_COMMIT_BRANCH && $GITLAB_FEATURES =~ /\bdependency_scanning\b/ && @@ -123,7 +66,7 @@ gemnasium-maven-dependency_scanning: variables: DS_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/gemnasium-maven:$DS_MAJOR_VERSION" rules: - - if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'false' + - if: $DEPENDENCY_SCANNING_DISABLED when: never - if: $CI_COMMIT_BRANCH && $GITLAB_FEATURES =~ /\bdependency_scanning\b/ && @@ -141,7 +84,7 @@ gemnasium-python-dependency_scanning: variables: DS_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/gemnasium-python:$DS_MAJOR_VERSION" rules: - - if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'false' + - if: $DEPENDENCY_SCANNING_DISABLED when: never - if: $CI_COMMIT_BRANCH && $GITLAB_FEATURES =~ /\bdependency_scanning\b/ && @@ -166,7 +109,7 @@ bundler-audit-dependency_scanning: variables: DS_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/bundler-audit:$DS_MAJOR_VERSION" rules: - - if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'false' + - if: $DEPENDENCY_SCANNING_DISABLED when: never - if: $CI_COMMIT_BRANCH && $GITLAB_FEATURES =~ /\bdependency_scanning\b/ && @@ -181,7 +124,7 @@ retire-js-dependency_scanning: variables: DS_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/retire.js:$DS_MAJOR_VERSION" rules: - - if: $DEPENDENCY_SCANNING_DISABLED || $DS_DISABLE_DIND == 'false' + - if: $DEPENDENCY_SCANNING_DISABLED when: never - if: $CI_COMMIT_BRANCH && $GITLAB_FEATURES =~ /\bdependency_scanning\b/ && diff --git a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml index 1fb8b6736aa..77ea11d01d1 100644 --- a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml @@ -12,45 +12,26 @@ variables: SAST_DEFAULT_ANALYZERS: "bandit, brakeman, gosec, spotbugs, flawfinder, phpcs-security-audit, security-code-scan, nodejs-scan, eslint, sobelow, pmd-apex, kubesec" SAST_EXCLUDED_PATHS: "spec, test, tests, tmp" SAST_ANALYZER_IMAGE_TAG: 2 - SAST_DISABLE_DIND: "true" SCAN_KUBERNETES_MANIFESTS: "false" sast: stage: test - allow_failure: true artifacts: reports: sast: gl-sast-report.json rules: - - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'true' - when: never - - if: $CI_COMMIT_BRANCH && $GITLAB_FEATURES =~ /\bsast\b/ - image: docker:stable + - when: never variables: SEARCH_MAX_DEPTH: 4 - DOCKER_DRIVER: overlay2 - DOCKER_TLS_CERTDIR: "" - services: - - docker:stable-dind script: - - | - if ! docker info &>/dev/null; then - if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then - export DOCKER_HOST='tcp://localhost:2375' - fi - fi - - | - docker run \ - $(awk 'BEGIN{for(v in ENVIRON) print v}' | grep -v -E '^(DOCKER_|CI|GITLAB_|FF_|HOME|PWD|OLDPWD|PATH|SHLVL|HOSTNAME)' | awk '{printf " -e %s", $0}') \ - --volume "$PWD:/code" \ - --volume /var/run/docker.sock:/var/run/docker.sock \ - "registry.gitlab.com/gitlab-org/security-products/sast:$SAST_ANALYZER_IMAGE_TAG" /app/bin/run /code + - echo "$CI_JOB_NAME is used for configuration only, and its script should not be executed" + - exit 1 .sast-analyzer: extends: sast - services: [] + allow_failure: true rules: - - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false' + - if: $SAST_DISABLED when: never - if: $CI_COMMIT_BRANCH script: @@ -63,7 +44,7 @@ bandit-sast: variables: SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/bandit:$SAST_ANALYZER_IMAGE_TAG" rules: - - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false' + - if: $SAST_DISABLED when: never - if: $CI_COMMIT_BRANCH && $SAST_DEFAULT_ANALYZERS =~ /bandit/ @@ -77,7 +58,7 @@ brakeman-sast: variables: SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/brakeman:$SAST_ANALYZER_IMAGE_TAG" rules: - - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false' + - if: $SAST_DISABLED when: never - if: $CI_COMMIT_BRANCH && $SAST_DEFAULT_ANALYZERS =~ /brakeman/ @@ -91,7 +72,7 @@ eslint-sast: variables: SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/eslint:$SAST_ANALYZER_IMAGE_TAG" rules: - - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false' + - if: $SAST_DISABLED when: never - if: $CI_COMMIT_BRANCH && $SAST_DEFAULT_ANALYZERS =~ /eslint/ @@ -109,7 +90,7 @@ flawfinder-sast: variables: SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/flawfinder:$SAST_ANALYZER_IMAGE_TAG" rules: - - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false' + - if: $SAST_DISABLED when: never - if: $CI_COMMIT_BRANCH && $SAST_DEFAULT_ANALYZERS =~ /flawfinder/ @@ -124,7 +105,7 @@ kubesec-sast: variables: SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/kubesec:$SAST_ANALYZER_IMAGE_TAG" rules: - - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false' + - if: $SAST_DISABLED when: never - if: $CI_COMMIT_BRANCH && $SAST_DEFAULT_ANALYZERS =~ /kubesec/ && @@ -137,7 +118,7 @@ gosec-sast: variables: SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/gosec:$SAST_ANALYZER_IMAGE_TAG" rules: - - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false' + - if: $SAST_DISABLED when: never - if: $CI_COMMIT_BRANCH && $SAST_DEFAULT_ANALYZERS =~ /gosec/ @@ -151,7 +132,7 @@ nodejs-scan-sast: variables: SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/nodejs-scan:$SAST_ANALYZER_IMAGE_TAG" rules: - - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false' + - if: $SAST_DISABLED when: never - if: $CI_COMMIT_BRANCH && $SAST_DEFAULT_ANALYZERS =~ /nodejs-scan/ @@ -165,7 +146,7 @@ phpcs-security-audit-sast: variables: SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/phpcs-security-audit:$SAST_ANALYZER_IMAGE_TAG" rules: - - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false' + - if: $SAST_DISABLED when: never - if: $CI_COMMIT_BRANCH && $SAST_DEFAULT_ANALYZERS =~ /phpcs-security-audit/ @@ -179,7 +160,7 @@ pmd-apex-sast: variables: SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/pmd-apex:$SAST_ANALYZER_IMAGE_TAG" rules: - - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false' + - if: $SAST_DISABLED when: never - if: $CI_COMMIT_BRANCH && $SAST_DEFAULT_ANALYZERS =~ /pmd-apex/ @@ -193,7 +174,7 @@ security-code-scan-sast: variables: SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/security-code-scan:$SAST_ANALYZER_IMAGE_TAG" rules: - - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false' + - if: $SAST_DISABLED when: never - if: $CI_COMMIT_BRANCH && $SAST_DEFAULT_ANALYZERS =~ /security-code-scan/ @@ -208,7 +189,7 @@ sobelow-sast: variables: SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/sobelow:$SAST_ANALYZER_IMAGE_TAG" rules: - - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false' + - if: $SAST_DISABLED when: never - if: $CI_COMMIT_BRANCH && $SAST_DEFAULT_ANALYZERS =~ /sobelow/ @@ -222,7 +203,7 @@ spotbugs-sast: variables: SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/spotbugs:$SAST_ANALYZER_IMAGE_TAG" rules: - - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false' + - if: $SAST_DISABLED when: never - if: $CI_COMMIT_BRANCH && $SAST_DEFAULT_ANALYZERS =~ /spotbugs/ diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 4cff4073e4d..6910cf8f1a2 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -7102,6 +7102,11 @@ msgstr "" msgid "Coverage Fuzzing" msgstr "" +msgid "Coverage value for this pipeline was calculated by the coverage value of %d job." +msgid_plural "Coverage value for this pipeline was calculated by averaging the resulting coverage values of %d jobs." +msgstr[0] "" +msgstr[1] "" + msgid "Create" msgstr "" @@ -9536,6 +9541,9 @@ msgstr "" msgid "Environments in %{name}" msgstr "" +msgid "EnvironmentsAlert|%{severity} • %{title} %{text}. %{linkStart}View Details%{linkEnd} · %{startedAt} " +msgstr "" + msgid "EnvironmentsDashboard|Add a project to the dashboard" msgstr "" @@ -29512,6 +29520,11 @@ msgstr "" msgid "from" msgstr "" +msgid "from %d job" +msgid_plural "from %d jobs" +msgstr[0] "" +msgstr[1] "" + msgid "group" msgstr "" diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb index a069a5dc050..3605699bb5d 100644 --- a/spec/controllers/invites_controller_spec.rb +++ b/spec/controllers/invites_controller_spec.rb @@ -3,96 +3,122 @@ require 'spec_helper' RSpec.describe InvitesController do - let(:token) { '123456' } let_it_be(:user) { create(:user) } - let(:member) { create(:project_member, :invited, invite_token: token, invite_email: user.email) } + let(:member) { create(:project_member, :invited, invite_email: user.email) } + let(:raw_invite_token) { member.raw_invite_token } let(:project_members) { member.source.users } let(:md5_member_global_id) { Digest::MD5.hexdigest(member.to_global_id.to_s) } + let(:params) { { id: raw_invite_token } } before do stub_application_setting(snowplow_enabled: true, snowplow_collector_hostname: 'localhost') controller.instance_variable_set(:@member, member) - sign_in(user) end describe 'GET #show' do - let(:params) { { id: token } } - subject(:request) { get :show, params: params } - it 'accepts user if invite email matches signed in user' do - expect do - request - end.to change { project_members.include?(user) }.from(false).to(true) - - expect(response).to have_gitlab_http_status(:found) - expect(flash[:notice]).to include 'You have been granted' - end + context 'when logged in' do + before do + sign_in(user) + end - it 'forces re-confirmation if email does not match signed in user' do - member.invite_email = 'bogus@email.com' + it 'accepts user if invite email matches signed in user' do + expect do + request + end.to change { project_members.include?(user) }.from(false).to(true) - expect do - request - end.not_to change { project_members.include?(user) } + expect(response).to have_gitlab_http_status(:found) + expect(flash[:notice]).to include 'You have been granted' + end - expect(response).to have_gitlab_http_status(:ok) - expect(flash[:notice]).to be_nil - end + it 'forces re-confirmation if email does not match signed in user' do + member.invite_email = 'bogus@email.com' - context 'when new_user_invite is not set' do - it 'does not track the user as experiment group' do - expect(Gitlab::Tracking).not_to receive(:event) + expect do + request + end.not_to change { project_members.include?(user) } - request + expect(response).to have_gitlab_http_status(:ok) + expect(flash[:notice]).to be_nil end - end - context 'when new_user_invite is experiment' do - let(:params) { { id: token, new_user_invite: 'experiment' } } + context 'when new_user_invite is not set' do + it 'does not track the user as experiment group' do + expect(Gitlab::Tracking).not_to receive(:event) - it 'tracks the user as experiment group' do - expect(Gitlab::Tracking).to receive(:event).and_call_original.with( - 'Growth::Acquisition::Experiment::InviteEmail', - 'opened', - property: 'experiment_group', - label: md5_member_global_id - ) - expect(Gitlab::Tracking).to receive(:event).and_call_original.with( - 'Growth::Acquisition::Experiment::InviteEmail', - 'accepted', - property: 'experiment_group', - label: md5_member_global_id - ) + request + end + end - request + context 'when new_user_invite is experiment' do + let(:params) { { id: raw_invite_token, new_user_invite: 'experiment' } } + + it 'tracks the user as experiment group' do + expect(Gitlab::Tracking).to receive(:event).and_call_original.with( + 'Growth::Acquisition::Experiment::InviteEmail', + 'opened', + property: 'experiment_group', + label: md5_member_global_id + ) + expect(Gitlab::Tracking).to receive(:event).and_call_original.with( + 'Growth::Acquisition::Experiment::InviteEmail', + 'accepted', + property: 'experiment_group', + label: md5_member_global_id + ) + + request + end + end + + context 'when new_user_invite is control' do + let(:params) { { id: raw_invite_token, new_user_invite: 'control' } } + + it 'tracks the user as control group' do + expect(Gitlab::Tracking).to receive(:event).and_call_original.with( + 'Growth::Acquisition::Experiment::InviteEmail', + 'opened', + property: 'control_group', + label: md5_member_global_id + ) + expect(Gitlab::Tracking).to receive(:event).and_call_original.with( + 'Growth::Acquisition::Experiment::InviteEmail', + 'accepted', + property: 'control_group', + label: md5_member_global_id + ) + + request + end end end - context 'when new_user_invite is control' do - let(:params) { { id: token, new_user_invite: 'control' } } + context 'when not logged in' do + context 'when inviter is a member' do + it 'is redirected to a new session with invite email param' do + request - it 'tracks the user as control group' do - expect(Gitlab::Tracking).to receive(:event).and_call_original.with( - 'Growth::Acquisition::Experiment::InviteEmail', - 'opened', - property: 'control_group', - label: md5_member_global_id - ) - expect(Gitlab::Tracking).to receive(:event).and_call_original.with( - 'Growth::Acquisition::Experiment::InviteEmail', - 'accepted', - property: 'control_group', - label: md5_member_global_id - ) + expect(response).to redirect_to(new_user_session_path(invite_email: member.invite_email)) + end + end - request + context 'when inviter is not a member' do + let(:params) { { id: '_bogus_token_' } } + + it 'is redirected to a new session' do + request + + expect(response).to redirect_to(new_user_session_path) + end end end end describe 'POST #accept' do - let(:params) { { id: token } } + before do + sign_in(user) + end subject(:request) { post :accept, params: params } @@ -105,7 +131,7 @@ RSpec.describe InvitesController do end context 'when new_user_invite is experiment' do - let(:params) { { id: token, new_user_invite: 'experiment' } } + let(:params) { { id: raw_invite_token, new_user_invite: 'experiment' } } it 'tracks the user as experiment group' do expect(Gitlab::Tracking).to receive(:event).and_call_original.with( @@ -120,7 +146,7 @@ RSpec.describe InvitesController do end context 'when new_user_invite is control' do - let(:params) { { id: token, new_user_invite: 'control' } } + let(:params) { { id: raw_invite_token, new_user_invite: 'control' } } it 'tracks the user as control group' do expect(Gitlab::Tracking).to receive(:event).and_call_original.with( diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js index 6486826c3ec..7e7be1eaae9 100644 --- a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js +++ b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js @@ -29,6 +29,8 @@ describe('MRWidgetPipeline', () => { const findAllPipelineStages = () => wrapper.findAll(PipelineStage); const findPipelineCoverage = () => wrapper.find('[data-testid="pipeline-coverage"]'); const findPipelineCoverageDelta = () => wrapper.find('[data-testid="pipeline-coverage-delta"]'); + const findPipelineCoverageTooltipText = () => + wrapper.find('[data-testid="pipeline-coverage-tooltip"]').text(); const findMonitoringPipelineMessage = () => wrapper.find('[data-testid="monitoring-pipeline-message"]'); const findLoadingIcon = () => wrapper.find(GlLoadingIcon); @@ -140,6 +142,7 @@ describe('MRWidgetPipeline', () => { createWrapper( { pipelineCoverageDelta: mockData.pipelineCoverageDelta, + buildsWithCoverage: mockData.buildsWithCoverage, }, mount, ); @@ -178,6 +181,22 @@ describe('MRWidgetPipeline', () => { expect(findPipelineCoverageDelta().exists()).toBe(true); expect(findPipelineCoverageDelta().text()).toBe(`(${mockData.pipelineCoverageDelta}%)`); }); + + it('should render tooltip for jobs contributing to code coverage', () => { + const tooltipText = findPipelineCoverageTooltipText(); + const expectedDescription = `Coverage value for this pipeline was calculated by averaging the resulting coverage values of ${mockData.buildsWithCoverage.length} jobs.`; + + expect(tooltipText).toContain(expectedDescription); + }); + + it.each(mockData.buildsWithCoverage)( + 'should have name and coverage for build %s listed in tooltip', + build => { + const tooltipText = findPipelineCoverageTooltipText(); + + expect(tooltipText).toContain(`${build.name} (${build.coverage}%)`); + }, + ); }); describe('without commit path', () => { diff --git a/spec/frontend/vue_mr_widget/mock_data.js b/spec/frontend/vue_mr_widget/mock_data.js index d64a7f88b6b..4688af30269 100644 --- a/spec/frontend/vue_mr_widget/mock_data.js +++ b/spec/frontend/vue_mr_widget/mock_data.js @@ -193,6 +193,7 @@ export default { updated_at: '2017-04-07T15:28:44.800Z', }, pipelineCoverageDelta: '15.25', + buildsWithCoverage: [{ name: 'karma', coverage: '40.2' }, { name: 'rspec', coverage: '80.4' }], work_in_progress: false, source_branch_exists: false, mergeable_discussions_state: true, diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index b83c769284a..7905e3531bc 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -630,6 +630,14 @@ RSpec.describe Member do end end + describe '.find_by_invite_token' do + let!(:member) { create(:project_member, invite_email: "user@example.com", user: nil) } + + it 'finds the member' do + expect(described_class.find_by_invite_token(member.raw_invite_token)).to eq member + end + end + describe "#invite_to_unknown_user?" do subject { member.invite_to_unknown_user? } diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb index 81dd9022657..02f62b03170 100644 --- a/spec/requests/api/snippets_spec.rb +++ b/spec/requests/api/snippets_spec.rb @@ -417,7 +417,7 @@ RSpec.describe API::Snippets do true | nil | '' | [create_action] | :bad_request true | nil | nil | [bad_file_path] | :bad_request true | nil | nil | [bad_previous_path] | :bad_request - true | nil | nil | [invalid_move] | :forbidden + true | nil | nil | [invalid_move] | :unprocessable_entity false | 'foo.txt' | 'bar' | nil | :success false | 'foo.txt' | nil | nil | :success diff --git a/spec/services/snippets/create_service_spec.rb b/spec/services/snippets/create_service_spec.rb index 2106a9c2045..b7fb5a98d06 100644 --- a/spec/services/snippets/create_service_spec.rb +++ b/spec/services/snippets/create_service_spec.rb @@ -313,6 +313,7 @@ RSpec.describe Snippets::CreateService do it_behaves_like 'creates repository and files' it_behaves_like 'after_save callback to store_mentions', ProjectSnippet it_behaves_like 'when snippet_actions param is present' + it_behaves_like 'invalid params error response' context 'when uploaded files are passed to the service' do let(:extra_opts) { { files: ['foo'] } } @@ -340,6 +341,7 @@ RSpec.describe Snippets::CreateService do it_behaves_like 'creates repository and files' it_behaves_like 'after_save callback to store_mentions', PersonalSnippet it_behaves_like 'when snippet_actions param is present' + it_behaves_like 'invalid params error response' context 'when the snippet description contains files' do include FileMoverHelpers diff --git a/spec/services/snippets/update_service_spec.rb b/spec/services/snippets/update_service_spec.rb index 9d1fe8fe83f..641fc56294a 100644 --- a/spec/services/snippets/update_service_spec.rb +++ b/spec/services/snippets/update_service_spec.rb @@ -698,6 +698,7 @@ RSpec.describe Snippets::UpdateService do it_behaves_like 'when snippet_actions param is present' it_behaves_like 'only file_name is present' it_behaves_like 'only content is present' + it_behaves_like 'invalid params error response' it_behaves_like 'snippets spam check is performed' do before do subject @@ -725,6 +726,7 @@ RSpec.describe Snippets::UpdateService do it_behaves_like 'when snippet_actions param is present' it_behaves_like 'only file_name is present' it_behaves_like 'only content is present' + it_behaves_like 'invalid params error response' it_behaves_like 'snippets spam check is performed' do before do subject diff --git a/spec/support/shared_examples/services/snippets_shared_examples.rb b/spec/support/shared_examples/services/snippets_shared_examples.rb index 51a4a8b1cd9..4a08c0d4365 100644 --- a/spec/support/shared_examples/services/snippets_shared_examples.rb +++ b/spec/support/shared_examples/services/snippets_shared_examples.rb @@ -40,3 +40,20 @@ RSpec.shared_examples 'snippets spam check is performed' do end end end + +shared_examples 'invalid params error response' do + before do + allow_next_instance_of(described_class) do |service| + allow(service).to receive(:valid_params?).and_return false + end + end + + it 'responds to errors appropriately' do + response = subject + + aggregate_failures do + expect(response).to be_error + expect(response.http_status).to eq 422 + end + end +end |