diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-24 12:09:34 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-24 12:09:34 +0000 |
commit | 0c27b33a8d0e2582b09579676677df5eaebfdbe2 (patch) | |
tree | ddc30f64f1a1da653cf89ff554f48376fde1d6ac /spec | |
parent | ff83f24eacc7c78884458d7261086b4f3a9547bb (diff) | |
download | gitlab-ce-0c27b33a8d0e2582b09579676677df5eaebfdbe2.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r-- | spec/frontend/helpers/backoff_helper.js | 33 | ||||
-rw-r--r-- | spec/frontend/monitoring/requests/index_spec.js | 149 | ||||
-rw-r--r-- | spec/frontend/monitoring/store/actions_spec.js | 57 | ||||
-rw-r--r-- | spec/frontend/snippets/components/show_spec.js | 53 | ||||
-rw-r--r-- | spec/frontend/snippets/components/snippet_blob_view_spec.js | 19 | ||||
-rw-r--r-- | spec/serializers/pipeline_serializer_spec.rb | 2 |
6 files changed, 239 insertions, 74 deletions
diff --git a/spec/frontend/helpers/backoff_helper.js b/spec/frontend/helpers/backoff_helper.js new file mode 100644 index 00000000000..e5c0308d3fb --- /dev/null +++ b/spec/frontend/helpers/backoff_helper.js @@ -0,0 +1,33 @@ +/** + * A mock version of a commonUtils `backOff` to test multiple + * retries. + * + * Usage: + * + * ``` + * import * as commonUtils from '~/lib/utils/common_utils'; + * import { backoffMockImplementation } from '../../helpers/backoff_helper'; + * + * beforeEach(() => { + * // ... + * jest.spyOn(commonUtils, 'backOff').mockImplementation(backoffMockImplementation); + * }); + * ``` + * + * @param {Function} callback + */ +export const backoffMockImplementation = callback => { + const q = new Promise((resolve, reject) => { + const stop = arg => (arg instanceof Error ? reject(arg) : resolve(arg)); + const next = () => callback(next, stop); + // Define a timeout based on a mock timer + setTimeout(() => { + callback(next, stop); + }); + }); + // Run all resolved promises in chain + jest.runOnlyPendingTimers(); + return q; +}; + +export default { backoffMockImplementation }; diff --git a/spec/frontend/monitoring/requests/index_spec.js b/spec/frontend/monitoring/requests/index_spec.js new file mode 100644 index 00000000000..7c22e0bc5d7 --- /dev/null +++ b/spec/frontend/monitoring/requests/index_spec.js @@ -0,0 +1,149 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import statusCodes from '~/lib/utils/http_status'; +import * as commonUtils from '~/lib/utils/common_utils'; +import { backoffMockImplementation } from 'jest/helpers/backoff_helper'; +import { metricsDashboardResponse } from '../fixture_data'; +import { getDashboard, getPrometheusQueryData } from '~/monitoring/requests'; + +describe('monitoring metrics_requests', () => { + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + jest.spyOn(commonUtils, 'backOff').mockImplementation(backoffMockImplementation); + }); + + afterEach(() => { + mock.reset(); + + commonUtils.backOff.mockReset(); + }); + + describe('getDashboard', () => { + const response = metricsDashboardResponse; + const dashboardEndpoint = '/dashboard'; + const params = { + start_time: 'start_time', + end_time: 'end_time', + }; + + it('returns a dashboard response', () => { + mock.onGet(dashboardEndpoint).reply(statusCodes.OK, response); + + return getDashboard(dashboardEndpoint, params).then(data => { + expect(data).toEqual(metricsDashboardResponse); + }); + }); + + it('returns a dashboard response after retrying twice', () => { + mock.onGet(dashboardEndpoint).replyOnce(statusCodes.NO_CONTENT); + mock.onGet(dashboardEndpoint).replyOnce(statusCodes.NO_CONTENT); + mock.onGet(dashboardEndpoint).reply(statusCodes.OK, response); + + return getDashboard(dashboardEndpoint, params).then(data => { + expect(data).toEqual(metricsDashboardResponse); + expect(mock.history.get).toHaveLength(3); + }); + }); + + it('rejects after getting an error', () => { + mock.onGet(dashboardEndpoint).reply(500); + + return getDashboard(dashboardEndpoint, params).catch(error => { + expect(error).toEqual(expect.any(Error)); + expect(mock.history.get).toHaveLength(1); + }); + }); + }); + + describe('getPrometheusQueryData', () => { + const response = { + status: 'success', + data: { + resultType: 'matrix', + result: [], + }, + }; + const prometheusEndpoint = '/query_range'; + const params = { + start_time: 'start_time', + end_time: 'end_time', + }; + + it('returns a dashboard response', () => { + mock.onGet(prometheusEndpoint).reply(statusCodes.OK, response); + + return getPrometheusQueryData(prometheusEndpoint, params).then(data => { + expect(data).toEqual(response.data); + }); + }); + + it('returns a dashboard response after retrying twice', () => { + // Mock multiple attempts while the cache is filling up + mock.onGet(prometheusEndpoint).replyOnce(statusCodes.NO_CONTENT); + mock.onGet(prometheusEndpoint).replyOnce(statusCodes.NO_CONTENT); + mock.onGet(prometheusEndpoint).reply(statusCodes.OK, response); // 3rd attempt + + return getPrometheusQueryData(prometheusEndpoint, params).then(data => { + expect(data).toEqual(response.data); + expect(mock.history.get).toHaveLength(3); + }); + }); + + it('rejects after getting an HTTP 500 error', () => { + mock.onGet(prometheusEndpoint).reply(500, { + status: 'error', + error: 'An error ocurred', + }); + + return getPrometheusQueryData(prometheusEndpoint, params).catch(error => { + expect(error).toEqual(new Error('Request failed with status code 500')); + }); + }); + + it('rejects after retrying twice and getting an HTTP 401 error', () => { + // Mock multiple attempts while the cache is filling up and fails + mock.onGet(prometheusEndpoint).reply(statusCodes.UNAUTHORIZED, { + status: 'error', + error: 'An error ocurred', + }); + + return getPrometheusQueryData(prometheusEndpoint, params).catch(error => { + expect(error).toEqual(new Error('Request failed with status code 401')); + }); + }); + + it('rejects after retrying twice and getting an HTTP 500 error', () => { + // Mock multiple attempts while the cache is filling up and fails + mock.onGet(prometheusEndpoint).replyOnce(statusCodes.NO_CONTENT); + mock.onGet(prometheusEndpoint).replyOnce(statusCodes.NO_CONTENT); + mock.onGet(prometheusEndpoint).reply(500, { + status: 'error', + error: 'An error ocurred', + }); // 3rd attempt + + return getPrometheusQueryData(prometheusEndpoint, params).catch(error => { + expect(error).toEqual(new Error('Request failed with status code 500')); + expect(mock.history.get).toHaveLength(3); + }); + }); + + test.each` + code | reason + ${statusCodes.BAD_REQUEST} | ${'Parameters are missing or incorrect'} + ${statusCodes.UNPROCESSABLE_ENTITY} | ${"Expression can't be executed"} + ${statusCodes.SERVICE_UNAVAILABLE} | ${'Query timed out or aborted'} + `('rejects with details: "$reason" after getting an HTTP $code error', ({ code, reason }) => { + mock.onGet(prometheusEndpoint).reply(code, { + status: 'error', + error: reason, + }); + + return getPrometheusQueryData(prometheusEndpoint, params).catch(error => { + expect(error).toEqual(new Error(reason)); + expect(mock.history.get).toHaveLength(1); + }); + }); + }); +}); diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js index 22f2b2e3c77..a6d6accbe23 100644 --- a/spec/frontend/monitoring/store/actions_spec.js +++ b/spec/frontend/monitoring/store/actions_spec.js @@ -8,6 +8,7 @@ import createFlash from '~/flash'; import { defaultTimeRange } from '~/vue_shared/constants'; import * as getters from '~/monitoring/stores/getters'; import { ENVIRONMENT_AVAILABLE_STATE } from '~/monitoring/constants'; +import { backoffMockImplementation } from 'jest/helpers/backoff_helper'; import { createStore } from '~/monitoring/stores'; import * as types from '~/monitoring/stores/mutation_types'; @@ -73,19 +74,7 @@ describe('Monitoring store actions', () => { commit = jest.fn(); dispatch = jest.fn(); - jest.spyOn(commonUtils, 'backOff').mockImplementation(callback => { - const q = new Promise((resolve, reject) => { - const stop = arg => (arg instanceof Error ? reject(arg) : resolve(arg)); - const next = () => callback(next, stop); - // Define a timeout based on a mock timer - setTimeout(() => { - callback(next, stop); - }); - }); - // Run all resolved promises in chain - jest.runOnlyPendingTimers(); - return q; - }); + jest.spyOn(commonUtils, 'backOff').mockImplementation(backoffMockImplementation); }); afterEach(() => { @@ -483,7 +472,6 @@ describe('Monitoring store actions', () => { ], [], () => { - expect(mock.history.get).toHaveLength(1); done(); }, ).catch(done.fail); @@ -569,46 +557,8 @@ describe('Monitoring store actions', () => { }); }); - it('commits result, when waiting for results', done => { - // Mock multiple attempts while the cache is filling up - mock.onGet(prometheusEndpointPath).replyOnce(statusCodes.NO_CONTENT); - mock.onGet(prometheusEndpointPath).replyOnce(statusCodes.NO_CONTENT); - mock.onGet(prometheusEndpointPath).replyOnce(statusCodes.NO_CONTENT); - mock.onGet(prometheusEndpointPath).reply(200, { data }); // 4th attempt - - testAction( - fetchPrometheusMetric, - { metric, defaultQueryParams }, - state, - [ - { - type: types.REQUEST_METRIC_RESULT, - payload: { - metricId: metric.metricId, - }, - }, - { - type: types.RECEIVE_METRIC_RESULT_SUCCESS, - payload: { - metricId: metric.metricId, - data, - }, - }, - ], - [], - () => { - expect(mock.history.get).toHaveLength(4); - done(); - }, - ).catch(done.fail); - }); - it('commits failure, when waiting for results and getting a server error', done => { - // Mock multiple attempts while the cache is filling up and fails - mock.onGet(prometheusEndpointPath).replyOnce(statusCodes.NO_CONTENT); - mock.onGet(prometheusEndpointPath).replyOnce(statusCodes.NO_CONTENT); - mock.onGet(prometheusEndpointPath).replyOnce(statusCodes.NO_CONTENT); - mock.onGet(prometheusEndpointPath).reply(500); // 4th attempt + mock.onGet(prometheusEndpointPath).reply(500); const error = new Error('Request failed with status code 500'); @@ -633,7 +583,6 @@ describe('Monitoring store actions', () => { ], [], ).catch(e => { - expect(mock.history.get).toHaveLength(4); expect(e).toEqual(error); done(); }); diff --git a/spec/frontend/snippets/components/show_spec.js b/spec/frontend/snippets/components/show_spec.js index b5446e70028..1906380e60e 100644 --- a/spec/frontend/snippets/components/show_spec.js +++ b/spec/frontend/snippets/components/show_spec.js @@ -3,17 +3,25 @@ import BlobEmbeddable from '~/blob/components/blob_embeddable.vue'; import SnippetHeader from '~/snippets/components/snippet_header.vue'; import SnippetTitle from '~/snippets/components/snippet_title.vue'; import SnippetBlob from '~/snippets/components/snippet_blob_view.vue'; +import CloneDropdownButton from '~/vue_shared/components/clone_dropdown.vue'; import { GlLoadingIcon } from '@gitlab/ui'; import { Blob, BinaryBlob } from 'jest/blob/components/mock_data'; import { shallowMount } from '@vue/test-utils'; -import { SNIPPET_VISIBILITY_PUBLIC } from '~/snippets/constants'; +import { + SNIPPET_VISIBILITY_INTERNAL, + SNIPPET_VISIBILITY_PRIVATE, + SNIPPET_VISIBILITY_PUBLIC, +} from '~/snippets/constants'; describe('Snippet view app', () => { let wrapper; const defaultProps = { snippetGid: 'gid://gitlab/PersonalSnippet/42', }; + const webUrl = 'http://foo.bar'; + const dummyHTTPUrl = webUrl; + const dummySSHUrl = 'ssh://foo.bar'; function createComponent({ props = defaultProps, data = {}, loading = false } = {}) { const $apollo = { @@ -72,4 +80,47 @@ describe('Snippet view app', () => { expect(blobs.at(0).props('blob')).toEqual(Blob); expect(blobs.at(1).props('blob')).toEqual(BinaryBlob); }); + + describe('Embed dropdown rendering', () => { + it.each` + visibilityLevel | condition | isRendered + ${SNIPPET_VISIBILITY_INTERNAL} | ${'not render'} | ${false} + ${SNIPPET_VISIBILITY_PRIVATE} | ${'not render'} | ${false} + ${'foo'} | ${'not render'} | ${false} + ${SNIPPET_VISIBILITY_PUBLIC} | ${'render'} | ${true} + `('does $condition blob-embeddable by default', ({ visibilityLevel, isRendered }) => { + createComponent({ + data: { + snippet: { + visibilityLevel, + webUrl, + }, + }, + }); + expect(wrapper.contains(BlobEmbeddable)).toBe(isRendered); + }); + }); + + describe('Clone button rendering', () => { + it.each` + httpUrlToRepo | sshUrlToRepo | shouldRender | isRendered + ${null} | ${null} | ${'Should not'} | ${false} + ${null} | ${dummySSHUrl} | ${'Should'} | ${true} + ${dummyHTTPUrl} | ${null} | ${'Should'} | ${true} + ${dummyHTTPUrl} | ${dummySSHUrl} | ${'Should'} | ${true} + `( + '$shouldRender render "Clone" button when `httpUrlToRepo` is $httpUrlToRepo and `sshUrlToRepo` is $sshUrlToRepo', + ({ httpUrlToRepo, sshUrlToRepo, isRendered }) => { + createComponent({ + data: { + snippet: { + sshUrlToRepo, + httpUrlToRepo, + }, + }, + }); + expect(wrapper.contains(CloneDropdownButton)).toBe(isRendered); + }, + ); + }); }); diff --git a/spec/frontend/snippets/components/snippet_blob_view_spec.js b/spec/frontend/snippets/components/snippet_blob_view_spec.js index c8f1c8fc8a9..0de130aa667 100644 --- a/spec/frontend/snippets/components/snippet_blob_view_spec.js +++ b/spec/frontend/snippets/components/snippet_blob_view_spec.js @@ -1,7 +1,6 @@ import { mount } from '@vue/test-utils'; import SnippetBlobView from '~/snippets/components/snippet_blob_view.vue'; import BlobHeader from '~/blob/components/blob_header.vue'; -import BlobEmbeddable from '~/blob/components/blob_embeddable.vue'; import BlobContent from '~/blob/components/blob_content.vue'; import { BLOB_RENDER_EVENT_LOAD, @@ -9,11 +8,7 @@ import { BLOB_RENDER_ERRORS, } from '~/blob/components/constants'; import { RichViewer, SimpleViewer } from '~/vue_shared/components/blob_viewers'; -import { - SNIPPET_VISIBILITY_PRIVATE, - SNIPPET_VISIBILITY_INTERNAL, - SNIPPET_VISIBILITY_PUBLIC, -} from '~/snippets/constants'; +import { SNIPPET_VISIBILITY_PUBLIC } from '~/snippets/constants'; import { Blob as BlobMock, SimpleViewerMock, RichViewerMock } from 'jest/blob/components/mock_data'; @@ -72,18 +67,6 @@ describe('Blob Embeddable', () => { expect(wrapper.find(BlobContent).exists()).toBe(true); }); - it.each([SNIPPET_VISIBILITY_INTERNAL, SNIPPET_VISIBILITY_PRIVATE, 'foo'])( - 'does not render blob-embeddable by default', - visibilityLevel => { - createComponent({ - snippetProps: { - visibilityLevel, - }, - }); - expect(wrapper.find(BlobEmbeddable).exists()).toBe(false); - }, - ); - it('sets simple viewer correctly', () => { createComponent(); expect(wrapper.find(SimpleViewer).exists()).toBe(true); diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index c1386ac4eb2..7a1b27d563c 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -231,7 +231,7 @@ RSpec.describe PipelineSerializer do # :source_pipeline and :source_job # Existing numbers are high and require performance optimization # https://gitlab.com/gitlab-org/gitlab/-/issues/225156 - expected_queries = Gitlab.ee? ? 101 : 92 + expected_queries = Gitlab.ee? ? 95 : 86 expect(recorded.count).to be_within(2).of(expected_queries) expect(recorded.cached_count).to eq(0) |