diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-08 21:09:50 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-08 21:09:50 +0000 |
commit | 76358aee81a471a5e71eaf3e8c2d91b7c9a0a5a9 (patch) | |
tree | df9ba3dcc09eb404de31e0d79cb8f0b77812e655 /spec | |
parent | 80e9fdc9682cfbcfb9202a2733605a6a6bd23f05 (diff) | |
download | gitlab-ce-76358aee81a471a5e71eaf3e8c2d91b7c9a0a5a9.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
7 files changed, 519 insertions, 103 deletions
diff --git a/spec/frontend/custom_metrics/components/custom_metrics_form_fields_spec.js b/spec/frontend/custom_metrics/components/custom_metrics_form_fields_spec.js new file mode 100644 index 00000000000..61cbef0c557 --- /dev/null +++ b/spec/frontend/custom_metrics/components/custom_metrics_form_fields_spec.js @@ -0,0 +1,334 @@ +import { mount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import { TEST_HOST } from 'helpers/test_constants'; +import waitForPromises from 'helpers/wait_for_promises'; +import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue'; +import axios from '~/lib/utils/axios_utils'; + +const { CancelToken } = axios; + +describe('custom metrics form fields component', () => { + let component; + let mockAxios; + + const getNamedInput = name => component.element.querySelector(`input[name="${name}"]`); + const validateQueryPath = `${TEST_HOST}/mock/path`; + const validQueryResponse = { data: { success: true, query: { valid: true, error: '' } } }; + const csrfToken = 'mockToken'; + const formOperation = 'post'; + const debouncedValidateQueryMock = jest.fn(); + const makeFormData = (data = {}) => ({ + formData: { + title: '', + yLabel: '', + query: '', + unit: '', + group: '', + legend: '', + ...data, + }, + }); + const mountComponent = (props, methods = {}) => { + component = mount(CustomMetricsFormFields, { + propsData: { + formOperation, + validateQueryPath, + ...props, + }, + csrfToken, + methods, + }); + }; + + beforeEach(() => { + mockAxios = new MockAdapter(axios); + mockAxios.onPost(validateQueryPath).reply(validQueryResponse); + }); + + afterEach(() => { + component.destroy(); + mockAxios.restore(); + }); + + it('checks form validity', done => { + mountComponent({ + metricPersisted: true, + ...makeFormData({ + title: 'title', + yLabel: 'yLabel', + unit: 'unit', + group: 'group', + }), + }); + + component.vm.$nextTick(() => { + expect(component.vm.formIsValid).toBe(false); + done(); + }); + }); + + describe('hidden inputs', () => { + beforeEach(() => { + mountComponent(); + }); + + it('specifies form operation _method', () => { + expect(getNamedInput('_method', 'input').value).toBe('post'); + }); + + it('specifies authenticity token', () => { + expect(getNamedInput('authenticity_token', 'input').value).toBe(csrfToken); + }); + }); + + describe('name input', () => { + const name = 'prometheus_metric[title]'; + + it('is empty by default', () => { + mountComponent(); + + expect(getNamedInput(name).value).toBe(''); + }); + + it('receives a persisted value', () => { + const title = 'mockTitle'; + mountComponent(makeFormData({ title })); + + expect(getNamedInput(name).value).toBe(title); + }); + }); + + describe('group input', () => { + it('has a default value', () => { + mountComponent(); + + expect(getNamedInput('prometheus_metric[group]', 'glformradiogroup-stub').value).toBe( + 'business', + ); + }); + }); + + describe('query input', () => { + const queryInputName = 'prometheus_metric[query]'; + beforeEach(() => { + mockAxios.onPost(validateQueryPath).reply(validQueryResponse); + }); + + it('is empty by default', () => { + mountComponent(); + + expect(getNamedInput(queryInputName).value).toBe(''); + }); + + it('receives and validates a persisted value', () => { + const query = 'persistedQuery'; + const axiosPost = jest.spyOn(axios, 'post'); + const source = CancelToken.source(); + mountComponent({ metricPersisted: true, ...makeFormData({ query }) }); + + expect(axiosPost).toHaveBeenCalledWith( + validateQueryPath, + { query }, + { cancelToken: source.token }, + ); + expect(getNamedInput(queryInputName).value).toBe(query); + jest.runAllTimers(); + }); + + it('checks validity on user input', () => { + const query = 'changedQuery'; + mountComponent( + {}, + { + debouncedValidateQuery: debouncedValidateQueryMock, + }, + ); + const queryInput = component.find(`input[name="${queryInputName}"]`); + queryInput.setValue(query); + queryInput.trigger('input'); + + expect(debouncedValidateQueryMock).toHaveBeenCalledWith(query); + }); + + describe('when query validation is in flight', () => { + beforeEach(() => { + jest.useFakeTimers(); + mountComponent( + { metricPersisted: true, ...makeFormData({ query: 'validQuery' }) }, + { + requestValidation: jest.fn().mockImplementation( + () => + new Promise(resolve => + setTimeout(() => { + resolve(validQueryResponse); + }, 4000), + ), + ), + }, + ); + }); + + afterEach(() => { + jest.clearAllTimers(); + }); + + it('expect queryValidateInFlight is in flight', done => { + const queryInput = component.find(`input[name="${queryInputName}"]`); + queryInput.setValue('query'); + queryInput.trigger('input'); + + component.vm.$nextTick(() => { + expect(component.vm.queryValidateInFlight).toBe(true); + jest.runOnlyPendingTimers(); + waitForPromises() + .then(() => { + component.vm.$nextTick(() => { + expect(component.vm.queryValidateInFlight).toBe(false); + expect(component.vm.queryIsValid).toBe(true); + done(); + }); + }) + .catch(done.fail); + }); + }); + + it('expect loading message to display', done => { + const queryInput = component.find(`input[name="${queryInputName}"]`); + queryInput.setValue('query'); + queryInput.trigger('input'); + component.vm.$nextTick(() => { + expect(component.text()).toContain('Validating query'); + jest.runOnlyPendingTimers(); + done(); + }); + }); + + it('expect loading message to disappear', done => { + const queryInput = component.find(`input[name="${queryInputName}"]`); + queryInput.setValue('query'); + queryInput.trigger('input'); + component.vm.$nextTick(() => { + jest.runOnlyPendingTimers(); + waitForPromises() + .then(() => { + component.vm.$nextTick(() => { + expect(component.vm.queryValidateInFlight).toBe(false); + expect(component.vm.queryIsValid).toBe(true); + expect(component.vm.errorMessage).toBe(''); + done(); + }); + }) + .catch(done.fail); + }); + }); + }); + + describe('when query is invalid', () => { + const errorMessage = 'mockErrorMessage'; + const invalidQueryResponse = { + data: { success: true, query: { valid: false, error: errorMessage } }, + }; + + beforeEach(() => { + mountComponent( + { metricPersisted: true, ...makeFormData({ query: 'invalidQuery' }) }, + { + requestValidation: jest + .fn() + .mockImplementation(() => Promise.resolve(invalidQueryResponse)), + }, + ); + }); + + it('sets queryIsValid to false', done => { + component.vm.$nextTick(() => { + expect(component.vm.queryValidateInFlight).toBe(false); + expect(component.vm.queryIsValid).toBe(false); + done(); + }); + }); + + it('shows invalid query message', done => { + component.vm.$nextTick(() => { + expect(component.text()).toContain(errorMessage); + done(); + }); + }); + }); + + describe('when query is valid', () => { + beforeEach(() => { + mountComponent( + { metricPersisted: true, ...makeFormData({ query: 'validQuery' }) }, + { + requestValidation: jest + .fn() + .mockImplementation(() => Promise.resolve(validQueryResponse)), + }, + ); + }); + + it('sets queryIsValid to true when query is valid', done => { + component.vm.$nextTick(() => { + expect(component.vm.queryIsValid).toBe(true); + done(); + }); + }); + + it('shows valid query message', () => { + expect(component.text()).toContain('PromQL query is valid'); + }); + }); + }); + + describe('yLabel input', () => { + const name = 'prometheus_metric[y_label]'; + + it('is empty by default', () => { + mountComponent(); + + expect(getNamedInput(name).value).toBe(''); + }); + + it('receives a persisted value', () => { + const yLabel = 'mockYLabel'; + mountComponent(makeFormData({ yLabel })); + + expect(getNamedInput(name).value).toBe(yLabel); + }); + }); + + describe('unit input', () => { + const name = 'prometheus_metric[unit]'; + + it('is empty by default', () => { + mountComponent(); + + expect(getNamedInput(name).value).toBe(''); + }); + + it('receives a persisted value', () => { + const unit = 'mockUnit'; + mountComponent(makeFormData({ unit })); + + expect(getNamedInput(name).value).toBe(unit); + }); + }); + + describe('legend input', () => { + const name = 'prometheus_metric[legend]'; + + it('is empty by default', () => { + mountComponent(); + + expect(getNamedInput(name).value).toBe(''); + }); + + it('receives a persisted value', () => { + const legend = 'mockLegend'; + mountComponent(makeFormData({ legend })); + + expect(getNamedInput(name).value).toBe(legend); + }); + }); +}); diff --git a/spec/frontend/custom_metrics/components/custom_metrics_form_spec.js b/spec/frontend/custom_metrics/components/custom_metrics_form_spec.js new file mode 100644 index 00000000000..384d6699150 --- /dev/null +++ b/spec/frontend/custom_metrics/components/custom_metrics_form_spec.js @@ -0,0 +1,48 @@ +import { shallowMount } from '@vue/test-utils'; +import CustomMetricsForm from '~/custom_metrics/components/custom_metrics_form.vue'; + +describe('CustomMetricsForm', () => { + let wrapper; + + function mountComponent({ + metricPersisted = false, + formData = { + title: '', + query: '', + yLabel: '', + unit: '', + group: '', + legend: '', + }, + }) { + wrapper = shallowMount(CustomMetricsForm, { + propsData: { + customMetricsPath: '', + editProjectServicePath: '', + metricPersisted, + validateQueryPath: '', + formData, + }, + }); + } + + afterEach(() => { + wrapper.destroy(); + }); + + describe('Computed', () => { + it('Form button and title text indicate the custom metric is being edited', () => { + mountComponent({ metricPersisted: true }); + + expect(wrapper.vm.saveButtonText).toBe('Save Changes'); + expect(wrapper.vm.titleText).toBe('Edit metric'); + }); + + it('Form button and title text indicate the custom metric is being created', () => { + mountComponent({ metricPersisted: false }); + + expect(wrapper.vm.saveButtonText).toBe('Create metric'); + expect(wrapper.vm.titleText).toBe('New metric'); + }); + }); +}); diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js index 7c559aed2c5..b37c10791bf 100644 --- a/spec/frontend/monitoring/store/actions_spec.js +++ b/spec/frontend/monitoring/store/actions_spec.js @@ -6,6 +6,7 @@ import statusCodes from '~/lib/utils/http_status'; import * as commonUtils from '~/lib/utils/common_utils'; import createFlash from '~/flash'; import { defaultTimeRange } from '~/vue_shared/constants'; +import { ENVIRONMENT_AVAILABLE_STATE } from '~/monitoring/constants'; import store from '~/monitoring/stores'; import * as types from '~/monitoring/stores/mutation_types'; @@ -157,17 +158,21 @@ describe('Monitoring store actions', () => { variables: { projectPath: state.projectPath, search: searchTerm, + states: [ENVIRONMENT_AVAILABLE_STATE], }, }; state.environmentsSearchTerm = searchTerm; - mockMutate.mockReturnValue(Promise.resolve()); + mockMutate.mockResolvedValue({}); return testAction( fetchEnvironmentsData, null, state, [], - [{ type: 'requestEnvironmentsData' }, { type: 'receiveEnvironmentsDataFailure' }], + [ + { type: 'requestEnvironmentsData' }, + { type: 'receiveEnvironmentsDataSuccess', payload: [] }, + ], () => { expect(mockMutate).toHaveBeenCalledWith(mutationVariables); }, diff --git a/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js b/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js new file mode 100644 index 00000000000..794deca42ac --- /dev/null +++ b/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js @@ -0,0 +1,112 @@ +import { shallowMount } from '@vue/test-utils'; +import AccessibilityIssueBody from '~/reports/accessibility_report/components/accessibility_issue_body.vue'; + +const issue = { + name: + 'The accessibility scanning found 2 errors of the following type: WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent', + code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent', + message: 'This element has insufficient contrast at this conformance level.', + status: 'failed', + className: 'spec.test_spec', + learnMoreUrl: 'https://www.w3.org/TR/WCAG20-TECHS/H91.html', +}; + +describe('CustomMetricsForm', () => { + let wrapper; + + const mountComponent = ({ name, code, message, status, className }, isNew = false) => { + wrapper = shallowMount(AccessibilityIssueBody, { + propsData: { + issue: { + name, + code, + message, + status, + className, + }, + isNew, + }, + }); + }; + + const findIsNewBadge = () => wrapper.find({ ref: 'accessibility-issue-is-new-badge' }); + + beforeEach(() => { + mountComponent(issue); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('Displays the issue message', () => { + const description = wrapper.find({ ref: 'accessibility-issue-description' }).text(); + + expect(description).toContain(`Message: ${issue.message}`); + }); + + describe('When an issue code is present', () => { + it('Creates the correct URL for learning more about the issue code', () => { + const learnMoreUrl = wrapper + .find({ ref: 'accessibility-issue-learn-more' }) + .attributes('href'); + + expect(learnMoreUrl).toEqual(issue.learnMoreUrl); + }); + }); + + describe('When an issue code is not present', () => { + beforeEach(() => { + mountComponent({ + ...issue, + code: undefined, + }); + }); + + it('Creates a URL leading to the overview documentation page', () => { + const learnMoreUrl = wrapper + .find({ ref: 'accessibility-issue-learn-more' }) + .attributes('href'); + + expect(learnMoreUrl).toEqual('https://www.w3.org/TR/WCAG20-TECHS/Overview.html'); + }); + }); + + describe('When an issue code does not contain the TECHS code', () => { + beforeEach(() => { + mountComponent({ + ...issue, + code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2', + }); + }); + + it('Creates a URL leading to the overview documentation page', () => { + const learnMoreUrl = wrapper + .find({ ref: 'accessibility-issue-learn-more' }) + .attributes('href'); + + expect(learnMoreUrl).toEqual('https://www.w3.org/TR/WCAG20-TECHS/Overview.html'); + }); + }); + + describe('When issue is new', () => { + beforeEach(() => { + mountComponent(issue, true); + }); + + it('Renders the new badge', () => { + expect(findIsNewBadge().exists()).toEqual(true); + }); + }); + + describe('When issue is not new', () => { + beforeEach(() => { + mountComponent(issue, false); + }); + + it('Does not render the new badge', () => { + expect(findIsNewBadge().exists()).toEqual(false); + }); + }); +}); diff --git a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb index 96aed774cfc..c8229eeee94 100644 --- a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb @@ -11,7 +11,7 @@ describe Gitlab::ImportExport::Project::TreeRestorer do let(:shared) { project.import_export_shared } - RSpec.shared_examples 'project tree restorer work properly' do |reader| + RSpec.shared_examples 'project tree restorer work properly' do |reader, ndjson_enabled| describe 'restore project tree' do before_all do # Using an admin for import, so we can check assignment of existing members @@ -25,6 +25,9 @@ describe Gitlab::ImportExport::Project::TreeRestorer do @project = create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project') @shared = @project.import_export_shared + allow(Feature).to receive(:enabled?).and_call_original + stub_feature_flags(project_import_ndjson: ndjson_enabled) + setup_import_export_config('complex') setup_reader(reader) @@ -999,23 +1002,12 @@ describe Gitlab::ImportExport::Project::TreeRestorer do end context 'enable ndjson import' do - before_all do - # Test suite `restore project tree` run `project_tree_restorer.restore` in `before_all`. - # `Enable all features by default for testing` happens in `before(:each)` - # So it requires manually enable feature flag to allow ndjson_reader - Feature.enable(:project_import_ndjson) - end - - it_behaves_like 'project tree restorer work properly', :legacy_reader + it_behaves_like 'project tree restorer work properly', :legacy_reader, true - it_behaves_like 'project tree restorer work properly', :ndjson_reader + it_behaves_like 'project tree restorer work properly', :ndjson_reader, true end context 'disable ndjson import' do - before do - stub_feature_flags(project_import_ndjson: false) - end - - it_behaves_like 'project tree restorer work properly', :legacy_reader + it_behaves_like 'project tree restorer work properly', :legacy_reader, false end end diff --git a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb index d859af5df02..ded57b1d576 100644 --- a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb @@ -16,7 +16,6 @@ describe Gitlab::ImportExport::Project::TreeSaver do let_it_be(:group) { create(:group) } let_it_be(:project) { setup_project } let_it_be(:shared) { project.import_export_shared } - let_it_be(:project_tree_saver ) { described_class.new(project: project, current_user: user, shared: shared) } let(:relation_name) { :projects } @@ -29,9 +28,17 @@ describe Gitlab::ImportExport::Project::TreeSaver do end before_all do - Feature.enable(:project_export_as_ndjson) if ndjson_enabled - project.add_maintainer(user) - project_tree_saver.save + RSpec::Mocks.with_temporary_scope do + allow(Feature).to receive(:enabled?).and_call_original + stub_feature_flags(project_export_as_ndjson: ndjson_enabled) + + project.add_maintainer(user) + + stub_feature_flags(project_export_as_ndjson: ndjson_enabled) + project_tree_saver = described_class.new(project: project, current_user: user, shared: shared) + + project_tree_saver.save + end end after :all do diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb index f84336b64c2..0629d51154b 100644 --- a/spec/requests/api/internal/base_spec.rb +++ b/spec/requests/api/internal/base_spec.rb @@ -882,88 +882,6 @@ describe API::Internal::Base do end end - # TODO: Uncomment when the end-point is reenabled - # describe 'POST /notify_post_receive' do - # let(:valid_params) do - # { project: project.repository.path, secret_token: secret_token } - # end - # - # let(:valid_wiki_params) do - # { project: project.wiki.repository.path, secret_token: secret_token } - # end - # - # before do - # allow(Gitlab.config.gitaly).to receive(:enabled).and_return(true) - # end - # - # it "calls the Gitaly client with the project's repository" do - # expect(Gitlab::GitalyClient::NotificationService). - # to receive(:new).with(gitlab_git_repository_with(path: project.repository.path)). - # and_call_original - # expect_any_instance_of(Gitlab::GitalyClient::NotificationService). - # to receive(:post_receive) - # - # post api("/internal/notify_post_receive"), valid_params - # - # expect(response).to have_gitlab_http_status(:ok) - # end - # - # it "calls the Gitaly client with the wiki's repository if it's a wiki" do - # expect(Gitlab::GitalyClient::NotificationService). - # to receive(:new).with(gitlab_git_repository_with(path: project.wiki.repository.path)). - # and_call_original - # expect_any_instance_of(Gitlab::GitalyClient::NotificationService). - # to receive(:post_receive) - # - # post api("/internal/notify_post_receive"), valid_wiki_params - # - # expect(response).to have_gitlab_http_status(:ok) - # end - # - # it "returns 500 if the gitaly call fails" do - # expect_any_instance_of(Gitlab::GitalyClient::NotificationService). - # to receive(:post_receive).and_raise(GRPC::Unavailable) - # - # post api("/internal/notify_post_receive"), valid_params - # - # expect(response).to have_gitlab_http_status(:internal_server_error) - # end - # - # context 'with a gl_repository parameter' do - # let(:valid_params) do - # { gl_repository: "project-#{project.id}", secret_token: secret_token } - # end - # - # let(:valid_wiki_params) do - # { gl_repository: "wiki-#{project.id}", secret_token: secret_token } - # end - # - # it "calls the Gitaly client with the project's repository" do - # expect(Gitlab::GitalyClient::NotificationService). - # to receive(:new).with(gitlab_git_repository_with(path: project.repository.path)). - # and_call_original - # expect_any_instance_of(Gitlab::GitalyClient::NotificationService). - # to receive(:post_receive) - # - # post api("/internal/notify_post_receive"), valid_params - # - # expect(response).to have_gitlab_http_status(:ok) - # end - # - # it "calls the Gitaly client with the wiki's repository if it's a wiki" do - # expect(Gitlab::GitalyClient::NotificationService). - # to receive(:new).with(gitlab_git_repository_with(path: project.wiki.repository.path)). - # and_call_original - # expect_any_instance_of(Gitlab::GitalyClient::NotificationService). - # to receive(:post_receive) - # - # post api("/internal/notify_post_receive"), valid_wiki_params - # - # expect(response).to have_gitlab_http_status(:ok) - # end - # end - # end - describe 'POST /internal/post_receive', :clean_gitlab_redis_shared_state do let(:identifier) { 'key-123' } let(:branch_name) { 'feature' } |