From 4e249d5baea99a52915025fc2827d01ab2d77c18 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9my=20Coutable?= <remy@rymai.me>
Date: Fri, 2 Dec 2016 13:54:57 +0100
Subject: [PATCH] Use :maximum instead of :within for length validators with a
 0..N range
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Rémy Coutable <remy@rymai.me>
---
 app/models/ci/variable.rb                     |  4 ++--
 app/models/concerns/issuable.rb               |  2 +-
 app/models/environment.rb                     |  2 +-
 app/models/key.rb                             | 16 +++++++++----
 app/models/namespace.rb                       | 14 +++++------
 app/models/project.rb                         |  4 ++--
 app/models/snippet.rb                         |  8 +++++--
 .../lib/gitlab/github_import/importer_spec.rb |  2 +-
 spec/models/ci/variable_spec.rb               |  7 ++++++
 spec/models/concerns/issuable_spec.rb         |  2 +-
 spec/models/environment_spec.rb               |  4 ++--
 spec/models/key_spec.rb                       |  8 +++++--
 spec/models/namespace_spec.rb                 | 13 +++++++---
 spec/models/project_spec.rb                   | 10 +++++---
 spec/models/snippet_spec.rb                   | 24 +++++++++++++++++--
 spec/models/user_spec.rb                      |  2 +-
 spec/requests/api/deploy_keys_spec.rb         |  4 +---
 spec/support/matchers/is_within.rb            |  9 -------
 18 files changed, 89 insertions(+), 46 deletions(-)
 delete mode 100644 spec/support/matchers/is_within.rb

diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index 94d9e2b3208..2c8698d8b5d 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -4,10 +4,10 @@ module Ci
 
     belongs_to :project, foreign_key: :gl_project_id
 
-    validates_uniqueness_of :key, scope: :gl_project_id
     validates :key,
       presence: true,
-      length: { within: 0..255 },
+      uniqueness: { scope: :gl_project_id },
+      length: { maximum: 255 },
       format: { with: /\A[a-zA-Z0-9_]+\z/,
                 message: "can contain only letters, digits and '_'." }
 
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 69d8afc45da..0ea7b1b1098 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -41,7 +41,7 @@ module Issuable
     has_one :metrics
 
     validates :author, presence: true
-    validates :title, presence: true, length: { within: 0..255 }
+    validates :title, presence: true, length: { maximum: 255 }
 
     scope :authored, ->(user) { where(author_id: user) }
     scope :assigned_to, ->(u) { where(assignee_id: u.id)}
diff --git a/app/models/environment.rb b/app/models/environment.rb
index a7f4156fc2e..96700143ddd 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -9,7 +9,7 @@ class Environment < ActiveRecord::Base
   validates :name,
             presence: true,
             uniqueness: { scope: :project_id },
-            length: { within: 0..255 },
+            length: { maximum: 255 },
             format: { with: Gitlab::Regex.environment_name_regex,
                       message: Gitlab::Regex.environment_name_regex_message }
 
diff --git a/app/models/key.rb b/app/models/key.rb
index ff8dda2dc89..a5d25409730 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -8,10 +8,18 @@ class Key < ActiveRecord::Base
 
   before_validation :generate_fingerprint
 
-  validates :title, presence: true, length: { within: 0..255 }
-  validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }
-  validates :key, format: { without: /\n|\r/, message: 'should be a single line' }
-  validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' }
+  validates :title,
+    presence: true,
+    length: { maximum: 255 }
+  validates :key,
+    presence: true,
+    length: { maximum: 5000 },
+    format: { with: /\A(ssh|ecdsa)-.*\Z/ }
+  validates :key,
+    format: { without: /\n|\r/, message: 'should be a single line' }
+  validates :fingerprint,
+    uniqueness: true,
+    presence: { message: 'cannot be generated' }
 
   delegate :name, :email, to: :user, prefix: true
 
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 891dffac648..7a545f752b6 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -12,17 +12,17 @@ class Namespace < ActiveRecord::Base
 
   validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
   validates :name,
-    length: { within: 0..255 },
-    namespace_name: true,
     presence: true,
-    uniqueness: true
+    uniqueness: true,
+    length: { maximum: 255 },
+    namespace_name: true
 
-  validates :description, length: { within: 0..255 }
+  validates :description, length: { maximum: 255 }
   validates :path,
-    length: { within: 1..255 },
-    namespace: true,
     presence: true,
-    uniqueness: { case_sensitive: false }
+    uniqueness: { case_sensitive: false },
+    length: { maximum: 255 },
+    namespace: true
 
   delegate :name, to: :owner, allow_nil: true, prefix: true
 
diff --git a/app/models/project.rb b/app/models/project.rb
index f01cb613b85..e783e455a49 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -172,13 +172,13 @@ class Project < ActiveRecord::Base
   validates :description, length: { maximum: 2000 }, allow_blank: true
   validates :name,
     presence: true,
