6d7a52480c
* master: (94 commits) Merge branch 'open-redirect-fix-continue-to' into 'security' Merge branch 'open-redirect-host-fix' into 'security' Merge branch 'path-disclosure-proj-import-export' into 'security' Merge branch '29364-private-projects-mr-fix' Merge branch '30125-markdown-security' Issue title realtime Update CHANGELOG.md for 8.16.9 Update CHANGELOG.md for 8.17.5 Update CHANGELOG.md for 9.0.4 Add "search" optional param and docs for V4 Use PDFLab to render PDFs in GitLab Separate Scala from Java in CI examples Fix broken link Reorganize CI examples, add more links Refactor CI index page Remove deprecated field from workhorse response Use gitlab-workhorse 1.4.3 Document how ETag caching middleware handles query parameters Make group skip validation in the frontend Use NamespaceValidator::WILDCARD_ROUTES in ETag caching middleware ...
327 lines
12 KiB
Ruby
327 lines
12 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(repository: :any, 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 'raises an error about not movable project' do
|
|
expect { @namespace.move_dir }.to raise_error(/Namespace cannot be moved/)
|
|
end
|
|
end
|
|
|
|
context 'with subgroups' 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') }
|
|
let(:pages_dir) { TestEnv.pages_path }
|
|
|
|
before do
|
|
FileUtils.mkdir_p(File.join(uploads_dir, 'parent', 'child', 'the-project'))
|
|
FileUtils.mkdir_p(File.join(pages_dir, 'parent', 'child', 'the-project'))
|
|
end
|
|
|
|
context 'renaming child' do
|
|
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, 'parent', 'renamed', 'the-project')
|
|
expected_pages_path = File.join(pages_dir, 'parent', '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
|
|
|
|
context 'renaming parent' do
|
|
it 'correctly moves the repository, uploads and pages' do
|
|
expected_repository_path = File.join(TestEnv.repos_path, 'renamed', 'child', 'the-project.git')
|
|
expected_upload_path = File.join(uploads_dir, 'renamed', 'child', 'the-project')
|
|
expected_pages_path = File.join(pages_dir, 'renamed', 'child', 'the-project')
|
|
|
|
parent.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
|
|
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
|
|
|
|
describe '#all_projects' do
|
|
let(:group) { create(:group) }
|
|
let(:child) { create(:group, parent: group) }
|
|
let!(:project1) { create(:project_empty_repo, namespace: group) }
|
|
let!(:project2) { create(:project_empty_repo, namespace: child) }
|
|
|
|
it { expect(group.all_projects.to_a).to eq([project2, project1]) }
|
|
end
|
|
end
|