Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-09-15 03:14:10 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-09-15 03:14:10 +0300
commit119c999cf1f1bb51d4324e3c4847435347eb32cf (patch)
tree83f92f606877455177bb57c7d2ad31704d10027b /spec
parent5594a6badf033359b84c2e9822f145c66b0dce8f (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects_controller_spec.rb2
-rw-r--r--spec/factories/packages/rpm/metadata.rb1
-rw-r--r--spec/factories/projects.rb1
-rw-r--r--spec/features/monitor_sidebar_link_spec.rb102
-rw-r--r--spec/frontend/groups/components/group_item_spec.js30
-rw-r--r--spec/frontend/groups/components/groups_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_row_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_header_spec.js52
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_row_spec.js75
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_spec.js66
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/mock_data.js12
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js125
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js156
-rw-r--r--spec/frontend/snippets/components/edit_spec.js38
-rw-r--r--spec/frontend/snippets/components/show_spec.js20
-rw-r--r--spec/frontend/snippets/components/snippet_blob_view_spec.js4
-rw-r--r--spec/frontend/snippets/components/snippet_visibility_edit_spec.js18
-rw-r--r--spec/lib/gitlab/import_export/config_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb63
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/lib/sidebars/projects/menus/monitor_menu_spec.rb25
-rw-r--r--spec/models/concerns/project_features_compatibility_spec.rb1
-rw-r--r--spec/models/packages/rpm/metadatum_spec.rb4
-rw-r--r--spec/policies/project_policy_spec.rb113
-rw-r--r--spec/requests/jwt_controller_spec.rb20
-rw-r--r--spec/services/bulk_imports/tree_export_service_spec.rb4
-rw-r--r--spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb16
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