From 21c8f4814b6e48e21c9c5bbe1abfaa6bfc45fe2d Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 27 Oct 2022 03:10:53 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- Gemfile | 2 +- Gemfile.checksum | 16 ++-- Gemfile.lock | 4 +- doc/api/protected_branches.md | 23 +++++ doc/api/protected_tags.md | 3 + .../img/primary_identifier_changed_v15_6.png | Bin 0 -> 36767 bytes doc/development/sec/index.md | 85 ++++++++++++++++++ lib/api/entities/protected_ref_access.rb | 1 + .../14_analytics/performance_bar_spec.rb | 2 +- .../service_ping_default_enabled_spec.rb | 2 +- .../service_ping_disabled_spec.rb | 2 +- .../entities/protected_ref_access.json | 25 ++++++ .../api/schemas/protected_branch.json | 33 +++++++ .../api/schemas/protected_branches.json | 6 ++ spec/fixtures/api/schemas/protected_tag.json | 19 ++++ spec/fixtures/api/schemas/protected_tags.json | 6 ++ .../markdown_snapshot_spec_helper.js | 8 +- spec/requests/api/protected_branches_spec.rb | 12 ++- spec/requests/api/protected_tags_spec.rb | 8 +- 19 files changed, 236 insertions(+), 21 deletions(-) create mode 100644 doc/development/sec/img/primary_identifier_changed_v15_6.png create mode 100644 spec/fixtures/api/schemas/entities/protected_ref_access.json create mode 100644 spec/fixtures/api/schemas/protected_branch.json create mode 100644 spec/fixtures/api/schemas/protected_branches.json create mode 100644 spec/fixtures/api/schemas/protected_tag.json create mode 100644 spec/fixtures/api/schemas/protected_tags.json diff --git a/Gemfile b/Gemfile index 7901320f28f..6ea3242653a 100644 --- a/Gemfile +++ b/Gemfile @@ -510,7 +510,7 @@ gem 'kas-grpc', '~> 0.0.2' gem 'grpc', '~> 1.42.0' -gem 'google-protobuf', '~> 3.21', '>= 3.21.8' +gem 'google-protobuf', '~> 3.21', '>= 3.21.9' gem 'toml-rb', '~> 2.2.0' diff --git a/Gemfile.checksum b/Gemfile.checksum index c5fad96428c..f8b12ab681b 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -219,14 +219,14 @@ {"name":"gon","version":"6.4.0","platform":"ruby","checksum":"e3a618d659392890f1aa7db420f17c75fd7d35aeb5f8fe003697d02c4b88d2f0"}, {"name":"google-api-client","version":"0.50.0","platform":"ruby","checksum":"3ae45e972f293f3a66e53950ecc0fd350d85d6347c06a430bb971bd1ae5ad617"}, {"name":"google-cloud-env","version":"1.6.0","platform":"ruby","checksum":"6179acb946975892c7908748df5722a4ebadfc8cf5bb7b0d8d933ca67183fa15"}, -{"name":"google-protobuf","version":"3.21.8","platform":"java","checksum":"fed99be5fa3f5aafded120fb3efd1544a35cbbbc34d4dc6b84dfabe9fce044e0"}, -{"name":"google-protobuf","version":"3.21.8","platform":"ruby","checksum":"d1ff763c4fdf5744e5435a70b3a8100a1b112576aec8133ebee1a861a165f881"}, -{"name":"google-protobuf","version":"3.21.8","platform":"x64-mingw-ucrt","checksum":"1a3aed52e5279c914a7bcfe6339b586c169f64f4132c822af7ce3b837ff3a2ae"}, -{"name":"google-protobuf","version":"3.21.8","platform":"x64-mingw32","checksum":"ad868d1b7cc090a766bda2980a88a485f7348028aa22bbce6ddee011f9d39810"}, -{"name":"google-protobuf","version":"3.21.8","platform":"x86-linux","checksum":"76b0c65611ac96049cec2ab12735dd338e5901def03212c148991c2af3542c40"}, -{"name":"google-protobuf","version":"3.21.8","platform":"x86-mingw32","checksum":"94ad5bc5ef18e6b265b4b535b49fb49c7dc093b048df6ed5d9a09de032560a3b"}, -{"name":"google-protobuf","version":"3.21.8","platform":"x86_64-darwin","checksum":"9062431f13d4ff6597abd38177910828d339ff755a6609cc02d5963458f00479"}, -{"name":"google-protobuf","version":"3.21.8","platform":"x86_64-linux","checksum":"fed2e72269cd0bb1ed5bf372e0d7d9720a9ec749b9618868e98a7e6a09bef162"}, +{"name":"google-protobuf","version":"3.21.9","platform":"java","checksum":"8483ab2487170434f7a139d6534b3a166e4ec244a6fd8929f758d87abbb82fee"}, +{"name":"google-protobuf","version":"3.21.9","platform":"ruby","checksum":"5a656c159aa2c85008af7eab3f603cf22921b748e09438f6682dcf696d518adc"}, +{"name":"google-protobuf","version":"3.21.9","platform":"x64-mingw-ucrt","checksum":"7cb37b76241150212703f0ac582555f6fda1c7c66f58c1164667e783141e25fe"}, +{"name":"google-protobuf","version":"3.21.9","platform":"x64-mingw32","checksum":"54df7b9df435cc5c715261fbe8897fe03dd4b0e68e052aa0bb814c31bc66ef35"}, +{"name":"google-protobuf","version":"3.21.9","platform":"x86-linux","checksum":"11f28f344f6b6afa78fa0688379e39fbc86da4c199f04a51da7a29cf2db8205d"}, +{"name":"google-protobuf","version":"3.21.9","platform":"x86-mingw32","checksum":"a2dce43556196b6bb0fce2cf28df70fdca4255607fb9e1ffb7ee611953436a9a"}, +{"name":"google-protobuf","version":"3.21.9","platform":"x86_64-darwin","checksum":"9e948a08ee27cca8acf794c798db16d918ce503eae06525d7551dc05ac3324c0"}, +{"name":"google-protobuf","version":"3.21.9","platform":"x86_64-linux","checksum":"d4053012022f7bf47cd54c7c19416f600325e6cc1e1604a631c2fde69dd920a4"}, {"name":"googleapis-common-protos-types","version":"1.3.0","platform":"ruby","checksum":"c5411f3197cc3e02547ded1858303b1f830b4dc89c588c142ad6c8a231050671"}, {"name":"googleauth","version":"0.14.0","platform":"ruby","checksum":"4659b563d5b2727e775ba9231e75485c1b55ac8fc319e0bf1bc87d5e9705a632"}, {"name":"gpgme","version":"2.0.20","platform":"ruby","checksum":"fc194689cff40cd4ccafb3086031e930650b3efc15348bbfdf7a2f8b5a826f75"}, diff --git a/Gemfile.lock b/Gemfile.lock index 9a21b223d3e..6920fb22fc5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -619,7 +619,7 @@ GEM signet (~> 0.12) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - google-protobuf (3.21.8) + google-protobuf (3.21.9) googleapis-common-protos-types (1.3.0) google-protobuf (~> 3.14) googleauth (0.14.0) @@ -1644,7 +1644,7 @@ DEPENDENCIES gitlab_omniauth-ldap (~> 2.2.0) gon (~> 6.4.0) google-api-client (~> 0.33) - google-protobuf (~> 3.21, >= 3.21.8) + google-protobuf (~> 3.21, >= 3.21.9) gpgme (~> 2.0.19) grape (~> 1.5.2) grape-entity (~> 0.10.0) diff --git a/doc/api/protected_branches.md b/doc/api/protected_branches.md index 620cb0e0bae..8a96b2aa9ae 100644 --- a/doc/api/protected_branches.md +++ b/doc/api/protected_branches.md @@ -43,12 +43,14 @@ Example response: "name": "master", "push_access_levels": [ { + "id": 1, "access_level": 40, "access_level_description": "Maintainers" } ], "merge_access_levels": [ { + "id": 1, "access_level": 40, "access_level_description": "Maintainers" } @@ -61,12 +63,14 @@ Example response: "name": "release/*", "push_access_levels": [ { + "id": 1, "access_level": 40, "access_level_description": "Maintainers" } ], "merge_access_levels": [ { + "id": 1, "access_level": 40, "access_level_description": "Maintainers" } @@ -90,6 +94,7 @@ Example response: "name": "master", "push_access_levels": [ { + "id": 1, "access_level": 40, "user_id": null, "group_id": null, @@ -98,6 +103,7 @@ Example response: ], "merge_access_levels": [ { + "id": 1, "access_level": null, "user_id": null, "group_id": 1234, @@ -136,12 +142,14 @@ Example response: "name": "master", "push_access_levels": [ { + "id": 1, "access_level": 40, "access_level_description": "Maintainers" } ], "merge_access_levels": [ { + "id": 1, "access_level": 40, "access_level_description": "Maintainers" } @@ -162,6 +170,7 @@ Example response: "name": "master", "push_access_levels": [ { + "id": 1, "access_level": 40, "user_id": null, "group_id": null, @@ -170,6 +179,7 @@ Example response: ], "merge_access_levels": [ { + "id": 1, "access_level": null, "user_id": null, "group_id": 1234, @@ -215,18 +225,21 @@ Example response: "name": "*-stable", "push_access_levels": [ { + "id": 1, "access_level": 30, "access_level_description": "Developers + Maintainers" } ], "merge_access_levels": [ { + "id": 1, "access_level": 30, "access_level_description": "Developers + Maintainers" } ], "unprotect_access_levels": [ { + "id": 1, "access_level": 40, "access_level_description": "Maintainers" } @@ -247,6 +260,7 @@ Example response: "name": "*-stable", "push_access_levels": [ { + "id": 1, "access_level": 30, "user_id": null, "group_id": null, @@ -255,6 +269,7 @@ Example response: ], "merge_access_levels": [ { + "id": 1, "access_level": 30, "user_id": null, "group_id": null, @@ -263,6 +278,7 @@ Example response: ], "unprotect_access_levels": [ { + "id": 1, "access_level": 40, "user_id": null, "group_id": null, @@ -291,6 +307,7 @@ Example response: "name": "*-stable", "push_access_levels": [ { + "id": 1, "access_level": null, "user_id": 1, "group_id": null, @@ -299,6 +316,7 @@ Example response: ], "merge_access_levels": [ { + "id": 1, "access_level": 40, "user_id": null, "group_id": null, @@ -307,6 +325,7 @@ Example response: ], "unprotect_access_levels": [ { + "id": 1, "access_level": 40, "user_id": null, "group_id": null, @@ -348,6 +367,7 @@ Example response: "name": "master", "push_access_levels": [ { + "id": 1, "access_level": 30, "access_level_description": "Developers + Maintainers", "user_id": null, @@ -356,12 +376,14 @@ Example response: ], "merge_access_levels": [ { + "id": 1, "access_level": 30, "access_level_description": "Developers + Maintainers", "user_id": null, "group_id": null }, { + "id": 2, "access_level": 40, "access_level_description": "Maintainers", "user_id": null, @@ -370,6 +392,7 @@ Example response: ], "unprotect_access_levels": [ { + "id": 1, "access_level": 40, "access_level_description": "Maintainers", "user_id": null, diff --git a/doc/api/protected_tags.md b/doc/api/protected_tags.md index c8e7117e5a9..88b868a3965 100644 --- a/doc/api/protected_tags.md +++ b/doc/api/protected_tags.md @@ -41,6 +41,7 @@ Example response: "name": "release-1-0", "create_access_levels": [ { + "id":1, "access_level": 40, "access_level_description": "Maintainers" } @@ -75,6 +76,7 @@ Example response: "name": "release-1-0", "create_access_levels": [ { + "id": 1, "access_level": 40, "access_level_description": "Maintainers" } @@ -109,6 +111,7 @@ Example response: "name": "*-stable", "create_access_levels": [ { + "id": 1, "access_level": 30, "access_level_description": "Developers + Maintainers" } diff --git a/doc/development/sec/img/primary_identifier_changed_v15_6.png b/doc/development/sec/img/primary_identifier_changed_v15_6.png new file mode 100644 index 0000000000000000000000000000000000000000..6bdca5152f2634697c73f3e4165c4d27f0286667 GIT binary patch literal 36767 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 literal 0 HcmV?d00001 diff --git a/doc/development/sec/index.md b/doc/development/sec/index.md index c9805044e58..f988cbc595a 100644 --- a/doc/development/sec/index.md +++ b/doc/development/sec/index.md @@ -68,3 +68,88 @@ Depending on the context, the security reports may be stored either in the datab While CI/CD templates are the responsibility of the Verify section, many are critical to the Sec Section's feature usage. If you are working with CI/CD templates, please read the [development guide for GitLab CI/CD templates](../cicd/templates.md). + +## Importance of the primary identifier + +Within analyzer JSON reports, the [`identifiers` field](../integrations/secure.md#identifiers) contains a collection of types and categories by which +a vulnerability can be described (that is, a CWE family). + +The first item in the `identifiers` collection is known as the [primary identifier](../../user/application_security/terminology#primary-identifier), +a critical component to both describing and tracking vulnerabilities. + +In most other cases, the `identifiers` collection is unordered, where the remaining secondary identifiers act as metadata for grouping vulnerabilities +(see [Analyzer vulnerability translation](#analyzer-vulnerability-translation) below for the exception). + +Any time the primary identifier changes and a project pipeline is re-run, ingestion of the new report will “orphan” the previous DB record. +Because our processing logic relies on generating a delta of two different vulnerabilities, it can end up looking rather confusing. For example: + +[!Screenshot of primary identifier mismatch in MR widget](img/primary_identifier_changed_v15_6.png) + +After being [merged](../integrations/secure.md#tracking-and-merging-vulnerabilities), the previous vulnerability is listed as "remediated" and the introduced as ["detected"](../../user/application_security/vulnerabilities/index.md#vulnerability-status-values). + +### Guiding principles for ensuring primary identifier stability + +- A primary identifier should never change unless we have a compelling reason. +- Analyzer supporting vulnerability translation must include the legacy primary identifiers in a secondary position to prevent “orphaning” of results. +- Beyond the primary identifier, the order of secondary identifiers does not matter. +- The identifier is unique based on a combination of the `Type` and `Value` fields (see [identifier fingerprint](https://gitlab.com/gitlab-org/gitlab/-/blob/v15.5.1-ee/lib/gitlab/ci/reports/security/identifier.rb#L63)). +- If we change the primary identifier, rolling back analyzers to previous versions will not fix the orphaned results. The data previously ingested into our database is an artifact of previous jobs with few ways of automating data migrations. + +### Analyzer vulnerability translation + +In the case of SAST's semgrep analyzer, there is a secondary identifier of particular importance: the identifier linking the report’s vulnerability +to the legacy analyzer (that is, bandit or eslint). + +To [enable vulnerability translation](../../user/application_security/sast/analyzers.md#vulnerability-translation) +the semgrep analyzer relies on a secondary identifier exactly matching the primary identifier of the legacy analyzer. + +For example, when [`eslint`](https://gitlab.com/gitlab-org/security-products/analyzers/eslint) was previously used to generate vulnerability records, +the [`semgrep`](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) analyzer must produce an identifier collection containing the +original eslint primary identifier. + +Given the original `eslint` report: + +```json +{ + "version": "14.0.4", + "vulnerabilities": [ + { + "identifiers": [ + { + "type": "eslint_rule_id", + "name": "ESLint rule ID security/detect-eval-with-expression", + "value": "security/detect-eval-with-expression" + } + ] + } + ] +} +``` + +The corresponding semgrep report must contain the `eslint_rule_id`: + +```json +{ + "version": "14.0.4", + "vulnerabilities": [ + { + "identifiers": [ + { + "type": "semgrep_id", + "name": "eslint.detect-eval-with-expression", + "value": "eslint.detect-eval-with-expression", + "url": "https://semgrep.dev/r/gitlab.eslint.detect-eval-with-expression" + }, + { + "type": "eslint_rule_id", + "name": "ESLint rule ID security/detect-eval-with-expression", + "value": "security/detect-eval-with-expression" + } + ] + } + ] +} +``` + +[Tracking of vulnerabilities](../integrations/secure.md#tracking-and-merging-vulnerabilities) relies on a combination of the two identifiers +to remap DB records previously generated with the legacy analyzers to those generated with the new `semgrep` ones. diff --git a/lib/api/entities/protected_ref_access.rb b/lib/api/entities/protected_ref_access.rb index 443277e23cf..7b9b30d2385 100644 --- a/lib/api/entities/protected_ref_access.rb +++ b/lib/api/entities/protected_ref_access.rb @@ -3,6 +3,7 @@ module API module Entities class ProtectedRefAccess < Grape::Entity + expose :id expose :access_level expose :access_level_description do |protected_ref_access| protected_ref_access.humanize diff --git a/qa/qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb b/qa/qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb index 867c54102ae..6dfc58fbfea 100644 --- a/qa/qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb +++ b/qa/qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb @@ -2,7 +2,7 @@ module QA RSpec.describe 'Analytics' do - describe 'Performance bar display', :requires_admin, :skip_live_env do + describe 'Performance bar display', :requires_admin, :skip_live_env, product_group: :product_analytics do context 'when logged in as an admin user' do # performance metrics: pg, gitaly, redis, rugged (feature flagged), total (not always provided) let(:minimum_metrics_count) { 3 } diff --git a/qa/qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb b/qa/qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb index 7826aca3601..8e4b76cdb7c 100644 --- a/qa/qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb +++ b/qa/qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb @@ -2,7 +2,7 @@ module QA RSpec.describe 'Analytics' do - describe 'Service ping default enabled' do + describe 'Service ping default enabled', product_group: :product_intelligence do context 'when using default enabled from gitlab.yml config', :requires_admin, except: { job: 'review-qa-*' } do before do Flow::Login.sign_in_as_admin diff --git a/qa/qa/specs/features/browser_ui/14_analytics/service_ping_disabled_spec.rb b/qa/qa/specs/features/browser_ui/14_analytics/service_ping_disabled_spec.rb index 8b30d6a7ad7..e25bba97288 100644 --- a/qa/qa/specs/features/browser_ui/14_analytics/service_ping_disabled_spec.rb +++ b/qa/qa/specs/features/browser_ui/14_analytics/service_ping_disabled_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Analytics' do + RSpec.describe 'Analytics', product_group: :product_intelligence do describe 'Service ping disabled', :orchestrated, :service_ping_disabled, :requires_admin do context 'when disabled from gitlab.yml config' do before do diff --git a/spec/fixtures/api/schemas/entities/protected_ref_access.json b/spec/fixtures/api/schemas/entities/protected_ref_access.json new file mode 100644 index 00000000000..144852e1558 --- /dev/null +++ b/spec/fixtures/api/schemas/entities/protected_ref_access.json @@ -0,0 +1,25 @@ +{ + "type": "object", + "required": [ + "id", + "access_level", + "access_level_description" + ], + "properties": { + "id": { + "type": "integer" + }, + "access_level": { + "type": [ + "integer", + "null" + ] + }, + "access_level_description": { + "type": [ + "string", + "null" + ] + } + } +} diff --git a/spec/fixtures/api/schemas/protected_branch.json b/spec/fixtures/api/schemas/protected_branch.json new file mode 100644 index 00000000000..4ad5dbe2313 --- /dev/null +++ b/spec/fixtures/api/schemas/protected_branch.json @@ -0,0 +1,33 @@ +{ + "type": "object", + "required": [ + "id", + "name", + "push_access_levels", + "merge_access_levels", + "allow_force_push" + ], + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "push_access_levels": { + "type": "array", + "items": { + "$ref": "entities/protected_ref_access.json" + } + }, + "merge_access_levels": { + "type": "array", + "items": { + "$ref": "entities/protected_ref_access.json" + } + }, + "allow_force_push": { + "type": "boolean" + } + } +} diff --git a/spec/fixtures/api/schemas/protected_branches.json b/spec/fixtures/api/schemas/protected_branches.json new file mode 100644 index 00000000000..c87b3b153a9 --- /dev/null +++ b/spec/fixtures/api/schemas/protected_branches.json @@ -0,0 +1,6 @@ +{ + "type": "array", + "items": { + "$ref": "protected_branch.json" + } +} diff --git a/spec/fixtures/api/schemas/protected_tag.json b/spec/fixtures/api/schemas/protected_tag.json new file mode 100644 index 00000000000..c5aaf0f0cba --- /dev/null +++ b/spec/fixtures/api/schemas/protected_tag.json @@ -0,0 +1,19 @@ +{ + "type": "object", + "required": [ + "name", + "create_access_levels" + ], + "properties": { + "name": { + "type": "string" + }, + "create_access_levels": { + "type": "array", + "items": { + "$ref": "entities/protected_ref_access.json" + } + }, + "additionalProperties": false + } +} diff --git a/spec/fixtures/api/schemas/protected_tags.json b/spec/fixtures/api/schemas/protected_tags.json new file mode 100644 index 00000000000..731d0368a09 --- /dev/null +++ b/spec/fixtures/api/schemas/protected_tags.json @@ -0,0 +1,6 @@ +{ + "type": "array", + "items": { + "$ref": "protected_tag.json" + } +} diff --git a/spec/frontend/content_editor/markdown_snapshot_spec_helper.js b/spec/frontend/content_editor/markdown_snapshot_spec_helper.js index 05fa8e6a6b2..b85e64e71a5 100644 --- a/spec/frontend/content_editor/markdown_snapshot_spec_helper.js +++ b/spec/frontend/content_editor/markdown_snapshot_spec_helper.js @@ -34,11 +34,11 @@ export const describeMarkdownSnapshots = (description, glfmSpecificationDir) => path.join(glfmSpecificationDir, 'input', 'gitlab_flavored_markdown'), 'glfm_example_status.yml', ); - const glfmExampleSnapshotsDir = path.join(glfmSpecificationDir, 'example_snapshots'); - const markdownExamples = loadExamples(glfmExampleSnapshotsDir, 'markdown.yml'); - const expectedHtmlExamples = loadExamples(glfmExampleSnapshotsDir, 'html.yml'); + const outputExampleSnapshotsDir = path.join(glfmSpecificationDir, 'output_example_snapshots'); + const markdownExamples = loadExamples(outputExampleSnapshotsDir, 'markdown.yml'); + const expectedHtmlExamples = loadExamples(outputExampleSnapshotsDir, 'html.yml'); const expectedProseMirrorJsonExamples = loadExamples( - glfmExampleSnapshotsDir, + outputExampleSnapshotsDir, 'prosemirror_json.yml', ); diff --git a/spec/requests/api/protected_branches_spec.rb b/spec/requests/api/protected_branches_spec.rb index 9f10eb1bb9f..09a755cf79d 100644 --- a/spec/requests/api/protected_branches_spec.rb +++ b/spec/requests/api/protected_branches_spec.rb @@ -29,8 +29,7 @@ RSpec.describe API::ProtectedBranches do expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers - expect(json_response).to be_an Array - + expect(response).to match_response_schema('protected_branches') protected_branch_names = json_response.map { |x| x['name'] } expect(protected_branch_names).to match_array(expected_branch_names) end @@ -71,6 +70,7 @@ RSpec.describe API::ProtectedBranches do get api(route, user) expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('protected_branch') expect(json_response['name']).to eq(branch_name) expect(json_response['allow_force_push']).to eq(false) expect(json_response['push_access_levels'][0]['access_level']).to eq(::Gitlab::Access::MAINTAINER) @@ -130,6 +130,7 @@ RSpec.describe API::ProtectedBranches do post post_endpoint, params: { name: branch_name } expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('protected_branch') expect(json_response['name']).to eq(branch_name) expect(json_response['allow_force_push']).to eq(false) expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::MAINTAINER) @@ -140,6 +141,7 @@ RSpec.describe API::ProtectedBranches do post post_endpoint, params: { name: branch_name, push_access_level: 30 } expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('protected_branch') expect(json_response['name']).to eq(branch_name) expect(json_response['allow_force_push']).to eq(false) expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::DEVELOPER) @@ -150,6 +152,7 @@ RSpec.describe API::ProtectedBranches do post post_endpoint, params: { name: branch_name, merge_access_level: 30 } expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('protected_branch') expect(json_response['name']).to eq(branch_name) expect(json_response['allow_force_push']).to eq(false) expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::MAINTAINER) @@ -160,6 +163,7 @@ RSpec.describe API::ProtectedBranches do post post_endpoint, params: { name: branch_name, push_access_level: 30, merge_access_level: 30 } expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('protected_branch') expect(json_response['name']).to eq(branch_name) expect(json_response['allow_force_push']).to eq(false) expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::DEVELOPER) @@ -170,6 +174,7 @@ RSpec.describe API::ProtectedBranches do post post_endpoint, params: { name: branch_name, push_access_level: 0 } expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('protected_branch') expect(json_response['name']).to eq(branch_name) expect(json_response['allow_force_push']).to eq(false) expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::NO_ACCESS) @@ -180,6 +185,7 @@ RSpec.describe API::ProtectedBranches do post post_endpoint, params: { name: branch_name, merge_access_level: 0 } expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('protected_branch') expect(json_response['name']).to eq(branch_name) expect(json_response['allow_force_push']).to eq(false) expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::MAINTAINER) @@ -190,6 +196,7 @@ RSpec.describe API::ProtectedBranches do post post_endpoint, params: { name: branch_name, push_access_level: 0, merge_access_level: 0 } expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('protected_branch') expect(json_response['name']).to eq(branch_name) expect(json_response['allow_force_push']).to eq(false) expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::NO_ACCESS) @@ -200,6 +207,7 @@ RSpec.describe API::ProtectedBranches do post post_endpoint, params: { name: branch_name, allow_force_push: true } expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('protected_branch') expect(json_response['name']).to eq(branch_name) expect(json_response['allow_force_push']).to eq(true) expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::MAINTAINER) diff --git a/spec/requests/api/protected_tags_spec.rb b/spec/requests/api/protected_tags_spec.rb index 84b7df86f31..f1db39ac204 100644 --- a/spec/requests/api/protected_tags_spec.rb +++ b/spec/requests/api/protected_tags_spec.rb @@ -22,7 +22,7 @@ RSpec.describe API::ProtectedTags do expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers - expect(json_response).to be_an Array + expect(response).to match_response_schema('protected_tags') protected_tag_names = json_response.map { |x| x['name'] } expected_tags_names = project.protected_tags.map { |x| x['name'] } @@ -57,6 +57,7 @@ RSpec.describe API::ProtectedTags do get api(route, user) expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('protected_tag') expect(json_response['name']).to eq(tag_name) expect(json_response['create_access_levels'][0]['access_level']).to eq(::Gitlab::Access::MAINTAINER) end @@ -108,6 +109,7 @@ RSpec.describe API::ProtectedTags do post api("/projects/#{project.id}/protected_tags", user), params: { name: tag_name } expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('protected_tag') expect(json_response['name']).to eq(tag_name) expect(json_response['create_access_levels'][0]['access_level']).to eq(Gitlab::Access::MAINTAINER) end @@ -117,6 +119,7 @@ RSpec.describe API::ProtectedTags do params: { name: tag_name, create_access_level: 30 } expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('protected_tag') expect(json_response['name']).to eq(tag_name) expect(json_response['create_access_levels'][0]['access_level']).to eq(Gitlab::Access::DEVELOPER) end @@ -126,6 +129,7 @@ RSpec.describe API::ProtectedTags do params: { name: tag_name, create_access_level: 0 } expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('protected_tag') expect(json_response['name']).to eq(tag_name) expect(json_response['create_access_levels'][0]['access_level']).to eq(Gitlab::Access::NO_ACCESS) end @@ -142,6 +146,7 @@ RSpec.describe API::ProtectedTags do post api("/projects/#{project2.id}/protected_tags", user), params: { name: protected_name } expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('protected_tag') expect(json_response['name']).to eq(protected_name) end @@ -152,6 +157,7 @@ RSpec.describe API::ProtectedTags do post api("/projects/#{project.id}/protected_tags", user), params: { name: tag_name } expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('protected_tag') expect(json_response['name']).to eq(tag_name) expect(json_response['create_access_levels'][0]['access_level']).to eq(Gitlab::Access::MAINTAINER) end