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>2022-07-05 13:20:03 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-07-05 13:20:03 +0300
commitd2612b42b9da6638d70b9d7144f6d427070d042d (patch)
treeed7de87d4b112cae8a45ba186d717ca9768c7d4e /spec
parentd80373b353005e70f44eca8a3bc4a4c5cfbf0e9e (diff)
Add latest changes from gitlab-org/gitlab@15-1-stable-ee
Diffstat (limited to 'spec')
-rw-r--r--spec/config/object_store_settings_spec.rb76
-rw-r--r--spec/features/projects/new_project_spec.rb14
-rw-r--r--spec/frontend/clusters/agents/components/create_token_button_spec.js255
-rw-r--r--spec/frontend/clusters/agents/components/create_token_modal_spec.js223
-rw-r--r--spec/frontend/clusters/agents/components/token_table_spec.js6
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb98
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb37
-rw-r--r--spec/uploaders/object_storage_spec.rb44
8 files changed, 413 insertions, 340 deletions
diff --git a/spec/config/object_store_settings_spec.rb b/spec/config/object_store_settings_spec.rb
index 56ad0943377..1555124fe03 100644
--- a/spec/config/object_store_settings_spec.rb
+++ b/spec/config/object_store_settings_spec.rb
@@ -73,6 +73,7 @@ RSpec.describe ObjectStoreSettings do
expect(settings.artifacts['object_store']['background_upload']).to be false
expect(settings.artifacts['object_store']['proxy_download']).to be false
expect(settings.artifacts['object_store']['remote_directory']).to eq('artifacts')
+ expect(settings.artifacts['object_store']['bucket_prefix']).to eq(nil)
expect(settings.artifacts['object_store']['consolidated_settings']).to be true
expect(settings.artifacts).to eq(settings['artifacts'])
@@ -83,6 +84,7 @@ RSpec.describe ObjectStoreSettings do
expect(settings.lfs['object_store']['background_upload']).to be false
expect(settings.lfs['object_store']['proxy_download']).to be true
expect(settings.lfs['object_store']['remote_directory']).to eq('lfs-objects')
+ expect(settings.lfs['object_store']['bucket_prefix']).to eq(nil)
expect(settings.lfs['object_store']['consolidated_settings']).to be true
expect(settings.lfs).to eq(settings['lfs'])
@@ -90,6 +92,7 @@ RSpec.describe ObjectStoreSettings do
expect(settings.pages['object_store']['enabled']).to be true
expect(settings.pages['object_store']['connection']).to eq(connection)
expect(settings.pages['object_store']['remote_directory']).to eq('pages')
+ expect(settings.pages['object_store']['bucket_prefix']).to eq(nil)
expect(settings.pages['object_store']['consolidated_settings']).to be true
expect(settings.pages).to eq(settings['pages'])
@@ -98,6 +101,18 @@ RSpec.describe ObjectStoreSettings do
expect(settings.external_diffs).to eq(settings['external_diffs'])
end
+ it 'supports bucket prefixes' do
+ config['object_store']['objects']['artifacts']['bucket'] = 'gitlab/artifacts'
+ config['object_store']['objects']['lfs']['bucket'] = 'gitlab/lfs'
+
+ subject
+
+ expect(settings.artifacts['object_store']['remote_directory']).to eq('gitlab')
+ expect(settings.artifacts['object_store']['bucket_prefix']).to eq('artifacts')
+ expect(settings.lfs['object_store']['remote_directory']).to eq('gitlab')
+ expect(settings.lfs['object_store']['bucket_prefix']).to eq('lfs')
+ end
+
it 'raises an error when a bucket is missing' do
config['object_store']['objects']['lfs'].delete('bucket')
@@ -152,6 +167,7 @@ RSpec.describe ObjectStoreSettings do
expect(settings.artifacts['enabled']).to be true
expect(settings.artifacts['object_store']['remote_directory']).to be_nil
+ expect(settings.artifacts['object_store']['bucket_prefix']).to be_nil
expect(settings.artifacts['object_store']['enabled']).to be_falsey
expect(settings.artifacts['object_store']['consolidated_settings']).to be_falsey
end
@@ -177,6 +193,7 @@ RSpec.describe ObjectStoreSettings do
expect(settings.artifacts['object_store']).to be_nil
expect(settings.lfs['object_store']['remote_directory']).to eq('some-bucket')
+ expect(settings.lfs['object_store']['bucket_prefix']).to eq(nil)
# Disable background_upload, regardless of the input config
expect(settings.lfs['object_store']['direct_upload']).to eq(true)
expect(settings.lfs['object_store']['background_upload']).to eq(false)
@@ -203,6 +220,7 @@ RSpec.describe ObjectStoreSettings do
expect(settings.artifacts['object_store']).to be_nil
expect(settings.lfs['object_store']['remote_directory']).to eq('some-bucket')
+ expect(settings.lfs['object_store']['bucket_prefix']).to eq(nil)
# Enable background_upload if the environment variable is available
expect(settings.lfs['object_store']['direct_upload']).to eq(false)
expect(settings.lfs['object_store']['background_upload']).to eq(true)
@@ -220,6 +238,7 @@ RSpec.describe ObjectStoreSettings do
expect(settings['direct_upload']).to be true
expect(settings['background_upload']).to be false
expect(settings['remote_directory']).to be nil
+ expect(settings['bucket_prefix']).to be nil
end
it 'respects original values' do
@@ -234,6 +253,18 @@ RSpec.describe ObjectStoreSettings do
expect(settings['direct_upload']).to be true
expect(settings['background_upload']).to be false
expect(settings['remote_directory']).to eq 'artifacts'
+ expect(settings['bucket_prefix']).to be nil
+ end
+
+ it 'supports bucket prefixes' do
+ original_settings = Settingslogic.new({
+ 'enabled' => true,
+ 'remote_directory' => 'gitlab/artifacts'
+ })
+
+ settings = described_class.legacy_parse(original_settings, 'artifacts')
+ expect(settings['remote_directory']).to eq 'gitlab'
+ expect(settings['bucket_prefix']).to eq 'artifacts'
end
context 'legacy background upload environment variable is enabled' do
@@ -253,6 +284,7 @@ RSpec.describe ObjectStoreSettings do
expect(settings['direct_upload']).to be false
expect(settings['background_upload']).to be true
expect(settings['remote_directory']).to eq 'artifacts'
+ expect(settings['bucket_prefix']).to eq nil
end
end
@@ -273,6 +305,50 @@ RSpec.describe ObjectStoreSettings do
expect(settings['direct_upload']).to be true
expect(settings['background_upload']).to be false
expect(settings['remote_directory']).to eq 'artifacts'
+ expect(settings['bucket_prefix']).to eq nil
+ end
+ end
+ end
+
+ describe '.split_bucket_prefix' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { described_class.split_bucket_prefix(input) }
+
+ context 'valid inputs' do
+ where(:input, :bucket, :prefix) do
+ nil | nil | nil
+ '' | nil | nil
+ 'bucket' | 'bucket' | nil
+ 'bucket/prefix' | 'bucket' | 'prefix'
+ 'bucket/pre/fix' | 'bucket' | 'pre/fix'
+ end
+
+ with_them do
+ it { expect(subject).to eq([bucket, prefix]) }
+ end
+ end
+
+ context 'invalid inputs' do
+ where(:input) do
+ [
+ ['bucket/'],
+ ['bucket/.'],
+ ['bucket/..'],
+ ['bucket/prefix/'],
+ ['bucket/prefix/.'],
+ ['bucket/prefix/..'],
+ ['/bucket/prefix'],
+ ['./bucket/prefix'],
+ ['../bucket/prefix'],
+ ['bucket//prefix'],
+ ['bucket/./prefix'],
+ ['bucket/../prefix']
+ ]
+ end
+
+ with_them do
+ it { expect { subject }.to raise_error(/invalid bucket/) }
end
end
end
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index c323e60bb71..a1e92a79516 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -298,6 +298,20 @@ RSpec.describe 'New project', :js do
end
end
+ context 'Import project options without any sources', :js do
+ before do
+ stub_application_setting(import_sources: [])
+
+ visit new_project_path
+ click_link 'Import project'
+ end
+
+ it 'displays the no import options message' do
+ expect(page).to have_text s_('ProjectsNew|No import options available')
+ expect(page).to have_text s_('ProjectsNew|Contact an administrator to enable options for importing your project.')
+ end
+ end
+
context 'Import project options', :js do
before do
visit new_project_path
diff --git a/spec/frontend/clusters/agents/components/create_token_button_spec.js b/spec/frontend/clusters/agents/components/create_token_button_spec.js
index fb1a3aa2963..73856b74a8d 100644
--- a/spec/frontend/clusters/agents/components/create_token_button_spec.js
+++ b/spec/frontend/clusters/agents/components/create_token_button_spec.js
@@ -1,262 +1,71 @@
-import { GlButton, GlTooltip, GlModal, GlFormInput, GlFormTextarea, GlAlert } from '@gitlab/ui';
-import Vue from 'vue';
-import VueApollo from 'vue-apollo';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import waitForPromises from 'helpers/wait_for_promises';
+import { GlButton, GlTooltip } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { mockTracking } from 'helpers/tracking_helper';
-import {
- EVENT_LABEL_MODAL,
- EVENT_ACTIONS_OPEN,
- TOKEN_NAME_LIMIT,
- TOKEN_STATUS_ACTIVE,
- MAX_LIST_COUNT,
- CREATE_TOKEN_MODAL,
-} from '~/clusters/agents/constants';
-import createNewAgentToken from '~/clusters/agents/graphql/mutations/create_new_agent_token.mutation.graphql';
-import getClusterAgentQuery from '~/clusters/agents/graphql/queries/get_cluster_agent.query.graphql';
-import AgentToken from '~/clusters_list/components/agent_token.vue';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import CreateTokenButton from '~/clusters/agents/components/create_token_button.vue';
-import {
- clusterAgentToken,
- getTokenResponse,
- createAgentTokenErrorResponse,
-} from '../../mock_data';
-
-Vue.use(VueApollo);
+import { CREATE_TOKEN_MODAL } from '~/clusters/agents/constants';
describe('CreateTokenButton', () => {
let wrapper;
- let apolloProvider;
- let trackingSpy;
- let createResponse;
-
- const clusterAgentId = 'cluster-agent-id';
- const cursor = {
- first: MAX_LIST_COUNT,
- last: null,
- };
- const agentName = 'cluster-agent';
- const projectPath = 'path/to/project';
const defaultProvide = {
- agentName,
- projectPath,
canAdminCluster: true,
};
- const propsData = {
- clusterAgentId,
- cursor,
- };
- const findModal = () => wrapper.findComponent(GlModal);
- const findBtn = () => wrapper.findComponent(GlButton);
- const findInput = () => wrapper.findComponent(GlFormInput);
- const findTextarea = () => wrapper.findComponent(GlFormTextarea);
- const findAlert = () => wrapper.findComponent(GlAlert);
+ const findButton = () => wrapper.findComponent(GlButton);
const findTooltip = () => wrapper.findComponent(GlTooltip);
- const findAgentInstructions = () => findModal().findComponent(AgentToken);
- const findButtonByVariant = (variant) =>
- findModal()
- .findAll(GlButton)
- .wrappers.find((button) => button.props('variant') === variant);
- const findActionButton = () => findButtonByVariant('confirm');
- const findCancelButton = () => wrapper.findByTestId('agent-token-close-button');
-
- const expectDisabledAttribute = (element, disabled) => {
- if (disabled) {
- expect(element.attributes('disabled')).toBe('true');
- } else {
- expect(element.attributes('disabled')).toBeUndefined();
- }
- };
-
- const createMockApolloProvider = ({ mutationResponse }) => {
- createResponse = jest.fn().mockResolvedValue(mutationResponse);
-
- return createMockApollo([[createNewAgentToken, createResponse]]);
- };
-
- const writeQuery = () => {
- apolloProvider.clients.defaultClient.cache.writeQuery({
- query: getClusterAgentQuery,
- data: getTokenResponse.data,
- variables: {
- agentName,
- projectPath,
- tokenStatus: TOKEN_STATUS_ACTIVE,
- ...cursor,
- },
- });
- };
- const createWrapper = async ({ provideData = {} } = {}) => {
+ const createWrapper = ({ provideData = {} } = {}) => {
wrapper = shallowMountExtended(CreateTokenButton, {
- apolloProvider,
provide: {
...defaultProvide,
...provideData,
},
- propsData,
+ directives: {
+ GlModalDirective: createMockDirective(),
+ },
stubs: {
- GlModal,
GlTooltip,
},
});
- wrapper.vm.$refs.modal.hide = jest.fn();
-
- trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
};
- const mockCreatedResponse = (mutationResponse) => {
- apolloProvider = createMockApolloProvider({ mutationResponse });
- writeQuery();
-
- createWrapper();
-
- findInput().vm.$emit('input', 'new-token');
- findTextarea().vm.$emit('input', 'new-token-description');
- findActionButton().vm.$emit('click');
-
- return waitForPromises();
- };
-
- beforeEach(() => {
- createWrapper();
- });
-
afterEach(() => {
wrapper.destroy();
- apolloProvider = null;
- createResponse = null;
});
- describe('create agent token action', () => {
- it('displays create agent token button', () => {
- expect(findBtn().text()).toBe('Create token');
+ describe('when user can create token', () => {
+ beforeEach(() => {
+ createWrapper();
});
- describe('when user cannot create token', () => {
- beforeEach(() => {
- createWrapper({ provideData: { canAdminCluster: false } });
- });
-
- it('disabled the button', () => {
- expect(findBtn().attributes('disabled')).toBe('true');
- });
-
- it('shows a disabled tooltip', () => {
- expect(findTooltip().attributes('title')).toBe(
- 'Requires a Maintainer or greater role to perform these actions',
- );
- });
+ it('displays create agent token button', () => {
+ expect(findButton().text()).toBe('Create token');
});
- describe('when user can create a token and clicks the button', () => {
- beforeEach(() => {
- findBtn().vm.$emit('click');
- });
-
- it('displays a token creation modal', () => {
- expect(findModal().isVisible()).toBe(true);
- });
-
- describe('initial state', () => {
- it('renders an input for the token name', () => {
- expect(findInput().exists()).toBe(true);
- expectDisabledAttribute(findInput(), false);
- expect(findInput().attributes('max-length')).toBe(TOKEN_NAME_LIMIT.toString());
- });
-
- it('renders a textarea for the token description', () => {
- expect(findTextarea().exists()).toBe(true);
- expectDisabledAttribute(findTextarea(), false);
- });
-
- it('renders a cancel button', () => {
- expect(findCancelButton().isVisible()).toBe(true);
- expectDisabledAttribute(findCancelButton(), false);
- });
-
- it('renders a disabled next button', () => {
- expect(findActionButton().text()).toBe('Create token');
- expectDisabledAttribute(findActionButton(), true);
- });
-
- it('sends tracking event for modal shown', () => {
- findModal().vm.$emit('show');
- expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS_OPEN, {
- label: EVENT_LABEL_MODAL,
- });
- });
- });
-
- describe('when user inputs the token name', () => {
- beforeEach(() => {
- expectDisabledAttribute(findActionButton(), true);
- findInput().vm.$emit('input', 'new-token');
- });
-
- it('enables the next button', () => {
- expectDisabledAttribute(findActionButton(), false);
- });
- });
-
- describe('when user clicks the create-token button', () => {
- beforeEach(async () => {
- const loadingResponse = new Promise(() => {});
- await mockCreatedResponse(loadingResponse);
-
- findInput().vm.$emit('input', 'new-token');
- findActionButton().vm.$emit('click');
- });
-
- it('disables the create-token button', () => {
- expectDisabledAttribute(findActionButton(), true);
- });
-
- it('hides the cancel button', () => {
- expect(findCancelButton().exists()).toBe(false);
- });
- });
-
- describe('creating a new token', () => {
- beforeEach(async () => {
- await mockCreatedResponse(clusterAgentToken);
- });
+ it('displays create agent token button as not disabled', () => {
+ expect(findButton().attributes('disabled')).toBeUndefined();
+ });
- it('creates a token', () => {
- expect(createResponse).toHaveBeenCalledWith({
- input: { clusterAgentId, name: 'new-token', description: 'new-token-description' },
- });
- });
+ it('triggers the modal', () => {
+ const binding = getBinding(findButton().element, 'gl-modal-directive');
- it('shows agent instructions', () => {
- expect(findAgentInstructions().props()).toMatchObject({
- agentName,
- agentToken: 'token-secret',
- modalId: CREATE_TOKEN_MODAL,
- });
- });
+ expect(binding.value).toBe(CREATE_TOKEN_MODAL);
+ });
+ });
- it('renders a close button', () => {
- expect(findActionButton().isVisible()).toBe(true);
- expect(findActionButton().text()).toBe('Close');
- expectDisabledAttribute(findActionButton(), false);
- });
- });
+ describe('when user cannot create token', () => {
+ beforeEach(() => {
+ createWrapper({ provideData: { canAdminCluster: false } });
+ });
- describe('error creating a new token', () => {
- beforeEach(async () => {
- await mockCreatedResponse(createAgentTokenErrorResponse);
- });
+ it('disabled the button', () => {
+ expect(findButton().attributes('disabled')).toBe('true');
+ });
- it('displays the error message', async () => {
- expect(findAlert().text()).toBe(
- createAgentTokenErrorResponse.data.clusterAgentTokenCreate.errors[0],
- );
- });
- });
+ it('shows a disabled tooltip', () => {
+ expect(findTooltip().attributes('title')).toBe(
+ 'Requires a Maintainer or greater role to perform these actions',
+ );
});
});
});
diff --git a/spec/frontend/clusters/agents/components/create_token_modal_spec.js b/spec/frontend/clusters/agents/components/create_token_modal_spec.js
new file mode 100644
index 00000000000..ad48afe10b6
--- /dev/null
+++ b/spec/frontend/clusters/agents/components/create_token_modal_spec.js
@@ -0,0 +1,223 @@
+import { GlButton, GlModal, GlFormInput, GlFormTextarea, GlAlert } from '@gitlab/ui';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { mockTracking } from 'helpers/tracking_helper';
+import {
+ EVENT_LABEL_MODAL,
+ EVENT_ACTIONS_OPEN,
+ TOKEN_NAME_LIMIT,
+ TOKEN_STATUS_ACTIVE,
+ MAX_LIST_COUNT,
+ CREATE_TOKEN_MODAL,
+} from '~/clusters/agents/constants';
+import createNewAgentToken from '~/clusters/agents/graphql/mutations/create_new_agent_token.mutation.graphql';
+import getClusterAgentQuery from '~/clusters/agents/graphql/queries/get_cluster_agent.query.graphql';
+import AgentToken from '~/clusters_list/components/agent_token.vue';
+import CreateTokenModal from '~/clusters/agents/components/create_token_modal.vue';
+import {
+ clusterAgentToken,
+ getTokenResponse,
+ createAgentTokenErrorResponse,
+} from '../../mock_data';
+
+Vue.use(VueApollo);
+
+describe('CreateTokenModal', () => {
+ let wrapper;
+ let apolloProvider;
+ let trackingSpy;
+ let createResponse;
+
+ const clusterAgentId = 'cluster-agent-id';
+ const cursor = {
+ first: MAX_LIST_COUNT,
+ last: null,
+ };
+ const agentName = 'cluster-agent';
+ const projectPath = 'path/to/project';
+
+ const provide = {
+ agentName,
+ projectPath,
+ };
+ const propsData = {
+ clusterAgentId,
+ cursor,
+ };
+
+ const findModal = () => wrapper.findComponent(GlModal);
+ const findInput = () => wrapper.findComponent(GlFormInput);
+ const findTextarea = () => wrapper.findComponent(GlFormTextarea);
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findAgentInstructions = () => findModal().findComponent(AgentToken);
+ const findButtonByVariant = (variant) =>
+ findModal()
+ .findAll(GlButton)
+ .wrappers.find((button) => button.props('variant') === variant);
+ const findActionButton = () => findButtonByVariant('confirm');
+ const findCancelButton = () => wrapper.findByTestId('agent-token-close-button');
+
+ const expectDisabledAttribute = (element, disabled) => {
+ if (disabled) {
+ expect(element.attributes('disabled')).toBe('true');
+ } else {
+ expect(element.attributes('disabled')).toBeUndefined();
+ }
+ };
+
+ const createMockApolloProvider = ({ mutationResponse }) => {
+ createResponse = jest.fn().mockResolvedValue(mutationResponse);
+
+ return createMockApollo([[createNewAgentToken, createResponse]]);
+ };
+
+ const writeQuery = () => {
+ apolloProvider.clients.defaultClient.cache.writeQuery({
+ query: getClusterAgentQuery,
+ data: getTokenResponse.data,
+ variables: {
+ agentName,
+ projectPath,
+ tokenStatus: TOKEN_STATUS_ACTIVE,
+ ...cursor,
+ },
+ });
+ };
+
+ const createWrapper = () => {
+ wrapper = shallowMountExtended(CreateTokenModal, {
+ apolloProvider,
+ provide,
+ propsData,
+ stubs: {
+ GlModal,
+ },
+ });
+ wrapper.vm.$refs.modal.hide = jest.fn();
+
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ };
+
+ const mockCreatedResponse = (mutationResponse) => {
+ apolloProvider = createMockApolloProvider({ mutationResponse });
+ writeQuery();
+
+ createWrapper();
+
+ findInput().vm.$emit('input', 'new-token');
+ findTextarea().vm.$emit('input', 'new-token-description');
+ findActionButton().vm.$emit('click');
+
+ return waitForPromises();
+ };
+
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ apolloProvider = null;
+ createResponse = null;
+ });
+
+ describe('initial state', () => {
+ it('renders an input for the token name', () => {
+ expect(findInput().exists()).toBe(true);
+ expectDisabledAttribute(findInput(), false);
+ expect(findInput().attributes('max-length')).toBe(TOKEN_NAME_LIMIT.toString());
+ });
+
+ it('renders a textarea for the token description', () => {
+ expect(findTextarea().exists()).toBe(true);
+ expectDisabledAttribute(findTextarea(), false);
+ });
+
+ it('renders a cancel button', () => {
+ expect(findCancelButton().isVisible()).toBe(true);
+ expectDisabledAttribute(findCancelButton(), false);
+ });
+
+ it('renders a disabled next button', () => {
+ expect(findActionButton().text()).toBe('Create token');
+ expectDisabledAttribute(findActionButton(), true);
+ });
+
+ it('sends tracking event for modal shown', () => {
+ findModal().vm.$emit('show');
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS_OPEN, {
+ label: EVENT_LABEL_MODAL,
+ });
+ });
+ });
+
+ describe('when user inputs the token name', () => {
+ beforeEach(() => {
+ expectDisabledAttribute(findActionButton(), true);
+ findInput().vm.$emit('input', 'new-token');
+ });
+
+ it('enables the next button', () => {
+ expectDisabledAttribute(findActionButton(), false);
+ });
+ });
+
+ describe('when user clicks the create-token button', () => {
+ beforeEach(async () => {
+ const loadingResponse = new Promise(() => {});
+ await mockCreatedResponse(loadingResponse);
+
+ findInput().vm.$emit('input', 'new-token');
+ findActionButton().vm.$emit('click');
+ });
+
+ it('disables the create-token button', () => {
+ expectDisabledAttribute(findActionButton(), true);
+ });
+
+ it('hides the cancel button', () => {
+ expect(findCancelButton().exists()).toBe(false);
+ });
+ });
+
+ describe('creating a new token', () => {
+ beforeEach(async () => {
+ await mockCreatedResponse(clusterAgentToken);
+ });
+
+ it('creates a token', () => {
+ expect(createResponse).toHaveBeenCalledWith({
+ input: { clusterAgentId, name: 'new-token', description: 'new-token-description' },
+ });
+ });
+
+ it('shows agent instructions', () => {
+ expect(findAgentInstructions().props()).toMatchObject({
+ agentName,
+ agentToken: 'token-secret',
+ modalId: CREATE_TOKEN_MODAL,
+ });
+ });
+
+ it('renders a close button', () => {
+ expect(findActionButton().isVisible()).toBe(true);
+ expect(findActionButton().text()).toBe('Close');
+ expectDisabledAttribute(findActionButton(), false);
+ });
+ });
+
+ describe('error creating a new token', () => {
+ beforeEach(async () => {
+ await mockCreatedResponse(createAgentTokenErrorResponse);
+ });
+
+ it('displays the error message', async () => {
+ expect(findAlert().text()).toBe(
+ createAgentTokenErrorResponse.data.clusterAgentTokenCreate.errors[0],
+ );
+ });
+ });
+});
diff --git a/spec/frontend/clusters/agents/components/token_table_spec.js b/spec/frontend/clusters/agents/components/token_table_spec.js
index f6baaf87fa4..6caeaf5c192 100644
--- a/spec/frontend/clusters/agents/components/token_table_spec.js
+++ b/spec/frontend/clusters/agents/components/token_table_spec.js
@@ -2,6 +2,7 @@ import { GlEmptyState, GlTooltip, GlTruncate } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import TokenTable from '~/clusters/agents/components/token_table.vue';
import CreateTokenButton from '~/clusters/agents/components/create_token_button.vue';
+import CreateTokenModal from '~/clusters/agents/components/create_token_modal.vue';
import { useFakeDate } from 'helpers/fake_date';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { MAX_LIST_COUNT } from '~/clusters/agents/constants';
@@ -50,6 +51,7 @@ describe('ClusterAgentTokenTable', () => {
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findCreateTokenBtn = () => wrapper.findComponent(CreateTokenButton);
+ const findCreateModal = () => wrapper.findComponent(CreateTokenModal);
beforeEach(() => {
return createComponent(defaultTokens);
@@ -63,8 +65,8 @@ describe('ClusterAgentTokenTable', () => {
expect(findCreateTokenBtn().exists()).toBe(true);
});
- it('passes the correct params to the create token component', () => {
- expect(findCreateTokenBtn().props()).toMatchObject({
+ it('passes the correct params to the create token modal component', () => {
+ expect(findCreateModal().props()).toMatchObject({
clusterAgentId,
cursor,
});
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
index dfd5092b54d..8abd041fd4e 100644
--- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -171,20 +171,6 @@ RSpec.describe Gitlab::Kubernetes::KubeClient do
end
end
- describe '#extensions_client' do
- subject { client.extensions_client }
-
- it_behaves_like 'a Kubeclient'
-
- it 'has the extensions API group endpoint' do
- expect(subject.api_endpoint.to_s).to match(%r{\/apis\/extensions\Z})
- end
-
- it 'has the api_version' do
- expect(subject.instance_variable_get(:@api_version)).to eq('v1beta1')
- end
- end
-
describe '#istio_client' do
subject { client.istio_client }
@@ -307,86 +293,38 @@ RSpec.describe Gitlab::Kubernetes::KubeClient do
end
end
- describe '#get_deployments' do
- let(:extensions_client) { client.extensions_client }
+ describe 'apps/v1 API group' do
let(:apps_client) { client.apps_client }
- include_examples 'redirection not allowed', 'get_deployments'
- include_examples 'dns rebinding not allowed', 'get_deployments'
-
- it 'delegates to the extensions client' do
- expect(extensions_client).to receive(:get_deployments)
-
- client.get_deployments
- end
-
- context 'extensions does not have deployments for Kubernetes 1.16+ clusters' do
- before do
- WebMock
- .stub_request(:get, api_url + '/apis/extensions/v1beta1')
- .to_return(kube_response(kube_1_16_extensions_v1beta1_discovery_body))
- end
+ describe 'get_deployments' do
+ include_examples 'redirection not allowed', 'get_deployments'
+ include_examples 'dns rebinding not allowed', 'get_deployments'
it 'delegates to the apps client' do
- expect(apps_client).to receive(:get_deployments)
-
- client.get_deployments
+ expect(client).to delegate_method(:get_deployments).to(:apps_client)
end
- end
- end
-
- describe '#get_ingresses' do
- let(:extensions_client) { client.extensions_client }
- let(:networking_client) { client.networking_client }
-
- include_examples 'redirection not allowed', 'get_ingresses'
- include_examples 'dns rebinding not allowed', 'get_ingresses'
- it 'delegates to the extensions client' do
- expect(extensions_client).to receive(:get_ingresses)
-
- client.get_ingresses
- end
-
- context 'extensions does not have deployments for Kubernetes 1.22+ clusters' do
- before do
- WebMock
- .stub_request(:get, api_url + '/apis/extensions/v1beta1')
- .to_return(kube_response(kube_1_22_extensions_v1beta1_discovery_body))
- end
-
- it 'delegates to the apps client' do
- expect(networking_client).to receive(:get_ingresses)
-
- client.get_ingresses
+ it 'responds to the method' do
+ expect(client).to respond_to :get_deployments
end
end
end
- describe '#patch_ingress' do
- let(:extensions_client) { client.extensions_client }
+ describe 'networking.k8s.io/v1 API group' do
let(:networking_client) { client.networking_client }
- include_examples 'redirection not allowed', 'patch_ingress'
- include_examples 'dns rebinding not allowed', 'patch_ingress'
-
- it 'delegates to the extensions client' do
- expect(extensions_client).to receive(:patch_ingress)
-
- client.patch_ingress
- end
-
- context 'extensions does not have ingress for Kubernetes 1.22+ clusters' do
- before do
- WebMock
- .stub_request(:get, api_url + '/apis/extensions/v1beta1')
- .to_return(kube_response(kube_1_22_extensions_v1beta1_discovery_body))
- end
+ [:get_ingresses, :patch_ingress].each do |method|
+ describe "##{method}" do
+ include_examples 'redirection not allowed', method
+ include_examples 'dns rebinding not allowed', method
- it 'delegates to the apps client' do
- expect(networking_client).to receive(:patch_ingress)
+ it 'delegates to the networking client' do
+ expect(client).to delegate_method(method).to(:networking_client)
+ end
- client.patch_ingress
+ it 'responds to the method' do
+ expect(client).to respond_to method
+ end
end
end
end
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index ff61cceba06..29064f01913 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -40,9 +40,6 @@ module KubernetesHelpers
def stub_kubeclient_discover_base(api_url)
WebMock.stub_request(:get, api_url + '/api/v1').to_return(kube_response(kube_v1_discovery_body))
WebMock
- .stub_request(:get, api_url + '/apis/extensions/v1beta1')
- .to_return(kube_response(kube_extensions_v1beta1_discovery_body))
- WebMock
.stub_request(:get, api_url + '/apis/apps/v1')
.to_return(kube_response(kube_apps_v1_discovery_body))
WebMock
@@ -149,7 +146,7 @@ module KubernetesHelpers
def stub_kubeclient_deployments(namespace, status: nil)
stub_kubeclient_discover(service.api_url)
- deployments_url = service.api_url + "/apis/extensions/v1beta1/namespaces/#{namespace}/deployments"
+ deployments_url = service.api_url + "/apis/apps/v1/namespaces/#{namespace}/deployments"
response = { status: status } if status
WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response)
@@ -157,7 +154,7 @@ module KubernetesHelpers
def stub_kubeclient_ingresses(namespace, status: nil, method: :get, resource_path: "", response: kube_ingresses_response)
stub_kubeclient_discover(service.api_url)
- ingresses_url = service.api_url + "/apis/extensions/v1beta1/namespaces/#{namespace}/ingresses#{resource_path}"
+ ingresses_url = service.api_url + "/apis/networking.k8s.io/v1/namespaces/#{namespace}/ingresses#{resource_path}"
response = { status: status } if status
WebMock.stub_request(method, ingresses_url).to_return(response)
@@ -314,24 +311,6 @@ module KubernetesHelpers
}
end
- # From Kubernetes 1.16+ Deployments are no longer served from apis/extensions
- def kube_1_16_extensions_v1beta1_discovery_body
- {
- "kind" => "APIResourceList",
- "resources" => [
- { "name" => "ingresses", "namespaced" => true, "kind" => "Deployment" }
- ]
- }
- end
-
- # From Kubernetes 1.22+ Ingresses are no longer served from apis/extensions
- def kube_1_22_extensions_v1beta1_discovery_body
- {
- "kind" => "APIResourceList",
- "resources" => []
- }
- end
-
def kube_knative_discovery_body
{
"kind" => "APIResourceList",
@@ -339,18 +318,6 @@ module KubernetesHelpers
}
end
- def kube_extensions_v1beta1_discovery_body
- {
- "kind" => "APIResourceList",
- "resources" => [
- { "name" => "deployments", "namespaced" => true, "kind" => "Deployment" },
- { "name" => "ingresses", "namespaced" => true, "kind" => "Ingress" }
- ]
- }
- end
-
- # Yes, deployments are defined in both apis/extensions/v1beta1 and apis/v1
- # (for Kubernetes < 1.16). This matches what Kubenetes API server returns.
def kube_apps_v1_discovery_body
{
"kind" => "APIResourceList",
diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb
index 13f70e3f85b..1bcc43b81a8 100644
--- a/spec/uploaders/object_storage_spec.rb
+++ b/spec/uploaders/object_storage_spec.rb
@@ -48,6 +48,28 @@ RSpec.describe ObjectStorage do
expect(uploader.store_dir).to start_with("uploads/-/system/user/")
end
end
+
+ describe '#store_path' do
+ subject { uploader.store_path('filename') }
+
+ it 'uses store_dir' do
+ expect(subject).to eq("uploads/-/system/user/#{object.id}/filename")
+ end
+
+ context 'when a bucket prefix is configured' do
+ before do
+ allow(uploader_class).to receive(:object_store_options) do
+ double(
+ bucket_prefix: 'my/prefix'
+ )
+ end
+ end
+
+ it 'uses store_dir and ignores prefix' do
+ expect(subject).to eq("uploads/-/system/user/#{object.id}/filename")
+ end
+ end
+ end
end
context 'object_store is Store::REMOTE' do
@@ -60,6 +82,28 @@ RSpec.describe ObjectStorage do
expect(uploader.store_dir).to start_with("user/")
end
end
+
+ describe '#store_path' do
+ subject { uploader.store_path('filename') }
+
+ it 'uses store_dir' do
+ expect(subject).to eq("user/#{object.id}/filename")
+ end
+
+ context 'when a bucket prefix is configured' do
+ before do
+ allow(uploader_class).to receive(:object_store_options) do
+ double(
+ bucket_prefix: 'my/prefix'
+ )
+ end
+ end
+
+ it 'uses the prefix and store_dir' do
+ expect(subject).to eq("my/prefix/user/#{object.id}/filename")
+ end
+ end
+ end
end
end