fc550b398b
* master: (230 commits) Fix N+1 query in loading pipelines in merge requests Fix Spinach and Capybara dependencies Prevent users from disconnecting gitlab account from CAS 30276 Move issue, mr, todos next to profile dropdown in top nav Refactor SearchController#show Properly eagerly-load the Capybara server for JS feature specs only Updating documentation to include a missing step in the update procedure Eager-load the Capybara server to prevent timeouts Increase Capybara's timeout Add metrics button to Environment Overview page Fix link to Jira service documentation Handle parsing OpenBSD ps output properly to display sidekiq infos on ... Eliminate unnecessary queries that add ~500 ms of load time for a large issue 20914 Limits line length for project home page Allow users to import GitHub projects to subgroups Update dpl CI example Fix the docs:check:links job Don't clean up the gitlab-test-fork_bare repo Make GitLab use Gitaly for commit_is_ancestor Remove unnecessary ORDER BY clause from `forked_to_project_id` subquery ...
300 lines
11 KiB
Ruby
300 lines
11 KiB
Ruby
require 'spec_helper'
|
|
|
|
describe Namespace, models: true do
|
|
let!(:namespace) { create(:namespace) }
|
|
|
|
describe 'associations' do
|
|
it { is_expected.to have_many :projects }
|
|
it { is_expected.to have_many :project_statistics }
|
|
it { is_expected.to belong_to :parent }
|
|
it { is_expected.to have_many :children }
|
|
end
|
|
|
|
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) }
|
|
it { is_expected.to validate_length_of(:path).is_at_most(255) }
|
|
it { is_expected.to validate_presence_of(:owner) }
|
|
|
|
it 'does not allow too deep nesting' do
|
|
ancestors = (1..21).to_a
|
|
nested = build(:namespace, parent: namespace)
|
|
|
|
allow(nested).to receive(:ancestors).and_return(ancestors)
|
|
|
|
expect(nested).not_to be_valid
|
|
expect(nested.errors[:parent_id].first).to eq('has too deep level of nesting')
|
|
end
|
|
|
|
describe 'reserved path validation' do
|
|
context 'nested group' do
|
|
let(:group) { build(:group, :nested, path: 'tree') }
|
|
|
|
it { expect(group).not_to be_valid }
|
|
end
|
|
|
|
context 'top-level group' do
|
|
let(:group) { build(:group, path: 'tree') }
|
|
|
|
it { expect(group).to be_valid }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "Respond to" do
|
|
it { is_expected.to respond_to(:human_name) }
|
|
it { is_expected.to respond_to(:to_param) }
|
|
end
|
|
|
|
describe '#to_param' do
|
|
it { expect(namespace.to_param).to eq(namespace.full_path) }
|
|
end
|
|
|
|
describe '#human_name' do
|
|
it { expect(namespace.human_name).to eq(namespace.owner_name) }
|
|
end
|
|
|
|
describe '.search' do
|
|
let(:namespace) { create(:namespace) }
|
|
|
|
it 'returns namespaces with a matching name' do
|
|
expect(described_class.search(namespace.name)).to eq([namespace])
|
|
end
|
|
|
|
it 'returns namespaces with a partially matching name' do
|
|
expect(described_class.search(namespace.name[0..2])).to eq([namespace])
|
|
end
|
|
|
|
it 'returns namespaces with a matching name regardless of the casing' do
|
|
expect(described_class.search(namespace.name.upcase)).to eq([namespace])
|
|
end
|
|
|
|
it 'returns namespaces with a matching path' do
|
|
expect(described_class.search(namespace.path)).to eq([namespace])
|
|
end
|
|
|
|
it 'returns namespaces with a partially matching path' do
|
|
expect(described_class.search(namespace.path[0..2])).to eq([namespace])
|
|
end
|
|
|
|
it 'returns namespaces with a matching path regardless of the casing' do
|
|
expect(described_class.search(namespace.path.upcase)).to eq([namespace])
|
|
end
|
|
end
|
|
|
|
describe '.with_statistics' do
|
|
let(:namespace) { create :namespace }
|
|
|
|
let(:project1) do
|
|
create(:empty_project,
|
|
namespace: namespace,
|
|
statistics: build(:project_statistics,
|
|
storage_size: 606,
|
|
repository_size: 101,
|
|
lfs_objects_size: 202,
|
|
build_artifacts_size: 303))
|
|
end
|
|
|
|
let(:project2) do
|
|
create(:empty_project,
|
|
namespace: namespace,
|
|
statistics: build(:project_statistics,
|
|
storage_size: 60,
|
|
repository_size: 10,
|
|
lfs_objects_size: 20,
|
|
build_artifacts_size: 30))
|
|
end
|
|
|
|
it "sums all project storage counters in the namespace" do
|
|
project1
|
|
project2
|
|
statistics = Namespace.with_statistics.find(namespace.id)
|
|
|
|
expect(statistics.storage_size).to eq 666
|
|
expect(statistics.repository_size).to eq 111
|
|
expect(statistics.lfs_objects_size).to eq 222
|
|
expect(statistics.build_artifacts_size).to eq 333
|
|
end
|
|
|
|
it "correctly handles namespaces without projects" do
|
|
statistics = Namespace.with_statistics.find(namespace.id)
|
|
|
|
expect(statistics.storage_size).to eq 0
|
|
expect(statistics.repository_size).to eq 0
|
|
expect(statistics.lfs_objects_size).to eq 0
|
|
expect(statistics.build_artifacts_size).to eq 0
|
|
end
|
|
end
|
|
|
|
describe '#move_dir', repository: true do
|
|
before do
|
|
@namespace = create :namespace
|
|
@project = create(:project_empty_repo, namespace: @namespace)
|
|
allow(@namespace).to receive(:path_changed?).and_return(true)
|
|
end
|
|
|
|
it "raises error when directory exists" do
|
|
expect { @namespace.move_dir }.to raise_error("namespace directory cannot be moved")
|
|
end
|
|
|
|
it "moves dir if path changed" do
|
|
new_path = @namespace.full_path + "_new"
|
|
allow(@namespace).to receive(:full_path_was).and_return(@namespace.full_path)
|
|
allow(@namespace).to receive(:full_path).and_return(new_path)
|
|
expect(@namespace).to receive(:remove_exports!)
|
|
expect(@namespace.move_dir).to be_truthy
|
|
end
|
|
|
|
context "when any project has container images" do
|
|
let(:container_repository) { create(:container_repository) }
|
|
|
|
before do
|
|
stub_container_registry_config(enabled: true)
|
|
stub_container_registry_tags('tag')
|
|
|
|
create(:empty_project, namespace: @namespace, container_repositories: [container_repository])
|
|
|
|
allow(@namespace).to receive(:path_was).and_return(@namespace.path)
|
|
allow(@namespace).to receive(:path).and_return('new_path')
|
|
end
|
|
|
|
it { expect { @namespace.move_dir }.to raise_error('Namespace cannot be moved, because at least one project has images in container registry') }
|
|
end
|
|
|
|
context 'renaming a sub-group' do
|
|
let(:parent) { create(:group, name: 'parent', path: 'parent') }
|
|
let(:child) { create(:group, name: 'child', path: 'child', parent: parent) }
|
|
let!(:project) { create(:project_empty_repo, path: 'the-project', namespace: child) }
|
|
let(:uploads_dir) { File.join(CarrierWave.root, 'uploads', 'parent') }
|
|
let(:pages_dir) { File.join(TestEnv.pages_path, 'parent') }
|
|
|
|
before do
|
|
FileUtils.mkdir_p(File.join(uploads_dir, 'child', 'the-project'))
|
|
FileUtils.mkdir_p(File.join(pages_dir, 'child', 'the-project'))
|
|
end
|
|
|
|
it 'correctly moves the repository, uploads and pages' do
|
|
expected_repository_path = File.join(TestEnv.repos_path, 'parent', 'renamed', 'the-project.git')
|
|
expected_upload_path = File.join(uploads_dir, 'renamed', 'the-project')
|
|
expected_pages_path = File.join(pages_dir, 'renamed', 'the-project')
|
|
|
|
child.update_attributes!(path: 'renamed')
|
|
|
|
expect(File.directory?(expected_repository_path)).to be(true)
|
|
expect(File.directory?(expected_upload_path)).to be(true)
|
|
expect(File.directory?(expected_pages_path)).to be(true)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#rm_dir', 'callback', repository: true do
|
|
let!(:project) { create(:project_empty_repo, namespace: namespace) }
|
|
let(:repository_storage_path) { Gitlab.config.repositories.storages.default['path'] }
|
|
let(:path_in_dir) { File.join(repository_storage_path, namespace.full_path) }
|
|
let(:deleted_path) { namespace.full_path.gsub(namespace.path, "#{namespace.full_path}+#{namespace.id}+deleted") }
|
|
let(:deleted_path_in_dir) { File.join(repository_storage_path, deleted_path) }
|
|
|
|
it 'renames its dirs when deleted' do
|
|
allow(GitlabShellWorker).to receive(:perform_in)
|
|
|
|
namespace.destroy
|
|
|
|
expect(File.exist?(deleted_path_in_dir)).to be(true)
|
|
end
|
|
|
|
it 'schedules the namespace for deletion' do
|
|
expect(GitlabShellWorker).to receive(:perform_in).with(5.minutes, :rm_namespace, repository_storage_path, deleted_path)
|
|
|
|
namespace.destroy
|
|
end
|
|
|
|
context 'in sub-groups' do
|
|
let(:parent) { create(:namespace, path: 'parent') }
|
|
let(:child) { create(:namespace, parent: parent, path: 'child') }
|
|
let!(:project) { create(:project_empty_repo, namespace: child) }
|
|
let(:path_in_dir) { File.join(repository_storage_path, 'parent', 'child') }
|
|
let(:deleted_path) { File.join('parent', "child+#{child.id}+deleted") }
|
|
let(:deleted_path_in_dir) { File.join(repository_storage_path, deleted_path) }
|
|
|
|
it 'renames its dirs when deleted' do
|
|
allow(GitlabShellWorker).to receive(:perform_in)
|
|
|
|
child.destroy
|
|
|
|
expect(File.exist?(deleted_path_in_dir)).to be(true)
|
|
end
|
|
|
|
it 'schedules the namespace for deletion' do
|
|
expect(GitlabShellWorker).to receive(:perform_in).with(5.minutes, :rm_namespace, repository_storage_path, deleted_path)
|
|
|
|
child.destroy
|
|
end
|
|
end
|
|
|
|
it 'removes the exports folder' do
|
|
expect(namespace).to receive(:remove_exports!)
|
|
|
|
namespace.destroy
|
|
end
|
|
end
|
|
|
|
describe '.find_by_path_or_name' do
|
|
before do
|
|
@namespace = create(:namespace, name: 'WoW', path: 'woW')
|
|
end
|
|
|
|
it { expect(Namespace.find_by_path_or_name('wow')).to eq(@namespace) }
|
|
it { expect(Namespace.find_by_path_or_name('WOW')).to eq(@namespace) }
|
|
it { expect(Namespace.find_by_path_or_name('unknown')).to eq(nil) }
|
|
end
|
|
|
|
describe ".clean_path" do
|
|
let!(:user) { create(:user, username: "johngitlab-etc") }
|
|
let!(:namespace) { create(:namespace, path: "JohnGitLab-etc1") }
|
|
|
|
it "cleans the path and makes sure it's available" do
|
|
expect(Namespace.clean_path("-john+gitlab-ETC%.git@gmail.com")).to eq("johngitlab-ETC2")
|
|
expect(Namespace.clean_path("--%+--valid_*&%name=.git.%.atom.atom.@email.com")).to eq("valid_name")
|
|
end
|
|
end
|
|
|
|
describe '#ancestors' do
|
|
let(:group) { create(:group) }
|
|
let(:nested_group) { create(:group, parent: group) }
|
|
let(:deep_nested_group) { create(:group, parent: nested_group) }
|
|
let(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
|
|
|
|
it 'returns the correct ancestors' do
|
|
expect(very_deep_nested_group.ancestors).to eq([group, nested_group, deep_nested_group])
|
|
expect(deep_nested_group.ancestors).to eq([group, nested_group])
|
|
expect(nested_group.ancestors).to eq([group])
|
|
expect(group.ancestors).to eq([])
|
|
end
|
|
end
|
|
|
|
describe '#descendants' do
|
|
let!(:group) { create(:group, path: 'git_lab') }
|
|
let!(:nested_group) { create(:group, parent: group) }
|
|
let!(:deep_nested_group) { create(:group, parent: nested_group) }
|
|
let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
|
|
let!(:another_group) { create(:group, path: 'gitllab') }
|
|
let!(:another_group_nested) { create(:group, path: 'foo', parent: another_group) }
|
|
|
|
it 'returns the correct descendants' do
|
|
expect(very_deep_nested_group.descendants.to_a).to eq([])
|
|
expect(deep_nested_group.descendants.to_a).to eq([very_deep_nested_group])
|
|
expect(nested_group.descendants.to_a).to eq([deep_nested_group, very_deep_nested_group])
|
|
expect(group.descendants.to_a).to eq([nested_group, deep_nested_group, very_deep_nested_group])
|
|
end
|
|
end
|
|
|
|
describe '#user_ids_for_project_authorizations' do
|
|
it 'returns the user IDs for which to refresh authorizations' do
|
|
expect(namespace.user_ids_for_project_authorizations).
|
|
to eq([namespace.owner_id])
|
|
end
|
|
end
|
|
end
|