-    length: { within: 0..255 },
+    length: { maximum: 255 },
     format: { with: Gitlab::Regex.project_name_regex,
               message: Gitlab::Regex.project_name_regex_message }
   validates :path,
     presence: true,
     project_path: true,
-    length: { within: 0..255 },
+    length: { maximum: 255 },
     format: { with: Gitlab::Regex.project_path_regex,
               message: Gitlab::Regex.project_path_regex_message }
   validates :namespace, presence: true
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 8ff4e7ae718..99493e561ab 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -27,9 +27,9 @@ class Snippet < ActiveRecord::Base
   delegate :name, :email, to: :author, prefix: true, allow_nil: true
 
   validates :author, presence: true
-  validates :title, presence: true, length: { within: 0..255 }
+  validates :title, presence: true, length: { maximum: 255 }
   validates :file_name,
-    length: { within: 0..255 },
+    length: { maximum: 255 },
     format: { with: Gitlab::Regex.file_name_regex,
               message: Gitlab::Regex.file_name_regex_message }
 
@@ -94,6 +94,10 @@ class Snippet < ActiveRecord::Base
     0
   end
 
+  def file_name
+    super.to_s
+  end
+
   # alias for compatibility with blobs and highlighting
   def path
     file_name
diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb
index 000b9aa6f83..9e027839f59 100644
--- a/spec/lib/gitlab/github_import/importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer_spec.rb
@@ -155,7 +155,7 @@ describe Gitlab::GithubImport::Importer, lib: true do
           message: 'The remote data could not be fully imported.',
           errors: [
             { type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title can't be blank, Title is invalid" },
-            { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank, Title is too short (minimum is 0 characters)" },
+            { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank" },
             { type: :wiki, errors: "Gitlab::Shell::Error" },
             { type: :release, url: 'https://api.github.com/repos/octocat/Hello-World/releases/2', errors: "Validation failed: Description can't be blank" }
           ]
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index 4e7833c3162..bee9f714849 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -5,6 +5,13 @@ describe Ci::Variable, models: true do
 
   let(:secret_value) { 'secret' }
 
+  it { is_expected.to validate_presence_of(:key) }
+  it { is_expected.to validate_uniqueness_of(:key).scoped_to(:gl_project_id) }
+  it { is_expected.to validate_length_of(:key).is_at_most(255) }
+  it { is_expected.to allow_value('foo').for(:key) }
+  it { is_expected.not_to allow_value('foo bar').for(:key) }
+  it { is_expected.not_to allow_value('foo/bar').for(:key) }
+
   before :each do
     subject.value = secret_value
   end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 6f84bffe046..4fa06a8c60a 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -35,7 +35,7 @@ describe Issue, "Issuable" do
     it { is_expected.to validate_presence_of(:iid) }
     it { is_expected.to validate_presence_of(:author) }
     it { is_expected.to validate_presence_of(:title) }
-    it { is_expected.to validate_length_of(:title).is_at_least(0).is_at_most(255) }
+    it { is_expected.to validate_length_of(:title).is_at_most(255) }
   end
 
   describe "Scope" do
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index d06665197db..c8170164898 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -13,9 +13,9 @@ describe Environment, models: true do
 
   it { is_expected.to validate_presence_of(:name) }
   it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) }
-  it { is_expected.to validate_length_of(:name).is_within(0..255) }
+  it { is_expected.to validate_length_of(:name).is_at_most(255) }
 
-  it { is_expected.to validate_length_of(:external_url).is_within(0..255) }
+  it { is_expected.to validate_length_of(:external_url).is_at_most(255) }
 
   # To circumvent a not null violation of the name column:
   # https://github.com/thoughtbot/shoulda-matchers/issues/336
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 90731f55470..2a33d819138 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -7,9 +7,13 @@ describe Key, models: true do
 
   describe "Validation" do
     it { is_expected.to validate_presence_of(:title) }
+    it { is_expected.to validate_length_of(:title).is_at_most(255) }
+
     it { is_expected.to validate_presence_of(:key) }
-    it { is_expected.to validate_length_of(:title).is_within(0..255) }
-    it { is_expected.to validate_length_of(:key).is_within(0..5000) }
+    it { is_expected.to validate_length_of(:key).is_at_most(5000) }
+    it { is_expected.to allow_value('ssh-foo').for(:key) }
+    it { is_expected.to allow_value('ecdsa-foo').for(:key) }
+    it { is_expected.not_to allow_value('foo-bar').for(:key) }
   end
 
   describe "Methods" do
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 431b3e4435f..ba0ed4a3603 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -4,11 +4,18 @@ describe Namespace, models: true do
   let!(:namespace) { create(:namespace) }
 
   it { is_expected.to have_many :projects }
-  it { is_expected.to validate_presence_of :name }
+
+  it { is_expected.to validate_presence_of(:name) }
   it { is_expected.to validate_uniqueness_of(:name) }
-  it { is_expected.to validate_presence_of :path }
+  it { is_expected.to validate_length_of(:name).is_at_most(255) }
+
+  it { is_expected.to validate_length_of(:description).is_at_most(255) }
+
+  it { is_expected.to validate_presence_of(:path) }
   it { is_expected.to validate_uniqueness_of(:path) }
-  it { is_expected.to validate_presence_of :owner }
+  it { is_expected.to validate_length_of(:path).is_at_most(255) }
+
+  it { is_expected.to validate_presence_of(:owner) }
 
   describe "Mass assignment" do
   end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 8abcce42ce0..f7c8c97fdee 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -131,14 +131,18 @@ describe Project, models: true do
 
     it { is_expected.to validate_presence_of(:name) }
     it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) }
