From 2b71995e104c512fc04161b3507378176249023a Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin <godfat@godfat.org>
Date: Sat, 20 May 2017 00:10:10 +0800
Subject: [PATCH 01/13] Add shared_runners_minutes_limit to groups and users
 API

---
 lib/api/entities.rb              | 11 +++++++++++
 lib/api/groups.rb                |  5 +++--
 lib/api/users.rb                 | 15 +++++++++++----
 spec/requests/api/groups_spec.rb |  8 ++++++++
 spec/requests/api/users_spec.rb  |  9 +++++++++
 5 files changed, 42 insertions(+), 6 deletions(-)

diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index c4425749d4ebf..699bfbdd2bc56 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -38,6 +38,14 @@ class UserPublic < User
       expose :can_create_project?, as: :can_create_project
       expose :two_factor_enabled?, as: :two_factor_enabled
       expose :external
+
+      # EE-only
+      expose :namespace, using: 'API::Entities::UserNamespace'
+    end
+
+    # EE-only
+    class UserNamespace < Grape::Entity
+      expose :shared_runners_minutes_limit
     end
 
     class UserWithPrivateDetails < UserPublic
@@ -181,6 +189,9 @@ class Group < Grape::Entity
     class GroupDetail < Group
       expose :projects, using: Entities::Project
       expose :shared_projects, using: Entities::Project
+
+      # EE-only
+      expose :shared_runners_minutes_limit
     end
 
     class RepoCommit < Grape::Entity
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index d1d80cabf2b0f..92878aa3ada7d 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -17,6 +17,7 @@ class Groups < Grape::API
         optional :membership_lock, type: Boolean, desc: 'Prevent adding new members to project membership within this group'
         optional :ldap_cn, type: String, desc: 'LDAP Common Name'
         optional :ldap_access, type: Integer, desc: 'A valid access level'
+        optional :shared_runners_minutes_limit, type: Integer, desc: 'Pipeline minutes quota for this group'
         all_or_none_of :ldap_cn, :ldap_access
       end
 
@@ -118,8 +119,8 @@ def present_groups(groups, options = {})
         optional :name, type: String, desc: 'The name of the group'
         optional :path, type: String, desc: 'The path of the group'
         use :optional_params
-        at_least_one_of :name, :path, :description, :visibility,
-                        :lfs_enabled, :request_access_enabled
+
+        at_least_one_of(*@declared_params.flatten - [:id])
       end
       put ':id' do
         group = find_group!(params[:id])
diff --git a/lib/api/users.rb b/lib/api/users.rb
index a10030aa31e99..f83901a87dec0 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -30,6 +30,11 @@ def find_user(params)
           optional :skip_confirmation, type: Boolean, default: false, desc: 'Flag indicating the account is confirmed'
           optional :external, type: Boolean, desc: 'Flag indicating the user is an external user'
           all_or_none_of :extern_uid, :provider
+
+          # EE
+          optional :namespace_attributes, type: Hash do
+            optional :shared_runners_minutes_limit, type: Integer, desc: 'Pipeline minutes quota for this user'
+          end
         end
       end
 
@@ -137,10 +142,8 @@ def find_user(params)
         optional :name, type: String, desc: 'The name of the user'
         optional :username, type: String, desc: 'The username of the user'
         use :optional_attributes
-        at_least_one_of :email, :password, :name, :username, :skype, :linkedin,
-                        :twitter, :website_url, :organization, :projects_limit,
-                        :extern_uid, :provider, :bio, :location, :admin,
-                        :can_create_group, :confirm, :external
+
+        at_least_one_of(*@declared_params.flatten - [:id])
       end
       put ":id" do
         authenticated_as_admin!
@@ -172,6 +175,10 @@ def find_user(params)
 
         user_params[:password_expires_at] = Time.now if user_params[:password].present?
 
