Commit 72ffebc5 authored by Alexandru Croitor's avatar Alexandru Croitor

Add specs specific to project namespace creation

Check that project namespace is created when project is
created and also that there is no project namespace created
if the project is not created for any reason.
parent dbcd6df7
...@@ -2920,18 +2920,20 @@ class Project < ApplicationRecord ...@@ -2920,18 +2920,20 @@ class Project < ApplicationRecord
end end
def ensure_project_namespace_in_sync def ensure_project_namespace_in_sync
if self.namespace && self.root_namespace.project_namespace_creation_enabled? # create project_namespace when project is created if create_project_namespace_on_project_create FF is enabled
if new_record? && !project_namespace build_project_namespace if project_namespace_creation_enabled?
build_project_namespace
sync_attributes(project_namespace)
end
sync_attributes(project_namespace) if sync_project_namespace? # regardless of create_project_namespace_on_project_create FF we need
end # to keep project and project namespace in sync if there is one
sync_attributes(project_namespace) if sync_project_namespace?
end
def project_namespace_creation_enabled?
new_record? && !project_namespace && self.namespace && self.root_namespace.project_namespace_creation_enabled?
end end
def sync_project_namespace? def sync_project_namespace?
(changes.keys & %w(name path namespace_id namespace visibility_level)).any? && project_namespace.present? (changes.keys & %w(name path namespace_id namespace visibility_level shared_runners_enabled)).any? && project_namespace.present?
end end
def sync_attributes(project_namespace) def sync_attributes(project_namespace)
......
--- ---
name: create_project_namespace_on_project_create name: create_project_namespace_on_project_create
introduced_by_url: introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70972
rollout_issue_url: rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344954
milestone: '14.4' milestone: '14.5'
type: development type: development
group: group::workspace group: group::workspace
default_enabled: false default_enabled: false
...@@ -418,16 +418,16 @@ RSpec.describe Gitlab::PathRegex do ...@@ -418,16 +418,16 @@ RSpec.describe Gitlab::PathRegex do
it { is_expected.to match('_underscore.js') } it { is_expected.to match('_underscore.js') }
it { is_expected.to match('100px.com') } it { is_expected.to match('100px.com') }
it { is_expected.to match('gitlab.org') } it { is_expected.to match('gitlab.org') }
it { is_expected.to match('gitlab.org-') }
it { is_expected.to match('gitlab.org_') }
it { is_expected.not_to match('gitlab.org.') }
it { is_expected.not_to match('gitlab.org/') }
it { is_expected.not_to match('/gitlab.org') }
it { is_expected.not_to match('?gitlab') } it { is_expected.not_to match('?gitlab') }
it { is_expected.not_to match('gitlab?') }
it { is_expected.not_to match('git lab') } it { is_expected.not_to match('git lab') }
it { is_expected.not_to match('gitlab.git') } it { is_expected.not_to match('gitlab.git') }
it { is_expected.not_to match('gitlab.org.') }
it { is_expected.not_to match('gitlab.org/') }
it { is_expected.not_to match('/gitlab.org') }
it { is_expected.not_to match('gitlab git') } it { is_expected.not_to match('gitlab git') }
it { is_expected.not_to match('gitlab?') }
it { is_expected.to match('gitlab.org-') }
it { is_expected.to match('gitlab.org_') }
end end
describe '.project_path_format_regex' do describe '.project_path_format_regex' do
...@@ -437,17 +437,17 @@ RSpec.describe Gitlab::PathRegex do ...@@ -437,17 +437,17 @@ RSpec.describe Gitlab::PathRegex do
it { is_expected.to match('gitlab_git') } it { is_expected.to match('gitlab_git') }
it { is_expected.to match('_underscore.js') } it { is_expected.to match('_underscore.js') }
it { is_expected.to match('100px.com') } it { is_expected.to match('100px.com') }
it { is_expected.not_to match('?gitlab') }
it { is_expected.not_to match('git lab') }
it { is_expected.not_to match('gitlab.git') }
it { is_expected.not_to match('gitlab?') }
it { is_expected.not_to match('gitlab git') }
it { is_expected.to match('gitlab.org') } it { is_expected.to match('gitlab.org') }
it { is_expected.to match('gitlab.org-') } it { is_expected.to match('gitlab.org-') }
it { is_expected.to match('gitlab.org_') } it { is_expected.to match('gitlab.org_') }
it { is_expected.to match('gitlab.org.') } it { is_expected.to match('gitlab.org.') }
it { is_expected.not_to match('gitlab.org/') } it { is_expected.not_to match('gitlab.org/') }
it { is_expected.not_to match('/gitlab.org') } it { is_expected.not_to match('/gitlab.org') }
it { is_expected.not_to match('?gitlab') }
it { is_expected.not_to match('gitlab?') }
it { is_expected.not_to match('git lab') }
it { is_expected.not_to match('gitlab.git') }
it { is_expected.not_to match('gitlab git') }
end end
context 'repository routes' do context 'repository routes' do
......
...@@ -243,7 +243,7 @@ RSpec.describe Namespace do ...@@ -243,7 +243,7 @@ RSpec.describe Namespace do
project = create(:project) project = create(:project)
namespace = project.project_namespace namespace = project.project_namespace
namespace.update(path: 'j') namespace.update!(path: 'j')
expect(namespace).to be_valid expect(namespace).to be_valid
end end
...@@ -569,7 +569,7 @@ RSpec.describe Namespace do ...@@ -569,7 +569,7 @@ RSpec.describe Namespace do
context 'with project namespaces' do context 'with project namespaces' do
let_it_be(:project) { create(:project, namespace: parent_group, path: 'some-new-path') } let_it_be(:project) { create(:project, namespace: parent_group, path: 'some-new-path') }
let_it_be(:project_namespace) { create(:project_namespace, project: project) } let_it_be(:project_namespace) { project.project_namespace }
it 'does not return project namespace' do it 'does not return project namespace' do
search_result = described_class.search('path') search_result = described_class.search('path')
......
...@@ -233,6 +233,58 @@ RSpec.describe Project, factory_default: :keep do ...@@ -233,6 +233,58 @@ RSpec.describe Project, factory_default: :keep do
expect(project.project_setting).to be_an_instance_of(ProjectSetting) expect(project.project_setting).to be_an_instance_of(ProjectSetting)
expect(project.project_setting).to be_new_record expect(project.project_setting).to be_new_record
end end
context 'with project namespaces' do
it 'automatically creates a project namespace' do
project = build(:project, path: 'hopefully-valid-path1')
project.save!
expect(project).to be_persisted
expect(project.project_namespace).to be_persisted
expect(project.project_namespace).to be_in_sync_with_project(project)
end
context 'with FF disabled' do
before do
stub_feature_flags(create_project_namespace_on_project_create: false)
end
it 'does not create a project namespace' do
project = build(:project, path: 'hopefully-valid-path2')
project.save!
expect(project).to be_persisted
expect(project.project_namespace).to be_nil
end
end
end
end
context 'updating a project' do
context 'with project namespaces' do
it 'keeps project namespace in sync with project' do
project = create(:project)
project.update!(path: 'hopefully-valid-path1')
expect(project).to be_persisted
expect(project.project_namespace).to be_persisted
expect(project.project_namespace).to be_in_sync_with_project(project)
end
context 'with FF disabled' do
before do
stub_feature_flags(create_project_namespace_on_project_create: false)
end
it 'does not create a project namespace when project is updated' do
project = create(:project)
project.update!(path: 'hopefully-valid-path1')
expect(project).to be_persisted
expect(project.project_namespace).to be_nil
end
end
end
end end
context 'updating cd_cd_settings' do context 'updating cd_cd_settings' do
...@@ -322,6 +374,18 @@ RSpec.describe Project, factory_default: :keep do ...@@ -322,6 +374,18 @@ RSpec.describe Project, factory_default: :keep do
create(:project) create(:project)
end end
context 'validates project namespace creation' do
it 'does not create project namespace if project is not created' do
project = build(:project, path: 'tree')
project.valid?
expect(project).not_to be_valid
expect(project).to be_new_record
expect(project.project_namespace).to be_new_record
end
end
context 'repository storages inclusion' do context 'repository storages inclusion' do
let(:project2) { build(:project, repository_storage: 'missing') } let(:project2) { build(:project, repository_storage: 'missing') }
......
...@@ -49,6 +49,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -49,6 +49,7 @@ RSpec.describe Projects::CreateService, '#execute' do
it 'keeps them as specified' do it 'keeps them as specified' do
expect(project.name).to eq('one') expect(project.name).to eq('one')
expect(project.path).to eq('two') expect(project.path).to eq('two')
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
end end
...@@ -58,6 +59,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -58,6 +59,7 @@ RSpec.describe Projects::CreateService, '#execute' do
it 'sets name == path' do it 'sets name == path' do
expect(project.path).to eq('one.two_three-four') expect(project.path).to eq('one.two_three-four')
expect(project.name).to eq(project.path) expect(project.name).to eq(project.path)
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
end end
...@@ -67,6 +69,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -67,6 +69,7 @@ RSpec.describe Projects::CreateService, '#execute' do
it 'sets path == name' do it 'sets path == name' do
expect(project.name).to eq('one.two_three-four') expect(project.name).to eq('one.two_three-four')
expect(project.path).to eq(project.name) expect(project.path).to eq(project.name)
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
end end
...@@ -78,6 +81,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -78,6 +81,7 @@ RSpec.describe Projects::CreateService, '#execute' do
it 'parameterizes the name' do it 'parameterizes the name' do
expect(project.name).to eq('one.two_three-four and five') expect(project.name).to eq('one.two_three-four and five')
expect(project.path).to eq('one-two_three-four-and-five') expect(project.path).to eq('one-two_three-four-and-five')
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
end end
end end
...@@ -111,13 +115,14 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -111,13 +115,14 @@ RSpec.describe Projects::CreateService, '#execute' do
end end
context 'user namespace' do context 'user namespace' do
it do it 'creates a project in user namespace' do
project = create_project(user, opts) project = create_project(user, opts)
expect(project).to be_valid expect(project).to be_valid
expect(project.owner).to eq(user) expect(project.owner).to eq(user)
expect(project.team.maintainers).to include(user) expect(project.team.maintainers).to include(user)
expect(project.namespace).to eq(user.namespace) expect(project.namespace).to eq(user.namespace)
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
end end
...@@ -151,6 +156,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -151,6 +156,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project.owner).to eq(user) expect(project.owner).to eq(user)
expect(project.team.maintainers).to contain_exactly(user) expect(project.team.maintainers).to contain_exactly(user)
expect(project.namespace).to eq(user.namespace) expect(project.namespace).to eq(user.namespace)
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
end end
...@@ -160,6 +166,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -160,6 +166,7 @@ RSpec.describe Projects::CreateService, '#execute' do
project = create_project(admin, opts) project = create_project(admin, opts)
expect(project).not_to be_persisted expect(project).not_to be_persisted
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
end end
end end
...@@ -183,6 +190,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -183,6 +190,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project.namespace).to eq(group) expect(project.namespace).to eq(group)
expect(project.team.owners).to include(user) expect(project.team.owners).to include(user)
expect(user.authorized_projects).to include(project) expect(user.authorized_projects).to include(project)
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
end end
...@@ -339,6 +347,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -339,6 +347,7 @@ RSpec.describe Projects::CreateService, '#execute' do
end end
imported_project imported_project
expect(imported_project.project_namespace).to be_in_sync_with_project(imported_project)
end end
it 'stores import data and URL' do it 'stores import data and URL' do
...@@ -406,6 +415,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -406,6 +415,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project.visibility_level).to eq(project_level) expect(project.visibility_level).to eq(project_level)
expect(project).to be_saved expect(project).to be_saved
expect(project).to be_valid expect(project).to be_valid
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
end end
end end
...@@ -424,6 +434,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -424,6 +434,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project.errors.messages[:visibility_level].first).to( expect(project.errors.messages[:visibility_level].first).to(
match('restricted by your GitLab administrator') match('restricted by your GitLab administrator')
) )
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
it 'does not allow a restricted visibility level for admins when admin mode is disabled' do it 'does not allow a restricted visibility level for admins when admin mode is disabled' do
...@@ -493,6 +504,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -493,6 +504,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project).to be_valid expect(project).to be_valid
expect(project.owner).to eq(user) expect(project.owner).to eq(user)
expect(project.namespace).to eq(user.namespace) expect(project.namespace).to eq(user.namespace)
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
context 'when another repository already exists on disk' do context 'when another repository already exists on disk' do
...@@ -522,6 +534,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -522,6 +534,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project).to respond_to(:errors) expect(project).to respond_to(:errors)
expect(project.errors.messages).to have_key(:base) expect(project.errors.messages).to have_key(:base)
expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk') expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk')
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
it 'does not allow to import project when path matches existing repository on disk' do it 'does not allow to import project when path matches existing repository on disk' do
...@@ -531,6 +544,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -531,6 +544,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project).to respond_to(:errors) expect(project).to respond_to(:errors)
expect(project.errors.messages).to have_key(:base) expect(project.errors.messages).to have_key(:base)
expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk') expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk')
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
end end
...@@ -555,6 +569,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -555,6 +569,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project).to respond_to(:errors) expect(project).to respond_to(:errors)
expect(project.errors.messages).to have_key(:base) expect(project.errors.messages).to have_key(:base)
expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk') expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk')
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
end end
end end
...@@ -870,6 +885,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -870,6 +885,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project).to be_valid expect(project).to be_valid
expect(project.shared_runners_enabled).to eq(expected_result_for_project) expect(project.shared_runners_enabled).to eq(expected_result_for_project)
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
end end
end end
...@@ -890,6 +906,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -890,6 +906,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project).to be_valid expect(project).to be_valid
expect(project.shared_runners_enabled).to eq(expected_result_for_project) expect(project.shared_runners_enabled).to eq(expected_result_for_project)
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
end end
end end
...@@ -907,6 +924,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -907,6 +924,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project.persisted?).to eq(false) expect(project.persisted?).to eq(false)
expect(project).to be_invalid expect(project).to be_invalid
expect(project.errors[:shared_runners_enabled]).to include('cannot be enabled because parent group does not allow it') expect(project.errors[:shared_runners_enabled]).to include('cannot be enabled because parent group does not allow it')
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
end end
end end
...@@ -926,6 +944,7 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -926,6 +944,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project).to be_valid expect(project).to be_valid
expect(project.shared_runners_enabled).to eq(expected_result) expect(project.shared_runners_enabled).to eq(expected_result)
expect(project.project_namespace).to be_in_sync_with_project(project)
end end
end end
end end
......
# frozen_string_literal: true
RSpec::Matchers.define :be_in_sync_with_project do |project|
match do |project_namespace|
# if project is not persisted make sure we do not have a persisted project_namespace for it
break false if project.new_record? && project_namespace&.persisted?
# don't really care if project is not in sync if the project was never persisted.
break true if project.new_record? && !project_namespace.present?
project_namespace.present? &&
project.name == project_namespace.name &&
project.path == project_namespace.path &&
project.namespace == project_namespace.parent &&
project.visibility_level == project_namespace.visibility_level &&
project.shared_runners_enabled == project_namespace.shared_runners_enabled
end
failure_message_when_negated do |project_namespace|
if project.new_record? && project_namespace&.persisted?
"expected that a non persisted project #{project} does not have a persisted project namespace #{project_namespace}"
else
<<-MSG
expected that the project's attributes name, path, namespace_id, visibility_level, shared_runners_enabled
are in sync with the corresponding project namespace attributes
MSG
end
end
end
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