Commit 71c77e6c authored by Douwe Maan's avatar Douwe Maan

Merge branch '11877-public-approval-api' into 'master'

Public project-level approval rule API

See merge request gitlab-org/gitlab-ee!13895
parents dc35eaa3 f2051d0c
...@@ -23,36 +23,6 @@ GET /projects/:id/approvals ...@@ -23,36 +23,6 @@ GET /projects/:id/approvals
```json ```json
{ {
"approvers": [
{
"user": {
"id": 5,
"name": "John Doe6",
"username": "user5",
"state":"active","avatar_url":"https://www.gravatar.com/avatar/4aea8cf834ed91844a2da4ff7ae6b491?s=80\u0026d=identicon","web_url":"http://localhost/user5"
}
}
],
"approver_groups": [
{
"group": {
"id": 1,
"name": "group1",
"path": "group1",
"description": "",
"visibility": "public",
"lfs_enabled": false,
"avatar_url": null,
"web_url": "http://localhost/groups/group1",
"request_access_enabled": false,
"full_name": "group1",
"full_path": "group1",
"parent_id": null,
"ldap_cn": null,
"ldap_access": null
}
}
],
"approvals_before_merge": 2, "approvals_before_merge": 2,
"reset_approvals_on_push": true, "reset_approvals_on_push": true,
"disable_overriding_approvers_per_merge_request": false "disable_overriding_approvers_per_merge_request": false
...@@ -75,7 +45,7 @@ POST /projects/:id/approvals ...@@ -75,7 +45,7 @@ POST /projects/:id/approvals
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| ------------------------------------------------ | ------- | -------- | --------------------------------------------------------------------------------------------------- | | ------------------------------------------------ | ------- | -------- | --------------------------------------------------------------------------------------------------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `approvals_before_merge` | integer | no | How many approvals are required before an MR can be merged | | `approvals_before_merge` | integer | no | How many approvals are required before an MR can be merged. Deprecated in 12.0 in favor of Approval Rules API. |
| `reset_approvals_on_push` | boolean | no | Reset approvals on a new push | | `reset_approvals_on_push` | boolean | no | Reset approvals on a new push |
| `disable_overriding_approvers_per_merge_request` | boolean | no | Allow/Disallow overriding approvers per MR | | `disable_overriding_approvers_per_merge_request` | boolean | no | Allow/Disallow overriding approvers per MR |
| `merge_requests_author_approval` | boolean | no | Allow/Disallow authors from self approving merge requests; `true` means authors cannot self approve | | `merge_requests_author_approval` | boolean | no | Allow/Disallow authors from self approving merge requests; `true` means authors cannot self approve |
...@@ -83,20 +53,68 @@ POST /projects/:id/approvals ...@@ -83,20 +53,68 @@ POST /projects/:id/approvals
```json ```json
{ {
"approvers": [ "approvals_before_merge": 2,
{ "reset_approvals_on_push": true,
"user": { "disable_overriding_approvers_per_merge_request": false,
"merge_requests_author_approval": false,
"merge_requests_disable_committers_approval": false
}
```
### Get project-level rules
>**Note:** This API endpoint is only available on 12.3 Starter and above.
You can request information about a project's approval rules using the following endpoint:
```
GET /projects/:id/approval_rules
```
**Parameters:**
| Attribute | Type | Required | Description |
|----------------------|---------|----------|-----------------------------------------------------------|
| `id` | integer | yes | The ID of a project |
```json
[
{
"id": 1,
"name": "security",
"rule_type": "regular",
"eligible_approvers": [
{
"id": 5, "id": 5,
"name": "John Doe6", "name": "John Doe",
"username": "user5", "username": "jdoe",
"state":"active","avatar_url":"https://www.gravatar.com/avatar/4aea8cf834ed91844a2da4ff7ae6b491?s=80\u0026d=identicon","web_url":"http://localhost/user5" "state": "active",
"avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
"web_url": "http://localhost/jdoe"
},
{
"id": 50,
"name": "Group Member 1",
"username": "group_member_1",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
"web_url": "http://localhost/group_member_1"
} }
} ],
], "approvals_required": 3,
"approver_groups": [ "users": [
{ {
"group": { "id": 5,
"id": 1, "name": "John Doe",
"username": "jdoe",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
"web_url": "http://localhost/jdoe"
}
],
"groups": [
{
"id": 5,
"name": "group1", "name": "group1",
"path": "group1", "path": "group1",
"description": "", "description": "",
...@@ -111,18 +129,187 @@ POST /projects/:id/approvals ...@@ -111,18 +129,187 @@ POST /projects/:id/approvals
"ldap_cn": null, "ldap_cn": null,
"ldap_access": null "ldap_access": null
} }
],
"contains_hidden_groups": false
}
]
```
### Create project-level rule
>**Note:** This API endpoint is only available on 12.3 Starter and above.
You can create project approval rules using the following endpoint:
```
POST /projects/:id/approval_rules
```
**Parameters:**
| Attribute | Type | Required | Description |
|----------------------|---------|----------|-----------------------------------------------------------|
| `id` | integer | yes | The ID of a project |
| `name` | string | yes | The name of the approval rule |
| `approvals_required` | integer | yes | The number of required approvals for this rule |
| `users` | integer | no | The ids of users as approvers |
| `groups` | integer | no | The ids of groups as approvers |
```json
{
"id": 1,
"name": "security",
"rule_type": "regular",
"eligible_approvers": [
{
"id": 2,
"name": "John Doe",
"username": "jdoe",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
"web_url": "http://localhost/jdoe"
},
{
"id": 50,
"name": "Group Member 1",
"username": "group_member_1",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
"web_url": "http://localhost/group_member_1"
} }
], ],
"approvals_before_merge": 2, "approvals_required": 1,
"reset_approvals_on_push": true, "users": [
"disable_overriding_approvers_per_merge_request": false, {
"merge_requests_author_approval": false, "id": 2,
"merge_requests_disable_committers_approval": false "name": "John Doe",
"username": "jdoe",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
"web_url": "http://localhost/jdoe"
}
],
"groups": [
{
"id": 5,
"name": "group1",
"path": "group1",
"description": "",
"visibility": "public",
"lfs_enabled": false,
"avatar_url": null,
"web_url": "http://localhost/groups/group1",
"request_access_enabled": false,
"full_name": "group1",
"full_path": "group1",
"parent_id": null,
"ldap_cn": null,
"ldap_access": null
}
],
"contains_hidden_groups": false
}
```
### Update project-level rule
>**Note:** This API endpoint is only available on 12.3 Starter and above.
You can update project approval rules using the following endpoint:
```
PUT /projects/:id/approval_rules/:approval_rule_id
```
**Important:** Approvers and groups not in the `users`/`groups` param will be **removed**
**Parameters:**
| Attribute | Type | Required | Description |
|----------------------|---------|----------|-----------------------------------------------------------|
| `id` | integer | yes | The ID of a project |
| `approval_rule_id` | integer | yes | The ID of a approval rule |
| `name` | string | yes | The name of the approval rule |
| `approvals_required` | integer | yes | The number of required approvals for this rule |
| `users` | integer | no | The ids of users as approvers |
| `groups` | integer | no | The ids of groups as approvers |
```json
{
"id": 1,
"name": "security",
"rule_type": "regular",
"eligible_approvers": [
{
"id": 2,
"name": "John Doe",
"username": "jdoe",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
"web_url": "http://localhost/jdoe"
},
{
"id": 50,
"name": "Group Member 1",
"username": "group_member_1",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
"web_url": "http://localhost/group_member_1"
}
],
"approvals_required": 1,
"users": [
{
"id": 2,
"name": "John Doe",
"username": "jdoe",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
"web_url": "http://localhost/jdoe"
}
],
"groups": [
{
"id": 5,
"name": "group1",
"path": "group1",
"description": "",
"visibility": "public",
"lfs_enabled": false,
"avatar_url": null,
"web_url": "http://localhost/groups/group1",
"request_access_enabled": false,
"full_name": "group1",
"full_path": "group1",
"parent_id": null,
"ldap_cn": null,
"ldap_access": null
}
],
"contains_hidden_groups": false
} }
``` ```
### Delete project-level rule
>**Note:** This API endpoint is only available on 12.3 Starter and above.
You can delete project approval rules using the following endpoint:
```
DELETE /projects/:id/approval_rules/:approval_rule_id
```
**Parameters:**
| Attribute | Type | Required | Description |
|----------------------|---------|----------|-----------------------------------------------------------|
| `id` | integer | yes | The ID of a project |
| `approval_rule_id` | integer | yes | The ID of a approval rule
### Change allowed approvers ### Change allowed approvers
>**Note:** This API endpoint has been deprecated. Please use Approval Rule API instead.
>**Note:** This API endpoint is only available on 10.6 Starter and above. >**Note:** This API endpoint is only available on 10.6 Starter and above.
If you are allowed to, you can change approvers and approver groups using If you are allowed to, you can change approvers and approver groups using
...@@ -227,8 +414,6 @@ GET /projects/:id/merge_requests/:merge_request_iid/approvals ...@@ -227,8 +414,6 @@ GET /projects/:id/merge_requests/:merge_request_iid/approvals
} }
} }
], ],
"approvers": [],
"approver_groups": []
} }
``` ```
...@@ -249,7 +434,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/approvals ...@@ -249,7 +434,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/approvals
|----------------------|---------|----------|--------------------------------------------| |----------------------|---------|----------|--------------------------------------------|
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `merge_request_iid` | integer | yes | The IID of MR | | `merge_request_iid` | integer | yes | The IID of MR |
| `approvals_required` | integer | yes | Approvals required before MR can be merged | | `approvals_required` | integer | yes | Approvals required before MR can be merged. Deprecated in 12.0 in favor of Approval Rules API. |
```json ```json
{ {
...@@ -264,14 +449,13 @@ POST /projects/:id/merge_requests/:merge_request_iid/approvals ...@@ -264,14 +449,13 @@ POST /projects/:id/merge_requests/:merge_request_iid/approvals
"merge_status": "cannot_be_merged", "merge_status": "cannot_be_merged",
"approvals_required": 2, "approvals_required": 2,
"approvals_left": 2, "approvals_left": 2,
"approved_by": [], "approved_by": []
"approvers": [],
"approver_groups": []
} }
``` ```
### Change allowed approvers for Merge Request ### Change allowed approvers for Merge Request
>**Note:** This API endpoint has been deprecated. Please use Approval Rule API instead.
>**Note:** This API endpoint is only available on 10.6 Starter and above. >**Note:** This API endpoint is only available on 10.6 Starter and above.
If you are allowed to, you can change approvers and approver groups using If you are allowed to, you can change approvers and approver groups using
...@@ -401,8 +585,6 @@ does not match, the response code will be `409`. ...@@ -401,8 +585,6 @@ does not match, the response code will be `409`.
} }
} }
], ],
"approvers": [],
"approver_groups": []
} }
``` ```
......
---
title: Public project-level approval rule API
merge_request: 13895
author:
type: added
# frozen_string_literal: true
module API
module Helpers
module ProjectApprovalRulesHelpers
extend Grape::API::Helpers
ARRAY_COERCION_LAMBDA = ->(val) { val.empty? ? [] : Array.wrap(val) }
params :create_project_approval_rule do
requires :name, type: String, desc: 'The name of the approval rule'
requires :approvals_required, type: Integer, desc: 'The number of required approvals for this rule'
optional :rule_type, type: String, desc: 'The type of approval rule'
optional :users, as: :user_ids, type: Array, coerce_with: ARRAY_COERCION_LAMBDA, desc: 'The user ids for this rule'
optional :groups, as: :group_ids, type: Array, coerce_with: ARRAY_COERCION_LAMBDA, desc: 'The group ids for this rule'
end
params :update_project_approval_rule do
requires :approval_rule_id, type: Integer, desc: 'The ID of an approval_rule'
optional :name, type: String, desc: 'The name of the approval rule'
optional :approvals_required, type: Integer, desc: 'The number of required approvals for this rule'
optional :users, as: :user_ids, type: Array, coerce_with: ARRAY_COERCION_LAMBDA, desc: 'The user ids for this rule'
optional :groups, as: :group_ids, type: Array, coerce_with: ARRAY_COERCION_LAMBDA, desc: 'The group ids for this rule'
optional :remove_hidden_groups, type: Boolean, desc: 'Whether hidden groups should be removed'
end
params :delete_project_approval_rule do
requires :approval_rule_id, type: Integer, desc: 'The ID of an approval_rule'
end
def authorize_create_merge_request_in_project
authorize! :create_merge_request_in, user_project
end
def create_project_approval_rule(present_with:)
authorize! :admin_project, user_project
result = ::ApprovalRules::CreateService.new(user_project, current_user, declared_params(include_missing: false)).execute
if result[:status] == :success
present result[:rule], with: present_with, current_user: current_user
else
render_api_error!(result[:message], 400)
end
end
def update_project_approval_rule(present_with:)
authorize! :admin_project, user_project
params = declared_params(include_missing: false)
approval_rule = user_project.approval_rules.find(params.delete(:approval_rule_id))
result = ::ApprovalRules::UpdateService.new(approval_rule, current_user, params).execute
if result[:status] == :success
present result[:rule], with: present_with, current_user: current_user
else
render_api_error!(result[:message], 400)
end
end
def destroy_project_approval_rule
authorize! :admin_project, user_project
approval_rule = user_project.approval_rules.find(params[:approval_rule_id])
destroy_conditionally!(approval_rule) do |rule|
::ApprovalRules::ProjectRuleDestroyService.new(rule).execute
end
end
end
end
end
...@@ -4,89 +4,49 @@ module API ...@@ -4,89 +4,49 @@ module API
class ProjectApprovalRules < ::Grape::API class ProjectApprovalRules < ::Grape::API
before { authenticate! } before { authenticate! }
ARRAY_COERCION_LAMBDA = ->(val) { val.empty? ? [] : Array.wrap(val) } helpers ::API::Helpers::ProjectApprovalRulesHelpers
params do params do
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
end end
resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
segment ':id/approval_settings' do segment ':id/approval_rules' do
desc 'Get all project approval rules' do desc 'Get all project approval rules' do
detail 'Private API subject to change' success EE::API::Entities::ApprovalRule
success EE::API::Entities::ProjectApprovalRules
end end
get do get do
authorize! :create_merge_request_in, user_project authorize_create_merge_request_in_project
present user_project, with: EE::API::Entities::ProjectApprovalRules, current_user: current_user present user_project.visible_approval_rules, with: EE::API::Entities::ApprovalRule, current_user: current_user
end end
segment 'rules' do desc 'Create new project approval rule' do
desc 'Create new approval rule' do success EE::API::Entities::ApprovalRule
detail 'Private API subject to change' end
params do
use :create_project_approval_rule
end
post do
create_project_approval_rule(present_with: EE::API::Entities::ApprovalRule)
end
segment ':approval_rule_id' do
desc 'Update project approval rule' do
success EE::API::Entities::ApprovalRule success EE::API::Entities::ApprovalRule
end end
params do params do
requires :name, type: String, desc: 'The name of the approval rule' use :update_project_approval_rule
requires :approvals_required, type: Integer, desc: 'The number of required approvals for this rule'
optional :rule_type, type: String, desc: 'The type of approval rule'
optional :users, as: :user_ids, type: Array, coerce_with: ARRAY_COERCION_LAMBDA, desc: 'The user ids for this rule'
optional :groups, as: :group_ids, type: Array, coerce_with: ARRAY_COERCION_LAMBDA, desc: 'The group ids for this rule'
end end
post do put do
authorize! :admin_project, user_project update_project_approval_rule(present_with: EE::API::Entities::ApprovalRule)
result = ::ApprovalRules::CreateService.new(user_project, current_user, declared_params(include_missing: false)).execute
if result[:status] == :success
present result[:rule], with: EE::API::Entities::ApprovalRule, current_user: current_user
else
render_api_error!(result[:message], 400)
end
end end
segment ':approval_rule_id' do desc 'Destroy project approval rule'
desc 'Update approval rule' do params do
detail 'Private API subject to change' use :delete_project_approval_rule
success EE::API::Entities::ApprovalRule end
end delete do
params do destroy_project_approval_rule
requires :approval_rule_id, type: Integer, desc: 'The ID of an approval_rule'
optional :name, type: String, desc: 'The name of the approval rule'
optional :approvals_required, type: Integer, desc: 'The number of required approvals for this rule'
optional :users, as: :user_ids, type: Array, coerce_with: ARRAY_COERCION_LAMBDA, desc: 'The user ids for this rule'
optional :groups, as: :group_ids, type: Array, coerce_with: ARRAY_COERCION_LAMBDA, desc: 'The group ids for this rule'
optional :remove_hidden_groups, type: Boolean, desc: 'Whether hidden groups should be removed'
end
put do
authorize! :admin_project, user_project
params = declared_params(include_missing: false)
approval_rule = user_project.approval_rules.find(params.delete(:approval_rule_id))
result = ::ApprovalRules::UpdateService.new(approval_rule, current_user, params).execute
if result[:status] == :success
present result[:rule], with: EE::API::Entities::ApprovalRule, current_user: current_user
else
render_api_error!(result[:message], 400)
end
end
desc 'Delete an approval rule' do
detail 'Private API subject to change'
end
params do
requires :approval_rule_id, type: Integer, desc: 'The ID of an approval_rule'
end
delete do
authorize! :admin_project, user_project
approval_rule = user_project.approval_rules.find(params[:approval_rule_id])
destroy_conditionally!(approval_rule) do |rule|
::ApprovalRules::ProjectRuleDestroyService.new(rule).execute
end
end
end end
end end
end end
......
# frozen_string_literal: true
module API
class ProjectApprovalSettings < ::Grape::API
before { authenticate! }
helpers ::API::Helpers::ProjectApprovalRulesHelpers
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
segment ':id/approval_settings' do
desc 'Get all project approval rules' do
detail 'Private API subject to change'
success EE::API::Entities::ProjectApprovalSettings
end
get do
authorize_create_merge_request_in_project
present user_project, with: EE::API::Entities::ProjectApprovalSettings, current_user: current_user
end
segment 'rules' do
desc 'Create new approval rule' do
detail 'Private API subject to change'
success EE::API::Entities::ApprovalSettingRule
end
params do
use :create_project_approval_rule
end
post do
create_project_approval_rule(present_with: EE::API::Entities::ApprovalSettingRule)
end
segment ':approval_rule_id' do
desc 'Update approval rule' do
detail 'Private API subject to change'
success EE::API::Entities::ApprovalSettingRule
end
params do
use :update_project_approval_rule
end
put do
update_project_approval_rule(present_with: EE::API::Entities::ApprovalSettingRule)
end
desc 'Delete an approval rule' do
detail 'Private API subject to change'
end
params do
use :delete_project_approval_rule
end
delete do
destroy_project_approval_rule
end
end
end
end
end
end
end
...@@ -10,6 +10,7 @@ module EE ...@@ -10,6 +10,7 @@ module EE
mount ::EE::API::GroupBoards mount ::EE::API::GroupBoards
mount ::API::ProjectApprovalRules mount ::API::ProjectApprovalRules
mount ::API::ProjectApprovalSettings
mount ::API::Unleash mount ::API::Unleash
mount ::API::EpicIssues mount ::API::EpicIssues
mount ::API::EpicLinks mount ::API::EpicLinks
......
...@@ -309,7 +309,7 @@ module EE ...@@ -309,7 +309,7 @@ module EE
expose :id, :name, :rule_type expose :id, :name, :rule_type
end end
class ApprovalRule < ApprovalRuleShort class ApprovalSettingRule < ApprovalRuleShort
def initialize(object, options = {}) def initialize(object, options = {})
presenter = ::ApprovalRulePresenter.new(object, current_user: options[:current_user]) presenter = ::ApprovalRulePresenter.new(object, current_user: options[:current_user])
super(presenter, options) super(presenter, options)
...@@ -322,7 +322,11 @@ module EE ...@@ -322,7 +322,11 @@ module EE
expose :contains_hidden_groups?, as: :contains_hidden_groups expose :contains_hidden_groups?, as: :contains_hidden_groups
end end
class MergeRequestApprovalRule < ApprovalRule class ApprovalRule < ApprovalSettingRule
expose :approvers, as: :eligible_approvers, using: ::API::Entities::UserBasic, override: true
end
class MergeRequestApprovalRule < ApprovalSettingRule
class SourceRule < Grape::Entity class SourceRule < Grape::Entity
expose :approvals_required expose :approvals_required
end end
...@@ -343,8 +347,8 @@ module EE ...@@ -343,8 +347,8 @@ module EE
end end
# Decorates Project # Decorates Project
class ProjectApprovalRules < Grape::Entity class ProjectApprovalSettings < Grape::Entity
expose :visible_approval_rules, as: :rules, using: ApprovalRule expose :visible_approval_rules, as: :rules, using: ApprovalSettingRule
expose :min_fallback_approvals, as: :fallback_approvals_required expose :min_fallback_approvals, as: :fallback_approvals_required
end end
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
"approvals_required": { "type": "integer" }, "approvals_required": { "type": "integer" },
"contains_hidden_groups": { "type": "boolean" }, "contains_hidden_groups": { "type": "boolean" },
"rule_type": { "type": "tring" }, "rule_type": { "type": "tring" },
"approvers": { "eligible_approvers": {
"type": "array", "type": "array",
"items": { "items": {
"type": "object", "type": "object",
......
{ {
"type": "object", "type": "array",
"properties": { "items": { "$ref": "./project_approval_rule.json" }
"rules": {
"type": "array",
"items": { "$ref": "project_approval_rule.json" }
},
"fallback_approvals_required": { "type": "integer" }
},
"additionalProperties": false
} }
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"approvals_required": { "type": "integer" },
"contains_hidden_groups": { "type": "boolean" },
"rule_type": { "type": "tring" },
"approvers": {
"type": "array",
"items": {
"type": "object",
"properties": {}
}
},
"groups": {
"type": "array",
"items": {
"type": "object",
"properties": {}
}
},
"users": {
"type": "array",
"items": {
"type": "object",
"properties": {}
}
}
},
"additionalProperties": false
}
{
"type": "object",
"properties": {
"rules": {
"type": "array",
"items": { "$ref": "project_approval_setting.json" }
},
"fallback_approvals_required": { "type": "integer" }
},
"additionalProperties": false
}
...@@ -3,17 +3,15 @@ ...@@ -3,17 +3,15 @@
require 'spec_helper' require 'spec_helper'
describe API::ProjectApprovalRules do describe API::ProjectApprovalRules do
set(:group) { create(:group_with_members) } set(:group) { create(:group_with_members) }
set(:user) { create(:user) } set(:user) { create(:user) }
set(:user2) { create(:user) } set(:user2) { create(:user) }
set(:admin) { create(:user, :admin) } set(:admin) { create(:user, :admin) }
set(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace, only_allow_merge_if_pipeline_succeeds: false) } set(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace, only_allow_merge_if_pipeline_succeeds: false) }
set(:approver) { create(:user) } set(:approver) { create(:user) }
let(:url) { "/projects/#{project.id}/approval_settings/rules" } describe 'GET /projects/:id/approval_rules' do
let(:url) { "/projects/#{project.id}/approval_rules" }
describe 'GET /projects/:id/approval_settings' do
let(:url) { "/projects/#{project.id}/approval_settings" }
context 'when the request is correct' do context 'when the request is correct' do
let!(:rule) do let!(:rule) do
...@@ -36,9 +34,9 @@ describe API::ProjectApprovalRules do ...@@ -36,9 +34,9 @@ describe API::ProjectApprovalRules do
json = json_response json = json_response
expect(json['rules'].size).to eq(1) expect(json.size).to eq(1)
rule = json['rules'].first rule = json.first
expect(rule['approvals_required']).to eq(7) expect(rule['approvals_required']).to eq(7)
expect(rule['name']).to eq('security') expect(rule['name']).to eq('security')
...@@ -55,7 +53,7 @@ describe API::ProjectApprovalRules do ...@@ -55,7 +53,7 @@ describe API::ProjectApprovalRules do
get api(url, developer) get api(url, developer)
json = json_response json = json_response
rule = json['rules'].first rule = json.first
expect(rule['groups'].size).to eq(0) expect(rule['groups'].size).to eq(0)
end end
...@@ -66,7 +64,7 @@ describe API::ProjectApprovalRules do ...@@ -66,7 +64,7 @@ describe API::ProjectApprovalRules do
get api(url, developer) get api(url, developer)
json = json_response json = json_response
rule = json['rules'].first rule = json.first
expect(rule['groups'].size).to eq(1) expect(rule['groups'].size).to eq(1)
end end
...@@ -82,162 +80,32 @@ describe API::ProjectApprovalRules do ...@@ -82,162 +80,32 @@ describe API::ProjectApprovalRules do
json = json_response json = json_response
expect(json['rules'].size).to eq(2) expect(json.size).to eq(2)
expect(json['rules'].map { |rule| rule['name'] }).to contain_exactly(rule.name, report_approver_rule.name) expect(json.map { |rule| rule['name'] }).to contain_exactly(rule.name, report_approver_rule.name)
end end
end end
end end
end end
describe 'POST /projects/:id/approval_settings/rules' do describe 'POST /projects/:id/approval_rules' do
let(:current_user) { user } let(:schema) { 'public_api/v4/project_approval_rule' }
let(:params) do let(:url) { "/projects/#{project.id}/approval_rules" }
{
name: 'security',
approvals_required: 10
}
end
context 'when missing parameters' do
it 'returns 400 status' do
post api(url, current_user)
expect(response).to have_gitlab_http_status(400)
end
end
context 'when user is without access' do
it 'returns 403' do
post api(url, user2), params: params
expect(response).to have_gitlab_http_status(403)
end
end
context 'when the request is correct' do
it 'returns 201 status' do
post api(url, current_user), params: params
expect(response).to have_gitlab_http_status(201)
expect(response).to match_response_schema('public_api/v4/project_approval_rule', dir: 'ee')
end
it 'changes settings properly' do
create(:approval_project_rule, project: project, approvals_required: 2)
project.reset_approvals_on_push = false it_behaves_like 'an API endpoint for creating project approval rule'
project.disable_overriding_approvers_per_merge_request = true
project.save
post api(url, current_user), params: params
expect(json_response.symbolize_keys).to include(params)
end
it 'sets rule_type as report_approver if name matches default name for security reports' do
expect do
post api(url, current_user), params: params.merge(name: ApprovalProjectRule::DEFAULT_NAME_FOR_SECURITY_REPORT)
end.to change { ApprovalProjectRule.report_approver.count }.from(0).to(1)
expect(response).to have_gitlab_http_status(201)
end
end
end end
describe 'PUT /projects/:id/approval_settings/:approval_rule_id' do describe 'PUT /projects/:id/approval_rules/:approval_rule_id' do
let!(:approval_rule) { create(:approval_project_rule, project: project) } let!(:approval_rule) { create(:approval_project_rule, project: project) }
let(:url) { "/projects/#{project.id}/approval_settings/rules/#{approval_rule.id}" } let(:schema) { 'public_api/v4/project_approval_rule' }
let(:url) { "/projects/#{project.id}/approval_rules/#{approval_rule.id}" }
shared_examples_for 'a user with access' do
before do
project.add_developer(approver)
end
context 'when approver already exists' do
before do
approval_rule.users << approver
approval_rule.groups << group
end
context 'when sending json data' do
it 'removes all approvers if empty params are given' do
expect do
put api(url, current_user), params: { users: [], groups: [] }.to_json, headers: { CONTENT_TYPE: 'application/json' }
end.to change { approval_rule.users.count + approval_rule.groups.count }.from(2).to(0)
expect(response).to have_gitlab_http_status(200)
end
end
end
it 'sets approvers' do
expect do
put api(url, current_user), params: { users: [approver.id] }
end.to change { approval_rule.users.count }.from(0).to(1)
expect(approval_rule.users).to contain_exactly(approver)
expect(approval_rule.groups).to be_empty
expect(response).to have_gitlab_http_status(200)
expect(json_response['approvers'][0]['id']).to eq(approver.id)
end
end
context 'as a project admin' do
it_behaves_like 'a user with access' do
let(:current_user) { user }
let(:visible_approver_groups_count) { 0 }
end
end
context 'as a global admin' do
it_behaves_like 'a user with access' do
let(:current_user) { admin }
let(:visible_approver_groups_count) { 1 }
end
end
context 'as a random user' do
it 'returns 403' do
project.approvers.create(user: approver)
expect do it_behaves_like 'an API endpoint for updating project approval rule'
put api(url, user2), params: { users: [], groups: [] }.to_json, headers: { CONTENT_TYPE: 'application/json' }
end.not_to change { approval_rule.approvers.size }
expect(response).to have_gitlab_http_status(403)
end
end
end end
describe 'DELETE /projects/:id/approval_settings/rules/:approval_rule_id' do describe 'DELETE /projects/:id/approval_rules/:approval_rule_id' do
let!(:approval_rule) { create(:approval_project_rule, project: project) } let!(:approval_rule) { create(:approval_project_rule, project: project) }
let(:url) { "/projects/#{project.id}/approval_settings/rules/#{approval_rule.id}" } let(:url) { "/projects/#{project.id}/approval_rules/#{approval_rule.id}" }
it 'destroys' do
delete api(url, user)
expect(ApprovalProjectRule.exists?(id: approval_rule.id)).to eq(false)
expect(response).to have_gitlab_http_status(204)
end
context 'when approval rule not found' do
let!(:approval_rule_2) { create(:approval_project_rule) }
let(:url) { "/projects/#{project.id}/approval_settings/#{approval_rule_2.id}" }
it 'returns not found' do it_behaves_like 'an API endpoint for deleting project approval rule'
delete api(url, user)
expect(response).to have_gitlab_http_status(404)
end
end
context 'when user is not eligible to delete' do
it 'returns forbidden' do
delete api(url, user2)
expect(response).to have_gitlab_http_status(403)
end
end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
describe API::ProjectApprovalSettings do
set(:group) { create(:group_with_members) }
set(:user) { create(:user) }
set(:user2) { create(:user) }
set(:admin) { create(:user, :admin) }
set(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace, only_allow_merge_if_pipeline_succeeds: false) }
set(:approver) { create(:user) }
describe 'GET /projects/:id/approval_settings' do
let(:url) { "/projects/#{project.id}/approval_settings" }
context 'when the request is correct' do
let!(:rule) do
rule = create(:approval_project_rule, name: 'security', project: project, approvals_required: 7)
rule.users << approver
rule
end
let(:developer) do
user = create(:user)
project.add_guest(user)
user
end
it 'matches the response schema' do
get api(url, developer)
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/project_approval_settings', dir: 'ee')
json = json_response
expect(json['rules'].size).to eq(1)
rule = json['rules'].first
expect(rule['approvals_required']).to eq(7)
expect(rule['name']).to eq('security')
end
context 'private group filtering' do
set(:private_group) { create :group, :private }
before do
rule.groups << private_group
end
it 'excludes private groups if user has no access' do
get api(url, developer)
json = json_response
rule = json['rules'].first
expect(rule['groups'].size).to eq(0)
end
it 'includes private groups if user has access' do
private_group.add_owner(developer)
get api(url, developer)
json = json_response
rule = json['rules'].first
expect(rule['groups'].size).to eq(1)
end
end
context 'report_approver rules' do
let!(:report_approver_rule) do
create(:approval_project_rule, :security_report, project: project)
end
it 'includes report_approver rules' do
get api(url, developer)
json = json_response
expect(json['rules'].size).to eq(2)
expect(json['rules'].map { |rule| rule['name'] }).to contain_exactly(rule.name, report_approver_rule.name)
end
end
end
end
describe 'POST /projects/:id/approval_settings/rules' do
let(:schema) { 'public_api/v4/project_approval_setting' }
let(:url) { "/projects/#{project.id}/approval_settings/rules" }
it_behaves_like 'an API endpoint for creating project approval rule'
end
describe 'PUT /projects/:id/approval_settings/:approval_rule_id' do
let!(:approval_rule) { create(:approval_project_rule, project: project) }
let(:schema) { 'public_api/v4/project_approval_setting' }
let(:url) { "/projects/#{project.id}/approval_settings/rules/#{approval_rule.id}" }
it_behaves_like 'an API endpoint for updating project approval rule'
end
describe 'DELETE /projects/:id/approval_settings/rules/:approval_rule_id' do
let!(:approval_rule) { create(:approval_project_rule, project: project) }
let(:url) { "/projects/#{project.id}/approval_settings/rules/#{approval_rule.id}" }
it_behaves_like 'an API endpoint for deleting project approval rule'
end
end
# frozen_string_literal: true
shared_examples_for 'an API endpoint for creating project approval rule' do
let(:current_user) { user }
let(:params) do
{
name: 'security',
approvals_required: 10
}
end
context 'when missing parameters' do
it 'returns 400 status' do
post api(url, current_user)
expect(response).to have_gitlab_http_status(400)
end
end
context 'when user is without access' do
it 'returns 403' do
post api(url, user2), params: params
expect(response).to have_gitlab_http_status(403)
end
end
context 'when the request is correct' do
it 'returns 201 status' do
post api(url, current_user), params: params
expect(response).to have_gitlab_http_status(201)
expect(response).to match_response_schema(schema, dir: 'ee')
end
it 'changes settings properly' do
create(:approval_project_rule, project: project, approvals_required: 2)
project.reset_approvals_on_push = false
project.disable_overriding_approvers_per_merge_request = true
project.save
post api(url, current_user), params: params
expect(json_response.symbolize_keys).to include(params)
end
it 'sets rule_type as report_approver if name matches default name for security reports' do
expect do
post api(url, current_user), params: params.merge(name: ApprovalProjectRule::DEFAULT_NAME_FOR_SECURITY_REPORT)
end.to change { ApprovalProjectRule.report_approver.count }.from(0).to(1)
expect(response).to have_gitlab_http_status(201)
end
end
end
shared_examples_for 'an API endpoint for updating project approval rule' do
shared_examples_for 'a user with access' do
before do
project.add_developer(approver)
end
context 'when approver already exists' do
before do
approval_rule.users << approver
approval_rule.groups << group
end
context 'when sending json data' do
it 'removes all approvers if empty params are given' do
expect do
put api(url, current_user), params: { users: [], groups: [] }.to_json, headers: { CONTENT_TYPE: 'application/json' }
end.to change { approval_rule.users.count + approval_rule.groups.count }.from(2).to(0)
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema(schema, dir: 'ee')
end
end
end
it 'sets approvers' do
expect do
put api(url, current_user), params: { users: [approver.id] }
end.to change { approval_rule.users.count }.from(0).to(1)
expect(approval_rule.users).to contain_exactly(approver)
expect(approval_rule.groups).to be_empty
expect(response).to have_gitlab_http_status(200)
end
end
context 'as a project admin' do
it_behaves_like 'a user with access' do
let(:current_user) { user }
let(:visible_approver_groups_count) { 0 }
end
end
context 'as a global admin' do
it_behaves_like 'a user with access' do
let(:current_user) { admin }
let(:visible_approver_groups_count) { 1 }
end
end
context 'as a random user' do
it 'returns 403' do
project.approvers.create(user: approver)
expect do
put api(url, user2), params: { users: [], groups: [] }.to_json, headers: { CONTENT_TYPE: 'application/json' }
end.not_to change { approval_rule.approvers.size }
expect(response).to have_gitlab_http_status(403)
end
end
end
shared_examples_for 'an API endpoint for deleting project approval rule' do
it 'destroys' do
delete api(url, user)
expect(ApprovalProjectRule.exists?(id: approval_rule.id)).to eq(false)
expect(response).to have_gitlab_http_status(204)
end
context 'when approval rule not found' do
let!(:approval_rule_2) { create(:approval_project_rule) }
let(:url) { "/projects/#{project.id}/approval_settings/#{approval_rule_2.id}" }
it 'returns not found' do
delete api(url, user)
expect(response).to have_gitlab_http_status(404)
end
end
context 'when user is not eligible to delete' do
it 'returns forbidden' do
delete api(url, user2)
expect(response).to have_gitlab_http_status(403)
end
end
end
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