GitLab steht wegen Wartungsarbeiten am Montag, den 10. Mai, zwischen 17:00 und 19:00 Uhr nicht zur Verfügung.

namespace_spec.rb 20 KB
Newer Older
1 2
require 'spec_helper'

3
describe Namespace do
4 5
  include ProjectForksHelper

6
  let!(:namespace) { create(:namespace) }
7
  let(:gitlab_shell) { Gitlab::Shell.new }
8

9 10 11 12 13 14
  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
15

16 17 18 19 20 21 22 23
  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) }
24

25 26 27
    it 'does not allow too deep nesting' do
      ancestors = (1..21).to_a
      nested = build(:namespace, parent: namespace)
28

29
      allow(nested).to receive(:ancestors).and_return(ancestors)
30

31 32 33
      expect(nested).not_to be_valid
      expect(nested.errors[:parent_id].first).to eq('has too deep level of nesting')
    end
34 35 36 37 38 39

    describe 'reserved path validation' do
      context 'nested group' do
        let(:group) { build(:group, :nested, path: 'tree') }

        it { expect(group).not_to be_valid }
40 41 42

        it 'rejects nested paths' do
          parent = create(:group, :nested, path: 'environments')
43
          namespace = build(:group, path: 'folders', parent: parent)
44 45 46

          expect(namespace).not_to be_valid
        end
47 48
      end

49
      context "is case insensitive" do
50
        let(:group) { build(:group, path: "Groups") }
51 52 53 54

        it { expect(group).not_to be_valid }
      end

55 56 57 58 59 60
      context 'top-level group' do
        let(:group) { build(:group, path: 'tree') }

        it { expect(group).to be_valid }
      end
    end
61
  end
62 63

  describe "Respond to" do
64 65
    it { is_expected.to respond_to(:human_name) }
    it { is_expected.to respond_to(:to_param) }
66
    it { is_expected.to respond_to(:has_parent?) }
67
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
68

69 70 71 72 73 74 75 76
  describe 'inclusions' do
    it { is_expected.to include_module(Gitlab::VisibilityLevel) }
  end

  describe '#visibility_level_field' do
    it { expect(namespace.visibility_level_field).to eq(:visibility_level) }
  end

77
  describe '#to_param' do
78
    it { expect(namespace.to_param).to eq(namespace.full_path) }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
79 80
  end

81
  describe '#human_name' do
82
    it { expect(namespace.human_name).to eq(namespace.owner_name) }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
83 84
  end

85
  describe '.search' do
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
    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])
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
102 103
    end

104 105 106 107 108 109 110
    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
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
111 112
  end

Markus Koller's avatar
Markus Koller committed
113 114 115 116
  describe '.with_statistics' do
    let(:namespace) { create :namespace }

    let(:project1) do
117
      create(:project,
Markus Koller's avatar
Markus Koller committed
118 119 120 121 122 123 124 125 126
             namespace: namespace,
             statistics: build(:project_statistics,
                               storage_size:         606,
                               repository_size:      101,
                               lfs_objects_size:     202,
                               build_artifacts_size: 303))
    end

    let(:project2) do
127
      create(:project,
Markus Koller's avatar
Markus Koller committed
128 129 130 131 132 133 134 135 136 137 138
             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
139
      statistics = described_class.with_statistics.find(namespace.id)
Markus Koller's avatar
Markus Koller committed
140 141 142 143 144 145 146 147

      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
148
      statistics = described_class.with_statistics.find(namespace.id)
Markus Koller's avatar
Markus Koller committed
149 150 151 152 153 154 155 156

      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

157
  describe '#move_dir', :request_store do
158 159 160
    let(:namespace) { create(:namespace) }
    let!(:project) { create(:project_empty_repo, namespace: namespace) }

161
    it "raises error when directory exists" do
162
      expect { namespace.move_dir }.to raise_error("namespace directory cannot be moved")
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
163 164
    end

165
    it "moves dir if path changed" do
166
      namespace.update_attributes(path: namespace.full_path + '_new')
167

168
      expect(gitlab_shell.exists?(project.repository_storage_path, "#{namespace.path}/#{project.path}.git")).to be_truthy
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
169
    end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
170

Andre Guedes's avatar
Andre Guedes committed
171
    context "when any project has container images" do
172
      let(:container_repository) { create(:container_repository) }
Andre Guedes's avatar
Andre Guedes committed
173

Kamil Trzcinski's avatar
Kamil Trzcinski committed
174 175
      before do
        stub_container_registry_config(enabled: true)
176
        stub_container_registry_tags(repository: :any, tags: ['tag'])
Kamil Trzcinski's avatar
Kamil Trzcinski committed
177

178
        create(:project, namespace: namespace, container_repositories: [container_repository])
Kamil Trzcinski's avatar
Kamil Trzcinski committed
179

180 181
        allow(namespace).to receive(:path_was).and_return(namespace.path)
        allow(namespace).to receive(:path).and_return('new_path')
Kamil Trzcinski's avatar
Kamil Trzcinski committed
182 183
      end

184
      it 'raises an error about not movable project' do
185
        expect { namespace.move_dir }.to raise_error(/Namespace cannot be moved/)
186
      end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
187
    end
188

189
    context 'with subgroups' do
190 191 192
      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) }
193 194
      let(:uploads_dir) { File.join(CarrierWave.root, FileUploader.base_dir) }
      let(:pages_dir) { File.join(TestEnv.pages_path) }
195 196

      before do
197 198
        FileUtils.mkdir_p(File.join(uploads_dir, 'parent', 'child', 'the-project'))
        FileUtils.mkdir_p(File.join(pages_dir, 'parent', 'child', 'the-project'))
199 200
      end

201 202 203 204 205
      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')
206

207
          child.update_attributes!(path: 'renamed')
208

209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
          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
227 228
      end
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
229 230
  end

231
  describe '#rm_dir', 'callback' do
232 233 234 235 236 237 238 239
    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)
240

241 242
      namespace.destroy

243 244 245 246 247 248 249 250 251 252
      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
253 254
      let(:parent) { create(:group, path: 'parent') }
      let(:child) { create(:group, parent: parent, path: 'child') }
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
      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
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
273
    end
274 275 276 277 278 279

    it 'removes the exports folder' do
      expect(namespace).to receive(:remove_exports!)

      namespace.destroy
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
280
  end
281

282
  describe '.find_by_path_or_name' do
283 284 285 286
    before do
      @namespace = create(:namespace, name: 'WoW', path: 'woW')
    end

287 288 289
    it { expect(described_class.find_by_path_or_name('wow')).to eq(@namespace) }
    it { expect(described_class.find_by_path_or_name('WOW')).to eq(@namespace) }
    it { expect(described_class.find_by_path_or_name('unknown')).to eq(nil) }
290
  end
291 292 293 294 295 296

  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
297 298
      expect(described_class.clean_path("-john+gitlab-ETC%.git@gmail.com")).to eq("johngitlab-ETC2")
      expect(described_class.clean_path("--%+--valid_*&%name=.git.%.atom.atom.@email.com")).to eq("valid_name")
299 300
    end
  end
301

302
  describe '#ancestors', :nested_groups do
303 304 305
    let(:group) { create(:group) }
    let(:nested_group) { create(:group, parent: group) }
    let(:deep_nested_group) { create(:group, parent: nested_group) }
306
    let(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
307

308
    it 'returns the correct ancestors' do
309 310 311
      expect(very_deep_nested_group.ancestors).to include(group, nested_group, deep_nested_group)
      expect(deep_nested_group.ancestors).to include(group, nested_group)
      expect(nested_group.ancestors).to include(group)
312 313 314 315
      expect(group.ancestors).to eq([])
    end
  end

316 317 318 319 320 321 322 323 324 325 326 327 328 329
  describe '#self_and_ancestors', :nested_groups 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.self_and_ancestors).to contain_exactly(group, nested_group, deep_nested_group, very_deep_nested_group)
      expect(deep_nested_group.self_and_ancestors).to contain_exactly(group, nested_group, deep_nested_group)
      expect(nested_group.self_and_ancestors).to contain_exactly(group, nested_group)
      expect(group.self_and_ancestors).to contain_exactly(group)
    end
  end

330
  describe '#descendants', :nested_groups do
331
    let!(:group) { create(:group, path: 'git_lab') }
332 333 334
    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) }
335 336
    let!(:another_group) { create(:group, path: 'gitllab') }
    let!(:another_group_nested) { create(:group, path: 'foo', parent: another_group) }
337 338 339

    it 'returns the correct descendants' do
      expect(very_deep_nested_group.descendants.to_a).to eq([])
340 341 342
      expect(deep_nested_group.descendants.to_a).to include(very_deep_nested_group)
      expect(nested_group.descendants.to_a).to include(deep_nested_group, very_deep_nested_group)
      expect(group.descendants.to_a).to include(nested_group, deep_nested_group, very_deep_nested_group)
343
    end
344
  end
345

346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
  describe '#self_and_descendants', :nested_groups 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.self_and_descendants).to contain_exactly(very_deep_nested_group)
      expect(deep_nested_group.self_and_descendants).to contain_exactly(deep_nested_group, very_deep_nested_group)
      expect(nested_group.self_and_descendants).to contain_exactly(nested_group, deep_nested_group, very_deep_nested_group)
      expect(group.self_and_descendants).to contain_exactly(group, nested_group, deep_nested_group, very_deep_nested_group)
    end
  end

362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
  describe '#users_with_descendants', :nested_groups do
    let(:user_a) { create(:user) }
    let(:user_b) { create(:user) }

    let(:group) { create(:group) }
    let(:nested_group) { create(:group, parent: group) }
    let(:deep_nested_group) { create(:group, parent: nested_group) }

    it 'returns member users on every nest level without duplication' do
      group.add_developer(user_a)
      nested_group.add_developer(user_b)
      deep_nested_group.add_developer(user_a)

      expect(group.users_with_descendants).to contain_exactly(user_a, user_b)
      expect(nested_group.users_with_descendants).to contain_exactly(user_a, user_b)
      expect(deep_nested_group.users_with_descendants).to contain_exactly(user_a)
    end
  end

381 382 383 384 385 386 387 388 389 390 391
  describe '#soft_delete_without_removing_associations' do
    let(:project1) { create(:project_empty_repo, namespace: namespace) }

    it 'updates the deleted_at timestamp but preserves projects' do
      namespace.soft_delete_without_removing_associations

      expect(Project.all).to include(project1)
      expect(namespace.deleted_at).not_to be_nil
    end
  end

392 393
  describe '#user_ids_for_project_authorizations' do
    it 'returns the user IDs for which to refresh authorizations' do
394 395
      expect(namespace.user_ids_for_project_authorizations)
        .to eq([namespace.owner_id])
396 397
    end
  end
398 399 400 401 402 403 404

  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) }

405
    it { expect(group.all_projects.to_a).to match_array([project2, project1]) }
406
  end
407

Michael Kozono's avatar
Michael Kozono committed
408
  describe '#share_with_group_lock with subgroups', :nested_groups do
409 410 411
    context 'when creating a subgroup' do
      let(:subgroup) { create(:group, parent: root_group )}

412
      context 'under a parent with "Share with group lock" enabled' do
413 414
        let(:root_group) { create(:group, share_with_group_lock: true) }

415
        it 'enables "Share with group lock" on the subgroup' do
416 417 418 419
          expect(subgroup.share_with_group_lock).to be_truthy
        end
      end

420
      context 'under a parent with "Share with group lock" disabled' do
421 422
        let(:root_group) { create(:group) }

423
        it 'does not enable "Share with group lock" on the subgroup' do
424 425 426 427 428
          expect(subgroup.share_with_group_lock).to be_falsey
        end
      end
    end

429
    context 'when enabling the parent group "Share with group lock"' do
430 431 432
      let(:root_group) { create(:group) }
      let!(:subgroup) { create(:group, parent: root_group )}

433
      it 'the subgroup "Share with group lock" becomes enabled' do
Michael Kozono's avatar
Michael Kozono committed
434
        root_group.update!(share_with_group_lock: true)
435 436 437 438 439

        expect(subgroup.reload.share_with_group_lock).to be_truthy
      end
    end

440
    context 'when disabling the parent group "Share with group lock" (which was already enabled)' do
441 442
      let(:root_group) { create(:group, share_with_group_lock: true) }

443
      context 'and the subgroup "Share with group lock" is enabled' do
444 445
        let(:subgroup) { create(:group, parent: root_group, share_with_group_lock: true )}

446
        it 'the subgroup "Share with group lock" does not change' do
Michael Kozono's avatar
Michael Kozono committed
447
          root_group.update!(share_with_group_lock: false)
448 449 450 451 452

          expect(subgroup.reload.share_with_group_lock).to be_truthy
        end
      end

453
      context 'but the subgroup "Share with group lock" is disabled' do
454 455
        let(:subgroup) { create(:group, parent: root_group )}

456
        it 'the subgroup "Share with group lock" does not change' do
Michael Kozono's avatar
Michael Kozono committed
457
          root_group.update!(share_with_group_lock: false)
458 459 460 461 462 463 464 465

          expect(subgroup.reload.share_with_group_lock?).to be_falsey
        end
      end
    end

    # Note: Group transfers are not yet implemented
    context 'when a group is transferred into a root group' do
466
      context 'when the root group "Share with group lock" is enabled' do
467 468
        let(:root_group) { create(:group, share_with_group_lock: true) }

469
        context 'when the subgroup "Share with group lock" is enabled' do
470 471
          let(:subgroup) { create(:group, share_with_group_lock: true )}

472
          it 'the subgroup "Share with group lock" does not change' do
473 474 475 476 477 478 479
            subgroup.parent = root_group
            subgroup.save!

            expect(subgroup.share_with_group_lock).to be_truthy
          end
        end

480
        context 'when the subgroup "Share with group lock" is disabled' do
481 482
          let(:subgroup) { create(:group)}

483
          it 'the subgroup "Share with group lock" becomes enabled' do
484 485 486 487 488 489 490 491
            subgroup.parent = root_group
            subgroup.save!

            expect(subgroup.share_with_group_lock).to be_truthy
          end
        end
      end

492
      context 'when the root group "Share with group lock" is disabled' do
493 494
        let(:root_group) { create(:group) }

495
        context 'when the subgroup "Share with group lock" is enabled' do
496 497
          let(:subgroup) { create(:group, share_with_group_lock: true )}

498
          it 'the subgroup "Share with group lock" does not change' do
499 500 501 502 503 504 505
            subgroup.parent = root_group
            subgroup.save!

            expect(subgroup.share_with_group_lock).to be_truthy
          end
        end

506
        context 'when the subgroup "Share with group lock" is disabled' do
507 508
          let(:subgroup) { create(:group)}

509
          it 'the subgroup "Share with group lock" does not change' do
510 511 512 513 514 515 516 517 518
            subgroup.parent = root_group
            subgroup.save!

            expect(subgroup.share_with_group_lock).to be_falsey
          end
        end
      end
    end
  end
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539

  describe '#has_forks_of?' do
    let(:project) { create(:project, :public) }
    let!(:forked_project) { fork_project(project, namespace.owner, namespace: namespace) }

    before do
      # Reset the fork network relation
      project.reload
    end

    it 'knows if there is a direct fork in the namespace' do
      expect(namespace.find_fork_of(project)).to eq(forked_project)
    end

    it 'knows when there is as fork-of-fork in the namespace' do
      other_namespace = create(:namespace)
      other_fork = fork_project(forked_project, other_namespace.owner, namespace: other_namespace)

      expect(other_namespace.find_fork_of(project)).to eq(other_fork)
    end
  end
540
end