Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-03-05 21:09:03 +00:00
parent 6ba372cf11
commit 250c4c2596
28 changed files with 401 additions and 126 deletions

View file

@ -15,7 +15,9 @@ export default {
},
data() {
return {
selectedRadio: this.$options.ALL_PROJECTS,
selectedRadio: !this.inputAttrs.value
? this.$options.ALL_PROJECTS
: this.$options.SELECTED_PROJECTS,
selectedProjects: [],
};
},
@ -28,6 +30,13 @@ export default {
? null
: this.selectedProjects.map((project) => project.id).join(',');
},
initialProjectIds() {
if (!this.inputAttrs.value) {
return [];
}
return this.inputAttrs.value.split(',');
},
},
methods: {
handleTokenSelectorFocus() {
@ -50,7 +59,11 @@ export default {
__('Selected projects')
}}</gl-form-radio>
<input :id="inputAttrs.id" type="hidden" :name="inputAttrs.name" :value="hiddenInputValue" />
<projects-token-selector v-model="selectedProjects" @focus="handleTokenSelectorFocus" />
<projects-token-selector
v-model="selectedProjects"
:initial-project-ids="initialProjectIds"
@focus="handleTokenSelectorFocus"
/>
</gl-form-group>
</div>
</template>

View file

@ -8,12 +8,13 @@ import {
} from '@gitlab/ui';
import produce from 'immer';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
import getProjectsQuery from '../graphql/queries/get_projects.query.graphql';
const DEBOUNCE_DELAY = 250;
const PROJECTS_PER_PAGE = 20;
const GRAPHQL_ENTITY_TYPE = 'Project';
export default {
name: 'ProjectsTokenSelector',
@ -32,6 +33,10 @@ export default {
type: Array,
required: true,
},
initialProjectIds: {
type: Array,
required: true,
},
},
apollo: {
projects: {
@ -46,10 +51,7 @@ export default {
},
update({ projects }) {
return {
list: projects.nodes.map((project) => ({
...project,
id: getIdFromGraphQLId(project.id),
})),
list: this.formatProjectNodes(projects),
pageInfo: projects.pageInfo,
};
},
@ -58,6 +60,21 @@ export default {
this.isSearching = false;
},
},
initialProjects: {
query: getProjectsQuery,
variables() {
return {
ids: this.initialProjectIds.map((id) => convertToGraphQLId(GRAPHQL_ENTITY_TYPE, id)),
};
},
manual: true,
skip() {
return !this.initialProjectIds.length;
},
result({ data: { projects } }) {
this.$emit('input', this.formatProjectNodes(projects));
},
},
},
data() {
return {
@ -71,6 +88,12 @@ export default {
};
},
methods: {
formatProjectNodes(projects) {
return projects.nodes.map((project) => ({
...project,
id: getIdFromGraphQLId(project.id),
}));
},
handleSearch(query) {
this.isSearching = true;
this.searchQuery = query;

View file

@ -1,10 +1,16 @@
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
query getProjects($search: String!, $after: String = "", $first: Int!) {
query getProjects(
$search: String = ""
$after: String = ""
$first: Int = null
$ids: [ID!] = null
) {
projects(
search: $search
after: $after
first: $first
ids: $ids
membership: true
searchNamespaces: true
sort: "UPDATED_ASC"

View file

@ -10,6 +10,7 @@ const getInputAttrs = (el) => {
return {
id: input.id,
name: input.name,
value: input.value,
placeholder: input.placeholder,
};
};

View file

@ -63,7 +63,7 @@ export default {
<span class="text-truncate w-100">{{ job.name ? job.name : job.id }}</span>
<gl-icon v-if="job.retried" name="retry" class="js-retry-icon" />
<gl-icon v-if="job.retried" name="retry" />
</gl-link>
</div>
</template>

View file

@ -70,7 +70,6 @@ export default {
},
{
key: 'actions',
label: '',
thClass: DEFAULT_TH_CLASSES,
tdClass: DEFAULT_TD_CLASS,
columnClass: 'gl-w-20p',
@ -190,6 +189,7 @@ export default {
fixed
>
<template #head(actions)>
<span class="gl-display-block gl-lg-display-none!">{{ s__('Pipeline|Actions') }}</span>
<slot name="table-header-actions"></slot>
</template>
@ -202,11 +202,7 @@ export default {
</template>
<template #cell(pipeline)="{ item }">
<pipeline-url
class="gl-text-truncate"
:pipeline="item"
:pipeline-schedule-url="pipelineScheduleUrl"
/>
<pipeline-url :pipeline="item" :pipeline-schedule-url="pipelineScheduleUrl" />
</template>
<template #cell(triggerer)="{ item }">

View file

@ -0,0 +1,27 @@
# frozen_string_literal: true
module Resolvers
class GroupPackagesResolver < BaseResolver
type Types::Packages::PackageType.connection_type, null: true
def ready?(**args)
context[self.class] ||= { executions: 0 }
context[self.class][:executions] += 1
raise GraphQL::ExecutionError, "Packages can be requested only for one group at a time" if context[self.class][:executions] > 1
super
end
def resolve(**args)
return unless packages_available?
::Packages::GroupPackagesFinder.new(current_user, object).execute
end
private
def packages_available?
::Gitlab.config.packages.enabled
end
end
end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
module Resolvers
class PackagesResolver < BaseResolver
class ProjectPackagesResolver < BaseResolver
type Types::Packages::PackageType.connection_type, null: true
def resolve(**args)

View file

@ -95,6 +95,10 @@ module Types
field :container_repositories_count, GraphQL::INT_TYPE, null: false,
description: 'Number of container repositories in the group.'
field :packages,
description: 'Packages of the group.',
resolver: Resolvers::GroupPackagesResolver
def label(title:)
BatchLoader::GraphQL.for(title).batch(key: group) do |titles, loader, args|
LabelsFinder

View file

@ -181,7 +181,7 @@ module Types
field :packages,
description: 'Packages of the project.',
resolver: Resolvers::PackagesResolver
resolver: Resolvers::ProjectPackagesResolver
field :pipelines,
null: true,

View file

@ -9,7 +9,7 @@
= form_for @hook, as: :hook, url: admin_hook_path do |f|
= render partial: 'form', locals: { form: f, hook: @hook }
.form-actions
%span>= f.submit _('Save changes'), class: 'btn gl-button btn-success gl-mr-3'
%span>= f.submit _('Save changes'), class: 'btn gl-button btn-confirm gl-mr-3'
= render 'shared/web_hooks/test_button', hook: @hook
= link_to _('Delete'), admin_hook_path(@hook), method: :delete, class: 'btn gl-button btn-danger float-right', data: { confirm: _('Are you sure?') }

View file

@ -7,7 +7,7 @@
.col-lg-8.gl-mb-3
= form_for @hook, as: :hook, url: admin_hooks_path do |f|
= render partial: 'form', locals: { form: f, hook: @hook }
= f.submit _('Add system hook'), class: 'btn gl-button btn-success'
= f.submit _('Add system hook'), class: 'btn gl-button btn-confirm'
= render 'shared/web_hooks/index', hooks: @hooks, hook_class: @hook.class

View file

@ -0,0 +1,5 @@
---
title: Add package list to group graphql type
merge_request: 54672
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: Move to btn-confirm from btn-success in admin/hooks directory
merge_request: 55272
author: Yogi (@yo)
type: changed

View file

@ -2215,6 +2215,7 @@ Autogenerated return type of GitlabSubscriptionActivate.
| `milestones` | MilestoneConnection | Milestones of the group. |
| `name` | String! | Name of the namespace. |
| `packageSettings` | PackageSettings | The package settings for the namespace. |
| `packages` | PackageConnection | Packages of the group. |
| `parent` | Group | Parent group. |
| `path` | String! | Path of the namespace. |
| `projectCreationLevel` | String | The permission level required to create projects in the group. |

View file

@ -22283,6 +22283,9 @@ msgstr ""
msgid "Pipelines|parent"
msgstr ""
msgid "Pipeline|Actions"
msgstr ""
msgid "Pipeline|Branch name"
msgstr ""

View file

@ -14,11 +14,11 @@ module QA
end
def has_registry_repository?(name)
find('a[data-testid="details-link"]', text: name)
find_element(:registry_image_content, text: name)
end
def click_on_image(name)
find('a[data-testid="details-link"]', text: name).click
click_element(:registry_image_content, text: name)
end
def has_tag?(tag_name)
@ -30,7 +30,7 @@ module QA
end
def click_delete
find('[data-testid="single-delete-button"]').click
click_element(:tag_delete_button)
find_button('Confirm').click
end
end

View file

@ -6,12 +6,13 @@ import ProjectsTokenSelector from '~/access_tokens/components/projects_token_sel
describe('ProjectsField', () => {
let wrapper;
const createComponent = () => {
const createComponent = ({ inputAttrsValue = '' } = {}) => {
wrapper = mount(ProjectsField, {
propsData: {
inputAttrs: {
id: 'projects',
name: 'projects',
value: inputAttrsValue,
},
},
});
@ -24,39 +25,63 @@ describe('ProjectsField', () => {
const findProjectsTokenSelector = () => wrapper.findComponent(ProjectsTokenSelector);
const findHiddenInput = () => wrapper.find('input[type="hidden"]');
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('renders label and sub-label', () => {
createComponent();
expect(queryByText('Projects')).not.toBe(null);
expect(queryByText('Set access permissions for this token.')).not.toBe(null);
});
it('renders "All projects" radio selected by default', () => {
const allProjectsRadio = findAllProjectsRadio();
describe('when `inputAttrs.value` is empty', () => {
beforeEach(() => {
createComponent();
});
expect(allProjectsRadio).not.toBe(null);
expect(allProjectsRadio.checked).toBe(true);
it('renders "All projects" radio as checked', () => {
expect(findAllProjectsRadio().checked).toBe(true);
});
it('renders "Selected projects" radio as unchecked', () => {
expect(findSelectedProjectsRadio().checked).toBe(false);
});
it('sets `projects-token-selector` `initialProjectIds` prop to an empty array', () => {
expect(findProjectsTokenSelector().props('initialProjectIds')).toEqual([]);
});
});
it('renders "Selected projects" radio unchecked by default', () => {
const selectedProjectsRadio = findSelectedProjectsRadio();
describe('when `inputAttrs.value` is a comma separated list of project IDs', () => {
beforeEach(() => {
createComponent({ inputAttrsValue: '1,2' });
});
expect(selectedProjectsRadio).not.toBe(null);
expect(selectedProjectsRadio.checked).toBe(false);
it('renders "All projects" radio as unchecked', () => {
expect(findAllProjectsRadio().checked).toBe(false);
});
it('renders "Selected projects" radio as checked', () => {
expect(findSelectedProjectsRadio().checked).toBe(true);
});
it('sets `projects-token-selector` `initialProjectIds` prop to an array of project IDs', () => {
expect(findProjectsTokenSelector().props('initialProjectIds')).toEqual(['1', '2']);
});
});
it('renders `projects-token-selector` component', () => {
createComponent();
expect(findProjectsTokenSelector().exists()).toBe(true);
});
it('renders hidden input with correct `name` and `id` attributes', () => {
createComponent();
expect(findHiddenInput().attributes()).toEqual(
expect.objectContaining({
id: 'projects',
@ -67,6 +92,8 @@ describe('ProjectsField', () => {
describe('when `projects-token-selector` is focused', () => {
beforeEach(() => {
createComponent();
findProjectsTokenSelector().vm.$emit('focus');
});

View file

@ -44,10 +44,15 @@ describe('ProjectsTokenSelector', () => {
let wrapper;
let resolveGetProjectsQuery;
let resolveGetInitialProjectsQuery;
const getProjectsQueryRequestHandler = jest.fn(
() =>
({ ids }) =>
new Promise((resolve) => {
resolveGetProjectsQuery = resolve;
if (ids) {
resolveGetInitialProjectsQuery = resolve;
} else {
resolveGetProjectsQuery = resolve;
}
}),
);
@ -63,6 +68,7 @@ describe('ProjectsTokenSelector', () => {
apolloProvider,
propsData: {
selectedProjects: [],
initialProjectIds: [],
...propsData,
},
stubs: ['gl-intersection-observer'],
@ -156,6 +162,7 @@ describe('ProjectsTokenSelector', () => {
search: searchTerm,
after: null,
first: 20,
ids: null,
});
});
@ -181,6 +188,7 @@ describe('ProjectsTokenSelector', () => {
after: pageInfo.endCursor,
first: 20,
search: '',
ids: null,
});
});
@ -221,4 +229,41 @@ describe('ProjectsTokenSelector', () => {
expect(wrapper.emitted('focus')[0]).toEqual([event]);
});
});
describe('when `initialProjectIds` is an empty array', () => {
it('does not request initial projects', async () => {
await createComponent();
expect(getProjectsQueryRequestHandler).toHaveBeenCalledTimes(1);
expect(getProjectsQueryRequestHandler).toHaveBeenCalledWith(
expect.objectContaining({
ids: null,
}),
);
});
});
describe('when `initialProjectIds` is an array of project IDs', () => {
it('requests those projects and emits `input` event with result', async () => {
await createComponent({
propsData: {
initialProjectIds: [getIdFromGraphQLId(project1.id), getIdFromGraphQLId(project2.id)],
},
});
resolveGetInitialProjectsQuery(getProjectsQueryResponse);
await waitForPromises();
expect(getProjectsQueryRequestHandler).toHaveBeenCalledWith({
after: '',
first: null,
search: '',
ids: [project1.id, project2.id],
});
expect(wrapper.emitted('input')[0][0]).toEqual([
{ ...project1, id: getIdFromGraphQLId(project1.id) },
{ ...project2, id: getIdFromGraphQLId(project2.id) },
]);
});
});
});

View file

@ -38,6 +38,7 @@ describe('access tokens', () => {
input.setAttribute('name', 'foo-bar');
input.setAttribute('id', 'foo-bar');
input.setAttribute('placeholder', 'Foo bar');
input.setAttribute('value', '1,2');
mountEl.appendChild(input);
@ -58,6 +59,7 @@ describe('access tokens', () => {
expect(component.props('inputAttrs')).toEqual({
name: 'foo-bar',
id: 'foo-bar',
value: '1,2',
placeholder: 'Foo bar',
});
});

View file

@ -1,10 +1,10 @@
import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import component from '~/jobs/components/jobs_container.vue';
import { GlLink } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import JobsContainer from '~/jobs/components/jobs_container.vue';
describe('Jobs List block', () => {
const Component = Vue.extend(component);
let vm;
let wrapper;
const retried = {
status: {
@ -52,80 +52,96 @@ describe('Jobs List block', () => {
tooltip: 'build - passed',
};
const findAllJobs = () => wrapper.findAllComponents(GlLink);
const findJob = () => findAllJobs().at(0);
const findArrowIcon = () => wrapper.findByTestId('arrow-right-icon');
const findRetryIcon = () => wrapper.findByTestId('retry-icon');
const createComponent = (props) => {
wrapper = extendedWrapper(
mount(JobsContainer, {
propsData: {
...props,
},
}),
);
};
afterEach(() => {
vm.$destroy();
wrapper.destroy();
});
it('renders list of jobs', () => {
vm = mountComponent(Component, {
it('renders a list of jobs', () => {
createComponent({
jobs: [job, retried, active],
jobId: 12313,
});
expect(vm.$el.querySelectorAll('a').length).toEqual(3);
expect(findAllJobs()).toHaveLength(3);
});
it('renders arrow right when job id matches `jobId`', () => {
vm = mountComponent(Component, {
it('renders the arrow right icon when job id matches `jobId`', () => {
createComponent({
jobs: [active],
jobId: active.id,
});
expect(vm.$el.querySelector('a .js-arrow-right')).not.toBeNull();
expect(findArrowIcon().exists()).toBe(true);
});
it('does not render arrow right when job is not active', () => {
vm = mountComponent(Component, {
it('does not render the arrow right icon when the job is not active', () => {
createComponent({
jobs: [job],
jobId: active.id,
});
expect(vm.$el.querySelector('a .js-arrow-right')).toBeNull();
expect(findArrowIcon().exists()).toBe(false);
});
it('renders job name when present', () => {
vm = mountComponent(Component, {
it('renders the job name when present', () => {
createComponent({
jobs: [job],
jobId: active.id,
});
expect(vm.$el.querySelector('a').textContent.trim()).toContain(job.name);
expect(vm.$el.querySelector('a').textContent.trim()).not.toContain(job.id);
expect(findJob().text()).toBe(job.name);
expect(findJob().text()).not.toContain(job.id);
});
it('renders job id when job name is not available', () => {
vm = mountComponent(Component, {
createComponent({
jobs: [retried],
jobId: active.id,
});
expect(vm.$el.querySelector('a').textContent.trim()).toContain(retried.id);
expect(findJob().text()).toBe(retried.id.toString());
});
it('links to the job page', () => {
vm = mountComponent(Component, {
createComponent({
jobs: [job],
jobId: active.id,
});
expect(vm.$el.querySelector('a').getAttribute('href')).toEqual(job.status.details_path);
expect(findJob().attributes('href')).toBe(job.status.details_path);
});
it('renders retry icon when job was retried', () => {
vm = mountComponent(Component, {
createComponent({
jobs: [retried],
jobId: active.id,
});
expect(vm.$el.querySelector('.js-retry-icon')).not.toBeNull();
expect(findRetryIcon().exists()).toBe(true);
});
it('does not render retry icon when job was not retried', () => {
vm = mountComponent(Component, {
createComponent({
jobs: [job],
jobId: active.id,
});
expect(vm.$el.querySelector('.js-retry-icon')).toBeNull();
expect(findRetryIcon().exists()).toBe(false);
});
});

View file

@ -117,9 +117,7 @@ describe('Pipelines Table', () => {
expect(findCommitTh().text()).toBe('Commit');
expect(findStagesTh().text()).toBe('Stages');
expect(findTimeAgoTh().text()).toBe('Duration');
// last column should have no text in th
expect(findActionsTh().text()).toBe('');
expect(findActionsTh().text()).toBe('Actions');
});
it('should display a table row', () => {

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::GroupPackagesResolver do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :public, group: group) }
let_it_be(:package) { create(:package, project: project) }
describe '#resolve' do
subject(:packages) { resolve(described_class, ctx: { current_user: user }, obj: group) }
it { is_expected.to contain_exactly(package) }
end
end

View file

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Resolvers::PackagesResolver do
RSpec.describe Resolvers::ProjectPackagesResolver do
include GraphqlHelpers
let_it_be(:user) { create(:user) }

View file

@ -18,6 +18,7 @@ RSpec.describe GitlabSchema.types['Group'] do
two_factor_grace_period auto_devops_enabled emails_disabled
mentions_disabled parent boards milestones group_members
merge_requests container_repositories container_repositories_count
packages
]
expect(described_class).to include_graphql_fields(*expected_fields)

View file

@ -0,0 +1,78 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'getting a package list for a group' do
include GraphqlHelpers
let_it_be(:resource) { create(:group, :private) }
let_it_be(:group_two) { create(:group, :private) }
let_it_be(:project) { create(:project, :repository, group: resource) }
let_it_be(:another_project) { create(:project, :repository, group: resource) }
let_it_be(:group_two_project) { create(:project, :repository, group: group_two) }
let_it_be(:current_user) { create(:user) }
let_it_be(:package) { create(:package, project: project) }
let_it_be(:npm_package) { create(:npm_package, project: group_two_project) }
let_it_be(:maven_package) { create(:maven_package, project: project) }
let_it_be(:debian_package) { create(:debian_package, project: another_project) }
let_it_be(:composer_package) { create(:composer_package, project: another_project) }
let_it_be(:composer_metadatum) do
create(:composer_metadatum, package: composer_package,
target_sha: 'afdeh',
composer_json: { name: 'x', type: 'y', license: 'z', version: 1 })
end
let(:package_names) { graphql_data_at(:group, :packages, :nodes, :name) }
let(:target_shas) { graphql_data_at(:group, :packages, :nodes, :metadata, :target_sha) }
let(:packages) { graphql_data_at(:group, :packages, :nodes) }
let(:fields) do
<<~QUERY
nodes {
#{all_graphql_fields_for('packages'.classify, excluded: ['project'])}
metadata { #{query_graphql_fragment('ComposerMetadata')} }
}
QUERY
end
let(:query) do
graphql_query_for(
'group',
{ 'fullPath' => resource.full_path },
query_graphql_field('packages', {}, fields)
)
end
it_behaves_like 'group and project packages query'
context 'with a batched query' do
let(:batch_query) do
<<~QUERY
{
a: group(fullPath: "#{resource.full_path}") { packages { nodes { name } } }
b: group(fullPath: "#{group_two.full_path}") { packages { nodes { name } } }
}
QUERY
end
let(:a_packages_names) { graphql_data_at(:a, :packages, :nodes, :name) }
before do
resource.add_reporter(current_user)
group_two.add_reporter(current_user)
post_graphql(batch_query, current_user: current_user)
end
it 'returns an error for the second group and data for the first' do
expect(a_packages_names).to contain_exactly(
package.name,
maven_package.name,
debian_package.name,
composer_package.name
)
expect_graphql_errors_to_include [/Packages can be requested only for one group at a time/]
expect(graphql_data_at(:b, :packages)).to be(nil)
end
end
end

View file

@ -5,28 +5,28 @@ require 'spec_helper'
RSpec.describe 'getting a package list for a project' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:resource) { create(:project, :repository) }
let_it_be(:current_user) { create(:user) }
let_it_be(:package) { create(:package, project: project) }
let_it_be(:maven_package) { create(:maven_package, project: project) }
let_it_be(:debian_package) { create(:debian_package, project: project) }
let_it_be(:composer_package) { create(:composer_package, project: project) }
let_it_be(:package) { create(:package, project: resource) }
let_it_be(:maven_package) { create(:maven_package, project: resource) }
let_it_be(:debian_package) { create(:debian_package, project: resource) }
let_it_be(:composer_package) { create(:composer_package, project: resource) }
let_it_be(:composer_metadatum) do
create(:composer_metadatum, package: composer_package,
target_sha: 'afdeh',
composer_json: { name: 'x', type: 'y', license: 'z', version: 1 })
end
let(:package_names) { graphql_data_at(:project, :packages, :edges, :node, :name) }
let(:package_names) { graphql_data_at(:project, :packages, :nodes, :name) }
let(:target_shas) { graphql_data_at(:project, :packages, :nodes, :metadata, :target_sha) }
let(:packages) { graphql_data_at(:project, :packages, :nodes) }
let(:fields) do
<<~QUERY
edges {
node {
#{all_graphql_fields_for('packages'.classify, excluded: ['project'])}
metadata { #{query_graphql_fragment('ComposerMetadata')} }
}
nodes {
#{all_graphql_fields_for('packages'.classify, excluded: ['project'])}
metadata { #{query_graphql_fragment('ComposerMetadata')} }
}
QUERY
end
@ -34,55 +34,10 @@ RSpec.describe 'getting a package list for a project' do
let(:query) do
graphql_query_for(
'project',
{ 'fullPath' => project.full_path },
{ 'fullPath' => resource.full_path },
query_graphql_field('packages', {}, fields)
)
end
context 'when user has access to the project' do
before do
project.add_reporter(current_user)
post_graphql(query, current_user: current_user)
end
it_behaves_like 'a working graphql query'
it 'returns packages successfully' do
expect(package_names).to contain_exactly(
package.name,
maven_package.name,
debian_package.name,
composer_package.name
)
end
it 'deals with metadata' do
target_shas = graphql_data_at(:project, :packages, :edges, :node, :metadata, :target_sha)
expect(target_shas).to contain_exactly(composer_metadatum.target_sha)
end
end
context 'when the user does not have access to the project/packages' do
before do
post_graphql(query, current_user: current_user)
end
it_behaves_like 'a working graphql query'
it 'returns nil' do
expect(graphql_data['project']).to be_nil
end
end
context 'when the user is not authenticated' do
before do
post_graphql(query)
end
it_behaves_like 'a working graphql query'
it 'returns nil' do
expect(graphql_data['project']).to be_nil
end
end
it_behaves_like 'group and project packages query'
end

View file

@ -0,0 +1,51 @@
# frozen_string_literal: true
RSpec.shared_examples 'group and project packages query' do
include GraphqlHelpers
context 'when user has access to the resource' do
before do
resource.add_reporter(current_user)
post_graphql(query, current_user: current_user)
end
it_behaves_like 'a working graphql query'
it 'returns packages successfully' do
expect(package_names).to contain_exactly(
package.name,
maven_package.name,
debian_package.name,
composer_package.name
)
end
it 'deals with metadata' do
expect(target_shas).to contain_exactly(composer_metadatum.target_sha)
end
end
context 'when the user does not have access to the resource' do
before do
post_graphql(query, current_user: current_user)
end
it_behaves_like 'a working graphql query'
it 'returns nil' do
expect(packages).to be_nil
end
end
context 'when the user is not authenticated' do
before do
post_graphql(query)
end
it_behaves_like 'a working graphql query'
it 'returns nil' do
expect(packages).to be_nil
end
end
end