Commit 75144b1e authored by Douwe Maan's avatar Douwe Maan

Validate path uniqueness only on Route, and bubble up appropriately

parent 8d69436c
...@@ -7,11 +7,12 @@ module Routable ...@@ -7,11 +7,12 @@ module Routable
has_one :route, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_one :route, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
validates_associated :route
validates :route, presence: true validates :route, presence: true
scope :with_route, -> { includes(:route) } scope :with_route, -> { includes(:route) }
after_validation :set_path_errors
before_validation do before_validation do
if full_path_changed? || full_name_changed? if full_path_changed? || full_name_changed?
prepare_route prepare_route
...@@ -125,6 +126,11 @@ module Routable ...@@ -125,6 +126,11 @@ module Routable
private private
def set_path_errors
route_path_errors = self.errors.delete(:"route.path")
self.errors[:path].concat(route_path_errors) if route_path_errors
end
def uncached_full_path def uncached_full_path
if route && route.path.present? if route && route.path.present?
@full_path ||= route.path # rubocop:disable Gitlab/ModuleWithInstanceVariables @full_path ||= route.path # rubocop:disable Gitlab/ModuleWithInstanceVariables
......
...@@ -32,7 +32,6 @@ class Namespace < ActiveRecord::Base ...@@ -32,7 +32,6 @@ class Namespace < ActiveRecord::Base
validates :owner, presence: true, unless: ->(n) { n.type == "Group" } validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
validates :name, validates :name,
presence: true, presence: true,
uniqueness: { scope: :parent_id },
length: { maximum: 255 }, length: { maximum: 255 },
namespace_name: true namespace_name: true
......
...@@ -245,8 +245,7 @@ class Project < ActiveRecord::Base ...@@ -245,8 +245,7 @@ class Project < ActiveRecord::Base
validates :path, validates :path,
presence: true, presence: true,
project_path: true, project_path: true,
length: { maximum: 255 }, length: { maximum: 255 }
uniqueness: { scope: :namespace_id }
validates :namespace, presence: true validates :namespace, presence: true
validates :name, uniqueness: { scope: :namespace_id } validates :name, uniqueness: { scope: :namespace_id }
......
...@@ -75,7 +75,7 @@ class Route < ActiveRecord::Base ...@@ -75,7 +75,7 @@ class Route < ActiveRecord::Base
def ensure_permanent_paths def ensure_permanent_paths
return if path.nil? return if path.nil?
errors.add(:path, "#{path} has been taken before. Please use another one") if conflicting_redirect_exists? errors.add(:path, "has been taken before") if conflicting_redirect_exists?
end end
def conflicting_redirect_exists? def conflicting_redirect_exists?
......
...@@ -153,10 +153,9 @@ class User < ActiveRecord::Base ...@@ -153,10 +153,9 @@ class User < ActiveRecord::Base
numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: Gitlab::Database::MAX_INT_VALUE } numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: Gitlab::Database::MAX_INT_VALUE }
validates :username, validates :username,
user_path: true, user_path: true,
presence: true, presence: true
uniqueness: { case_sensitive: false }
validate :namespace_uniq, if: :username_changed? validates :namespace, presence: true
validate :namespace_move_dir_allowed, if: :username_changed? validate :namespace_move_dir_allowed, if: :username_changed?
validate :unique_email, if: :email_changed? validate :unique_email, if: :email_changed?
...@@ -172,6 +171,7 @@ class User < ActiveRecord::Base ...@@ -172,6 +171,7 @@ class User < ActiveRecord::Base
before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) } before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? } before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? }
before_validation :ensure_namespace_correct before_validation :ensure_namespace_correct
after_validation :set_username_errors
after_update :username_changed_hook, if: :username_changed? after_update :username_changed_hook, if: :username_changed?
after_destroy :post_destroy_hook after_destroy :post_destroy_hook
after_destroy :remove_key_cache after_destroy :remove_key_cache
...@@ -505,17 +505,6 @@ class User < ActiveRecord::Base ...@@ -505,17 +505,6 @@ class User < ActiveRecord::Base
end end
end end
def namespace_uniq
# Return early if username already failed the first uniqueness validation
return if errors.key?(:username) &&
errors[:username].include?('has already been taken')
existing_namespace = Namespace.by_path(username)
if existing_namespace && existing_namespace != namespace
errors.add(:username, 'has already been taken')
end
end
def namespace_move_dir_allowed def namespace_move_dir_allowed
if namespace&.any_project_has_container_registry_tags? if namespace&.any_project_has_container_registry_tags?
errors.add(:username, 'cannot be changed if a personal project has container registry tags.') errors.add(:username, 'cannot be changed if a personal project has container registry tags.')
...@@ -891,6 +880,11 @@ class User < ActiveRecord::Base ...@@ -891,6 +880,11 @@ class User < ActiveRecord::Base
end end
end end
def set_username_errors
namespace_path_errors = self.errors.delete(:"namespace.path")
self.errors[:username].concat(namespace_path_errors) if namespace_path_errors
end
def username_changed_hook def username_changed_hook
system_hook_service.execute_hooks_for(self, :rename) system_hook_service.execute_hooks_for(self, :rename)
end end
......
...@@ -39,7 +39,7 @@ describe Group, 'Routable' do ...@@ -39,7 +39,7 @@ describe Group, 'Routable' do
create(:group, parent: group, path: 'xyz') create(:group, parent: group, path: 'xyz')
duplicate = build(:project, namespace: group, path: 'xyz') duplicate = build(:project, namespace: group, path: 'xyz')
expect { duplicate.save! }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Route path has already been taken, Route is invalid') expect { duplicate.save! }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Path has already been taken')
end end
end end
......
...@@ -41,7 +41,6 @@ describe Group do ...@@ -41,7 +41,6 @@ describe Group do
describe 'validations' do describe 'validations' do
it { is_expected.to validate_presence_of :name } it { is_expected.to validate_presence_of :name }
it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) }
it { is_expected.to validate_presence_of :path } it { is_expected.to validate_presence_of :path }
it { is_expected.not_to validate_presence_of :owner } it { is_expected.not_to validate_presence_of :owner }
it { is_expected.to validate_presence_of :two_factor_grace_period } it { is_expected.to validate_presence_of :two_factor_grace_period }
......
...@@ -15,7 +15,6 @@ describe Namespace do ...@@ -15,7 +15,6 @@ describe Namespace do
describe 'validations' do describe 'validations' do
it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) }
it { is_expected.to validate_length_of(:name).is_at_most(255) } 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_length_of(:description).is_at_most(255) }
it { is_expected.to validate_presence_of(:path) } it { is_expected.to validate_presence_of(:path) }
......
...@@ -129,7 +129,6 @@ describe Project do ...@@ -129,7 +129,6 @@ describe Project do
it { is_expected.to validate_length_of(:name).is_at_most(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_presence_of(:path) }
it { is_expected.to validate_uniqueness_of(:path).scoped_to(:namespace_id) }
it { is_expected.to validate_length_of(:path).is_at_most(255) } 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_length_of(:description).is_at_most(2000) }
......
...@@ -27,7 +27,7 @@ describe Route do ...@@ -27,7 +27,7 @@ describe Route do
redirect.save!(validate: false) redirect.save!(validate: false)
expect(new_route.valid?).to be_falsey expect(new_route.valid?).to be_falsey
expect(new_route.errors.first[1]).to eq('foo has been taken before. Please use another one') expect(new_route.errors.first[1]).to eq('has been taken before')
end end
end end
...@@ -49,7 +49,7 @@ describe Route do ...@@ -49,7 +49,7 @@ describe Route do
redirect.save!(validate: false) redirect.save!(validate: false)
expect(route.valid?).to be_falsey expect(route.valid?).to be_falsey
expect(route.errors.first[1]).to eq('foo has been taken before. Please use another one') expect(route.errors.first[1]).to eq('has been taken before')
end end
end end
...@@ -368,7 +368,7 @@ describe Route do ...@@ -368,7 +368,7 @@ describe Route do
group2.path = 'foo' group2.path = 'foo'
group2.valid? group2.valid?
expect(group2.errors["route.path"].first).to eq('foo has been taken before. Please use another one') expect(group2.errors[:path]).to eq(['has been taken before'])
end end
end end
......
...@@ -116,12 +116,6 @@ describe User do ...@@ -116,12 +116,6 @@ describe User do
expect(user).to be_valid expect(user).to be_valid
end end
it 'validates uniqueness' do
user = build(:user)
expect(user).to validate_uniqueness_of(:username).case_insensitive
end
context 'when username is changed' do context 'when username is changed' do
let(:user) { build_stubbed(:user, username: 'old_path', namespace: build_stubbed(:namespace)) } let(:user) { build_stubbed(:user, username: 'old_path', namespace: build_stubbed(:namespace)) }
...@@ -2287,17 +2281,17 @@ describe User do ...@@ -2287,17 +2281,17 @@ describe User do
end end
context 'when there is a validation error (namespace name taken) while updating namespace' do context 'when there is a validation error (namespace name taken) while updating namespace' do
let!(:conflicting_namespace) { create(:group, name: new_username, path: 'quz') } let!(:conflicting_namespace) { create(:group, path: new_username) }
it 'causes the user save to fail' do it 'causes the user save to fail' do
expect(user.update_attributes(username: new_username)).to be_falsey expect(user.update_attributes(username: new_username)).to be_falsey
expect(user.namespace.errors.messages[:name].first).to eq('has already been taken') expect(user.namespace.errors.messages[:path].first).to eq('has already been taken')
end end
it 'adds the namespace errors to the user' do it 'adds the namespace errors to the user' do
user.update_attributes(username: new_username) user.update_attributes(username: new_username)
expect(user.errors.full_messages.first).to eq('Namespace name has already been taken') expect(user.errors.full_messages.first).to eq('Username has already been taken')
end end
end end
end end
......
...@@ -21,13 +21,13 @@ describe Users::UpdateService do ...@@ -21,13 +21,13 @@ describe Users::UpdateService do
end end
it 'includes namespace error messages' do it 'includes namespace error messages' do
create(:group, name: 'taken', path: 'something_else') create(:group, path: 'taken')
result = {} result = {}
expect do expect do
result = update_user(user, { username: 'taken' }) result = update_user(user, { username: 'taken' })
end.not_to change { user.reload.username } end.not_to change { user.reload.username }
expect(result[:status]).to eq(:error) expect(result[:status]).to eq(:error)
expect(result[:message]).to eq('Namespace name has already been taken') expect(result[:message]).to eq('Username has already been taken')
end end
def update_user(user, opts) def update_user(user, opts)
......
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