+        # EE
+        namespace_attributes = user_params[:namespace_attributes]
+        namespace_attributes[:id] = user.namespace_id if namespace_attributes
+
         if user.update_attributes(user_params.except(:extern_uid, :provider))
           present user, with: Entities::UserPublic
         else
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index e3ed4c3232d3c..3b8cebf89c543 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -272,6 +272,14 @@
 
         expect(response).to have_http_status(404)
       end
+
+      # EE
+      it 'updates the group for shared_runners_minutes_limit' do
+        put api("/groups/#{group1.id}", user1), shared_runners_minutes_limit: 133
+
+        expect(response).to have_http_status(200)
+        expect(json_response['shared_runners_minutes_limit']).to eq(133)
+      end
     end
 
     context 'when authenticated as the admin' do
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 758c61f50e7f2..a0bc0ae2c1262 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -425,6 +425,15 @@
       expect(user.reload.external?).to be_truthy
     end
 
+    it "updates shared_runners_minutes_limit" do
+      put api("/users/#{user.id}", admin), { namespace_attributes: { shared_runners_minutes_limit: 133 } }
+
+      expect(response).to have_http_status(200)
+      expect(json_response.dig('namespace', 'shared_runners_minutes_limit'))
+        .to eq(133)
+      expect(user.reload.namespace.shared_runners_minutes_limit).to eq(133)
+    end
+
     it "does not update admin status" do
       put api("/users/#{admin_user.id}", admin), { can_create_group: false }
       expect(response).to have_http_status(200)
-- 
GitLab


From 6a0a4dfea2bf9c1d55a8409f2bf4a85d0f30ed14 Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin <godfat@godfat.org>
Date: Tue, 23 May 2017 22:58:13 +0800
Subject: [PATCH 02/13] Use delegate so that we set
 shared_runners_minutes_limit

from the user directly rather than from the namespace.
---
 app/models/user.rb              | 4 +++-
 lib/api/entities.rb             | 5 -----
 lib/api/users.rb                | 8 +-------
 spec/requests/api/users_spec.rb | 4 ++--
 4 files changed, 6 insertions(+), 15 deletions(-)

diff --git a/app/models/user.rb b/app/models/user.rb
index f2f3e6460e065..557252b2499a3 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -63,7 +63,7 @@ def update_tracked_fields!(request)
   #
 
   # Namespace for personal projects
-  has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id
+  has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, autosave: true
 
   # Profile
   has_many :keys, -> do
@@ -182,6 +182,8 @@ def update_tracked_fields!(request)
   alias_attribute :private_token, :authentication_token
 
   delegate :path, to: :namespace, allow_nil: true, prefix: true
+  delegate :shared_runners_minutes_limit, :shared_runners_minutes_limit=,
+           to: :namespace
 
   accepts_nested_attributes_for :namespace
 
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 699bfbdd2bc56..0301447b8abac 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -40,11 +40,6 @@ class UserPublic < User
       expose :external
 
       # EE-only
-      expose :namespace, using: 'API::Entities::UserNamespace'
-    end
-
-    # EE-only
-    class UserNamespace < Grape::Entity
       expose :shared_runners_minutes_limit
     end
 
diff --git a/lib/api/users.rb b/lib/api/users.rb
index f83901a87dec0..055c1e85cda6a 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -32,9 +32,7 @@ def find_user(params)
           all_or_none_of :extern_uid, :provider
 
           # EE
-          optional :namespace_attributes, type: Hash do
-            optional :shared_runners_minutes_limit, type: Integer, desc: 'Pipeline minutes quota for this user'
-          end
+          optional :shared_runners_minutes_limit, type: Integer, desc: 'Pipeline minutes quota for this user'
         end
       end
 
@@ -175,10 +173,6 @@ def find_user(params)
 
         user_params[:password_expires_at] = Time.now if user_params[:password].present?
 
-        # EE
-        namespace_attributes = user_params[:namespace_attributes]
-        namespace_attributes[:id] = user.namespace_id if namespace_attributes
-
         if user.update_attributes(user_params.except(:extern_uid, :provider))
           present user, with: Entities::UserPublic
         else
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index a0bc0ae2c1262..ce2b608106253 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -426,10 +426,10 @@
     end
 
     it "updates shared_runners_minutes_limit" do
