diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-02 15:07:36 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-02 15:07:36 +0000 |
commit | e61f798b74e8e18fca7239fd01802182479bfcfc (patch) | |
tree | 4a49b062d8df2ffe3e6e8a07d2aadc034acb9092 /spec | |
parent | afbfbfc87abfa006f1d369fdf9c740eb1c826808 (diff) | |
download | gitlab-ce-e61f798b74e8e18fca7239fd01802182479bfcfc.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r-- | spec/frontend/runner/components/runner_update_form_spec.js | 12 | ||||
-rw-r--r-- | spec/frontend/runner/runner_detail/runner_update_form_utils_spec.js | 96 | ||||
-rw-r--r-- | spec/frontend/token_access/mock_data.js | 84 | ||||
-rw-r--r-- | spec/frontend/token_access/token_access_spec.js | 213 | ||||
-rw-r--r-- | spec/frontend/token_access/token_projects_table_spec.js | 51 | ||||
-rw-r--r-- | spec/requests/api/repositories_spec.rb | 11 |
6 files changed, 460 insertions, 7 deletions
diff --git a/spec/frontend/runner/components/runner_update_form_spec.js b/spec/frontend/runner/components/runner_update_form_spec.js index dad041072ce..15029d7a911 100644 --- a/spec/frontend/runner/components/runner_update_form_spec.js +++ b/spec/frontend/runner/components/runner_update_form_spec.js @@ -207,13 +207,11 @@ describe('RunnerUpdateForm', () => { }); it.each` - value | submitted - ${''} | ${{ tagList: [] }} - ${'tag1, tag2'} | ${{ tagList: ['tag1', 'tag2'] }} - ${'with spaces'} | ${{ tagList: ['with spaces'] }} - ${',,,,, commas'} | ${{ tagList: ['commas'] }} - ${'more ,,,,, commas'} | ${{ tagList: ['more', 'commas'] }} - ${' trimmed , trimmed2 '} | ${{ tagList: ['trimmed', 'trimmed2'] }} + value | submitted + ${''} | ${{ tagList: [] }} + ${'tag1, tag2'} | ${{ tagList: ['tag1', 'tag2'] }} + ${'with spaces'} | ${{ tagList: ['with spaces'] }} + ${'more ,,,,, commas'} | ${{ tagList: ['more', 'commas'] }} `('Field updates runner\'s tags for "$value"', async ({ value, submitted }) => { const runner = { ...mockRunner, tagList: ['tag1'] }; createComponent({ props: { runner } }); diff --git a/spec/frontend/runner/runner_detail/runner_update_form_utils_spec.js b/spec/frontend/runner/runner_detail/runner_update_form_utils_spec.js new file mode 100644 index 00000000000..510b4e604ac --- /dev/null +++ b/spec/frontend/runner/runner_detail/runner_update_form_utils_spec.js @@ -0,0 +1,96 @@ +import { ACCESS_LEVEL_NOT_PROTECTED } from '~/runner/constants'; +import { + modelToUpdateMutationVariables, + runnerToModel, +} from '~/runner/runner_details/runner_update_form_utils'; + +const mockId = 'gid://gitlab/Ci::Runner/1'; +const mockDescription = 'Runner Desc.'; + +const mockRunner = { + id: mockId, + description: mockDescription, + maximumTimeout: 100, + accessLevel: ACCESS_LEVEL_NOT_PROTECTED, + active: true, + locked: true, + runUntagged: true, + tagList: ['tag-1', 'tag-2'], +}; + +const mockModel = { + ...mockRunner, + tagList: 'tag-1, tag-2', +}; + +describe('~/runner/runner_details/runner_update_form_utils', () => { + describe('runnerToModel', () => { + it('collects all model data', () => { + expect(runnerToModel(mockRunner)).toEqual(mockModel); + }); + + it('does not collect other data', () => { + const model = runnerToModel({ + ...mockRunner, + unrelated: 'unrelatedValue', + }); + + expect(model.unrelated).toEqual(undefined); + }); + + it('tag list defaults to an empty string', () => { + const model = runnerToModel({ + ...mockRunner, + tagList: undefined, + }); + + expect(model.tagList).toEqual(''); + }); + }); + + describe('modelToUpdateMutationVariables', () => { + it('collects all model data', () => { + expect(modelToUpdateMutationVariables(mockModel)).toEqual({ + input: { + ...mockRunner, + }, + }); + }); + + it('collects a nullable timeout from the model', () => { + const variables = modelToUpdateMutationVariables({ + ...mockModel, + maximumTimeout: '', + }); + + expect(variables).toEqual({ + input: { + ...mockRunner, + maximumTimeout: null, + }, + }); + }); + + it.each` + tagList | tagListInput + ${''} | ${[]} + ${'tag1, tag2'} | ${['tag1', 'tag2']} + ${'with spaces'} | ${['with spaces']} + ${',,,,, commas'} | ${['commas']} + ${'more ,,,,, commas'} | ${['more', 'commas']} + ${' trimmed , trimmed2 '} | ${['trimmed', 'trimmed2']} + `('collect tags separated by commas for "$value"', ({ tagList, tagListInput }) => { + const variables = modelToUpdateMutationVariables({ + ...mockModel, + tagList, + }); + + expect(variables).toEqual({ + input: { + ...mockRunner, + tagList: tagListInput, + }, + }); + }); + }); +}); diff --git a/spec/frontend/token_access/mock_data.js b/spec/frontend/token_access/mock_data.js new file mode 100644 index 00000000000..14d7b00cb6d --- /dev/null +++ b/spec/frontend/token_access/mock_data.js @@ -0,0 +1,84 @@ +export const enabledJobTokenScope = { + data: { + project: { + ciCdSettings: { + jobTokenScopeEnabled: true, + __typename: 'ProjectCiCdSetting', + }, + __typename: 'Project', + }, + }, +}; + +export const disabledJobTokenScope = { + data: { + project: { + ciCdSettings: { + jobTokenScopeEnabled: false, + __typename: 'ProjectCiCdSetting', + }, + __typename: 'Project', + }, + }, +}; + +export const updateJobTokenScope = { + data: { + ciCdSettingsUpdate: { + ciCdSettings: { + jobTokenScopeEnabled: true, + __typename: 'ProjectCiCdSetting', + }, + errors: [], + __typename: 'CiCdSettingsUpdatePayload', + }, + }, +}; + +export const projectsWithScope = { + data: { + project: { + __typename: 'Project', + ciJobTokenScope: { + __typename: 'CiJobTokenScopeType', + projects: { + __typename: 'ProjectConnection', + nodes: [ + { + fullPath: 'root/332268-test', + name: 'root/332268-test', + }, + ], + }, + }, + }, + }, +}; + +export const addProjectSuccess = { + data: { + ciJobTokenScopeAddProject: { + errors: [], + __typename: 'CiJobTokenScopeAddProjectPayload', + }, + }, +}; + +export const removeProjectSuccess = { + data: { + ciJobTokenScopeRemoveProject: { + errors: [], + __typename: 'CiJobTokenScopeRemoveProjectPayload', + }, + }, +}; + +export const mockProjects = [ + { + name: 'merge-train-stuff', + fullPath: 'root/merge-train-stuff', + isLocked: false, + __typename: 'Project', + }, + { name: 'ci-project', fullPath: 'root/ci-project', isLocked: true, __typename: 'Project' }, +]; diff --git a/spec/frontend/token_access/token_access_spec.js b/spec/frontend/token_access/token_access_spec.js new file mode 100644 index 00000000000..244a7f00cb9 --- /dev/null +++ b/spec/frontend/token_access/token_access_spec.js @@ -0,0 +1,213 @@ +import { GlToggle, GlLoadingIcon } from '@gitlab/ui'; +import { createLocalVue, shallowMount, mount } from '@vue/test-utils'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import createFlash from '~/flash'; +import TokenAccess from '~/token_access/components/token_access.vue'; +import addProjectCIJobTokenScopeMutation from '~/token_access/graphql/mutations/add_project_ci_job_token_scope.mutation.graphql'; +import removeProjectCIJobTokenScopeMutation from '~/token_access/graphql/mutations/remove_project_ci_job_token_scope.mutation.graphql'; +import updateCIJobTokenScopeMutation from '~/token_access/graphql/mutations/update_ci_job_token_scope.mutation.graphql'; +import getCIJobTokenScopeQuery from '~/token_access/graphql/queries/get_ci_job_token_scope.query.graphql'; +import getProjectsWithCIJobTokenScopeQuery from '~/token_access/graphql/queries/get_projects_with_ci_job_token_scope.query.graphql'; +import { + enabledJobTokenScope, + disabledJobTokenScope, + updateJobTokenScope, + projectsWithScope, + addProjectSuccess, + removeProjectSuccess, +} from './mock_data'; + +const projectPath = 'root/my-repo'; +const error = new Error('Error'); +const localVue = createLocalVue(); + +localVue.use(VueApollo); + +jest.mock('~/flash'); + +describe('TokenAccess component', () => { + let wrapper; + + const enabledJobTokenScopeHandler = jest.fn().mockResolvedValue(enabledJobTokenScope); + const disabledJobTokenScopeHandler = jest.fn().mockResolvedValue(disabledJobTokenScope); + const updateJobTokenScopeHandler = jest.fn().mockResolvedValue(updateJobTokenScope); + const getProjectsWithScope = jest.fn().mockResolvedValue(projectsWithScope); + const addProjectSuccessHandler = jest.fn().mockResolvedValue(addProjectSuccess); + const addProjectFailureHandler = jest.fn().mockRejectedValue(error); + const removeProjectSuccessHandler = jest.fn().mockResolvedValue(removeProjectSuccess); + const removeProjectFailureHandler = jest.fn().mockRejectedValue(error); + + const findToggle = () => wrapper.findComponent(GlToggle); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findAddProjectBtn = () => wrapper.find('[data-testid="add-project-button"]'); + const findRemoveProjectBtn = () => wrapper.find('[data-testid="remove-project-button"]'); + const findTokenSection = () => wrapper.find('[data-testid="token-section"]'); + + const createMockApolloProvider = (requestHandlers) => { + return createMockApollo(requestHandlers); + }; + + const createComponent = (requestHandlers, mountFn = shallowMount) => { + wrapper = mountFn(TokenAccess, { + localVue, + provide: { + fullPath: projectPath, + }, + apolloProvider: createMockApolloProvider(requestHandlers), + data() { + return { + targetProjectPath: 'root/test', + }; + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('loading state', () => { + it('shows loading state while waiting on query to resolve', async () => { + createComponent([ + [getCIJobTokenScopeQuery, enabledJobTokenScopeHandler], + [getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope], + ]); + + expect(findLoadingIcon().exists()).toBe(true); + + await waitForPromises(); + + expect(findLoadingIcon().exists()).toBe(false); + }); + }); + + describe('toggle', () => { + it('the toggle should be enabled and the token section should show', async () => { + createComponent([ + [getCIJobTokenScopeQuery, enabledJobTokenScopeHandler], + [getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope], + ]); + + await waitForPromises(); + + expect(findToggle().props('value')).toBe(true); + expect(findTokenSection().exists()).toBe(true); + }); + + it('the toggle should be disabled and the token section should not show', async () => { + createComponent([ + [getCIJobTokenScopeQuery, disabledJobTokenScopeHandler], + [getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope], + ]); + + await waitForPromises(); + + expect(findToggle().props('value')).toBe(false); + expect(findTokenSection().exists()).toBe(false); + }); + + it('switching the toggle calls the mutation', async () => { + createComponent([ + [getCIJobTokenScopeQuery, disabledJobTokenScopeHandler], + [updateCIJobTokenScopeMutation, updateJobTokenScopeHandler], + [getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope], + ]); + + await waitForPromises(); + + findToggle().vm.$emit('change', true); + + expect(updateJobTokenScopeHandler).toHaveBeenCalledWith({ + input: { fullPath: projectPath, jobTokenScopeEnabled: true }, + }); + }); + }); + + describe('add project', () => { + it('calls add project mutation', async () => { + createComponent( + [ + [getCIJobTokenScopeQuery, enabledJobTokenScopeHandler], + [getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope], + [addProjectCIJobTokenScopeMutation, addProjectSuccessHandler], + ], + mount, + ); + + await waitForPromises(); + + findAddProjectBtn().trigger('click'); + + expect(addProjectSuccessHandler).toHaveBeenCalledWith({ + input: { + projectPath, + targetProjectPath: 'root/test', + }, + }); + }); + + it('add project handles error correctly', async () => { + createComponent( + [ + [getCIJobTokenScopeQuery, enabledJobTokenScopeHandler], + [getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope], + [addProjectCIJobTokenScopeMutation, addProjectFailureHandler], + ], + mount, + ); + + await waitForPromises(); + + findAddProjectBtn().trigger('click'); + + await waitForPromises(); + + expect(createFlash).toHaveBeenCalled(); + }); + }); + + describe('remove project', () => { + it('calls remove project mutation', async () => { + createComponent( + [ + [getCIJobTokenScopeQuery, enabledJobTokenScopeHandler], + [getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope], + [removeProjectCIJobTokenScopeMutation, removeProjectSuccessHandler], + ], + mount, + ); + + await waitForPromises(); + + findRemoveProjectBtn().trigger('click'); + + expect(removeProjectSuccessHandler).toHaveBeenCalledWith({ + input: { + projectPath, + targetProjectPath: 'root/332268-test', + }, + }); + }); + + it('remove project handles error correctly', async () => { + createComponent( + [ + [getCIJobTokenScopeQuery, enabledJobTokenScopeHandler], + [getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope], + [removeProjectCIJobTokenScopeMutation, removeProjectFailureHandler], + ], + mount, + ); + + await waitForPromises(); + + findRemoveProjectBtn().trigger('click'); + + await waitForPromises(); + + expect(createFlash).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/token_access/token_projects_table_spec.js b/spec/frontend/token_access/token_projects_table_spec.js new file mode 100644 index 00000000000..3bda0d0b530 --- /dev/null +++ b/spec/frontend/token_access/token_projects_table_spec.js @@ -0,0 +1,51 @@ +import { GlTable, GlButton } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import TokenProjectsTable from '~/token_access/components/token_projects_table.vue'; +import { mockProjects } from './mock_data'; + +describe('Token projects table', () => { + let wrapper; + + const createComponent = () => { + wrapper = mount(TokenProjectsTable, { + provide: { + fullPath: 'root/ci-project', + }, + propsData: { + projects: mockProjects, + }, + }); + }; + + const findTable = () => wrapper.findComponent(GlTable); + const findAllTableRows = () => wrapper.findAll('[data-testid="projects-token-table-row"]'); + const findDeleteProjectBtn = () => wrapper.findComponent(GlButton); + const findAllDeleteProjectBtn = () => wrapper.findAllComponents(GlButton); + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('displays a table', () => { + expect(findTable().exists()).toBe(true); + }); + + it('displays the correct amount of table rows', () => { + expect(findAllTableRows()).toHaveLength(mockProjects.length); + }); + + it('delete project button emits event with correct project to delete', async () => { + await findDeleteProjectBtn().trigger('click'); + + expect(wrapper.emitted('removeProject')).toEqual([[mockProjects[0].fullPath]]); + }); + + it('does not show the remove icon if the project is locked', () => { + // currently two mock projects with one being a locked project + expect(findAllDeleteProjectBtn()).toHaveLength(1); + }); +}); diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 1b96efeca22..d019e89e0b4 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -477,6 +477,17 @@ RSpec.describe API::Repositories do let(:request) { get api(route, guest) } end end + + context 'api_caching_rate_limit_repository_compare is disabled' do + before do + stub_feature_flags(api_caching_rate_limit_repository_compare: false) + end + + it_behaves_like 'repository compare' do + let(:project) { create(:project, :public, :repository) } + let(:current_user) { nil } + end + end end describe 'GET /projects/:id/repository/contributors' do |