summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-07-24 12:09:34 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-07-24 12:09:34 +0000
commit0c27b33a8d0e2582b09579676677df5eaebfdbe2 (patch)
treeddc30f64f1a1da653cf89ff554f48376fde1d6ac /spec
parentff83f24eacc7c78884458d7261086b4f3a9547bb (diff)
downloadgitlab-ce-0c27b33a8d0e2582b09579676677df5eaebfdbe2.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/frontend/helpers/backoff_helper.js33
-rw-r--r--spec/frontend/monitoring/requests/index_spec.js149
-rw-r--r--spec/frontend/monitoring/store/actions_spec.js57
-rw-r--r--spec/frontend/snippets/components/show_spec.js53
-rw-r--r--spec/frontend/snippets/components/snippet_blob_view_spec.js19
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb2
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)