-      put api("/users/#{user.id}", admin), { namespace_attributes: { shared_runners_minutes_limit: 133 } }
+      put api("/users/#{user.id}", admin), { shared_runners_minutes_limit: 133 }
 
       expect(response).to have_http_status(200)
-      expect(json_response.dig('namespace', 'shared_runners_minutes_limit'))
+      expect(json_response['shared_runners_minutes_limit'])
         .to eq(133)
       expect(user.reload.namespace.shared_runners_minutes_limit).to eq(133)
     end
-- 
GitLab


From 0106d2f928a4f9b61fcd1213080e89315baa8e83 Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin <godfat@godfat.org>
Date: Wed, 24 May 2017 01:42:22 +0800
Subject: [PATCH 03/13] There's little point to check having at least one

argument at all. Feedback:
https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1942#note_30495480
---
 lib/api/groups.rb               | 2 --
 lib/api/users.rb                | 2 --
 spec/requests/api/users_spec.rb | 2 +-
 3 files changed, 1 insertion(+), 5 deletions(-)

diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 92878aa3ada7d..625816483971d 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -119,8 +119,6 @@ def present_groups(groups, options = {})
         optional :name, type: String, desc: 'The name of the group'
         optional :path, type: String, desc: 'The path of the group'
         use :optional_params
-
-        at_least_one_of(*@declared_params.flatten - [:id])
       end
       put ':id' do
         group = find_group!(params[:id])
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 055c1e85cda6a..c2f6260359333 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -140,8 +140,6 @@ def find_user(params)
         optional :name, type: String, desc: 'The name of the user'
         optional :username, type: String, desc: 'The username of the user'
         use :optional_attributes
-
-        at_least_one_of(*@declared_params.flatten - [:id])
       end
       put ":id" do
         authenticated_as_admin!
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index ce2b608106253..837ff983831d9 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -431,7 +431,7 @@
       expect(response).to have_http_status(200)
       expect(json_response['shared_runners_minutes_limit'])
         .to eq(133)
-      expect(user.reload.namespace.shared_runners_minutes_limit).to eq(133)
+      expect(user.reload.shared_runners_minutes_limit).to eq(133)
     end
 
     it "does not update admin status" do
-- 
GitLab


From 32e3955b4a1ae605d9318ab628e05a48a33773b6 Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin <godfat@godfat.org>
Date: Thu, 25 May 2017 04:57:21 +0800
Subject: [PATCH 04/13] Make sure only the admin could update
 shared_runners_minutes_limit

---
 app/models/user.rb               |  2 ++
 lib/api/groups.rb                |  3 +++
 spec/requests/api/groups_spec.rb | 13 ++++++++++---
 spec/requests/api/users_spec.rb  | 13 ++++++++++---
 4 files changed, 25 insertions(+), 6 deletions(-)

diff --git a/app/models/user.rb b/app/models/user.rb
index 557252b2499a3..c0b31e7b22219 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -182,6 +182,8 @@ def update_tracked_fields!(request)
   alias_attribute :private_token, :authentication_token
 
   delegate :path, to: :namespace, allow_nil: true, prefix: true
+
+  # EE-only
   delegate :shared_runners_minutes_limit, :shared_runners_minutes_limit=,
            to: :namespace
 
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 625816483971d..074789d4466da 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -124,6 +124,9 @@ def present_groups(groups, options = {})
         group = find_group!(params[:id])
         authorize! :admin_group, group
 
+        # EE
+        authenticated_as_admin! if params[:shared_runners_minutes_limit]
+
         if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute
           present group, with: Entities::GroupDetail, current_user: current_user
         else
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 3b8cebf89c543..89482500ee81a 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -274,11 +274,10 @@
       end
 
       # EE
-      it 'updates the group for shared_runners_minutes_limit' do
+      it 'returns 403 for updating shared_runners_minutes_limit' do
         put api("/groups/#{group1.id}", user1), shared_runners_minutes_limit: 133
 
