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:
Diffstat (limited to 'spec')
-rw-r--r--spec/experiments/ios_specific_templates_experiment_spec.rb62
-rw-r--r--spec/features/admin/admin_users_impersonation_tokens_spec.rb4
-rw-r--r--spec/features/profiles/personal_access_tokens_spec.rb4
-rw-r--r--spec/finders/users_finder_spec.rb12
-rw-r--r--spec/frontend/header_search/components/header_search_autocomplete_items_spec.js58
-rw-r--r--spec/frontend/header_search/mock_data.js36
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js2
-rw-r--r--spec/helpers/ci/pipelines_helper_spec.rb41
-rw-r--r--spec/helpers/search_helper_spec.rb2
-rw-r--r--spec/migrations/20220322132242_update_pages_onboarding_state_spec.rb53
-rw-r--r--spec/models/project_spec.rb57
-rw-r--r--spec/models/user_spec.rb31
-rw-r--r--spec/requests/api/project_export_spec.rb23
-rw-r--r--spec/support/shared_examples/features/access_tokens_shared_examples.rb4
14 files changed, 348 insertions, 41 deletions
diff --git a/spec/experiments/ios_specific_templates_experiment_spec.rb b/spec/experiments/ios_specific_templates_experiment_spec.rb
new file mode 100644
index 00000000000..4d02381dbde
--- /dev/null
+++ b/spec/experiments/ios_specific_templates_experiment_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe IosSpecificTemplatesExperiment do
+ subject do
+ described_class.new(actor: user, project: project) do |e|
+ e.candidate { true }
+ end.run
+ end
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :auto_devops_disabled) }
+
+ let!(:project_setting) { create(:project_setting, project: project, target_platforms: target_platforms) }
+ let(:target_platforms) { %w(ios) }
+
+ before do
+ stub_experiments(ios_specific_templates: :candidate)
+ project.add_developer(user) if user
+ end
+
+ it { is_expected.to be true }
+
+ describe 'skipping the experiment' do
+ context 'no actor' do
+ let_it_be(:user) { nil }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'actor cannot create pipelines' do
+ before do
+ project.add_guest(user)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'targeting a non iOS platform' do
+ let(:target_platforms) { [] }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'project has a ci.yaml file' do
+ before do
+ allow(project).to receive(:has_ci?).and_return(true)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'project has pipelines' do
+ before do
+ create(:ci_pipeline, project: project)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+end
diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
index 6643ebe82e6..15bc2318022 100644
--- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb
+++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
@@ -36,14 +36,14 @@ RSpec.describe 'Admin > Users > Impersonation Tokens', :js do
click_on "1"
# Scopes
- check "api"
+ check "read_api"
check "read_user"
click_on "Create impersonation token"
expect(active_impersonation_tokens).to have_text(name)
expect(active_impersonation_tokens).to have_text('in')
- expect(active_impersonation_tokens).to have_text('api')
+ expect(active_impersonation_tokens).to have_text('read_api')
expect(active_impersonation_tokens).to have_text('read_user')
expect(PersonalAccessTokensFinder.new(impersonation: true).execute.count).to equal(1)
expect(created_impersonation_token).not_to be_empty
diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb
index f1e5658cd7b..8cbc0491441 100644
--- a/spec/features/profiles/personal_access_tokens_spec.rb
+++ b/spec/features/profiles/personal_access_tokens_spec.rb
@@ -47,14 +47,14 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do
click_on "1"
# Scopes
- check "api"
+ check "read_api"
check "read_user"
click_on "Create personal access token"
expect(active_personal_access_tokens).to have_text(name)
expect(active_personal_access_tokens).to have_text('in')
- expect(active_personal_access_tokens).to have_text('api')
+ expect(active_personal_access_tokens).to have_text('read_api')
expect(active_personal_access_tokens).to have_text('read_user')
expect(created_personal_access_token).not_to be_empty
end
diff --git a/spec/finders/users_finder_spec.rb b/spec/finders/users_finder_spec.rb
index bf40aea4a5d..271dce44db7 100644
--- a/spec/finders/users_finder_spec.rb
+++ b/spec/finders/users_finder_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe UsersFinder do
it 'returns searchable users' do
users = described_class.new(user).execute
- expect(users).to contain_exactly(user, normal_user, external_user, omniauth_user, internal_user, admin_user, project_bot)
+ expect(users).to contain_exactly(user, normal_user, external_user, unconfirmed_user, omniauth_user, internal_user, admin_user, project_bot)
end
it 'filters by username' do
@@ -56,7 +56,7 @@ RSpec.describe UsersFinder do
it 'filters by non external users' do
users = described_class.new(user, non_external: true).execute
- expect(users).to contain_exactly(user, normal_user, omniauth_user, internal_user, admin_user, project_bot)
+ expect(users).to contain_exactly(user, normal_user, unconfirmed_user, omniauth_user, internal_user, admin_user, project_bot)
end
it 'filters by created_at' do
@@ -73,7 +73,7 @@ RSpec.describe UsersFinder do
it 'filters by non internal users' do
users = described_class.new(user, non_internal: true).execute
- expect(users).to contain_exactly(user, normal_user, external_user, omniauth_user, admin_user, project_bot)
+ expect(users).to contain_exactly(user, normal_user, unconfirmed_user, external_user, omniauth_user, admin_user, project_bot)
end
it 'does not filter by custom attributes' do
@@ -82,18 +82,18 @@ RSpec.describe UsersFinder do
custom_attributes: { foo: 'bar' }
).execute
- expect(users).to contain_exactly(user, normal_user, external_user, omniauth_user, internal_user, admin_user, project_bot)
+ expect(users).to contain_exactly(user, normal_user, external_user, unconfirmed_user, omniauth_user, internal_user, admin_user, project_bot)
end
it 'orders returned results' do
users = described_class.new(user, sort: 'id_asc').execute
- expect(users).to eq([normal_user, admin_user, external_user, omniauth_user, internal_user, project_bot, user])
+ expect(users).to eq([normal_user, admin_user, external_user, unconfirmed_user, omniauth_user, internal_user, project_bot, user])
end
it 'does not filter by admins' do
users = described_class.new(user, admins: true).execute
- expect(users).to contain_exactly(user, normal_user, external_user, admin_user, omniauth_user, internal_user, project_bot)
+ expect(users).to contain_exactly(user, normal_user, external_user, admin_user, unconfirmed_user, omniauth_user, internal_user, project_bot)
end
end
diff --git a/spec/frontend/header_search/components/header_search_autocomplete_items_spec.js b/spec/frontend/header_search/components/header_search_autocomplete_items_spec.js
index b6be53fec99..7952661e2d2 100644
--- a/spec/frontend/header_search/components/header_search_autocomplete_items_spec.js
+++ b/spec/frontend/header_search/components/header_search_autocomplete_items_spec.js
@@ -8,6 +8,9 @@ import {
LARGE_AVATAR_PX,
PROJECTS_CATEGORY,
SMALL_AVATAR_PX,
+ ISSUES_CATEGORY,
+ MERGE_REQUEST_CATEGORY,
+ RECENT_EPICS_CATEGORY,
} from '~/header_search/constants';
import {
MOCK_GROUPED_AUTOCOMPLETE_OPTIONS,
@@ -50,7 +53,12 @@ describe('HeaderSearchAutocompleteItems', () => {
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findGlDropdownDividers = () => wrapper.findAllComponents(GlDropdownDivider);
const findFirstDropdownItem = () => findDropdownItems().at(0);
- const findDropdownItemTitles = () => findDropdownItems().wrappers.map((w) => w.text());
+ const findDropdownItemTitles = () =>
+ findDropdownItems().wrappers.map((w) => w.findAll('span').at(1).text());
+ const findDropdownItemSubTitles = () =>
+ findDropdownItems()
+ .wrappers.filter((w) => w.findAll('span').length > 2)
+ .map((w) => w.findAll('span').at(2).text());
const findDropdownItemLinks = () => findDropdownItems().wrappers.map((w) => w.attributes('href'));
const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findGlAvatar = () => wrapper.findComponent(GlAvatar);
@@ -95,10 +103,17 @@ describe('HeaderSearchAutocompleteItems', () => {
});
it('renders titles correctly', () => {
- const expectedTitles = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.map((o) => o.label);
+ const expectedTitles = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.map((o) => o.value || o.label);
expect(findDropdownItemTitles()).toStrictEqual(expectedTitles);
});
+ it('renders sub-titles correctly', () => {
+ const expectedSubTitles = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.filter((o) => o.value).map(
+ (o) => o.label,
+ );
+ expect(findDropdownItemSubTitles()).toStrictEqual(expectedSubTitles);
+ });
+
it('renders links correctly', () => {
const expectedLinks = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.map((o) => o.url);
expect(findDropdownItemLinks()).toStrictEqual(expectedLinks);
@@ -106,15 +121,30 @@ describe('HeaderSearchAutocompleteItems', () => {
});
describe.each`
- item | showAvatar | avatarSize
- ${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(LARGE_AVATAR_PX)}
- ${{ data: [{ category: GROUPS_CATEGORY, avatar_url: '/123' }] }} | ${true} | ${String(LARGE_AVATAR_PX)}
- ${{ data: [{ category: 'Help', avatar_url: '' }] }} | ${true} | ${String(SMALL_AVATAR_PX)}
- ${{ data: [{ category: 'Settings' }] }} | ${false} | ${false}
- `('GlAvatar', ({ item, showAvatar, avatarSize }) => {
+ item | showAvatar | avatarSize | searchContext | entityId | entityName
+ ${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ project: { id: 29 } }} | ${'29'} | ${''}
+ ${{ data: [{ category: GROUPS_CATEGORY, avatar_url: '/123' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ group: { id: 12 } }} | ${'12'} | ${''}
+ ${{ data: [{ category: 'Help', avatar_url: '' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${null} | ${'0'} | ${''}
+ ${{ data: [{ category: 'Settings' }] }} | ${false} | ${false} | ${null} | ${false} | ${false}
+ ${{ data: [{ category: GROUPS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ group: { id: 1, name: 'test1' } }} | ${'1'} | ${'test1'}
+ ${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ project: { id: 2, name: 'test2' } }} | ${'2'} | ${'test2'}
+ ${{ data: [{ category: ISSUES_CATEGORY, avatar_url: null }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ project: { id: 3, name: 'test3' } }} | ${'3'} | ${'test3'}
+ ${{ data: [{ category: MERGE_REQUEST_CATEGORY, avatar_url: null }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ project: { id: 4, name: 'test4' } }} | ${'4'} | ${'test4'}
+ ${{ data: [{ category: RECENT_EPICS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ group: { id: 5, name: 'test5' } }} | ${'5'} | ${'test5'}
+ ${{ data: [{ category: GROUPS_CATEGORY, avatar_url: null, group_id: 6, group_name: 'test6' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${null} | ${'6'} | ${'test6'}
+ ${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null, project_id: 7, project_name: 'test7' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${null} | ${'7'} | ${'test7'}
+ ${{ data: [{ category: ISSUES_CATEGORY, avatar_url: null, project_id: 8, project_name: 'test8' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${null} | ${'8'} | ${'test8'}
+ ${{ data: [{ category: MERGE_REQUEST_CATEGORY, avatar_url: null, project_id: 9, project_name: 'test9' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${null} | ${'9'} | ${'test9'}
+ ${{ data: [{ category: RECENT_EPICS_CATEGORY, avatar_url: null, group_id: 10, group_name: 'test10' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${null} | ${'10'} | ${'test10'}
+ ${{ data: [{ category: GROUPS_CATEGORY, avatar_url: null, group_id: 11, group_name: 'test11' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ group: { id: 1, name: 'test1' } }} | ${'11'} | ${'test11'}
+ ${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null, project_id: 12, project_name: 'test12' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ project: { id: 2, name: 'test2' } }} | ${'12'} | ${'test12'}
+ ${{ data: [{ category: ISSUES_CATEGORY, avatar_url: null, project_id: 13, project_name: 'test13' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ project: { id: 3, name: 'test3' } }} | ${'13'} | ${'test13'}
+ ${{ data: [{ category: MERGE_REQUEST_CATEGORY, avatar_url: null, project_id: 14, project_name: 'test14' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ project: { id: 4, name: 'test4' } }} | ${'14'} | ${'test14'}
+ ${{ data: [{ category: RECENT_EPICS_CATEGORY, avatar_url: null, group_id: 15, group_name: 'test15' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ group: { id: 5, name: 'test5' } }} | ${'15'} | ${'test15'}
+ `('GlAvatar', ({ item, showAvatar, avatarSize, searchContext, entityId, entityName }) => {
describe(`when category is ${item.data[0].category} and avatar_url is ${item.data[0].avatar_url}`, () => {
beforeEach(() => {
- createComponent({}, { autocompleteGroupedSearchOptions: () => [item] });
+ createComponent({ searchContext }, { autocompleteGroupedSearchOptions: () => [item] });
});
it(`should${showAvatar ? '' : ' not'} render`, () => {
@@ -124,6 +154,16 @@ describe('HeaderSearchAutocompleteItems', () => {
it(`should set avatarSize to ${avatarSize}`, () => {
expect(findGlAvatar().exists() && findGlAvatar().attributes('size')).toBe(avatarSize);
});
+
+ it(`should set avatar entityId to ${entityId}`, () => {
+ expect(findGlAvatar().exists() && findGlAvatar().attributes('entityid')).toBe(entityId);
+ });
+
+ it(`should set avatar entityName to ${entityName}`, () => {
+ expect(findGlAvatar().exists() && findGlAvatar().attributes('entityname')).toBe(
+ entityName,
+ );
+ });
});
});
});
diff --git a/spec/frontend/header_search/mock_data.js b/spec/frontend/header_search/mock_data.js
index 358c224dfa6..b6f0fdcc29d 100644
--- a/spec/frontend/header_search/mock_data.js
+++ b/spec/frontend/header_search/mock_data.js
@@ -96,19 +96,22 @@ export const MOCK_AUTOCOMPLETE_OPTIONS_RES = [
{
category: 'Projects',
id: 1,
- label: 'MockProject1',
+ label: 'Gitlab Org / MockProject1',
+ value: 'MockProject1',
url: 'project/1',
},
{
category: 'Groups',
id: 1,
- label: 'MockGroup1',
+ label: 'Gitlab Org / MockGroup1',
+ value: 'MockGroup1',
url: 'group/1',
},
{
category: 'Projects',
id: 2,
- label: 'MockProject2',
+ label: 'Gitlab Org / MockProject2',
+ value: 'MockProject2',
url: 'project/2',
},
{
@@ -123,21 +126,24 @@ export const MOCK_AUTOCOMPLETE_OPTIONS = [
category: 'Projects',
html_id: 'autocomplete-Projects-0',
id: 1,
- label: 'MockProject1',
+ label: 'Gitlab Org / MockProject1',
+ value: 'MockProject1',
url: 'project/1',
},
{
category: 'Groups',
html_id: 'autocomplete-Groups-1',
id: 1,
- label: 'MockGroup1',
+ label: 'Gitlab Org / MockGroup1',
+ value: 'MockGroup1',
url: 'group/1',
},
{
category: 'Projects',
html_id: 'autocomplete-Projects-2',
id: 2,
- label: 'MockProject2',
+ label: 'Gitlab Org / MockProject2',
+ value: 'MockProject2',
url: 'project/2',
},
{
@@ -157,7 +163,8 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [
html_id: 'autocomplete-Projects-0',
id: 1,
- label: 'MockProject1',
+ label: 'Gitlab Org / MockProject1',
+ value: 'MockProject1',
url: 'project/1',
},
{
@@ -165,7 +172,8 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [
html_id: 'autocomplete-Projects-2',
id: 2,
- label: 'MockProject2',
+ label: 'Gitlab Org / MockProject2',
+ value: 'MockProject2',
url: 'project/2',
},
],
@@ -178,7 +186,8 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [
html_id: 'autocomplete-Groups-1',
id: 1,
- label: 'MockGroup1',
+ label: 'Gitlab Org / MockGroup1',
+ value: 'MockGroup1',
url: 'group/1',
},
],
@@ -202,21 +211,24 @@ export const MOCK_SORTED_AUTOCOMPLETE_OPTIONS = [
category: 'Projects',
html_id: 'autocomplete-Projects-0',
id: 1,
- label: 'MockProject1',
+ label: 'Gitlab Org / MockProject1',
+ value: 'MockProject1',
url: 'project/1',
},
{
category: 'Projects',
html_id: 'autocomplete-Projects-2',
id: 2,
- label: 'MockProject2',
+ label: 'Gitlab Org / MockProject2',
+ value: 'MockProject2',
url: 'project/2',
},
{
category: 'Groups',
html_id: 'autocomplete-Groups-1',
id: 1,
- label: 'MockGroup1',
+ label: 'Gitlab Org / MockGroup1',
+ value: 'MockGroup1',
url: 'group/1',
},
{
diff --git a/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js b/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js
index b0d5859cd31..3d7bf7acb41 100644
--- a/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js
@@ -72,7 +72,7 @@ describe('GroupsListItem', () => {
expect(addSubscriptionSpy).toHaveBeenCalledWith(mockSubscriptionPath, mockGroup1.full_path);
expect(persistAlert).toHaveBeenCalledWith({
- linkUrl: '/help/integration/jira_development_panel.html#usage',
+ linkUrl: '/help/integration/jira_development_panel.html#use-the-integration',
message:
'You should now see GitLab.com activity inside your Jira Cloud issues. %{linkStart}Learn more%{linkEnd}',
title: 'Namespace successfully linked',
diff --git a/spec/helpers/ci/pipelines_helper_spec.rb b/spec/helpers/ci/pipelines_helper_spec.rb
index 2b76eaa87bc..c473e1e4ab6 100644
--- a/spec/helpers/ci/pipelines_helper_spec.rb
+++ b/spec/helpers/ci/pipelines_helper_spec.rb
@@ -151,5 +151,46 @@ RSpec.describe Ci::PipelinesHelper do
end
end
end
+
+ describe 'the `registration_token` attribute' do
+ subject { data[:registration_token] }
+
+ describe 'when the project is eligible for the `ios_specific_templates` experiment' do
+ let_it_be(:project) { create(:project, :auto_devops_disabled) }
+ let_it_be(:user) { create(:user) }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ project.add_developer(user)
+ create(:project_setting, project: project, target_platforms: %w(ios))
+ end
+
+ context 'when the `ios_specific_templates` experiment variant is control' do
+ before do
+ stub_experiments(ios_specific_templates: :control)
+ end
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when the `ios_specific_templates` experiment variant is candidate' do
+ before do
+ stub_experiments(ios_specific_templates: :candidate)
+ end
+
+ context 'when the user cannot register project runners' do
+ before do
+ allow(helper).to receive(:can?).with(user, :register_project_runners, project).and_return(false)
+ end
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when the user can register project runners' do
+ it { is_expected.to eq(project.runners_token) }
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 96d8157dfde..d1be451a759 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -71,7 +71,7 @@ RSpec.describe SearchHelper do
create(:group).add_owner(user)
result = search_autocomplete_opts("gro").first
- expect(result.keys).to match_array(%i[category id label url avatar_url])
+ expect(result.keys).to match_array(%i[category id value label url avatar_url])
end
it 'includes the users recently viewed issues', :aggregate_failures do
diff --git a/spec/migrations/20220322132242_update_pages_onboarding_state_spec.rb b/spec/migrations/20220322132242_update_pages_onboarding_state_spec.rb
new file mode 100644
index 00000000000..fbd5fe546fa
--- /dev/null
+++ b/spec/migrations/20220322132242_update_pages_onboarding_state_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+require 'spec_helper'
+require_migration!
+
+RSpec.describe UpdatePagesOnboardingState do
+ let(:migration) { described_class.new }
+ let!(:namespaces) { table(:namespaces) }
+ let!(:projects) { table(:projects) }
+ let!(:project_pages_metadata) { table(:project_pages_metadata) }
+
+ let!(:namespace1) { namespaces.create!(name: 'foo', path: 'foo') }
+ let!(:namespace2) { namespaces.create!(name: 'bar', path: 'bar') }
+ let!(:project1) { projects.create!(namespace_id: namespace1.id) }
+ let!(:project2) { projects.create!(namespace_id: namespace2.id) }
+ let!(:pages_metadata1) do
+ project_pages_metadata.create!(
+ project_id: project1.id,
+ deployed: true,
+ onboarding_complete: false
+ )
+ end
+
+ let!(:pages_metadata2) do
+ project_pages_metadata.create!(
+ project_id: project2.id,
+ deployed: false,
+ onboarding_complete: false
+ )
+ end
+
+ describe '#up' do
+ before do
+ migration.up
+ end
+
+ it 'sets the onboarding_complete attribute to the value of deployed' do
+ expect(pages_metadata1.reload.onboarding_complete).to eq(true)
+ expect(pages_metadata2.reload.onboarding_complete).to eq(false)
+ end
+ end
+
+ describe '#down' do
+ before do
+ migration.up
+ migration.down
+ end
+
+ it 'sets all onboarding_complete attributes to false' do
+ expect(pages_metadata1.reload.onboarding_complete).to eq(false)
+ expect(pages_metadata2.reload.onboarding_complete).to eq(false)
+ end
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index ead7f7d0786..43088739f34 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2293,6 +2293,44 @@ RSpec.describe Project, factory_default: :keep do
end
end
+ describe '#pages_show_onboarding?' do
+ let(:project) { create(:project) }
+
+ subject { project.pages_show_onboarding? }
+
+ context "if there is no metadata" do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'if onboarding is complete' do
+ before do
+ project.pages_metadatum.update_column(:onboarding_complete, true)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'if there is metadata, but onboarding is not complete' do
+ before do
+ project.pages_metadatum.update_column(:onboarding_complete, false)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ # During migration, the onboarding_complete property can still be false,
+ # but will be updated later. To account for that case, pages_show_onboarding?
+ # should return false if `deployed` is true.
+ context "will return false if pages is deployed even if onboarding_complete is false" do
+ before do
+ project.pages_metadatum.update_column(:onboarding_complete, false)
+ project.pages_metadatum.update_column(:deployed, true)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
describe '#pages_deployed?' do
let(:project) { create(:project) }
@@ -6626,6 +6664,25 @@ RSpec.describe Project, factory_default: :keep do
end
end
+ describe '#mark_pages_onboarding_complete' do
+ let(:project) { create(:project) }
+
+ it "creates new record and sets onboarding_complete to true if none exists yet" do
+ project.mark_pages_onboarding_complete
+
+ expect(project.pages_metadatum.reload.onboarding_complete).to eq(true)
+ end
+
+ it "overrides an existing setting" do
+ pages_metadatum = project.pages_metadatum
+ pages_metadatum.update!(onboarding_complete: false)
+
+ expect do
+ project.mark_pages_onboarding_complete
+ end.to change { pages_metadatum.reload.onboarding_complete }.from(false).to(true)
+ end
+ end
+
describe '#mark_pages_as_deployed' do
let(:project) { create(:project) }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 5f2777b12d2..09e3a9c1156 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -4988,17 +4988,36 @@ RSpec.describe User do
end
describe '#attention_requested_open_merge_requests_count' do
- it 'returns number of open merge requests from non-archived projects' do
- user = create(:user)
- project = create(:project, :public)
- archived_project = create(:project, :public, :archived)
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:archived_project) { create(:project, :public, :archived) }
+ before do
create(:merge_request, source_project: project, author: user, reviewers: [user])
create(:merge_request, :closed, source_project: project, author: user, reviewers: [user])
create(:merge_request, source_project: archived_project, author: user, reviewers: [user])
+ end
+ it 'returns number of open merge requests from non-archived projects' do
+ expect(Rails.cache).not_to receive(:fetch)
expect(user.attention_requested_open_merge_requests_count(force: true)).to eq 1
end
+
+ context 'when uncached_mr_attention_requests_count is disabled' do
+ before do
+ stub_feature_flags(uncached_mr_attention_requests_count: false)
+ end
+
+ it 'fetches from cache' do
+ expect(Rails.cache).to receive(:fetch).with(
+ user.attention_request_cache_key,
+ force: false,
+ expires_in: described_class::COUNT_CACHE_VALIDITY_PERIOD
+ ).and_call_original
+
+ expect(user.attention_requested_open_merge_requests_count).to eq 1
+ end
+ end
end
describe '#assigned_open_issues_count' do
@@ -6748,9 +6767,9 @@ RSpec.describe User do
let_it_be(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') }
let_it_be(:internal_user) { User.alert_bot.tap { |u| u.confirm } }
- it 'does not return blocked, banned or unconfirmed users' do
+ it 'does not return blocked or banned users' do
expect(described_class.without_forbidden_states).to match_array([
- normal_user, admin_user, external_user, omniauth_user, internal_user
+ normal_user, admin_user, external_user, unconfirmed_user, omniauth_user, internal_user
])
end
end
diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb
index 2bc31153f2c..07efd56fef4 100644
--- a/spec/requests/api/project_export_spec.rb
+++ b/spec/requests/api/project_export_spec.rb
@@ -260,6 +260,29 @@ RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache do
expect(json_response['message']['error']).to eq('This endpoint has been requested too many times. Try again later.')
end
end
+
+ context 'applies correct scope when throttling' do
+ before do
+ stub_application_setting(project_download_export_limit: 1)
+ end
+
+ it 'throttles downloads within same namespaces' do
+ # simulate prior request to the same namespace, which increments the rate limit counter for that scope
+ Gitlab::ApplicationRateLimiter.throttled?(:project_download_export, scope: [user, project_finished.namespace])
+
+ get api(download_path_finished, user)
+ expect(response).to have_gitlab_http_status(:too_many_requests)
+ end
+
+ it 'allows downloads from different namespaces' do
+ # simulate prior request to a different namespace, which increments the rate limit counter for that scope
+ Gitlab::ApplicationRateLimiter.throttled?(:project_download_export,
+ scope: [user, create(:project, :with_export).namespace])
+
+ get api(download_path_finished, user)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
end
context 'when user is a maintainer' do
diff --git a/spec/support/shared_examples/features/access_tokens_shared_examples.rb b/spec/support/shared_examples/features/access_tokens_shared_examples.rb
index ae246a87bb6..215d9d3e5a8 100644
--- a/spec/support/shared_examples/features/access_tokens_shared_examples.rb
+++ b/spec/support/shared_examples/features/access_tokens_shared_examples.rb
@@ -29,15 +29,15 @@ RSpec.shared_examples 'resource access tokens creation' do |resource_type|
click_on '1'
# Scopes
- check 'api'
check 'read_api'
+ check 'read_repository'
click_on "Create #{resource_type} access token"
expect(active_resource_access_tokens).to have_text(name)
expect(active_resource_access_tokens).to have_text('in')
- expect(active_resource_access_tokens).to have_text('api')
expect(active_resource_access_tokens).to have_text('read_api')
+ expect(active_resource_access_tokens).to have_text('read_repository')
expect(active_resource_access_tokens).to have_text('Maintainer')
expect(created_resource_access_token).not_to be_empty
end