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>2024-01-17 21:09:52 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2024-01-17 21:09:52 +0300
commit003efb27fc4d7d0571979553c602fccfbf5ad0c2 (patch)
tree721ec9af57108c73fc5c4c7a06e996800ead367e /spec
parent78a5f872de316860ccd7a983c10805bf6c6b771c (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/frontend/organizations/shared/components/projects_view_spec.js96
-rw-r--r--spec/frontend/organizations/shared/utils_spec.js14
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap1
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap2
-rw-r--r--spec/frontend/vue_shared/components/projects_list/projects_list_item_spec.js38
-rw-r--r--spec/helpers/organizations/organization_helper_spec.rb8
-rw-r--r--spec/requests/api/members_spec.rb18
-rw-r--r--spec/services/members/create_service_spec.rb16
-rw-r--r--spec/support/shared_examples/models/member_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/members_shared_examples.rb2
10 files changed, 135 insertions, 62 deletions
diff --git a/spec/frontend/organizations/shared/components/projects_view_spec.js b/spec/frontend/organizations/shared/components/projects_view_spec.js
index 3cc71927bfa..c406ba2cd47 100644
--- a/spec/frontend/organizations/shared/components/projects_view_spec.js
+++ b/spec/frontend/organizations/shared/components/projects_view_spec.js
@@ -2,19 +2,18 @@ import VueApollo from 'vue-apollo';
import Vue from 'vue';
import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
import ProjectsView from '~/organizations/shared/components/projects_view.vue';
+import projectsQuery from '~/organizations/shared/graphql/queries/projects.query.graphql';
import { formatProjects } from '~/organizations/shared/utils';
-import resolvers from '~/organizations/shared/graphql/resolvers';
import ProjectsList from '~/vue_shared/components/projects_list/projects_list.vue';
import { createAlert } from '~/alert';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { organizationProjects } from '~/organizations/mock_data';
+import { organizationProjects as nodes, pageInfo, pageInfoEmpty } from '~/organizations/mock_data';
jest.mock('~/alert');
Vue.use(VueApollo);
-jest.useFakeTimers();
describe('ProjectsView', () => {
let wrapper;
@@ -23,14 +22,29 @@ describe('ProjectsView', () => {
const defaultProvide = {
projectsEmptyStateSvgPath: 'illustrations/empty-state/empty-projects-md.svg',
newProjectPath: '/projects/new',
+ organizationGid: 'gid://gitlab/Organizations::Organization/1',
};
const defaultPropsData = {
listItemClass: 'gl-px-5',
};
- const createComponent = ({ mockResolvers = resolvers, propsData = {} } = {}) => {
- mockApollo = createMockApollo([], mockResolvers);
+ const projects = {
+ nodes,
+ pageInfo,
+ };
+
+ const successHandler = jest.fn().mockResolvedValue({
+ data: {
+ organization: {
+ id: defaultProvide.organizationGid,
+ projects,
+ },
+ },
+ });
+
+ const createComponent = ({ handler = successHandler, propsData = {} } = {}) => {
+ mockApollo = createMockApollo([[projectsQuery, handler]]);
wrapper = shallowMountExtended(ProjectsView, {
apolloProvider: mockApollo,
@@ -42,45 +56,45 @@ describe('ProjectsView', () => {
});
};
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+ const findProjectsList = () => wrapper.findComponent(ProjectsList);
+
afterEach(() => {
mockApollo = null;
});
describe('when API call is loading', () => {
- beforeEach(() => {
- const mockResolvers = {
- Query: {
- organization: jest.fn().mockReturnValueOnce(new Promise(() => {})),
- },
- };
-
- createComponent({ mockResolvers });
- });
-
it('renders loading icon', () => {
- expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ createComponent();
+
+ expect(findLoadingIcon().exists()).toBe(true);
});
});
describe('when API call is successful', () => {
describe('when there are no projects', () => {
- it('renders empty state without buttons by default', async () => {
- const mockResolvers = {
- Query: {
- organization: jest.fn().mockResolvedValueOnce({
- projects: { nodes: [] },
- }),
+ const emptyHandler = jest.fn().mockResolvedValue({
+ data: {
+ organization: {
+ id: defaultProvide.organizationGid,
+ projects: {
+ nodes: [],
+ pageInfo: pageInfoEmpty,
+ },
},
- };
- createComponent({ mockResolvers });
+ },
+ });
+
+ it('renders empty state without buttons by default', async () => {
+ createComponent({ handler: emptyHandler });
- jest.runAllTimers();
await waitForPromises();
- expect(wrapper.findComponent(GlEmptyState).props()).toMatchObject({
+ expect(findEmptyState().props()).toMatchObject({
title: "You don't have any projects yet.",
description:
- 'Projects are where you can store your code, access issues, wiki, and other features of Gitlab.',
+ 'Projects are where you can store your code, access issues, wiki, and other features of GitLab.',
svgHeight: 144,
svgPath: defaultProvide.projectsEmptyStateSvgPath,
primaryButtonLink: null,
@@ -90,19 +104,14 @@ describe('ProjectsView', () => {
describe('when `shouldShowEmptyStateButtons` is `true` and `projectsEmptyStateSvgPath` is set', () => {
it('renders empty state with buttons', async () => {
- const mockResolvers = {
- Query: {
- organization: jest.fn().mockResolvedValueOnce({
- projects: { nodes: [] },
- }),
- },
- };
- createComponent({ mockResolvers, propsData: { shouldShowEmptyStateButtons: true } });
+ createComponent({
+ handler: emptyHandler,
+ propsData: { shouldShowEmptyStateButtons: true },
+ });
- jest.runAllTimers();
await waitForPromises();
- expect(wrapper.findComponent(GlEmptyState).props()).toMatchObject({
+ expect(findEmptyState().props()).toMatchObject({
primaryButtonLink: defaultProvide.newProjectPath,
primaryButtonText: 'New project',
});
@@ -116,11 +125,10 @@ describe('ProjectsView', () => {
});
it('renders `ProjectsList` component and passes correct props', async () => {
- jest.runAllTimers();
await waitForPromises();
- expect(wrapper.findComponent(ProjectsList).props()).toEqual({
- projects: formatProjects(organizationProjects.nodes),
+ expect(findProjectsList().props()).toMatchObject({
+ projects: formatProjects(nodes),
showProjectIcon: true,
listItemClass: defaultPropsData.listItemClass,
});
@@ -132,13 +140,7 @@ describe('ProjectsView', () => {
const error = new Error();
beforeEach(() => {
- const mockResolvers = {
- Query: {
- organization: jest.fn().mockRejectedValueOnce(error),
- },
- };
-
- createComponent({ mockResolvers });
+ createComponent({ handler: jest.fn().mockRejectedValue(error) });
});
it('displays error alert', async () => {
diff --git a/spec/frontend/organizations/shared/utils_spec.js b/spec/frontend/organizations/shared/utils_spec.js
index 778a18ab2bc..d8d5279b670 100644
--- a/spec/frontend/organizations/shared/utils_spec.js
+++ b/spec/frontend/organizations/shared/utils_spec.js
@@ -5,21 +5,19 @@ import { organizationProjects, organizationGroups } from '~/organizations/mock_d
describe('formatProjects', () => {
it('correctly formats the projects', () => {
- const [firstMockProject] = organizationProjects.nodes;
- const formattedProjects = formatProjects(organizationProjects.nodes);
+ const [firstMockProject] = organizationProjects;
+ const formattedProjects = formatProjects(organizationProjects);
const [firstFormattedProject] = formattedProjects;
expect(firstFormattedProject).toMatchObject({
id: getIdFromGraphQLId(firstMockProject.id),
name: firstMockProject.nameWithNamespace,
- permissions: {
- projectAccess: {
- accessLevel: firstMockProject.accessLevel.integerValue,
- },
- },
+ mergeRequestsAccessLevel: firstMockProject.mergeRequestsAccessLevel.stringValue,
+ issuesAccessLevel: firstMockProject.issuesAccessLevel.stringValue,
+ forkingAccessLevel: firstMockProject.forkingAccessLevel.stringValue,
availableActions: [ACTION_EDIT, ACTION_DELETE],
});
- expect(formattedProjects.length).toBe(organizationProjects.nodes.length);
+ expect(formattedProjects.length).toBe(organizationProjects.length);
});
});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap
index 6af9e38192e..009d9c959cb 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap
@@ -18,7 +18,6 @@ exports[`packages_list_app renders 1`] = `
alt=""
class="gl-dark-invert-keep-hue gl-max-w-full"
height="144"
- role="img"
src="helpSvg"
/>
</div>
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap
index d6c3d98efa3..89b55b69893 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap
@@ -56,7 +56,7 @@ exports[`PypiInstallation renders all the messages 1`] = `
class="gl-new-dropdown-contents gl-new-dropdown-contents-with-scrim-overlay"
id="reference-2"
role="listbox"
- tabindex="-1"
+ tabindex="0"
>
<li
aria-hidden="true"
diff --git a/spec/frontend/vue_shared/components/projects_list/projects_list_item_spec.js b/spec/frontend/vue_shared/components/projects_list/projects_list_item_spec.js
index a5a5a43effe..9900baa6bdb 100644
--- a/spec/frontend/vue_shared/components/projects_list/projects_list_item_spec.js
+++ b/spec/frontend/vue_shared/components/projects_list/projects_list_item_spec.js
@@ -36,6 +36,8 @@ describe('ProjectsListItem', () => {
};
const findAvatarLabeled = () => wrapper.findComponent(GlAvatarLabeled);
+ const findMergeRequestsLink = () =>
+ wrapper.findByRole('link', { name: ProjectsListItem.i18n.mergeRequests });
const findIssuesLink = () => wrapper.findByRole('link', { name: ProjectsListItem.i18n.issues });
const findForksLink = () => wrapper.findByRole('link', { name: ProjectsListItem.i18n.forks });
const findProjectTopics = () => wrapper.findByTestId('project-topics');
@@ -148,6 +150,42 @@ describe('ProjectsListItem', () => {
});
});
+ describe('when merge requests are enabled', () => {
+ it('renders merge requests count', () => {
+ createComponent({
+ propsData: {
+ project: {
+ ...project,
+ openMergeRequestsCount: 5,
+ },
+ },
+ });
+
+ const mergeRequestsLink = findMergeRequestsLink();
+ const tooltip = getBinding(mergeRequestsLink.element, 'gl-tooltip');
+
+ expect(tooltip.value).toBe(ProjectsListItem.i18n.mergeRequests);
+ expect(mergeRequestsLink.attributes('href')).toBe(`${project.webUrl}/-/merge_requests`);
+ expect(mergeRequestsLink.text()).toBe('5');
+ expect(mergeRequestsLink.findComponent(GlIcon).props('name')).toBe('git-merge');
+ });
+ });
+
+ describe('when merge requests are not enabled', () => {
+ it('does not render merge requests count', () => {
+ createComponent({
+ propsData: {
+ project: {
+ ...project,
+ mergeRequestsAccessLevel: FEATURABLE_DISABLED,
+ },
+ },
+ });
+
+ expect(findMergeRequestsLink().exists()).toBe(false);
+ });
+ });
+
describe('when issues are enabled', () => {
it('renders issues count', () => {
createComponent();
diff --git a/spec/helpers/organizations/organization_helper_spec.rb b/spec/helpers/organizations/organization_helper_spec.rb
index 0f2f4ed1b54..0535a860b94 100644
--- a/spec/helpers/organizations/organization_helper_spec.rb
+++ b/spec/helpers/organizations/organization_helper_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
let_it_be(:organization_detail) { build_stubbed(:organization_detail, description_html: '<em>description</em>') }
let_it_be(:organization) { organization_detail.organization }
+ let_it_be(:organization_gid) { 'gid://gitlab/Organizations::Organization/1' }
let_it_be(:new_group_path) { '/groups/new' }
let_it_be(:new_project_path) { '/projects/new' }
let_it_be(:organizations_empty_state_svg_path) { 'illustrations/empty-state/empty-organizations-md.svg' }
@@ -15,6 +16,7 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
let_it_be(:preview_markdown_organizations_path) { '/-/organizations/preview_markdown' }
before do
+ allow(organization).to receive(:to_global_id).and_return(organization_gid)
allow(helper).to receive(:new_group_path).and_return(new_group_path)
allow(helper).to receive(:new_project_path).and_return(new_project_path)
allow(helper).to receive(:image_path).with(organizations_empty_state_svg_path)
@@ -41,6 +43,7 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
)
).to eq(
{
+ 'organization_gid' => organization_gid,
'organization' => {
'id' => organization.id,
'name' => organization.name,
@@ -66,10 +69,11 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
it 'returns expected json' do
expect(
Gitlab::Json.parse(
- helper.organization_groups_and_projects_app_data
+ helper.organization_groups_and_projects_app_data(organization)
)
).to eq(
{
+ 'organization_gid' => organization_gid,
'new_group_path' => new_group_path,
'new_project_path' => new_project_path,
'groups_empty_state_svg_path' => groups_empty_state_svg_path,
@@ -139,7 +143,7 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
it 'returns expected json' do
expect(Gitlab::Json.parse(helper.organization_user_app_data(organization))).to eq(
{
- 'organization_gid' => organization.to_global_id.to_s,
+ 'organization_gid' => organization_gid,
'paths' => {
'admin_user' => admin_user_path(:id)
}
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 7fc58140fb6..01a8ca055a5 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -346,6 +346,24 @@ RSpec.describe API::Members, feature_category: :groups_and_projects do
expect(json_response['access_level']).to eq(Member::DEVELOPER)
end
+ it 'returns the error message if there was an error adding the member to the group' do
+ error_message = 'Test CreateService Error Message'
+ allow_next_instance_of(::Members::CreateService) do |service|
+ expect(service).to receive(:execute).and_return(status: :error, message: error_message)
+ allow(service).to receive(:single_member).and_return(
+ instance_double(Member, invalid?: false)
+ )
+ end
+
+ expect do
+ post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
+ params: { user_id: stranger.id, access_level: Member::DEVELOPER }
+ end.not_to change { source.members.count }
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['status']).to eq('error')
+ expect(json_response['message']).to eq(error_message)
+ end
+
context 'with invite_source considerations', :snowplow do
let(:params) { { user_id: stranger.id, access_level: Member::DEVELOPER } }
diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb
index c08b40e9528..f019e7f4046 100644
--- a/spec/services/members/create_service_spec.rb
+++ b/spec/services/members/create_service_spec.rb
@@ -31,8 +31,10 @@ RSpec.describe Members::CreateService, :aggregate_failures, :clean_gitlab_redis_
context 'when the current user does not have permission to create members' do
let(:current_user) { create(:user) }
- it 'raises a Gitlab::Access::AccessDeniedError' do
- expect { execute_service }.to raise_error(Gitlab::Access::AccessDeniedError)
+ it 'returns an unauthorized http_status' do
+ expect(execute_service[:status]).to eq(:error)
+ # this is expected by API::Helpers::MembersHelpers#add_single_member_by_user_id
+ expect(execute_service[:http_status]).to eq(:unauthorized)
end
context 'when a project maintainer attempts to add owners' do
@@ -56,6 +58,15 @@ RSpec.describe Members::CreateService, :aggregate_failures, :clean_gitlab_redis_
end
end
+ context 'when trying to create a Membership with invalid params' do
+ let(:additional_params) { Hash[invite_source: '_invite_source_', expires_at: 3.days.ago] }
+
+ it 'returns an error response' do
+ expect(execute_service[:status]).to eq(:error)
+ expect(execute_service[:http_status]).to be_nil
+ end
+ end
+
context 'when passing valid parameters' do
it 'adds a user to members' do
expect(execute_service[:status]).to eq(:success)
@@ -251,6 +262,7 @@ RSpec.describe Members::CreateService, :aggregate_failures, :clean_gitlab_redis_
it 'does not update the member' do
expect(execute_service[:status]).to eq(:error)
+ expect(execute_service[:http_status]).to eq(:unauthorized)
expect(execute_service[:message]).to eq("#{project_bot.username}: not authorized to update member")
expect(Onboarding::Progress.completed?(source.namespace, :user_added)).to be(false)
end
diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb
index 01d6642e814..43b60fad67d 100644
--- a/spec/support/shared_examples/models/member_shared_examples.rb
+++ b/spec/support/shared_examples/models/member_shared_examples.rb
@@ -139,6 +139,7 @@ RSpec.shared_examples_for "member creation" do
expect(source.reload).to have_user(project_bot)
expect(member).to be_persisted
expect(member.access_level).to eq(Gitlab::Access::DEVELOPER)
+ expect(member.errors.added?(:base, :unauthorized)).to eq(true)
expect(member.errors.full_messages).to include(/not authorized to update member/)
end
end
@@ -169,6 +170,7 @@ RSpec.shared_examples_for "member creation" do
expect(member).not_to be_persisted
expect(source).not_to have_user(user)
+ expect(member.errors.added?(:base, :unauthorized)).to eq(true)
expect(member.errors.full_messages).to include(/not authorized to create member/)
end
end
diff --git a/spec/support/shared_examples/requests/api/members_shared_examples.rb b/spec/support/shared_examples/requests/api/members_shared_examples.rb
index 9136f60eb93..135a984f8f9 100644
--- a/spec/support/shared_examples/requests/api/members_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/members_shared_examples.rb
@@ -14,7 +14,7 @@ end
RSpec.shared_examples 'a 403 response when user does not have rights to manage members of a specific access level' do
it 'returns 403' do
- route
+ expect { route }.not_to change { Member.count }
expect(response).to have_gitlab_http_status(:forbidden)
end