-        expect(response).to have_http_status(200)
-        expect(json_response['shared_runners_minutes_limit']).to eq(133)
+        expect(response).to have_http_status(403)
       end
     end
 
@@ -289,6 +288,14 @@
         expect(response).to have_http_status(200)
         expect(json_response['name']).to eq(new_group_name)
       end
+
+      # EE
+      it 'updates the group for shared_runners_minutes_limit' do
+        put api("/groups/#{group1.id}", admin), shared_runners_minutes_limit: 133
+
+        expect(response).to have_http_status(200)
+        expect(json_response['shared_runners_minutes_limit']).to eq(133)
+      end
     end
 
     context 'when authenticated as an user that can see the group' do
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 837ff983831d9..b4cb01f1e75c5 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -447,9 +447,16 @@
       expect(user.reload.email).not_to eq('invalid email')
     end
 
-    it "is not available for non admin users" do
-      put api("/users/#{user.id}", user), attributes_for(:user)
-      expect(response).to have_http_status(403)
+    context 'when the current user is not an admin' do
+      it "is not available" do
+        put api("/users/#{user.id}", user), attributes_for(:user)
+        expect(response).to have_http_status(403)
+      end
+
+      it "cannot update their own shared_runners_minutes_limit" do
+        put api("/users/#{user.id}", user), { shared_runners_minutes_limit: 133 }
+        expect(response).to have_http_status(403)
+      end
     end
 
     it "returns 404 for non-existing user" do
-- 
GitLab


From d88c4c12f7897cbc6fc090f58d9e0e80d4a13346 Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin <godfat@godfat.org>
Date: Thu, 25 May 2017 05:44:34 +0800
Subject: [PATCH 05/13] Test for group creation and remove duplicated test

---
 lib/api/groups.rb                |  3 +++
 spec/requests/api/groups_spec.rb | 38 +++++++++++++++++++-------------
 2 files changed, 26 insertions(+), 15 deletions(-)

diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 074789d4466da..b3f0ac6c34e54 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -90,6 +90,9 @@ def present_groups(groups, options = {})
           group_access: params.delete(:ldap_access)
         }
 
+        # EE
+        authenticated_as_admin! if params[:shared_runners_minutes_limit]
+
         group = ::Groups::CreateService.new(current_user, declared_params(include_missing: false)).execute
 
         if group.persisted?
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 89482500ee81a..0779d161959f9 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -494,23 +494,31 @@
         group_attributes = attributes_for(:group, ldap_cn: 'ldap-group', ldap_access: Gitlab::Access::DEVELOPER)
         expect { post api("/groups", admin), group_attributes }.to change{ LdapGroupLink.count }.by(1)
       end
-    end
-  end
 
-  describe "PUT /groups" do
-    context "when authenticated as user without group permissions" do
-      it "does not create group" do
-        put api("/groups/#{group2.id}", user1), attributes_for(:group)
-        expect(response.status).to eq(404)
-      end
-    end
+      # EE
+      context 'when shared_runners_minutes_limit is given' do
+        context 'when the current user is not an admin' do
+          it "does not create a group with shared_runners_minutes_limit" do
+            group = attributes_for(:group, { shared_runners_minutes_limit: 133 })
 
-    context "when authenticated as user with group permissions" do
-      it "updates group" do
-        group2.update(owner: user2)
-        put api("/groups/#{group2.id}", user2), { name: 'Renamed' }
-        expect(response.status).to eq(200)
-        expect(group2.reload.name).to eq('Renamed')
+            post api("/groups", user3), group
+
+            expect(response).to have_http_status(403)
+          end
+        end
+
+        context 'when the current user is an admin' do
+          it "creates a group with shared_runners_minutes_limit" do
+            group = attributes_for(:group, { shared_runners_minutes_limit: 133 })
+
+            post api("/groups", admin), group
+
+            created_group = Group.find(json_response['id'])
+
+            expect(response).to have_http_status(201)
+            expect(created_group.shared_runners_minutes_limit).to eq(133)
+          end
+        end
       end
     end
   end
