diff options
27 files changed, 282 insertions, 325 deletions
diff --git a/app/assets/javascripts/blob/notebook/notebook_viewer.vue b/app/assets/javascripts/blob/notebook/notebook_viewer.vue index dc1a9cb865a..ade92f2562b 100644 --- a/app/assets/javascripts/blob/notebook/notebook_viewer.vue +++ b/app/assets/javascripts/blob/notebook/notebook_viewer.vue @@ -1,6 +1,7 @@ <script> import { GlLoadingIcon } from '@gitlab/ui'; import axios from '~/lib/utils/axios_utils'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import NotebookLab from '~/notebook/index.vue'; export default { @@ -51,7 +52,7 @@ export default { this.loading = false; }) .catch((e) => { - if (e.status !== 200) { + if (e.status !== HTTP_STATUS_OK) { this.loadError = true; } this.error = true; diff --git a/app/assets/javascripts/ide/lib/mirror.js b/app/assets/javascripts/ide/lib/mirror.js index 78990953beb..f437965b25a 100644 --- a/app/assets/javascripts/ide/lib/mirror.js +++ b/app/assets/javascripts/ide/lib/mirror.js @@ -1,3 +1,4 @@ +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { getWebSocketUrl, mergeUrlParams } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; import createDiff from './create_diff'; @@ -26,7 +27,7 @@ const cancellableWait = (time) => { const isErrorResponse = (error) => error && error.code !== 0; -const isErrorPayload = (payload) => payload && payload.status_code !== 200; +const isErrorPayload = (payload) => payload && payload.status_code !== HTTP_STATUS_OK; const getErrorFromResponse = (data) => { if (isErrorResponse(data.error)) { diff --git a/app/services/clusters/agents/refresh_authorization_service.rb b/app/services/clusters/agents/refresh_authorization_service.rb index 53b14ab54da..23ececef6a1 100644 --- a/app/services/clusters/agents/refresh_authorization_service.rb +++ b/app/services/clusters/agents/refresh_authorization_service.rb @@ -58,7 +58,7 @@ module Clusters if project_entries allowed_projects.where_full_path_in(project_entries.keys).map do |project| - { project_id: project.id, config: project_entries[project.full_path] } + { project_id: project.id, config: project_entries[project.full_path.downcase] } end end end @@ -70,7 +70,7 @@ module Clusters if group_entries allowed_groups.where_full_path_in(group_entries.keys).map do |group| - { group_id: group.id, config: group_entries[group.full_path] } + { group_id: group.id, config: group_entries[group.full_path.downcase] } end end end @@ -79,7 +79,7 @@ module Clusters def extract_config_entries(entity:) config.dig('ci_access', entity) &.first(AUTHORIZED_ENTITY_LIMIT) - &.index_by { |config| config.delete('id') } + &.index_by { |config| config.delete('id').downcase } end def allowed_projects diff --git a/db/post_migrate/20210604070207_retry_backfill_traversal_ids.rb b/db/post_migrate/20210604070207_retry_backfill_traversal_ids.rb deleted file mode 100644 index 5e540c7f359..00000000000 --- a/db/post_migrate/20210604070207_retry_backfill_traversal_ids.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -class RetryBackfillTraversalIds < ActiveRecord::Migration[6.1] - include Gitlab::Database::MigrationHelpers - - ROOTS_MIGRATION = 'BackfillNamespaceTraversalIdsRoots' - CHILDREN_MIGRATION = 'BackfillNamespaceTraversalIdsChildren' - DOWNTIME = false - DELAY_INTERVAL = 2.minutes - - disable_ddl_transaction! - - def up - duration = requeue_background_migration_jobs_by_range_at_intervals(ROOTS_MIGRATION, DELAY_INTERVAL) - requeue_background_migration_jobs_by_range_at_intervals(CHILDREN_MIGRATION, DELAY_INTERVAL, initial_delay: duration) - end - - def down - # no-op - end -end diff --git a/db/schema_migrations/20210604070207 b/db/schema_migrations/20210604070207 deleted file mode 100644 index 3531c9775bd..00000000000 --- a/db/schema_migrations/20210604070207 +++ /dev/null @@ -1 +0,0 @@ -ec44b7f134de2ea6537c6fe3109fa9d7e32785233f3d1b8e9ea118474d21526a
\ No newline at end of file diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 2bb5ab6bca6..984ac49c514 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -378,6 +378,7 @@ Component statuses are linked to configuration documentation for each component. | [Runner](#gitlab-runner) | Executes GitLab CI/CD jobs | ⤓ | ⤓ | ✅ | ⚙ | ✅ | ⚙ | ⚙ | CE & EE | | [Sentry integration](#sentry) | Error tracking for deployed apps | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | CE & EE | | [Sidekiq](#sidekiq) | Background jobs processor | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | CE & EE | +| [Token Revocation API](sec/token_revocation_api.md) | Receives and revokes leaked secrets | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | EE Only | ### Component details diff --git a/doc/development/sec/token_revocation_api.md b/doc/development/sec/token_revocation_api.md new file mode 100644 index 00000000000..15d1d2d0ef3 --- /dev/null +++ b/doc/development/sec/token_revocation_api.md @@ -0,0 +1,118 @@ +--- +stage: Secure +group: Static Analysis +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments +--- + +# Token Revocation API + +The Token Revocation API is an externally-deployed HTTP API that interfaces with GitLab +to receive and revoke API tokens and other secrets detected by GitLab Secret Detection. +See the [high-level architecture](../../user/application_security/secret_detection/post_processing.md) +to understand the Secret Detection post-processing and revocation flow. + +GitLab.com uses the internally-maintained [Secret Revocation Service](https://gitlab.com/gitlab-com/gl-security/engineering-and-research/automation-team/secret-revocation-service) +(team-members only) as its Token Revocation API. For GitLab self-managed, you can create +your own API and configure GitLab to use it. + +## Implement a Token Revocation API for self-managed + +GitLab self-managed instances interested in using the revocation capabilities must: + +- Implement and deploy your own Token Revocation API. +- Configure the GitLab instance to use the Token Revocation API. + +Your service must: + +- Match the API specification below. +- Provide two endpoints: + - Fetching revocable token types. + - Revoking leaked tokens. +- Be rate-limited and idempotent. + +Requests to the documented endpoints are authenticated using API tokens passed in +the `Authorization` header. Request and response bodies, if present, are +expected to have the content type `application/json`. + +All endpoints may return these responses: + +- `401 Unauthorized` +- `405 Method Not Allowed` +- `500 Internal Server Error` + +### `GET /v1/revocable_token_types` + +Returns the valid `type` values for use in the `revoke_tokens` endpoint. + +NOTE: +These values match the concatenation of [the `secrets` analyzer's](../../user/application_security/secret_detection/index.md) +[primary identifier](../integrations/secure.md#identifiers) by means +of concatenating the `primary_identifier.type` and `primary_identifier.value`. +For example, the value `gitleaks_rule_id_gitlab_personal_access_token` matches the following finding identifier: + +```json +{"type": "gitleaks_rule_id", "name": "Gitleaks rule ID GitLab Personal Access Token", "value": "GitLab Personal Access Token"} +``` + +| Status Code | Description | +| ----- | --- | +| `200` | The response body contains the valid token `type` values. | + +Example response body: + +```json +{ + "types": ["gitleaks_rule_id_gitlab_personal_access_token"] +} +``` + +### `POST /v1/revoke_tokens` + +Accepts a list of tokens to be revoked by the appropriate provider. Your service is responsible for communicating +with each provider to revoke the token. + +| Status Code | Description | +| ----- | --- | +| `204` | All submitted tokens have been accepted for eventual revocation. | +| `400` | The request body is invalid or one of the submitted token types is not supported. The request should not be retried. | +| `429` | The provider has received too many requests. The request should be retried later. | + +Example request body: + +```json +[{ + "type": "gitleaks_rule_id_gitlab_personal_access_token", + "token": "glpat--8GMtG8Mf4EnMJzmAWDU", + "location": "https://example.com/some-repo/blob/abcdefghijklmnop/compromisedfile1.java" +}, +{ + "type": "gitleaks_rule_id_gitlab_personal_access_token", + "token": "glpat--tG84EGK33nMLLDE70zU", + "location": "https://example.com/some-repo/blob/abcdefghijklmnop/compromisedfile2.java" +}] +``` + +### Configure GitLab to interface with the Token Revocation API + +You must configure the following database settings in the GitLab instance: + +| Setting | Type | Description | +| ------- | ---- | ----------- | +| `secret_detection_token_revocation_enabled` | Boolean | Whether automatic token revocation is enabled | +| `secret_detection_token_revocation_url` | String | A fully-qualified URL to the `/v1/revoke_tokens` endpoint of the Token Revocation API | +| `secret_detection_revocation_token_types_url` | String | A fully-qualified URL to the `/v1/revocable_token_types` endpoint of the Token Revocation API | +| `secret_detection_token_revocation_token` | String | A pre-shared token to authenticate requests to the Token Revocation API | + +For example, to configure these values in the +[Rails console](../../administration/operations/rails_console.md#starting-a-rails-console-session): + +```ruby +::Gitlab::CurrentSettings.update!(secret_detection_token_revocation_token: 'MYSECRETTOKEN') +::Gitlab::CurrentSettings.update!(secret_detection_token_revocation_url: 'https://example.gitlab.com/revocation_service/v1/revoke_tokens') +::Gitlab::CurrentSettings.update!(secret_detection_revocation_token_types_url: 'https://example.gitlab.com/revocation_service/v1/revocable_token_types') +::Gitlab::CurrentSettings.update!(secret_detection_token_revocation_enabled: true) +``` + +After you configure these values, the Token Revocation API will be called according to the +[high-level architecture](../../user/application_security/secret_detection/post_processing.md#high-level-architecture) +diagram. diff --git a/doc/user/application_security/secret_detection/index.md b/doc/user/application_security/secret_detection/index.md index d316ed7a9de..d6aab71a2c6 100644 --- a/doc/user/application_security/secret_detection/index.md +++ b/doc/user/application_security/secret_detection/index.md @@ -195,7 +195,12 @@ Pipelines now include a Secret Detection job. ## Responding to a leaked secret -If the scanner detects a secret you should rotate it immediately. [Purging a file from the repository's history](../../project/repository/reducing_the_repo_size_using_git.md#purge-files-from-repository-history) may not be effective in removing all references to the file. Also, the secret remains in any forks of the repository. +Secrets detected by the analyzer should be immediately rotated. +[Purging a file from the repository's history](../../project/repository/reducing_the_repo_size_using_git.md#purge-files-from-repository-history) +may not be effective in removing all references to the file. Additionally, the secret will remain in any existing +forks or clones of the repository. + +GitLab will attempt to [automatically revoke](post_processing.md) some types of leaked secrets. ## Pinning to specific analyzer version diff --git a/doc/user/application_security/secret_detection/post_processing.md b/doc/user/application_security/secret_detection/post_processing.md index 6e9b77426ad..10742114b90 100644 --- a/doc/user/application_security/secret_detection/post_processing.md +++ b/doc/user/application_security/secret_detection/post_processing.md @@ -10,26 +10,28 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - [Disabled by default for GitLab personal access tokens](https://gitlab.com/gitlab-org/gitlab/-/issues/371658) in GitLab 15.6 [with a flag](../../../administration/feature_flags.md) named `gitlab_pat_auto_revocation`. Available to GitLab.com only. > - [Enabled by default for GitLab personal access tokens](https://gitlab.com/gitlab-org/gitlab/-/issues/371658) in GitLab 15.9 -GitLab supports running post-processing hooks after detecting a secret. These -hooks can perform actions, like notifying the cloud service that issued the secret. -The cloud provider can then confirm the credentials and take remediation actions, like: +GitLab.com and self-managed supports running post-processing hooks after detecting a secret. These +hooks can perform actions, like notifying the vendor that issued the secret. +The vendor can then confirm the credentials and take remediation actions, like: - Revoking a secret. - Reissuing a secret. - Notifying the creator of the secret. -GitLab SaaS supports post-processing for [GitLab personal access tokens](../../profile/personal_access_tokens.md) and Amazon Web Services (AWS). -Post-processing workflows vary by supported cloud providers. +GitLab supports post-processing for the following vendors and secrets: -Post-processing is limited to a project's default branch. The epic -[Post-processing of leaked secrets](https://gitlab.com/groups/gitlab-org/-/epics/4639). -contains: +| Vendor | Secret | GitLab.com | Self-managed | +| ----- | --- | --- | --- | +| GitLab | [Personal access tokens](../../profile/personal_access_tokens.md) | ✅ | ✅ 15.9 and later | +| Amazon Web Services (AWS) | [IAM access keys](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) | ✅ | ⚙ | -- Technical details of post-processing secrets. -- Discussions of efforts to support additional branches. +**Component legend** + +- ✅ - Available by default +- ⚙ - Requires manual integration using a [Token Revocation API](../../../development/sec/token_revocation_api.md) NOTE: -Post-processing is currently limited to a project's default branch +Post-processing is limited to a project's default branch. ## High-level architecture @@ -40,142 +42,51 @@ sequenceDiagram autonumber GitLab Rails->>+Sidekiq: gl-secret-detection-report.json Sidekiq-->+Sidekiq: StoreSecurityReportsWorker - Sidekiq-->+RevocationAPI: GET revocable keys types - RevocationAPI-->>-Sidekiq: OK - Sidekiq->>+RevocationAPI: POST revoke revocable keys - RevocationAPI-->>-Sidekiq: ACCEPTED - RevocationAPI-->>+Cloud Vendor: revoke revocable keys - Cloud Vendor-->>+RevocationAPI: ACCEPTED -``` - -## Integrate your cloud provider service with GitLab SaaS - -Third party cloud and SaaS providers can [express integration interest by filling out this form](https://forms.gle/wWpvrtLRK21Q2WJL9). - -### Implement a vendor revocation receiver service - -A vendor revocation receiver service integrates with a GitLab instance to receive -a web notification and respond to leaked token requests. - -To implement a receiver service to revoke leaked tokens: - -1. Create a publicly accessible HTTP service matching the corresponding API contract - below. Your service should be idempotent and rate-limited. -1. When a pipeline corresponding to its revocable token type (in the example, `my_api_token`) - completes, GitLab sends a request to your receiver service. -1. The included URL should be publicly accessible, and contain the commit where the - leaked token can be found. For example: - - ```plaintext - POST / HTTP/2 - Accept: */* - Content-Type: application/json - X-Gitlab-Token: MYSECRETTOKEN - - [ - {"type": "my_api_token", "token":"XXXXXXXXXXXXXXXX","url": "https://example.com/some-repo/blob/abcdefghijklmnop/compromisedfile1.java"} - ] - ``` - -## Implement a revocation service for self-managed - -Self-managed instances interested in using the revocation capabilities must: - -- Deploy the [RevocationAPI](#high-level-architecture). -- Configure the GitLab instance to use the RevocationAPI. - -A RevocationAPI must: - -- Match a minimal API specification. -- Provide two endpoints: - - Fetching revocable token types. - - Revoking leaked tokens. -- Be rate-limited and idempotent. - -Requests to the documented endpoints are authenticated via API tokens passed in -the `Authorization` header. Request and response bodies, if present, are -expected to have the content type `application/json`. - -All endpoints may return these responses: - -- `401 Unauthorized` -- `405 Method Not Allowed` -- `500 Internal Server Error` - -### `GET /v1/revocable_token_types` - -Returns the valid `type` values for use in the `revoke_tokens` endpoint. - -NOTE: -These values match the concatenation of [the `secrets` analyzer's](index.md) -[primary identifier](../../../development/integrations/secure.md#identifiers) by means -of concatenating the `primary_identifier.type` and `primary_identifier.value`. -In the case below, a finding identifier matches: - -```json -{"type": "gitleaks_rule_id", "name": "Gitleaks rule ID GitLab Personal Access Token", "value": "GitLab Personal Access Token"} + Sidekiq-->+Token Revocation API: GET revocable keys types + Token Revocation API-->>-Sidekiq: OK + Sidekiq->>+Token Revocation API: POST revoke revocable keys + Token Revocation API-->>-Sidekiq: ACCEPTED + Token Revocation API-->>+Receiver Service: revoke revocable keys + Receiver Service-->>+Token Revocation API: ACCEPTED ``` -| Status Code | Description | -| ----- | --- | -| `200` | The response body contains the valid token `type` values. | - -Example response body: - -```json -{ - "types": ["gitleaks_rule_id_gitlab_personal_access_token"] -} +1. A pipeline with a Secret Detection job completes on the project's default branch, producing a scan + report (**1**). +1. The report is processed (**2**) by an asynchronous worker, which communicates with an externally + deployed HTTP service (**3** and **4**) to determine which kinds of secrets can be automatically + revoked. +1. The worker sends (**5** and **6**) the list of detected secrets which the Token Revocation API is able to + revoke. +1. The Token Revocation API sends (**7** and **8**) each revocable token to their respective vendor's [receiver service](#integrate-your-cloud-provider-service-with-gitlabcom). + +See the [Token Revocation API](../../../development/sec/token_revocation_api.md) documentation for more +information. + +## Integrate your cloud provider service with GitLab.com + +Third-party cloud and SaaS vendors interested in automated token revocation can +[express integration interest by filling out this form](https://forms.gle/wWpvrtLRK21Q2WJL9). +Vendors must [implement a revocation receiver service](#implement-a-revocation-receiver-service) +which will be called by the Token Revocation API. + +### Implement a revocation receiver service + +A revocation receiver service integrates with a GitLab instance's Token Revocation API to receive and respond +to leaked token revocation requests. The service should be a publicly accessible HTTP API that is +idempotent and rate-limited. Requests to your service from the Token Revocation API will follow the example +below: + +```plaintext +POST / HTTP/2 +Accept: */* +Content-Type: application/json +X-Gitlab-Token: MYSECRETTOKEN + +[ + {"type": "my_api_token", "token":"XXXXXXXXXXXXXXXX","url": "https://example.com/some-repo/~/raw/abcdefghijklmnop/compromisedfile1.java"} +] ``` -### `POST /v1/revoke_tokens` - -Accepts a list of tokens to be revoked by the appropriate provider. - -| Status Code | Description | -| ----- | --- | -| `204` | All submitted tokens have been accepted for eventual revocation. | -| `400` | The request body is invalid or one of the submitted token types is not supported. The request should not be retried. | -| `429` | The provider has received too many requests. The request should be retried later. | - -Example request body: - -```json -[{ - "type": "gitleaks_rule_id_gitlab_personal_access_token", - "token": "glpat--8GMtG8Mf4EnMJzmAWDU", - "location": "https://example.com/some-repo/blob/abcdefghijklmnop/compromisedfile1.java" -}, -{ - "type": "gitleaks_rule_id_gitlab_personal_access_token", - "token": "glpat--tG84EGK33nMLLDE70zU", - "location": "https://example.com/some-repo/blob/abcdefghijklmnop/compromisedfile2.java" -}] -``` - -### Configure GitLab to interface with RevocationAPI - -You must configure the following database settings in the GitLab instance: - -- `secret_detection_token_revocation_enabled` -- `secret_detection_token_revocation_url` -- `secret_detection_token_revocation_token` -- `secret_detection_revocation_token_types_url` - -For example, to configure these values in the -[Rails console](../../../administration/operations/rails_console.md#starting-a-rails-console-session): - -```ruby -::Gitlab::CurrentSettings.update!(secret_detection_token_revocation_token: 'MYSECRETTOKEN') -::Gitlab::CurrentSettings.update!(secret_detection_token_revocation_url: 'https://example.gitlab.com/revocation_service/v1/revoke_tokens') -::Gitlab::CurrentSettings.update!(secret_detection_revocation_token_types_url: 'https://example.gitlab.com/revocation_service/v1/revocable_token_types') -::Gitlab::CurrentSettings.update!(secret_detection_token_revocation_enabled: true) -``` - -After you configure these values, completing a pipeline performs these actions: - -1. The revocation service is triggered once. -1. A request is made to `secret_detection_revocation_token_types_url` to fetch a - list of revocable tokens. -1. Any Secret Detection findings matching the results of the `token_types` request - are included in the subsequent revocation request. +In this example, Secret Detection has determined that an instance of `my_api_token` has been leaked. The +value of the token is provided to you, in addition to a publicly accessible URL to the raw content of the +file containing the leaked token. diff --git a/spec/frontend/badges/store/actions_spec.js b/spec/frontend/badges/store/actions_spec.js index 4e8d7aaaca9..5ca199357f9 100644 --- a/spec/frontend/badges/store/actions_spec.js +++ b/spec/frontend/badges/store/actions_spec.js @@ -5,7 +5,7 @@ import actions, { transformBackendBadge } from '~/badges/store/actions'; import mutationTypes from '~/badges/store/mutation_types'; import createState from '~/badges/store/state'; 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 { createDummyBadge, createDummyBadgeResponse } from '../dummy_badge'; describe('Badges store actions', () => { @@ -99,7 +99,7 @@ describe('Badges store actions', () => { expect(dispatch.mock.calls).toEqual([['requestNewBadge']]); dispatch.mockClear(); - return [200, dummyResponse]; + return [HTTP_STATUS_OK, dummyResponse]; }); const dummyBadge = transformBackendBadge(dummyResponse); @@ -177,7 +177,7 @@ describe('Badges store actions', () => { endpointMock.replyOnce(() => { expect(dispatch.mock.calls).toEqual([['requestDeleteBadge', badgeId]]); dispatch.mockClear(); - return [200, '']; + return [HTTP_STATUS_OK, '']; }); await actions.deleteBadge({ state, dispatch }, { id: badgeId }); @@ -266,7 +266,7 @@ describe('Badges store actions', () => { endpointMock.replyOnce(() => { expect(dispatch.mock.calls).toEqual([['requestLoadBadges', dummyData]]); dispatch.mockClear(); - return [200, dummyReponse]; + return [HTTP_STATUS_OK, dummyReponse]; }); await actions.loadBadges({ state, dispatch }, dummyData); @@ -381,7 +381,7 @@ describe('Badges store actions', () => { endpointMock.replyOnce(() => { expect(dispatch.mock.calls).toEqual([['requestRenderedBadge']]); dispatch.mockClear(); - return [200, dummyReponse]; + return [HTTP_STATUS_OK, dummyReponse]; }); await actions.renderBadge({ state, dispatch }); @@ -468,7 +468,7 @@ describe('Badges store actions', () => { expect(dispatch.mock.calls).toEqual([['requestUpdatedBadge']]); dispatch.mockClear(); - return [200, dummyResponse]; + return [HTTP_STATUS_OK, dummyResponse]; }); const updatedBadge = transformBackendBadge(dummyResponse); diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js index 0ab8a89bcca..1d011eacf1c 100644 --- a/spec/frontend/boards/mock_data.js +++ b/spec/frontend/boards/mock_data.js @@ -1,6 +1,7 @@ import { GlFilteredSearchToken } from '@gitlab/ui'; import { keyBy } from 'lodash'; import { ListType } from '~/boards/constants'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { OPERATORS_IS, OPERATORS_IS_NOT, @@ -460,7 +461,7 @@ export const BoardsMockData = { export const boardsMockInterceptor = (config) => { const body = BoardsMockData[config.method.toUpperCase()][config.url]; - return [200, body]; + return [HTTP_STATUS_OK, body]; }; export const mockList = { diff --git a/spec/frontend/clusters_list/components/clusters_spec.js b/spec/frontend/clusters_list/components/clusters_spec.js index e8e705a6384..20dbff9df15 100644 --- a/spec/frontend/clusters_list/components/clusters_spec.js +++ b/spec/frontend/clusters_list/components/clusters_spec.js @@ -7,6 +7,7 @@ import Clusters from '~/clusters_list/components/clusters.vue'; import ClustersEmptyState from '~/clusters_list/components/clusters_empty_state.vue'; import ClusterStore from '~/clusters_list/store'; import axios from '~/lib/utils/axios_utils'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { apiData } from '../mock_data'; describe('Clusters', () => { @@ -68,7 +69,7 @@ describe('Clusters', () => { captureException = jest.spyOn(Sentry, 'captureException'); mock = new MockAdapter(axios); - mockPollingApi(200, apiData, paginationHeader()); + mockPollingApi(HTTP_STATUS_OK, apiData, paginationHeader()); return createWrapper({}); }); @@ -255,7 +256,7 @@ describe('Clusters', () => { const totalSecondPage = 500; beforeEach(() => { - mockPollingApi(200, apiData, paginationHeader(totalFirstPage, perPage, 1)); + mockPollingApi(HTTP_STATUS_OK, apiData, paginationHeader(totalFirstPage, perPage, 1)); return createWrapper({}); }); @@ -269,7 +270,7 @@ describe('Clusters', () => { describe('when updating currentPage', () => { beforeEach(() => { - mockPollingApi(200, apiData, paginationHeader(totalSecondPage, perPage, 2)); + mockPollingApi(HTTP_STATUS_OK, apiData, paginationHeader(totalSecondPage, perPage, 2)); // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details // eslint-disable-next-line no-restricted-syntax wrapper.setData({ currentPage: 2 }); diff --git a/spec/frontend/environments/edit_environment_spec.js b/spec/frontend/environments/edit_environment_spec.js index ba4b4db54ff..57545c565b4 100644 --- a/spec/frontend/environments/edit_environment_spec.js +++ b/spec/frontend/environments/edit_environment_spec.js @@ -5,6 +5,7 @@ import waitForPromises from 'helpers/wait_for_promises'; import EditEnvironment from '~/environments/components/edit_environment.vue'; import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { visitUrl } from '~/lib/utils/url_utility'; jest.mock('~/lib/utils/url_utility'); @@ -68,7 +69,7 @@ describe('~/environments/components/edit.vue', () => { expect(showsLoading()).toBe(false); - await submitForm(expected, [200, { path: '/test' }]); + await submitForm(expected, [HTTP_STATUS_OK, { path: '/test' }]); expect(showsLoading()).toBe(true); }); @@ -76,7 +77,7 @@ describe('~/environments/components/edit.vue', () => { it('submits the updated environment on submit', async () => { const expected = { url: 'https://google.ca' }; - await submitForm(expected, [200, { path: '/test' }]); + await submitForm(expected, [HTTP_STATUS_OK, { path: '/test' }]); expect(visitUrl).toHaveBeenCalledWith('/test'); }); diff --git a/spec/frontend/environments/new_environment_spec.js b/spec/frontend/environments/new_environment_spec.js index 5dd77759391..016d9571094 100644 --- a/spec/frontend/environments/new_environment_spec.js +++ b/spec/frontend/environments/new_environment_spec.js @@ -5,6 +5,7 @@ import waitForPromises from 'helpers/wait_for_promises'; import NewEnvironment from '~/environments/components/new_environment.vue'; import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { visitUrl } from '~/lib/utils/url_utility'; jest.mock('~/lib/utils/url_utility'); @@ -79,7 +80,7 @@ describe('~/environments/components/new.vue', () => { expect(showsLoading()).toBe(false); - await submitForm(expected, [200, { path: '/test' }]); + await submitForm(expected, [HTTP_STATUS_OK, { path: '/test' }]); expect(showsLoading()).toBe(true); }); @@ -87,7 +88,7 @@ describe('~/environments/components/new.vue', () => { it('submits the new environment on submit', async () => { const expected = { name: 'test', url: 'https://google.ca' }; - await submitForm(expected, [200, { path: '/test' }]); + await submitForm(expected, [HTTP_STATUS_OK, { path: '/test' }]); expect(visitUrl).toHaveBeenCalledWith('/test'); }); diff --git a/spec/frontend/error_tracking_settings/store/actions_spec.js b/spec/frontend/error_tracking_settings/store/actions_spec.js index 40f82b58fe5..bad2bdfaa1a 100644 --- a/spec/frontend/error_tracking_settings/store/actions_spec.js +++ b/spec/frontend/error_tracking_settings/store/actions_spec.js @@ -29,7 +29,7 @@ describe('error tracking settings actions', () => { }); it('should request and transform the project list', async () => { - mock.onGet(TEST_HOST).reply(() => [200, { projects: projectList }]); + mock.onGet(TEST_HOST).reply(() => [HTTP_STATUS_OK, { projects: projectList }]); await testAction( actions.fetchProjects, null, diff --git a/spec/frontend/ide/lib/mirror_spec.js b/spec/frontend/ide/lib/mirror_spec.js index 33dd0fefc6c..98e6b0deee6 100644 --- a/spec/frontend/ide/lib/mirror_spec.js +++ b/spec/frontend/ide/lib/mirror_spec.js @@ -7,7 +7,7 @@ import { MSG_CONNECTION_ERROR, SERVICE_DELAY, } from '~/ide/lib/mirror'; -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 { getWebSocketUrl } from '~/lib/utils/url_utility'; jest.mock('~/ide/lib/create_diff', () => jest.fn()); @@ -19,10 +19,13 @@ const TEST_DIFF = { }; const TEST_ERROR = 'Something bad happened...'; const TEST_SUCCESS_RESPONSE = { - data: JSON.stringify({ error: { code: 0 }, payload: { status_code: 200 } }), + data: JSON.stringify({ error: { code: 0 }, payload: { status_code: HTTP_STATUS_OK } }), }; const TEST_ERROR_RESPONSE = { - data: JSON.stringify({ error: { code: 1, Message: TEST_ERROR }, payload: { status_code: 200 } }), + data: JSON.stringify({ + error: { code: 1, Message: TEST_ERROR }, + payload: { status_code: HTTP_STATUS_OK }, + }), }; const TEST_ERROR_PAYLOAD_RESPONSE = { data: JSON.stringify({ diff --git a/spec/frontend/ide/services/terminals_spec.js b/spec/frontend/ide/services/terminals_spec.js index 788fdb6471c..5f752197e13 100644 --- a/spec/frontend/ide/services/terminals_spec.js +++ b/spec/frontend/ide/services/terminals_spec.js @@ -1,6 +1,7 @@ import MockAdapter from 'axios-mock-adapter'; import * as terminalService from '~/ide/services/terminals'; import axios from '~/lib/utils/axios_utils'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; const TEST_PROJECT_PATH = 'lorem/ipsum/dolar'; const TEST_BRANCH = 'ref'; @@ -11,7 +12,7 @@ describe('~/ide/services/terminals', () => { const prevRelativeUrlRoot = gon.relative_url_root; beforeEach(() => { - axiosSpy = jest.fn().mockReturnValue([200, {}]); + axiosSpy = jest.fn().mockReturnValue([HTTP_STATUS_OK, {}]); mock = new MockAdapter(axios); mock.onPost(/.*/).reply((...args) => axiosSpy(...args)); diff --git a/spec/frontend/issues/show/components/app_spec.js b/spec/frontend/issues/show/components/app_spec.js index acb04e0af56..863657b3547 100644 --- a/spec/frontend/issues/show/components/app_spec.js +++ b/spec/frontend/issues/show/components/app_spec.js @@ -23,6 +23,7 @@ import PinnedLinks from '~/issues/show/components/pinned_links.vue'; import { POLLING_DELAY } from '~/issues/show/constants'; import eventHub from '~/issues/show/event_hub'; import axios from '~/lib/utils/axios_utils'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { visitUrl } from '~/lib/utils/url_utility'; import { appProps, @@ -100,7 +101,7 @@ describe('Issuable output', () => { mock .onGet('/gitlab-org/gitlab-shell/-/issues/9/realtime_changes/realtime_changes') .reply(() => { - const res = Promise.resolve([200, REALTIME_REQUEST_STACK[realtimeRequestCount]]); + const res = Promise.resolve([HTTP_STATUS_OK, REALTIME_REQUEST_STACK[realtimeRequestCount]]); realtimeRequestCount += 1; return res; }); @@ -336,7 +337,9 @@ describe('Issuable output', () => { const mockData = { test: [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }], }; - mock.onGet('/issuable-templates-path').reply(() => Promise.resolve([200, mockData])); + mock + .onGet('/issuable-templates-path') + .reply(() => Promise.resolve([HTTP_STATUS_OK, mockData])); return wrapper.vm.requestTemplatesAndShowForm().then(() => { expect(formSpy).toHaveBeenCalledWith(mockData); @@ -345,7 +348,9 @@ describe('Issuable output', () => { it('shows the form if template names as array request is successful', () => { const mockData = [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }]; - mock.onGet('/issuable-templates-path').reply(() => Promise.resolve([200, mockData])); + mock + .onGet('/issuable-templates-path') + .reply(() => Promise.resolve([HTTP_STATUS_OK, mockData])); return wrapper.vm.requestTemplatesAndShowForm().then(() => { expect(formSpy).toHaveBeenCalledWith(mockData); diff --git a/spec/frontend/lib/utils/rails_ujs_spec.js b/spec/frontend/lib/utils/rails_ujs_spec.js index da9cc5c6f3c..8ca4dfc9340 100644 --- a/spec/frontend/lib/utils/rails_ujs_spec.js +++ b/spec/frontend/lib/utils/rails_ujs_spec.js @@ -1,5 +1,6 @@ import { setHTMLFixture } from 'helpers/fixtures'; import waitForPromises from 'helpers/wait_for_promises'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; beforeAll(async () => { // @rails/ujs expects jQuery.ajaxPrefilter to exist if jQuery exists at @@ -20,7 +21,7 @@ function mockXHRResponse({ responseText, responseContentType } = {}) { jest.spyOn(global.XMLHttpRequest.prototype, 'send').mockImplementation(function send() { Object.defineProperties(this, { readyState: { value: XMLHttpRequest.DONE }, - status: { value: 200 }, + status: { value: HTTP_STATUS_OK }, response: { value: responseText }, }); this.onreadystatechange(); diff --git a/spec/frontend/milestones/components/milestone_combobox_spec.js b/spec/frontend/milestones/components/milestone_combobox_spec.js index 27485f3d51d..f8ddca1a2ad 100644 --- a/spec/frontend/milestones/components/milestone_combobox_spec.js +++ b/spec/frontend/milestones/components/milestone_combobox_spec.js @@ -4,7 +4,7 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; -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 { ENTER_KEY } from '~/lib/utils/keys'; import MilestoneCombobox from '~/milestones/components/milestone_combobox.vue'; import createStore from '~/milestones/stores/'; @@ -64,15 +64,15 @@ describe('Milestone combobox component', () => { projectMilestonesApiCallSpy = jest .fn() - .mockReturnValue([200, projectMilestones, { [X_TOTAL_HEADER]: '6' }]); + .mockReturnValue([HTTP_STATUS_OK, projectMilestones, { [X_TOTAL_HEADER]: '6' }]); groupMilestonesApiCallSpy = jest .fn() - .mockReturnValue([200, groupMilestones, { [X_TOTAL_HEADER]: '6' }]); + .mockReturnValue([HTTP_STATUS_OK, groupMilestones, { [X_TOTAL_HEADER]: '6' }]); searchApiCallSpy = jest .fn() - .mockReturnValue([200, projectMilestones, { [X_TOTAL_HEADER]: '6' }]); + .mockReturnValue([HTTP_STATUS_OK, projectMilestones, { [X_TOTAL_HEADER]: '6' }]); mock .onGet(`/api/v4/projects/${projectId}/milestones`) @@ -248,9 +248,11 @@ describe('Milestone combobox component', () => { beforeEach(() => { projectMilestonesApiCallSpy = jest .fn() - .mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]); + .mockReturnValue([HTTP_STATUS_OK, [], { [X_TOTAL_HEADER]: '0' }]); - groupMilestonesApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]); + groupMilestonesApiCallSpy = jest + .fn() + .mockReturnValue([HTTP_STATUS_OK, [], { [X_TOTAL_HEADER]: '0' }]); createComponent(); @@ -301,7 +303,7 @@ describe('Milestone combobox component', () => { beforeEach(() => { projectMilestonesApiCallSpy = jest .fn() - .mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]); + .mockReturnValue([HTTP_STATUS_OK, [], { [X_TOTAL_HEADER]: '0' }]); createComponent(); @@ -366,7 +368,7 @@ describe('Milestone combobox component', () => { createComponent(); projectMilestonesApiCallSpy = jest .fn() - .mockReturnValue([200, [{ title: 'v1.0' }], { [X_TOTAL_HEADER]: '1' }]); + .mockReturnValue([HTTP_STATUS_OK, [{ title: 'v1.0' }], { [X_TOTAL_HEADER]: '1' }]); return waitForRequests(); }); @@ -430,7 +432,7 @@ describe('Milestone combobox component', () => { beforeEach(() => { groupMilestonesApiCallSpy = jest .fn() - .mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]); + .mockReturnValue([HTTP_STATUS_OK, [], { [X_TOTAL_HEADER]: '0' }]); createComponent(); @@ -495,7 +497,11 @@ describe('Milestone combobox component', () => { createComponent(); groupMilestonesApiCallSpy = jest .fn() - .mockReturnValue([200, [{ title: 'group-v1.0' }], { [X_TOTAL_HEADER]: '1' }]); + .mockReturnValue([ + HTTP_STATUS_OK, + [{ title: 'group-v1.0' }], + { [X_TOTAL_HEADER]: '1' }, + ]); return waitForRequests(); }); diff --git a/spec/frontend/notes/mock_data.js b/spec/frontend/notes/mock_data.js index 286f2adc1d8..d5b7ad73177 100644 --- a/spec/frontend/notes/mock_data.js +++ b/spec/frontend/notes/mock_data.js @@ -1,4 +1,5 @@ // Copied to ee/spec/frontend/notes/mock_data.js +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { __ } from '~/locale'; export const notesDataMock = { @@ -655,11 +656,11 @@ export const DISCUSSION_NOTE_RESPONSE_MAP = { }; export function getIndividualNoteResponse(config) { - return [200, INDIVIDUAL_NOTE_RESPONSE_MAP[config.method.toUpperCase()][config.url]]; + return [HTTP_STATUS_OK, INDIVIDUAL_NOTE_RESPONSE_MAP[config.method.toUpperCase()][config.url]]; } export function getDiscussionNoteResponse(config) { - return [200, DISCUSSION_NOTE_RESPONSE_MAP[config.method.toUpperCase()][config.url]]; + return [HTTP_STATUS_OK, DISCUSSION_NOTE_RESPONSE_MAP[config.method.toUpperCase()][config.url]]; } export const notesWithDescriptionChanges = [ diff --git a/spec/frontend/profile/account/components/update_username_spec.js b/spec/frontend/profile/account/components/update_username_spec.js index 575df9fb3c0..bb9ae01963e 100644 --- a/spec/frontend/profile/account/components/update_username_spec.js +++ b/spec/frontend/profile/account/components/update_username_spec.js @@ -5,7 +5,7 @@ import { nextTick } from 'vue'; import { TEST_HOST } from 'helpers/test_constants'; import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; - +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import UpdateUsername from '~/profile/account/components/update_username.vue'; jest.mock('~/flash'); @@ -97,7 +97,7 @@ describe('UpdateUsername component', () => { }); it('executes API call on confirmation button click', async () => { - axiosMock.onPut(actionUrl).replyOnce(() => [200, { message: 'Username changed' }]); + axiosMock.onPut(actionUrl).replyOnce(() => [HTTP_STATUS_OK, { message: 'Username changed' }]); jest.spyOn(axios, 'put'); await wrapper.vm.onConfirm(); @@ -114,7 +114,7 @@ describe('UpdateUsername component', () => { expect(openModalBtn.props('disabled')).toBe(false); expect(openModalBtn.props('loading')).toBe(true); - return [200, { message: 'Username changed' }]; + return [HTTP_STATUS_OK, { message: 'Username changed' }]; }); await wrapper.vm.onConfirm(); diff --git a/spec/frontend/ref/components/ref_selector_spec.js b/spec/frontend/ref/components/ref_selector_spec.js index ec14d8bacb2..bfff0c2fedf 100644 --- a/spec/frontend/ref/components/ref_selector_spec.js +++ b/spec/frontend/ref/components/ref_selector_spec.js @@ -9,7 +9,11 @@ import commit from 'test_fixtures/api/commits/commit.json'; import branches from 'test_fixtures/api/branches/branches.json'; import tags from 'test_fixtures/api/tags/tags.json'; import { trimText } from 'helpers/text_helper'; -import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status'; +import { + HTTP_STATUS_INTERNAL_SERVER_ERROR, + HTTP_STATUS_NOT_FOUND, + HTTP_STATUS_OK, +} from '~/lib/utils/http_status'; import { ENTER_KEY } from '~/lib/utils/keys'; import { sprintf } from '~/locale'; import RefSelector from '~/ref/components/ref_selector.vue'; @@ -69,9 +73,11 @@ describe('Ref selector component', () => { branchesApiCallSpy = jest .fn() - .mockReturnValue([200, fixtures.branches, { [X_TOTAL_HEADER]: '123' }]); - tagsApiCallSpy = jest.fn().mockReturnValue([200, fixtures.tags, { [X_TOTAL_HEADER]: '456' }]); - commitApiCallSpy = jest.fn().mockReturnValue([200, fixtures.commit]); + .mockReturnValue([HTTP_STATUS_OK, fixtures.branches, { [X_TOTAL_HEADER]: '123' }]); + tagsApiCallSpy = jest + .fn() + .mockReturnValue([HTTP_STATUS_OK, fixtures.tags, { [X_TOTAL_HEADER]: '456' }]); + commitApiCallSpy = jest.fn().mockReturnValue([HTTP_STATUS_OK, fixtures.commit]); requestSpies = { branchesApiCallSpy, tagsApiCallSpy, commitApiCallSpy }; mock @@ -309,8 +315,10 @@ describe('Ref selector component', () => { describe('when no results are found', () => { beforeEach(() => { - branchesApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]); - tagsApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]); + branchesApiCallSpy = jest + .fn() + .mockReturnValue([HTTP_STATUS_OK, [], { [X_TOTAL_HEADER]: '0' }]); + tagsApiCallSpy = jest.fn().mockReturnValue([HTTP_STATUS_OK, [], { [X_TOTAL_HEADER]: '0' }]); commitApiCallSpy = jest.fn().mockReturnValue([HTTP_STATUS_NOT_FOUND]); createComponent(); @@ -386,7 +394,9 @@ describe('Ref selector component', () => { describe('when the branches search returns no results', () => { beforeEach(() => { - branchesApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]); + branchesApiCallSpy = jest + .fn() + .mockReturnValue([HTTP_STATUS_OK, [], { [X_TOTAL_HEADER]: '0' }]); createComponent(); @@ -451,7 +461,9 @@ describe('Ref selector component', () => { describe('when the tags search returns no results', () => { beforeEach(() => { - tagsApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]); + tagsApiCallSpy = jest + .fn() + .mockReturnValue([HTTP_STATUS_OK, [], { [X_TOTAL_HEADER]: '0' }]); createComponent(); diff --git a/spec/frontend/vue_merge_request_widget/extentions/terraform/index_spec.js b/spec/frontend/vue_merge_request_widget/extentions/terraform/index_spec.js index b62ea542488..13384e1efca 100644 --- a/spec/frontend/vue_merge_request_widget/extentions/terraform/index_spec.js +++ b/spec/frontend/vue_merge_request_widget/extentions/terraform/index_spec.js @@ -3,7 +3,7 @@ import { mountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import api from '~/api'; 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 Poll from '~/lib/utils/poll'; import extensionsContainer from '~/vue_merge_request_widget/components/extensions/container'; import { registerExtension } from '~/vue_merge_request_widget/components/extensions'; @@ -23,7 +23,6 @@ describe('Terraform extension', () => { let mock; const endpoint = '/path/to/terraform/report.json'; - const successStatusCode = 200; const findListItem = (at) => wrapper.findAllByTestId('extension-list-item').at(at); @@ -57,7 +56,7 @@ describe('Terraform extension', () => { describe('while loading', () => { const loadingText = 'Loading Terraform reports...'; it('should render loading text', async () => { - mockPollingApi(successStatusCode, plans, {}); + mockPollingApi(HTTP_STATUS_OK, plans, {}); createComponent(); expect(wrapper.text()).toContain(loadingText); @@ -85,7 +84,7 @@ describe('Terraform extension', () => { ${'1 valid and 2 invalid reports'} | ${{ 0: validPlanWithName, 1: invalidPlanWithName, 2: invalidPlanWithName }} | ${'Terraform report was generated in your pipelines'} | ${'2 Terraform reports failed to generate'} `('and received $responseType', ({ response, summaryTitle, summarySubtitle }) => { beforeEach(async () => { - mockPollingApi(successStatusCode, response, {}); + mockPollingApi(HTTP_STATUS_OK, response, {}); return createComponent(); }); @@ -102,7 +101,7 @@ describe('Terraform extension', () => { describe('expanded data', () => { beforeEach(async () => { - mockPollingApi(successStatusCode, plans, {}); + mockPollingApi(HTTP_STATUS_OK, plans, {}); await createComponent(); wrapper.findByTestId('toggle-button').trigger('click'); @@ -164,7 +163,7 @@ describe('Terraform extension', () => { describe('successful poll', () => { beforeEach(() => { - mockPollingApi(successStatusCode, plans, {}); + mockPollingApi(HTTP_STATUS_OK, plans, {}); return createComponent(); }); diff --git a/spec/frontend_integration/test_helpers/mock_server/routes/emojis.js b/spec/frontend_integration/test_helpers/mock_server/routes/emojis.js index 64e9006a710..83991ad5af9 100644 --- a/spec/frontend_integration/test_helpers/mock_server/routes/emojis.js +++ b/spec/frontend_integration/test_helpers/mock_server/routes/emojis.js @@ -1,9 +1,10 @@ import { Response } from 'miragejs'; import emojis from 'public/-/emojis/2/emojis.json'; import { EMOJI_VERSION } from '~/emoji'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; export default (server) => { server.get(`/-/emojis/${EMOJI_VERSION}/emojis.json`, () => { - return new Response(200, {}, emojis); + return new Response(HTTP_STATUS_OK, {}, emojis); }); }; diff --git a/spec/migrations/retry_backfill_traversal_ids_spec.rb b/spec/migrations/retry_backfill_traversal_ids_spec.rb deleted file mode 100644 index f3658d1b8a3..00000000000 --- a/spec/migrations/retry_backfill_traversal_ids_spec.rb +++ /dev/null @@ -1,93 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require_migration! - -RSpec.describe RetryBackfillTraversalIds, :migration, feature_category: :subgroups do - include ReloadHelpers - - let!(:namespaces_table) { table(:namespaces) } - - context 'when BackfillNamespaceTraversalIdsRoots jobs are pending' do - before do - table(:background_migration_jobs).create!( - class_name: 'BackfillNamespaceTraversalIdsRoots', - arguments: [1, 4, 100], - status: Gitlab::Database::BackgroundMigrationJob.statuses['pending'] - ) - table(:background_migration_jobs).create!( - class_name: 'BackfillNamespaceTraversalIdsRoots', - arguments: [5, 9, 100], - status: Gitlab::Database::BackgroundMigrationJob.statuses['succeeded'] - ) - end - - it 'queues pending jobs' do - migrate! - - expect(BackgroundMigrationWorker.jobs.length).to eq(1) - expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['BackfillNamespaceTraversalIdsRoots', [1, 4, 100]]) - expect(BackgroundMigrationWorker.jobs[0]['at']).to be_nil - end - end - - context 'when BackfillNamespaceTraversalIdsChildren jobs are pending' do - before do - table(:background_migration_jobs).create!( - class_name: 'BackfillNamespaceTraversalIdsChildren', - arguments: [1, 4, 100], - status: Gitlab::Database::BackgroundMigrationJob.statuses['pending'] - ) - table(:background_migration_jobs).create!( - class_name: 'BackfillNamespaceTraversalIdsRoots', - arguments: [5, 9, 100], - status: Gitlab::Database::BackgroundMigrationJob.statuses['succeeded'] - ) - end - - it 'queues pending jobs' do - migrate! - - expect(BackgroundMigrationWorker.jobs.length).to eq(1) - expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['BackfillNamespaceTraversalIdsChildren', [1, 4, 100]]) - expect(BackgroundMigrationWorker.jobs[0]['at']).to be_nil - end - end - - context 'when BackfillNamespaceTraversalIdsRoots and BackfillNamespaceTraversalIdsChildren jobs are pending' do - before do - table(:background_migration_jobs).create!( - class_name: 'BackfillNamespaceTraversalIdsRoots', - arguments: [1, 4, 100], - status: Gitlab::Database::BackgroundMigrationJob.statuses['pending'] - ) - table(:background_migration_jobs).create!( - class_name: 'BackfillNamespaceTraversalIdsChildren', - arguments: [5, 9, 100], - status: Gitlab::Database::BackgroundMigrationJob.statuses['pending'] - ) - table(:background_migration_jobs).create!( - class_name: 'BackfillNamespaceTraversalIdsRoots', - arguments: [11, 14, 100], - status: Gitlab::Database::BackgroundMigrationJob.statuses['succeeded'] - ) - table(:background_migration_jobs).create!( - class_name: 'BackfillNamespaceTraversalIdsChildren', - arguments: [15, 19, 100], - status: Gitlab::Database::BackgroundMigrationJob.statuses['succeeded'] - ) - end - - it 'queues pending jobs' do - freeze_time do - migrate! - - expect(BackgroundMigrationWorker.jobs.length).to eq(2) - expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['BackfillNamespaceTraversalIdsRoots', [1, 4, 100]]) - expect(BackgroundMigrationWorker.jobs[0]['at']).to be_nil - expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['BackfillNamespaceTraversalIdsChildren', [5, 9, 100]]) - expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(RetryBackfillTraversalIds::DELAY_INTERVAL.from_now.to_f) - end - end - end -end diff --git a/spec/services/clusters/agents/refresh_authorization_service_spec.rb b/spec/services/clusters/agents/refresh_authorization_service_spec.rb index fa38bc202e7..51c054ddc98 100644 --- a/spec/services/clusters/agents/refresh_authorization_service_spec.rb +++ b/spec/services/clusters/agents/refresh_authorization_service_spec.rb @@ -2,17 +2,17 @@ require 'spec_helper' -RSpec.describe Clusters::Agents::RefreshAuthorizationService do +RSpec.describe Clusters::Agents::RefreshAuthorizationService, feature_category: :kubernetes_management do describe '#execute' do let_it_be(:root_ancestor) { create(:group) } let_it_be(:removed_group) { create(:group, parent: root_ancestor) } let_it_be(:modified_group) { create(:group, parent: root_ancestor) } - let_it_be(:added_group) { create(:group, parent: root_ancestor) } + let_it_be(:added_group) { create(:group, path: 'group-path-with-UPPERCASE', parent: root_ancestor) } let_it_be(:removed_project) { create(:project, namespace: root_ancestor) } let_it_be(:modified_project) { create(:project, namespace: root_ancestor) } - let_it_be(:added_project) { create(:project, namespace: root_ancestor) } + let_it_be(:added_project) { create(:project, path: 'project-path-with-UPPERCASE', namespace: root_ancestor) } let(:project) { create(:project, namespace: root_ancestor) } let(:agent) { create(:cluster_agent, project: project) } @@ -22,11 +22,13 @@ RSpec.describe Clusters::Agents::RefreshAuthorizationService do ci_access: { groups: [ { id: added_group.full_path, default_namespace: 'default' }, - { id: modified_group.full_path, default_namespace: 'new-namespace' } + # Uppercase path verifies case-insensitive matching. + { id: modified_group.full_path.upcase, default_namespace: 'new-namespace' } ], projects: [ { id: added_project.full_path, default_namespace: 'default' }, - { id: modified_project.full_path, default_namespace: 'new-namespace' } + # Uppercase path verifies case-insensitive matching. + { id: modified_project.full_path.upcase, default_namespace: 'new-namespace' } ] } }.deep_stringify_keys |