diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-15 03:14:10 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-15 03:14:10 +0300 |
commit | 119c999cf1f1bb51d4324e3c4847435347eb32cf (patch) | |
tree | 83f92f606877455177bb57c7d2ad31704d10027b /spec | |
parent | 5594a6badf033359b84c2e9822f145c66b0dce8f (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
27 files changed, 774 insertions, 187 deletions
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index e0adad832f5..b30610d98d7 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -920,6 +920,7 @@ RSpec.describe ProjectsController do environments_access_level feature_flags_access_level releases_access_level + monitor_access_level ] end @@ -947,6 +948,7 @@ RSpec.describe ProjectsController do where(:feature_access_level) do %i[ environments_access_level feature_flags_access_level + monitor_access_level ] end diff --git a/spec/factories/packages/rpm/metadata.rb b/spec/factories/packages/rpm/metadata.rb index 96c785bd1ae..5ee85aed3bb 100644 --- a/spec/factories/packages/rpm/metadata.rb +++ b/spec/factories/packages/rpm/metadata.rb @@ -7,5 +7,6 @@ FactoryBot.define do summary { FFaker::Lorem.sentences(2).join } description { FFaker::Lorem.sentences(4).join } arch { FFaker::Lorem.word } + epoch { 0 } end end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 95b72648cf5..871917a725e 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -35,6 +35,7 @@ FactoryBot.define do end metrics_dashboard_access_level { ProjectFeature::PRIVATE } operations_access_level { ProjectFeature::ENABLED } + monitor_access_level { ProjectFeature::ENABLED } container_registry_access_level { ProjectFeature::ENABLED } security_and_compliance_access_level { ProjectFeature::PRIVATE } environments_access_level { ProjectFeature::ENABLED } diff --git a/spec/features/monitor_sidebar_link_spec.rb b/spec/features/monitor_sidebar_link_spec.rb index b888e2f4171..f612956600f 100644 --- a/spec/features/monitor_sidebar_link_spec.rb +++ b/spec/features/monitor_sidebar_link_spec.rb @@ -4,39 +4,59 @@ require 'spec_helper' RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures do let_it_be_with_reload(:project) { create(:project, :internal, :repository) } + let_it_be(:user) { create(:user) } - let(:user) { create(:user) } - let(:access_level) { ProjectFeature::PUBLIC } let(:role) { nil } before do project.add_role(user, role) if role - project.project_feature.update_attribute(:operations_access_level, access_level) - sign_in(user) - visit project_issues_path(project) end shared_examples 'shows Monitor menu based on the access level' do - context 'when operations project feature is PRIVATE' do - let(:access_level) { ProjectFeature::PRIVATE } - - it 'shows the `Monitor` menu' do - expect(page).to have_selector('a.shortcuts-monitor', text: 'Monitor') - end + using RSpec::Parameterized::TableSyntax + + let(:enabled) { Featurable::PRIVATE } + let(:disabled) { Featurable::DISABLED } + + where(:flag_enabled, :operations_access_level, :monitor_level, :render) do + true | ref(:disabled) | ref(:enabled) | true + true | ref(:disabled) | ref(:disabled) | false + true | ref(:enabled) | ref(:enabled) | true + true | ref(:enabled) | ref(:disabled) | false + false | ref(:disabled) | ref(:enabled) | false + false | ref(:disabled) | ref(:disabled) | false + false | ref(:enabled) | ref(:enabled) | true + false | ref(:enabled) | ref(:disabled) | true end - context 'when operations project feature is DISABLED' do - let(:access_level) { ProjectFeature::DISABLED } + with_them do + it 'renders when expected to' do + stub_feature_flags(split_operations_visibility_permissions: flag_enabled) + project.project_feature.update_attribute(:operations_access_level, operations_access_level) + project.project_feature.update_attribute(:monitor_access_level, monitor_level) + + visit project_issues_path(project) - it 'does not show the `Monitor` menu' do - expect(page).not_to have_selector('a.shortcuts-monitor') + if render + expect(page).to have_selector('a.shortcuts-monitor', text: 'Monitor') + else + expect(page).not_to have_selector('a.shortcuts-monitor') + end end end end - context 'user is not a member' do + context 'when user is not a member' do + let(:access_level) { ProjectFeature::PUBLIC } + + before do + project.project_feature.update_attribute(:operations_access_level, access_level) + project.project_feature.update_attribute(:monitor_access_level, access_level) + end + it 'has the correct `Monitor` menu items', :aggregate_failures do + visit project_issues_path(project) expect(page).to have_selector('a.shortcuts-monitor', text: 'Monitor') expect(page).to have_link('Incidents', href: project_incidents_path(project)) expect(page).to have_link('Environments', href: project_environments_path(project)) @@ -48,27 +68,50 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures do expect(page).not_to have_link('Kubernetes', href: project_clusters_path(project)) end - context 'when operations project feature is PRIVATE' do - let(:access_level) { ProjectFeature::PRIVATE } + context 'with new monitor visiblity flag disabled' do + stub_feature_flags(split_operations_visibility_permissions: false) - it 'does not show the `Monitor` menu' do - expect(page).not_to have_selector('a.shortcuts-monitor') + context 'when operations project feature is PRIVATE' do + let(:access_level) { ProjectFeature::PRIVATE } + + it 'does not show the `Monitor` menu' do + expect(page).not_to have_selector('a.shortcuts-monitor') + end + end + + context 'when operations project feature is DISABLED' do + let(:access_level) { ProjectFeature::DISABLED } + + it 'does not show the `Operations` menu' do + expect(page).not_to have_selector('a.shortcuts-monitor') + end end end - context 'when operations project feature is DISABLED' do - let(:access_level) { ProjectFeature::DISABLED } + context 'with new monitor visiblity flag enabled' do + context 'when monitor project feature is PRIVATE' do + let(:access_level) { ProjectFeature::PRIVATE } + + it 'does not show the `Monitor` menu' do + expect(page).not_to have_selector('a.shortcuts-monitor') + end + end + + context 'when operations project feature is DISABLED' do + let(:access_level) { ProjectFeature::DISABLED } - it 'does not show the `Operations` menu' do - expect(page).not_to have_selector('a.shortcuts-monitor') + it 'does not show the `Operations` menu' do + expect(page).not_to have_selector('a.shortcuts-monitor') + end end end end - context 'user has guest role' do + context 'when user has guest role' do let(:role) { :guest } it 'has the correct `Monitor` menu items' do + visit project_issues_path(project) expect(page).to have_selector('a.shortcuts-monitor', text: 'Monitor') expect(page).to have_link('Incidents', href: project_incidents_path(project)) expect(page).to have_link('Environments', href: project_environments_path(project)) @@ -83,10 +126,11 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures do it_behaves_like 'shows Monitor menu based on the access level' end - context 'user has reporter role' do + context 'when user has reporter role' do let(:role) { :reporter } it 'has the correct `Monitor` menu items' do + visit project_issues_path(project) expect(page).to have_link('Metrics', href: project_metrics_dashboard_path(project)) expect(page).to have_link('Incidents', href: project_incidents_path(project)) expect(page).to have_link('Environments', href: project_environments_path(project)) @@ -100,10 +144,11 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures do it_behaves_like 'shows Monitor menu based on the access level' end - context 'user has developer role' do + context 'when user has developer role' do let(:role) { :developer } it 'has the correct `Monitor` menu items' do + visit project_issues_path(project) expect(page).to have_link('Metrics', href: project_metrics_dashboard_path(project)) expect(page).to have_link('Alerts', href: project_alert_management_index_path(project)) expect(page).to have_link('Incidents', href: project_incidents_path(project)) @@ -116,10 +161,11 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures do it_behaves_like 'shows Monitor menu based on the access level' end - context 'user has maintainer role' do + context 'when user has maintainer role' do let(:role) { :maintainer } it 'has the correct `Monitor` menu items' do + visit project_issues_path(project) expect(page).to have_link('Metrics', href: project_metrics_dashboard_path(project)) expect(page).to have_link('Alerts', href: project_alert_management_index_path(project)) expect(page).to have_link('Incidents', href: project_incidents_path(project)) diff --git a/spec/frontend/groups/components/group_item_spec.js b/spec/frontend/groups/components/group_item_spec.js index 9906f62878f..fcf305d60e6 100644 --- a/spec/frontend/groups/components/group_item_spec.js +++ b/spec/frontend/groups/components/group_item_spec.js @@ -8,9 +8,9 @@ import { getGroupItemMicrodata } from '~/groups/store/utils'; import * as urlUtilities from '~/lib/utils/url_utility'; import { ITEM_TYPE } from '~/groups/constants'; import { - VISIBILITY_LEVEL_PRIVATE, - VISIBILITY_LEVEL_INTERNAL, - VISIBILITY_LEVEL_PUBLIC, + VISIBILITY_LEVEL_PRIVATE_STRING, + VISIBILITY_LEVEL_INTERNAL_STRING, + VISIBILITY_LEVEL_PUBLIC_STRING, } from '~/visibility_level/constants'; import { helpPagePath } from '~/helpers/help_page_helper'; import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper'; @@ -19,7 +19,7 @@ import { mockParentGroupItem, mockChildren } from '../mock_data'; const createComponent = ( propsData = { group: mockParentGroupItem, parentGroup: mockChildren[0] }, provide = { - currentGroupVisibility: VISIBILITY_LEVEL_PRIVATE, + currentGroupVisibility: VISIBILITY_LEVEL_PRIVATE_STRING, }, ) => { return mountExtended(GroupItem, { @@ -320,16 +320,16 @@ describe('GroupItemComponent', () => { describe('when showing projects', () => { describe.each` - itemVisibility | currentGroupVisibility | isPopoverShown - ${VISIBILITY_LEVEL_PRIVATE} | ${VISIBILITY_LEVEL_PUBLIC} | ${false} - ${VISIBILITY_LEVEL_INTERNAL} | ${VISIBILITY_LEVEL_PUBLIC} | ${false} - ${VISIBILITY_LEVEL_PUBLIC} | ${VISIBILITY_LEVEL_PUBLIC} | ${false} - ${VISIBILITY_LEVEL_PRIVATE} | ${VISIBILITY_LEVEL_PRIVATE} | ${false} - ${VISIBILITY_LEVEL_INTERNAL} | ${VISIBILITY_LEVEL_PRIVATE} | ${true} - ${VISIBILITY_LEVEL_PUBLIC} | ${VISIBILITY_LEVEL_PRIVATE} | ${true} - ${VISIBILITY_LEVEL_PRIVATE} | ${VISIBILITY_LEVEL_INTERNAL} | ${false} - ${VISIBILITY_LEVEL_INTERNAL} | ${VISIBILITY_LEVEL_INTERNAL} | ${false} - ${VISIBILITY_LEVEL_PUBLIC} | ${VISIBILITY_LEVEL_INTERNAL} | ${true} + itemVisibility | currentGroupVisibility | isPopoverShown + ${VISIBILITY_LEVEL_PRIVATE_STRING} | ${VISIBILITY_LEVEL_PUBLIC_STRING} | ${false} + ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${VISIBILITY_LEVEL_PUBLIC_STRING} | ${false} + ${VISIBILITY_LEVEL_PUBLIC_STRING} | ${VISIBILITY_LEVEL_PUBLIC_STRING} | ${false} + ${VISIBILITY_LEVEL_PRIVATE_STRING} | ${VISIBILITY_LEVEL_PRIVATE_STRING} | ${false} + ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${VISIBILITY_LEVEL_PRIVATE_STRING} | ${true} + ${VISIBILITY_LEVEL_PUBLIC_STRING} | ${VISIBILITY_LEVEL_PRIVATE_STRING} | ${true} + ${VISIBILITY_LEVEL_PRIVATE_STRING} | ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${false} + ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${false} + ${VISIBILITY_LEVEL_PUBLIC_STRING} | ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${true} `( 'when item visibility is $itemVisibility and parent group visibility is $currentGroupVisibility', ({ itemVisibility, currentGroupVisibility, isPopoverShown }) => { @@ -374,7 +374,7 @@ describe('GroupItemComponent', () => { wrapper = createComponent({ group: { ...mockParentGroupItem, - visibility: VISIBILITY_LEVEL_PUBLIC, + visibility: VISIBILITY_LEVEL_PUBLIC_STRING, type: ITEM_TYPE.PROJECT, }, parentGroup: mockChildren[0], diff --git a/spec/frontend/groups/components/groups_spec.js b/spec/frontend/groups/components/groups_spec.js index 6c1eb373b7e..866868eff36 100644 --- a/spec/frontend/groups/components/groups_spec.js +++ b/spec/frontend/groups/components/groups_spec.js @@ -6,7 +6,7 @@ import GroupItemComponent from '~/groups/components/group_item.vue'; import PaginationLinks from '~/vue_shared/components/pagination_links.vue'; import GroupsComponent from '~/groups/components/groups.vue'; import eventHub from '~/groups/event_hub'; -import { VISIBILITY_LEVEL_PRIVATE } from '~/visibility_level/constants'; +import { VISIBILITY_LEVEL_PRIVATE_STRING } from '~/visibility_level/constants'; import { mockGroups, mockPageInfo } from '../mock_data'; describe('GroupsComponent', () => { @@ -26,7 +26,7 @@ describe('GroupsComponent', () => { ...propsData, }, provide: { - currentGroupVisibility: VISIBILITY_LEVEL_PRIVATE, + currentGroupVisibility: VISIBILITY_LEVEL_PRIVATE_STRING, }, }); }; diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_row_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_row_spec.js index 50210fd5943..a2e5cbdce8b 100644 --- a/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_row_spec.js +++ b/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_row_spec.js @@ -90,7 +90,7 @@ describe('Harbor artifact list row', () => { }); it('has correct digest', () => { - expect(findByTestId('digest').text()).toBe('Digest: 5d98daa'); + expect(findByTestId('digest').text()).toBe('Digest: mock_sh'); }); describe('time', () => { it('has the correct push time', () => { diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_header_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_header_spec.js new file mode 100644 index 00000000000..5e299a269e3 --- /dev/null +++ b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_header_spec.js @@ -0,0 +1,52 @@ +import { nextTick } from 'vue'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import TagsHeader from '~/packages_and_registries/harbor_registry/components/tags/tags_header.vue'; +import TitleArea from '~/vue_shared/components/registry/title_area.vue'; +import { mockArtifactDetail, MOCK_SHA_DIGEST } from '../../mock_data'; + +describe('Harbor Tags Header', () => { + let wrapper; + + const findTitle = () => wrapper.findByTestId('title'); + const findTagsCount = () => wrapper.findByTestId('tags-count'); + + const mountComponent = ({ propsData }) => { + wrapper = shallowMountExtended(TagsHeader, { + propsData, + stubs: { + TitleArea, + }, + }); + }; + + const mockPageInfo = { + page: 1, + perPage: 20, + total: 1, + totalPages: 1, + }; + + afterEach(() => { + wrapper.destroy(); + }); + + beforeEach(() => { + mountComponent({ + propsData: { artifactDetail: mockArtifactDetail, pageInfo: mockPageInfo, tagsLoading: false }, + }); + }); + + describe('tags title', () => { + it('should be artifact digest', () => { + expect(findTitle().text()).toBe(`sha256:${MOCK_SHA_DIGEST}`); + }); + }); + + describe('tags count', () => { + it('would has the correct text', async () => { + await nextTick(); + + expect(findTagsCount().props('text')).toBe('1 tag'); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_row_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_row_spec.js new file mode 100644 index 00000000000..6fe3dabc603 --- /dev/null +++ b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_row_spec.js @@ -0,0 +1,75 @@ +import { GlSprintf } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ListItem from '~/vue_shared/components/registry/list_item.vue'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import TagsListRow from '~/packages_and_registries/harbor_registry/components/tags/tags_list_row.vue'; +import { defaultConfig, harborTagsList } from '../../mock_data'; + +describe('Harbor tag list row', () => { + let wrapper; + + const findListItem = () => wrapper.find(ListItem); + const findClipboardButton = () => wrapper.find(ClipboardButton); + const findByTestId = (testId) => wrapper.findByTestId(testId); + + const $route = { + params: { + project: defaultConfig.harborIntegrationProjectName, + image: 'test-repository', + }, + }; + + const mountComponent = ({ propsData, config = defaultConfig }) => { + wrapper = shallowMountExtended(TagsListRow, { + stubs: { + ListItem, + GlSprintf, + }, + propsData, + mocks: { + $route, + }, + provide() { + return { + ...config, + }; + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('list item', () => { + beforeEach(() => { + mountComponent({ + propsData: { + tag: harborTagsList[0], + }, + }); + }); + + it('exists', () => { + expect(findListItem().exists()).toBe(true); + }); + + it('has the correct tag name', () => { + expect(findByTestId('name').text()).toBe(harborTagsList[0].name); + }); + + describe(' clipboard button', () => { + it('exists', () => { + expect(findClipboardButton().exists()).toBe(true); + }); + + it('has the correct props', () => { + const pullCommand = `docker pull demo.harbor.com/test-project/test-repository:${harborTagsList[0].name}`; + expect(findClipboardButton().attributes()).toMatchObject({ + text: pullCommand, + title: pullCommand, + }); + }); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_spec.js new file mode 100644 index 00000000000..6bcf6611d07 --- /dev/null +++ b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_spec.js @@ -0,0 +1,66 @@ +import { shallowMount } from '@vue/test-utils'; +import TagsList from '~/packages_and_registries/harbor_registry/components/tags/tags_list.vue'; +import TagsLoader from '~/packages_and_registries/shared/components/tags_loader.vue'; +import TagsListRow from '~/packages_and_registries/harbor_registry/components/tags/tags_list_row.vue'; +import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue'; +import { defaultConfig, harborTagsResponse } from '../../mock_data'; + +describe('Harbor Tags List', () => { + let wrapper; + + const findTagsLoader = () => wrapper.find(TagsLoader); + const findTagsListRows = () => wrapper.findAllComponents(TagsListRow); + const findRegistryList = () => wrapper.find(RegistryList); + + const mountComponent = ({ propsData, config = defaultConfig }) => { + wrapper = shallowMount(TagsList, { + propsData, + stubs: { RegistryList }, + provide() { + return { + ...config, + }; + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('when isLoading is true', () => { + beforeEach(() => { + mountComponent({ + propsData: { + isLoading: true, + pageInfo: {}, + tags: [], + }, + }); + }); + + it('show the loader', () => { + expect(findTagsLoader().exists()).toBe(true); + }); + }); + + describe('tags list', () => { + beforeEach(() => { + mountComponent({ + propsData: { + isLoading: false, + pageInfo: {}, + tags: harborTagsResponse, + }, + }); + }); + + it('should render correctly', () => { + expect(findRegistryList().exists()).toBe(true); + }); + + it('one tag row exists', () => { + expect(findTagsListRows()).toHaveLength(harborTagsResponse.length); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/harbor_registry/mock_data.js b/spec/frontend/packages_and_registries/harbor_registry/mock_data.js index 0ba274ea176..b8989b6092e 100644 --- a/spec/frontend/packages_and_registries/harbor_registry/mock_data.js +++ b/spec/frontend/packages_and_registries/harbor_registry/mock_data.js @@ -2,6 +2,8 @@ export const harborImageDetailEmptyResponse = { data: null, }; +export const MOCK_SHA_DIGEST = 'mock_sha_digest_value'; + export const harborImageDetailResponse = { artifactCount: 10, creationTime: '2022-03-02T06:35:53.205Z', @@ -16,7 +18,7 @@ export const harborImageDetailResponse = { export const harborArtifactsResponse = [ { id: 1, - digest: 'sha256:5d98daa36cdc8d6c7ed6579ce17230f0f9fd893a9012fc069cb7d714c0e3df35', + digest: `sha256:${MOCK_SHA_DIGEST}`, size: 773928, push_time: '2022-05-19T15:54:47.821Z', tags: ['latest'], @@ -26,7 +28,7 @@ export const harborArtifactsResponse = [ export const harborArtifactsList = [ { id: 1, - digest: 'sha256:5d98daa36cdc8d6c7ed6579ce17230f0f9fd893a9012fc069cb7d714c0e3df35', + digest: `sha256:${MOCK_SHA_DIGEST}`, size: 773928, pushTime: '2022-05-19T15:54:47.821Z', tags: ['latest'], @@ -104,3 +106,9 @@ export const dockerCommands = { dockerPushCommand: 'barbar', dockerLoginCommand: 'bazbaz', }; + +export const mockArtifactDetail = { + project: 'test-project', + image: 'test-repository', + digest: `sha256:${MOCK_SHA_DIGEST}`, +}; diff --git a/spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js b/spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js new file mode 100644 index 00000000000..7e0f05e736b --- /dev/null +++ b/spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js @@ -0,0 +1,125 @@ +import { nextTick } from 'vue'; +import { shallowMount } from '@vue/test-utils'; +import HarborTagsPage from '~/packages_and_registries/harbor_registry/pages/harbor_tags.vue'; +import TagsHeader from '~/packages_and_registries/harbor_registry/components/tags/tags_header.vue'; +import TagsList from '~/packages_and_registries/harbor_registry/components/tags/tags_list.vue'; +import waitForPromises from 'helpers/wait_for_promises'; +import { defaultConfig, harborTagsResponse, mockArtifactDetail } from '../mock_data'; + +let mockHarborTagsResponse; + +jest.mock('~/rest_api', () => ({ + getHarborTags: () => mockHarborTagsResponse, +})); + +describe('Harbor Tags page', () => { + let wrapper; + + const findTagsHeader = () => wrapper.find(TagsHeader); + const findTagsList = () => wrapper.find(TagsList); + + const waitForHarborTagsRequest = async () => { + await waitForPromises(); + await nextTick(); + }; + + const breadCrumbState = { + updateName: jest.fn(), + updateHref: jest.fn(), + }; + + const $route = { + params: mockArtifactDetail, + }; + + const defaultHeaders = { + 'x-page': '1', + 'X-Per-Page': '20', + 'X-TOTAL': '1', + 'X-Total-Pages': '1', + }; + + const mountComponent = ({ endpoint = defaultConfig.endpoint } = {}) => { + wrapper = shallowMount(HarborTagsPage, { + mocks: { + $route, + }, + provide() { + return { + breadCrumbState, + endpoint, + }; + }, + }); + }; + + beforeEach(() => { + mockHarborTagsResponse = Promise.resolve({ + data: harborTagsResponse, + headers: defaultHeaders, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('contains tags header', () => { + mountComponent(); + + expect(findTagsHeader().exists()).toBe(true); + }); + + it('contains tags list', () => { + mountComponent(); + + expect(findTagsList().exists()).toBe(true); + }); + + describe('header', () => { + it('has the correct props', async () => { + mountComponent(); + + await waitForHarborTagsRequest(); + expect(findTagsHeader().props()).toMatchObject({ + artifactDetail: mockArtifactDetail, + pageInfo: { + page: 1, + perPage: 20, + total: 1, + totalPages: 1, + }, + tagsLoading: false, + }); + }); + }); + + describe('list', () => { + it('has the correct props', async () => { + mountComponent(); + + await waitForHarborTagsRequest(); + expect(findTagsList().props()).toMatchObject({ + tags: [ + { + repositoryId: 4, + artifactId: 5, + id: 4, + name: 'latest', + pullTime: '0001-01-01T00:00:00.000Z', + pushTime: '2022-05-27T18:21:27.903Z', + signed: false, + immutable: false, + }, + ], + isLoading: false, + pageInfo: { + page: 1, + perPage: 20, + total: 1, + totalPages: 1, + }, + }); + }); + }); +}); diff --git a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js index b34c64cb1b0..ed7d4ad269e 100644 --- a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js +++ b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js @@ -5,8 +5,12 @@ import settingsPanel from '~/pages/projects/shared/permissions/components/settin import { featureAccessLevel, visibilityLevelDescriptions, - visibilityOptions, } from '~/pages/projects/shared/permissions/constants'; +import { + VISIBILITY_LEVEL_PRIVATE_INTEGER, + VISIBILITY_LEVEL_INTERNAL_INTEGER, + VISIBILITY_LEVEL_PUBLIC_INTEGER, +} from '~/visibility_level/constants'; import ConfirmDanger from '~/vue_shared/components/confirm_danger/confirm_danger.vue'; const defaultProps = { @@ -134,6 +138,7 @@ describe('Settings Panel', () => { const findEnvironmentsSettings = () => wrapper.findComponent({ ref: 'environments-settings' }); const findFeatureFlagsSettings = () => wrapper.findComponent({ ref: 'feature-flags-settings' }); const findReleasesSettings = () => wrapper.findComponent({ ref: 'environments-settings' }); + const findMonitorSettings = () => wrapper.findComponent({ ref: 'monitor-settings' }); afterEach(() => { wrapper.destroy(); @@ -162,13 +167,13 @@ describe('Settings Panel', () => { }); it.each` - option | allowedOptions | disabled - ${visibilityOptions.PRIVATE} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${false} - ${visibilityOptions.PRIVATE} | ${[visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${true} - ${visibilityOptions.INTERNAL} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${false} - ${visibilityOptions.INTERNAL} | ${[visibilityOptions.PRIVATE, visibilityOptions.PUBLIC]} | ${true} - ${visibilityOptions.PUBLIC} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${false} - ${visibilityOptions.PUBLIC} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL]} | ${true} + option | allowedOptions | disabled + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${[VISIBILITY_LEVEL_PRIVATE_INTEGER, VISIBILITY_LEVEL_INTERNAL_INTEGER, VISIBILITY_LEVEL_PUBLIC_INTEGER]} | ${false} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${[VISIBILITY_LEVEL_INTERNAL_INTEGER, VISIBILITY_LEVEL_PUBLIC_INTEGER]} | ${true} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${[VISIBILITY_LEVEL_PRIVATE_INTEGER, VISIBILITY_LEVEL_INTERNAL_INTEGER, VISIBILITY_LEVEL_PUBLIC_INTEGER]} | ${false} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${[VISIBILITY_LEVEL_PRIVATE_INTEGER, VISIBILITY_LEVEL_PUBLIC_INTEGER]} | ${true} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${[VISIBILITY_LEVEL_PRIVATE_INTEGER, VISIBILITY_LEVEL_INTERNAL_INTEGER, VISIBILITY_LEVEL_PUBLIC_INTEGER]} | ${false} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${[VISIBILITY_LEVEL_PRIVATE_INTEGER, VISIBILITY_LEVEL_INTERNAL_INTEGER]} | ${true} `( 'sets disabled to $disabled for the visibility option $option when given $allowedOptions', ({ option, allowedOptions, disabled }) => { @@ -187,35 +192,37 @@ describe('Settings Panel', () => { it('should set the visibility level description based upon the selected visibility level', () => { wrapper = mountComponent({ stubs: { GlSprintf } }); - findProjectVisibilityLevelInput().setValue(visibilityOptions.INTERNAL); + findProjectVisibilityLevelInput().setValue(VISIBILITY_LEVEL_INTERNAL_INTEGER); expect(findProjectVisibilitySettings().text()).toContain( - visibilityLevelDescriptions[visibilityOptions.INTERNAL], + visibilityLevelDescriptions[VISIBILITY_LEVEL_INTERNAL_INTEGER], ); }); it('should show the request access checkbox if the visibility level is not private', () => { wrapper = mountComponent({ - currentSettings: { visibilityLevel: visibilityOptions.INTERNAL }, + currentSettings: { visibilityLevel: VISIBILITY_LEVEL_INTERNAL_INTEGER }, }); expect(findRequestAccessEnabledInput().exists()).toBe(true); }); it('should not show the request access checkbox if the visibility level is private', () => { - wrapper = mountComponent({ currentSettings: { visibilityLevel: visibilityOptions.PRIVATE } }); + wrapper = mountComponent({ + currentSettings: { visibilityLevel: VISIBILITY_LEVEL_PRIVATE_INTEGER }, + }); expect(findRequestAccessEnabledInput().exists()).toBe(false); }); it('does not require confirmation if the visibility is reduced', async () => { wrapper = mountComponent({ - currentSettings: { visibilityLevel: visibilityOptions.INTERNAL }, + currentSettings: { visibilityLevel: VISIBILITY_LEVEL_INTERNAL_INTEGER }, }); expect(findConfirmDangerButton().exists()).toBe(false); - await findProjectVisibilityLevelInput().setValue(visibilityOptions.PRIVATE); + await findProjectVisibilityLevelInput().setValue(VISIBILITY_LEVEL_PRIVATE_INTEGER); expect(findConfirmDangerButton().exists()).toBe(false); }); @@ -223,7 +230,7 @@ describe('Settings Panel', () => { describe('showVisibilityConfirmModal=true', () => { beforeEach(() => { wrapper = mountComponent({ - currentSettings: { visibilityLevel: visibilityOptions.INTERNAL }, + currentSettings: { visibilityLevel: VISIBILITY_LEVEL_INTERNAL_INTEGER }, showVisibilityConfirmModal: true, }); }); @@ -231,7 +238,7 @@ describe('Settings Panel', () => { it('will render the confirmation dialog if the visibility is reduced', async () => { expect(findConfirmDangerButton().exists()).toBe(false); - await findProjectVisibilityLevelInput().setValue(visibilityOptions.PRIVATE); + await findProjectVisibilityLevelInput().setValue(VISIBILITY_LEVEL_PRIVATE_INTEGER); expect(findConfirmDangerButton().exists()).toBe(true); }); @@ -239,7 +246,7 @@ describe('Settings Panel', () => { it('emits the `confirm` event when the reduce visibility warning is confirmed', async () => { expect(wrapper.emitted('confirm')).toBeUndefined(); - await findProjectVisibilityLevelInput().setValue(visibilityOptions.PRIVATE); + await findProjectVisibilityLevelInput().setValue(VISIBILITY_LEVEL_PRIVATE_INTEGER); await findConfirmDangerButton().vm.$emit('confirm'); expect(wrapper.emitted('confirm')).toHaveLength(1); @@ -259,7 +266,9 @@ describe('Settings Panel', () => { describe('Repository', () => { it('should set the repository help text when the visibility level is set to private', () => { - wrapper = mountComponent({ currentSettings: { visibilityLevel: visibilityOptions.PRIVATE } }); + wrapper = mountComponent({ + currentSettings: { visibilityLevel: VISIBILITY_LEVEL_PRIVATE_INTEGER }, + }); expect(findRepositoryFeatureProjectRow().props('helpText')).toBe( 'View and edit files in this project.', @@ -267,7 +276,9 @@ describe('Settings Panel', () => { }); it('should set the repository help text with a read access warning when the visibility level is set to non-private', () => { - wrapper = mountComponent({ currentSettings: { visibilityLevel: visibilityOptions.PUBLIC } }); + wrapper = mountComponent({ + currentSettings: { visibilityLevel: VISIBILITY_LEVEL_PUBLIC_INTEGER }, + }); expect(findRepositoryFeatureProjectRow().props('helpText')).toBe( 'View and edit files in this project. Non-project members have only read access.', @@ -351,7 +362,7 @@ describe('Settings Panel', () => { it('should show the container registry public note if the visibility level is public and the registry is available', () => { wrapper = mountComponent({ currentSettings: { - visibilityLevel: visibilityOptions.PUBLIC, + visibilityLevel: VISIBILITY_LEVEL_PUBLIC_INTEGER, containerRegistryAccessLevel: featureAccessLevel.EVERYONE, }, registryAvailable: true, @@ -366,7 +377,7 @@ describe('Settings Panel', () => { it('should hide the container registry public note if the visibility level is public but the registry is private', () => { wrapper = mountComponent({ currentSettings: { - visibilityLevel: visibilityOptions.PUBLIC, + visibilityLevel: VISIBILITY_LEVEL_PUBLIC_INTEGER, containerRegistryAccessLevel: featureAccessLevel.PROJECT_MEMBERS, }, registryAvailable: true, @@ -377,7 +388,7 @@ describe('Settings Panel', () => { it('should hide the container registry public note if the visibility level is private and the registry is available', () => { wrapper = mountComponent({ - currentSettings: { visibilityLevel: visibilityOptions.PRIVATE }, + currentSettings: { visibilityLevel: VISIBILITY_LEVEL_PRIVATE_INTEGER }, registryAvailable: true, }); @@ -386,7 +397,7 @@ describe('Settings Panel', () => { it('has label for the toggle', () => { wrapper = mountComponent({ - currentSettings: { visibilityLevel: visibilityOptions.PUBLIC }, + currentSettings: { visibilityLevel: VISIBILITY_LEVEL_PUBLIC_INTEGER }, registryAvailable: true, }); @@ -575,10 +586,10 @@ describe('Settings Panel', () => { }); it.each` - visibilityLevel | output - ${visibilityOptions.PRIVATE} | ${[[featureAccessLevel.PROJECT_MEMBERS, 'Only Project Members'], [30, 'Everyone']]} - ${visibilityOptions.INTERNAL} | ${[[featureAccessLevel.EVERYONE, 'Everyone With Access'], [30, 'Everyone']]} - ${visibilityOptions.PUBLIC} | ${[[30, 'Everyone']]} + visibilityLevel | output + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${[[featureAccessLevel.PROJECT_MEMBERS, 'Only Project Members'], [30, 'Everyone']]} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${[[featureAccessLevel.EVERYONE, 'Everyone With Access'], [30, 'Everyone']]} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${[[30, 'Everyone']]} `( 'renders correct options when visibilityLevel is $visibilityLevel', async ({ visibilityLevel, output }) => { @@ -595,23 +606,23 @@ describe('Settings Panel', () => { ); it.each` - initialProjectVisibilityLevel | newProjectVisibilityLevel | initialPackageRegistryOption | expectedPackageRegistryOption - ${visibilityOptions.PRIVATE} | ${visibilityOptions.INTERNAL} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${visibilityOptions.PRIVATE} | ${visibilityOptions.INTERNAL} | ${featureAccessLevel.PROJECT_MEMBERS} | ${featureAccessLevel.EVERYONE} - ${visibilityOptions.PRIVATE} | ${visibilityOptions.INTERNAL} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${visibilityOptions.PRIVATE} | ${visibilityOptions.PUBLIC} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${visibilityOptions.PRIVATE} | ${visibilityOptions.PUBLIC} | ${featureAccessLevel.PROJECT_MEMBERS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${visibilityOptions.PRIVATE} | ${visibilityOptions.PUBLIC} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${visibilityOptions.INTERNAL} | ${visibilityOptions.PRIVATE} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${visibilityOptions.INTERNAL} | ${visibilityOptions.PRIVATE} | ${featureAccessLevel.EVERYONE} | ${featureAccessLevel.PROJECT_MEMBERS} - ${visibilityOptions.INTERNAL} | ${visibilityOptions.PRIVATE} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${visibilityOptions.INTERNAL} | ${visibilityOptions.PUBLIC} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${visibilityOptions.INTERNAL} | ${visibilityOptions.PUBLIC} | ${featureAccessLevel.EVERYONE} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${visibilityOptions.INTERNAL} | ${visibilityOptions.PUBLIC} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${visibilityOptions.PUBLIC} | ${visibilityOptions.PRIVATE} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${visibilityOptions.PUBLIC} | ${visibilityOptions.PRIVATE} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.PROJECT_MEMBERS} - ${visibilityOptions.PUBLIC} | ${visibilityOptions.INTERNAL} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${visibilityOptions.PUBLIC} | ${visibilityOptions.INTERNAL} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.EVERYONE} + initialProjectVisibilityLevel | newProjectVisibilityLevel | initialPackageRegistryOption | expectedPackageRegistryOption + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.PROJECT_MEMBERS} | ${featureAccessLevel.EVERYONE} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.PROJECT_MEMBERS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.EVERYONE} | ${featureAccessLevel.PROJECT_MEMBERS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.EVERYONE} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.PROJECT_MEMBERS} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.EVERYONE} `( 'changes option from $initialPackageRegistryOption to $expectedPackageRegistryOption when visibilityLevel changed from $initialProjectVisibilityLevel to $newProjectVisibilityLevel', async ({ @@ -641,13 +652,13 @@ describe('Settings Panel', () => { describe('Pages', () => { it.each` - visibilityLevel | pagesAccessControlForced | output - ${visibilityOptions.PRIVATE} | ${true} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access']]} - ${visibilityOptions.PRIVATE} | ${false} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access'], [30, 'Everyone']]} - ${visibilityOptions.INTERNAL} | ${true} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access']]} - ${visibilityOptions.INTERNAL} | ${false} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access'], [30, 'Everyone']]} - ${visibilityOptions.PUBLIC} | ${true} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access']]} - ${visibilityOptions.PUBLIC} | ${false} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access'], [30, 'Everyone']]} + visibilityLevel | pagesAccessControlForced | output + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${true} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access']]} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${false} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access'], [30, 'Everyone']]} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${true} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access']]} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${false} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access'], [30, 'Everyone']]} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${true} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access']]} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${false} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access'], [30, 'Everyone']]} `( 'renders correct options when pagesAccessControlForced is $pagesAccessControlForced and visibilityLevel is $visibilityLevel', async ({ visibilityLevel, pagesAccessControlForced, output }) => { @@ -766,13 +777,13 @@ describe('Settings Panel', () => { it('should reduce Metrics visibility level when visibility is set to private', async () => { wrapper = mountComponent({ currentSettings: { - visibilityLevel: visibilityOptions.PUBLIC, + visibilityLevel: VISIBILITY_LEVEL_PUBLIC_INTEGER, operationsAccessLevel: featureAccessLevel.EVERYONE, metricsDashboardAccessLevel: featureAccessLevel.EVERYONE, }, }); - await findProjectVisibilityLevelInput().setValue(visibilityOptions.PRIVATE); + await findProjectVisibilityLevelInput().setValue(VISIBILITY_LEVEL_PRIVATE_INTEGER); expect(findMetricsVisibilityInput().props('value')).toBe(featureAccessLevel.PROJECT_MEMBERS); }); @@ -830,7 +841,6 @@ describe('Settings Panel', () => { }); }); }); - describe('Releases', () => { describe('with feature flag', () => { it('should show the releases toggle', () => { @@ -849,4 +859,42 @@ describe('Settings Panel', () => { }); }); }); + describe('Monitor', () => { + const expectedAccessLevel = [ + [10, 'Only Project Members'], + [20, 'Everyone With Access'], + ]; + describe('with feature flag', () => { + it('shows Monitor toggle instead of Operations toggle', () => { + wrapper = mountComponent({ + glFeatures: { splitOperationsVisibilityPermissions: true }, + }); + + expect(findMonitorSettings().exists()).toBe(true); + expect(findOperationsSettings().exists()).toBe(false); + expect(findMonitorSettings().findComponent(ProjectFeatureSetting).props('options')).toEqual( + expectedAccessLevel, + ); + }); + it('when monitorAccessLevel is for project members, it is also for everyone', () => { + wrapper = mountComponent({ + glFeatures: { splitOperationsVisibilityPermissions: true }, + currentSettings: { monitorAccessLevel: featureAccessLevel.PROJECT_MEMBERS }, + }); + + expect(findMetricsVisibilityInput().props('value')).toBe(featureAccessLevel.EVERYONE); + }); + }); + describe('without feature flag', () => { + it('shows Operations toggle instead of Monitor toggle', () => { + wrapper = mountComponent({}); + + expect(findMonitorSettings().exists()).toBe(false); + expect(findOperationsSettings().exists()).toBe(true); + expect( + findOperationsSettings().findComponent(ProjectFeatureSetting).props('options'), + ).toEqual(expectedAccessLevel); + }); + }); + }); }); diff --git a/spec/frontend/snippets/components/edit_spec.js b/spec/frontend/snippets/components/edit_spec.js index f49ceb2fede..cf897414ccb 100644 --- a/spec/frontend/snippets/components/edit_spec.js +++ b/spec/frontend/snippets/components/edit_spec.js @@ -16,10 +16,10 @@ import SnippetBlobActionsEdit from '~/snippets/components/snippet_blob_actions_e import SnippetDescriptionEdit from '~/snippets/components/snippet_description_edit.vue'; import SnippetVisibilityEdit from '~/snippets/components/snippet_visibility_edit.vue'; import { - SNIPPET_VISIBILITY_PRIVATE, - SNIPPET_VISIBILITY_INTERNAL, - SNIPPET_VISIBILITY_PUBLIC, -} from '~/snippets/constants'; + VISIBILITY_LEVEL_PRIVATE_STRING, + VISIBILITY_LEVEL_INTERNAL_STRING, + VISIBILITY_LEVEL_PUBLIC_STRING, +} from '~/visibility_level/constants'; import CreateSnippetMutation from '~/snippets/mutations/create_snippet.mutation.graphql'; import UpdateSnippetMutation from '~/snippets/mutations/update_snippet.mutation.graphql'; import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue'; @@ -41,7 +41,7 @@ const TEST_SNIPPET_GID = 'gid://gitlab/PersonalSnippet/42'; const createSnippet = () => merge(createGQLSnippet(), { webUrl: TEST_WEB_URL, - visibilityLevel: SNIPPET_VISIBILITY_PRIVATE, + visibilityLevel: VISIBILITY_LEVEL_PRIVATE_STRING, }); const createQueryResponse = (obj = {}) => @@ -70,7 +70,7 @@ const getApiData = ({ id, title = '', description = '', - visibilityLevel = SNIPPET_VISIBILITY_PRIVATE, + visibilityLevel = VISIBILITY_LEVEL_PRIVATE_STRING, } = {}) => ({ id, title, @@ -128,7 +128,10 @@ describe('Snippet Edit app', () => { const setDescription = (val) => wrapper.findComponent(SnippetDescriptionEdit).vm.$emit('input', val); - const createComponent = ({ props = {}, selectedLevel = SNIPPET_VISIBILITY_PRIVATE } = {}) => { + const createComponent = ({ + props = {}, + selectedLevel = VISIBILITY_LEVEL_PRIVATE_STRING, + } = {}) => { if (wrapper) { throw new Error('wrapper already created'); } @@ -260,17 +263,18 @@ describe('Snippet Edit app', () => { }, ); - it.each([SNIPPET_VISIBILITY_PRIVATE, SNIPPET_VISIBILITY_INTERNAL, SNIPPET_VISIBILITY_PUBLIC])( - 'marks %s visibility by default', - async (visibility) => { - createComponent({ - props: { snippetGid: '' }, - selectedLevel: visibility, - }); + it.each([ + VISIBILITY_LEVEL_PRIVATE_STRING, + VISIBILITY_LEVEL_INTERNAL_STRING, + VISIBILITY_LEVEL_PUBLIC_STRING, + ])('marks %s visibility by default', async (visibility) => { + createComponent({ + props: { snippetGid: '' }, + selectedLevel: visibility, + }); - expect(wrapper.find(SnippetVisibilityEdit).props('value')).toBe(visibility); - }, - ); + expect(wrapper.find(SnippetVisibilityEdit).props('value')).toBe(visibility); + }); describe('form submission handling', () => { describe('when creating a new snippet', () => { diff --git a/spec/frontend/snippets/components/show_spec.js b/spec/frontend/snippets/components/show_spec.js index d3b5c0e69db..032dcf8e5f5 100644 --- a/spec/frontend/snippets/components/show_spec.js +++ b/spec/frontend/snippets/components/show_spec.js @@ -7,10 +7,10 @@ import SnippetBlob from '~/snippets/components/snippet_blob_view.vue'; import SnippetHeader from '~/snippets/components/snippet_header.vue'; import SnippetTitle from '~/snippets/components/snippet_title.vue'; import { - SNIPPET_VISIBILITY_INTERNAL, - SNIPPET_VISIBILITY_PRIVATE, - SNIPPET_VISIBILITY_PUBLIC, -} from '~/snippets/constants'; + VISIBILITY_LEVEL_INTERNAL_STRING, + VISIBILITY_LEVEL_PRIVATE_STRING, + VISIBILITY_LEVEL_PUBLIC_STRING, +} from '~/visibility_level/constants'; import CloneDropdownButton from '~/vue_shared/components/clone_dropdown.vue'; import { stubPerformanceWebAPI } from 'helpers/performance'; @@ -69,7 +69,7 @@ describe('Snippet view app', () => { createComponent({ data: { snippet: { - visibilityLevel: SNIPPET_VISIBILITY_PUBLIC, + visibilityLevel: VISIBILITY_LEVEL_PUBLIC_STRING, webUrl: 'http://foo.bar', }, }, @@ -93,11 +93,11 @@ describe('Snippet view app', () => { describe('Embed dropdown rendering', () => { it.each` - visibilityLevel | condition | isRendered - ${SNIPPET_VISIBILITY_INTERNAL} | ${'not render'} | ${false} - ${SNIPPET_VISIBILITY_PRIVATE} | ${'not render'} | ${false} - ${'foo'} | ${'not render'} | ${false} - ${SNIPPET_VISIBILITY_PUBLIC} | ${'render'} | ${true} + visibilityLevel | condition | isRendered + ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${'not render'} | ${false} + ${VISIBILITY_LEVEL_PRIVATE_STRING} | ${'not render'} | ${false} + ${'foo'} | ${'not render'} | ${false} + ${VISIBILITY_LEVEL_PUBLIC_STRING} | ${'render'} | ${true} `('does $condition embed-dropdown by default', ({ visibilityLevel, isRendered }) => { createComponent({ data: { diff --git a/spec/frontend/snippets/components/snippet_blob_view_spec.js b/spec/frontend/snippets/components/snippet_blob_view_spec.js index c395112e313..aa31377f390 100644 --- a/spec/frontend/snippets/components/snippet_blob_view_spec.js +++ b/spec/frontend/snippets/components/snippet_blob_view_spec.js @@ -15,7 +15,7 @@ import { BLOB_RENDER_ERRORS, } from '~/blob/components/constants'; import SnippetBlobView from '~/snippets/components/snippet_blob_view.vue'; -import { SNIPPET_VISIBILITY_PUBLIC } from '~/snippets/constants'; +import { VISIBILITY_LEVEL_PUBLIC_STRING } from '~/visibility_level/constants'; import { RichViewer, SimpleViewer } from '~/vue_shared/components/blob_viewers'; describe('Blob Embeddable', () => { @@ -23,7 +23,7 @@ describe('Blob Embeddable', () => { const snippet = { id: 'gid://foo.bar/snippet', webUrl: 'https://foo.bar', - visibilityLevel: SNIPPET_VISIBILITY_PUBLIC, + visibilityLevel: VISIBILITY_LEVEL_PUBLIC_STRING, }; const dataMock = { activeViewerType: SimpleViewerMock.type, diff --git a/spec/frontend/snippets/components/snippet_visibility_edit_spec.js b/spec/frontend/snippets/components/snippet_visibility_edit_spec.js index a86ff566683..2d043a5caba 100644 --- a/spec/frontend/snippets/components/snippet_visibility_edit_spec.js +++ b/spec/frontend/snippets/components/snippet_visibility_edit_spec.js @@ -2,10 +2,12 @@ import { GlFormRadio, GlIcon, GlFormRadioGroup, GlLink } from '@gitlab/ui'; import { mount, shallowMount } from '@vue/test-utils'; import SnippetVisibilityEdit from '~/snippets/components/snippet_visibility_edit.vue'; import { + VISIBILITY_LEVEL_PRIVATE_STRING, + VISIBILITY_LEVEL_INTERNAL_STRING, + VISIBILITY_LEVEL_PUBLIC_STRING, +} from '~/visibility_level/constants'; +import { SNIPPET_VISIBILITY, - SNIPPET_VISIBILITY_PRIVATE, - SNIPPET_VISIBILITY_INTERNAL, - SNIPPET_VISIBILITY_PUBLIC, SNIPPET_LEVELS_RESTRICTED, SNIPPET_LEVELS_DISABLED, } from '~/snippets/constants'; @@ -75,19 +77,19 @@ describe('Snippet Visibility Edit component', () => { const findRestrictedInfo = () => wrapper.find('[data-testid="restricted-levels-info"]'); const RESULTING_OPTIONS = { 0: { - value: SNIPPET_VISIBILITY_PRIVATE, + value: VISIBILITY_LEVEL_PRIVATE_STRING, icon: SNIPPET_VISIBILITY.private.icon, text: SNIPPET_VISIBILITY.private.label, description: SNIPPET_VISIBILITY.private.description, }, 10: { - value: SNIPPET_VISIBILITY_INTERNAL, + value: VISIBILITY_LEVEL_INTERNAL_STRING, icon: SNIPPET_VISIBILITY.internal.icon, text: SNIPPET_VISIBILITY.internal.label, description: SNIPPET_VISIBILITY.internal.description, }, 20: { - value: SNIPPET_VISIBILITY_PUBLIC, + value: VISIBILITY_LEVEL_PUBLIC_STRING, icon: SNIPPET_VISIBILITY.public.icon, text: SNIPPET_VISIBILITY.public.label, description: SNIPPET_VISIBILITY.public.description, @@ -130,7 +132,7 @@ describe('Snippet Visibility Edit component', () => { createComponent({ propsData: { isProjectSnippet: true }, deep: true }); expect(findRadiosData()[0]).toEqual({ - value: SNIPPET_VISIBILITY_PRIVATE, + value: VISIBILITY_LEVEL_PRIVATE_STRING, icon: SNIPPET_VISIBILITY.private.icon, text: SNIPPET_VISIBILITY.private.label, description: SNIPPET_VISIBILITY.private.description_project, @@ -141,7 +143,7 @@ describe('Snippet Visibility Edit component', () => { describe('functionality', () => { it('pre-selects correct option in the list', () => { - const value = SNIPPET_VISIBILITY_INTERNAL; + const value = VISIBILITY_LEVEL_INTERNAL_STRING; createComponent({ propsData: { value } }); diff --git a/spec/lib/gitlab/import_export/config_spec.rb b/spec/lib/gitlab/import_export/config_spec.rb index fcb48678b88..8f848af8bd3 100644 --- a/spec/lib/gitlab/import_export/config_spec.rb +++ b/spec/lib/gitlab/import_export/config_spec.rb @@ -21,10 +21,12 @@ RSpec.describe Gitlab::ImportExport::Config do end it 'parses default config' do + expected_keys = [:tree, :excluded_attributes, :included_attributes, :methods, :preloads, :export_reorders] + expected_keys << :include_if_exportable if ee + expect { subject }.not_to raise_error expect(subject).to be_a(Hash) - expect(subject.keys).to contain_exactly( - :tree, :excluded_attributes, :included_attributes, :methods, :preloads, :export_reorders) + expect(subject.keys).to match_array(expected_keys) end end end diff --git a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb index b8d18718dfb..02ac8065c9f 100644 --- a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb +++ b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb @@ -32,18 +32,20 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer do let(:hash) { { name: exportable.name, description: exportable.description }.stringify_keys } let(:include) { [] } let(:custom_orderer) { nil } + let(:include_if_exportable) { {} } let(:relations_schema) do { only: [:name, :description], include: include, preload: { issues: nil }, - export_reorder: custom_orderer + export_reorder: custom_orderer, + include_if_exportable: include_if_exportable } end subject do - described_class.new(exportable, relations_schema, json_writer, exportable_path: exportable_path, logger: logger) + described_class.new(exportable, relations_schema, json_writer, exportable_path: exportable_path, logger: logger, current_user: user) end describe '#execute' do @@ -210,6 +212,63 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer do subject.execute end end + + describe 'conditional export of included associations' do + let(:include) do + [{ issues: { include: [{ label_links: { include: [:label] } }] } }] + end + + let(:include_if_exportable) do + { issues: [:label_links] } + end + + let_it_be(:label) { create(:label, project: exportable) } + let_it_be(:link) { create(:label_link, label: label, target: issue) } + + context 'when association is exportable' do + before do + allow_next_found_instance_of(Issue) do |issue| + allow(issue).to receive(:exportable_association?).with(:label_links, current_user: user).and_return(true) + end + end + + it 'includes exportable association' do + expected_issue = issue.to_json(include: [{ label_links: { include: [:label] } }]) + + expect(json_writer).to receive(:write_relation_array).with(exportable_path, :issues, array_including(expected_issue)) + + subject.execute + end + end + + context 'when association is not exportable' do + before do + allow_next_found_instance_of(Issue) do |issue| + allow(issue).to receive(:exportable_association?).with(:label_links, current_user: user).and_return(false) + end + end + + it 'filters out not exportable association' do + expect(json_writer).to receive(:write_relation_array).with(exportable_path, :issues, array_including(issue.to_json)) + + subject.execute + end + end + + context 'when association does not respond to exportable_association?' do + before do + allow_next_found_instance_of(Issue) do |issue| + allow(issue).to receive(:respond_to?).with(:exportable_association?).and_return(false) + end + end + + it 'filters out not exportable association' do + expect(json_writer).to receive(:write_relation_array).with(exportable_path, :issues, array_including(issue.to_json)) + + subject.execute + end + end + end end describe '#serialize_relation' do diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 361640659b8..352255afc8d 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -586,6 +586,7 @@ ProjectFeature: - environments_access_level - feature_flags_access_level - releases_access_level +- monitor_access_level - created_at - updated_at ProtectedBranch::MergeAccessLevel: diff --git a/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb b/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb index ba5137e2b92..bd0904b9db2 100644 --- a/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb @@ -12,11 +12,28 @@ RSpec.describe Sidebars::Projects::Menus::MonitorMenu do subject { described_class.new(context) } describe '#render?' do - context 'when operations feature is disabled' do - it 'returns false' do - project.project_feature.update!(operations_access_level: Featurable::DISABLED) + using RSpec::Parameterized::TableSyntax + let(:enabled) { Featurable::PRIVATE } + let(:disabled) { Featurable::DISABLED } + + where(:flag_enabled, :operations_access_level, :monitor_level, :render) do + true | ref(:disabled) | ref(:enabled) | true + true | ref(:disabled) | ref(:disabled) | false + true | ref(:enabled) | ref(:enabled) | true + true | ref(:enabled) | ref(:disabled) | false + false | ref(:disabled) | ref(:enabled) | false + false | ref(:disabled) | ref(:disabled) | false + false | ref(:enabled) | ref(:enabled) | true + false | ref(:enabled) | ref(:disabled) | true + end + + with_them do + it 'renders when expected to' do + stub_feature_flags(split_operations_visibility_permissions: flag_enabled) + project.project_feature.update!(operations_access_level: operations_access_level) + project.project_feature.update!(monitor_access_level: monitor_level) - expect(subject.render?).to be false + expect(subject.render?).to be render end end diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb index b49b9ce8a2a..89f34834aa4 100644 --- a/spec/models/concerns/project_features_compatibility_spec.rb +++ b/spec/models/concerns/project_features_compatibility_spec.rb @@ -8,6 +8,7 @@ RSpec.describe ProjectFeaturesCompatibility do let(:features) do features_enabled + %w( repository pages operations container_registry package_registry environments feature_flags releases + monitor ) end diff --git a/spec/models/packages/rpm/metadatum_spec.rb b/spec/models/packages/rpm/metadatum_spec.rb index 53a40b4a4b3..0e7817fdf86 100644 --- a/spec/models/packages/rpm/metadatum_spec.rb +++ b/spec/models/packages/rpm/metadatum_spec.rb @@ -8,10 +8,14 @@ RSpec.describe Packages::Rpm::Metadatum, type: :model do describe 'validations' do it { is_expected.to validate_presence_of(:package) } + it { is_expected.to validate_presence_of(:epoch) } it { is_expected.to validate_presence_of(:release) } it { is_expected.to validate_presence_of(:summary) } it { is_expected.to validate_presence_of(:description) } it { is_expected.to validate_presence_of(:arch) } + + it { is_expected.to validate_numericality_of(:epoch).only_integer.is_greater_than_or_equal_to(0) } + it { is_expected.to validate_length_of(:release).is_at_most(128) } it { is_expected.to validate_length_of(:summary).is_at_most(1000) } it { is_expected.to validate_length_of(:description).is_at_most(5000) } diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 9bf96475fc1..5b032c53352 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -1930,14 +1930,10 @@ RSpec.describe ProjectPolicy do describe 'operations feature' do using RSpec::Parameterized::TableSyntax - before do - stub_feature_flags(split_operations_visibility_permissions: false) - end + let(:guest_permissions) { [:read_environment, :read_deployment] } - let(:guest_operations_permissions) { [:read_environment, :read_deployment] } - - let(:developer_operations_permissions) do - guest_operations_permissions + [ + let(:developer_permissions) do + guest_permissions + [ :read_feature_flag, :read_sentry_issue, :read_alert_management_alert, :read_terraform_state, :metrics_dashboard, :read_pod_logs, :read_prometheus, :create_feature_flag, :create_environment, :create_deployment, :update_feature_flag, :update_environment, @@ -1946,13 +1942,17 @@ RSpec.describe ProjectPolicy do ] end - let(:maintainer_operations_permissions) do - developer_operations_permissions + [ + let(:maintainer_permissions) do + developer_permissions + [ :read_cluster, :create_cluster, :update_cluster, :admin_environment, :admin_cluster, :admin_terraform_state, :admin_deployment ] end + before do + stub_feature_flags(split_operations_visibility_permissions: false) + end + where(:project_visibility, :access_level, :role, :allowed) do :public | ProjectFeature::ENABLED | :maintainer | true :public | ProjectFeature::ENABLED | :developer | true @@ -2005,33 +2005,22 @@ RSpec.describe ProjectPolicy do expect_disallowed(*permissions_abilities(role)) end end - - def permissions_abilities(role) - case role - when :maintainer - maintainer_operations_permissions - when :developer - developer_operations_permissions - else - guest_operations_permissions - end - end end end describe 'environments feature' do using RSpec::Parameterized::TableSyntax - let(:guest_environments_permissions) { [:read_environment, :read_deployment] } + let(:guest_permissions) { [:read_environment, :read_deployment] } - let(:developer_environments_permissions) do - guest_environments_permissions + [ + let(:developer_permissions) do + guest_permissions + [ :create_environment, :create_deployment, :update_environment, :update_deployment, :destroy_environment ] end - let(:maintainer_environments_permissions) do - developer_environments_permissions + [:admin_environment, :admin_deployment] + let(:maintainer_permissions) do + developer_permissions + [:admin_environment, :admin_deployment] end where(:project_visibility, :access_level, :role, :allowed) do @@ -2086,15 +2075,73 @@ RSpec.describe ProjectPolicy do expect_disallowed(*permissions_abilities(role)) end end + end + end - def permissions_abilities(role) - case role - when :maintainer - maintainer_environments_permissions - when :developer - developer_environments_permissions + describe 'monitor feature' do + using RSpec::Parameterized::TableSyntax + + let(:guest_permissions) { [] } + + let(:developer_permissions) do + guest_permissions + [ + :read_sentry_issue, :read_alert_management_alert, :metrics_dashboard, + :update_sentry_issue, :update_alert_management_alert + ] + end + + let(:maintainer_permissions) { developer_permissions } + + where(:project_visibility, :access_level, :role, :allowed) do + :public | ProjectFeature::ENABLED | :maintainer | true + :public | ProjectFeature::ENABLED | :developer | true + :public | ProjectFeature::ENABLED | :guest | true + :public | ProjectFeature::ENABLED | :anonymous | true + :public | ProjectFeature::PRIVATE | :maintainer | true + :public | ProjectFeature::PRIVATE | :developer | true + :public | ProjectFeature::PRIVATE | :guest | true + :public | ProjectFeature::PRIVATE | :anonymous | false + :public | ProjectFeature::DISABLED | :maintainer | false + :public | ProjectFeature::DISABLED | :developer | false + :public | ProjectFeature::DISABLED | :guest | false + :public | ProjectFeature::DISABLED | :anonymous | false + :internal | ProjectFeature::ENABLED | :maintainer | true + :internal | ProjectFeature::ENABLED | :developer | true + :internal | ProjectFeature::ENABLED | :guest | true + :internal | ProjectFeature::ENABLED | :anonymous | false + :internal | ProjectFeature::PRIVATE | :maintainer | true + :internal | ProjectFeature::PRIVATE | :developer | true + :internal | ProjectFeature::PRIVATE | :guest | true + :internal | ProjectFeature::PRIVATE | :anonymous | false + :internal | ProjectFeature::DISABLED | :maintainer | false + :internal | ProjectFeature::DISABLED | :developer | false + :internal | ProjectFeature::DISABLED | :guest | false + :internal | ProjectFeature::DISABLED | :anonymous | false + :private | ProjectFeature::ENABLED | :maintainer | true + :private | ProjectFeature::ENABLED | :developer | true + :private | ProjectFeature::ENABLED | :guest | false + :private | ProjectFeature::ENABLED | :anonymous | false + :private | ProjectFeature::PRIVATE | :maintainer | true + :private | ProjectFeature::PRIVATE | :developer | true + :private | ProjectFeature::PRIVATE | :guest | false + :private | ProjectFeature::PRIVATE | :anonymous | false + :private | ProjectFeature::DISABLED | :maintainer | false + :private | ProjectFeature::DISABLED | :developer | false + :private | ProjectFeature::DISABLED | :guest | false + :private | ProjectFeature::DISABLED | :anonymous | false + end + + with_them do + let(:current_user) { user_subject(role) } + let(:project) { project_subject(project_visibility) } + + it 'allows/disallows the abilities based on the monitor feature access level' do + project.project_feature.update!(monitor_access_level: access_level) + + if allowed + expect_allowed(*permissions_abilities(role)) else - guest_environments_permissions + expect_disallowed(*permissions_abilities(role)) end end end @@ -2682,6 +2729,8 @@ RSpec.describe ProjectPolicy do end end + private + def project_subject(project_type) case project_type when :public diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index 81c4f5d8188..e6916e02fde 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -84,7 +84,7 @@ RSpec.describe JwtController do context 'project with enabled CI' do subject! { get '/jwt/auth', params: parameters, headers: headers } - it { expect(service_class).to have_received(:new).with(project, user, ActionController::Parameters.new(parameters).permit!) } + it { expect(service_class).to have_received(:new).with(project, user, ActionController::Parameters.new(parameters.merge(auth_type: :build)).permit!) } it_behaves_like 'user logging' end @@ -107,7 +107,12 @@ RSpec.describe JwtController do it 'authenticates correctly' do expect(response).to have_gitlab_http_status(:ok) - expect(service_class).to have_received(:new).with(nil, nil, ActionController::Parameters.new(parameters.merge(deploy_token: deploy_token)).permit!) + expect(service_class).to have_received(:new) + .with( + nil, + nil, + ActionController::Parameters.new(parameters.merge(deploy_token: deploy_token, auth_type: :deploy_token)).permit! + ) end it 'does not log a user' do @@ -127,7 +132,12 @@ RSpec.describe JwtController do it 'authenticates correctly' do expect(response).to have_gitlab_http_status(:ok) - expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!) + expect(service_class).to have_received(:new) + .with( + nil, + user, + ActionController::Parameters.new(parameters.merge(auth_type: :personal_access_token)).permit! + ) end it_behaves_like 'rejecting a blocked user' @@ -142,7 +152,7 @@ RSpec.describe JwtController do subject! { get '/jwt/auth', params: parameters, headers: headers } - it { expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!) } + it { expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters.merge(auth_type: :gitlab_or_ldap)).permit!) } it_behaves_like 'rejecting a blocked user' @@ -162,7 +172,7 @@ RSpec.describe JwtController do ActionController::Parameters.new({ service: service_name, scopes: %w(scope1 scope2) }).permit! end - it { expect(service_class).to have_received(:new).with(nil, user, service_parameters) } + it { expect(service_class).to have_received(:new).with(nil, user, service_parameters.merge(auth_type: :gitlab_or_ldap)) } it_behaves_like 'user logging' end diff --git a/spec/services/bulk_imports/tree_export_service_spec.rb b/spec/services/bulk_imports/tree_export_service_spec.rb index ffb81fe2b5f..6e26cb6dc2b 100644 --- a/spec/services/bulk_imports/tree_export_service_spec.rb +++ b/spec/services/bulk_imports/tree_export_service_spec.rb @@ -8,7 +8,7 @@ RSpec.describe BulkImports::TreeExportService do let(:relation) { 'issues' } - subject(:service) { described_class.new(project, export_path, relation) } + subject(:service) { described_class.new(project, export_path, relation, project.owner) } describe '#execute' do it 'executes export service and archives exported data' do @@ -21,7 +21,7 @@ RSpec.describe BulkImports::TreeExportService do context 'when unsupported relation is passed' do it 'raises an error' do - service = described_class.new(project, export_path, 'unsupported') + service = described_class.new(project, export_path, 'unsupported', project.owner) expect { service.execute }.to raise_error(BulkImports::Error, 'Unsupported relation export type') end diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb index 352326de868..58659775d8c 100644 --- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb +++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb @@ -54,6 +54,12 @@ RSpec.shared_examples 'a valid token' do end end +RSpec.shared_examples 'with auth_type' do + let(:current_params) { super().merge(auth_type: :foo) } + + it { expect(payload['auth_type']).to eq('foo') } +end + RSpec.shared_examples 'a browsable' do let(:access) do [{ 'type' => 'registry', @@ -286,6 +292,7 @@ RSpec.shared_examples 'a container registry auth service' do shared_examples 'private project' do context 'allow to use scope-less authentication' do it_behaves_like 'a valid token' + it_behaves_like 'with auth_type' end context 'allow developer to push images' do @@ -299,6 +306,7 @@ RSpec.shared_examples 'a container registry auth service' do it_behaves_like 'a pushable' it_behaves_like 'container repository factory' + it_behaves_like 'with auth_type' end context 'disallow developer to delete images' do @@ -341,6 +349,7 @@ RSpec.shared_examples 'a container registry auth service' do it_behaves_like 'a pullable' it_behaves_like 'not a container repository factory' + it_behaves_like 'with auth_type' end end @@ -381,6 +390,7 @@ RSpec.shared_examples 'a container registry auth service' do it_behaves_like 'a pullable' it_behaves_like 'not a container repository factory' + it_behaves_like 'with auth_type' end context 'disallow guest to pull or push images' do @@ -445,6 +455,7 @@ RSpec.shared_examples 'a container registry auth service' do it_behaves_like 'a pullable' it_behaves_like 'not a container repository factory' + it_behaves_like 'with auth_type' end context 'disallow anyone to push images' do @@ -495,6 +506,7 @@ RSpec.shared_examples 'a container registry auth service' do it_behaves_like 'a pullable' it_behaves_like 'not a container repository factory' + it_behaves_like 'with auth_type' end context 'disallow anyone to push images' do @@ -600,6 +612,7 @@ RSpec.shared_examples 'a container registry auth service' do end it_behaves_like 'a valid token' + it_behaves_like 'with auth_type' context 'allow to pull and push images' do let(:current_params) do @@ -944,10 +957,11 @@ RSpec.shared_examples 'a container registry auth service' do shared_examples 'able to login' do context 'registry provides read_container_image authentication_abilities' do - let(:current_params) { { deploy_token: deploy_token } } + let(:current_params) { { deploy_token: deploy_token, auth_type: :deploy_token } } let(:authentication_abilities) { [:read_container_image] } it_behaves_like 'an authenticated' + it { expect(payload['auth_type']).to eq('deploy_token') } end end |