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>2023-11-08 18:07:19 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-11-08 18:07:19 +0300
commita34d7fd9a723d6cc9c7348be2afe522bdc2be67f (patch)
tree5971e13ca0832ae06c599b3d5eec2e2fe71d884f /spec
parent5f89187f0433fc84d8387de25220185235d61ed1 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb68
-rw-r--r--spec/finders/organizations/user_organizations_finder_spec.rb50
-rw-r--r--spec/frontend/analytics/product_analytics/components/activity_chart_spec.js34
-rw-r--r--spec/frontend/import/details/components/import_details_table_spec.js66
-rw-r--r--spec/frontend/projects/settings_service_desk/components/custom_email_form_spec.js34
-rw-r--r--spec/graphql/resolvers/users/frecent_groups_resolver_spec.rb7
-rw-r--r--spec/graphql/resolvers/users/frecent_projects_resolver_spec.rb7
-rw-r--r--spec/migrations/db/migrate/20231103162825_add_wolfi_purl_type_to_package_metadata_purl_types_spec.rb35
-rw-r--r--spec/models/concerns/enums/sbom_spec.rb1
-rw-r--r--spec/models/users/group_visit_spec.rb25
-rw-r--r--spec/models/users/project_visit_spec.rb25
-rw-r--r--spec/policies/group_policy_spec.rb107
-rw-r--r--spec/policies/user_policy_spec.rb26
-rw-r--r--spec/requests/api/graphql/user_spec.rb32
-rw-r--r--spec/requests/jwt_controller_spec.rb12
-rw-r--r--spec/services/auth/dependency_proxy_authentication_service_spec.rb77
-rw-r--r--spec/services/service_desk/custom_emails/create_service_spec.rb29
-rw-r--r--spec/support/shared_contexts/graphql/types/query_type_shared_context.rb2
-rw-r--r--spec/support/shared_examples/graphql/resolvers/users/pages_visits_resolvers_shared_examples.rb39
-rw-r--r--spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/models/users/pages_visits_shared_examples.rb104
21 files changed, 677 insertions, 104 deletions
diff --git a/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb b/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb
index 6bb791d2fd4..810c773de00 100644
--- a/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb
+++ b/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb
@@ -60,6 +60,42 @@ RSpec.describe Groups::DependencyProxyForContainersController, feature_category:
it { is_expected.to have_gitlab_http_status(:not_found) }
end
+ context 'with invalid group access token' do
+ let_it_be(:user) { create(:user, :project_bot) }
+
+ context 'not under the group' do
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+
+ context 'with sufficient scopes, but not active' do
+ context 'expired' do
+ let_it_be(:pat) do
+ create(:personal_access_token, :expired, user: user).tap do |pat|
+ pat.update_column(:scopes, Gitlab::Auth::REGISTRY_SCOPES)
+ end
+ end
+
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+
+ context 'revoked' do
+ let_it_be(:pat) do
+ create(:personal_access_token, :revoked, user: user).tap do |pat|
+ pat.update_column(:scopes, Gitlab::Auth::REGISTRY_SCOPES)
+ end
+ end
+
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+ end
+
+ context 'with insufficient scopes' do
+ let_it_be(:pat) { create(:personal_access_token, user: user, scopes: [Gitlab::Auth::READ_API_SCOPE]) }
+
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+ end
+
context 'with deploy token from a different group,' do
let_it_be(:user) { create(:deploy_token, :group, :dependency_proxy_scopes) }
@@ -119,11 +155,7 @@ RSpec.describe Groups::DependencyProxyForContainersController, feature_category:
end
shared_examples 'authorize action with permission' do
- context 'with a valid user' do
- before do
- group.add_guest(user)
- end
-
+ shared_examples 'sends Workhorse instructions' do
it 'sends Workhorse local file instructions', :aggregate_failures do
subject
@@ -144,6 +176,32 @@ RSpec.describe Groups::DependencyProxyForContainersController, feature_category:
expect(json_response['MaximumSize']).to eq(maximum_size)
end
end
+
+ before do
+ group.add_guest(user)
+ end
+
+ context 'with a valid user' do
+ it_behaves_like 'sends Workhorse instructions'
+ end
+
+ context 'with a valid group access token' do
+ let_it_be(:user) { create(:user, :project_bot) }
+ let_it_be_with_reload(:token) { create(:personal_access_token, user: user) }
+
+ before do
+ token.update_column(:scopes, Gitlab::Auth::REGISTRY_SCOPES)
+ end
+
+ it_behaves_like 'sends Workhorse instructions'
+ end
+
+ context 'with a deploy token' do
+ let_it_be(:user) { create(:deploy_token, :dependency_proxy_scopes, :group) }
+ let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
+
+ it_behaves_like 'sends Workhorse instructions'
+ end
end
shared_examples 'namespace statistics refresh' do
diff --git a/spec/finders/organizations/user_organizations_finder_spec.rb b/spec/finders/organizations/user_organizations_finder_spec.rb
new file mode 100644
index 00000000000..71c9b861831
--- /dev/null
+++ b/spec/finders/organizations/user_organizations_finder_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Organizations::UserOrganizationsFinder, '#execute', feature_category: :cell do
+ let_it_be(:admin) { create(:user, :admin) }
+ let_it_be(:organization_user) { create(:organization_user) }
+ let_it_be(:organization) { organization_user.organization }
+ let_it_be(:another_organization) { create(:organization) }
+ let_it_be(:another_user) { create(:user) }
+
+ let(:current_user) { organization_user.user }
+ let(:target_user) { organization_user.user }
+
+ subject(:finder) { described_class.new(current_user, target_user).execute }
+
+ context 'when the current user has access to the organization' do
+ it { is_expected.to contain_exactly(organization) }
+ end
+
+ context 'when the current user is an admin' do
+ let(:current_user) { admin }
+
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it { is_expected.to contain_exactly(organization) }
+ end
+
+ context 'when admin mode is disabled' do
+ it { is_expected.to be_empty }
+ end
+ end
+
+ context 'when the current user does not access to the organization' do
+ let(:current_user) { another_user }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'when the current user is nil' do
+ let(:current_user) { nil }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'when the target user is nil' do
+ let(:target_user) { nil }
+
+ it { is_expected.to be_empty }
+ end
+end
diff --git a/spec/frontend/analytics/product_analytics/components/activity_chart_spec.js b/spec/frontend/analytics/product_analytics/components/activity_chart_spec.js
deleted file mode 100644
index 4f8126aaacf..00000000000
--- a/spec/frontend/analytics/product_analytics/components/activity_chart_spec.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import { GlColumnChart } from '@gitlab/ui/dist/charts';
-import { shallowMount } from '@vue/test-utils';
-import ActivityChart from '~/analytics/product_analytics/components/activity_chart.vue';
-
-describe('Activity Chart Bundle', () => {
- let wrapper;
- function mountComponent({ provide }) {
- wrapper = shallowMount(ActivityChart, {
- provide: {
- formattedData: {},
- ...provide,
- },
- });
- }
-
- const findChart = () => wrapper.findComponent(GlColumnChart);
- const findNoData = () => wrapper.find('[data-testid="noActivityChartData"]');
-
- describe('Activity Chart', () => {
- it('renders an warning message with no data', () => {
- mountComponent({ provide: { formattedData: {} } });
- expect(findNoData().exists()).toBe(true);
- });
-
- it('renders a chart with data', () => {
- mountComponent({
- provide: { formattedData: { keys: ['key1', 'key2'], values: [5038, 2241] } },
- });
-
- expect(findNoData().exists()).toBe(false);
- expect(findChart().exists()).toBe(true);
- });
- });
-});
diff --git a/spec/frontend/import/details/components/import_details_table_spec.js b/spec/frontend/import/details/components/import_details_table_spec.js
index aee8573eb02..e2ba0ddad17 100644
--- a/spec/frontend/import/details/components/import_details_table_spec.js
+++ b/spec/frontend/import/details/components/import_details_table_spec.js
@@ -2,6 +2,7 @@ import { mount, shallowMount } from '@vue/test-utils';
import { GlEmptyState, GlLoadingIcon, GlTable } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
+import { getParameterValues } from '~/lib/utils/url_utility';
import { HTTP_STATUS_OK, HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status';
import { createAlert } from '~/alert';
import waitForPromises from 'helpers/wait_for_promises';
@@ -11,13 +12,32 @@ import ImportDetailsTable from '~/import/details/components/import_details_table
import { mockImportFailures, mockHeaders } from '../mock_data';
jest.mock('~/alert');
+jest.mock('~/lib/utils/url_utility', () => ({
+ ...jest.requireActual('~/lib/utils/url_utility'),
+ getParameterValues: jest.fn().mockReturnValue([]),
+}));
describe('Import details table', () => {
let wrapper;
let mock;
- const createComponent = ({ mountFn = shallowMount, provide = {} } = {}) => {
+ const mockFields = [
+ {
+ key: 'type',
+ label: 'Type',
+ },
+ {
+ key: 'title',
+ label: 'Title',
+ },
+ ];
+
+ const createComponent = ({ mountFn = shallowMount, props = {}, provide = {} } = {}) => {
wrapper = mountFn(ImportDetailsTable, {
+ propsData: {
+ fields: mockFields,
+ ...props,
+ },
provide,
});
};
@@ -109,5 +129,49 @@ describe('Import details table', () => {
});
});
});
+
+ describe('when bulk_import is true', () => {
+ const mockId = 144;
+ const mockEntityId = 68;
+
+ beforeEach(() => {
+ gon.api_version = 'v4';
+ getParameterValues.mockReturnValueOnce([mockId]);
+ getParameterValues.mockReturnValueOnce([mockEntityId]);
+
+ mock
+ .onGet(`/api/v4/bulk_imports/${mockId}/entities/${mockEntityId}/failures`)
+ .reply(HTTP_STATUS_OK, mockImportFailures, mockHeaders);
+
+ createComponent({
+ mountFn: mount,
+ props: {
+ bulkImport: true,
+ },
+ });
+ });
+
+ it('renders loading icon', () => {
+ expect(findGlLoadingIcon().exists()).toBe(true);
+ });
+
+ it('does not render loading icon after fetch', async () => {
+ await waitForPromises();
+
+ expect(findGlLoadingIcon().exists()).toBe(false);
+ });
+
+ it('sets items and pagination info', async () => {
+ await waitForPromises();
+
+ expect(findGlTableRows().length).toBe(mockImportFailures.length);
+ expect(findPaginationBar().props('pageInfo')).toMatchObject({
+ page: mockHeaders['x-page'],
+ perPage: mockHeaders['x-per-page'],
+ total: mockHeaders['x-total'],
+ totalPages: mockHeaders['x-total-pages'],
+ });
+ });
+ });
});
});
diff --git a/spec/frontend/projects/settings_service_desk/components/custom_email_form_spec.js b/spec/frontend/projects/settings_service_desk/components/custom_email_form_spec.js
index 9b012995ea4..efefcdb20df 100644
--- a/spec/frontend/projects/settings_service_desk/components/custom_email_form_spec.js
+++ b/spec/frontend/projects/settings_service_desk/components/custom_email_form_spec.js
@@ -1,11 +1,17 @@
import { mount } from '@vue/test-utils';
-import { GlLink } from '@gitlab/ui';
+import { GlFormSelect, GlLink } from '@gitlab/ui';
import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { helpPagePath } from '~/helpers/help_page_helper';
import CustomEmailForm from '~/projects/settings_service_desk/components/custom_email_form.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import { I18N_FORM_FORWARDING_CLIPBOARD_BUTTON_TITLE } from '~/projects/settings_service_desk/custom_email_constants';
+import {
+ I18N_FORM_FORWARDING_CLIPBOARD_BUTTON_TITLE,
+ I18N_FORM_SMTP_AUTHENTICATION_NONE,
+ I18N_FORM_SMTP_AUTHENTICATION_PLAIN,
+ I18N_FORM_SMTP_AUTHENTICATION_LOGIN,
+ I18N_FORM_SMTP_AUTHENTICATION_CRAM_MD5,
+} from '~/projects/settings_service_desk/custom_email_constants';
describe('CustomEmailForm', () => {
let wrapper;
@@ -24,6 +30,7 @@ describe('CustomEmailForm', () => {
const findSmtpPortInput = () => findInputByTestId('form-smtp-port');
const findSmtpUsernameInput = () => findInputByTestId('form-smtp-username');
const findSmtpPasswordInput = () => findInputByTestId('form-smtp-password');
+ const findSmtpAuthenticationSelect = () => wrapper.findComponent(GlFormSelect).find('select');
const findSubmit = () => wrapper.findByTestId('form-submit');
const clickButtonAndExpectNoSubmitEvent = async () => {
@@ -60,6 +67,23 @@ describe('CustomEmailForm', () => {
);
});
+ it('renders correct translations for options for SMTP authentication', () => {
+ createWrapper();
+
+ const translationStrings = [
+ I18N_FORM_SMTP_AUTHENTICATION_NONE,
+ I18N_FORM_SMTP_AUTHENTICATION_PLAIN,
+ I18N_FORM_SMTP_AUTHENTICATION_LOGIN,
+ I18N_FORM_SMTP_AUTHENTICATION_CRAM_MD5,
+ ];
+
+ findSmtpAuthenticationSelect()
+ .findAll('option')
+ .wrappers.forEach((item, index) => {
+ expect(item.text()).toEqual(translationStrings[index]);
+ });
+ });
+
it('form inputs are disabled when submitting', () => {
createWrapper({ isSubmitting: true });
@@ -68,6 +92,7 @@ describe('CustomEmailForm', () => {
expect(findSmtpPortInput().attributes('disabled')).toBeDefined();
expect(findSmtpUsernameInput().attributes('disabled')).toBeDefined();
expect(findSmtpPasswordInput().attributes('disabled')).toBeDefined();
+ expect(findSmtpAuthenticationSelect().attributes('disabled')).toBeDefined();
expect(findSubmit().props('loading')).toBe(true);
});
@@ -99,6 +124,8 @@ describe('CustomEmailForm', () => {
findSmtpPasswordInput().setValue('supersecret');
findSmtpPasswordInput().trigger('change');
+
+ findSmtpAuthenticationSelect().setValue('login');
});
it('is invalid when malformed email provided', async () => {
@@ -200,9 +227,10 @@ describe('CustomEmailForm', () => {
{
custom_email: 'user@example.com',
smtp_address: 'smtp.example.com',
+ smtp_username: 'user@example.com',
smtp_password: 'supersecret',
smtp_port: '587',
- smtp_username: 'user@example.com',
+ smtp_authentication: 'login',
},
],
]);
diff --git a/spec/graphql/resolvers/users/frecent_groups_resolver_spec.rb b/spec/graphql/resolvers/users/frecent_groups_resolver_spec.rb
new file mode 100644
index 00000000000..8836ba73110
--- /dev/null
+++ b/spec/graphql/resolvers/users/frecent_groups_resolver_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::Users::FrecentGroupsResolver, feature_category: :navigation do
+ it_behaves_like 'namespace visits resolver'
+end
diff --git a/spec/graphql/resolvers/users/frecent_projects_resolver_spec.rb b/spec/graphql/resolvers/users/frecent_projects_resolver_spec.rb
new file mode 100644
index 00000000000..30a0a7d93b3
--- /dev/null
+++ b/spec/graphql/resolvers/users/frecent_projects_resolver_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::Users::FrecentProjectsResolver, feature_category: :navigation do
+ it_behaves_like 'namespace visits resolver'
+end
diff --git a/spec/migrations/db/migrate/20231103162825_add_wolfi_purl_type_to_package_metadata_purl_types_spec.rb b/spec/migrations/db/migrate/20231103162825_add_wolfi_purl_type_to_package_metadata_purl_types_spec.rb
new file mode 100644
index 00000000000..43ce53fffcb
--- /dev/null
+++ b/spec/migrations/db/migrate/20231103162825_add_wolfi_purl_type_to_package_metadata_purl_types_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe AddWolfiPurlTypeToPackageMetadataPurlTypes, feature_category: :software_composition_analysis do
+ let(:settings) { table(:application_settings) }
+
+ describe "#up" do
+ it 'updates setting' do
+ settings.create!(package_metadata_purl_types: [1, 2, 4, 5, 9, 10])
+
+ disable_migrations_output do
+ migrate!
+ end
+
+ expect(ApplicationSetting.last.package_metadata_purl_types).to eq([1, 2, 4, 5, 9, 10, 13])
+ end
+ end
+
+ describe "#down" do
+ context 'with default value' do
+ it 'updates setting' do
+ settings.create!(package_metadata_purl_types: [1, 2, 4, 5, 9, 10, 13])
+
+ disable_migrations_output do
+ migrate!
+ schema_migrate_down!
+ end
+
+ expect(ApplicationSetting.last.package_metadata_purl_types).to eq([1, 2, 4, 5, 9, 10])
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/enums/sbom_spec.rb b/spec/models/concerns/enums/sbom_spec.rb
index 41670880630..4cfd50b6c8a 100644
--- a/spec/models/concerns/enums/sbom_spec.rb
+++ b/spec/models/concerns/enums/sbom_spec.rb
@@ -23,6 +23,7 @@ RSpec.describe Enums::Sbom, feature_category: :dependency_management do
:rpm | 10
:deb | 11
:cbl_mariner | 12
+ :wolfi | 13
'unknown-pkg-manager' | 0
'Python (unknown)' | 0
end
diff --git a/spec/models/users/group_visit_spec.rb b/spec/models/users/group_visit_spec.rb
index 63c4631ad7d..241cb537fad 100644
--- a/spec/models/users/group_visit_spec.rb
+++ b/spec/models/users/group_visit_spec.rb
@@ -7,10 +7,6 @@ RSpec.describe Users::GroupVisit, feature_category: :navigation do
let_it_be(:user) { create(:user) }
let_it_be(:base_time) { DateTime.now }
- before do
- described_class.create!(entity_id: entity.id, user_id: user.id, visited_at: base_time)
- end
-
it_behaves_like 'namespace visits model'
it_behaves_like 'cleanup by a loose foreign key' do
@@ -22,4 +18,25 @@ RSpec.describe Users::GroupVisit, feature_category: :navigation do
let!(:model) { create(:group_visit, entity_id: entity.id, user_id: user.id, visited_at: base_time) }
let!(:parent) { user }
end
+
+ describe '#frecent_groups' do
+ let_it_be(:group1) { create(:group) }
+ let_it_be(:group2) { create(:group) }
+
+ before do
+ [
+ [group1.id, 1.day.ago],
+ [group2.id, 2.days.ago]
+ ].each do |id, datetime|
+ described_class.create!(entity_id: id, user_id: user.id, visited_at: datetime)
+ end
+ end
+
+ it "returns the associated frecently visited groups" do
+ expect(described_class.frecent_groups(user_id: user.id)).to eq([
+ group1,
+ group2
+ ])
+ end
+ end
end
diff --git a/spec/models/users/project_visit_spec.rb b/spec/models/users/project_visit_spec.rb
index 38747bd6462..50e9ef02fd8 100644
--- a/spec/models/users/project_visit_spec.rb
+++ b/spec/models/users/project_visit_spec.rb
@@ -7,10 +7,6 @@ RSpec.describe Users::ProjectVisit, feature_category: :navigation do
let_it_be(:user) { create(:user) }
let_it_be(:base_time) { DateTime.now }
- before do
- described_class.create!(entity_id: entity.id, user_id: user.id, visited_at: base_time)
- end
-
it_behaves_like 'namespace visits model'
it_behaves_like 'cleanup by a loose foreign key' do
@@ -22,4 +18,25 @@ RSpec.describe Users::ProjectVisit, feature_category: :navigation do
let!(:model) { create(:project_visit, entity_id: entity.id, user_id: user.id, visited_at: base_time) }
let!(:parent) { user }
end
+
+ describe '#frecent_projects' do
+ let_it_be(:project1) { create(:project) }
+ let_it_be(:project2) { create(:project) }
+
+ before do
+ [
+ [project1.id, 1.day.ago],
+ [project2.id, 2.days.ago]
+ ].each do |id, datetime|
+ described_class.create!(entity_id: id, user_id: user.id, visited_at: datetime)
+ end
+ end
+
+ it "returns the associated frecently visited projects" do
+ expect(described_class.frecent_projects(user_id: user.id)).to eq([
+ project1,
+ project2
+ ])
+ end
+ end
end
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index cb7884b141e..042dbb09436 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -1110,53 +1110,103 @@ RSpec.describe GroupPolicy, feature_category: :system_access do
it { is_expected.to be_allowed(:admin_dependency_proxy) }
end
+ shared_examples 'disallows all dependency proxy access' do
+ it { is_expected.to be_disallowed(:read_dependency_proxy) }
+ it { is_expected.to be_disallowed(:admin_dependency_proxy) }
+ end
+
+ shared_examples 'allows dependency proxy read access but not admin' do
+ it { is_expected.to be_allowed(:read_dependency_proxy) }
+ it { is_expected.to be_disallowed(:admin_dependency_proxy) }
+ end
+
context 'feature disabled' do
let(:current_user) { owner }
- it { is_expected.to be_disallowed(:read_dependency_proxy) }
- it { is_expected.to be_disallowed(:admin_dependency_proxy) }
+ before do
+ stub_config(dependency_proxy: { enabled: false })
+ end
+
+ it_behaves_like 'disallows all dependency proxy access'
end
context 'feature enabled' do
before do
- stub_config(dependency_proxy: { enabled: true })
+ stub_config(dependency_proxy: { enabled: true }, registry: { enabled: true })
end
- context 'reporter' do
- let(:current_user) { reporter }
+ context 'human user' do
+ context 'reporter' do
+ let(:current_user) { reporter }
- it { is_expected.to be_allowed(:read_dependency_proxy) }
- it { is_expected.to be_disallowed(:admin_dependency_proxy) }
- end
+ it_behaves_like 'allows dependency proxy read access but not admin'
+ end
- context 'developer' do
- let(:current_user) { developer }
+ context 'developer' do
+ let(:current_user) { developer }
+
+ it_behaves_like 'allows dependency proxy read access but not admin'
+ end
+
+ context 'maintainer' do
+ let(:current_user) { maintainer }
+
+ it_behaves_like 'allows dependency proxy read access but not admin'
+ it_behaves_like 'disabling admin_package feature flag'
+ end
+
+ context 'owner' do
+ let(:current_user) { owner }
- it { is_expected.to be_allowed(:read_dependency_proxy) }
- it { is_expected.to be_disallowed(:admin_dependency_proxy) }
+ it { is_expected.to be_allowed(:read_dependency_proxy) }
+ it { is_expected.to be_allowed(:admin_dependency_proxy) }
+
+ it_behaves_like 'disabling admin_package feature flag'
+ end
end
- context 'maintainer' do
- let(:current_user) { maintainer }
+ context 'deploy token user' do
+ let!(:group_deploy_token) do
+ create(:group_deploy_token, group: group, deploy_token: deploy_token)
+ end
+
+ subject { described_class.new(deploy_token, group) }
- it { is_expected.to be_allowed(:read_dependency_proxy) }
- it { is_expected.to be_disallowed(:admin_dependency_proxy) }
+ context 'with insufficient scopes' do
+ let_it_be(:deploy_token) { create(:deploy_token, :group) }
- it_behaves_like 'disabling admin_package feature flag'
+ it_behaves_like 'disallows all dependency proxy access'
+ end
+
+ context 'with sufficient scopes' do
+ let_it_be(:deploy_token) { create(:deploy_token, :group, :dependency_proxy_scopes) }
+
+ it_behaves_like 'allows dependency proxy read access but not admin'
+ end
end
- context 'owner' do
- let(:current_user) { owner }
+ context 'group access token user' do
+ let_it_be(:bot_user) { create(:user, :project_bot) }
+ let_it_be(:token) { create(:personal_access_token, user: bot_user, scopes: [Gitlab::Auth::READ_API_SCOPE]) }
+
+ subject { described_class.new(bot_user, group) }
- it { is_expected.to be_allowed(:read_dependency_proxy) }
- it { is_expected.to be_allowed(:admin_dependency_proxy) }
+ context 'not a member of the group' do
+ it_behaves_like 'disallows all dependency proxy access'
+ end
+
+ context 'a member of the group' do
+ before do
+ group.add_guest(bot_user)
+ end
- it_behaves_like 'disabling admin_package feature flag'
+ it_behaves_like 'allows dependency proxy read access but not admin'
+ end
end
end
end
- context 'deploy token access' do
+ context 'deploy token user' do
let!(:group_deploy_token) do
create(:group_deploy_token, group: group, deploy_token: deploy_token)
end
@@ -1179,17 +1229,6 @@ RSpec.describe GroupPolicy, feature_category: :system_access do
it { is_expected.to be_allowed(:read_group) }
it { is_expected.to be_disallowed(:destroy_package) }
end
-
- context 'a deploy token with dependency proxy scopes' do
- let_it_be(:deploy_token) { create(:deploy_token, :group, :dependency_proxy_scopes) }
-
- before do
- stub_config(dependency_proxy: { enabled: true })
- end
-
- it { is_expected.to be_allowed(:read_dependency_proxy) }
- it { is_expected.to be_disallowed(:admin_dependency_proxy) }
- end
end
it_behaves_like 'Self-managed Core resource access tokens'
diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb
index 9a2caeb7435..bbfd231ed38 100644
--- a/spec/policies/user_policy_spec.rb
+++ b/spec/policies/user_policy_spec.rb
@@ -247,6 +247,32 @@ RSpec.describe UserPolicy do
end
end
+ describe ':read_user_organizations' do
+ context 'when user is admin' do
+ let(:current_user) { admin }
+
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it { is_expected.to be_allowed(:read_user_organizations) }
+ end
+
+ context 'when admin mode is disabled' do
+ it { is_expected.not_to be_allowed(:read_user_organizations) }
+ end
+ end
+
+ context 'when user is not an admin' do
+ context 'requesting their own organizations' do
+ subject { described_class.new(current_user, current_user) }
+
+ it { is_expected.to be_allowed(:read_user_organizations) }
+ end
+
+ context "requesting a different user's orgnanizations" do
+ it { is_expected.not_to be_allowed(:read_user_organizations) }
+ end
+ end
+ end
+
describe ':read_user_email_address' do
context 'when user is admin' do
let(:current_user) { admin }
diff --git a/spec/requests/api/graphql/user_spec.rb b/spec/requests/api/graphql/user_spec.rb
index 41ee233dfc5..22ebc1be964 100644
--- a/spec/requests/api/graphql/user_spec.rb
+++ b/spec/requests/api/graphql/user_spec.rb
@@ -113,4 +113,36 @@ RSpec.describe 'User', feature_category: :user_profile do
end
end
end
+
+ describe 'organizations field' do
+ let_it_be(:organization_user) { create(:organization_user, user: current_user) }
+ let_it_be(:organization) { organization_user.organization }
+ let_it_be(:another_organization) { create(:organization) }
+ let_it_be(:another_user) { create(:user) }
+
+ let(:query) do
+ graphql_query_for(
+ :user,
+ { username: current_user.username.to_s.upcase },
+ 'organizations { nodes { path } }'
+ )
+ end
+
+ context 'with permission' do
+ it 'returns the relevant organization details' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data.dig('user', 'organizations', 'nodes').pluck('path'))
+ .to match_array(organization.path)
+ end
+ end
+
+ context 'without permission' do
+ it 'does not return organization details' do
+ post_graphql(query, current_user: another_user)
+
+ expect(graphql_data.dig('user', 'organizations', 'nodes')).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index 966cc2d6d4e..0ac059b5ed3 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -92,7 +92,7 @@ RSpec.describe JwtController, feature_category: :system_access 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.merge(auth_type: :build)).permit!) }
+ it { expect(service_class).to have_received(:new).with(project, user, ActionController::Parameters.new(parameters.merge(auth_type: :build, raw_token: build.token)).permit!) }
it_behaves_like 'user logging'
end
@@ -119,7 +119,7 @@ RSpec.describe JwtController, feature_category: :system_access do
.with(
nil,
nil,
- ActionController::Parameters.new(parameters.merge(deploy_token: deploy_token, auth_type: :deploy_token)).permit!
+ ActionController::Parameters.new(parameters.merge(deploy_token: deploy_token, auth_type: :deploy_token, raw_token: deploy_token.token)).permit!
)
end
@@ -144,7 +144,7 @@ RSpec.describe JwtController, feature_category: :system_access do
.with(
nil,
user,
- ActionController::Parameters.new(parameters.merge(auth_type: :personal_access_token)).permit!
+ ActionController::Parameters.new(parameters.merge(auth_type: :personal_access_token, raw_token: pat.token)).permit!
)
end
@@ -160,7 +160,7 @@ RSpec.describe JwtController, feature_category: :system_access do
subject! { get '/jwt/auth', params: parameters, headers: headers }
- it { expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters.merge(auth_type: :gitlab_or_ldap)).permit!) }
+ it { expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters.merge(auth_type: :gitlab_or_ldap, raw_token: user.password)).permit!) }
it_behaves_like 'rejecting a blocked user'
@@ -180,7 +180,7 @@ RSpec.describe JwtController, feature_category: :system_access 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.merge(auth_type: :gitlab_or_ldap)) }
+ it { expect(service_class).to have_received(:new).with(nil, user, service_parameters.merge(auth_type: :gitlab_or_ldap, raw_token: user.password)) }
it_behaves_like 'user logging'
end
@@ -197,7 +197,7 @@ RSpec.describe JwtController, feature_category: :system_access 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.merge(auth_type: :gitlab_or_ldap)) }
+ it { expect(service_class).to have_received(:new).with(nil, user, service_parameters.merge(auth_type: :gitlab_or_ldap, raw_token: user.password)) }
end
context 'when user has 2FA enabled' do
diff --git a/spec/services/auth/dependency_proxy_authentication_service_spec.rb b/spec/services/auth/dependency_proxy_authentication_service_spec.rb
index 3ef9c8fc96e..04f7e46daa6 100644
--- a/spec/services/auth/dependency_proxy_authentication_service_spec.rb
+++ b/spec/services/auth/dependency_proxy_authentication_service_spec.rb
@@ -4,15 +4,17 @@ require 'spec_helper'
RSpec.describe Auth::DependencyProxyAuthenticationService, feature_category: :dependency_proxy do
let_it_be(:user) { create(:user) }
+ let_it_be(:params) { {} }
+ let_it_be(:authentication_abilities) { nil }
- let(:service) { described_class.new(nil, user) }
+ let(:service) { described_class.new(nil, user, params) }
before do
- stub_config(dependency_proxy: { enabled: true })
+ stub_config(dependency_proxy: { enabled: true }, registry: { enabled: true })
end
describe '#execute' do
- subject { service.execute(authentication_abilities: nil) }
+ subject { service.execute(authentication_abilities: authentication_abilities) }
shared_examples 'returning' do |status:, message:|
it "returns #{message}", :aggregate_failures do
@@ -21,9 +23,23 @@ RSpec.describe Auth::DependencyProxyAuthenticationService, feature_category: :de
end
end
- shared_examples 'returning a token' do
- it 'returns a token' do
- expect(subject[:token]).not_to be_nil
+ shared_examples 'returning a token with encoded user_id' do
+ it 'returns a token with encoded user_id' do
+ token = subject[:token]
+ expect(token).not_to be_nil
+
+ decoded_token = decode(token)
+ expect(decoded_token['user_id']).not_to be_nil
+ end
+ end
+
+ shared_examples 'returning a token with encoded deploy_token' do
+ it 'returns a token with encoded deploy_token' do
+ token = subject[:token]
+ expect(token).not_to be_nil
+
+ decoded_token = decode(token)
+ expect(decoded_token['deploy_token']).not_to be_nil
end
end
@@ -41,14 +57,53 @@ RSpec.describe Auth::DependencyProxyAuthenticationService, feature_category: :de
it_behaves_like 'returning', status: 403, message: 'access forbidden'
end
- context 'with a deploy token as user' do
- let_it_be(:user) { create(:deploy_token, :group, :dependency_proxy_scopes) }
+ context 'with a deploy token' do
+ let_it_be(:deploy_token) { create(:deploy_token, :group, :dependency_proxy_scopes) }
+ let_it_be(:params) { { deploy_token: deploy_token } }
+
+ it_behaves_like 'returning a token with encoded deploy_token'
+ end
+
+ context 'with a human user' do
+ it_behaves_like 'returning a token with encoded user_id'
+ end
+
+ context 'with a group access token' do
+ let_it_be(:user) { create(:user, :project_bot) }
+ let_it_be_with_reload(:token) { create(:personal_access_token, user: user) }
- it_behaves_like 'returning a token'
+ context 'with insufficient authentication abilities' do
+ it_behaves_like 'returning', status: 403, message: 'access forbidden'
+ end
+
+ context 'with sufficient authentication abilities' do
+ let_it_be(:authentication_abilities) { Auth::DependencyProxyAuthenticationService::REQUIRED_ABILITIES }
+ let_it_be(:params) { { raw_token: token.token } }
+
+ subject { service.execute(authentication_abilities: authentication_abilities) }
+
+ it_behaves_like 'returning a token with encoded user_id'
+
+ context 'revoked' do
+ before do
+ token.revoke!
+ end
+
+ it_behaves_like 'returning', status: 403, message: 'access forbidden'
+ end
+
+ context 'expired' do
+ before do
+ token.update_column(:expires_at, 1.day.ago)
+ end
+
+ it_behaves_like 'returning', status: 403, message: 'access forbidden'
+ end
+ end
end
- context 'with a user' do
- it_behaves_like 'returning a token'
+ def decode(token)
+ DependencyProxy::AuthTokenService.new(token).execute
end
end
end
diff --git a/spec/services/service_desk/custom_emails/create_service_spec.rb b/spec/services/service_desk/custom_emails/create_service_spec.rb
index 2029c9a0c3f..e165131bcf9 100644
--- a/spec/services/service_desk/custom_emails/create_service_spec.rb
+++ b/spec/services/service_desk/custom_emails/create_service_spec.rb
@@ -156,7 +156,7 @@ RSpec.describe ServiceDesk::CustomEmails::CreateService, feature_category: :serv
}
end
- it 'creates all records returns a successful response' do
+ it 'creates all records and returns a successful response' do
# Because we also log in ServiceDesk::CustomEmailVerifications::CreateService
expect(Gitlab::AppLogger).to receive(:info).with({ category: 'custom_email_verification' }).once
expect(Gitlab::AppLogger).to receive(:info).with(logger_params).once
@@ -174,7 +174,8 @@ RSpec.describe ServiceDesk::CustomEmails::CreateService, feature_category: :serv
smtp_address: params[:smtp_address],
smtp_port: params[:smtp_port].to_i,
smtp_username: params[:smtp_username],
- smtp_password: params[:smtp_password]
+ smtp_password: params[:smtp_password],
+ smtp_authentication: nil
)
expect(project.service_desk_custom_email_verification).to have_attributes(
state: 'started',
@@ -183,6 +184,30 @@ RSpec.describe ServiceDesk::CustomEmails::CreateService, feature_category: :serv
)
end
+ context 'with optional smtp_authentication parameter' do
+ before do
+ params[:smtp_authentication] = 'login'
+ end
+
+ it 'sets authentication and returns a successful response' do
+ response = service.execute
+ project.reset
+
+ expect(response).to be_success
+ expect(project.service_desk_custom_email_credential.smtp_authentication).to eq 'login'
+ end
+
+ context 'with unsupported value' do
+ let(:expected_error_message) { error_cannot_create_custom_email }
+
+ before do
+ params[:smtp_authentication] = 'unsupported'
+ end
+
+ it_behaves_like 'a failing service that does not create records'
+ end
+ end
+
context 'when custom email aready exists' do
let!(:settings) { create(:service_desk_setting, project: project, custom_email: 'user@example.com') }
let!(:credential) { create(:service_desk_custom_email_credential, project: project) }
diff --git a/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb b/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb
index 434592ccd38..257ccc553fe 100644
--- a/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb
+++ b/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb
@@ -13,6 +13,8 @@ RSpec.shared_context 'with FOSS query type fields' do
:current_user,
:design_management,
:echo,
+ :frecent_groups,
+ :frecent_projects,
:gitpod_enabled,
:group,
:groups,
diff --git a/spec/support/shared_examples/graphql/resolvers/users/pages_visits_resolvers_shared_examples.rb b/spec/support/shared_examples/graphql/resolvers/users/pages_visits_resolvers_shared_examples.rb
new file mode 100644
index 00000000000..0dca28a4e74
--- /dev/null
+++ b/spec/support/shared_examples/graphql/resolvers/users/pages_visits_resolvers_shared_examples.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'namespace visits resolver' do
+ include GraphqlHelpers
+
+ describe '#resolve' do
+ context 'when user is not logged in' do
+ let_it_be(:current_user) { nil }
+
+ it 'returns nil' do
+ expect(resolve_items).to eq(nil)
+ end
+ end
+
+ context 'when user is logged in' do
+ let_it_be(:current_user) { create(:user) }
+
+ context 'when the frecent_namespaces_suggestions feature flag is disabled' do
+ before do
+ stub_feature_flags(frecent_namespaces_suggestions: false)
+ end
+
+ it 'raises a "Resource not available" exception' do
+ expect(resolve_items).to be_a(::Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ it 'returns frecent groups' do
+ expect(resolve_items).to be_an_instance_of(Array)
+ end
+ end
+ end
+
+ private
+
+ def resolve_items
+ sync(resolve(described_class, ctx: { current_user: current_user }))
+ end
+end
diff --git a/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb b/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb
index b653b4e265a..7a3b3d6924c 100644
--- a/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb
@@ -28,6 +28,7 @@ RSpec.shared_examples "a user type with merge request interaction type" do
authoredMergeRequests
assignedMergeRequests
reviewRequestedMergeRequests
+ organizations
groupMemberships
groupCount
projectMemberships
diff --git a/spec/support/shared_examples/models/users/pages_visits_shared_examples.rb b/spec/support/shared_examples/models/users/pages_visits_shared_examples.rb
index 0b3e8516d25..ec7dc1fb8b9 100644
--- a/spec/support/shared_examples/models/users/pages_visits_shared_examples.rb
+++ b/spec/support/shared_examples/models/users/pages_visits_shared_examples.rb
@@ -6,6 +6,10 @@ RSpec.shared_examples 'namespace visits model' do
it { is_expected.to validate_presence_of(:visited_at) }
describe '#visited_around?' do
+ before do
+ described_class.create!(entity_id: entity.id, user_id: user.id, visited_at: base_time)
+ end
+
context 'when the checked time matches a recent visit' do
[-15.minutes, 15.minutes].each do |time_diff|
it 'returns true' do
@@ -24,4 +28,104 @@ RSpec.shared_examples 'namespace visits model' do
end
end
end
+
+ describe '#frecent_visits_scores' do
+ def frecent_visits_scores_to_array(visits)
+ visits.map { |visit| [visit["entity_id"], visit["score"]] }
+ end
+
+ context 'when there is lots of data' do
+ before do
+ create_visit_records
+ end
+
+ it 'returns the frecent items, sorted by their frecency score' do
+ expect(frecent_visits_scores_to_array(described_class.frecent_visits_scores(user_id: user.id,
+ limit: 10))).to eq([[2, 31], [1, 30], [3, 28], [6, 6], [7, 6], [8, 6], [4, 6], [5, 6]])
+ end
+
+ it 'limits the amount of returned entries' do
+ expect(frecent_visits_scores_to_array(described_class.frecent_visits_scores(user_id: user.id,
+ limit: 2))).to eq([
+ [2, 31], [1, 30]
+ ])
+ end
+ end
+
+ context 'when there is few data' do
+ before do
+ [
+ # Multiplier: 4
+ [1, Time.current],
+
+ # Multiplier: 3
+ [2, 2.weeks.ago],
+ [3, 2.weeks.ago],
+
+ # Multiplier: 2
+ [1, 3.weeks.ago],
+ [1, 3.weeks.ago],
+
+ # Multiplier: 1
+ [2, 5.weeks.ago]
+ ].each do |id, datetime|
+ described_class.create!(entity_id: id, user_id: user.id, visited_at: datetime)
+ end
+ end
+
+ it 'returns the frecent items, sorted by their frecency score' do
+ expect(frecent_visits_scores_to_array(described_class.frecent_visits_scores(user_id: user.id,
+ limit: 5))).to eq([
+ [1, 8], # Entity 1 gets a score of (1 * 4) + (2 * 2) = 8
+ [2, 4], # Entity 2 gets a score of (1 * 3) + (1 * 1) = 4
+ [3, 3] # Entity 3 gets a score of 1 * 3 = 3
+ ])
+ end
+ end
+ end
+
+ private
+
+ # rubocop: disable Metrics/AbcSize -- Despite being long, this method is quite straightforward. Splitting it in smaller chunks would likely harm readability more than anything.
+ def create_visit_records
+ [
+ [1, Time.current],
+
+ [2, 1.week.ago],
+ [2, 1.week.ago],
+
+ [2, 2.weeks.ago],
+ [3, 2.weeks.ago],
+ [3, 2.weeks.ago],
+ [4, 2.weeks.ago],
+ [5, 2.weeks.ago],
+ [6, 2.weeks.ago],
+ [7, 2.weeks.ago],
+ [8, 2.weeks.ago],
+
+ [1, 3.weeks.ago],
+ [1, 3.weeks.ago],
+ [3, 3.weeks.ago],
+ [3, 3.weeks.ago],
+
+ [1, 4.weeks.ago],
+ [2, 4.weeks.ago],
+ [2, 4.weeks.ago],
+
+ [3, 7.weeks.ago],
+ [3, 7.weeks.ago],
+
+ [1, 8.weeks.ago],
+ [1, 8.weeks.ago],
+ [1, 8.weeks.ago],
+ [1, 8.weeks.ago],
+
+ [2, 9.weeks.ago],
+ [2, 9.weeks.ago],
+ [2, 9.weeks.ago]
+ ].each do |id, datetime|
+ described_class.create!(entity_id: id, user_id: user.id, visited_at: datetime)
+ end
+ end
+ # rubocop: enable Metrics/AbcSize
end