diff options
Diffstat (limited to 'spec')
8 files changed, 372 insertions, 207 deletions
diff --git a/spec/frontend/logs/components/environment_logs_spec.js b/spec/frontend/logs/components/environment_logs_spec.js index d097610cb0a..9046253bdc6 100644 --- a/spec/frontend/logs/components/environment_logs_spec.js +++ b/spec/frontend/logs/components/environment_logs_spec.js @@ -10,7 +10,6 @@ import { mockPods, mockLogsResult, mockTrace, - mockPodName, mockEnvironmentsEndpoint, mockDocumentationPath, } from '../mock_data'; @@ -302,11 +301,11 @@ describe('EnvironmentLogs', () => { }); it('refresh button, trace is refreshed', () => { - expect(dispatch).not.toHaveBeenCalledWith(`${module}/showPodLogs`, expect.anything()); + expect(dispatch).not.toHaveBeenCalledWith(`${module}/fetchLogs`, undefined); findLogControlButtons().vm.$emit('refresh'); - expect(dispatch).toHaveBeenCalledWith(`${module}/showPodLogs`, mockPodName); + expect(dispatch).toHaveBeenCalledWith(`${module}/fetchLogs`, undefined); }); }); }); diff --git a/spec/frontend/logs/components/log_advanced_filters_spec.js b/spec/frontend/logs/components/log_advanced_filters_spec.js index a6fbc40c2c6..adcd6b4fb07 100644 --- a/spec/frontend/logs/components/log_advanced_filters_spec.js +++ b/spec/frontend/logs/components/log_advanced_filters_spec.js @@ -1,8 +1,9 @@ -import { GlIcon, GlDropdownItem } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { defaultTimeRange } from '~/vue_shared/constants'; +import { GlFilteredSearch } from '@gitlab/ui'; import { convertToFixedRange } from '~/lib/utils/datetime_range'; import { createStore } from '~/logs/stores'; +import { TOKEN_TYPE_POD_NAME } from '~/logs/constants'; import { mockPods, mockSearch } from '../mock_data'; import LogAdvancedFilters from '~/logs/components/log_advanced_filters.vue'; @@ -15,26 +16,19 @@ describe('LogAdvancedFilters', () => { let wrapper; let state; - const findPodsDropdown = () => wrapper.find({ ref: 'podsDropdown' }); - const findPodsNoPodsText = () => wrapper.find({ ref: 'noPodsMsg' }); - const findPodsDropdownItems = () => - findPodsDropdown() - .findAll(GlDropdownItem) - .filter(item => !item.is('[disabled]')); - const findPodsDropdownItemsSelected = () => - findPodsDropdownItems() - .filter(item => { - return !item.find(GlIcon).classes('invisible'); - }) - .at(0); - const findSearchBox = () => wrapper.find({ ref: 'searchBox' }); + const findFilteredSearch = () => wrapper.find(GlFilteredSearch); const findTimeRangePicker = () => wrapper.find({ ref: 'dateTimePicker' }); + const getSearchToken = type => + findFilteredSearch() + .props('availableTokens') + .filter(token => token.type === type)[0]; const mockStateLoading = () => { state.timeRange.selected = defaultTimeRange; state.timeRange.current = convertToFixedRange(defaultTimeRange); state.pods.options = []; state.pods.current = null; + state.logs.isLoading = true; }; const mockStateWithData = () => { @@ -42,6 +36,7 @@ describe('LogAdvancedFilters', () => { state.timeRange.current = convertToFixedRange(defaultTimeRange); state.pods.options = mockPods; state.pods.current = null; + state.logs.isLoading = false; }; const initWrapper = (propsData = {}) => { @@ -76,11 +71,18 @@ describe('LogAdvancedFilters', () => { expect(wrapper.isVueInstance()).toBe(true); expect(wrapper.isEmpty()).toBe(false); - expect(findPodsDropdown().exists()).toBe(true); - expect(findSearchBox().exists()).toBe(true); + expect(findFilteredSearch().exists()).toBe(true); expect(findTimeRangePicker().exists()).toBe(true); }); + it('displays search tokens', () => { + expect(getSearchToken(TOKEN_TYPE_POD_NAME)).toMatchObject({ + title: 'Pod name', + unique: true, + operators: [expect.objectContaining({ value: '=' })], + }); + }); + describe('disabled state', () => { beforeEach(() => { mockStateLoading(); @@ -90,9 +92,7 @@ describe('LogAdvancedFilters', () => { }); it('displays disabled filters', () => { - expect(findPodsDropdown().props('text')).toBe('All pods'); - expect(findPodsDropdown().attributes('disabled')).toBeTruthy(); - expect(findSearchBox().attributes('disabled')).toBeTruthy(); + expect(findFilteredSearch().attributes('disabled')).toBeTruthy(); expect(findTimeRangePicker().attributes('disabled')).toBeTruthy(); }); }); @@ -103,16 +103,17 @@ describe('LogAdvancedFilters', () => { initWrapper(); }); - it('displays a enabled filters', () => { - expect(findPodsDropdown().props('text')).toBe('All pods'); - expect(findPodsDropdown().attributes('disabled')).toBeFalsy(); - expect(findSearchBox().attributes('disabled')).toBeFalsy(); + it('displays a disabled search', () => { + expect(findFilteredSearch().attributes('disabled')).toBeTruthy(); + }); + + it('displays an enable date filter', () => { expect(findTimeRangePicker().attributes('disabled')).toBeFalsy(); }); - it('displays an empty pods dropdown', () => { - expect(findPodsNoPodsText().exists()).toBe(true); - expect(findPodsDropdownItems()).toHaveLength(0); + it('displays no pod options when no pods are available, so suggestions can be displayed', () => { + expect(getSearchToken(TOKEN_TYPE_POD_NAME).options).toBe(null); + expect(getSearchToken(TOKEN_TYPE_POD_NAME).loading).toBe(true); }); }); @@ -122,20 +123,24 @@ describe('LogAdvancedFilters', () => { initWrapper(); }); - it('displays an enabled pods dropdown', () => { - expect(findPodsDropdown().attributes('disabled')).toBeFalsy(); - expect(findPodsDropdown().props('text')).toBe('All pods'); + it('displays a single token for pods', () => { + initWrapper(); + + const tokens = findFilteredSearch().props('availableTokens'); + + expect(tokens).toHaveLength(1); + expect(tokens[0].type).toBe(TOKEN_TYPE_POD_NAME); }); - it('displays options in a pods dropdown', () => { - const items = findPodsDropdownItems(); - expect(items).toHaveLength(mockPods.length + 1); + it('displays a enabled filters', () => { + expect(findFilteredSearch().attributes('disabled')).toBeFalsy(); + expect(findTimeRangePicker().attributes('disabled')).toBeFalsy(); }); - it('displays "all pods" selected in a pods dropdown', () => { - const selected = findPodsDropdownItemsSelected(); + it('displays options in the pods token', () => { + const { options } = getSearchToken(TOKEN_TYPE_POD_NAME); - expect(selected.text()).toBe('All pods'); + expect(options).toHaveLength(mockPods.length); }); it('displays options in date time picker', () => { @@ -146,30 +151,16 @@ describe('LogAdvancedFilters', () => { }); describe('when the user interacts', () => { - it('clicks on a all options, showPodLogs is dispatched with null', () => { - const items = findPodsDropdownItems(); - items.at(0).vm.$emit('click'); - - expect(dispatch).toHaveBeenCalledWith(`${module}/showPodLogs`, null); - }); - - it('clicks on a pod name, showPodLogs is dispatched with pod name', () => { - const items = findPodsDropdownItems(); - const index = 2; // any pod + it('clicks on the search button, showFilteredLogs is dispatched', () => { + findFilteredSearch().vm.$emit('submit', null); - items.at(index + 1).vm.$emit('click'); // skip "All pods" option - - expect(dispatch).toHaveBeenCalledWith(`${module}/showPodLogs`, mockPods[index]); + expect(dispatch).toHaveBeenCalledWith(`${module}/showFilteredLogs`, null); }); - it('clicks on search, a serches is done', () => { - expect(findSearchBox().attributes('disabled')).toBeFalsy(); - - // input a query and click `search` - findSearchBox().vm.$emit('input', mockSearch); - findSearchBox().vm.$emit('submit'); + it('clicks on the search button, showFilteredLogs is dispatched with null', () => { + findFilteredSearch().vm.$emit('submit', [mockSearch]); - expect(dispatch).toHaveBeenCalledWith(`${module}/setSearch`, mockSearch); + expect(dispatch).toHaveBeenCalledWith(`${module}/showFilteredLogs`, [mockSearch]); }); it('selects a new time range', () => { diff --git a/spec/frontend/logs/components/tokens/token_with_loading_state_spec.js b/spec/frontend/logs/components/tokens/token_with_loading_state_spec.js new file mode 100644 index 00000000000..d98d7d05c92 --- /dev/null +++ b/spec/frontend/logs/components/tokens/token_with_loading_state_spec.js @@ -0,0 +1,68 @@ +import { GlFilteredSearchToken, GlLoadingIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; + +import TokenWithLoadingState from '~/logs/components/tokens/token_with_loading_state.vue'; + +describe('TokenWithLoadingState', () => { + let wrapper; + + const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken); + const findLoadingIcon = () => wrapper.find(GlLoadingIcon); + + const initWrapper = (props = {}, options) => { + wrapper = shallowMount(TokenWithLoadingState, { + propsData: props, + ...options, + }); + }; + + beforeEach(() => {}); + + it('passes entire config correctly', () => { + const config = { + icon: 'pod', + type: 'pod', + title: 'Pod name', + unique: true, + }; + + initWrapper({ config }); + + expect(findFilteredSearchToken().props('config')).toEqual(config); + }); + + describe('suggestions are replaced', () => { + let mockNoOptsText; + let config; + let stubs; + + beforeEach(() => { + mockNoOptsText = 'No suggestions available'; + config = { + loading: false, + noOptionsText: mockNoOptsText, + }; + stubs = { + GlFilteredSearchToken: { + template: `<div><slot name="suggestions"></slot></div>`, + }, + }; + }); + + it('renders a loading icon', () => { + config.loading = true; + + initWrapper({ config }, { stubs }); + + expect(findLoadingIcon().exists()).toBe(true); + expect(wrapper.text()).toBe(''); + }); + + it('renders an empty results message', () => { + initWrapper({ config }, { stubs }); + + expect(findLoadingIcon().exists()).toBe(false); + expect(wrapper.text()).toBe(mockNoOptsText); + }); + }); +}); diff --git a/spec/frontend/logs/stores/actions_spec.js b/spec/frontend/logs/stores/actions_spec.js index 882673af984..6199c400e16 100644 --- a/spec/frontend/logs/stores/actions_spec.js +++ b/spec/frontend/logs/stores/actions_spec.js @@ -6,7 +6,7 @@ import { convertToFixedRange } from '~/lib/utils/datetime_range'; import logsPageState from '~/logs/stores/state'; import { setInitData, - setSearch, + showFilteredLogs, showPodLogs, fetchEnvironments, fetchLogs, @@ -31,6 +31,7 @@ import { mockCursor, mockNextCursor, } from '../mock_data'; +import { TOKEN_TYPE_POD_NAME } from '~/logs/constants'; jest.mock('~/flash'); jest.mock('~/lib/utils/datetime_range'); @@ -93,13 +94,80 @@ describe('Logs Store actions', () => { )); }); - describe('setSearch', () => { - it('should commit search mutation', () => + describe('showFilteredLogs', () => { + it('empty search should filter with defaults', () => testAction( - setSearch, - mockSearch, + showFilteredLogs, + undefined, state, - [{ type: types.SET_SEARCH, payload: mockSearch }], + [ + { type: types.SET_CURRENT_POD_NAME, payload: null }, + { type: types.SET_SEARCH, payload: '' }, + ], + [{ type: 'fetchLogs' }], + )); + + it('text search should filter with a search term', () => + testAction( + showFilteredLogs, + [mockSearch], + state, + [ + { type: types.SET_CURRENT_POD_NAME, payload: null }, + { type: types.SET_SEARCH, payload: mockSearch }, + ], + [{ type: 'fetchLogs' }], + )); + + it('pod search should filter with a search term', () => + testAction( + showFilteredLogs, + [{ type: TOKEN_TYPE_POD_NAME, value: { data: mockPodName, operator: '=' } }], + state, + [ + { type: types.SET_CURRENT_POD_NAME, payload: mockPodName }, + { type: types.SET_SEARCH, payload: '' }, + ], + [{ type: 'fetchLogs' }], + )); + + it('pod search should filter with a pod selection and a search term', () => + testAction( + showFilteredLogs, + [{ type: TOKEN_TYPE_POD_NAME, value: { data: mockPodName, operator: '=' } }, mockSearch], + state, + [ + { type: types.SET_CURRENT_POD_NAME, payload: mockPodName }, + { type: types.SET_SEARCH, payload: mockSearch }, + ], + [{ type: 'fetchLogs' }], + )); + + it('pod search should filter with a pod selection and two search terms', () => + testAction( + showFilteredLogs, + ['term1', 'term2'], + state, + [ + { type: types.SET_CURRENT_POD_NAME, payload: null }, + { type: types.SET_SEARCH, payload: `term1 term2` }, + ], + [{ type: 'fetchLogs' }], + )); + + it('pod search should filter with a pod selection and a search terms before and after', () => + testAction( + showFilteredLogs, + [ + 'term1', + { type: TOKEN_TYPE_POD_NAME, value: { data: mockPodName, operator: '=' } }, + 'term2', + ], + state, + [ + { type: types.SET_CURRENT_POD_NAME, payload: mockPodName }, + { type: types.SET_SEARCH, payload: `term1 term2` }, + ], [{ type: 'fetchLogs' }], )); }); diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index 5a9ca9f7b7e..13f1bcb389a 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -655,4 +655,26 @@ describe GroupPolicy do end end end + + it_behaves_like 'model with wiki policies' do + let(:container) { create(:group) } + + def set_access_level(access_level) + allow(container).to receive(:wiki_access_level).and_return(access_level) + end + + before do + stub_feature_flags(group_wiki: true) + end + + context 'when the feature flag is disabled' do + before do + stub_feature_flags(group_wiki: false) + end + + it 'does not include the wiki permissions' do + expect_disallowed(*permissions) + end + end + end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index d098369e124..db643e3a31f 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -121,147 +121,11 @@ describe ProjectPolicy do expect(Ability).not_to be_allowed(user, :read_issue, project) end - context 'wiki feature' do - let(:permissions) { %i(read_wiki create_wiki update_wiki admin_wiki download_wiki_code) } + it_behaves_like 'model with wiki policies' do + let(:container) { project } - subject { described_class.new(owner, project) } - - context 'when the feature is disabled' do - before do - project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED) - end - - it 'does not include the wiki permissions' do - expect_disallowed(*permissions) - end - - context 'when there is an external wiki' do - it 'does not include the wiki permissions' do - allow(project).to receive(:has_external_wiki?).and_return(true) - - expect_disallowed(*permissions) - end - end - end - - describe 'read_wiki' do - subject { described_class.new(user, project) } - - member_roles = %i[guest developer] - stranger_roles = %i[anonymous non_member] - - user_roles = stranger_roles + member_roles - - # When a user is anonymous, their `current_user == nil` - let(:user) { create(:user) unless user_role == :anonymous } - - before do - project.visibility = project_visibility - project.project_feature.update_attribute(:wiki_access_level, wiki_access_level) - project.add_user(user, user_role) if member_roles.include?(user_role) - end - - title = ->(project_visibility, wiki_access_level, user_role) do - [ - "project is #{Gitlab::VisibilityLevel.level_name project_visibility}", - "wiki is #{ProjectFeature.str_from_access_level wiki_access_level}", - "user is #{user_role}" - ].join(', ') - end - - describe 'Situations where :read_wiki is always false' do - where(case_names: title, - project_visibility: Gitlab::VisibilityLevel.options.values, - wiki_access_level: [ProjectFeature::DISABLED], - user_role: user_roles) - - with_them do - it { is_expected.to be_disallowed(:read_wiki) } - end - end - - describe 'Situations where :read_wiki is always true' do - where(case_names: title, - project_visibility: [Gitlab::VisibilityLevel::PUBLIC], - wiki_access_level: [ProjectFeature::ENABLED], - user_role: user_roles) - - with_them do - it { is_expected.to be_allowed(:read_wiki) } - end - end - - describe 'Situations where :read_wiki requires project membership' do - context 'the wiki is private, and the user is a member' do - where(case_names: title, - project_visibility: [Gitlab::VisibilityLevel::PUBLIC, - Gitlab::VisibilityLevel::INTERNAL], - wiki_access_level: [ProjectFeature::PRIVATE], - user_role: member_roles) - - with_them do - it { is_expected.to be_allowed(:read_wiki) } - end - end - - context 'the wiki is private, and the user is not member' do - where(case_names: title, - project_visibility: [Gitlab::VisibilityLevel::PUBLIC, - Gitlab::VisibilityLevel::INTERNAL], - wiki_access_level: [ProjectFeature::PRIVATE], - user_role: stranger_roles) - - with_them do - it { is_expected.to be_disallowed(:read_wiki) } - end - end - - context 'the wiki is enabled, and the user is a member' do - where(case_names: title, - project_visibility: [Gitlab::VisibilityLevel::PRIVATE], - wiki_access_level: [ProjectFeature::ENABLED], - user_role: member_roles) - - with_them do - it { is_expected.to be_allowed(:read_wiki) } - end - end - - context 'the wiki is enabled, and the user is not a member' do - where(case_names: title, - project_visibility: [Gitlab::VisibilityLevel::PRIVATE], - wiki_access_level: [ProjectFeature::ENABLED], - user_role: stranger_roles) - - with_them do - it { is_expected.to be_disallowed(:read_wiki) } - end - end - end - - describe 'Situations where :read_wiki prohibits anonymous access' do - context 'the user is not anonymous' do - where(case_names: title, - project_visibility: [Gitlab::VisibilityLevel::INTERNAL], - wiki_access_level: [ProjectFeature::ENABLED, ProjectFeature::PUBLIC], - user_role: user_roles.reject { |u| u == :anonymous }) - - with_them do - it { is_expected.to be_allowed(:read_wiki) } - end - end - - context 'the user is not anonymous' do - where(case_names: title, - project_visibility: [Gitlab::VisibilityLevel::INTERNAL], - wiki_access_level: [ProjectFeature::ENABLED, ProjectFeature::PUBLIC], - user_role: %i[anonymous]) - - with_them do - it { is_expected.to be_disallowed(:read_wiki) } - end - end - end + def set_access_level(access_level) + project.project_feature.update_attribute(:wiki_access_level, access_level) end end diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb index 4f81a71f586..c2797c49c02 100644 --- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb @@ -14,16 +14,17 @@ RSpec.shared_context 'GroupPolicy context' do %i[ read_label read_group upload_file read_namespace read_group_activity read_group_issues read_group_boards read_group_labels read_group_milestones - read_group_merge_requests + read_group_merge_requests read_wiki ] end let(:read_group_permissions) { %i[read_label read_list read_milestone read_board] } - let(:reporter_permissions) { %i[admin_label read_container_image read_metrics_dashboard_annotation] } - let(:developer_permissions) { %i[admin_milestone create_metrics_dashboard_annotation delete_metrics_dashboard_annotation update_metrics_dashboard_annotation] } + let(:reporter_permissions) { %i[admin_label read_container_image read_metrics_dashboard_annotation download_wiki_code] } + let(:developer_permissions) { %i[admin_milestone create_metrics_dashboard_annotation delete_metrics_dashboard_annotation update_metrics_dashboard_annotation create_wiki] } let(:maintainer_permissions) do %i[ create_projects read_cluster create_cluster update_cluster admin_cluster add_cluster + admin_wiki ] end let(:owner_permissions) do diff --git a/spec/support/shared_examples/policies/wiki_policies_shared_examples.rb b/spec/support/shared_examples/policies/wiki_policies_shared_examples.rb new file mode 100644 index 00000000000..b91500ffd9c --- /dev/null +++ b/spec/support/shared_examples/policies/wiki_policies_shared_examples.rb @@ -0,0 +1,152 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'model with wiki policies' do + let(:container) { raise NotImplementedError } + let(:permissions) { %i(read_wiki create_wiki update_wiki admin_wiki download_wiki_code) } + + # TODO: Remove this helper once we implement group features + # https://gitlab.com/gitlab-org/gitlab/-/issues/208412 + def set_access_level(access_level) + raise NotImplementedError + end + + subject { described_class.new(owner, container) } + + context 'when the feature is disabled' do + before do + set_access_level(ProjectFeature::DISABLED) + end + + it 'does not include the wiki permissions' do + expect_disallowed(*permissions) + end + + context 'when there is an external wiki' do + it 'does not include the wiki permissions' do + allow(container).to receive(:has_external_wiki?).and_return(true) + + expect_disallowed(*permissions) + end + end + end + + describe 'read_wiki' do + subject { described_class.new(user, container) } + + member_roles = %i[guest developer] + stranger_roles = %i[anonymous non_member] + + user_roles = stranger_roles + member_roles + + # When a user is anonymous, their `current_user == nil` + let(:user) { create(:user) unless user_role == :anonymous } + + before do + container.visibility = container_visibility + set_access_level(wiki_access_level) + container.add_user(user, user_role) if member_roles.include?(user_role) + end + + title = ->(container_visibility, wiki_access_level, user_role) do + [ + "container is #{Gitlab::VisibilityLevel.level_name container_visibility}", + "wiki is #{ProjectFeature.str_from_access_level wiki_access_level}", + "user is #{user_role}" + ].join(', ') + end + + describe 'Situations where :read_wiki is always false' do + where(case_names: title, + container_visibility: Gitlab::VisibilityLevel.options.values, + wiki_access_level: [ProjectFeature::DISABLED], + user_role: user_roles) + + with_them do + it { is_expected.to be_disallowed(:read_wiki) } + end + end + + describe 'Situations where :read_wiki is always true' do + where(case_names: title, + container_visibility: [Gitlab::VisibilityLevel::PUBLIC], + wiki_access_level: [ProjectFeature::ENABLED], + user_role: user_roles) + + with_them do + it { is_expected.to be_allowed(:read_wiki) } + end + end + + describe 'Situations where :read_wiki requires membership' do + context 'the wiki is private, and the user is a member' do + where(case_names: title, + container_visibility: [Gitlab::VisibilityLevel::PUBLIC, + Gitlab::VisibilityLevel::INTERNAL], + wiki_access_level: [ProjectFeature::PRIVATE], + user_role: member_roles) + + with_them do + it { is_expected.to be_allowed(:read_wiki) } + end + end + + context 'the wiki is private, and the user is not member' do + where(case_names: title, + container_visibility: [Gitlab::VisibilityLevel::PUBLIC, + Gitlab::VisibilityLevel::INTERNAL], + wiki_access_level: [ProjectFeature::PRIVATE], + user_role: stranger_roles) + + with_them do + it { is_expected.to be_disallowed(:read_wiki) } + end + end + + context 'the wiki is enabled, and the user is a member' do + where(case_names: title, + container_visibility: [Gitlab::VisibilityLevel::PRIVATE], + wiki_access_level: [ProjectFeature::ENABLED], + user_role: member_roles) + + with_them do + it { is_expected.to be_allowed(:read_wiki) } + end + end + + context 'the wiki is enabled, and the user is not a member' do + where(case_names: title, + container_visibility: [Gitlab::VisibilityLevel::PRIVATE], + wiki_access_level: [ProjectFeature::ENABLED], + user_role: stranger_roles) + + with_them do + it { is_expected.to be_disallowed(:read_wiki) } + end + end + end + + describe 'Situations where :read_wiki prohibits anonymous access' do + context 'the user is not anonymous' do + where(case_names: title, + container_visibility: [Gitlab::VisibilityLevel::INTERNAL], + wiki_access_level: [ProjectFeature::ENABLED, ProjectFeature::PUBLIC], + user_role: user_roles.reject { |u| u == :anonymous }) + + with_them do + it { is_expected.to be_allowed(:read_wiki) } + end + end + + context 'the user is anonymous' do + where(case_names: title, + container_visibility: [Gitlab::VisibilityLevel::INTERNAL], + wiki_access_level: [ProjectFeature::ENABLED, ProjectFeature::PUBLIC], + user_role: %i[anonymous]) + + with_them do + it { is_expected.to be_disallowed(:read_wiki) } + end + end + end + end +end |