Skip to content
Snippets Groups Projects
Commit 75144b1e authored by Douwe Maan's avatar Douwe Maan
Browse files

Validate path uniqueness only on Route, and bubble up appropriately

parent 8d69436c
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -7,11 +7,12 @@ module Routable
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
 
validates_associated :route
validates :route, presence: true
 
scope :with_route, -> { includes(:route) }
 
after_validation :set_path_errors
before_validation do
if full_path_changed? || full_name_changed?
prepare_route
Loading
Loading
@@ -125,6 +126,11 @@ module Routable
 
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
if route && route.path.present?
@full_path ||= route.path # rubocop:disable Gitlab/ModuleWithInstanceVariables
Loading
Loading
Loading
Loading
@@ -32,7 +32,6 @@ class Namespace < ActiveRecord::Base
validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
validates :name,
presence: true,
uniqueness: { scope: :parent_id },
length: { maximum: 255 },
namespace_name: true
 
Loading
Loading
Loading
Loading
@@ -245,8 +245,7 @@ class Project < ActiveRecord::Base
validates :path,
presence: true,
project_path: true,
length: { maximum: 255 },
uniqueness: { scope: :namespace_id }
length: { maximum: 255 }
 
validates :namespace, presence: true
validates :name, uniqueness: { scope: :namespace_id }
Loading
Loading
Loading
Loading
@@ -75,7 +75,7 @@ class Route < ActiveRecord::Base
def ensure_permanent_paths
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
 
def conflicting_redirect_exists?
Loading
Loading
Loading
Loading
@@ -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 }
validates :username,
user_path: true,
presence: true,
uniqueness: { case_sensitive: false }
presence: true
 
validate :namespace_uniq, if: :username_changed?
validates :namespace, presence: true
validate :namespace_move_dir_allowed, if: :username_changed?
 
validate :unique_email, if: :email_changed?
Loading
Loading
@@ -172,6 +171,7 @@ class User < ActiveRecord::Base
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_validation :ensure_namespace_correct
after_validation :set_username_errors
after_update :username_changed_hook, if: :username_changed?
after_destroy :post_destroy_hook
after_destroy :remove_key_cache
Loading
Loading
@@ -505,17 +505,6 @@ class User < ActiveRecord::Base
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
if namespace&.any_project_has_container_registry_tags?
errors.add(:username, 'cannot be changed if a personal project has container registry tags.')
Loading
Loading
@@ -891,6 +880,11 @@ class User < ActiveRecord::Base
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
system_hook_service.execute_hooks_for(self, :rename)
end
Loading
Loading
Loading
Loading
@@ -39,7 +39,7 @@ describe Group, 'Routable' do
create(:group, parent: 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
 
Loading
Loading
Loading
Loading
@@ -41,7 +41,6 @@ describe Group do
 
describe 'validations' do
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.not_to validate_presence_of :owner }
it { is_expected.to validate_presence_of :two_factor_grace_period }
Loading
Loading
Loading
Loading
@@ -15,7 +15,6 @@ describe Namespace do
 
describe 'validations' do
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(:description).is_at_most(255) }
it { is_expected.to validate_presence_of(:path) }
Loading
Loading
Loading
Loading
@@ -129,7 +129,6 @@ describe Project do
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_at_most(255) }
 
it { is_expected.to validate_length_of(:description).is_at_most(2000) }
Loading
Loading
Loading
Loading
@@ -27,7 +27,7 @@ describe Route do
redirect.save!(validate: false)
 
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
 
Loading
Loading
@@ -49,7 +49,7 @@ describe Route do
redirect.save!(validate: false)
 
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
 
Loading
Loading
@@ -368,7 +368,7 @@ describe Route do
 
group2.path = 'foo'
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
 
Loading
Loading
Loading
Loading
@@ -116,12 +116,6 @@ describe User do
expect(user).to be_valid
end
 
it 'validates uniqueness' do
user = build(:user)
expect(user).to validate_uniqueness_of(:username).case_insensitive
end
context 'when username is changed' do
let(:user) { build_stubbed(:user, username: 'old_path', namespace: build_stubbed(:namespace)) }
 
Loading
Loading
@@ -2287,17 +2281,17 @@ describe User do
end
 
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
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
 
it 'adds the namespace errors to the user' do
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
Loading
Loading
Loading
Loading
@@ -21,13 +21,13 @@ describe Users::UpdateService do
end
 
it 'includes namespace error messages' do
create(:group, name: 'taken', path: 'something_else')
create(:group, path: 'taken')
result = {}
expect do
result = update_user(user, { username: 'taken' })
end.not_to change { user.reload.username }
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
 
def update_user(user, opts)
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment