306 lines
10 KiB
Ruby
306 lines
10 KiB
Ruby
|
require 'spec_helper'
|
||
|
|
||
|
describe Group, 'Routable' do
|
||
|
let!(:group) { create(:group, name: 'foo') }
|
||
|
|
||
|
describe 'Validations' do
|
||
|
it { is_expected.to validate_presence_of(:route) }
|
||
|
end
|
||
|
|
||
|
describe 'Associations' do
|
||
|
it { is_expected.to have_one(:route).dependent(:destroy) }
|
||
|
it { is_expected.to have_many(:redirect_routes).dependent(:destroy) }
|
||
|
end
|
||
|
|
||
|
describe 'Callbacks' do
|
||
|
it 'creates route record on create' do
|
||
|
expect(group.route.path).to eq(group.path)
|
||
|
expect(group.route.name).to eq(group.name)
|
||
|
end
|
||
|
|
||
|
it 'updates route record on path change' do
|
||
|
group.update_attributes(path: 'wow', name: 'much')
|
||
|
|
||
|
expect(group.route.path).to eq('wow')
|
||
|
expect(group.route.name).to eq('much')
|
||
|
end
|
||
|
|
||
|
it 'ensure route path uniqueness across different objects' 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')
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe '.find_by_full_path' do
|
||
|
let!(:nested_group) { create(:group, parent: group) }
|
||
|
|
||
|
context 'without any redirect routes' do
|
||
|
it { expect(described_class.find_by_full_path(group.to_param)).to eq(group) }
|
||
|
it { expect(described_class.find_by_full_path(group.to_param.upcase)).to eq(group) }
|
||
|
it { expect(described_class.find_by_full_path(nested_group.to_param)).to eq(nested_group) }
|
||
|
it { expect(described_class.find_by_full_path('unknown')).to eq(nil) }
|
||
|
end
|
||
|
|
||
|
context 'with redirect routes' do
|
||
|
let!(:group_redirect_route) { group.redirect_routes.create!(path: 'bar') }
|
||
|
let!(:nested_group_redirect_route) { nested_group.redirect_routes.create!(path: nested_group.path.sub('foo', 'bar')) }
|
||
|
|
||
|
context 'without follow_redirects option' do
|
||
|
context 'with the given path not matching any route' do
|
||
|
it { expect(described_class.find_by_full_path('unknown')).to eq(nil) }
|
||
|
end
|
||
|
|
||
|
context 'with the given path matching the canonical route' do
|
||
|
it { expect(described_class.find_by_full_path(group.to_param)).to eq(group) }
|
||
|
it { expect(described_class.find_by_full_path(group.to_param.upcase)).to eq(group) }
|
||
|
it { expect(described_class.find_by_full_path(nested_group.to_param)).to eq(nested_group) }
|
||
|
end
|
||
|
|
||
|
context 'with the given path matching a redirect route' do
|
||
|
it { expect(described_class.find_by_full_path(group_redirect_route.path)).to eq(nil) }
|
||
|
it { expect(described_class.find_by_full_path(group_redirect_route.path.upcase)).to eq(nil) }
|
||
|
it { expect(described_class.find_by_full_path(nested_group_redirect_route.path)).to eq(nil) }
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'with follow_redirects option set to true' do
|
||
|
context 'with the given path not matching any route' do
|
||
|
it { expect(described_class.find_by_full_path('unknown', follow_redirects: true)).to eq(nil) }
|
||
|
end
|
||
|
|
||
|
context 'with the given path matching the canonical route' do
|
||
|
it { expect(described_class.find_by_full_path(group.to_param, follow_redirects: true)).to eq(group) }
|
||
|
it { expect(described_class.find_by_full_path(group.to_param.upcase, follow_redirects: true)).to eq(group) }
|
||
|
it { expect(described_class.find_by_full_path(nested_group.to_param, follow_redirects: true)).to eq(nested_group) }
|
||
|
end
|
||
|
|
||
|
context 'with the given path matching a redirect route' do
|
||
|
it { expect(described_class.find_by_full_path(group_redirect_route.path, follow_redirects: true)).to eq(group) }
|
||
|
it { expect(described_class.find_by_full_path(group_redirect_route.path.upcase, follow_redirects: true)).to eq(group) }
|
||
|
it { expect(described_class.find_by_full_path(nested_group_redirect_route.path, follow_redirects: true)).to eq(nested_group) }
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe '.where_full_path_in' do
|
||
|
context 'without any paths' do
|
||
|
it 'returns an empty relation' do
|
||
|
expect(described_class.where_full_path_in([])).to eq([])
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'without any valid paths' do
|
||
|
it 'returns an empty relation' do
|
||
|
expect(described_class.where_full_path_in(%w[unknown])).to eq([])
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'with valid paths' do
|
||
|
let!(:nested_group) { create(:group, parent: group) }
|
||
|
|
||
|
it 'returns the projects matching the paths' do
|
||
|
result = described_class.where_full_path_in([group.to_param, nested_group.to_param])
|
||
|
|
||
|
expect(result).to contain_exactly(group, nested_group)
|
||
|
end
|
||
|
|
||
|
it 'returns projects regardless of the casing of paths' do
|
||
|
result = described_class.where_full_path_in([group.to_param.upcase, nested_group.to_param.upcase])
|
||
|
|
||
|
expect(result).to contain_exactly(group, nested_group)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe '.member_descendants' do
|
||
|
let!(:user) { create(:user) }
|
||
|
let!(:nested_group) { create(:group, parent: group) }
|
||
|
|
||
|
before { group.add_owner(user) }
|
||
|
subject { described_class.member_descendants(user.id) }
|
||
|
|
||
|
it { is_expected.to eq([nested_group]) }
|
||
|
end
|
||
|
|
||
|
describe '.member_self_and_descendants' do
|
||
|
let!(:user) { create(:user) }
|
||
|
let!(:nested_group) { create(:group, parent: group) }
|
||
|
|
||
|
before { group.add_owner(user) }
|
||
|
subject { described_class.member_self_and_descendants(user.id) }
|
||
|
|
||
|
it { is_expected.to match_array [group, nested_group] }
|
||
|
|
||
|
context 'when the group has special chars in its path' do
|
||
|
let(:user1) { create(:user) }
|
||
|
let(:group1) { create(:group, name: 'demo', path: 'demo') }
|
||
|
let(:nested_group1) { create(:group, name: 'nest', path: 'nest', parent: group1) }
|
||
|
let!(:project1) { create(:empty_project, group: nested_group1) }
|
||
|
|
||
|
let(:user2) { create(:user) }
|
||
|
let(:group2) { create(:group, name: '____', path: '____') }
|
||
|
let(:nested_group2) { create(:group, name: 'test', path: 'test', parent: group2) }
|
||
|
let!(:project2) { create(:empty_project, group: nested_group2) }
|
||
|
|
||
|
before do
|
||
|
group1.add_master(user1)
|
||
|
group2.add_master(user2)
|
||
|
end
|
||
|
|
||
|
it 'only returns the right groups' do
|
||
|
groups = described_class.member_self_and_descendants(user2.id)
|
||
|
|
||
|
expect(groups).to match_array([group2, nested_group2])
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe '.member_hierarchy' do
|
||
|
# foo/bar would also match foo/barbaz instead of just foo/bar and foo/bar/baz
|
||
|
let!(:user) { create(:user) }
|
||
|
|
||
|
# group
|
||
|
# _______ (foo) _______
|
||
|
# | |
|
||
|
# | |
|
||
|
# nested_group_1 nested_group_2
|
||
|
# (bar) (barbaz)
|
||
|
# | |
|
||
|
# | |
|
||
|
# nested_group_1_1 nested_group_2_1
|
||
|
# (baz) (baz)
|
||
|
#
|
||
|
let!(:nested_group_1) { create :group, parent: group, name: 'bar' }
|
||
|
let!(:nested_group_1_1) { create :group, parent: nested_group_1, name: 'baz' }
|
||
|
let!(:nested_group_2) { create :group, parent: group, name: 'barbaz' }
|
||
|
let!(:nested_group_2_1) { create :group, parent: nested_group_2, name: 'baz' }
|
||
|
|
||
|
context 'user is not a member of any group' do
|
||
|
subject { described_class.member_hierarchy(user.id) }
|
||
|
|
||
|
it 'returns an empty array' do
|
||
|
is_expected.to eq []
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'user is member of all groups' do
|
||
|
before do
|
||
|
group.add_owner(user)
|
||
|
nested_group_1.add_owner(user)
|
||
|
nested_group_1_1.add_owner(user)
|
||
|
nested_group_2.add_owner(user)
|
||
|
nested_group_2_1.add_owner(user)
|
||
|
end
|
||
|
subject { described_class.member_hierarchy(user.id) }
|
||
|
|
||
|
it 'returns all groups' do
|
||
|
is_expected.to match_array [
|
||
|
group,
|
||
|
nested_group_1, nested_group_1_1,
|
||
|
nested_group_2, nested_group_2_1
|
||
|
]
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'user is member of the top group' do
|
||
|
before { group.add_owner(user) }
|
||
|
subject { described_class.member_hierarchy(user.id) }
|
||
|
|
||
|
it 'returns all groups' do
|
||
|
is_expected.to match_array [
|
||
|
group,
|
||
|
nested_group_1, nested_group_1_1,
|
||
|
nested_group_2, nested_group_2_1
|
||
|
]
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'user is member of the first child (internal node), branch 1' do
|
||
|
before { nested_group_1.add_owner(user) }
|
||
|
subject { described_class.member_hierarchy(user.id) }
|
||
|
|
||
|
it 'returns the groups in the hierarchy' do
|
||
|
is_expected.to match_array [
|
||
|
group,
|
||
|
nested_group_1, nested_group_1_1
|
||
|
]
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'user is member of the first child (internal node), branch 2' do
|
||
|
before { nested_group_2.add_owner(user) }
|
||
|
subject { described_class.member_hierarchy(user.id) }
|
||
|
|
||
|
it 'returns the groups in the hierarchy' do
|
||
|
is_expected.to match_array [
|
||
|
group,
|
||
|
nested_group_2, nested_group_2_1
|
||
|
]
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'user is member of the last child (leaf node)' do
|
||
|
before { nested_group_1_1.add_owner(user) }
|
||
|
subject { described_class.member_hierarchy(user.id) }
|
||
|
|
||
|
it 'returns the groups in the hierarchy' do
|
||
|
is_expected.to match_array [
|
||
|
group,
|
||
|
nested_group_1, nested_group_1_1
|
||
|
]
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe '#full_path' do
|
||
|
let(:group) { create(:group) }
|
||
|
let(:nested_group) { create(:group, parent: group) }
|
||
|
|
||
|
it { expect(group.full_path).to eq(group.path) }
|
||
|
it { expect(nested_group.full_path).to eq("#{group.full_path}/#{nested_group.path}") }
|
||
|
|
||
|
context 'with RequestStore active' do
|
||
|
before do
|
||
|
RequestStore.begin!
|
||
|
end
|
||
|
|
||
|
after do
|
||
|
RequestStore.end!
|
||
|
RequestStore.clear!
|
||
|
end
|
||
|
|
||
|
it 'does not load the route table more than once' do
|
||
|
expect(group).to receive(:uncached_full_path).once.and_call_original
|
||
|
|
||
|
3.times { group.full_path }
|
||
|
expect(group.full_path).to eq(group.path)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe '#full_name' do
|
||
|
let(:group) { create(:group) }
|
||
|
let(:nested_group) { create(:group, parent: group) }
|
||
|
|
||
|
it { expect(group.full_name).to eq(group.name) }
|
||
|
it { expect(nested_group.full_name).to eq("#{group.name} / #{nested_group.name}") }
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe Project, 'Routable' do
|
||
|
describe '#full_path' do
|
||
|
let(:project) { build_stubbed(:empty_project) }
|
||
|
|
||
|
it { expect(project.full_path).to eq "#{project.namespace.full_path}/#{project.path}" }
|
||
|
end
|
||
|
|
||
|
describe '#full_name' do
|
||
|
let(:project) { build_stubbed(:empty_project) }
|
||
|
|
||
|
it { expect(project.full_name).to eq "#{project.namespace.human_name} / #{project.name}" }
|
||
|
end
|
||
|
end
|