Commit f19b467f authored by Tiger Watson's avatar Tiger Watson

Merge branch '267511-approval-gate-rule-type' into 'master'

Add Approval Gate Rules [RUN ALL RSPEC] [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!54002
parents 3ae9b79a d8dea376
---
title: Create ExternalApprovalRule table and associations
merge_request: 54002
author:
type: added
# frozen_string_literal: true
class CreateExternalApprovalRules < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
DOWNTIME = false
def up
create_table_with_constraints :external_approval_rules, if_not_exists: true do |t|
t.references :project, foreign_key: { on_delete: :cascade }, null: false, index: false
t.timestamps_with_timezone
t.text :external_url, null: false
t.text_limit :external_url, 255
t.text :name, null: false
t.text_limit :name, 255
t.index([:project_id, :name],
unique: true,
name: 'idx_on_external_approval_rules_project_id_name')
t.index([:project_id, :external_url],
unique: true,
name: 'idx_on_external_approval_rules_project_id_external_url')
end
create_table :external_approval_rules_protected_branches do |t|
t.bigint :external_approval_rule_id, null: false, index: { name: 'idx_eaprpb_external_approval_rule_id' }
t.bigint :protected_branch_id, null: false
t.index([:protected_branch_id, :external_approval_rule_id],
unique: true,
name: 'idx_protected_branch_id_external_approval_rule_id')
end
end
def down
with_lock_retries do
drop_table :external_approval_rules_protected_branches, force: :cascade, if_exists: true
end
with_lock_retries do
drop_table :external_approval_rules, force: :cascade, if_exists: true
end
end
end
# frozen_string_literal: true
class AddForeignKeyToExternalApprovalRules < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :external_approval_rules_protected_branches, :external_approval_rules, column: :external_approval_rule_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :external_approval_rules_protected_branches, column: :external_approval_rule_id
end
end
end
# frozen_string_literal: true
class AddForeignKeyToExternalApprovalRulesProtectedBranches < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :external_approval_rules_protected_branches, :protected_branches, column: :protected_branch_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :external_approval_rules_protected_branches, column: :protected_branch_id
end
end
end
a24354264df3c12411af040903d26faf298f06a7334ef118f87b3e88b683b326
\ No newline at end of file
99ee6773319af0fa7a1dfef92f67cc95141c892bf7adcf500d46adc1ebd4c70f
\ No newline at end of file
991041c8d3092175165834a988eb32141e49d7785cda756c8a78170b4af6db64
\ No newline at end of file
...@@ -12349,6 +12349,41 @@ CREATE SEQUENCE experiments_id_seq ...@@ -12349,6 +12349,41 @@ CREATE SEQUENCE experiments_id_seq
ALTER SEQUENCE experiments_id_seq OWNED BY experiments.id; ALTER SEQUENCE experiments_id_seq OWNED BY experiments.id;
CREATE TABLE external_approval_rules (
id bigint NOT NULL,
project_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
external_url text NOT NULL,
name text NOT NULL,
CONSTRAINT check_1c64b53ea5 CHECK ((char_length(name) <= 255)),
CONSTRAINT check_b634ca168d CHECK ((char_length(external_url) <= 255))
);
CREATE SEQUENCE external_approval_rules_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE external_approval_rules_id_seq OWNED BY external_approval_rules.id;
CREATE TABLE external_approval_rules_protected_branches (
id bigint NOT NULL,
external_approval_rule_id bigint NOT NULL,
protected_branch_id bigint NOT NULL
);
CREATE SEQUENCE external_approval_rules_protected_branches_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE external_approval_rules_protected_branches_id_seq OWNED BY external_approval_rules_protected_branches.id;
CREATE TABLE external_pull_requests ( CREATE TABLE external_pull_requests (
id bigint NOT NULL, id bigint NOT NULL,
created_at timestamp with time zone NOT NULL, created_at timestamp with time zone NOT NULL,
...@@ -18995,6 +19030,10 @@ ALTER TABLE ONLY experiment_users ALTER COLUMN id SET DEFAULT nextval('experimen ...@@ -18995,6 +19030,10 @@ ALTER TABLE ONLY experiment_users ALTER COLUMN id SET DEFAULT nextval('experimen
ALTER TABLE ONLY experiments ALTER COLUMN id SET DEFAULT nextval('experiments_id_seq'::regclass); ALTER TABLE ONLY experiments ALTER COLUMN id SET DEFAULT nextval('experiments_id_seq'::regclass);
ALTER TABLE ONLY external_approval_rules ALTER COLUMN id SET DEFAULT nextval('external_approval_rules_id_seq'::regclass);
ALTER TABLE ONLY external_approval_rules_protected_branches ALTER COLUMN id SET DEFAULT nextval('external_approval_rules_protected_branches_id_seq'::regclass);
ALTER TABLE ONLY external_pull_requests ALTER COLUMN id SET DEFAULT nextval('external_pull_requests_id_seq'::regclass); ALTER TABLE ONLY external_pull_requests ALTER COLUMN id SET DEFAULT nextval('external_pull_requests_id_seq'::regclass);
ALTER TABLE ONLY feature_gates ALTER COLUMN id SET DEFAULT nextval('feature_gates_id_seq'::regclass); ALTER TABLE ONLY feature_gates ALTER COLUMN id SET DEFAULT nextval('feature_gates_id_seq'::regclass);
...@@ -20219,6 +20258,12 @@ ALTER TABLE ONLY experiment_users ...@@ -20219,6 +20258,12 @@ ALTER TABLE ONLY experiment_users
ALTER TABLE ONLY experiments ALTER TABLE ONLY experiments
ADD CONSTRAINT experiments_pkey PRIMARY KEY (id); ADD CONSTRAINT experiments_pkey PRIMARY KEY (id);
ALTER TABLE ONLY external_approval_rules
ADD CONSTRAINT external_approval_rules_pkey PRIMARY KEY (id);
ALTER TABLE ONLY external_approval_rules_protected_branches
ADD CONSTRAINT external_approval_rules_protected_branches_pkey PRIMARY KEY (id);
ALTER TABLE ONLY external_pull_requests ALTER TABLE ONLY external_pull_requests
ADD CONSTRAINT external_pull_requests_pkey PRIMARY KEY (id); ADD CONSTRAINT external_pull_requests_pkey PRIMARY KEY (id);
...@@ -21317,6 +21362,8 @@ CREATE INDEX idx_container_repositories_on_exp_cleanup_status_and_start_date ON ...@@ -21317,6 +21362,8 @@ CREATE INDEX idx_container_repositories_on_exp_cleanup_status_and_start_date ON
CREATE INDEX idx_deployment_clusters_on_cluster_id_and_kubernetes_namespace ON deployment_clusters USING btree (cluster_id, kubernetes_namespace); CREATE INDEX idx_deployment_clusters_on_cluster_id_and_kubernetes_namespace ON deployment_clusters USING btree (cluster_id, kubernetes_namespace);
CREATE INDEX idx_eaprpb_external_approval_rule_id ON external_approval_rules_protected_branches USING btree (external_approval_rule_id);
CREATE UNIQUE INDEX idx_environment_merge_requests_unique_index ON deployment_merge_requests USING btree (environment_id, merge_request_id); CREATE UNIQUE INDEX idx_environment_merge_requests_unique_index ON deployment_merge_requests USING btree (environment_id, merge_request_id);
CREATE INDEX idx_geo_con_rep_updated_events_on_container_repository_id ON geo_container_repository_updated_events USING btree (container_repository_id); CREATE INDEX idx_geo_con_rep_updated_events_on_container_repository_id ON geo_container_repository_updated_events USING btree (container_repository_id);
...@@ -21357,6 +21404,10 @@ CREATE INDEX idx_mr_cc_diff_files_on_mr_cc_id_and_sha ON merge_request_context_c ...@@ -21357,6 +21404,10 @@ CREATE INDEX idx_mr_cc_diff_files_on_mr_cc_id_and_sha ON merge_request_context_c
CREATE UNIQUE INDEX idx_on_compliance_management_frameworks_namespace_id_name ON compliance_management_frameworks USING btree (namespace_id, name); CREATE UNIQUE INDEX idx_on_compliance_management_frameworks_namespace_id_name ON compliance_management_frameworks USING btree (namespace_id, name);
CREATE UNIQUE INDEX idx_on_external_approval_rules_project_id_external_url ON external_approval_rules USING btree (project_id, external_url);
CREATE UNIQUE INDEX idx_on_external_approval_rules_project_id_name ON external_approval_rules USING btree (project_id, name);
CREATE INDEX idx_packages_build_infos_on_package_id ON packages_build_infos USING btree (package_id); CREATE INDEX idx_packages_build_infos_on_package_id ON packages_build_infos USING btree (package_id);
CREATE INDEX idx_packages_debian_group_component_files_on_architecture_id ON packages_debian_group_component_files USING btree (architecture_id); CREATE INDEX idx_packages_debian_group_component_files_on_architecture_id ON packages_debian_group_component_files USING btree (architecture_id);
...@@ -21385,6 +21436,8 @@ CREATE INDEX idx_projects_id_created_at_disable_overriding_approvers_true ON pro ...@@ -21385,6 +21436,8 @@ CREATE INDEX idx_projects_id_created_at_disable_overriding_approvers_true ON pro
CREATE INDEX idx_projects_on_repository_storage_last_repository_updated_at ON projects USING btree (id, repository_storage, last_repository_updated_at); CREATE INDEX idx_projects_on_repository_storage_last_repository_updated_at ON projects USING btree (id, repository_storage, last_repository_updated_at);
CREATE UNIQUE INDEX idx_protected_branch_id_external_approval_rule_id ON external_approval_rules_protected_branches USING btree (protected_branch_id, external_approval_rule_id);
CREATE INDEX idx_repository_states_on_last_repository_verification_ran_at ON project_repository_states USING btree (project_id, last_repository_verification_ran_at) WHERE ((repository_verification_checksum IS NOT NULL) AND (last_repository_verification_failure IS NULL)); CREATE INDEX idx_repository_states_on_last_repository_verification_ran_at ON project_repository_states USING btree (project_id, last_repository_verification_ran_at) WHERE ((repository_verification_checksum IS NOT NULL) AND (last_repository_verification_failure IS NULL));
CREATE INDEX idx_repository_states_on_last_wiki_verification_ran_at ON project_repository_states USING btree (project_id, last_wiki_verification_ran_at) WHERE ((wiki_verification_checksum IS NOT NULL) AND (last_wiki_verification_failure IS NULL)); CREATE INDEX idx_repository_states_on_last_wiki_verification_ran_at ON project_repository_states USING btree (project_id, last_wiki_verification_ran_at) WHERE ((wiki_verification_checksum IS NOT NULL) AND (last_wiki_verification_failure IS NULL));
...@@ -24695,6 +24748,12 @@ ALTER TABLE ONLY issues ...@@ -24695,6 +24748,12 @@ ALTER TABLE ONLY issues
ALTER TABLE ONLY issue_links ALTER TABLE ONLY issue_links
ADD CONSTRAINT fk_c900194ff2 FOREIGN KEY (source_id) REFERENCES issues(id) ON DELETE CASCADE; ADD CONSTRAINT fk_c900194ff2 FOREIGN KEY (source_id) REFERENCES issues(id) ON DELETE CASCADE;
ALTER TABLE ONLY external_approval_rules_protected_branches
ADD CONSTRAINT fk_c9a037a926 FOREIGN KEY (external_approval_rule_id) REFERENCES external_approval_rules(id) ON DELETE CASCADE;
ALTER TABLE ONLY external_approval_rules_protected_branches
ADD CONSTRAINT fk_ca2ffb55e6 FOREIGN KEY (protected_branch_id) REFERENCES protected_branches(id) ON DELETE CASCADE;
ALTER TABLE ONLY experiment_subjects ALTER TABLE ONLY experiment_subjects
ADD CONSTRAINT fk_ccc28f8ceb FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; ADD CONSTRAINT fk_ccc28f8ceb FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
...@@ -26309,6 +26368,9 @@ ALTER TABLE ONLY ci_job_variables ...@@ -26309,6 +26368,9 @@ ALTER TABLE ONLY ci_job_variables
ALTER TABLE ONLY packages_nuget_metadata ALTER TABLE ONLY packages_nuget_metadata
ADD CONSTRAINT fk_rails_fc0c19f5b4 FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_fc0c19f5b4 FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
ALTER TABLE ONLY external_approval_rules
ADD CONSTRAINT fk_rails_fd4f9ac573 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY experiment_users ALTER TABLE ONLY experiment_users
ADD CONSTRAINT fk_rails_fd805f771a FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_fd805f771a FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
...@@ -567,6 +567,91 @@ PUT /projects/:id/approvers ...@@ -567,6 +567,91 @@ PUT /projects/:id/approvers
} }
``` ```
## External Project-level MR approvals **(ULTIMATE)**
Configuration for approvals on a specific Merge Request which makes a call to an external HTTP resource.
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3869) in GitLab 13.10.
> - It's [deployed behind a feature flag](../user/feature_flags.md), disabled by default.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-external-project-level-mr-approvals). **(ULTIMATE SELF)**
### Get project external approval rules **(ULTIMATE)**
You can request information about a project's external approval rules using the following endpoint:
```plaintext
GET /projects/:id/external_approval_rules
```
**Parameters:**
| Attribute | Type | Required | Description |
|---------------------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
```json
[
{
"id": 1,
"name": "Compliance Check",
"project_id": 6,
"external_url": "https://gitlab.com/example/test.json",
"protected_branches": [
{
"id": 14,
"project_id": 6,
"name": "master",
"created_at": "2020-10-12T14:04:50.787Z",
"updated_at": "2020-10-12T14:04:50.787Z",
"code_owner_approval_required": false
}
]
}
]
```
### Create external approval rule **(ULTIMATE)**
You can create a new external approval rule for a project using the following endpoint:
```plaintext
POST /projects/:id/external_approval_rules
```
| Attribute | Type | Required | Description |
|------------------------|----------------|----------|----------------------------------------------------|
| `id` | integer | yes | The ID of a project |
| `name` | string | yes | Display name of approval rule |
| `external_url` | string | yes | URL of external approval resource |
| `protected_branch_ids` | array<Integer> | no | The ids of protected branches to scope the rule by |
### Enable or disable External Project-level MR approvals **(ULTIMATE SELF)**
Enable or disable External Project-level MR approvals is under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../user/feature_flags.md)
can enable it.
To enable it:
```ruby
# For the instance
Feature.enable(:ff_compliance_approval_gates)
# For a single project
Feature.enable(:ff_compliance_approval_gates, Project.find(<project id>))
```
To disable it:
```ruby
# For the instance
Feature.disable(:ff_compliance_approval_gates)
# For a single project
Feature.disable(:ff_compliance_approval_gates, Project.find(<project id>))
```
## Merge Request-level MR approvals ## Merge Request-level MR approvals
Configuration for approvals on a specific Merge Request. Must be authenticated for all endpoints. Configuration for approvals on a specific Merge Request. Must be authenticated for all endpoints.
......
# frozen_string_literal: true
module ApprovalRules
class ExternalApprovalRule < ApplicationRecord
self.table_name = 'external_approval_rules'
scope :with_api_entity_associations, -> { preload(:protected_branches) }
belongs_to :project
has_and_belongs_to_many :protected_branches
validates :external_url, presence: true, uniqueness: { scope: :project_id }, addressable_url: true
validates :name, uniqueness: { scope: :project_id }, presence: true
end
end
...@@ -62,6 +62,7 @@ module EE ...@@ -62,6 +62,7 @@ module EE
includes(:protected_branches).reject { |rule| rule.applies_to_branch?(branch) } includes(:protected_branches).reject { |rule| rule.applies_to_branch?(branch) }
end end
end end
has_many :external_approval_rules, class_name: 'ApprovalRules::ExternalApprovalRule'
has_many :approval_merge_request_rules, through: :merge_requests, source: :approval_rules has_many :approval_merge_request_rules, through: :merge_requests, source: :approval_rules
has_many :audit_events, as: :entity has_many :audit_events, as: :entity
has_many :path_locks has_many :path_locks
......
...@@ -137,6 +137,7 @@ class License < ApplicationRecord ...@@ -137,6 +137,7 @@ class License < ApplicationRecord
api_fuzzing api_fuzzing
auto_rollback auto_rollback
cilium_alerts cilium_alerts
compliance_approval_gates
container_scanning container_scanning
coverage_fuzzing coverage_fuzzing
credentials_inventory credentials_inventory
......
# frozen_string_literal: true
module ExternalApprovalRules
class CreateService < BaseContainerService
def execute
return ServiceResponse.error(message: 'Failed to create rule', payload: { errors: ['Not allowed'] }, http_status: :unauthorized) unless current_user.can?(:admin_project, container)
rule = container.external_approval_rules.new(name: params[:name],
project: container,
external_url: params[:external_url],
protected_branch_ids: params[:protected_branch_ids])
if rule.save
ServiceResponse.success(payload: { rule: rule })
else
ServiceResponse.error(message: 'Failed to create rule', payload: { errors: rule.errors.full_messages }, http_status: :unprocessable_entity)
end
end
end
end
---
name: ff_compliance_approval_gates
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54002
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/320783
milestone: '13.10'
type: development
group: group::compliance
default_enabled: false
# frozen_string_literal: true
module API
module Entities
class ExternalApprovalRule < Grape::Entity
expose :id
expose :name
expose :project_id
expose :external_url
expose :protected_branches, using: ::API::Entities::ProtectedBranch
end
end
end
# frozen_string_literal: true
module API
class ExternalApprovalRules < ::API::Base
include PaginationParams
feature_category :source_code_management
before { authenticate! }
before { user_project }
before { check_feature_enabled!(@project) }
helpers do
def check_feature_enabled!(project)
unauthorized! unless project.feature_available?(:compliance_approval_gates) &&
Feature.enabled?(:ff_compliance_approval_gates, project)
end
end
resource :projects do
segment ':id/external_approval_rules' do
params do
requires :id, type: Integer, desc: 'The ID of the project to associate the rule with'
requires :name, type: String, desc: 'The approval rule\'s name'
requires :external_url, type: String, desc: 'The URL to notify when MR receives new commits'
optional :protected_branch_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The protected branch ids for this rule'
use :pagination
end
desc 'Create a new external approval rule' do
success ::API::Entities::ExternalApprovalRule
detail 'This feature is gated by the :ff_compliance_approval_gates feature flag.'
end
post do
service = ::ExternalApprovalRules::CreateService.new(container: @project,
current_user: current_user,
params: declared(params, include_missing: false)).execute
if service.success?
present service.payload[:rule], with: ::API::Entities::ExternalApprovalRule
else
render_api_error!(service.payload[:errors], service.http_status)
end
end
desc 'List project\'s external approval rules' do
detail 'This feature is gated by the :ff_compliance_approval_gates feature flag.'
end
get do
unauthorized! unless current_user.can?(:admin_project, @project)
present paginate(@project.external_approval_rules), with: ::API::Entities::ExternalApprovalRule
end
end
end
end
end
...@@ -12,6 +12,7 @@ module EE ...@@ -12,6 +12,7 @@ module EE
mount ::API::AuditEvents mount ::API::AuditEvents
mount ::API::ProjectApprovalRules mount ::API::ProjectApprovalRules
mount ::API::ExternalApprovalRules
mount ::API::ProjectApprovalSettings mount ::API::ProjectApprovalSettings
mount ::API::EpicIssues mount ::API::EpicIssues
mount ::API::EpicLinks mount ::API::EpicLinks
......
# frozen_string_literal: true
FactoryBot.define do
factory :external_approval_rule, class: 'ApprovalRules::ExternalApprovalRule' do
project
external_url { "https://testurl.example.test" }
sequence :name do |i|
"rule #{i}"
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ApprovalRules::ExternalApprovalRule, type: :model do
subject { build(:external_approval_rule) }
describe 'Associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to have_and_belong_to_many(:protected_branches) }
end
describe 'Validations' do
it { is_expected.to validate_presence_of(:external_url) }
it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) }
it { is_expected.to validate_uniqueness_of(:external_url).scoped_to(:project_id) }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::API::ExternalApprovalRules do
using RSpec::Parameterized::TableSyntax
let_it_be(:project) { create(:project) }
describe 'GET projects/:id/external_approval_rules' do
let_it_be(:rule) { create(:external_approval_rule, project: project) }
let_it_be(:protected_branches) { create_list(:protected_branch, 3, project: project) }
before_all do
create(:external_approval_rule)
end
it 'responds with expected JSON' do
stub_licensed_features(compliance_approval_gates: true)
get api("/projects/#{project.id}/external_approval_rules", project.owner)
first_result = json_response.dig(0)
expect(json_response.size).to eq(1)
expect(first_result['id']).not_to be_nil
expect(first_result['name']).to eq(rule.name)
expect(first_result['external_url']).to eq(rule.external_url)
end
context 'when feature is disabled, unlicensed or user has permission' do
where(:licensed, :flag, :project_owner, :status) do
false | false | false | :not_found
false | false | true | :unauthorized
false | true | true | :unauthorized
false | true | false | :not_found
true | false | false | :not_found
true | false | true | :unauthorized
true | true | false | :not_found
true | true | true | :success
end
with_them do
before do
stub_feature_flags(ff_compliance_approval_gates: flag)
stub_licensed_features(compliance_approval_gates: licensed)
end
it 'returns the correct status code' do
get api("/projects/#{project.id}/external_approval_rules", (project_owner ? project.owner : build(:user)))
expect(response).to have_gitlab_http_status(status)
end
end
end
end
describe 'POST projects/:id/external_approval_rules' do
context 'successfully creating new external approval rule' do
before do
stub_feature_flags(ff_compliance_approval_gates: true)
stub_licensed_features(compliance_approval_gates: true)
end
subject do
post api("/projects/#{project.id}/external_approval_rules", project.owner), params: attributes_for(:external_approval_rule)
end
it 'creates a new external approval rule' do
expect { subject }.to change { ApprovalRules::ExternalApprovalRule.count }.by(1)
end
context 'with protected branches' do
let_it_be(:protected_branch) { create(:protected_branch, project: project) }
let(:params) do
{ name: 'New rule', external_url: 'https://gitlab.com/test/example.json', protected_branch_ids: protected_branch.id }
end
subject do
post api("/projects/#{project.id}/external_approval_rules", project.owner), params: params
end
it 'returns expected status code' do
subject
expect(response).to have_gitlab_http_status(:created)
end
it 'creates protected branch records' do
subject
expect(ApprovalRules::ExternalApprovalRule.last.protected_branches.count).to eq 1
end
it 'responds with expected JSON' do
subject
expect(json_response['id']).not_to be_nil
expect(json_response['name']).to eq('New rule')
expect(json_response['external_url']).to eq('https://gitlab.com/test/example.json')
expect(json_response['protected_branches'].size).to eq(1)
end
end
end
context 'when feature is disabled, unlicensed or user has permission' do
where(:licensed, :flag, :project_owner, :status) do
false | false | false | :not_found
false | false | true | :unauthorized
false | true | true | :unauthorized
false | true | false | :not_found
true | false | false | :not_found
true | false | true | :unauthorized
true | true | false | :not_found
true | true | true | :created
end
with_them do
before do
stub_feature_flags(ff_compliance_approval_gates: flag)
stub_licensed_features(compliance_approval_gates: licensed)
end
it 'returns the correct status code' do
post api("/projects/#{project.id}/external_approval_rules", (project_owner ? project.owner : build(:user))), params: attributes_for(:external_approval_rule)
expect(response).to have_gitlab_http_status(status)
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ExternalApprovalRules::CreateService do
let_it_be(:project) { create(:project) }
let_it_be(:protected_branch) { create(:protected_branch, project: project) }
let(:user) { project.owner }
let(:params) do
{
name: 'Test',
external_url: 'https://external_url.text/hello.json',
protected_branch_ids: [protected_branch.id]
}
end
subject { described_class.new(container: project, current_user: user, params: params).execute }
context 'parameters are invalid' do
let(:params) { { external_url: 'external_url.text/hello.json', name: 'test' } }
it 'is unsuccessful' do
expect(subject.success?).to be false
end
it 'does not create a new rule' do
expect { subject }.not_to change { ApprovalRules::ExternalApprovalRule.count }
end
end
context 'user not permitted to create approval rule' do
let_it_be(:user) { create(:user) }
it 'is unsuccessful' do
expect(subject.error?).to be true
end
it 'does not create a new rule' do
expect { subject }.not_to change { ApprovalRules::ExternalApprovalRule.count }
end
it 'responds with the expected errors' do
expect(subject.message).to eq('Failed to create rule')
expect(subject.payload[:errors]).to contain_exactly 'Not allowed'
expect(subject.http_status).to eq(:unauthorized)
end
end
context 'successfully creating approval rule' do
it 'creates a new ExternalApprovalRule' do
expect { subject }.to change { ApprovalRules::ExternalApprovalRule.count }.by(1)
end
it 'is successful' do
expect(subject.success?).to be true
end
it 'includes the newly created rule in its payload' do
rule = subject.payload[:rule]
expect(rule).to be_a(ApprovalRules::ExternalApprovalRule)
expect(rule.project).to eq(project)
expect(rule.external_url).to eq('https://external_url.text/hello.json')
expect(rule.name).to eq 'Test'
expect(rule.protected_branches).to contain_exactly(protected_branch)
end
end
end
...@@ -335,6 +335,7 @@ container_repositories: ...@@ -335,6 +335,7 @@ container_repositories:
- project - project
- name - name
project: project:
- external_approval_rules
- taggings - taggings
- base_tags - base_tags
- tag_taggings - tag_taggings
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment