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-02-22 18:07:57 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-02-22 18:07:57 +0300
commit68aa32736b50c3609348f3bf740b81a2dfd1fb25 (patch)
tree801bc83d3ff80e58cf68cf1c9f33a164c36eb7de /spec
parentfb336d5f6b8b2c8f3131ee97a68ebc80c64a0223 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/admin/applications_controller_spec.rb24
-rw-r--r--spec/controllers/groups/settings/applications_controller_spec.rb49
-rw-r--r--spec/controllers/oauth/applications_controller_spec.rb27
-rw-r--r--spec/factories/integrations.rb3
-rw-r--r--spec/features/projects/integrations/apple_app_store_spec.rb24
-rw-r--r--spec/fixtures/auth_key.p816
-rw-r--r--spec/frontend/integrations/edit/components/integration_form_spec.js50
-rw-r--r--spec/frontend/integrations/edit/components/sections/apple_app_store_spec.js57
-rw-r--r--spec/frontend/integrations/edit/components/upload_dropzone_field_spec.js88
-rw-r--r--spec/frontend/issues/list/components/issues_list_app_spec.js55
-rw-r--r--spec/frontend/issues/list/mock_data.js20
-rw-r--r--spec/frontend/lib/utils/file_upload_spec.js22
-rw-r--r--spec/frontend/super_sidebar/components/help_center_spec.js11
-rw-r--r--spec/frontend/vue_shared/components/timezone_dropdown/timezone_dropdown_spec.js31
-rw-r--r--spec/frontend/work_items/components/work_item_detail_spec.js11
-rw-r--r--spec/lib/sidebars/menu_spec.rb55
-rw-r--r--spec/lib/sidebars/panel_spec.rb57
-rw-r--r--spec/models/concerns/ci/partitionable/partitioned_filter_spec.rb80
-rw-r--r--spec/models/concerns/ci/partitionable_spec.rb24
-rw-r--r--spec/models/integrations/apple_app_store_spec.rb5
-rw-r--r--spec/services/clusters/agent_tokens/create_service_spec.rb8
-rw-r--r--spec/services/clusters/agent_tokens/revoke_service_spec.rb59
-rw-r--r--spec/support/shared_contexts/features/integrations/integrations_shared_context.rb2
-rw-r--r--spec/support/shared_examples/features/manage_applications_shared_examples.rb2
24 files changed, 523 insertions, 257 deletions
diff --git a/spec/controllers/admin/applications_controller_spec.rb b/spec/controllers/admin/applications_controller_spec.rb
index bf7707f177c..c9b3cf4bca7 100644
--- a/spec/controllers/admin/applications_controller_spec.rb
+++ b/spec/controllers/admin/applications_controller_spec.rb
@@ -38,6 +38,30 @@ RSpec.describe Admin::ApplicationsController do
end
end
+ describe 'PUT #renew' do
+ let(:oauth_params) do
+ {
+ id: application.id
+ }
+ end
+
+ subject { put :renew, params: oauth_params }
+
+ it { is_expected.to have_gitlab_http_status(:ok) }
+ it { expect { subject }.to change { application.reload.secret } }
+
+ context 'when renew fails' do
+ before do
+ allow_next_found_instance_of(Doorkeeper::Application) do |application|
+ allow(application).to receive(:save).and_return(false)
+ end
+ end
+
+ it { expect { subject }.not_to change { application.reload.secret } }
+ it { is_expected.to redirect_to(admin_application_url(application)) }
+ end
+ end
+
describe 'POST #create' do
context 'with hash_oauth_secrets flag off' do
before do
diff --git a/spec/controllers/groups/settings/applications_controller_spec.rb b/spec/controllers/groups/settings/applications_controller_spec.rb
index b9457770ed6..8919360d276 100644
--- a/spec/controllers/groups/settings/applications_controller_spec.rb
+++ b/spec/controllers/groups/settings/applications_controller_spec.rb
@@ -188,6 +188,55 @@ RSpec.describe Groups::Settings::ApplicationsController do
end
end
+ describe 'PUT #renew' do
+ context 'when user is owner' do
+ before do
+ group.add_owner(user)
+ end
+
+ let(:oauth_params) do
+ {
+ group_id: group,
+ id: application.id
+ }
+ end
+
+ subject { put :renew, params: oauth_params }
+
+ it { is_expected.to have_gitlab_http_status(:ok) }
+ it { expect { subject }.to change { application.reload.secret } }
+
+ context 'when renew fails' do
+ before do
+ allow_next_found_instance_of(Doorkeeper::Application) do |application|
+ allow(application).to receive(:save).and_return(false)
+ end
+ end
+
+ it { expect { subject }.not_to change { application.reload.secret } }
+ it { is_expected.to redirect_to(group_settings_application_url(group, application)) }
+ end
+ end
+
+ context 'when user is not owner' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ let(:oauth_params) do
+ {
+ group_id: group,
+ id: application.id
+ }
+ end
+
+ it 'renders a 404' do
+ put :renew, params: oauth_params
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
describe 'PATCH #update' do
context 'when user is owner' do
before do
diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb
index 9b16dc9a463..2b8017f1ff7 100644
--- a/spec/controllers/oauth/applications_controller_spec.rb
+++ b/spec/controllers/oauth/applications_controller_spec.rb
@@ -71,6 +71,33 @@ RSpec.describe Oauth::ApplicationsController do
it_behaves_like 'redirects to 2fa setup page when the user requires it'
end
+ describe 'PUT #renew' do
+ let(:oauth_params) do
+ {
+ id: application.id
+ }
+ end
+
+ subject { put :renew, params: oauth_params }
+
+ it { is_expected.to have_gitlab_http_status(:ok) }
+ it { expect { subject }.to change { application.reload.secret } }
+
+ it_behaves_like 'redirects to login page when the user is not signed in'
+ it_behaves_like 'redirects to 2fa setup page when the user requires it'
+
+ context 'when renew fails' do
+ before do
+ allow_next_found_instance_of(Doorkeeper::Application) do |application|
+ allow(application).to receive(:save).and_return(false)
+ end
+ end
+
+ it { expect { subject }.not_to change { application.reload.secret } }
+ it { is_expected.to redirect_to(oauth_application_url(application)) }
+ end
+ end
+
describe 'GET #show' do
subject { get :show, params: { id: application.id } }
diff --git a/spec/factories/integrations.rb b/spec/factories/integrations.rb
index 7740b2da911..dccca9ce30d 100644
--- a/spec/factories/integrations.rb
+++ b/spec/factories/integrations.rb
@@ -261,7 +261,8 @@ FactoryBot.define do
app_store_issuer_id { 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' }
app_store_key_id { 'ABC1' }
- app_store_private_key { File.read('spec/fixtures/ssl_key.pem') }
+ app_store_private_key_file_name { 'auth_key.p8' }
+ app_store_private_key { File.read('spec/fixtures/auth_key.p8') }
end
# this is for testing storing values inside properties, which is deprecated and will be removed in
diff --git a/spec/features/projects/integrations/apple_app_store_spec.rb b/spec/features/projects/integrations/apple_app_store_spec.rb
new file mode 100644
index 00000000000..b6dc6557e20
--- /dev/null
+++ b/spec/features/projects/integrations/apple_app_store_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Upload Dropzone Field', feature_category: :integrations do
+ include_context 'project integration activation'
+
+ it 'uploads the file data to the correct form fields and updates the messaging correctly', :js, :aggregate_failures do
+ visit_project_integration('Apple App Store Connect')
+
+ expect(page).to have_content('Drag your Private Key file here or click to upload.')
+ expect(page).not_to have_content('auth_key.p8')
+
+ find("input[name='service[dropzone_file_name]']",
+ visible: false).set(Rails.root.join('spec/fixtures/auth_key.p8'))
+
+ expect(find("input[name='service[app_store_private_key]']",
+ visible: false).value).to eq(File.read(Rails.root.join('spec/fixtures/auth_key.p8')))
+ expect(find("input[name='service[app_store_private_key_file_name]']", visible: false).value).to eq('auth_key.p8')
+
+ expect(page).not_to have_content('Drag your Private Key file here or click to upload.')
+ expect(page).to have_content('auth_key.p8')
+ end
+end
diff --git a/spec/fixtures/auth_key.p8 b/spec/fixtures/auth_key.p8
new file mode 100644
index 00000000000..1b53126536e
--- /dev/null
+++ b/spec/fixtures/auth_key.p8
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKS+CfS9GcRSdYSN
+SzyH5QJQBr5umRL6E+KilOV39iYFO/9oHjUdapTRWkrwnNPCp7qaeck4Jr8iv14t
+PVNDfNr76eGb6/3YknOAP0QOjLWunoC8kjU+N/JHU52NrUeX3qEy8EKV9LeCDJcB
+kBk+Yejn9nypg8c7sLsn33CB6i3bAgMBAAECgYA2D26w80T7WZvazYr86BNMePpd
+j2mIAqx32KZHzt/lhh40J/SRtX9+Kl0Y7nBoRR5Ja9u/HkAIxNxLiUjwg9r6cpg/
+uITEF5nMt7lAk391BuI+7VOZZGbJDsq2ulPd6lO+C8Kq/PI/e4kXcIjeH6KwQsuR
+5vrXfBZ3sQfflaiN4QJBANBt8JY2LIGQF8o89qwUpRL5vbnKQ4IzZ5+TOl4RLR7O
+AQpJ81tGuINghO7aunctb6rrcKJrxmEH1whzComybrMCQQDKV49nOBudRBAIgG4K
+EnLzsRKISUHMZSJiYTYnablof8cKw1JaQduw7zgrUlLwnroSaAGX88+Jw1f5n2Lh
+Vlg5AkBDdUGnrDLtYBCDEQYZHblrkc7ZAeCllDOWjxUV+uMqlCv8A4Ey6omvY57C
+m6I8DkWVAQx8VPtozhvHjUw80rZHAkB55HWHAM3h13axKG0htCt7klhPsZHpx6MH
+EPjGlXIT+aW2XiPmK3ZlCDcWIenE+lmtbOpI159Wpk8BGXs/s/xBAkEAlAY3ymgx
+63BDJEwvOb2IaP8lDDxNsXx9XJNVvQbv5n15vNsLHbjslHfAhAbxnLQ1fLhUPqSi
+nNp/xedE1YxutQ==
+-----END PRIVATE KEY-----
diff --git a/spec/frontend/integrations/edit/components/integration_form_spec.js b/spec/frontend/integrations/edit/components/integration_form_spec.js
index 383dfb36aa5..2d74fe0a7d8 100644
--- a/spec/frontend/integrations/edit/components/integration_form_spec.js
+++ b/spec/frontend/integrations/edit/components/integration_form_spec.js
@@ -507,30 +507,21 @@ describe('IntegrationForm', () => {
const dummyHelp = 'Foo Help';
it.each`
- integration | flagIsOn | helpHtml | sections | shouldShowSections | shouldShowHelp
- ${INTEGRATION_FORM_TYPE_SLACK} | ${false} | ${''} | ${[]} | ${false} | ${false}
- ${INTEGRATION_FORM_TYPE_SLACK} | ${false} | ${dummyHelp} | ${[]} | ${false} | ${true}
- ${INTEGRATION_FORM_TYPE_SLACK} | ${false} | ${undefined} | ${[mockSectionConnection]} | ${false} | ${false}
- ${INTEGRATION_FORM_TYPE_SLACK} | ${false} | ${dummyHelp} | ${[mockSectionConnection]} | ${false} | ${true}
- ${INTEGRATION_FORM_TYPE_SLACK} | ${true} | ${''} | ${[]} | ${false} | ${false}
- ${INTEGRATION_FORM_TYPE_SLACK} | ${true} | ${dummyHelp} | ${[]} | ${false} | ${true}
- ${INTEGRATION_FORM_TYPE_SLACK} | ${true} | ${undefined} | ${[mockSectionConnection]} | ${true} | ${false}
- ${INTEGRATION_FORM_TYPE_SLACK} | ${true} | ${dummyHelp} | ${[mockSectionConnection]} | ${true} | ${true}
- ${'foo'} | ${false} | ${''} | ${[]} | ${false} | ${false}
- ${'foo'} | ${false} | ${dummyHelp} | ${[]} | ${false} | ${true}
- ${'foo'} | ${false} | ${undefined} | ${[mockSectionConnection]} | ${true} | ${false}
- ${'foo'} | ${false} | ${dummyHelp} | ${[mockSectionConnection]} | ${true} | ${false}
- ${'foo'} | ${true} | ${''} | ${[]} | ${false} | ${false}
- ${'foo'} | ${true} | ${dummyHelp} | ${[]} | ${false} | ${true}
- ${'foo'} | ${true} | ${undefined} | ${[mockSectionConnection]} | ${true} | ${false}
- ${'foo'} | ${true} | ${dummyHelp} | ${[mockSectionConnection]} | ${true} | ${false}
+ integration | helpHtml | sections | shouldShowSections | shouldShowHelp
+ ${INTEGRATION_FORM_TYPE_SLACK} | ${''} | ${[]} | ${false} | ${false}
+ ${INTEGRATION_FORM_TYPE_SLACK} | ${dummyHelp} | ${[]} | ${false} | ${true}
+ ${INTEGRATION_FORM_TYPE_SLACK} | ${undefined} | ${[mockSectionConnection]} | ${true} | ${false}
+ ${INTEGRATION_FORM_TYPE_SLACK} | ${dummyHelp} | ${[mockSectionConnection]} | ${true} | ${true}
+ ${'foo'} | ${''} | ${[]} | ${false} | ${false}
+ ${'foo'} | ${dummyHelp} | ${[]} | ${false} | ${true}
+ ${'foo'} | ${undefined} | ${[mockSectionConnection]} | ${true} | ${false}
+ ${'foo'} | ${dummyHelp} | ${[mockSectionConnection]} | ${true} | ${false}
`(
- '$sections sections, and "$helpHtml" helpHtml when the FF is "$flagIsOn" for "$integration" integration',
- ({ integration, flagIsOn, helpHtml, sections, shouldShowSections, shouldShowHelp }) => {
+ '$sections sections, and "$helpHtml" helpHtml for "$integration" integration',
+ ({ integration, helpHtml, sections, shouldShowSections, shouldShowHelp }) => {
createComponent({
provide: {
helpHtml,
- glFeatures: { integrationSlackAppNotifications: flagIsOn },
},
customStateProps: {
sections,
@@ -553,20 +544,15 @@ describe('IntegrationForm', () => {
${false} | ${true} | ${'When having only the fields without a section'}
`('$description', ({ hasSections, hasFieldsWithoutSections }) => {
it.each`
- prefix | integration | shouldUpgradeSlack | flagIsOn | shouldShowAlert
- ${'does'} | ${INTEGRATION_FORM_TYPE_SLACK} | ${true} | ${true} | ${true}
- ${'does not'} | ${INTEGRATION_FORM_TYPE_SLACK} | ${false} | ${true} | ${false}
- ${'does not'} | ${INTEGRATION_FORM_TYPE_SLACK} | ${true} | ${false} | ${false}
- ${'does not'} | ${'foo'} | ${true} | ${true} | ${false}
- ${'does not'} | ${'foo'} | ${false} | ${true} | ${false}
- ${'does not'} | ${'foo'} | ${true} | ${false} | ${false}
+ prefix | integration | shouldUpgradeSlack | shouldShowAlert
+ ${'does'} | ${INTEGRATION_FORM_TYPE_SLACK} | ${true} | ${true}
+ ${'does not'} | ${INTEGRATION_FORM_TYPE_SLACK} | ${false} | ${false}
+ ${'does not'} | ${'foo'} | ${true} | ${false}
+ ${'does not'} | ${'foo'} | ${false} | ${false}
`(
- '$prefix render the upgrade warning when we are in "$integration" integration with the flag "$flagIsOn" and Slack-needs-upgrade is "$shouldUpgradeSlack" and have sections',
- ({ integration, shouldUpgradeSlack, flagIsOn, shouldShowAlert }) => {
+ '$prefix render the upgrade warning when we are in "$integration" integration with Slack-needs-upgrade is "$shouldUpgradeSlack" and have sections',
+ ({ integration, shouldUpgradeSlack, shouldShowAlert }) => {
createComponent({
- provide: {
- glFeatures: { integrationSlackAppNotifications: flagIsOn },
- },
customStateProps: {
shouldUpgradeSlack,
type: integration,
diff --git a/spec/frontend/integrations/edit/components/sections/apple_app_store_spec.js b/spec/frontend/integrations/edit/components/sections/apple_app_store_spec.js
new file mode 100644
index 00000000000..62f0439a13f
--- /dev/null
+++ b/spec/frontend/integrations/edit/components/sections/apple_app_store_spec.js
@@ -0,0 +1,57 @@
+import { shallowMount } from '@vue/test-utils';
+
+import IntegrationSectionAppleAppStore from '~/integrations/edit/components/sections/apple_app_store.vue';
+import UploadDropzoneField from '~/integrations/edit/components/upload_dropzone_field.vue';
+import { createStore } from '~/integrations/edit/store';
+
+describe('IntegrationSectionAppleAppStore', () => {
+ let wrapper;
+
+ const createComponent = (componentFields) => {
+ const store = createStore({
+ customState: { ...componentFields },
+ });
+ wrapper = shallowMount(IntegrationSectionAppleAppStore, {
+ store,
+ });
+ };
+
+ const componentFields = (fileName = '') => {
+ return {
+ fields: [
+ {
+ name: 'app_store_private_key_file_name',
+ value: fileName,
+ },
+ ],
+ };
+ };
+
+ const findUploadDropzoneField = () => wrapper.findComponent(UploadDropzoneField);
+
+ describe('computed properties', () => {
+ it('renders UploadDropzoneField with default values', () => {
+ createComponent(componentFields());
+
+ const field = findUploadDropzoneField();
+
+ expect(field.exists()).toBe(true);
+ expect(field.props()).toMatchObject({
+ label: 'The Apple App Store Connect Private Key (.p8)',
+ helpText: '',
+ });
+ });
+
+ it('renders UploadDropzoneField with custom values for an attached file', () => {
+ createComponent(componentFields('fileName.txt'));
+
+ const field = findUploadDropzoneField();
+
+ expect(field.exists()).toBe(true);
+ expect(field.props()).toMatchObject({
+ label: 'Upload a new Apple App Store Connect Private Key (replace fileName.txt)',
+ helpText: 'Leave empty to use your current Private Key.',
+ });
+ });
+ });
+});
diff --git a/spec/frontend/integrations/edit/components/upload_dropzone_field_spec.js b/spec/frontend/integrations/edit/components/upload_dropzone_field_spec.js
new file mode 100644
index 00000000000..36e20db0022
--- /dev/null
+++ b/spec/frontend/integrations/edit/components/upload_dropzone_field_spec.js
@@ -0,0 +1,88 @@
+import { mount } from '@vue/test-utils';
+import { GlAlert } from '@gitlab/ui';
+import { nextTick } from 'vue';
+
+import UploadDropzoneField from '~/integrations/edit/components/upload_dropzone_field.vue';
+import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
+import { mockField } from '../mock_data';
+
+describe('UploadDropzoneField', () => {
+ let wrapper;
+
+ const contentsInputName = 'service[app_store_private_key]';
+ const fileNameInputName = 'service[app_store_private_key_file_name]';
+
+ const createComponent = (props) => {
+ wrapper = mount(UploadDropzoneField, {
+ propsData: {
+ ...mockField,
+ ...props,
+ name: contentsInputName,
+ label: 'Input Label',
+ fileInputName: fileNameInputName,
+ },
+ });
+ };
+
+ const findGlAlert = () => wrapper.findComponent(GlAlert);
+ const findUploadDropzone = () => wrapper.findComponent(UploadDropzone);
+ const findFileContentsHiddenInput = () => wrapper.find(`input[name="${contentsInputName}"]`);
+ const findFileNameHiddenInput = () => wrapper.find(`input[name="${fileNameInputName}"]`);
+
+ describe('template', () => {
+ it('adds the expected file inputFieldName', () => {
+ createComponent();
+
+ expect(findUploadDropzone().props('inputFieldName')).toBe('service[dropzone_file_name]');
+ });
+
+ it('adds a disabled, hidden text input for the file contents', () => {
+ createComponent();
+
+ expect(findFileContentsHiddenInput().attributes('name')).toBe(contentsInputName);
+ expect(findFileContentsHiddenInput().attributes('disabled')).toBeDefined();
+ });
+
+ it('adds a disabled, hidden text input for the file name', () => {
+ createComponent();
+
+ expect(findFileNameHiddenInput().attributes('name')).toBe(fileNameInputName);
+ expect(findFileNameHiddenInput().attributes('disabled')).toBeDefined();
+ });
+ });
+
+ describe('clearError', () => {
+ it('clears uploadError when called', async () => {
+ createComponent();
+
+ expect(findGlAlert().exists()).toBe(false);
+
+ findUploadDropzone().vm.$emit('error');
+ await nextTick();
+
+ expect(findGlAlert().exists()).toBe(true);
+ expect(findGlAlert().text()).toBe(
+ 'Error: You are trying to upload something other than an allowed file.',
+ );
+
+ findGlAlert().vm.$emit('dismiss');
+ await nextTick();
+
+ expect(findGlAlert().exists()).toBe(false);
+ });
+ });
+
+ describe('onError', () => {
+ it('assigns uploadError to the supplied custom message', async () => {
+ const message = 'test error message';
+ createComponent({ errorMessage: message });
+
+ findUploadDropzone().vm.$emit('error');
+
+ await nextTick();
+
+ expect(findGlAlert().exists()).toBe(true);
+ expect(findGlAlert().text()).toBe(message);
+ });
+ });
+});
diff --git a/spec/frontend/issues/list/components/issues_list_app_spec.js b/spec/frontend/issues/list/components/issues_list_app_spec.js
index 8281ce0ed1a..93b9868b38b 100644
--- a/spec/frontend/issues/list/components/issues_list_app_spec.js
+++ b/spec/frontend/issues/list/components/issues_list_app_spec.js
@@ -15,6 +15,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import {
getIssuesCountsQueryResponse,
getIssuesQueryResponse,
+ getIssuesQueryEmptyResponse,
filteredTokens,
locationSearch,
setSortPreferenceMutationResponse,
@@ -154,7 +155,24 @@ describe('CE IssuesListApp component', () => {
router = new VueRouter({ mode: 'history' });
return mountFn(IssuesListApp, {
- apolloProvider: createMockApollo(requestHandlers),
+ apolloProvider: createMockApollo(
+ requestHandlers,
+ {},
+ {
+ typePolicies: {
+ Query: {
+ fields: {
+ project: {
+ merge: true,
+ },
+ group: {
+ merge: true,
+ },
+ },
+ },
+ },
+ },
+ ),
router,
provide: {
...defaultProvide,
@@ -180,7 +198,6 @@ describe('CE IssuesListApp component', () => {
describe('IssuableList', () => {
beforeEach(() => {
wrapper = mountComponent();
- jest.runOnlyPendingTimers();
return waitForPromises();
});
@@ -247,7 +264,6 @@ describe('CE IssuesListApp component', () => {
mountFn: mount,
});
- jest.runOnlyPendingTimers();
return waitForPromises();
});
@@ -477,7 +493,12 @@ describe('CE IssuesListApp component', () => {
describe('empty states', () => {
describe('when there are issues', () => {
beforeEach(() => {
- wrapper = mountComponent({ provide: { hasAnyIssues: true }, mountFn: mount });
+ wrapper = mountComponent({
+ provide: { hasAnyIssues: true },
+ mountFn: mount,
+ issuesQueryResponse: getIssuesQueryEmptyResponse,
+ });
+ return waitForPromises();
});
it('shows EmptyStateWithAnyIssues empty state', () => {
@@ -599,7 +620,6 @@ describe('CE IssuesListApp component', () => {
wrapper = mountComponent({
[mountOption]: jest.fn().mockRejectedValue(new Error('ERROR')),
});
- jest.runOnlyPendingTimers();
return waitForPromises();
});
@@ -620,8 +640,9 @@ describe('CE IssuesListApp component', () => {
describe('events', () => {
describe('when "click-tab" event is emitted by IssuableList', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper = mountComponent();
+ await waitForPromises();
router.push = jest.fn();
findIssuableList().vm.$emit('click-tab', IssuableStates.Closed);
@@ -641,19 +662,25 @@ describe('CE IssuesListApp component', () => {
describe.each`
event | params
${'next-page'} | ${{
- page_after: 'endCursor',
+ page_after: 'endcursor',
page_before: undefined,
first_page_size: 20,
last_page_size: undefined,
+ search: undefined,
+ sort: 'created_date',
+ state: 'opened',
}}
${'previous-page'} | ${{
page_after: undefined,
- page_before: 'startCursor',
+ page_before: 'startcursor',
first_page_size: undefined,
last_page_size: 20,
+ search: undefined,
+ sort: 'created_date',
+ state: 'opened',
}}
`('when "$event" event is emitted by IssuableList', ({ event, params }) => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper = mountComponent({
data: {
pageInfo: {
@@ -662,6 +689,7 @@ describe('CE IssuesListApp component', () => {
},
},
});
+ await waitForPromises();
router.push = jest.fn();
findIssuableList().vm.$emit(event);
@@ -735,7 +763,6 @@ describe('CE IssuesListApp component', () => {
provide: { isProject },
issuesQueryResponse: jest.fn().mockResolvedValue(response(isProject)),
});
- jest.runOnlyPendingTimers();
return waitForPromises();
});
@@ -761,7 +788,6 @@ describe('CE IssuesListApp component', () => {
wrapper = mountComponent({
issuesQueryResponse: jest.fn().mockResolvedValue(response()),
});
- jest.runOnlyPendingTimers();
return waitForPromises();
});
@@ -793,8 +819,6 @@ describe('CE IssuesListApp component', () => {
router.push = jest.fn();
findIssuableList().vm.$emit('sort', sortKey);
- jest.runOnlyPendingTimers();
- await nextTick();
expect(router.push).toHaveBeenCalledWith({
query: expect.objectContaining({ sort: urlSortParams[sortKey] }),
@@ -914,13 +938,13 @@ describe('CE IssuesListApp component', () => {
${'shows users when public visibility is not restricted and is signed in'} | ${false} | ${true} | ${false}
${'hides users when public visibility is restricted and is not signed in'} | ${true} | ${false} | ${true}
${'shows users when public visibility is restricted and is signed in'} | ${true} | ${true} | ${false}
- `('$description', ({ isPublicVisibilityRestricted, isSignedIn, hideUsers }) => {
+ `('$description', async ({ isPublicVisibilityRestricted, isSignedIn, hideUsers }) => {
const mockQuery = jest.fn().mockResolvedValue(defaultQueryResponse);
wrapper = mountComponent({
provide: { isPublicVisibilityRestricted, isSignedIn },
issuesQueryResponse: mockQuery,
});
- jest.runOnlyPendingTimers();
+ await waitForPromises();
expect(mockQuery).toHaveBeenCalledWith(expect.objectContaining({ hideUsers }));
});
@@ -929,7 +953,6 @@ describe('CE IssuesListApp component', () => {
describe('fetching issues', () => {
beforeEach(() => {
wrapper = mountComponent();
- jest.runOnlyPendingTimers();
});
it('fetches issue, incident, test case, and task types', () => {
diff --git a/spec/frontend/issues/list/mock_data.js b/spec/frontend/issues/list/mock_data.js
index 1e8a81116f3..0332f68ddb6 100644
--- a/spec/frontend/issues/list/mock_data.js
+++ b/spec/frontend/issues/list/mock_data.js
@@ -101,6 +101,26 @@ export const getIssuesQueryResponse = {
},
};
+export const getIssuesQueryEmptyResponse = {
+ data: {
+ project: {
+ id: '1',
+ __typename: 'Project',
+ issues: {
+ __persist: true,
+ pageInfo: {
+ __typename: 'PageInfo',
+ hasNextPage: true,
+ hasPreviousPage: false,
+ startCursor: 'startcursor',
+ endCursor: 'endcursor',
+ },
+ nodes: [],
+ },
+ },
+ },
+};
+
export const getIssuesCountsQueryResponse = {
data: {
project: {
diff --git a/spec/frontend/lib/utils/file_upload_spec.js b/spec/frontend/lib/utils/file_upload_spec.js
index f63af2fe0a4..509ddc7ce86 100644
--- a/spec/frontend/lib/utils/file_upload_spec.js
+++ b/spec/frontend/lib/utils/file_upload_spec.js
@@ -1,5 +1,9 @@
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import fileUpload, { getFilename, validateImageName } from '~/lib/utils/file_upload';
+import fileUpload, {
+ getFilename,
+ validateImageName,
+ validateFileFromAllowList,
+} from '~/lib/utils/file_upload';
describe('File upload', () => {
beforeEach(() => {
@@ -89,3 +93,19 @@ describe('file name validator', () => {
expect(validateImageName(file)).toBe('image.png');
});
});
+
+describe('validateFileFromAllowList', () => {
+ it('returns true if the file type is in the allowed list', () => {
+ const allowList = ['.foo', '.bar'];
+ const fileName = 'file.foo';
+
+ expect(validateFileFromAllowList(fileName, allowList)).toBe(true);
+ });
+
+ it('returns false if the file type is in the allowed list', () => {
+ const allowList = ['.foo', '.bar'];
+ const fileName = 'file.baz';
+
+ expect(validateFileFromAllowList(fileName, allowList)).toBe(false);
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/help_center_spec.js b/spec/frontend/super_sidebar/components/help_center_spec.js
index bc847a3e159..1d072c0ba3c 100644
--- a/spec/frontend/super_sidebar/components/help_center_spec.js
+++ b/spec/frontend/super_sidebar/components/help_center_spec.js
@@ -68,18 +68,23 @@ describe('HelpCenter component', () => {
});
describe('showKeyboardShortcuts', () => {
+ let button;
+
beforeEach(() => {
jest.spyOn(wrapper.vm.$refs.dropdown, 'close');
- window.toggleShortcutsHelp = jest.fn();
- findButton('Keyboard shortcuts ?').click();
+
+ button = findButton('Keyboard shortcuts ?');
});
it('closes the dropdown', () => {
+ button.click();
expect(wrapper.vm.$refs.dropdown.close).toHaveBeenCalled();
});
it('shows the keyboard shortcuts modal', () => {
- expect(window.toggleShortcutsHelp).toHaveBeenCalled();
+ // This relies on the event delegation set up by the Shortcuts class in
+ // ~/behaviors/shortcuts/shortcuts.js.
+ expect(button.classList.contains('js-shortcuts-modal-trigger')).toBe(true);
});
});
diff --git a/spec/frontend/vue_shared/components/timezone_dropdown/timezone_dropdown_spec.js b/spec/frontend/vue_shared/components/timezone_dropdown/timezone_dropdown_spec.js
index c8351ed61d7..c891c4700c9 100644
--- a/spec/frontend/vue_shared/components/timezone_dropdown/timezone_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/timezone_dropdown/timezone_dropdown_spec.js
@@ -1,4 +1,4 @@
-import { GlDropdownItem, GlDropdown } from '@gitlab/ui';
+import { GlDropdownItem, GlDropdown, GlSearchBoxByType } from '@gitlab/ui';
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import TimezoneDropdown from '~/vue_shared/components/timezone_dropdown/timezone_dropdown.vue';
@@ -9,7 +9,9 @@ describe('Deploy freeze timezone dropdown', () => {
let wrapper;
let store;
- const createComponent = (searchTerm, selectedTimezone) => {
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+
+ const createComponent = async (searchTerm, selectedTimezone) => {
wrapper = shallowMountExtended(TimezoneDropdown, {
store,
propsData: {
@@ -19,9 +21,8 @@ describe('Deploy freeze timezone dropdown', () => {
},
});
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ searchTerm });
+ findSearchBox().vm.$emit('input', searchTerm);
+ await nextTick();
};
const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
@@ -35,8 +36,8 @@ describe('Deploy freeze timezone dropdown', () => {
});
describe('No time zones found', () => {
- beforeEach(() => {
- createComponent('UTC timezone');
+ beforeEach(async () => {
+ await createComponent('UTC timezone');
});
it('renders empty results message', () => {
@@ -45,8 +46,8 @@ describe('Deploy freeze timezone dropdown', () => {
});
describe('Search term is empty', () => {
- beforeEach(() => {
- createComponent('');
+ beforeEach(async () => {
+ await createComponent('');
});
it('renders all timezones when search term is empty', () => {
@@ -55,8 +56,8 @@ describe('Deploy freeze timezone dropdown', () => {
});
describe('Time zones found', () => {
- beforeEach(() => {
- createComponent('Alaska');
+ beforeEach(async () => {
+ await createComponent('Alaska');
});
it('renders only the time zone searched for', () => {
@@ -87,8 +88,8 @@ describe('Deploy freeze timezone dropdown', () => {
});
describe('Selected time zone not found', () => {
- beforeEach(() => {
- createComponent('', 'Berlin');
+ beforeEach(async () => {
+ await createComponent('', 'Berlin');
});
it('renders empty selections', () => {
@@ -101,8 +102,8 @@ describe('Deploy freeze timezone dropdown', () => {
});
describe('Selected time zone found', () => {
- beforeEach(() => {
- createComponent('', 'Europe/Berlin');
+ beforeEach(async () => {
+ await createComponent('', 'Europe/Berlin');
});
it('renders selected time zone as dropdown label', () => {
diff --git a/spec/frontend/work_items/components/work_item_detail_spec.js b/spec/frontend/work_items/components/work_item_detail_spec.js
index 64a7502671e..38e1903ad47 100644
--- a/spec/frontend/work_items/components/work_item_detail_spec.js
+++ b/spec/frontend/work_items/components/work_item_detail_spec.js
@@ -420,6 +420,12 @@ describe('WorkItemDetail component', () => {
expect(findParentButton().props('icon')).toBe(mockParent.parent.workItemType.iconName);
});
+ it('shows parent title and iid', () => {
+ expect(findParentButton().text()).toBe(
+ `${mockParent.parent.title} #${mockParent.parent.iid}`,
+ );
+ });
+
it('sets the parent breadcrumb URL pointing to issue page when parent type is `Issue`', () => {
expect(findParentButton().attributes().href).toBe('../../issues/5');
});
@@ -441,6 +447,11 @@ describe('WorkItemDetail component', () => {
expect(findParentButton().attributes().href).toBe(mockParentObjective.parent.webUrl);
});
+
+ it('shows work item type and iid', () => {
+ const { iid, workItemType } = workItemQueryResponse.data.workItem;
+ expect(findParent().text()).toContain(`${workItemType.name} #${iid}`);
+ });
});
});
diff --git a/spec/lib/sidebars/menu_spec.rb b/spec/lib/sidebars/menu_spec.rb
index 983fc19ec0f..c84e04a738f 100644
--- a/spec/lib/sidebars/menu_spec.rb
+++ b/spec/lib/sidebars/menu_spec.rb
@@ -4,20 +4,14 @@ require 'spec_helper'
RSpec.describe Sidebars::Menu, feature_category: :navigation do
let(:menu) { described_class.new(context) }
- let(:context) do
- Sidebars::Context.new(current_user: nil, container: nil, route_is_active: ->(x) { x[:controller] == 'fooc' })
- end
-
- let(:menu_item) do
- Sidebars::MenuItem.new(title: 'foo2', link: 'foo2', active_routes: { controller: 'fooc' })
- end
+ let(:context) { Sidebars::Context.new(current_user: nil, container: nil) }
let(:nil_menu_item) { Sidebars::NilMenuItem.new(item_id: :foo) }
describe '#all_active_routes' do
it 'gathers all active routes of items and the current menu' do
menu.add_item(Sidebars::MenuItem.new(title: 'foo1', link: 'foo1', active_routes: { path: %w(bar test) }))
- menu.add_item(menu_item)
+ menu.add_item(Sidebars::MenuItem.new(title: 'foo2', link: 'foo2', active_routes: { controller: 'fooc' }))
menu.add_item(Sidebars::MenuItem.new(title: 'foo3', link: 'foo3', active_routes: { controller: 'barc' }))
menu.add_item(nil_menu_item)
@@ -29,39 +23,36 @@ RSpec.describe Sidebars::Menu, feature_category: :navigation do
end
describe '#serialize_for_super_sidebar' do
- it 'returns itself and all renderable menu entries' do
- menu.add_item(menu_item)
- menu.add_item(Sidebars::MenuItem.new(title: 'foo3', link: 'foo3', active_routes: { controller: 'barc' }))
+ it 'returns a tree-like structure of itself and all menu items' do
+ menu.add_item(Sidebars::MenuItem.new(title: 'Is active', link: 'foo2', active_routes: { controller: 'fooc' }))
+ menu.add_item(Sidebars::MenuItem.new(title: 'Not active', link: 'foo3', active_routes: { controller: 'barc' }))
menu.add_item(nil_menu_item)
+ allow(context).to receive(:route_is_active).and_return(->(x) { x[:controller] == 'fooc' })
allow(menu).to receive(:title).and_return('Title')
allow(menu).to receive(:active_routes).and_return({ path: 'foo' })
- allow(menu).to receive(:object_id).and_return(31)
- expect(menu.serialize_for_super_sidebar).to eq([
+ expect(menu.serialize_for_super_sidebar).to eq(
{
- id: 31,
- parent_id: nil,
title: "Title",
icon: nil,
link: "foo2",
- is_active: false
- },
- {
- parent_id: 31,
- title: "foo2",
- icon: nil,
- link: "foo2",
- is_active: true
- },
- {
- parent_id: 31,
- title: "foo3",
- icon: nil,
- link: "foo3",
- is_active: false
- }
- ])
+ is_active: true,
+ items: [
+ {
+ title: "Is active",
+ icon: nil,
+ link: "foo2",
+ is_active: true
+ },
+ {
+ title: "Not active",
+ icon: nil,
+ link: "foo3",
+ is_active: false
+ }
+ ]
+ })
end
end
diff --git a/spec/lib/sidebars/panel_spec.rb b/spec/lib/sidebars/panel_spec.rb
index faba356ac70..2c1b9c73595 100644
--- a/spec/lib/sidebars/panel_spec.rb
+++ b/spec/lib/sidebars/panel_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Sidebars::Panel, feature_category: :navigation do
let(:panel) { Sidebars::Panel.new(context) }
let(:menu1) { Sidebars::Menu.new(context) }
let(:menu2) { Sidebars::Menu.new(context) }
+ let(:menu3) { Sidebars::Menu.new(context) }
describe '#renderable_menus' do
it 'returns only renderable menus' do
@@ -21,57 +22,21 @@ RSpec.describe Sidebars::Panel, feature_category: :navigation do
end
describe '#super_sidebar_menu_items' do
- it "groups items under their parent and marks parent as active if a child item is active" do
+ it "serializes every renderable menu and returns a flattened result" do
panel.add_menu(menu1)
panel.add_menu(menu2)
+ panel.add_menu(menu3)
allow(menu1).to receive(:render?).and_return(true)
+ allow(menu1).to receive(:serialize_for_super_sidebar).and_return("foo")
+
allow(menu2).to receive(:render?).and_return(false)
- allow(menu1).to receive(:serialize_for_super_sidebar).and_return([
- {
- id: 31,
- parent_id: nil,
- title: "Title",
- is_active: false
- },
- {
- parent_id: "non_existent_which_makes_this_top_level",
- title: "Title 2",
- is_active: false
- },
- {
- parent_id: 31,
- title: "Title > Item 1",
- is_active: true
- },
- {
- parent_id: 31,
- title: "Title > Item 2",
- is_active: false
- }
- ])
-
- expect(panel.super_sidebar_menu_items).to eq([
- {
- id: 31,
- title: "Title",
- is_active: true,
- items: [
- {
- title: "Title > Item 1",
- is_active: true
- },
- {
- title: "Title > Item 2",
- is_active: false
- }
- ]
- },
- {
- title: "Title 2",
- is_active: false
- }
- ])
+ allow(menu2).to receive(:serialize_for_super_sidebar).and_return("i-should-not-appear-in-results")
+
+ allow(menu3).to receive(:render?).and_return(true)
+ allow(menu3).to receive(:serialize_for_super_sidebar).and_return(%w[bar baz])
+
+ expect(panel.super_sidebar_menu_items).to eq(%w[foo bar baz])
end
end
diff --git a/spec/models/concerns/ci/partitionable/partitioned_filter_spec.rb b/spec/models/concerns/ci/partitionable/partitioned_filter_spec.rb
deleted file mode 100644
index bb25d7d1665..00000000000
--- a/spec/models/concerns/ci/partitionable/partitioned_filter_spec.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Ci::Partitionable::PartitionedFilter, :aggregate_failures, feature_category: :continuous_integration do
- before do
- create_tables(<<~SQL)
- CREATE TABLE _test_ci_jobs_metadata (
- id serial NOT NULL,
- partition_id int NOT NULL DEFAULT 10,
- name text,
- PRIMARY KEY (id, partition_id)
- ) PARTITION BY LIST(partition_id);
-
- CREATE TABLE _test_ci_jobs_metadata_1
- PARTITION OF _test_ci_jobs_metadata
- FOR VALUES IN (10);
- SQL
- end
-
- let(:model) do
- Class.new(Ci::ApplicationRecord) do
- include Ci::Partitionable::PartitionedFilter
-
- self.primary_key = :id
- self.table_name = :_test_ci_jobs_metadata
-
- def self.name
- 'TestCiJobMetadata'
- end
- end
- end
-
- let!(:record) { model.create! }
-
- let(:where_filter) do
- /WHERE "_test_ci_jobs_metadata"."id" = #{record.id} AND "_test_ci_jobs_metadata"."partition_id" = 10/
- end
-
- describe '#save' do
- it 'uses id and partition_id' do
- record.name = 'test'
- recorder = ActiveRecord::QueryRecorder.new { record.save! }
-
- expect(recorder.log).to include(where_filter)
- expect(record.name).to eq('test')
- end
- end
-
- describe '#update' do
- it 'uses id and partition_id' do
- recorder = ActiveRecord::QueryRecorder.new { record.update!(name: 'test') }
-
- expect(recorder.log).to include(where_filter)
- expect(record.name).to eq('test')
- end
- end
-
- describe '#delete' do
- it 'uses id and partition_id' do
- recorder = ActiveRecord::QueryRecorder.new { record.delete }
-
- expect(recorder.log).to include(where_filter)
- expect(model.count).to be_zero
- end
- end
-
- describe '#destroy' do
- it 'uses id and partition_id' do
- recorder = ActiveRecord::QueryRecorder.new { record.destroy! }
-
- expect(recorder.log).to include(where_filter)
- expect(model.count).to be_zero
- end
- end
-
- def create_tables(table_sql)
- Ci::ApplicationRecord.connection.execute(table_sql)
- end
-end
diff --git a/spec/models/concerns/ci/partitionable_spec.rb b/spec/models/concerns/ci/partitionable_spec.rb
index 430ef57d493..f3d33c971c7 100644
--- a/spec/models/concerns/ci/partitionable_spec.rb
+++ b/spec/models/concerns/ci/partitionable_spec.rb
@@ -40,28 +40,4 @@ RSpec.describe Ci::Partitionable do
it { expect(ci_model.ancestors).to include(described_class::Switch) }
end
-
- context 'with partitioned options' do
- before do
- stub_const("#{described_class}::Testing::PARTITIONABLE_MODELS", [ci_model.name])
-
- ci_model.include(described_class)
- ci_model.partitionable scope: ->(r) { 1 }, partitioned: partitioned
- end
-
- context 'when partitioned is true' do
- let(:partitioned) { true }
-
- it { expect(ci_model.ancestors).to include(described_class::PartitionedFilter) }
- it { expect(ci_model).to be_partitioned }
- end
-
- context 'when partitioned is false' do
- let(:partitioned) { false }
-
- it { expect(ci_model.ancestors).not_to include(described_class::PartitionedFilter) }
-
- it { expect(ci_model).not_to be_partitioned }
- end
- end
end
diff --git a/spec/models/integrations/apple_app_store_spec.rb b/spec/models/integrations/apple_app_store_spec.rb
index 1a57f556895..b2a52c8aaf0 100644
--- a/spec/models/integrations/apple_app_store_spec.rb
+++ b/spec/models/integrations/apple_app_store_spec.rb
@@ -12,6 +12,7 @@ RSpec.describe Integrations::AppleAppStore, feature_category: :mobile_devops do
it { is_expected.to validate_presence_of :app_store_issuer_id }
it { is_expected.to validate_presence_of :app_store_key_id }
it { is_expected.to validate_presence_of :app_store_private_key }
+ it { is_expected.to validate_presence_of :app_store_private_key_file_name }
it { is_expected.to allow_value('aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee').for(:app_store_issuer_id) }
it { is_expected.not_to allow_value('abcde').for(:app_store_issuer_id) }
it { is_expected.to allow_value(File.read('spec/fixtures/ssl_key.pem')).for(:app_store_private_key) }
@@ -28,8 +29,8 @@ RSpec.describe Integrations::AppleAppStore, feature_category: :mobile_devops do
describe '#fields' do
it 'returns custom fields' do
- expect(apple_app_store_integration.fields.pluck(:name)).to eq(%w[app_store_issuer_id app_store_key_id
- app_store_private_key])
+ expect(apple_app_store_integration.fields.pluck(:name)).to match_array(%w[app_store_issuer_id app_store_key_id
+ app_store_private_key app_store_private_key_file_name])
end
end
diff --git a/spec/services/clusters/agent_tokens/create_service_spec.rb b/spec/services/clusters/agent_tokens/create_service_spec.rb
index dc7abd1504b..519a3ba7ce5 100644
--- a/spec/services/clusters/agent_tokens/create_service_spec.rb
+++ b/spec/services/clusters/agent_tokens/create_service_spec.rb
@@ -2,14 +2,14 @@
require 'spec_helper'
-RSpec.describe Clusters::AgentTokens::CreateService do
- subject(:service) { described_class.new(container: project, current_user: user, params: params) }
+RSpec.describe Clusters::AgentTokens::CreateService, feature_category: :kubernetes_management do
+ subject(:service) { described_class.new(agent: cluster_agent, current_user: user, params: params) }
let_it_be(:user) { create(:user) }
let(:cluster_agent) { create(:cluster_agent) }
let(:project) { cluster_agent.project }
- let(:params) { { agent_id: cluster_agent.id, description: 'token description', name: 'token name' } }
+ let(:params) { { description: 'token description', name: 'token name' } }
describe '#execute' do
subject { service.execute }
@@ -75,7 +75,7 @@ RSpec.describe Clusters::AgentTokens::CreateService do
it 'returns validation errors', :aggregate_failures do
expect(subject.status).to eq(:error)
- expect(subject.message).to eq(["Agent must exist", "Name can't be blank"])
+ expect(subject.message).to eq(["Name can't be blank"])
end
end
end
diff --git a/spec/services/clusters/agent_tokens/revoke_service_spec.rb b/spec/services/clusters/agent_tokens/revoke_service_spec.rb
new file mode 100644
index 00000000000..24485a5f66d
--- /dev/null
+++ b/spec/services/clusters/agent_tokens/revoke_service_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Clusters::AgentTokens::RevokeService, feature_category: :kubernetes_management do
+ describe '#execute' do
+ let(:agent) { create(:cluster_agent) }
+ let(:agent_token) { create(:cluster_agent_token, agent: agent) }
+ let(:project) { agent.project }
+ let(:user) { agent.created_by_user }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ context 'when user is authorized' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ context 'when user revokes agent token' do
+ it 'succeeds' do
+ described_class.new(token: agent_token, current_user: user).execute
+
+ expect(agent_token.revoked?).to be true
+ end
+ end
+
+ context 'when there is a validation failure' do
+ before do
+ agent_token.name = '' # make the record invalid, as we require a name to be present
+ end
+
+ it 'fails without raising an error', :aggregate_failures do
+ result = described_class.new(token: agent_token, current_user: user).execute
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq(["Name can't be blank"])
+ end
+ end
+ end
+
+ context 'when user is not authorized' do
+ let(:unauthorized_user) { create(:user) }
+
+ before do
+ project.add_guest(unauthorized_user)
+ end
+
+ context 'when user attempts to revoke agent token' do
+ it 'fails' do
+ described_class.new(token: agent_token, current_user: unauthorized_user).execute
+
+ expect(agent_token.revoked?).to be false
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
index bf5158c9a92..178dad627ed 100644
--- a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
+++ b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
@@ -74,6 +74,8 @@ Integration.available_integration_names.each do |integration|
hash.merge!(k => File.read('spec/fixtures/ssl_key.pem'))
elsif integration == 'apple_app_store' && k == :app_store_key_id
hash.merge!(k => 'ABC1')
+ elsif integration == 'apple_app_store' && k == :app_store_private_key_file_name
+ hash.merge!(k => 'ssl_key.pem')
else
hash.merge!(k => "someword")
end
diff --git a/spec/support/shared_examples/features/manage_applications_shared_examples.rb b/spec/support/shared_examples/features/manage_applications_shared_examples.rb
index b59f3f1e27b..239fd65c60a 100644
--- a/spec/support/shared_examples/features/manage_applications_shared_examples.rb
+++ b/spec/support/shared_examples/features/manage_applications_shared_examples.rb
@@ -36,7 +36,7 @@ RSpec.shared_examples 'manage applications' do
validate_application(application_name_changed, 'No')
expect(page).not_to have_link('Continue')
- expect(page).to have_content _('The secret is only available when you first create the application')
+ expect(page).to have_content _('The secret is only available when you create the application or renew the secret.')
visit_applications_path