diff options
40 files changed, 457 insertions, 254 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 89a5c6b44c3..bd3e8d510ee 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -9e9717d60e83bc08e6bcb3ca0e636edf1668a9b3 +988e9e17374a0ae05a155ce0b459145c389c1524 diff --git a/app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue b/app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue index 1f8096da94d..a1b264cfe54 100644 --- a/app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue +++ b/app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue @@ -8,7 +8,8 @@ import { GlTable, GlTooltipDirective, } from '@gitlab/ui'; -import { s__ } from '~/locale'; +import { __, s__ } from '~/locale'; +import { thWidthPercent } from '~/lib/utils/table_utility'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; @@ -43,43 +44,70 @@ export default { default: () => [], }, }, + data() { + return { + areValuesHidden: true, + }; + }, fields: [ { key: 'token', label: s__('Pipelines|Token'), + thClass: thWidthPercent(70), }, { key: 'description', label: s__('Pipelines|Description'), + thClass: thWidthPercent(15), }, { key: 'owner', label: s__('Pipelines|Owner'), + thClass: thWidthPercent(5), }, { key: 'lastUsed', label: s__('Pipelines|Last Used'), + thClass: thWidthPercent(5), }, { key: 'actions', label: '', tdClass: 'gl-text-right gl-white-space-nowrap', + thClass: thWidthPercent(5), }, ], + computed: { + valuesButtonText() { + return this.areValuesHidden ? __('Reveal values') : __('Hide values'); + }, + hasTriggers() { + return this.triggers.length; + }, + maskedToken() { + return '*'.repeat(47); + }, + }, + methods: { + toggleHiddenState() { + this.areValuesHidden = !this.areValuesHidden; + }, + }, }; </script> <template> <div> <gl-table - v-if="triggers.length" + v-if="hasTriggers" :fields="$options.fields" :items="triggers" class="triggers-list" responsive > <template #cell(token)="{ item }"> - {{ item.token }} + <span v-if="!areValuesHidden">{{ item.token }}</span> + <span v-else>{{ maskedToken }}</span> <clipboard-button v-if="item.hasTokenExposed" :text="item.token" @@ -157,5 +185,11 @@ export default { > {{ s__('Pipelines|No triggers have been created yet. Add one using the form above.') }} </gl-alert> + <gl-button + v-if="hasTriggers" + data-testid="reveal-hide-values-button" + @click="toggleHiddenState" + >{{ valuesButtonText }}</gl-button + > </div> </template> diff --git a/app/assets/javascripts/merge_requests/components/compare_app.vue b/app/assets/javascripts/merge_requests/components/compare_app.vue index 4973f8596b0..0e60f4bd8f9 100644 --- a/app/assets/javascripts/merge_requests/components/compare_app.vue +++ b/app/assets/javascripts/merge_requests/components/compare_app.vue @@ -21,7 +21,7 @@ export default { default: '', }, currentProject: { - default: '', + default: () => ({}), }, currentBranch: { default: () => ({}), @@ -50,6 +50,9 @@ export default { return [this.currentProject]; }, + showCommitBox() { + return this.commitHtml || this.loading || !this.selectedBranch.value; + }, }, mounted() { this.fetchCommit(); @@ -63,6 +66,8 @@ export default { this.fetchCommit(); }, async fetchCommit() { + if (!this.selectedBranch.value) return; + this.loading = true; const { data } = await axios.get(this.branchCommitPath, { @@ -104,16 +109,22 @@ export default { /> </div> </div> - <div v-if="commitHtml || loading" class="gl-bg-gray-50 gl-rounded-base gl-my-4"> + <div + v-if="showCommitBox" + class="gl-bg-gray-50 gl-rounded-base gl-my-4" + data-testid="commit-box" + > <gl-loading-icon v-if="loading" class="gl-py-3" /> - <div - v-if="!selectedBranch.value" - class="compare-commit-empty gl-display-flex gl-align-items-center gl-p-5" - > - <gl-icon name="branch" class="gl-mr-3" /> - {{ __('Select a branch to compare') }} - </div> - <ul v-safe-html="commitHtml" class="list-unstyled mr_source_commit"></ul> + <template v-else> + <div + v-if="!selectedBranch.value" + class="compare-commit-empty gl-display-flex gl-align-items-center gl-p-5" + > + <gl-icon name="branch" class="gl-mr-3" /> + {{ __('Select a branch to compare') }} + </div> + <ul v-safe-html="commitHtml" class="list-unstyled mr_source_commit"></ul> + </template> </div> </div> </template> diff --git a/app/graphql/types/ci/code_quality_report_summary_type.rb b/app/graphql/types/ci/code_quality_report_summary_type.rb new file mode 100644 index 00000000000..0d560d9e9e8 --- /dev/null +++ b/app/graphql/types/ci/code_quality_report_summary_type.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Types + module Ci + # rubocop: disable Graphql/AuthorizeTypes + # This is presented through `PipelineType` that has its own authorization + class CodeQualityReportSummaryType < BaseObject + graphql_name 'CodeQualityReportSummary' + description 'Code Quality report for a pipeline' + + field :count, GraphQL::Types::Int, null: true, + description: 'Total number of Code Quality reports.' + ::Gitlab::Ci::Reports::CodequalityReports::SEVERITY_PRIORITIES.each_key do |status| + field status, GraphQL::Types::Int, null: true, + description: "Total number of #{status} status." + end + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/models/release.rb b/app/models/release.rb index b770f3934ef..85699d259f5 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -11,7 +11,6 @@ class Release < ApplicationRecord cache_markdown_field :description belongs_to :project, touch: true - # releases prior to 11.7 have no author belongs_to :author, class_name: 'User' has_many :links, class_name: 'Releases::Link' @@ -26,7 +25,7 @@ class Release < ApplicationRecord before_create :set_released_at validates :project, :tag, presence: true - validates :author_id, presence: true, if: :validate_release_with_author? + validates :author_id, presence: true, on: :create, if: :validate_release_with_author? validates :tag, uniqueness: { scope: :project_id } diff --git a/doc/administration/raketasks/project_import_export.md b/doc/administration/raketasks/project_import_export.md index e43fbac25e9..4694af18af2 100644 --- a/doc/administration/raketasks/project_import_export.md +++ b/doc/administration/raketasks/project_import_export.md @@ -42,7 +42,7 @@ bundle exec rake gitlab:import_export:data RAILS_ENV=production Note the following: - Importing is only possible if the version of the import and export GitLab instances are - compatible as described in the [Version history](../../user/project/settings/import_export.md#version-history). + [compatible](../../user/project/settings/import_export.md#compatibility). - The project import option must be enabled: 1. On the top bar, select **Main menu > Admin**. diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index ff668d7d66b..26765eac2a6 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -11617,6 +11617,22 @@ Represents a code quality degradation on the pipeline. | <a id="codequalitydegradationseverity"></a>`severity` | [`CodeQualityDegradationSeverity!`](#codequalitydegradationseverity) | Status of the degradation (BLOCKER, CRITICAL, MAJOR, MINOR, INFO, UNKNOWN). | | <a id="codequalitydegradationweburl"></a>`webUrl` | [`String`](#string) | URL to the file along with line number. | +### `CodeQualityReportSummary` + +Code Quality report for a pipeline. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="codequalityreportsummaryblocker"></a>`blocker` | [`Int`](#int) | Total number of blocker status. | +| <a id="codequalityreportsummarycount"></a>`count` | [`Int`](#int) | Total number of Code Quality reports. | +| <a id="codequalityreportsummarycritical"></a>`critical` | [`Int`](#int) | Total number of critical status. | +| <a id="codequalityreportsummaryinfo"></a>`info` | [`Int`](#int) | Total number of info status. | +| <a id="codequalityreportsummarymajor"></a>`major` | [`Int`](#int) | Total number of major status. | +| <a id="codequalityreportsummaryminor"></a>`minor` | [`Int`](#int) | Total number of minor status. | +| <a id="codequalityreportsummaryunknown"></a>`unknown` | [`Int`](#int) | Total number of unknown status. | + ### `Commit` #### Fields @@ -17104,6 +17120,7 @@ Represents a file or directory in the project repository that has been locked. | <a id="pipelineactive"></a>`active` | [`Boolean!`](#boolean) | Indicates if the pipeline is active. | | <a id="pipelinebeforesha"></a>`beforeSha` | [`String`](#string) | Base SHA of the source branch. | | <a id="pipelinecancelable"></a>`cancelable` | [`Boolean!`](#boolean) | Specifies if a pipeline can be canceled. | +| <a id="pipelinecodequalityreportsummary"></a>`codeQualityReportSummary` | [`CodeQualityReportSummary`](#codequalityreportsummary) | Code Quality report summary for a pipeline. | | <a id="pipelinecodequalityreports"></a>`codeQualityReports` | [`CodeQualityDegradationConnection`](#codequalitydegradationconnection) | Code Quality degradations reported on the pipeline. (see [Connections](#connections)) | | <a id="pipelinecommit"></a>`commit` | [`Commit`](#commit) | Git commit of the pipeline. | | <a id="pipelinecommitpath"></a>`commitPath` | [`String`](#string) | Path to the commit that triggered the pipeline. | diff --git a/doc/development/import_export.md b/doc/development/import_export.md index 9aab7f38dbb..17e733e8904 100644 --- a/doc/development/import_export.md +++ b/doc/development/import_export.md @@ -105,10 +105,9 @@ module Gitlab VERSION = '0.2.4' ``` -## Version history +## Compatibility -Check the [version history](../user/project/settings/import_export.md#version-history) -for compatibility when importing and exporting projects. +Check for [compatibility](../user/project/settings/import_export.md#compatibility) when importing and exporting projects. ### When to bump the version up diff --git a/doc/integration/jenkins.md b/doc/integration/jenkins.md index 53a2fb8bbdd..6950ad38101 100644 --- a/doc/integration/jenkins.md +++ b/doc/integration/jenkins.md @@ -33,31 +33,20 @@ The Jenkins integration requires configuration in both GitLab and Jenkins. ## Grant Jenkins access to the GitLab project -Grant a GitLab user access to the relevant GitLab projects. +To grant Jenkins access to the GitLab project: -1. Create a new GitLab user, or choose an existing GitLab user. +1. Create a personal, project, or group access token. - This account is used by Jenkins to access the GitLab projects. We recommend creating a GitLab - user for only this purpose. If you use a person's account, and their account is deactivated or - deleted, the Jenkins integration stops working. + - [Create a personal access token](../user/profile/personal_access_tokens.md#create-a-personal-access-token) + to use the token for all Jenkins integrations of that user. + - [Create a project access token](../user/project/settings/project_access_tokens.md#create-a-project-access-token) + to use the token at the project level only. For instance, you can revoke + the token in a project without affecting Jenkins integrations in other projects. + - [Create a group access token](../user/group/settings/group_access_tokens.md#create-a-group-access-token-using-ui) + to use the token for all Jenkins integrations in all projects of that group. -1. Grant the user permission to the GitLab projects. - - If you're integrating Jenkins with many GitLab projects, consider granting the - user administrator access. Otherwise, add the user to each project - and grant the Maintainer role. - -## Grant Jenkins access to the GitLab API - -Create a personal access token to authorize Jenkins to access GitLab. - -1. Sign in to GitLab as the user to be used with Jenkins. -1. On the top bar, in the top right corner, select your avatar. -1. Select **Edit profile**. -1. On the left sidebar, select **Access Tokens**. -1. Create a [personal access token](../user/profile/personal_access_tokens.md) and - select the **API** scope. -1. Copy the personal access token. You need it to [configure the Jenkins server](#configure-the-jenkins-server). +1. Set the access token scope to **API**. +1. Copy the access token value to [configure the Jenkins server](#configure-the-jenkins-server). ## Configure the Jenkins server @@ -70,7 +59,7 @@ authorize the connection to GitLab. 1. In the **GitLab** section, select **Enable authentication for '/project' end-point**. 1. Select **Add**, then choose **Jenkins Credential Provider**. 1. Select **GitLab API token** as the token type. -1. Enter the GitLab personal access token's value in **API Token** and select **Add**. +1. In **API Token**, [paste the value you copied from GitLab](#grant-jenkins-access-to-the-gitlab-project) and select **Add**. 1. Enter the GitLab server's URL in **GitLab host URL**. 1. To test the connection, select **Test Connection**. diff --git a/doc/user/group/import/index.md b/doc/user/group/import/index.md index 02d4f098b5d..a56860118a1 100644 --- a/doc/user/group/import/index.md +++ b/doc/user/group/import/index.md @@ -342,6 +342,25 @@ Note the following: exporting a group from the Enterprise Edition to the Community Edition, you may lose this data. For more information, see [downgrading from EE to CE](../../../index.md). +### Compatibility + +Group file exports are in NDJSON format. GitLab previously produced group file exports in JSON format, however: + +- From GitLab 15.8, GitLab no longer supports importing a JSON-formatted group file export. +- Between GitLab 14.0 and GitLab 14.7, GitLab no longer produces group file exports in JSON format but, to support + transitions, can still import JSON-formatted group file exports. + +From GitLab 13.0, GitLab can import group file exports that were exported from a version of GitLab up to two +[minor](../../../policy/maintenance.md#versioning) versions behind, which is similar to our process for +[security releases](../../../policy/maintenance.md#security-releases). + +For example: + +| Destination version | Compatible source versions | +|:--------------------|:---------------------------| +| 13.0 | 13.0, 12.10, 12.9 | +| 13.1 | 13.1, 13.0, 12.10 | + ### Exported contents The [`import_export.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/import_export/group/import_export.yml) @@ -440,27 +459,6 @@ To help avoid abuse, by default, users are rate limited to: | Download export | 1 download per group per minute | | Import | 6 groups per minute | -### Version history - -#### 14.0+ - -In GitLab 14.0, the JSON format is no longer supported for project and group exports. To allow for a -transitional period, you can still import any JSON exports. The new format for imports and exports -is NDJSON. - -#### 13.0+ - -GitLab can import bundles that were exported from a different GitLab deployment. -This ability is limited to two previous GitLab [minor](../../../policy/maintenance.md#versioning) -releases, which is similar to our process for [Security Releases](../../../policy/maintenance.md#security-releases). - -For example: - -| Current version | Can import bundles exported from | -|-----------------|----------------------------------| -| 13.0 | 13.0, 12.10, 12.9 | -| 13.1 | 13.1, 13.0, 12.10 | - ## Automate group and project import **(PREMIUM)** For information on automating user, group, and project import API calls, see diff --git a/doc/user/profile/account/delete_account.md b/doc/user/profile/account/delete_account.md index 18b4e53fb31..5e2908a26e1 100644 --- a/doc/user/profile/account/delete_account.md +++ b/doc/user/profile/account/delete_account.md @@ -56,7 +56,6 @@ When deleting users, you can either: - Issues. - Merge requests. - Notes and comments. - - Releases. - Personal access tokens. - Snippets. diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md index 14068a1093a..2b5f5dfa21d 100644 --- a/doc/user/project/settings/import_export.md +++ b/doc/user/project/settings/import_export.md @@ -27,6 +27,30 @@ To preserve contribution history, [migrate using direct transfer](../../group/im If you migrate from GitLab.com to self-managed GitLab, an administrator can create users on the self-managed GitLab instance. +## Compatibility + +FLAG: +On self-managed GitLab by default project, file exports are in NDJSON format. To make GitLab produce project file +exports in JSON format, ask an administrator to [disable the feature flags](../../../administration/feature_flags.md) +named `project_export_as_ndjson`. To allow GitLab to import project file exports in JSON format, ask an administrator to +[disable the feature flags](../../../administration/feature_flags.md) named `project_import_ndjson`. On GitLab.com, +project file exports are in NDJSON format only. + +Project file exports are in NDJSON format. Before version 14.0, GitLab produced project file exports in JSON format. +To support transitions, you can still import JSON-formatted project file exports if you configure the relevant feature +flags. + +From GitLab 13.0, GitLab can import project file exports that were exported from a version of GitLab up to two +[minor](../../../policy/maintenance.md#versioning) versions behind, which is similar to our process for +[security releases](../../../policy/maintenance.md#security-releases). + +For example: + +| Destination version | Compatible source versions | +|:--------------------|:---------------------------| +| 13.0 | 13.0, 12.10, 12.9 | +| 13.1 | 13.1, 13.0, 12.10 | + ## Configure file exports as an import source **(FREE SELF)** Before you can migrate projects on a self-managed GitLab instance using file exports, GitLab administrators must: @@ -47,7 +71,7 @@ To enable file exports as an import source for the destination instance: ## Between CE and EE You can export projects from the [Community Edition to the Enterprise Edition](https://about.gitlab.com/install/ce-or-ee/) -and vice versa. This assumes [version history](#version-history) requirements are met. +and vice versa, assuming [compatibility](#compatibility) is met. If you're exporting a project from the Enterprise Edition to the Community Edition, you may lose data that is retained only in the Enterprise Edition. For more information, see @@ -146,7 +170,7 @@ Prerequisites: - You must have [exported the project and its data](#export-a-project-and-its-data). - Compare GitLab versions and ensure you are importing to a GitLab version that is the same or later than the GitLab version you exported to. -- Review the [Version history](#version-history) for compatibility issues. +- Review [compatibility](#compatibility) for any issues. - At least the Maintainer role on the destination group to migrate to. Using the Developer role for this purpose was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/387891) in GitLab 15.8 and will be removed in GitLab 16.0. @@ -221,32 +245,6 @@ To help avoid abuse, by default, users are rate limited to: | Download export | 1 download per group per minute | | Import | 6 projects per minute | -## Version history - -### 15.8+ - -Starting with GitLab 15.8, importing groups from a JSON export is no longer supported. Groups must be imported -in NDJSON format. - -### 14.0+ - -In GitLab 14.0, the JSON format is no longer supported for project and group exports. To allow for a -transitional period, you can still import any JSON exports. The new format for imports and exports -is NDJSON. - -### 13.0+ - -Starting with GitLab 13.0, GitLab can import bundles that were exported from a different GitLab deployment. -**This ability is limited to two previous GitLab [minor](../../../policy/maintenance.md#versioning) -releases**, which is similar to our process for [Security Releases](../../../policy/maintenance.md#security-releases). - -For example: - -| Current version | Can import bundles exported from | -|-----------------|----------------------------------| -| 13.0 | 13.0, 12.10, 12.9 | -| 13.1 | 13.1, 13.0, 12.10 | - ## Related topics - [Project import and export API](../../../api/project_import_export.md) diff --git a/lib/gitlab/ci/reports/codequality_reports.rb b/lib/gitlab/ci/reports/codequality_reports.rb index 3196bf3fc6d..aba2d2e8b19 100644 --- a/lib/gitlab/ci/reports/codequality_reports.rb +++ b/lib/gitlab/ci/reports/codequality_reports.rb @@ -42,6 +42,18 @@ module Gitlab rescue StandardError => _ false end + + def code_quality_report_summary + report_degradations = @degradations.presence + return if report_degradations.nil? + + summary = ::Gitlab::Ci::Reports::CodequalityReports::SEVERITY_PRIORITIES.keys.index_with(0) + report_degradations.each_value do |degradation| + summary[degradation[:severity]] += 1 + end + summary['count'] = summary.values.sum + summary + end end end end diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb index 23a13994fa4..903211ec250 100644 --- a/spec/features/triggers_spec.rb +++ b/spec/features/triggers_spec.rb @@ -113,11 +113,24 @@ RSpec.describe 'Triggers', :js, feature_category: :continuous_integration do end end + it 'hides the token value and reveals when clicking the "reveal values" button', :aggregate_failures do + create(:ci_trigger, owner: user, project: @project, description: trigger_title) + visit project_settings_ci_cd_path(@project) + + expect(page.find('.triggers-list')).to have_content('*' * 47) + + page.find('[data-testid="reveal-hide-values-button"]').click + + expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token) + end + it 'do not show "Edit" or full token for not owned trigger' do # Create trigger with user different from current_user create(:ci_trigger, owner: user2, project: @project, description: trigger_title) visit project_settings_ci_cd_path(@project) + page.find('[data-testid="reveal-hide-values-button"]').click + aggregate_failures 'shows truncated token, no clipboard button and no edit link' do expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3]) expect(page.find('.triggers-list')).not_to have_selector('[data-testid="clipboard-btn"]') @@ -130,6 +143,8 @@ RSpec.describe 'Triggers', :js, feature_category: :continuous_integration do create(:ci_trigger, owner: user, project: @project, description: trigger_title) visit project_settings_ci_cd_path(@project) + page.find('[data-testid="reveal-hide-values-button"]').click + aggregate_failures 'shows full token, clipboard button and edit link' do expect(page.find('.triggers-list')).to have_content @project.triggers.first.token expect(page.find('.triggers-list')).to have_selector('[data-testid="clipboard-btn"]') diff --git a/spec/frontend/__helpers__/init_vue_mr_page_helper.js b/spec/frontend/__helpers__/init_vue_mr_page_helper.js index 83ed0a869dc..d01affdaeac 100644 --- a/spec/frontend/__helpers__/init_vue_mr_page_helper.js +++ b/spec/frontend/__helpers__/init_vue_mr_page_helper.js @@ -1,5 +1,6 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import initMRPage from '~/mr_notes'; import { getDiffFileMock } from '../diffs/mock_data/diff_file'; import { userDataMock, notesDataMock, noteableDataMock } from '../notes/mock_data'; @@ -37,7 +38,7 @@ export default function initVueMRPage() { mrTestEl.appendChild(diffsAppEl); const mock = new MockAdapter(axios); - mock.onGet(diffsAppEndpoint).reply(200, { + mock.onGet(diffsAppEndpoint).reply(HTTP_STATUS_OK, { branch_name: 'foo', diff_files: [getDiffFileMock()], }); diff --git a/spec/frontend/blob/notebook/notebook_viever_spec.js b/spec/frontend/blob/notebook/notebook_viever_spec.js index e6480c8d874..2e7eadc912d 100644 --- a/spec/frontend/blob/notebook/notebook_viever_spec.js +++ b/spec/frontend/blob/notebook/notebook_viever_spec.js @@ -4,7 +4,7 @@ import MockAdapter from 'axios-mock-adapter'; import waitForPromises from 'helpers/wait_for_promises'; import component from '~/blob/notebook/notebook_viewer.vue'; import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import NotebookLab from '~/notebook/index.vue'; describe('iPython notebook renderer', () => { @@ -55,7 +55,7 @@ describe('iPython notebook renderer', () => { describe('successful response', () => { beforeEach(() => { - mock.onGet(endpoint).reply(200, mockNotebook); + mock.onGet(endpoint).reply(HTTP_STATUS_OK, mockNotebook); mountComponent(); return waitForPromises(); }); @@ -73,7 +73,7 @@ describe('iPython notebook renderer', () => { beforeEach(() => { mock.onGet(endpoint).reply(() => // eslint-disable-next-line prefer-promise-reject-errors - Promise.reject({ status: 200 }), + Promise.reject({ status: HTTP_STATUS_OK }), ); mountComponent(); diff --git a/spec/frontend/branches/divergence_graph_spec.js b/spec/frontend/branches/divergence_graph_spec.js index 7c367f83add..c3a0f6436c5 100644 --- a/spec/frontend/branches/divergence_graph_spec.js +++ b/spec/frontend/branches/divergence_graph_spec.js @@ -1,6 +1,7 @@ import MockAdapter from 'axios-mock-adapter'; import init from '~/branches/divergence_graph'; import axios from '~/lib/utils/axios_utils'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; describe('Divergence graph', () => { let mock; @@ -8,7 +9,7 @@ describe('Divergence graph', () => { beforeEach(() => { mock = new MockAdapter(axios); - mock.onGet('/-/diverging_counts').reply(200, { + mock.onGet('/-/diverging_counts').reply(HTTP_STATUS_OK, { main: { ahead: 1, behind: 1 }, 'test/hello-world': { ahead: 1, behind: 1 }, }); diff --git a/spec/frontend/ci_settings_pipeline_triggers/components/triggers_list_spec.js b/spec/frontend/ci_settings_pipeline_triggers/components/triggers_list_spec.js index 01eb08f4ece..f1df4208fa2 100644 --- a/spec/frontend/ci_settings_pipeline_triggers/components/triggers_list_spec.js +++ b/spec/frontend/ci_settings_pipeline_triggers/components/triggers_list_spec.js @@ -1,6 +1,6 @@ import { GlTable, GlBadge } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; import { nextTick } from 'vue'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; import TriggersList from '~/ci_settings_pipeline_triggers/components/triggers_list.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; @@ -11,7 +11,7 @@ describe('TriggersList', () => { let wrapper; const createComponent = (props = {}) => { - wrapper = mount(TriggersList, { + wrapper = mountExtended(TriggersList, { propsData: { triggers, ...props }, }); }; @@ -25,59 +25,75 @@ describe('TriggersList', () => { const findInvalidBadge = (i) => findCell(i, 0).findComponent(GlBadge); const findEditBtn = (i) => findRowAt(i).find('[data-testid="edit-btn"]'); const findRevokeBtn = (i) => findRowAt(i).find('[data-testid="trigger_revoke_button"]'); + const findRevealHideButton = () => wrapper.findByTestId('reveal-hide-values-button'); - beforeEach(async () => { - createComponent(); + describe('With triggers set', () => { + beforeEach(async () => { + createComponent(); - await nextTick(); - }); + await nextTick(); + }); - it('displays a table with expected headers', () => { - const headers = ['Token', 'Description', 'Owner', 'Last Used', '']; - headers.forEach((header, i) => { - expect(findHeaderAt(i).text()).toBe(header); + it('displays a table with expected headers', () => { + const headers = ['Token', 'Description', 'Owner', 'Last Used', '']; + headers.forEach((header, i) => { + expect(findHeaderAt(i).text()).toBe(header); + }); }); - }); - it('displays a table with rows', () => { - expect(findRows()).toHaveLength(triggers.length); + it('displays a "Reveal/Hide values" button', async () => { + const revealHideButton = findRevealHideButton(); - const [trigger] = triggers; + expect(revealHideButton.exists()).toBe(true); + expect(revealHideButton.text()).toBe('Reveal values'); - expect(findCell(0, 0).text()).toBe(trigger.token); - expect(findCell(0, 1).text()).toBe(trigger.description); - expect(findCell(0, 2).text()).toContain(trigger.owner.name); - }); + await revealHideButton.vm.$emit('click'); - it('displays a "copy to cliboard" button for exposed tokens', () => { - expect(findClipboardBtn(0).exists()).toBe(true); - expect(findClipboardBtn(0).props('text')).toBe(triggers[0].token); + expect(revealHideButton.text()).toBe('Hide values'); + }); - expect(findClipboardBtn(1).exists()).toBe(false); - }); + it('displays a table with rows', async () => { + await findRevealHideButton().vm.$emit('click'); - it('displays an "invalid" label for tokens without access', () => { - expect(findInvalidBadge(0).exists()).toBe(false); + expect(findRows()).toHaveLength(triggers.length); - expect(findInvalidBadge(1).exists()).toBe(true); - }); + const [trigger] = triggers; - it('displays a time ago label when last used', () => { - expect(findCell(0, 3).text()).toBe('Never'); + expect(findCell(0, 0).text()).toBe(trigger.token); + expect(findCell(0, 1).text()).toBe(trigger.description); + expect(findCell(0, 2).text()).toContain(trigger.owner.name); + }); - expect(findCell(1, 3).findComponent(TimeAgoTooltip).props('time')).toBe(triggers[1].lastUsed); - }); + it('displays a "copy to cliboard" button for exposed tokens', () => { + expect(findClipboardBtn(0).exists()).toBe(true); + expect(findClipboardBtn(0).props('text')).toBe(triggers[0].token); - it('displays actions in a rows', () => { - const [data] = triggers; - const confirmWarning = - 'By revoking a trigger you will break any processes making use of it. Are you sure?'; + expect(findClipboardBtn(1).exists()).toBe(false); + }); + + it('displays an "invalid" label for tokens without access', () => { + expect(findInvalidBadge(0).exists()).toBe(false); - expect(findEditBtn(0).attributes('href')).toBe(data.editProjectTriggerPath); + expect(findInvalidBadge(1).exists()).toBe(true); + }); - expect(findRevokeBtn(0).attributes('href')).toBe(data.projectTriggerPath); - expect(findRevokeBtn(0).attributes('data-method')).toBe('delete'); - expect(findRevokeBtn(0).attributes('data-confirm')).toBe(confirmWarning); + it('displays a time ago label when last used', () => { + expect(findCell(0, 3).text()).toBe('Never'); + + expect(findCell(1, 3).findComponent(TimeAgoTooltip).props('time')).toBe(triggers[1].lastUsed); + }); + + it('displays actions in a rows', () => { + const [data] = triggers; + const confirmWarning = + 'By revoking a trigger you will break any processes making use of it. Are you sure?'; + + expect(findEditBtn(0).attributes('href')).toBe(data.editProjectTriggerPath); + + expect(findRevokeBtn(0).attributes('href')).toBe(data.projectTriggerPath); + expect(findRevokeBtn(0).attributes('data-method')).toBe('delete'); + expect(findRevokeBtn(0).attributes('data-confirm')).toBe(confirmWarning); + }); }); describe('when there are no triggers set', () => { diff --git a/spec/frontend/commits_spec.js b/spec/frontend/commits_spec.js index db1516ed4ec..c79170aa37e 100644 --- a/spec/frontend/commits_spec.js +++ b/spec/frontend/commits_spec.js @@ -4,6 +4,7 @@ import 'vendor/jquery.endless-scroll'; import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import CommitsList from '~/commits'; import axios from '~/lib/utils/axios_utils'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import Pager from '~/pager'; describe('Commits List', () => { @@ -64,7 +65,7 @@ describe('Commits List', () => { jest.spyOn(window.history, 'replaceState').mockImplementation(() => {}); mock = new MockAdapter(axios); - mock.onGet('/h5bp/html5-boilerplate/commits/main').reply(200, { + mock.onGet('/h5bp/html5-boilerplate/commits/main').reply(HTTP_STATUS_OK, { html: '<li>Result</li>', }); diff --git a/spec/frontend/merge_request_spec.js b/spec/frontend/merge_request_spec.js index 7333ce345eb..579cee8c022 100644 --- a/spec/frontend/merge_request_spec.js +++ b/spec/frontend/merge_request_spec.js @@ -5,7 +5,7 @@ import { TEST_HOST } from 'spec/test_constants'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_CONFLICT } from '~/lib/utils/http_status'; +import { HTTP_STATUS_CONFLICT, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import MergeRequest from '~/merge_request'; jest.mock('~/flash'); @@ -23,7 +23,7 @@ describe('MergeRequest', () => { mock .onPatch(`${TEST_HOST}/frontend-fixtures/merge-requests-project/-/merge_requests/1.json`) - .reply(200, {}); + .reply(HTTP_STATUS_OK, {}); test.merge = new MergeRequest(); return test.merge; diff --git a/spec/frontend/merge_requests/components/compare_app_spec.js b/spec/frontend/merge_requests/components/compare_app_spec.js new file mode 100644 index 00000000000..8f84341b653 --- /dev/null +++ b/spec/frontend/merge_requests/components/compare_app_spec.js @@ -0,0 +1,50 @@ +import { shallowMount } from '@vue/test-utils'; +import CompareApp from '~/merge_requests/components/compare_app.vue'; + +let wrapper; + +function factory(provideData = {}) { + wrapper = shallowMount(CompareApp, { + provide: { + inputs: { + project: { + id: 'project', + name: 'project', + }, + branch: { + id: 'branch', + name: 'branch', + }, + }, + toggleClass: { + project: 'project', + branch: 'branch', + }, + i18n: { + projectHeaderText: 'Project', + branchHeaderText: 'Branch', + }, + ...provideData, + }, + }); +} + +describe('Merge requests compare app component', () => { + afterEach(() => { + wrapper.destroy(); + }); + + it('shows commit box when selected branch is empty', () => { + factory({ + currentBranch: { + text: '', + value: '', + }, + }); + + const commitBox = wrapper.find('[data-testid="commit-box"]'); + + expect(commitBox.exists()).toBe(true); + expect(commitBox.text()).toBe('Select a branch to compare'); + }); +}); diff --git a/spec/frontend/pager_spec.js b/spec/frontend/pager_spec.js index dfb3e87a342..2904ef547fe 100644 --- a/spec/frontend/pager_spec.js +++ b/spec/frontend/pager_spec.js @@ -4,6 +4,7 @@ import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import { TEST_HOST } from 'helpers/test_constants'; import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { removeParams } from '~/lib/utils/url_utility'; import Pager from '~/pager'; @@ -49,7 +50,7 @@ describe('pager', () => { const urlRegex = /(.*)some_list(.*)$/; function mockSuccess(count = 0) { - axiosMock.onGet(urlRegex).reply(200, { + axiosMock.onGet(urlRegex).reply(HTTP_STATUS_OK, { count, html: '', }); diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js index 17ff0969b30..518539d97ba 100644 --- a/spec/frontend/pipelines/pipelines_spec.js +++ b/spec/frontend/pipelines/pipelines_spec.js @@ -13,7 +13,7 @@ import waitForPromises from 'helpers/wait_for_promises'; import Api from '~/api'; import { createAlert, VARIANT_WARNING } from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue'; import PipelinesComponent from '~/pipelines/components/pipelines_list/pipelines.vue'; import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue'; @@ -142,7 +142,7 @@ describe('Pipelines', () => { beforeEach(() => { mock .onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '1' } }) - .reply(200, mockPipelinesResponse); + .reply(HTTP_STATUS_OK, mockPipelinesResponse); }); describe('when user has no permissions', () => { @@ -234,7 +234,7 @@ describe('Pipelines', () => { beforeEach(async () => { mock .onGet(mockPipelinesEndpoint, { params: { scope: 'finished', page: '1' } }) - .reply(200, { + .reply(HTTP_STATUS_OK, { pipelines: [mockFinishedPipeline], count: mockPipelinesResponse.count, }); @@ -278,7 +278,7 @@ describe('Pipelines', () => { beforeEach(async () => { mock .onGet(mockPipelinesEndpoint, { params: { scope: 'branches', page: '1' } }) - .reply(200, { + .reply(HTTP_STATUS_OK, { pipelines: [], count: mockPipelinesResponse.count, }); @@ -321,7 +321,7 @@ describe('Pipelines', () => { .onGet(mockPipelinesEndpoint, { params: expectedParams, }) - .replyOnce(200, { + .replyOnce(HTTP_STATUS_OK, { pipelines: [mockFilteredPipeline], count: mockPipelinesResponse.count, }); @@ -399,7 +399,7 @@ describe('Pipelines', () => { beforeEach(async () => { mock.onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '1' } }).reply( - 200, + HTTP_STATUS_OK, { pipelines: firstPage, count: mockPipelinesResponse.count, @@ -407,7 +407,7 @@ describe('Pipelines', () => { mockPageHeaders({ page: 1 }), ); mock.onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '2' } }).reply( - 200, + HTTP_STATUS_OK, { pipelines: secondPage, count: mockPipelinesResponse.count, @@ -462,13 +462,13 @@ describe('Pipelines', () => { // Mock no pipelines in the first attempt mock .onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '1' } }) - .replyOnce(200, emptyResponse, { + .replyOnce(HTTP_STATUS_OK, emptyResponse, { 'POLL-INTERVAL': 100, }); // Mock pipelines in the next attempt mock .onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '1' } }) - .reply(200, mockPipelinesResponse, { + .reply(HTTP_STATUS_OK, mockPipelinesResponse, { 'POLL-INTERVAL': 100, }); }); @@ -509,10 +509,12 @@ describe('Pipelines', () => { describe('when no pipelines exist', () => { beforeEach(() => { - mock.onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '1' } }).reply(200, { - pipelines: [], - count: { all: '0' }, - }); + mock + .onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '1' } }) + .reply(HTTP_STATUS_OK, { + pipelines: [], + count: { all: '0' }, + }); }); describe('when CI is enabled and user has permissions', () => { @@ -551,10 +553,12 @@ describe('Pipelines', () => { }); it('renders tab empty state finished scope', async () => { - mock.onGet(mockPipelinesEndpoint, { params: { scope: 'finished', page: '1' } }).reply(200, { - pipelines: [], - count: { all: '0' }, - }); + mock + .onGet(mockPipelinesEndpoint, { params: { scope: 'finished', page: '1' } }) + .reply(HTTP_STATUS_OK, { + pipelines: [], + count: { all: '0' }, + }); findNavigationTabs().vm.$emit('onChangeTab', 'finished'); @@ -644,7 +648,7 @@ describe('Pipelines', () => { beforeEach(() => { mock.onGet(mockPipelinesEndpoint, { scope: 'all', page: '1' }).reply( - 200, + HTTP_STATUS_OK, { pipelines: [mockPipelineWithStages], count: { all: '1' }, @@ -654,7 +658,9 @@ describe('Pipelines', () => { }, ); - mock.onGet(mockPipelineWithStages.details.stages[0].dropdown_path).reply(200, stageReply); + mock + .onGet(mockPipelineWithStages.details.stages[0].dropdown_path) + .reply(HTTP_STATUS_OK, stageReply); createComponent(); @@ -665,7 +671,7 @@ describe('Pipelines', () => { describe('when a request is being made', () => { beforeEach(async () => { - mock.onGet(mockPipelinesEndpoint).reply(200, mockPipelinesResponse); + mock.onGet(mockPipelinesEndpoint).reply(HTTP_STATUS_OK, mockPipelinesResponse); await waitForPromises(); }); diff --git a/spec/frontend/pipelines/test_reports/stores/actions_spec.js b/spec/frontend/pipelines/test_reports/stores/actions_spec.js index 6e61ef97257..f6287107ed0 100644 --- a/spec/frontend/pipelines/test_reports/stores/actions_spec.js +++ b/spec/frontend/pipelines/test_reports/stores/actions_spec.js @@ -4,6 +4,7 @@ import { TEST_HOST } from 'helpers/test_constants'; import testAction from 'helpers/vuex_action_helper'; import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import * as actions from '~/pipelines/stores/test_reports/actions'; import * as types from '~/pipelines/stores/test_reports/mutation_types'; @@ -35,7 +36,7 @@ describe('Actions TestReports Store', () => { describe('fetch report summary', () => { beforeEach(() => { - mock.onGet(summaryEndpoint).replyOnce(200, summary, {}); + mock.onGet(summaryEndpoint).replyOnce(HTTP_STATUS_OK, summary, {}); }); it('sets testReports and shows tests', () => { @@ -66,7 +67,7 @@ describe('Actions TestReports Store', () => { testReports.test_suites[0].build_ids = buildIds; mock .onGet(suiteEndpoint, { params: { build_ids: buildIds } }) - .replyOnce(200, testReports.test_suites[0], {}); + .replyOnce(HTTP_STATUS_OK, testReports.test_suites[0], {}); }); it('sets test suite and shows tests', () => { diff --git a/spec/frontend/projects/commits/store/actions_spec.js b/spec/frontend/projects/commits/store/actions_spec.js index 64eba056c6f..bae9c48fc1e 100644 --- a/spec/frontend/projects/commits/store/actions_spec.js +++ b/spec/frontend/projects/commits/store/actions_spec.js @@ -2,7 +2,7 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; import { createAlert } from '~/flash'; -import { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import actions from '~/projects/commits/store/actions'; import * as types from '~/projects/commits/store/mutation_types'; import createState from '~/projects/commits/store/state'; @@ -52,7 +52,7 @@ describe('Project commits actions', () => { state.projectId = '8'; const data = [{ id: 1 }]; - mock.onGet(path).replyOnce(200, data); + mock.onGet(path).replyOnce(HTTP_STATUS_OK, data); testAction( actions.fetchAuthors, null, diff --git a/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js b/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js index 6152965398d..53763bd7d8f 100644 --- a/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js +++ b/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js @@ -4,7 +4,7 @@ import AxiosMockAdapter from 'axios-mock-adapter'; import { nextTick } from 'vue'; import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status'; +import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import RevisionDropdown from '~/projects/compare/components/revision_dropdown_legacy.vue'; const defaultProps = { @@ -51,7 +51,7 @@ describe('RevisionDropdown component', () => { const Branches = ['branch-1', 'branch-2']; const Tags = ['tag-1', 'tag-2', 'tag-3']; - axiosMock.onGet(defaultProps.refsProjectPath).replyOnce(200, { + axiosMock.onGet(defaultProps.refsProjectPath).replyOnce(HTTP_STATUS_OK, { Branches, Tags, }); @@ -65,7 +65,7 @@ describe('RevisionDropdown component', () => { }); it('sets branches and tags to be an empty array when no tags or branches are given', async () => { - axiosMock.onGet(defaultProps.refsProjectPath).replyOnce(200, { + axiosMock.onGet(defaultProps.refsProjectPath).replyOnce(HTTP_STATUS_OK, { Branches: undefined, Tags: undefined, }); diff --git a/spec/frontend/projects/compare/components/revision_dropdown_spec.js b/spec/frontend/projects/compare/components/revision_dropdown_spec.js index d1f8fe96b86..db4a1158996 100644 --- a/spec/frontend/projects/compare/components/revision_dropdown_spec.js +++ b/spec/frontend/projects/compare/components/revision_dropdown_spec.js @@ -4,7 +4,7 @@ import AxiosMockAdapter from 'axios-mock-adapter'; import { nextTick } from 'vue'; import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status'; +import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import RevisionDropdown from '~/projects/compare/components/revision_dropdown.vue'; import { revisionDropdownDefaultProps as defaultProps } from './mock_data'; @@ -50,7 +50,7 @@ describe('RevisionDropdown component', () => { const Branches = ['branch-1', 'branch-2']; const Tags = ['tag-1', 'tag-2', 'tag-3']; - axiosMock.onGet(defaultProps.refsProjectPath).replyOnce(200, { + axiosMock.onGet(defaultProps.refsProjectPath).replyOnce(HTTP_STATUS_OK, { Branches, Tags, }); diff --git a/spec/frontend/protected_branches/protected_branch_edit_spec.js b/spec/frontend/protected_branches/protected_branch_edit_spec.js index 8e1a09500c7..b4029d94980 100644 --- a/spec/frontend/protected_branches/protected_branch_edit_spec.js +++ b/spec/frontend/protected_branches/protected_branch_edit_spec.js @@ -4,7 +4,7 @@ import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import { TEST_HOST } from 'helpers/test_constants'; import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import ProtectedBranchEdit from '~/protected_branches/protected_branch_edit'; jest.mock('~/flash'); @@ -116,7 +116,9 @@ describe('ProtectedBranchEdit', () => { describe('when clicked', () => { beforeEach(async () => { - mock.onPatch(TEST_URL, { protected_branch: { [patchParam]: true } }).replyOnce(200, {}); + mock + .onPatch(TEST_URL, { protected_branch: { [patchParam]: true } }) + .replyOnce(HTTP_STATUS_OK, {}); }); it('checks and disables button', async () => { diff --git a/spec/frontend/search_autocomplete_spec.js b/spec/frontend/search_autocomplete_spec.js index 266f047e9dc..a3098fb81ea 100644 --- a/spec/frontend/search_autocomplete_spec.js +++ b/spec/frontend/search_autocomplete_spec.js @@ -5,6 +5,7 @@ import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import axios from '~/lib/utils/axios_utils'; import initSearchAutocomplete from '~/search_autocomplete'; import '~/lib/utils/common_utils'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; describe('Search autocomplete dropdown', () => { let widget = null; @@ -191,7 +192,7 @@ describe('Search autocomplete dropdown', () => { const axiosMock = new AxiosMockAdapter(axios); const autocompleteUrl = new RegExp(autocompletePath); - axiosMock.onGet(autocompleteUrl).reply(200, [ + axiosMock.onGet(autocompleteUrl).reply(HTTP_STATUS_OK, [ { category: 'Projects', id: 1, diff --git a/spec/frontend/sidebar/components/labels/labels_select_vue/store/actions_spec.js b/spec/frontend/sidebar/components/labels/labels_select_vue/store/actions_spec.js index 56d516bf589..55651bccaa8 100644 --- a/spec/frontend/sidebar/components/labels/labels_select_vue/store/actions_spec.js +++ b/spec/frontend/sidebar/components/labels/labels_select_vue/store/actions_spec.js @@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import * as actions from '~/sidebar/components/labels/labels_select_vue/store/actions'; import * as types from '~/sidebar/components/labels/labels_select_vue/store/mutation_types'; import defaultState from '~/sidebar/components/labels/labels_select_vue/store/state'; @@ -122,7 +122,7 @@ describe('LabelsSelect Actions', () => { describe('on success', () => { it('dispatches `requestLabels` & `receiveLabelsSuccess` actions', () => { const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]; - mock.onGet(/labels.json/).replyOnce(200, labels); + mock.onGet(/labels.json/).replyOnce(HTTP_STATUS_OK, labels); return testAction( actions.fetchLabels, @@ -206,7 +206,7 @@ describe('LabelsSelect Actions', () => { describe('on success', () => { it('dispatches `requestCreateLabel`, `fetchLabels` & `receiveCreateLabelSuccess` & `toggleDropdownContentsCreateView` actions', () => { const label = { id: 1 }; - mock.onPost(/labels.json/).replyOnce(200, label); + mock.onPost(/labels.json/).replyOnce(HTTP_STATUS_OK, label); return testAction( actions.createLabel, diff --git a/spec/frontend/vue_merge_request_widget/stores/artifacts_list/actions_spec.js b/spec/frontend/vue_merge_request_widget/stores/artifacts_list/actions_spec.js index dd23d4fd3f5..be31c65b5e2 100644 --- a/spec/frontend/vue_merge_request_widget/stores/artifacts_list/actions_spec.js +++ b/spec/frontend/vue_merge_request_widget/stores/artifacts_list/actions_spec.js @@ -2,7 +2,11 @@ import MockAdapter from 'axios-mock-adapter'; import { TEST_HOST } from 'helpers/test_constants'; import testAction from 'helpers/vuex_action_helper'; import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_NO_CONTENT } from '~/lib/utils/http_status'; +import { + HTTP_STATUS_INTERNAL_SERVER_ERROR, + HTTP_STATUS_NO_CONTENT, + HTTP_STATUS_OK, +} from '~/lib/utils/http_status'; import { setEndpoint, requestArtifacts, @@ -62,7 +66,7 @@ describe('Artifacts App Store Actions', () => { describe('success', () => { it('dispatches requestArtifacts and receiveArtifactsSuccess', () => { - mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, [ + mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(HTTP_STATUS_OK, [ { text: 'result.txt', url: 'asda', @@ -90,7 +94,7 @@ describe('Artifacts App Store Actions', () => { job_path: 'asda', }, ], - status: 200, + status: HTTP_STATUS_OK, }, type: 'receiveArtifactsSuccess', }, @@ -127,7 +131,7 @@ describe('Artifacts App Store Actions', () => { it('should commit RECEIVE_ARTIFACTS_SUCCESS mutation with 200', () => { return testAction( receiveArtifactsSuccess, - { data: { summary: {} }, status: 200 }, + { data: { summary: {} }, status: HTTP_STATUS_OK }, mockedState, [{ type: types.RECEIVE_ARTIFACTS_SUCCESS, payload: { summary: {} } }], [], diff --git a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_assignees_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_assignees_spec.js index 5a0ee5a59ba..98a357bac2b 100644 --- a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_assignees_spec.js +++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_assignees_spec.js @@ -3,6 +3,7 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { nextTick } from 'vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import SidebarAssignee from '~/vue_shared/alert_details/components/sidebar/sidebar_assignee.vue'; import SidebarAssignees from '~/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue'; import AlertSetAssignees from '~/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql'; @@ -97,7 +98,7 @@ describe('Alert Details Sidebar Assignees', () => { beforeEach(() => { mock = new MockAdapter(axios); - mock.onGet(mockPath).replyOnce(200, mockUsers); + mock.onGet(mockPath).replyOnce(HTTP_STATUS_OK, mockUsers); mountComponent({ data: { alert: mockAlert }, sidebarCollapsed: false, @@ -187,7 +188,7 @@ describe('Alert Details Sidebar Assignees', () => { beforeEach(() => { mock = new MockAdapter(axios); - mock.onGet(mockPath).replyOnce(200, mockUsers); + mock.onGet(mockPath).replyOnce(HTTP_STATUS_OK, mockUsers); mountComponent({ data: { alert: mockAlert }, diff --git a/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js b/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js index ecbd322e17b..b0c0fc79676 100644 --- a/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js +++ b/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js @@ -3,7 +3,7 @@ import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import MarkdownViewer from '~/vue_shared/components/content_viewer/viewers/markdown_viewer.vue'; jest.mock('~/behaviors/markdown/render_gfm'); @@ -36,9 +36,11 @@ describe('MarkdownViewer', () => { describe('success', () => { beforeEach(() => { - mock.onPost(`${gon.relative_url_root}/testproject/preview_markdown`).replyOnce(200, { - body: '<b>testing</b> {{gl_md_img_1}}', - }); + mock + .onPost(`${gon.relative_url_root}/testproject/preview_markdown`) + .replyOnce(HTTP_STATUS_OK, { + body: '<b>testing</b> {{gl_md_img_1}}', + }); }); it('renders a skeleton loader while the markdown is loading', () => { diff --git a/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js b/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js index e6e9e486b11..221da35de3d 100644 --- a/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js +++ b/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js @@ -16,7 +16,7 @@ import { } from 'jest/vue_shared/security_reports/mock_data'; import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import HelpIcon from '~/vue_shared/security_reports/components/help_icon.vue'; import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue'; import { @@ -188,7 +188,7 @@ describe('Security reports app', () => { describe('when loading', () => { beforeEach(() => { mock = new MockAdapter(axios, { delayResponse: 1 }); - mock.onGet(path).replyOnce(200, successResponse); + mock.onGet(path).replyOnce(HTTP_STATUS_OK, successResponse); createComponentWithFlagEnabled({ propsData: { @@ -210,7 +210,7 @@ describe('Security reports app', () => { describe('when successfully loaded', () => { beforeEach(() => { - mock.onGet(path).replyOnce(200, successResponse); + mock.onGet(path).replyOnce(HTTP_STATUS_OK, successResponse); createComponentWithFlagEnabled({ propsData: { diff --git a/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js index d8474708d6c..0cab950cb77 100644 --- a/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js +++ b/spec/frontend/vue_shared/security_reports/store/modules/sast/actions_spec.js @@ -2,7 +2,7 @@ import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status'; +import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import * as actions from '~/vue_shared/security_reports/store/modules/sast/actions'; import * as types from '~/vue_shared/security_reports/store/modules/sast/mutation_types'; import createState from '~/vue_shared/security_reports/store/modules/sast/state'; @@ -100,9 +100,9 @@ describe('sast report actions', () => { beforeEach(() => { mock .onGet(diffEndpoint) - .replyOnce(200, reports.diff) + .replyOnce(HTTP_STATUS_OK, reports.diff) .onGet(vulnerabilityFeedbackPath) - .replyOnce(200, reports.enrichData); + .replyOnce(HTTP_STATUS_OK, reports.enrichData); }); it('should dispatch the `receiveDiffSuccess` action', () => { @@ -129,7 +129,7 @@ describe('sast report actions', () => { describe('when diff endpoint responds successfully and fetching vulnerability feedback is not authorized', () => { beforeEach(() => { rootState.canReadVulnerabilityFeedback = false; - mock.onGet(diffEndpoint).replyOnce(200, reports.diff); + mock.onGet(diffEndpoint).replyOnce(HTTP_STATUS_OK, reports.diff); }); it('should dispatch the `receiveDiffSuccess` action with empty enrich data', () => { @@ -158,7 +158,7 @@ describe('sast report actions', () => { beforeEach(() => { mock .onGet(diffEndpoint) - .replyOnce(200, reports.diff) + .replyOnce(HTTP_STATUS_OK, reports.diff) .onGet(vulnerabilityFeedbackPath) .replyOnce(HTTP_STATUS_NOT_FOUND); }); @@ -180,7 +180,7 @@ describe('sast report actions', () => { .onGet(diffEndpoint) .replyOnce(HTTP_STATUS_NOT_FOUND) .onGet(vulnerabilityFeedbackPath) - .replyOnce(200, reports.enrichData); + .replyOnce(HTTP_STATUS_OK, reports.enrichData); }); it('should dispatch the `receiveDiffError` action', () => { diff --git a/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js b/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js index d0a8468ad92..7197784c3e8 100644 --- a/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js +++ b/spec/frontend/vue_shared/security_reports/store/modules/secret_detection/actions_spec.js @@ -2,7 +2,7 @@ import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status'; +import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import * as actions from '~/vue_shared/security_reports/store/modules/secret_detection/actions'; import * as types from '~/vue_shared/security_reports/store/modules/secret_detection/mutation_types'; import createState from '~/vue_shared/security_reports/store/modules/secret_detection/state'; @@ -100,9 +100,9 @@ describe('secret detection report actions', () => { beforeEach(() => { mock .onGet(diffEndpoint) - .replyOnce(200, reports.diff) + .replyOnce(HTTP_STATUS_OK, reports.diff) .onGet(vulnerabilityFeedbackPath) - .replyOnce(200, reports.enrichData); + .replyOnce(HTTP_STATUS_OK, reports.enrichData); }); it('should dispatch the `receiveDiffSuccess` action', () => { @@ -130,7 +130,7 @@ describe('secret detection report actions', () => { describe('when diff endpoint responds successfully and fetching vulnerability feedback is not authorized', () => { beforeEach(() => { rootState.canReadVulnerabilityFeedback = false; - mock.onGet(diffEndpoint).replyOnce(200, reports.diff); + mock.onGet(diffEndpoint).replyOnce(HTTP_STATUS_OK, reports.diff); }); it('should dispatch the `receiveDiffSuccess` action with empty enrich data', () => { @@ -159,7 +159,7 @@ describe('secret detection report actions', () => { beforeEach(() => { mock .onGet(diffEndpoint) - .replyOnce(200, reports.diff) + .replyOnce(HTTP_STATUS_OK, reports.diff) .onGet(vulnerabilityFeedbackPath) .replyOnce(HTTP_STATUS_NOT_FOUND); }); @@ -181,7 +181,7 @@ describe('secret detection report actions', () => { .onGet(diffEndpoint) .replyOnce(HTTP_STATUS_NOT_FOUND) .onGet(vulnerabilityFeedbackPath) - .replyOnce(200, reports.enrichData); + .replyOnce(HTTP_STATUS_OK, reports.enrichData); }); it('should dispatch the `receiveDiffError` action', () => { diff --git a/spec/frontend/whats_new/store/actions_spec.js b/spec/frontend/whats_new/store/actions_spec.js index c9614c7330b..5f5e4e53be2 100644 --- a/spec/frontend/whats_new/store/actions_spec.js +++ b/spec/frontend/whats_new/store/actions_spec.js @@ -3,6 +3,7 @@ import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import testAction from 'helpers/vuex_action_helper'; import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import actions from '~/whats_new/store/actions'; import * as types from '~/whats_new/store/mutation_types'; @@ -33,7 +34,7 @@ describe('whats new actions', () => { axiosMock = new MockAdapter(axios); axiosMock .onGet('/-/whats_new') - .replyOnce(200, [{ title: 'Whats New Drawer', url: 'www.url.com' }], { + .replyOnce(HTTP_STATUS_OK, [{ title: 'Whats New Drawer', url: 'www.url.com' }], { 'x-next-page': '2', }); @@ -49,7 +50,7 @@ describe('whats new actions', () => { axiosMock .onGet('/-/whats_new', { params: { page: undefined, v: undefined } }) - .replyOnce(200, [{ title: 'GitLab Stories' }]); + .replyOnce(HTTP_STATUS_OK, [{ title: 'GitLab Stories' }]); testAction( actions.fetchItems, @@ -66,7 +67,7 @@ describe('whats new actions', () => { axiosMock .onGet('/-/whats_new', { params: { page: 8, v: 42 } }) - .replyOnce(200, [{ title: 'GitLab Stories' }]); + .replyOnce(HTTP_STATUS_OK, [{ title: 'GitLab Stories' }]); testAction( actions.fetchItems, diff --git a/spec/graphql/types/ci/pipeline_type_spec.rb b/spec/graphql/types/ci/pipeline_type_spec.rb index 5683b3f86c4..67209874b54 100644 --- a/spec/graphql/types/ci/pipeline_type_spec.rb +++ b/spec/graphql/types/ci/pipeline_type_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Types::Ci::PipelineType do if Gitlab.ee? expected_fields += %w[ security_report_summary security_report_findings security_report_finding - code_quality_reports dast_profile + code_quality_reports dast_profile code_quality_report_summary ] end diff --git a/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb index 68e70525c55..93644aa1497 100644 --- a/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb +++ b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb @@ -4,19 +4,25 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Reports::CodequalityReports do let(:codequality_report) { described_class.new } - let(:degradation_1) { build(:codequality_degradation_1) } - let(:degradation_2) { build(:codequality_degradation_2) } + let(:degradation_major) { build(:codequality_degradation, :major) } + let(:degradation_minor) { build(:codequality_degradation, :minor) } + let(:degradation_blocker) { build(:codequality_degradation, :blocker) } + let(:degradation_info) { build(:codequality_degradation, :info) } + let(:degradation_major_2) { build(:codequality_degradation, :major) } + let(:degradation_critical) { build(:codequality_degradation, :critical) } + let(:degradation_uppercase_major) { build(:codequality_degradation, severity: 'MAJOR') } + let(:degradation_unknown) { build(:codequality_degradation, severity: 'unknown') } it { expect(codequality_report.degradations).to eq({}) } describe '#add_degradation' do context 'when there is a degradation' do before do - codequality_report.add_degradation(degradation_1) + codequality_report.add_degradation(degradation_major) end it 'adds degradation to codequality report' do - expect(codequality_report.degradations.keys).to eq([degradation_1[:fingerprint]]) + expect(codequality_report.degradations.keys).to match_array([degradation_major[:fingerprint]]) expect(codequality_report.degradations.values.size).to eq(1) end end @@ -53,8 +59,8 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do context 'when there are many degradations' do before do - codequality_report.add_degradation(degradation_1) - codequality_report.add_degradation(degradation_2) + codequality_report.add_degradation(degradation_major) + codequality_report.add_degradation(degradation_minor) end it 'returns the number of degradations' do @@ -68,36 +74,25 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do context 'when there are many degradations' do before do - codequality_report.add_degradation(degradation_1) - codequality_report.add_degradation(degradation_2) + codequality_report.add_degradation(degradation_major) + codequality_report.add_degradation(degradation_minor) end it 'returns all degradations' do - expect(all_degradations).to contain_exactly(degradation_1, degradation_2) + expect(all_degradations).to contain_exactly(degradation_major, degradation_minor) end end end describe '#sort_degradations!' do - let(:major) { build(:codequality_degradation, :major) } - let(:minor) { build(:codequality_degradation, :minor) } - let(:blocker) { build(:codequality_degradation, :blocker) } - let(:info) { build(:codequality_degradation, :info) } - let(:major_2) { build(:codequality_degradation, :major) } - let(:critical) { build(:codequality_degradation, :critical) } - let(:uppercase_major) { build(:codequality_degradation, severity: 'MAJOR') } - let(:unknown) { build(:codequality_degradation, severity: 'unknown') } - - let(:codequality_report) { described_class.new } - before do - codequality_report.add_degradation(major) - codequality_report.add_degradation(minor) - codequality_report.add_degradation(blocker) - codequality_report.add_degradation(major_2) - codequality_report.add_degradation(info) - codequality_report.add_degradation(critical) - codequality_report.add_degradation(unknown) + codequality_report.add_degradation(degradation_major) + codequality_report.add_degradation(degradation_minor) + codequality_report.add_degradation(degradation_blocker) + codequality_report.add_degradation(degradation_major_2) + codequality_report.add_degradation(degradation_info) + codequality_report.add_degradation(degradation_critical) + codequality_report.add_degradation(degradation_unknown) codequality_report.sort_degradations! end @@ -105,36 +100,70 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do it 'sorts degradations based on severity' do expect(codequality_report.degradations.values).to eq( [ - blocker, - critical, - major, - major_2, - minor, - info, - unknown + degradation_blocker, + degradation_critical, + degradation_major, + degradation_major_2, + degradation_minor, + degradation_info, + degradation_unknown ]) end context 'with non-existence and uppercase severities' do let(:other_report) { described_class.new } - let(:non_existent) { build(:codequality_degradation, severity: 'non-existent') } + let(:degradation_non_existent) { build(:codequality_degradation, severity: 'non-existent') } before do - other_report.add_degradation(blocker) - other_report.add_degradation(uppercase_major) - other_report.add_degradation(minor) - other_report.add_degradation(non_existent) + other_report.add_degradation(degradation_blocker) + other_report.add_degradation(degradation_uppercase_major) + other_report.add_degradation(degradation_minor) + other_report.add_degradation(degradation_non_existent) end it 'sorts unknown last' do expect(other_report.degradations.values).to eq( [ - blocker, - uppercase_major, - minor, - non_existent + degradation_blocker, + degradation_uppercase_major, + degradation_minor, + degradation_non_existent ]) end end end + + describe '#code_quality_report_summary' do + context "when there is no degradation" do + it 'return nil' do + expect(codequality_report.code_quality_report_summary).to eq(nil) + end + end + + context "when there are degradations" do + before do + codequality_report.add_degradation(degradation_major) + codequality_report.add_degradation(degradation_major_2) + codequality_report.add_degradation(degradation_minor) + codequality_report.add_degradation(degradation_blocker) + codequality_report.add_degradation(degradation_info) + codequality_report.add_degradation(degradation_critical) + codequality_report.add_degradation(degradation_unknown) + end + + it 'returns the summary of the code quality report' do + expect(codequality_report.code_quality_report_summary).to eq( + { + 'major' => 2, + 'minor' => 1, + 'blocker' => 1, + 'info' => 1, + 'critical' => 1, + 'unknown' => 1, + 'count' => 7 + } + ) + end + end + end end diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb index 5ed4eb7d233..de67ec35587 100644 --- a/spec/models/release_spec.rb +++ b/spec/models/release_spec.rb @@ -81,15 +81,10 @@ RSpec.describe Release do end end - # Mimic releases created before 11.7 - # See: https://gitlab.com/gitlab-org/gitlab/-/blob/8e5a110b01f842d8b6a702197928757a40ce9009/app/models/release.rb#L14 + # Deleting user along with their contributions, nullifies releases author_id. context 'when updating existing release without author' do let(:release) { create(:release, :legacy) } - before do - stub_feature_flags(validate_release_with_author: false) - end - it 'updates successfully' do release.description += 'Update' |