diff options
Diffstat (limited to 'spec/models/namespaces/traversal/cached_spec.rb')
-rw-r--r-- | spec/models/namespaces/traversal/cached_spec.rb | 196 |
1 files changed, 135 insertions, 61 deletions
diff --git a/spec/models/namespaces/traversal/cached_spec.rb b/spec/models/namespaces/traversal/cached_spec.rb index 8263e28bb98..dd52f9c3d70 100644 --- a/spec/models/namespaces/traversal/cached_spec.rb +++ b/spec/models/namespaces/traversal/cached_spec.rb @@ -3,101 +3,175 @@ require 'spec_helper' RSpec.describe Namespaces::Traversal::Cached, feature_category: :database do - let_it_be_with_refind(:old_parent) { create(:group) } - let_it_be_with_refind(:new_parent) { create(:group) } - let_it_be_with_refind(:group) { create(:group, parent: old_parent) } - let_it_be_with_refind(:subgroup) { create(:group, parent: group) } + describe 'callbacks' do + let_it_be_with_refind(:old_parent) { create(:group) } + let_it_be_with_refind(:new_parent) { create(:group) } + let_it_be_with_refind(:group) { create(:group, parent: old_parent) } + let_it_be_with_refind(:subgroup) { create(:group, parent: group) } - context 'when the namespace_descendants_cache_expiration feature flag is off' do - let!(:cache) { create(:namespace_descendants, namespace: group) } + context 'when the namespace_descendants_cache_expiration feature flag is off' do + let!(:cache) { create(:namespace_descendants, namespace: group) } - before do - stub_feature_flags(namespace_descendants_cache_expiration: false) - end + before do + stub_feature_flags(namespace_descendants_cache_expiration: false) + end - it 'does not invalidate the cache' do - expect { group.update!(parent: new_parent) }.not_to change { cache.reload.outdated_at } - end + it 'does not invalidate the cache' do + expect { group.update!(parent: new_parent) }.not_to change { cache.reload.outdated_at } + end - context 'when the group is deleted' do - it 'invalidates the cache' do - expect { group.destroy! }.not_to change { cache.reload.outdated_at } + context 'when the group is deleted' do + it 'invalidates the cache' do + expect { group.destroy! }.not_to change { cache.reload.outdated_at } + end end end - end - context 'when no cached records are present' do - it 'does nothing' do - group.parent = new_parent + context 'when no cached records are present' do + it 'does nothing' do + group.parent = new_parent - expect { group.save! }.not_to change { Namespaces::Descendants.all.to_a } + expect { group.save! }.not_to change { Namespaces::Descendants.all.to_a } + end end - end - context 'when the namespace record is UserNamespace' do - it 'does nothing' do - # we won't use the optimization for UserNamespace - namespace = create(:user_namespace) - cache = create(:namespace_descendants, namespace: namespace) + context 'when the namespace record is UserNamespace' do + it 'does nothing' do + # we won't use the optimization for UserNamespace + namespace = create(:user_namespace) + cache = create(:namespace_descendants, namespace: namespace) - expect { namespace.destroy! }.not_to change { cache.reload.outdated_at } + expect { namespace.destroy! }.not_to change { cache.reload.outdated_at } + end end - end - context 'when cached record is present' do - let!(:cache) { create(:namespace_descendants, namespace: group) } + context 'when cached record is present' do + let!(:cache) { create(:namespace_descendants, namespace: group) } - it 'invalidates the cache' do - expect { group.update!(parent: new_parent) }.to change { cache.reload.outdated_at }.from(nil) - end + it 'invalidates the cache' do + expect { group.update!(parent: new_parent) }.to change { cache.reload.outdated_at }.from(nil) + end - it 'does not invalidate the cache of subgroups' do - subgroup_cache = create(:namespace_descendants, namespace: subgroup) + it 'does not invalidate the cache of subgroups' do + subgroup_cache = create(:namespace_descendants, namespace: subgroup) - expect { group.update!(parent: new_parent) }.not_to change { subgroup_cache.reload.outdated_at } + expect { group.update!(parent: new_parent) }.not_to change { subgroup_cache.reload.outdated_at } + end + + context 'when a new subgroup is added' do + it 'invalidates the cache' do + expect { create(:group, parent: group) }.to change { cache.reload.outdated_at } + end + end + + context 'when a new project is added' do + it 'invalidates the cache' do + expect { create(:project, group: group) }.to change { cache.reload.outdated_at } + end + end end - context 'when a new subgroup is added' do - it 'invalidates the cache' do - expect { create(:group, parent: group) }.to change { cache.reload.outdated_at } + context 'when parent group has cached record' do + it 'invalidates the parent cache' do + old_parent_cache = create(:namespace_descendants, namespace: old_parent) + new_parent_cache = create(:namespace_descendants, namespace: new_parent) + + group.update!(parent: new_parent) + + expect(old_parent_cache.reload.outdated_at).not_to be_nil + expect(new_parent_cache.reload.outdated_at).not_to be_nil end end - context 'when a new project is added' do + context 'when group is destroyed' do it 'invalidates the cache' do - expect { create(:project, group: group) }.to change { cache.reload.outdated_at } + cache = create(:namespace_descendants, namespace: group) + + expect { group.destroy! }.to change { cache.reload.outdated_at }.from(nil) end - end - end - context 'when parent group has cached record' do - it 'invalidates the parent cache' do - old_parent_cache = create(:namespace_descendants, namespace: old_parent) - new_parent_cache = create(:namespace_descendants, namespace: new_parent) + context 'when parent group has cached record' do + it 'invalidates the parent cache' do + old_parent_cache = create(:namespace_descendants, namespace: old_parent) + new_parent_cache = create(:namespace_descendants, namespace: new_parent) - group.update!(parent: new_parent) + group.destroy! - expect(old_parent_cache.reload.outdated_at).not_to be_nil - expect(new_parent_cache.reload.outdated_at).not_to be_nil + expect(old_parent_cache.reload.outdated_at).not_to be_nil + expect(new_parent_cache.reload.outdated_at).to be_nil # no change + end + end end end - context 'when group is destroyed' do - it 'invalidates the cache' do - cache = create(:namespace_descendants, namespace: group) + describe 'query methods' do + let_it_be(:group) { create(:group) } + let_it_be(:subgroup) { create(:group, parent: group) } + let_it_be(:subsubgroup) { create(:group, parent: subgroup) } + + let_it_be(:project1) { create(:project, group: group) } + let_it_be(:project2) { create(:project, group: subsubgroup) } + + # deliberately making self_and_descendant_group_ids different from the actual + # self_and_descendant_ids so we can verify that the cached query is running. + let_it_be_with_refind(:namespace_descendants) do + create(:namespace_descendants, + :up_to_date, + namespace: group, + self_and_descendant_group_ids: [group.id, subgroup.id], + all_project_ids: [project1.id] + ) + end + + describe '#self_and_descendant_ids' do + subject(:ids) { group.self_and_descendant_ids.pluck(:id) } + + it 'returns the cached values' do + expect(ids).to eq(namespace_descendants.self_and_descendant_group_ids) + end + + context 'when the cache is outdated' do + it 'returns the values from the uncached self_and_descendant_ids query' do + namespace_descendants.update!(outdated_at: Time.current) + + expect(ids.sort).to eq([group.id, subgroup.id, subsubgroup.id]) + end + end + + context 'when the group_hierarchy_optimization feature flag is disabled' do + before do + stub_feature_flags(group_hierarchy_optimization: false) + end - expect { group.destroy! }.to change { cache.reload.outdated_at }.from(nil) + it 'returns the values from the uncached self_and_descendant_ids query' do + expect(ids.sort).to eq([group.id, subgroup.id, subsubgroup.id]) + end + end end - context 'when parent group has cached record' do - it 'invalidates the parent cache' do - old_parent_cache = create(:namespace_descendants, namespace: old_parent) - new_parent_cache = create(:namespace_descendants, namespace: new_parent) + describe '#all_project_ids' do + subject(:ids) { group.all_project_ids.pluck(:id) } - group.destroy! + it 'returns the cached values' do + expect(ids).to eq(namespace_descendants.all_project_ids) + end - expect(old_parent_cache.reload.outdated_at).not_to be_nil - expect(new_parent_cache.reload.outdated_at).to be_nil # no change + context 'when the cache is outdated' do + it 'returns the values from the uncached all_project_ids query' do + namespace_descendants.update!(outdated_at: Time.current) + + expect(ids.sort).to eq([project1.id, project2.id]) + end + end + + context 'when the group_hierarchy_optimization feature flag is disabled' do + before do + stub_feature_flags(group_hierarchy_optimization: false) + end + + it 'returns the values from the uncached all_project_ids query' do + expect(ids.sort).to eq([project1.id, project2.id]) + end end end end |