From af2a9da12e96e9f455238131212e53c80fe7bfa6 Mon Sep 17 00:00:00 2001
From: Vijay Hawoldar <vhawoldar@gitlab.com>
Date: Thu, 20 Jul 2023 17:20:33 +0000
Subject: [PATCH] Fix repository push error message

Adds a new error message handling class to return the correct push
error/warning messages for repository size limits

Changelog: fixed
EE: true
---
 .../namespaces/storage/root_excess_size.rb    |  8 +--
 .../gitlab/root_excess_size_error_message.rb  | 53 ++++++++++++++++++
 .../root_excess_size_error_message_spec.rb    | 55 +++++++++++++++++++
 .../services/ee/post_receive_service_spec.rb  | 54 +++++++++++-------
 .../helpers/repository_storage_helpers.rb     | 16 ------
 lib/gitlab/repository_size_error_message.rb   | 32 +----------
 6 files changed, 145 insertions(+), 73 deletions(-)
 create mode 100644 ee/lib/gitlab/root_excess_size_error_message.rb
 create mode 100644 ee/spec/lib/gitlab/root_excess_size_error_message_spec.rb
 delete mode 100644 ee/spec/support/helpers/repository_storage_helpers.rb

diff --git a/ee/app/models/namespaces/storage/root_excess_size.rb b/ee/app/models/namespaces/storage/root_excess_size.rb
index 60c8e7dcd6c0..1dcbb1e6edc2 100644
--- a/ee/app/models/namespaces/storage/root_excess_size.rb
+++ b/ee/app/models/namespaces/storage/root_excess_size.rb
@@ -5,6 +5,8 @@ module Storage
     class RootExcessSize
       include ::Gitlab::Utils::StrongMemoize
 
+      attr_reader :root_namespace
+
       def initialize(root_namespace)
         @root_namespace = root_namespace.root_ancestor # just in case the true root isn't passed
       end
@@ -46,16 +48,12 @@ def enforce_limit?
       def error_message
         message_params = { namespace_name: root_namespace.name }
 
-        @error_message_object ||= ::Gitlab::RepositorySizeErrorMessage.new(self, message_params)
+        @error_message_object ||= ::Gitlab::RootExcessSizeErrorMessage.new(self, message_params)
       end
 
       def enforcement_type
         :project_repository_limit
       end
-
-      private
-
-      attr_reader :root_namespace
     end
   end
 end