-- 
GitLab


From 308aa151c12601b764daf2b5088cdd540805c1cd Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin <godfat@godfat.org>
Date: Thu, 25 May 2017 05:44:59 +0800
Subject: [PATCH 06/13] Update doc and add changelog entry

---
 .../unreleased-ee/add-api-shared_runners_minutes_limit.yml   | 4 ++++
 doc/api/groups.md                                            | 3 +++
 doc/api/users.md                                             | 5 ++++-
 lib/api/groups.rb                                            | 2 +-
 4 files changed, 12 insertions(+), 2 deletions(-)
 create mode 100644 changelogs/unreleased-ee/add-api-shared_runners_minutes_limit.yml

diff --git a/changelogs/unreleased-ee/add-api-shared_runners_minutes_limit.yml b/changelogs/unreleased-ee/add-api-shared_runners_minutes_limit.yml
new file mode 100644
index 0000000000000..0569c1045ef3f
--- /dev/null
+++ b/changelogs/unreleased-ee/add-api-shared_runners_minutes_limit.yml
@@ -0,0 +1,4 @@
+---
+title: Add shared_runners_minutes_limit to groups and users API
+merge_request: 1942
+author:
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 7b772a99bb50e..b5bc627344f1d 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -140,6 +140,7 @@ Example response:
   "full_name": "Twitter",
   "full_path": "twitter",
   "parent_id": null,
