2019-03-30 03:23:56 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-10-31 07:00:53 -04:00
|
|
|
require 'spec_helper'
|
|
|
|
|
2020-12-15 10:09:59 -05:00
|
|
|
RSpec.shared_examples '.find_by_full_path' do
|
|
|
|
describe '.find_by_full_path', :aggregate_failures do
|
|
|
|
it 'finds records by their full path' do
|
|
|
|
expect(described_class.find_by_full_path(record.full_path)).to eq(record)
|
|
|
|
expect(described_class.find_by_full_path(record.full_path.upcase)).to eq(record)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns nil for unknown paths' do
|
|
|
|
expect(described_class.find_by_full_path('unknown')).to be_nil
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'includes route information when loading a record' do
|
|
|
|
control_count = ActiveRecord::QueryRecorder.new do
|
|
|
|
described_class.find_by_full_path(record.full_path)
|
|
|
|
end.count
|
|
|
|
|
|
|
|
expect do
|
|
|
|
described_class.find_by_full_path(record.full_path).route
|
|
|
|
end.not_to exceed_all_query_limit(control_count)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with redirect routes' do
|
|
|
|
let_it_be(:redirect_route) { create(:redirect_route, source: record) }
|
|
|
|
|
|
|
|
context 'without follow_redirects option' do
|
|
|
|
it 'does not find records by their redirected path' do
|
|
|
|
expect(described_class.find_by_full_path(redirect_route.path)).to be_nil
|
|
|
|
expect(described_class.find_by_full_path(redirect_route.path.upcase)).to be_nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with follow_redirects option set to true' do
|
|
|
|
it 'finds records by their canonical path' do
|
|
|
|
expect(described_class.find_by_full_path(record.full_path, follow_redirects: true)).to eq(record)
|
|
|
|
expect(described_class.find_by_full_path(record.full_path.upcase, follow_redirects: true)).to eq(record)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'finds records by their redirected path' do
|
|
|
|
expect(described_class.find_by_full_path(redirect_route.path, follow_redirects: true)).to eq(record)
|
|
|
|
expect(described_class.find_by_full_path(redirect_route.path.upcase, follow_redirects: true)).to eq(record)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns nil for unknown paths' do
|
|
|
|
expect(described_class.find_by_full_path('unknown', follow_redirects: true)).to be_nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
RSpec.describe Routable do
|
|
|
|
it_behaves_like '.find_by_full_path' do
|
|
|
|
let_it_be(:record) { create(:group) }
|
|
|
|
end
|
|
|
|
|
|
|
|
it_behaves_like '.find_by_full_path' do
|
|
|
|
let_it_be(:record) { create(:project) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-05-13 05:10:44 -04:00
|
|
|
RSpec.describe Group, 'Routable', :with_clean_rails_cache do
|
2020-12-15 10:09:59 -05:00
|
|
|
let_it_be_with_reload(:group) { create(:group, name: 'foo') }
|
|
|
|
let_it_be(:nested_group) { create(:group, parent: group) }
|
2016-10-31 07:00:53 -04:00
|
|
|
|
2016-12-07 12:16:02 -05:00
|
|
|
describe 'Validations' do
|
|
|
|
it { is_expected.to validate_presence_of(:route) }
|
|
|
|
end
|
|
|
|
|
2016-10-31 07:00:53 -04:00
|
|
|
describe 'Associations' do
|
|
|
|
it { is_expected.to have_one(:route).dependent(:destroy) }
|
2017-05-01 16:46:30 -04:00
|
|
|
it { is_expected.to have_many(:redirect_routes).dependent(:destroy) }
|
2016-10-31 07:00:53 -04:00
|
|
|
end
|
2017-09-19 03:44:58 -04:00
|
|
|
|
2016-10-31 07:00:53 -04:00
|
|
|
describe 'Callbacks' do
|
2018-11-21 06:17:14 -05:00
|
|
|
context 'for a group' 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
|
2020-09-22 14:09:54 -04:00
|
|
|
group.update!(path: 'wow', name: 'much')
|
2018-11-21 06:17:14 -05:00
|
|
|
|
|
|
|
expect(group.route.path).to eq('wow')
|
|
|
|
expect(group.route.name).to eq('much')
|
|
|
|
end
|
2016-10-31 07:00:53 -04:00
|
|
|
|
2018-11-21 06:17:14 -05:00
|
|
|
it 'ensure route path uniqueness across different objects' do
|
|
|
|
create(:group, parent: group, path: 'xyz')
|
|
|
|
duplicate = build(:project, namespace: group, path: 'xyz')
|
2016-10-31 07:00:53 -04:00
|
|
|
|
2018-11-21 06:17:14 -05:00
|
|
|
expect { duplicate.save! }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Path has already been taken')
|
|
|
|
end
|
2016-10-31 07:00:53 -04:00
|
|
|
end
|
|
|
|
|
2018-11-21 06:17:14 -05:00
|
|
|
context 'for a user' do
|
|
|
|
let(:user) { create(:user, username: 'jane', name: "Jane Doe") }
|
|
|
|
|
|
|
|
it 'creates the route for a record on create' do
|
|
|
|
expect(user.namespace.name).to eq('Jane Doe')
|
|
|
|
expect(user.namespace.path).to eq('jane')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'updates routes and nested routes on name change' do
|
|
|
|
project = create(:project, path: 'work-stuff', name: 'Work stuff', namespace: user.namespace)
|
|
|
|
|
|
|
|
user.update!(username: 'jaen', name: 'Jaen Did')
|
|
|
|
project.reload
|
2016-10-31 07:00:53 -04:00
|
|
|
|
2018-11-21 06:17:14 -05:00
|
|
|
expect(user.namespace.name).to eq('Jaen Did')
|
|
|
|
expect(user.namespace.path).to eq('jaen')
|
|
|
|
expect(project.full_name).to eq('Jaen Did / Work stuff')
|
|
|
|
expect(project.full_path).to eq('jaen/work-stuff')
|
|
|
|
end
|
2016-10-31 07:00:53 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-02-06 16:08:48 -05:00
|
|
|
describe '.find_by_full_path' do
|
2020-12-15 10:09:59 -05:00
|
|
|
it_behaves_like '.find_by_full_path' do
|
|
|
|
let_it_be(:record) { group }
|
2017-05-01 16:46:30 -04:00
|
|
|
end
|
|
|
|
|
2020-12-15 10:09:59 -05:00
|
|
|
it_behaves_like '.find_by_full_path' do
|
|
|
|
let_it_be(:record) { nested_group }
|
|
|
|
end
|
2017-05-01 16:46:30 -04:00
|
|
|
|
2020-12-15 10:09:59 -05:00
|
|
|
it 'does not find projects with a matching path' do
|
|
|
|
project = create(:project)
|
|
|
|
redirect_route = create(:redirect_route, source: project)
|
2017-05-01 16:46:30 -04:00
|
|
|
|
2020-12-15 10:09:59 -05:00
|
|
|
expect(described_class.find_by_full_path(project.full_path)).to be_nil
|
|
|
|
expect(described_class.find_by_full_path(redirect_route.path, follow_redirects: true)).to be_nil
|
2017-05-01 16:46:30 -04:00
|
|
|
end
|
2016-10-31 07:00:53 -04:00
|
|
|
end
|
|
|
|
|
2016-12-09 10:27:11 -05:00
|
|
|
describe '.where_full_path_in' do
|
2016-10-31 07:00:53 -04:00
|
|
|
context 'without any paths' do
|
|
|
|
it 'returns an empty relation' do
|
2016-12-09 10:27:11 -05:00
|
|
|
expect(described_class.where_full_path_in([])).to eq([])
|
2016-10-31 07:00:53 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'without any valid paths' do
|
|
|
|
it 'returns an empty relation' do
|
2016-12-09 10:27:11 -05:00
|
|
|
expect(described_class.where_full_path_in(%w[unknown])).to eq([])
|
2016-10-31 07:00:53 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with valid paths' do
|
|
|
|
it 'returns the projects matching the paths' do
|
2016-12-09 10:27:11 -05:00
|
|
|
result = described_class.where_full_path_in([group.to_param, nested_group.to_param])
|
2016-10-31 07:00:53 -04:00
|
|
|
|
|
|
|
expect(result).to contain_exactly(group, nested_group)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns projects regardless of the casing of paths' do
|
2016-12-09 10:27:11 -05:00
|
|
|
result = described_class.where_full_path_in([group.to_param.upcase, nested_group.to_param.upcase])
|
2016-10-31 07:00:53 -04:00
|
|
|
|
|
|
|
expect(result).to contain_exactly(group, nested_group)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2017-01-05 12:20:12 -05:00
|
|
|
|
2021-05-13 05:10:44 -04:00
|
|
|
describe '#parent_loaded?' do
|
|
|
|
before do
|
|
|
|
group.parent = create(:group)
|
|
|
|
group.save!
|
|
|
|
|
|
|
|
group.reload
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'is false when the parent is not loaded' do
|
|
|
|
expect(group.parent_loaded?).to be_falsey
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'is true when the parent is loaded' do
|
|
|
|
group.parent
|
|
|
|
|
|
|
|
expect(group.parent_loaded?).to be_truthy
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#route_loaded?' do
|
|
|
|
it 'is false when the route is not loaded' do
|
|
|
|
expect(group.route_loaded?).to be_falsey
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'is true when the route is loaded' do
|
|
|
|
group.route
|
|
|
|
|
|
|
|
expect(group.route_loaded?).to be_truthy
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-02-04 13:26:11 -05:00
|
|
|
describe '#full_path' do
|
|
|
|
it { expect(group.full_path).to eq(group.path) }
|
2017-02-23 18:55:01 -05:00
|
|
|
it { expect(nested_group.full_path).to eq("#{group.full_path}/#{nested_group.path}") }
|
2021-05-13 05:10:44 -04:00
|
|
|
|
|
|
|
it 'hits the cache when not preloaded' do
|
|
|
|
forcibly_hit_cached_lookup(nested_group, :full_path)
|
|
|
|
|
|
|
|
expect(nested_group.full_path).to eq("#{group.full_path}/#{nested_group.path}")
|
|
|
|
end
|
2017-06-29 17:53:32 -04:00
|
|
|
end
|
|
|
|
|
2017-02-04 13:26:11 -05:00
|
|
|
describe '#full_name' do
|
|
|
|
it { expect(group.full_name).to eq(group.name) }
|
|
|
|
it { expect(nested_group.full_name).to eq("#{group.name} / #{nested_group.name}") }
|
2021-05-13 05:10:44 -04:00
|
|
|
|
|
|
|
it 'hits the cache when not preloaded' do
|
|
|
|
forcibly_hit_cached_lookup(nested_group, :full_name)
|
|
|
|
|
|
|
|
expect(nested_group.full_name).to eq("#{group.name} / #{nested_group.name}")
|
|
|
|
end
|
2017-02-04 13:26:11 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-05-13 05:10:44 -04:00
|
|
|
RSpec.describe Project, 'Routable', :with_clean_rails_cache do
|
|
|
|
let_it_be(:namespace) { create(:namespace) }
|
|
|
|
let_it_be(:project) { create(:project, namespace: namespace) }
|
2017-02-04 13:26:11 -05:00
|
|
|
|
2020-12-15 10:09:59 -05:00
|
|
|
it_behaves_like '.find_by_full_path' do
|
|
|
|
let_it_be(:record) { project }
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not find groups with a matching path' do
|
|
|
|
group = create(:group)
|
|
|
|
redirect_route = create(:redirect_route, source: group)
|
|
|
|
|
|
|
|
expect(described_class.find_by_full_path(group.full_path)).to be_nil
|
|
|
|
expect(described_class.find_by_full_path(redirect_route.path, follow_redirects: true)).to be_nil
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#full_path' do
|
2021-05-13 05:10:44 -04:00
|
|
|
it { expect(project.full_path).to eq "#{namespace.full_path}/#{project.path}" }
|
|
|
|
|
|
|
|
it 'hits the cache when not preloaded' do
|
|
|
|
forcibly_hit_cached_lookup(project, :full_path)
|
|
|
|
|
|
|
|
expect(project.full_path).to eq("#{namespace.full_path}/#{project.path}")
|
|
|
|
end
|
2017-02-04 13:26:11 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
describe '#full_name' do
|
2021-05-13 05:10:44 -04:00
|
|
|
it { expect(project.full_name).to eq "#{namespace.human_name} / #{project.name}" }
|
|
|
|
|
|
|
|
it 'hits the cache when not preloaded' do
|
|
|
|
forcibly_hit_cached_lookup(project, :full_name)
|
|
|
|
|
|
|
|
expect(project.full_name).to eq("#{namespace.human_name} / #{project.name}")
|
|
|
|
end
|
2017-02-04 13:26:11 -05:00
|
|
|
end
|
2016-10-31 07:00:53 -04:00
|
|
|
end
|
2021-05-13 05:10:44 -04:00
|
|
|
|
|
|
|
def forcibly_hit_cached_lookup(record, method)
|
|
|
|
stub_feature_flags(cached_route_lookups: true)
|
|
|
|
expect(record).to receive(:persisted?).and_return(true)
|
|
|
|
expect(record).to receive(:route_loaded?).and_return(false)
|
|
|
|
expect(record).to receive(:parent_loaded?).and_return(false)
|
|
|
|
expect(Gitlab::Cache).to receive(:fetch_once).with([record.cache_key, method]).and_call_original
|
|
|
|
end
|