-    it { is_expected.to validate_length_of(:name).is_within(0..255) }
+    it { is_expected.to validate_length_of(:name).is_at_most(255) }
 
     it { is_expected.to validate_presence_of(:path) }
     it { is_expected.to validate_uniqueness_of(:path).scoped_to(:namespace_id) }
-    it { is_expected.to validate_length_of(:path).is_within(0..255) }
-    it { is_expected.to validate_length_of(:description).is_within(0..2000) }
+    it { is_expected.to validate_length_of(:path).is_at_most(255) }
+
+    it { is_expected.to validate_length_of(:description).is_at_most(2000) }
+
     it { is_expected.to validate_presence_of(:creator) }
+
     it { is_expected.to validate_presence_of(:namespace) }
+
     it { is_expected.to validate_presence_of(:repository_storage) }
 
     it 'does not allow new projects beyond user limits' do
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index f62f6bacbaa..279dc30c357 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -23,9 +23,9 @@ describe Snippet, models: true do
     it { is_expected.to validate_presence_of(:author) }
 
     it { is_expected.to validate_presence_of(:title) }
-    it { is_expected.to validate_length_of(:title).is_within(0..255) }
+    it { is_expected.to validate_length_of(:title).is_at_most(255) }
 
-    it { is_expected.to validate_length_of(:file_name).is_within(0..255) }
+    it { is_expected.to validate_length_of(:file_name).is_at_most(255) }
 
     it { is_expected.to validate_presence_of(:content) }
 
@@ -46,6 +46,26 @@ describe Snippet, models: true do
     end
   end
 
+  describe '#file_name' do
+    let(:project) { create(:empty_project) }
+
+    context 'file_name is nil' do
+      let(:snippet) { create(:snippet, project: project, file_name: nil) }
+
+      it 'returns an empty string' do
+        expect(snippet.file_name).to eq ''
+      end
+    end
+
+    context 'file_name is not nil' do
+      let(:snippet) { create(:snippet, project: project, file_name: 'foo.txt') }
+
+      it 'returns the file_name' do
+        expect(snippet.file_name).to eq 'foo.txt'
+      end
+    end
+  end
+
   describe "#content_html_invalidated?" do
     let(:snippet) { create(:snippet, content: "md", content_html: "html", file_name: "foo.md") }
     it "invalidates the HTML cache of content when the filename changes" do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 14c891994d0..95fe2dc65d9 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -79,7 +79,7 @@ describe User, models: true do
     it { is_expected.to allow_value(0).for(:projects_limit) }
     it { is_expected.not_to allow_value(-1).for(:projects_limit) }
 
-    it { is_expected.to validate_length_of(:bio).is_within(0..255) }
+    it { is_expected.to validate_length_of(:bio).is_at_most(255) }
 
     it_behaves_like 'an object with email-formated attributes', :email do
       subject { build(:user) }
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 5fa7299044e..aabab8e6ae6 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -75,7 +75,6 @@ describe API::DeployKeys, api: true  do
       expect(response).to have_http_status(400)
       expect(json_response['message']['key']).to eq([
         'can\'t be blank',
-        'is too short (minimum is 0 characters)',
         'is invalid'
       ])
     end
@@ -85,8 +84,7 @@ describe API::DeployKeys, api: true  do
 
       expect(response).to have_http_status(400)
       expect(json_response['message']['title']).to eq([
-        'can\'t be blank',
-        'is too short (minimum is 0 characters)'
+        'can\'t be blank'
       ])
     end
 
diff --git a/spec/support/matchers/is_within.rb b/spec/support/matchers/is_within.rb
deleted file mode 100644
index 0c35fc7e899..00000000000
--- a/spec/support/matchers/is_within.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# Extend shoulda-matchers
-module Shoulda::Matchers::ActiveModel
-  class ValidateLengthOfMatcher
-    # Shortcut for is_at_least and is_at_most
-    def is_within(range)
-      is_at_least(range.min) && is_at_most(range.max)
-    end
-  end
-end
-- 
GitLab