+  "shared_runners_minutes_limit": 133,
   "projects": [
     {
       "id": 7,
@@ -290,6 +291,7 @@ Parameters:
 - `lfs_enabled` (optional)      - Enable/disable Large File Storage (LFS) for the projects in this group
 - `request_access_enabled` (optional) - Allow users to request member access.
 - `parent_id` (optional) - The parent group id for creating nested group.
+- `shared_runners_minutes_limit` (optional) - (admin-only) Pipeline minutes quota for this group
 
 ## Transfer project to group
 
@@ -323,6 +325,7 @@ PUT /groups/:id
 | `visibility` | string | no | The visibility level of the group. Can be `private`, `internal`, or `public`. |
 | `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group |
 | `request_access_enabled` | boolean | no | Allow users to request member access. |
+| `shared_runners_minutes_limit` | integer | no | (admin-only) Pipeline minutes quota for this group |
 
 ```bash
 curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/5?name=Experimental"
diff --git a/doc/api/users.md b/doc/api/users.md
index 8b57f8c0aaeaa..76ce729c399d4 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -220,7 +220,8 @@ Parameters:
   "can_create_group": true,
   "can_create_project": true,
   "two_factor_enabled": true,
-  "external": false
+  "external": false,
+  "shared_runners_minutes_limit": 133
 }
 ```
 
@@ -253,6 +254,7 @@ Parameters:
 - `can_create_group` (optional) - User can create groups - true or false
 - `confirm` (optional)          - Require confirmation - true (default) or false
 - `external` (optional)         - Flags the user as external - true or false(default)
+- `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user
 
 ## User modification
 
@@ -281,6 +283,7 @@ Parameters:
 - `admin` (optional)            - User is admin - true or false (default)
 - `can_create_group` (optional) - User can create groups - true or false
 - `external` (optional)         - Flags the user as external - true or false(default)
+- `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user
 
 On password update, user will be forced to change it upon next login.
 Note, at the moment this method does only return a `404` error,
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index b3f0ac6c34e54..4be02c4fbb824 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -17,7 +17,7 @@ class Groups < Grape::API
         optional :membership_lock, type: Boolean, desc: 'Prevent adding new members to project membership within this group'
         optional :ldap_cn, type: String, desc: 'LDAP Common Name'
         optional :ldap_access, type: Integer, desc: 'A valid access level'
-        optional :shared_runners_minutes_limit, type: Integer, desc: 'Pipeline minutes quota for this group'
+        optional :shared_runners_minutes_limit, type: Integer, desc: '(admin-only) Pipeline minutes quota for this group'
         all_or_none_of :ldap_cn, :ldap_access
       end
 
-- 
GitLab


From 46af4ac6ee4f86eab917561b0cace21b025e33a8 Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin <godfat@godfat.org>
Date: Thu, 25 May 2017 21:13:48 +0800
Subject: [PATCH 07/13] Use GroupDetail for POST /groups

Feedback:
https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1942#note_30680817
---
 lib/api/groups.rb                | 2 +-
 spec/requests/api/groups_spec.rb | 4 +---
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 4be02c4fbb824..9f597ff316953 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -104,7 +104,7 @@ def present_groups(groups, options = {})
             )
           end
 
-          present group, with: Entities::Group, current_user: current_user
+          present group, with: Entities::GroupDetail, current_user: current_user
         else
           render_api_error!("Failed to save group #{group.errors.messages}", 400)
         end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 355bf287750a0..f5e580684f555 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -513,10 +513,8 @@
 
             post api("/groups", admin), group
 
-            created_group = Group.find(json_response['id'])
-
             expect(response).to have_http_status(201)
-            expect(created_group.shared_runners_minutes_limit).to eq(133)
+            expect(json_response['shared_runners_minutes_limit']).to eq(133)
           end
         end
       end
-- 
GitLab


From 631f7026fc0134ae1e6882df429162d23d4bd992 Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin <godfat@godfat.org>
Date: Fri, 26 May 2017 18:11:23 +0800
Subject: [PATCH 08/13] Don't check for admin permission if we're not

going to change shared_runners_minutes_limit

Feedback:
https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1942/diffs#note_30680494
---
 lib/api/groups.rb                | 5 ++++-
 spec/requests/api/groups_spec.rb | 8 ++++++++
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 9f597ff316953..722f3eb609f55 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -128,7 +128,10 @@ def present_groups(groups, options = {})
         authorize! :admin_group, group
 
         # EE
-        authenticated_as_admin! if params[:shared_runners_minutes_limit]
+        if params[:shared_runners_minutes_limit].to_i !=
+            group.shared_runners_minutes_limit.to_i
+          authenticated_as_admin!
+        end
 
         if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute
           present group, with: Entities::GroupDetail, current_user: current_user
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index f5e580684f555..6b03d72a02c24 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -279,6 +279,14 @@
 
         expect(response).to have_http_status(403)
       end
+
+      it 'returns 200 if shared_runners_minutes_limit is not changing' do
+        group1.update(shared_runners_minutes_limit: 133)
+
+        put api("/groups/#{group1.id}", user1), shared_runners_minutes_limit: 133
+
+        expect(response).to have_http_status(200)
+      end
     end
 
     context 'when authenticated as the admin' do
-- 
GitLab


From 14af431a07b87f9bc798d9e98ce18325b82407c2 Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin <godfat@godfat.org>
Date: Thu, 1 Jun 2017 02:43:26 +0800
Subject: [PATCH 09/13] Add 3 delegation tests for user

Feedback:
https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1942/diffs#note_30950569
---
 spec/models/user_spec.rb | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 61c239f305105..53343606b6a82 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -13,6 +13,14 @@
     it { is_expected.to include_module(TokenAuthenticatable) }
   end
 
+  describe 'delegations' do
+    it { is_expected.to delegate_method(:path).to(:namespace).with_prefix }
+
+    # EE
+    it { is_expected.to delegate_method(:shared_runners_minutes_limit).to(:namespace) }
+    it { is_expected.to delegate_method(:shared_runners_minutes_limit=).to(:namespace).with_arguments(133) }
+  end
+
   describe 'associations' do
     it { is_expected.to have_one(:namespace) }
     it { is_expected.to have_many(:snippets).dependent(:destroy) }
-- 
GitLab


From 0ed083662df7d6b6bec97424e1558d48a3444e6d Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin <godfat@godfat.org>
Date: Thu, 1 Jun 2017 03:13:13 +0800
Subject: [PATCH 10/13] Move EE specific delegation to EE::User

---
 app/models/ee/user.rb | 3 +++
 app/models/user.rb    | 4 ----
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/app/models/ee/user.rb b/app/models/ee/user.rb
index cd578d2905d75..cf96291a0d28b 100644
--- a/app/models/ee/user.rb
+++ b/app/models/ee/user.rb
@@ -15,6 +15,9 @@ module User
       # column directly.
       validate :auditor_requires_license_add_on, if: :auditor
       validate :cannot_be_admin_and_auditor
+
+      delegate :shared_runners_minutes_limit, :shared_runners_minutes_limit=,
+               to: :namespace
     end
 
     module ClassMethods
diff --git a/app/models/user.rb b/app/models/user.rb
index dba16faf1bdc6..9bb594446ef54 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -190,10 +190,6 @@ def update_tracked_fields!(request)
 
   delegate :path, to: :namespace, allow_nil: true, prefix: true
 
-  # EE-only
-  delegate :shared_runners_minutes_limit, :shared_runners_minutes_limit=,
-           to: :namespace
-
   accepts_nested_attributes_for :namespace
 
   state_machine :state, initial: :active do
-- 
GitLab


From eaa4bfa7aa9aa317a4497b4e926fd1e4184b210e Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin <godfat@godfat.org>
Date: Sat, 3 Jun 2017 03:21:13 +0800
Subject: [PATCH 11/13] Fix updating shared_runners_minutes_limit; Check
 effects

made by the API. TODO: Need to do this for CE and all
the API tests as well
---
 lib/api/groups.rb                | 10 +++++++---
 spec/requests/api/groups_spec.rb | 24 +++++++++++++++++++-----
 spec/requests/api/users_spec.rb  | 20 ++++++++++++++------
 3 files changed, 40 insertions(+), 14 deletions(-)

diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 722f3eb609f55..b439646683f3a 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -128,9 +128,13 @@ def present_groups(groups, options = {})
         authorize! :admin_group, group
 
         # EE
-        if params[:shared_runners_minutes_limit].to_i !=
-            group.shared_runners_minutes_limit.to_i
-          authenticated_as_admin!
+        if params[:shared_runners_minutes_limit]
+          group.shared_runners_minutes_limit =
+            params[:shared_runners_minutes_limit].to_i
+
+          if group.shared_runners_minutes_limit_changed?
+            authenticated_as_admin!
+          end
         end
 
         if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 6b03d72a02c24..5204844c55070 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -275,7 +275,9 @@
 
       # EE
       it 'returns 403 for updating shared_runners_minutes_limit' do
-        put api("/groups/#{group1.id}", user1), shared_runners_minutes_limit: 133
+        expect do
+          put api("/groups/#{group1.id}", user1), shared_runners_minutes_limit: 133
+        end.not_to change { group1.shared_runners_minutes_limit }
 
         expect(response).to have_http_status(403)
       end
@@ -283,7 +285,9 @@
       it 'returns 200 if shared_runners_minutes_limit is not changing' do
         group1.update(shared_runners_minutes_limit: 133)
 
-        put api("/groups/#{group1.id}", user1), shared_runners_minutes_limit: 133
+        expect do
+          put api("/groups/#{group1.id}", user1), shared_runners_minutes_limit: 133
+        end.not_to change { group1.shared_runners_minutes_limit }
 
         expect(response).to have_http_status(200)
       end
@@ -299,7 +303,10 @@
 
       # EE
       it 'updates the group for shared_runners_minutes_limit' do
-        put api("/groups/#{group1.id}", admin), shared_runners_minutes_limit: 133
+        expect do
+          put api("/groups/#{group1.id}", admin), shared_runners_minutes_limit: 133
+        end.to change { group1.reload.shared_runners_minutes_limit }
+          .from(nil).to(133)
 
         expect(response).to have_http_status(200)
         expect(json_response['shared_runners_minutes_limit']).to eq(133)
@@ -509,7 +516,9 @@
           it "does not create a group with shared_runners_minutes_limit" do
             group = attributes_for(:group, { shared_runners_minutes_limit: 133 })
 
-            post api("/groups", user3), group
+            expect do
+              post api("/groups", user3), group
+            end.not_to change { Group.count }
 
             expect(response).to have_http_status(403)
           end
@@ -519,8 +528,13 @@
           it "creates a group with shared_runners_minutes_limit" do
             group = attributes_for(:group, { shared_runners_minutes_limit: 133 })
 
-            post api("/groups", admin), group
+            expect do
+              post api("/groups", admin), group
+            end.to change { Group.count }.by(1)
 
+            created_group = Group.find(json_response['id'])
+
+            expect(created_group.shared_runners_minutes_limit).to eq(133)
             expect(response).to have_http_status(201)
             expect(json_response['shared_runners_minutes_limit']).to eq(133)
           end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index c13d621512a0b..f4699da829bb7 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -425,13 +425,15 @@
       expect(user.reload.external?).to be_truthy
     end
 
+    # EE
     it "updates shared_runners_minutes_limit" do
-      put api("/users/#{user.id}", admin), { shared_runners_minutes_limit: 133 }
+      expect do
+        put api("/users/#{user.id}", admin), { shared_runners_minutes_limit: 133 }
+      end.to change { user.reload.shared_runners_minutes_limit }
+        .from(nil).to(133)
 
       expect(response).to have_http_status(200)
-      expect(json_response['shared_runners_minutes_limit'])
-        .to eq(133)
-      expect(user.reload.shared_runners_minutes_limit).to eq(133)
+      expect(json_response['shared_runners_minutes_limit']).to eq(133)
     end
 
     it "does not update admin status" do
@@ -449,12 +451,18 @@
 
     context 'when the current user is not an admin' do
       it "is not available" do
-        put api("/users/#{user.id}", user), attributes_for(:user)
+        expect do
+          put api("/users/#{user.id}", user), attributes_for(:user)
+        end.not_to change { user.reload.attributes }
+
         expect(response).to have_http_status(403)
       end
 
       it "cannot update their own shared_runners_minutes_limit" do
-        put api("/users/#{user.id}", user), { shared_runners_minutes_limit: 133 }
+        expect do
+          put api("/users/#{user.id}", user), { shared_runners_minutes_limit: 133 }
+        end.not_to change { user.reload.shared_runners_minutes_limit }
+
         expect(response).to have_http_status(403)
       end
     end
-- 
GitLab


From 9a44c9a6bed95a62dda3236e23615eec3d521987 Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin <godfat@godfat.org>
Date: Mon, 5 Jun 2017 16:52:57 +0800
Subject: [PATCH 12/13] Just check the value, simple and stupid

Feedback:
https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1942#note_31511130
---
 lib/api/groups.rb | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index b439646683f3a..5cdfaeb435308 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -128,13 +128,10 @@ def present_groups(groups, options = {})
         authorize! :admin_group, group
 
         # EE
-        if params[:shared_runners_minutes_limit]
-          group.shared_runners_minutes_limit =
-            params[:shared_runners_minutes_limit].to_i
-
-          if group.shared_runners_minutes_limit_changed?
-            authenticated_as_admin!
-          end
+        if params[:shared_runners_minutes_limit].present? &&
+            group.shared_runners_minutes_limit.to_i !=
+              params[:shared_runners_minutes_limit].to_i
+          authenticated_as_admin!
         end
 
         if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute
-- 
GitLab


From c14bc19d11ba8dc034e9428652d664a87f782255 Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin <godfat@godfat.org>
Date: Mon, 5 Jun 2017 17:39:38 +0800
Subject: [PATCH 13/13] I think Rubocop Style/MultilineOperationIndentation is
 broken

---
 lib/api/groups.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 5cdfaeb435308..4b3abc7a752c8 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -130,7 +130,7 @@ def present_groups(groups, options = {})
         # EE
         if params[:shared_runners_minutes_limit].present? &&
             group.shared_runners_minutes_limit.to_i !=
-              params[:shared_runners_minutes_limit].to_i
+                params[:shared_runners_minutes_limit].to_i
           authenticated_as_admin!
         end
 
-- 
GitLab