diff --git a/ee/lib/gitlab/root_excess_size_error_message.rb b/ee/lib/gitlab/root_excess_size_error_message.rb
new file mode 100644
index 000000000000..57d8206ca937
--- /dev/null
+++ b/ee/lib/gitlab/root_excess_size_error_message.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module Gitlab
+  class RootExcessSizeErrorMessage # rubocop:disable Gitlab/NamespacedClass
+    include ActiveSupport::NumberHelper
+
+    def initialize(checker, message_params = {})
+      @checker = checker
+      @current_size = formatted(checker.current_size)
+      @size_limit = formatted(checker.limit)
+      @namespace_name = message_params[:namespace_name]
+    end
+
+    def push_warning
+      <<~MSG.squish
+        ##### WARNING ##### You have used #{usage_percentage} of the storage quota for this project
+        (#{current_size} of #{size_limit}). If a project reaches 100% of the storage quota (#{size_limit})
+        the project will be in a read-only state, and you won't be able to push to your repository or add large files.
+        To reduce storage usage, reduce git repository and git LFS storage. For more information about storage limits,
+        see our docs: #{limit_docs_url}.
+      MSG
+    end
+
+    def push_error
+      <<~MSG.squish
+        You have reached the free storage limit of #{repository_limit} on one or more projects.
+        To unlock your projects over the free #{repository_limit} project limit, you must purchase
+        additional storage. You can't push to your repository, create pipelines, create issues, or add comments.
+        To reduce storage capacity, you can delete unused repositories, artifacts, wikis, issues, and pipelines.
+      MSG
+    end
+
+    private
+
+    attr_reader :checker, :current_size, :size_limit, :namespace_name
+
+    def usage_percentage
+      number_to_percentage(checker.usage_ratio * 100, precision: 0)
+    end
+
+    def limit_docs_url
+      ::Gitlab::Routing.url_helpers.help_page_url('user/usage_quotas', anchor: 'project-storage-limit')
+    end
+
+    def formatted(number)
+      number_to_human_size(number, delimiter: ',', precision: 2)
+    end
+
+    def repository_limit
+      formatted(checker.root_namespace.actual_repository_size_limit)
+    end
+  end
+end
diff --git a/ee/spec/lib/gitlab/root_excess_size_error_message_spec.rb b/ee/spec/lib/gitlab/root_excess_size_error_message_spec.rb
new file mode 100644
index 000000000000..af0afe855b9f
--- /dev/null
+++ b/ee/spec/lib/gitlab/root_excess_size_error_message_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::RootExcessSizeErrorMessage, feature_category: :consumables_cost_management do
+  let(:namespace) { build(:namespace, additional_purchased_storage_size: limit) }
+  let(:checker) { Namespaces::Storage::RootExcessSize.new(namespace) }
+  let(:current_size) { 9.megabytes }
+  let(:limit) { 10 }
+  let(:message_params) do
+    {
+      size_limit: limit,
+      namespace_name: namespace.name
+    }
+  end
+
+  before do
+    allow(namespace).to receive(:total_repository_size_excess).and_return(current_size)
+  end
+
+  subject(:message) { described_class.new(checker, message_params) }
+
+  describe '#push_warning' do
+    it 'returns the correct message' do
+      expect(message.push_warning)
+        .to eq(
+          <<~MSG.squish
+            ##### WARNING ##### You have used 90% of the storage quota for this project
+            (9 MiB of 10 MiB). If a project reaches 100% of the storage quota (10 MiB)
+            the project will be in a read-only state, and you won't be able to push to your repository or add large files.
+            To reduce storage usage, reduce git repository and git LFS storage. For more information about storage limits,
+            see our docs: http://localhost/help/user/usage_quotas#project-storage-limit.
+          MSG
+        )
+    end
+  end
+
+  describe '#push_error' do
+    before do
+      stub_ee_application_setting(repository_size_limit: 10.gigabytes)
+    end
+
+    it 'returns the correct message' do
+      expect(message.push_error)
+        .to eq(
+          <<~MSG.squish
+            You have reached the free storage limit of 10 GiB on one or more projects.
+            To unlock your projects over the free 10 GiB project limit, you must purchase
+            additional storage. You can't push to your repository, create pipelines, create issues, or add comments.
+            To reduce storage capacity, you can delete unused repositories, artifacts, wikis, issues, and pipelines.
+          MSG
+        )
+    end
+  end
+end
diff --git a/ee/spec/services/ee/post_receive_service_spec.rb b/ee/spec/services/ee/post_receive_service_spec.rb
index 2aa07666f499..0ba141f6851d 100644
--- a/ee/spec/services/ee/post_receive_service_spec.rb
+++ b/ee/spec/services/ee/post_receive_service_spec.rb
@@ -125,26 +125,37 @@
       context 'when repository size limit enforcement' do
         let(:user) { project.namespace.owner }
 
-        include RepositoryStorageHelpers
+        before do
+          stub_feature_flags(namespace_storage_limit: false)
+        end
 
-        it 'returns error message' do
-          stub_over_repository_limit(project.root_ancestor)
+        context 'when a project in the namespace is over the limit' do
+          before do
+            stub_ee_application_setting(repository_size_limit: 10.gigabytes)
 
-          expect(subject).to match_array([
-            {
-              "message" =>
-                "Your push has been rejected, because this repository " \
-                "has exceeded its size limit of 10 B by 45 B. " \
-                "Please contact your GitLab administrator for more information.",
-              "type" => "alert"
-            }
-          ])
+            allow_next_instance_of(Namespaces::Storage::RootExcessSize) do |root_storage_size|
+              allow(root_storage_size).to receive(:current_size).and_return(55)
+              allow(root_storage_size).to receive(:limit).and_return(10)
+            end
+          end
+
+          it 'returns error message' do
+            expect(subject).to match_array([
+              {
+                "message" =>
+                  <<~MSG.squish,
+                    You have reached the free storage limit of 10 GiB on one or more projects.
+                    To unlock your projects over the free 10 GiB project limit, you must purchase
+                    additional storage. You can't push to your repository, create pipelines, create issues, or add comments.
+                    To reduce storage capacity, you can delete unused repositories, artifacts, wikis, issues, and pipelines.
+                  MSG
+                "type" => "alert"
+              }
+            ])
+          end
         end
 
         it 'returns warning message when under storage limit' do
-          namespace = project.root_ancestor
-
-          stub_feature_flags(namespace_storage_limit: false)
           allow_next_instance_of(Namespaces::Storage::RootExcessSize) do |root_storage_size|
             allow(root_storage_size).to receive(:usage_ratio).and_return(0.95)
           end
@@ -152,12 +163,13 @@
           expect(subject).to match_array([
             {
               "message" =>
-                "##### WARNING ##### You have used 95% of the storage quota for #{namespace.name} " \
-                "(0 B of 0 B). If #{namespace.name} exceeds the storage quota, all projects " \
-                "in the namespace will be locked and actions will be restricted. To manage storage, " \
-                "or purchase additional storage, see " \
-                "http://localhost/help/user/usage_quotas#manage-your-storage-usage. To learn more about " \
-                "restricted actions, see http://localhost/help/user/read_only_namespaces#restricted-actions",
+                <<~MSG.squish,
+                  ##### WARNING ##### You have used 95% of the storage quota for this project
+                  (0 B of 0 B). If a project reaches 100% of the storage quota (0 B)
+                  the project will be in a read-only state, and you won't be able to push to your repository or add large files.
+                  To reduce storage usage, reduce git repository and git LFS storage. For more information about storage limits,
+                  see our docs: http://localhost/help/user/usage_quotas#project-storage-limit.
+                MSG
               "type" => "alert"
             }
           ])
diff --git a/ee/spec/support/helpers/repository_storage_helpers.rb b/ee/spec/support/helpers/repository_storage_helpers.rb
deleted file mode 100644
index 0b5ea08d79c6..000000000000
--- a/ee/spec/support/helpers/repository_storage_helpers.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-module RepositoryStorageHelpers
-  def stub_over_repository_limit(namespace)
-    stub_feature_flags(namespace_storage_limit: false)
-    allow(namespace.root_ancestor).to receive(:contains_locked_projects?).and_return(true)
-    allow(namespace.root_ancestor).to receive(:repository_size_excess_project_count).and_return(5)
-    allow(namespace.root_ancestor).to receive(:actual_repository_size_limit).and_return(10)
-    allow_next_instance_of(Namespaces::Storage::RootExcessSize) do |root_storage_size|
-      allow(root_storage_size).to receive(:above_size_limit?).and_return(true)
-      allow(root_storage_size).to receive(:usage_ratio).and_return(5.5).at_least(:once)
-      allow(root_storage_size).to receive(:current_size).and_return(55)
-      allow(root_storage_size).to receive(:limit).and_return(10)
-    end
-  end
-end
diff --git a/lib/gitlab/repository_size_error_message.rb b/lib/gitlab/repository_size_error_message.rb
index e7d527dd4ce5..fc700c3c62e3 100644
--- a/lib/gitlab/repository_size_error_message.rb
+++ b/lib/gitlab/repository_size_error_message.rb
@@ -7,8 +7,7 @@ class RepositorySizeErrorMessage
     delegate :current_size, :limit, :exceeded_size, :additional_repo_storage_available?, to: :@checker
 
     # @param checker [RepositorySizeChecker]
-    def initialize(checker, message_params = {})
-      @message_params = message_params
+    def initialize(checker)
       @checker = checker
     end
 
@@ -20,14 +19,6 @@ def merge_error
       "This merge request cannot be merged, #{base_message}"
     end
 
-    def push_warning
-      _("##### WARNING ##### You have used %{usage_percentage} of the storage quota for %{namespace_name} " \
-        "(%{current_size} of %{size_limit}). If %{namespace_name} exceeds the storage quota, " \
-        "all projects in the namespace will be locked and actions will be restricted. " \
-        "To manage storage, or purchase additional storage, see %{manage_storage_url}. " \
-        "To learn more about restricted actions, see %{restricted_actions_url}") % push_message_params
-    end
-
     def push_error(change_size = 0)
       "Your push has been rejected, #{base_message(change_size)}. #{more_info_message}"
     end
@@ -50,19 +41,6 @@ def above_size_limit_message
 
     private
 
-    attr_reader :message_params
-
-    def push_message_params
-      {
-        namespace_name: message_params[:namespace_name],
-        manage_storage_url: help_page_url('user/usage_quotas', 'manage-your-storage-usage'),
-        restricted_actions_url: help_page_url('user/read_only_namespaces', 'restricted-actions'),
-        current_size: formatted(current_size),
-        size_limit: formatted(limit),
-        usage_percentage: usage_percentage
-      }
-    end
-
     def base_message(change_size = 0)
       "because this repository has exceeded its size limit of #{formatted(limit)} by #{formatted(exceeded_size(change_size))}"
     end
@@ -70,13 +48,5 @@ def base_message(change_size = 0)
     def formatted(number)
       number_to_human_size(number, delimiter: ',', precision: 2)
     end
-
-    def usage_percentage
-      number_to_percentage(@checker.usage_ratio * 100, precision: 0)
-    end
-
-    def help_page_url(path, anchor = nil)
-      ::Gitlab::Routing.url_helpers.help_page_url(path, anchor: anchor)
-    end
   end
 end
-- 
GitLab