From 97b8853d617060fe38c861360181ef83321727e2 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 7 Apr 2019 03:26:58 +0100 Subject: Move karma serverless tests to jest for isolation These tests cause master failures because of an overflow in vue-test-utils sync watcher setup. --- spec/frontend/serverless/components/area_spec.js | 121 ++++++++++++++++++ .../serverless/components/environment_row_spec.js | 70 +++++++++++ .../serverless/components/function_details_spec.js | 113 +++++++++++++++++ .../serverless/components/function_row_spec.js | 26 ++++ .../serverless/components/functions_spec.js | 101 +++++++++++++++ .../components/missing_prometheus_spec.js | 37 ++++++ .../frontend/serverless/components/pod_box_spec.js | 22 ++++ spec/frontend/serverless/components/url_spec.js | 24 ++++ spec/frontend/serverless/mock_data.js | 136 +++++++++++++++++++++ spec/frontend/serverless/store/actions_spec.js | 88 +++++++++++++ spec/frontend/serverless/store/getters_spec.js | 43 +++++++ spec/frontend/serverless/store/mutations_spec.js | 86 +++++++++++++ spec/frontend/serverless/utils.js | 20 +++ .../javascripts/serverless/components/area_spec.js | 121 ------------------ .../serverless/components/environment_row_spec.js | 70 ----------- .../serverless/components/function_details_spec.js | 113 ----------------- .../serverless/components/function_row_spec.js | 26 ---- .../serverless/components/functions_spec.js | 101 --------------- .../components/missing_prometheus_spec.js | 37 ------ .../serverless/components/pod_box_spec.js | 22 ---- spec/javascripts/serverless/components/url_spec.js | 24 ---- spec/javascripts/serverless/mock_data.js | 136 --------------------- spec/javascripts/serverless/store/actions_spec.js | 88 ------------- spec/javascripts/serverless/store/getters_spec.js | 43 ------- .../javascripts/serverless/store/mutations_spec.js | 86 ------------- spec/javascripts/serverless/utils.js | 20 --- 26 files changed, 887 insertions(+), 887 deletions(-) create mode 100644 spec/frontend/serverless/components/area_spec.js create mode 100644 spec/frontend/serverless/components/environment_row_spec.js create mode 100644 spec/frontend/serverless/components/function_details_spec.js create mode 100644 spec/frontend/serverless/components/function_row_spec.js create mode 100644 spec/frontend/serverless/components/functions_spec.js create mode 100644 spec/frontend/serverless/components/missing_prometheus_spec.js create mode 100644 spec/frontend/serverless/components/pod_box_spec.js create mode 100644 spec/frontend/serverless/components/url_spec.js create mode 100644 spec/frontend/serverless/mock_data.js create mode 100644 spec/frontend/serverless/store/actions_spec.js create mode 100644 spec/frontend/serverless/store/getters_spec.js create mode 100644 spec/frontend/serverless/store/mutations_spec.js create mode 100644 spec/frontend/serverless/utils.js delete mode 100644 spec/javascripts/serverless/components/area_spec.js delete mode 100644 spec/javascripts/serverless/components/environment_row_spec.js delete mode 100644 spec/javascripts/serverless/components/function_details_spec.js delete mode 100644 spec/javascripts/serverless/components/function_row_spec.js delete mode 100644 spec/javascripts/serverless/components/functions_spec.js delete mode 100644 spec/javascripts/serverless/components/missing_prometheus_spec.js delete mode 100644 spec/javascripts/serverless/components/pod_box_spec.js delete mode 100644 spec/javascripts/serverless/components/url_spec.js delete mode 100644 spec/javascripts/serverless/mock_data.js delete mode 100644 spec/javascripts/serverless/store/actions_spec.js delete mode 100644 spec/javascripts/serverless/store/getters_spec.js delete mode 100644 spec/javascripts/serverless/store/mutations_spec.js delete mode 100644 spec/javascripts/serverless/utils.js diff --git a/spec/frontend/serverless/components/area_spec.js b/spec/frontend/serverless/components/area_spec.js new file mode 100644 index 00000000000..2be6ac3d268 --- /dev/null +++ b/spec/frontend/serverless/components/area_spec.js @@ -0,0 +1,121 @@ +import { shallowMount } from '@vue/test-utils'; +import Area from '~/serverless/components/area.vue'; +import { mockNormalizedMetrics } from '../mock_data'; + +describe('Area component', () => { + const mockWidgets = 'mockWidgets'; + const mockGraphData = mockNormalizedMetrics; + let areaChart; + + beforeEach(() => { + areaChart = shallowMount(Area, { + propsData: { + graphData: mockGraphData, + containerWidth: 0, + }, + slots: { + default: mockWidgets, + }, + }); + }); + + afterEach(() => { + areaChart.destroy(); + }); + + it('renders chart title', () => { + expect(areaChart.find({ ref: 'graphTitle' }).text()).toBe(mockGraphData.title); + }); + + it('contains graph widgets from slot', () => { + expect(areaChart.find({ ref: 'graphWidgets' }).text()).toBe(mockWidgets); + }); + + describe('methods', () => { + describe('formatTooltipText', () => { + const mockDate = mockNormalizedMetrics.queries[0].result[0].values[0].time; + const generateSeriesData = type => ({ + seriesData: [ + { + componentSubType: type, + value: [mockDate, 4], + }, + ], + value: mockDate, + }); + + describe('series is of line type', () => { + beforeEach(() => { + areaChart.vm.formatTooltipText(generateSeriesData('line')); + }); + + it('formats tooltip title', () => { + expect(areaChart.vm.tooltipPopoverTitle).toBe('28 Feb 2019, 11:11AM'); + }); + + it('formats tooltip content', () => { + expect(areaChart.vm.tooltipPopoverContent).toBe('Invocations (requests): 4'); + }); + }); + + it('verify default interval value of 1', () => { + expect(areaChart.vm.getInterval).toBe(1); + }); + }); + + describe('onResize', () => { + const mockWidth = 233; + + beforeEach(() => { + spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({ + width: mockWidth, + })); + areaChart.vm.onResize(); + }); + + it('sets area chart width', () => { + expect(areaChart.vm.width).toBe(mockWidth); + }); + }); + }); + + describe('computed', () => { + describe('chartData', () => { + it('utilizes all data points', () => { + expect(Object.keys(areaChart.vm.chartData)).toEqual(['requests']); + expect(areaChart.vm.chartData.requests.length).toBe(2); + }); + + it('creates valid data', () => { + const data = areaChart.vm.chartData.requests; + + expect( + data.filter( + datum => new Date(datum.time).getTime() > 0 && typeof datum.value === 'number', + ).length, + ).toBe(data.length); + }); + }); + + describe('generateSeries', () => { + it('utilizes correct time data', () => { + expect(areaChart.vm.generateSeries.data).toEqual([ + ['2019-02-28T11:11:38.756Z', 0], + ['2019-02-28T11:12:38.756Z', 0], + ]); + }); + }); + + describe('xAxisLabel', () => { + it('constructs a label for the chart x-axis', () => { + expect(areaChart.vm.xAxisLabel).toBe('invocations / minute'); + }); + }); + + describe('yAxisLabel', () => { + it('constructs a label for the chart y-axis', () => { + expect(areaChart.vm.yAxisLabel).toBe('Invocations (requests)'); + }); + }); + }); +}); diff --git a/spec/frontend/serverless/components/environment_row_spec.js b/spec/frontend/serverless/components/environment_row_spec.js new file mode 100644 index 00000000000..932d712dbec --- /dev/null +++ b/spec/frontend/serverless/components/environment_row_spec.js @@ -0,0 +1,70 @@ +import environmentRowComponent from '~/serverless/components/environment_row.vue'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; + +import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data'; +import { translate } from '~/serverless/utils'; + +const createComponent = (localVue, env, envName) => + shallowMount(environmentRowComponent, { localVue, propsData: { env, envName } }).vm; + +describe('environment row component', () => { + describe('default global cluster case', () => { + let localVue; + let vm; + + beforeEach(() => { + localVue = createLocalVue(); + vm = createComponent(localVue, translate(mockServerlessFunctions)['*'], '*'); + }); + + afterEach(() => vm.$destroy()); + + it('has the correct envId', () => { + expect(vm.envId).toEqual('env-global'); + }); + + it('is open by default', () => { + expect(vm.isOpenClass).toEqual({ 'is-open': true }); + }); + + it('generates correct output', () => { + expect(vm.$el.id).toEqual('env-global'); + expect(vm.$el.classList.contains('is-open')).toBe(true); + expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('*'); + }); + + it('opens and closes correctly', () => { + expect(vm.isOpen).toBe(true); + + vm.toggleOpen(); + + expect(vm.isOpen).toBe(false); + }); + }); + + describe('default named cluster case', () => { + let vm; + let localVue; + + beforeEach(() => { + localVue = createLocalVue(); + vm = createComponent(localVue, translate(mockServerlessFunctionsDiffEnv).test, 'test'); + }); + + afterEach(() => vm.$destroy()); + + it('has the correct envId', () => { + expect(vm.envId).toEqual('env-test'); + }); + + it('is open by default', () => { + expect(vm.isOpenClass).toEqual({ 'is-open': true }); + }); + + it('generates correct output', () => { + expect(vm.$el.id).toEqual('env-test'); + expect(vm.$el.classList.contains('is-open')).toBe(true); + expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('test'); + }); + }); +}); diff --git a/spec/frontend/serverless/components/function_details_spec.js b/spec/frontend/serverless/components/function_details_spec.js new file mode 100644 index 00000000000..a29d4a296ef --- /dev/null +++ b/spec/frontend/serverless/components/function_details_spec.js @@ -0,0 +1,113 @@ +import Vuex from 'vuex'; + +import functionDetailsComponent from '~/serverless/components/function_details.vue'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { createStore } from '~/serverless/store'; + +describe('functionDetailsComponent', () => { + let localVue; + let component; + let store; + + beforeEach(() => { + localVue = createLocalVue(); + localVue.use(Vuex); + + store = createStore(); + }); + + afterEach(() => { + component.vm.$destroy(); + }); + + describe('Verify base functionality', () => { + const serviceStub = { + name: 'test', + description: 'a description', + environment: '*', + url: 'http://service.com/test', + namespace: 'test-ns', + podcount: 0, + metricsUrl: '/metrics', + }; + + it('has a name, description, URL, and no pods loaded', () => { + component = shallowMount(functionDetailsComponent, { + localVue, + store, + propsData: { + func: serviceStub, + hasPrometheus: false, + clustersPath: '/clusters', + helpPath: '/help', + }, + }); + + expect( + component.vm.$el.querySelector('.serverless-function-name').innerHTML.trim(), + ).toContain('test'); + + expect( + component.vm.$el.querySelector('.serverless-function-description').innerHTML.trim(), + ).toContain('a description'); + + expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain( + 'No pods loaded at this time.', + ); + }); + + it('has a pods loaded', () => { + serviceStub.podcount = 1; + + component = shallowMount(functionDetailsComponent, { + localVue, + store, + propsData: { + func: serviceStub, + hasPrometheus: false, + clustersPath: '/clusters', + helpPath: '/help', + }, + }); + + expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('1 pod in use'); + }); + + it('has multiple pods loaded', () => { + serviceStub.podcount = 3; + + component = shallowMount(functionDetailsComponent, { + localVue, + store, + propsData: { + func: serviceStub, + hasPrometheus: false, + clustersPath: '/clusters', + helpPath: '/help', + }, + }); + + expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('3 pods in use'); + }); + + it('can support a missing description', () => { + serviceStub.description = null; + + component = shallowMount(functionDetailsComponent, { + localVue, + store, + propsData: { + func: serviceStub, + hasPrometheus: false, + clustersPath: '/clusters', + helpPath: '/help', + }, + }); + + expect( + component.vm.$el.querySelector('.serverless-function-description').querySelector('div') + .innerHTML.length, + ).toEqual(0); + }); + }); +}); diff --git a/spec/frontend/serverless/components/function_row_spec.js b/spec/frontend/serverless/components/function_row_spec.js new file mode 100644 index 00000000000..3987e1753bd --- /dev/null +++ b/spec/frontend/serverless/components/function_row_spec.js @@ -0,0 +1,26 @@ +import functionRowComponent from '~/serverless/components/function_row.vue'; +import { shallowMount } from '@vue/test-utils'; + +import { mockServerlessFunction } from '../mock_data'; + +const createComponent = func => shallowMount(functionRowComponent, { propsData: { func } }).vm; + +describe('functionRowComponent', () => { + it('Parses the function details correctly', () => { + const vm = createComponent(mockServerlessFunction); + + expect(vm.$el.querySelector('b').innerHTML).toEqual(mockServerlessFunction.name); + expect(vm.$el.querySelector('span').innerHTML).toEqual(mockServerlessFunction.image); + expect(vm.$el.querySelector('timeago-stub').getAttribute('time')).not.toBe(null); + + vm.$destroy(); + }); + + it('handles clicks correctly', () => { + const vm = createComponent(mockServerlessFunction); + + expect(vm.checkClass(vm.$el.querySelector('p'))).toBe(true); // check somewhere inside the row + + vm.$destroy(); + }); +}); diff --git a/spec/frontend/serverless/components/functions_spec.js b/spec/frontend/serverless/components/functions_spec.js new file mode 100644 index 00000000000..c32978ea58a --- /dev/null +++ b/spec/frontend/serverless/components/functions_spec.js @@ -0,0 +1,101 @@ +import Vuex from 'vuex'; + +import functionsComponent from '~/serverless/components/functions.vue'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { createStore } from '~/serverless/store'; +import { mockServerlessFunctions } from '../mock_data'; + +describe('functionsComponent', () => { + let component; + let store; + let localVue; + + beforeEach(() => { + localVue = createLocalVue(); + localVue.use(Vuex); + + store = createStore(); + }); + + afterEach(() => { + component.vm.$destroy(); + }); + + it('should render empty state when Knative is not installed', () => { + component = shallowMount(functionsComponent, { + localVue, + store, + propsData: { + installed: false, + clustersPath: '', + helpPath: '', + statusPath: '', + }, + sync: false, + }); + + expect(component.vm.$el.querySelector('emptystate-stub')).not.toBe(null); + }); + + it('should render a loading component', () => { + store.dispatch('requestFunctionsLoading'); + component = shallowMount(functionsComponent, { + localVue, + store, + propsData: { + installed: true, + clustersPath: '', + helpPath: '', + statusPath: '', + }, + sync: false, + }); + + expect(component.vm.$el.querySelector('glloadingicon-stub')).not.toBe(null); + }); + + it('should render empty state when there is no function data', () => { + store.dispatch('receiveFunctionsNoDataSuccess'); + component = shallowMount(functionsComponent, { + localVue, + store, + propsData: { + installed: true, + clustersPath: '', + helpPath: '', + statusPath: '', + }, + sync: false, + }); + + expect( + component.vm.$el + .querySelector('.empty-state, .js-empty-state') + .classList.contains('js-empty-state'), + ).toBe(true); + + expect(component.vm.$el.querySelector('.state-title, .text-center').innerHTML.trim()).toEqual( + 'No functions available', + ); + }); + + it('should render the functions list', () => { + component = shallowMount(functionsComponent, { + localVue, + store, + propsData: { + installed: true, + clustersPath: '', + helpPath: '', + statusPath: '', + }, + sync: false, + }); + + component.vm.$store.dispatch('receiveFunctionsSuccess', mockServerlessFunctions); + + return component.vm.$nextTick().then(() => { + expect(component.vm.$el.querySelector('environmentrow-stub')).not.toBe(null); + }); + }); +}); diff --git a/spec/frontend/serverless/components/missing_prometheus_spec.js b/spec/frontend/serverless/components/missing_prometheus_spec.js new file mode 100644 index 00000000000..77aca03772b --- /dev/null +++ b/spec/frontend/serverless/components/missing_prometheus_spec.js @@ -0,0 +1,37 @@ +import missingPrometheusComponent from '~/serverless/components/missing_prometheus.vue'; +import { shallowMount } from '@vue/test-utils'; + +const createComponent = missingData => + shallowMount(missingPrometheusComponent, { + propsData: { + clustersPath: '/clusters', + helpPath: '/help', + missingData, + }, + }).vm; + +describe('missingPrometheusComponent', () => { + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + it('should render missing prometheus message', () => { + vm = createComponent(false); + + expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( + 'Function invocation metrics require Prometheus to be installed first.', + ); + + expect(vm.$el.querySelector('glbutton-stub').getAttribute('variant')).toEqual('success'); + }); + + it('should render no prometheus data message', () => { + vm = createComponent(true); + + expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( + 'Invocation metrics loading or not available at this time.', + ); + }); +}); diff --git a/spec/frontend/serverless/components/pod_box_spec.js b/spec/frontend/serverless/components/pod_box_spec.js new file mode 100644 index 00000000000..69ac1a2bb5f --- /dev/null +++ b/spec/frontend/serverless/components/pod_box_spec.js @@ -0,0 +1,22 @@ +import podBoxComponent from '~/serverless/components/pod_box.vue'; +import { shallowMount } from '@vue/test-utils'; + +const createComponent = count => + shallowMount(podBoxComponent, { + propsData: { + count, + }, + }).vm; + +describe('podBoxComponent', () => { + it('should render three boxes', () => { + const count = 3; + const vm = createComponent(count); + const rects = vm.$el.querySelectorAll('rect'); + + expect(rects.length).toEqual(3); + expect(parseInt(rects[2].getAttribute('x'), 10)).toEqual(40); + + vm.$destroy(); + }); +}); diff --git a/spec/frontend/serverless/components/url_spec.js b/spec/frontend/serverless/components/url_spec.js new file mode 100644 index 00000000000..08c3e4146b1 --- /dev/null +++ b/spec/frontend/serverless/components/url_spec.js @@ -0,0 +1,24 @@ +import Vue from 'vue'; +import urlComponent from '~/serverless/components/url.vue'; +import { shallowMount } from '@vue/test-utils'; + +const createComponent = uri => + shallowMount(Vue.extend(urlComponent), { + propsData: { + uri, + }, + }).vm; + +describe('urlComponent', () => { + it('should render correctly', () => { + const uri = 'http://testfunc.apps.example.com'; + const vm = createComponent(uri); + + expect(vm.$el.classList.contains('clipboard-group')).toBe(true); + expect(vm.$el.querySelector('clipboardbutton-stub').getAttribute('text')).toEqual(uri); + + expect(vm.$el.querySelector('.url-text-field').innerHTML).toEqual(uri); + + vm.$destroy(); + }); +}); diff --git a/spec/frontend/serverless/mock_data.js b/spec/frontend/serverless/mock_data.js new file mode 100644 index 00000000000..a2c18616324 --- /dev/null +++ b/spec/frontend/serverless/mock_data.js @@ -0,0 +1,136 @@ +export const mockServerlessFunctions = [ + { + name: 'testfunc1', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', + podcount: null, + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc1.tm-example.apps.example.com', + description: 'A test service', + image: 'knative-test-container-buildtemplate', + }, + { + name: 'testfunc2', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc2', + podcount: null, + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc2.tm-example.apps.example.com', + description: 'A second test service\nThis one with additional descriptions', + image: 'knative-test-echo-buildtemplate', + }, +]; + +export const mockServerlessFunctionsDiffEnv = [ + { + name: 'testfunc1', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', + podcount: null, + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc1.tm-example.apps.example.com', + description: 'A test service', + image: 'knative-test-container-buildtemplate', + }, + { + name: 'testfunc2', + namespace: 'tm-example', + environment_scope: 'test', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc2', + podcount: null, + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc2.tm-example.apps.example.com', + description: 'A second test service\nThis one with additional descriptions', + image: 'knative-test-echo-buildtemplate', + }, +]; + +export const mockServerlessFunction = { + name: 'testfunc1', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', + podcount: '3', + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc1.tm-example.apps.example.com', + description: 'A test service', + image: 'knative-test-container-buildtemplate', +}; + +export const mockMultilineServerlessFunction = { + name: 'testfunc1', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', + podcount: '3', + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc1.tm-example.apps.example.com', + description: 'testfunc1\nA test service line\\nWith additional services', + image: 'knative-test-container-buildtemplate', +}; + +export const mockMetrics = { + success: true, + last_update: '2019-02-28T19:11:38.926Z', + metrics: { + id: 22, + title: 'Knative function invocations', + required_metrics: ['container_memory_usage_bytes', 'container_cpu_usage_seconds_total'], + weight: 0, + y_label: 'Invocations', + queries: [ + { + query_range: + 'floor(sum(rate(istio_revision_request_count{destination_configuration="%{function_name}", destination_namespace="%{kube_namespace}"}[1m])*30))', + unit: 'requests', + label: 'invocations / minute', + result: [ + { + metric: {}, + values: [[1551352298.756, '0'], [1551352358.756, '0']], + }, + ], + }, + ], + }, +}; + +export const mockNormalizedMetrics = { + id: 22, + title: 'Knative function invocations', + required_metrics: ['container_memory_usage_bytes', 'container_cpu_usage_seconds_total'], + weight: 0, + y_label: 'Invocations', + queries: [ + { + query_range: + 'floor(sum(rate(istio_revision_request_count{destination_configuration="%{function_name}", destination_namespace="%{kube_namespace}"}[1m])*30))', + unit: 'requests', + label: 'invocations / minute', + result: [ + { + metric: {}, + values: [ + { + time: '2019-02-28T11:11:38.756Z', + value: 0, + }, + { + time: '2019-02-28T11:12:38.756Z', + value: 0, + }, + ], + }, + ], + }, + ], +}; diff --git a/spec/frontend/serverless/store/actions_spec.js b/spec/frontend/serverless/store/actions_spec.js new file mode 100644 index 00000000000..602798573e9 --- /dev/null +++ b/spec/frontend/serverless/store/actions_spec.js @@ -0,0 +1,88 @@ +import MockAdapter from 'axios-mock-adapter'; +import statusCodes from '~/lib/utils/http_status'; +import { fetchFunctions, fetchMetrics } from '~/serverless/store/actions'; +import { mockServerlessFunctions, mockMetrics } from '../mock_data'; +import axios from '~/lib/utils/axios_utils'; +import testAction from '../../helpers/vuex_action_helper'; +import { adjustMetricQuery } from '../utils'; + +describe('ServerlessActions', () => { + describe('fetchFunctions', () => { + it('should successfully fetch functions', done => { + const endpoint = '/functions'; + const mock = new MockAdapter(axios); + mock.onGet(endpoint).reply(statusCodes.OK, JSON.stringify(mockServerlessFunctions)); + + testAction( + fetchFunctions, + { functionsPath: endpoint }, + {}, + [], + [ + { type: 'requestFunctionsLoading' }, + { type: 'receiveFunctionsSuccess', payload: mockServerlessFunctions }, + ], + () => { + mock.restore(); + done(); + }, + ); + }); + + it('should successfully retry', done => { + const endpoint = '/functions'; + const mock = new MockAdapter(axios); + mock.onGet(endpoint).reply(statusCodes.NO_CONTENT); + + testAction( + fetchFunctions, + { functionsPath: endpoint }, + {}, + [], + [{ type: 'requestFunctionsLoading' }], + () => { + mock.restore(); + done(); + }, + ); + }); + }); + + describe('fetchMetrics', () => { + it('should return no prometheus', done => { + const endpoint = '/metrics'; + const mock = new MockAdapter(axios); + mock.onGet(endpoint).reply(statusCodes.NO_CONTENT); + + testAction( + fetchMetrics, + { metricsPath: endpoint, hasPrometheus: false }, + {}, + [], + [{ type: 'receiveMetricsNoPrometheus' }], + () => { + mock.restore(); + done(); + }, + ); + }); + + it('should successfully fetch metrics', done => { + const endpoint = '/metrics'; + const mock = new MockAdapter(axios); + mock.onGet(endpoint).reply(statusCodes.OK, JSON.stringify(mockMetrics)); + + testAction( + fetchMetrics, + { metricsPath: endpoint, hasPrometheus: true }, + {}, + [], + [{ type: 'receiveMetricsSuccess', payload: adjustMetricQuery(mockMetrics) }], + () => { + mock.restore(); + done(); + }, + ); + }); + }); +}); diff --git a/spec/frontend/serverless/store/getters_spec.js b/spec/frontend/serverless/store/getters_spec.js new file mode 100644 index 00000000000..fb549c8f153 --- /dev/null +++ b/spec/frontend/serverless/store/getters_spec.js @@ -0,0 +1,43 @@ +import serverlessState from '~/serverless/store/state'; +import * as getters from '~/serverless/store/getters'; +import { mockServerlessFunctions } from '../mock_data'; + +describe('Serverless Store Getters', () => { + let state; + + beforeEach(() => { + state = serverlessState; + }); + + describe('hasPrometheusMissingData', () => { + it('should return false if Prometheus is not installed', () => { + state.hasPrometheus = false; + + expect(getters.hasPrometheusMissingData(state)).toEqual(false); + }); + + it('should return false if Prometheus is installed and there is data', () => { + state.hasPrometheusData = true; + + expect(getters.hasPrometheusMissingData(state)).toEqual(false); + }); + + it('should return true if Prometheus is installed and there is no data', () => { + state.hasPrometheus = true; + state.hasPrometheusData = false; + + expect(getters.hasPrometheusMissingData(state)).toEqual(true); + }); + }); + + describe('getFunctions', () => { + it('should translate the raw function array to group the functions per environment scope', () => { + state.functions = mockServerlessFunctions; + + const funcs = getters.getFunctions(state); + + expect(Object.keys(funcs)).toContain('*'); + expect(funcs['*'].length).toEqual(2); + }); + }); +}); diff --git a/spec/frontend/serverless/store/mutations_spec.js b/spec/frontend/serverless/store/mutations_spec.js new file mode 100644 index 00000000000..ca3053e5c38 --- /dev/null +++ b/spec/frontend/serverless/store/mutations_spec.js @@ -0,0 +1,86 @@ +import mutations from '~/serverless/store/mutations'; +import * as types from '~/serverless/store/mutation_types'; +import { mockServerlessFunctions, mockMetrics } from '../mock_data'; + +describe('ServerlessMutations', () => { + describe('Functions List Mutations', () => { + it('should ensure loading is true', () => { + const state = {}; + + mutations[types.REQUEST_FUNCTIONS_LOADING](state); + + expect(state.isLoading).toEqual(true); + }); + + it('should set proper state once functions are loaded', () => { + const state = {}; + + mutations[types.RECEIVE_FUNCTIONS_SUCCESS](state, mockServerlessFunctions); + + expect(state.isLoading).toEqual(false); + expect(state.hasFunctionData).toEqual(true); + expect(state.functions).toEqual(mockServerlessFunctions); + }); + + it('should ensure loading has stopped and hasFunctionData is false when there are no functions available', () => { + const state = {}; + + mutations[types.RECEIVE_FUNCTIONS_NODATA_SUCCESS](state); + + expect(state.isLoading).toEqual(false); + expect(state.hasFunctionData).toEqual(false); + expect(state.functions).toBe(undefined); + }); + + it('should ensure loading has stopped, and an error is raised', () => { + const state = {}; + + mutations[types.RECEIVE_FUNCTIONS_ERROR](state, 'sample error'); + + expect(state.isLoading).toEqual(false); + expect(state.hasFunctionData).toEqual(false); + expect(state.functions).toBe(undefined); + expect(state.error).not.toBe(undefined); + }); + }); + + describe('Function Details Metrics Mutations', () => { + it('should ensure isLoading and hasPrometheus data flags indicate data is loaded', () => { + const state = {}; + + mutations[types.RECEIVE_METRICS_SUCCESS](state, mockMetrics); + + expect(state.isLoading).toEqual(false); + expect(state.hasPrometheusData).toEqual(true); + expect(state.graphData).toEqual(mockMetrics); + }); + + it('should ensure isLoading and hasPrometheus data flags are cleared indicating no functions available', () => { + const state = {}; + + mutations[types.RECEIVE_METRICS_NODATA_SUCCESS](state); + + expect(state.isLoading).toEqual(false); + expect(state.hasPrometheusData).toEqual(false); + expect(state.graphData).toBe(undefined); + }); + + it('should properly indicate an error', () => { + const state = {}; + + mutations[types.RECEIVE_METRICS_ERROR](state, 'sample error'); + + expect(state.hasPrometheusData).toEqual(false); + expect(state.error).not.toBe(undefined); + }); + + it('should properly indicate when prometheus is installed', () => { + const state = {}; + + mutations[types.RECEIVE_METRICS_NO_PROMETHEUS](state); + + expect(state.hasPrometheus).toEqual(false); + expect(state.hasPrometheusData).toEqual(false); + }); + }); +}); diff --git a/spec/frontend/serverless/utils.js b/spec/frontend/serverless/utils.js new file mode 100644 index 00000000000..5ce2e37d493 --- /dev/null +++ b/spec/frontend/serverless/utils.js @@ -0,0 +1,20 @@ +export const adjustMetricQuery = data => { + const updatedMetric = data.metrics; + + const queries = data.metrics.queries.map(query => ({ + ...query, + result: query.result.map(result => ({ + ...result, + values: result.values.map(([timestamp, value]) => ({ + time: new Date(timestamp * 1000).toISOString(), + value: Number(value), + })), + })), + })); + + updatedMetric.queries = queries; + return updatedMetric; +}; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/spec/javascripts/serverless/components/area_spec.js b/spec/javascripts/serverless/components/area_spec.js deleted file mode 100644 index 2be6ac3d268..00000000000 --- a/spec/javascripts/serverless/components/area_spec.js +++ /dev/null @@ -1,121 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import Area from '~/serverless/components/area.vue'; -import { mockNormalizedMetrics } from '../mock_data'; - -describe('Area component', () => { - const mockWidgets = 'mockWidgets'; - const mockGraphData = mockNormalizedMetrics; - let areaChart; - - beforeEach(() => { - areaChart = shallowMount(Area, { - propsData: { - graphData: mockGraphData, - containerWidth: 0, - }, - slots: { - default: mockWidgets, - }, - }); - }); - - afterEach(() => { - areaChart.destroy(); - }); - - it('renders chart title', () => { - expect(areaChart.find({ ref: 'graphTitle' }).text()).toBe(mockGraphData.title); - }); - - it('contains graph widgets from slot', () => { - expect(areaChart.find({ ref: 'graphWidgets' }).text()).toBe(mockWidgets); - }); - - describe('methods', () => { - describe('formatTooltipText', () => { - const mockDate = mockNormalizedMetrics.queries[0].result[0].values[0].time; - const generateSeriesData = type => ({ - seriesData: [ - { - componentSubType: type, - value: [mockDate, 4], - }, - ], - value: mockDate, - }); - - describe('series is of line type', () => { - beforeEach(() => { - areaChart.vm.formatTooltipText(generateSeriesData('line')); - }); - - it('formats tooltip title', () => { - expect(areaChart.vm.tooltipPopoverTitle).toBe('28 Feb 2019, 11:11AM'); - }); - - it('formats tooltip content', () => { - expect(areaChart.vm.tooltipPopoverContent).toBe('Invocations (requests): 4'); - }); - }); - - it('verify default interval value of 1', () => { - expect(areaChart.vm.getInterval).toBe(1); - }); - }); - - describe('onResize', () => { - const mockWidth = 233; - - beforeEach(() => { - spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({ - width: mockWidth, - })); - areaChart.vm.onResize(); - }); - - it('sets area chart width', () => { - expect(areaChart.vm.width).toBe(mockWidth); - }); - }); - }); - - describe('computed', () => { - describe('chartData', () => { - it('utilizes all data points', () => { - expect(Object.keys(areaChart.vm.chartData)).toEqual(['requests']); - expect(areaChart.vm.chartData.requests.length).toBe(2); - }); - - it('creates valid data', () => { - const data = areaChart.vm.chartData.requests; - - expect( - data.filter( - datum => new Date(datum.time).getTime() > 0 && typeof datum.value === 'number', - ).length, - ).toBe(data.length); - }); - }); - - describe('generateSeries', () => { - it('utilizes correct time data', () => { - expect(areaChart.vm.generateSeries.data).toEqual([ - ['2019-02-28T11:11:38.756Z', 0], - ['2019-02-28T11:12:38.756Z', 0], - ]); - }); - }); - - describe('xAxisLabel', () => { - it('constructs a label for the chart x-axis', () => { - expect(areaChart.vm.xAxisLabel).toBe('invocations / minute'); - }); - }); - - describe('yAxisLabel', () => { - it('constructs a label for the chart y-axis', () => { - expect(areaChart.vm.yAxisLabel).toBe('Invocations (requests)'); - }); - }); - }); -}); diff --git a/spec/javascripts/serverless/components/environment_row_spec.js b/spec/javascripts/serverless/components/environment_row_spec.js deleted file mode 100644 index 932d712dbec..00000000000 --- a/spec/javascripts/serverless/components/environment_row_spec.js +++ /dev/null @@ -1,70 +0,0 @@ -import environmentRowComponent from '~/serverless/components/environment_row.vue'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; - -import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data'; -import { translate } from '~/serverless/utils'; - -const createComponent = (localVue, env, envName) => - shallowMount(environmentRowComponent, { localVue, propsData: { env, envName } }).vm; - -describe('environment row component', () => { - describe('default global cluster case', () => { - let localVue; - let vm; - - beforeEach(() => { - localVue = createLocalVue(); - vm = createComponent(localVue, translate(mockServerlessFunctions)['*'], '*'); - }); - - afterEach(() => vm.$destroy()); - - it('has the correct envId', () => { - expect(vm.envId).toEqual('env-global'); - }); - - it('is open by default', () => { - expect(vm.isOpenClass).toEqual({ 'is-open': true }); - }); - - it('generates correct output', () => { - expect(vm.$el.id).toEqual('env-global'); - expect(vm.$el.classList.contains('is-open')).toBe(true); - expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('*'); - }); - - it('opens and closes correctly', () => { - expect(vm.isOpen).toBe(true); - - vm.toggleOpen(); - - expect(vm.isOpen).toBe(false); - }); - }); - - describe('default named cluster case', () => { - let vm; - let localVue; - - beforeEach(() => { - localVue = createLocalVue(); - vm = createComponent(localVue, translate(mockServerlessFunctionsDiffEnv).test, 'test'); - }); - - afterEach(() => vm.$destroy()); - - it('has the correct envId', () => { - expect(vm.envId).toEqual('env-test'); - }); - - it('is open by default', () => { - expect(vm.isOpenClass).toEqual({ 'is-open': true }); - }); - - it('generates correct output', () => { - expect(vm.$el.id).toEqual('env-test'); - expect(vm.$el.classList.contains('is-open')).toBe(true); - expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('test'); - }); - }); -}); diff --git a/spec/javascripts/serverless/components/function_details_spec.js b/spec/javascripts/serverless/components/function_details_spec.js deleted file mode 100644 index a29d4a296ef..00000000000 --- a/spec/javascripts/serverless/components/function_details_spec.js +++ /dev/null @@ -1,113 +0,0 @@ -import Vuex from 'vuex'; - -import functionDetailsComponent from '~/serverless/components/function_details.vue'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; -import { createStore } from '~/serverless/store'; - -describe('functionDetailsComponent', () => { - let localVue; - let component; - let store; - - beforeEach(() => { - localVue = createLocalVue(); - localVue.use(Vuex); - - store = createStore(); - }); - - afterEach(() => { - component.vm.$destroy(); - }); - - describe('Verify base functionality', () => { - const serviceStub = { - name: 'test', - description: 'a description', - environment: '*', - url: 'http://service.com/test', - namespace: 'test-ns', - podcount: 0, - metricsUrl: '/metrics', - }; - - it('has a name, description, URL, and no pods loaded', () => { - component = shallowMount(functionDetailsComponent, { - localVue, - store, - propsData: { - func: serviceStub, - hasPrometheus: false, - clustersPath: '/clusters', - helpPath: '/help', - }, - }); - - expect( - component.vm.$el.querySelector('.serverless-function-name').innerHTML.trim(), - ).toContain('test'); - - expect( - component.vm.$el.querySelector('.serverless-function-description').innerHTML.trim(), - ).toContain('a description'); - - expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain( - 'No pods loaded at this time.', - ); - }); - - it('has a pods loaded', () => { - serviceStub.podcount = 1; - - component = shallowMount(functionDetailsComponent, { - localVue, - store, - propsData: { - func: serviceStub, - hasPrometheus: false, - clustersPath: '/clusters', - helpPath: '/help', - }, - }); - - expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('1 pod in use'); - }); - - it('has multiple pods loaded', () => { - serviceStub.podcount = 3; - - component = shallowMount(functionDetailsComponent, { - localVue, - store, - propsData: { - func: serviceStub, - hasPrometheus: false, - clustersPath: '/clusters', - helpPath: '/help', - }, - }); - - expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('3 pods in use'); - }); - - it('can support a missing description', () => { - serviceStub.description = null; - - component = shallowMount(functionDetailsComponent, { - localVue, - store, - propsData: { - func: serviceStub, - hasPrometheus: false, - clustersPath: '/clusters', - helpPath: '/help', - }, - }); - - expect( - component.vm.$el.querySelector('.serverless-function-description').querySelector('div') - .innerHTML.length, - ).toEqual(0); - }); - }); -}); diff --git a/spec/javascripts/serverless/components/function_row_spec.js b/spec/javascripts/serverless/components/function_row_spec.js deleted file mode 100644 index 3987e1753bd..00000000000 --- a/spec/javascripts/serverless/components/function_row_spec.js +++ /dev/null @@ -1,26 +0,0 @@ -import functionRowComponent from '~/serverless/components/function_row.vue'; -import { shallowMount } from '@vue/test-utils'; - -import { mockServerlessFunction } from '../mock_data'; - -const createComponent = func => shallowMount(functionRowComponent, { propsData: { func } }).vm; - -describe('functionRowComponent', () => { - it('Parses the function details correctly', () => { - const vm = createComponent(mockServerlessFunction); - - expect(vm.$el.querySelector('b').innerHTML).toEqual(mockServerlessFunction.name); - expect(vm.$el.querySelector('span').innerHTML).toEqual(mockServerlessFunction.image); - expect(vm.$el.querySelector('timeago-stub').getAttribute('time')).not.toBe(null); - - vm.$destroy(); - }); - - it('handles clicks correctly', () => { - const vm = createComponent(mockServerlessFunction); - - expect(vm.checkClass(vm.$el.querySelector('p'))).toBe(true); // check somewhere inside the row - - vm.$destroy(); - }); -}); diff --git a/spec/javascripts/serverless/components/functions_spec.js b/spec/javascripts/serverless/components/functions_spec.js deleted file mode 100644 index c32978ea58a..00000000000 --- a/spec/javascripts/serverless/components/functions_spec.js +++ /dev/null @@ -1,101 +0,0 @@ -import Vuex from 'vuex'; - -import functionsComponent from '~/serverless/components/functions.vue'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; -import { createStore } from '~/serverless/store'; -import { mockServerlessFunctions } from '../mock_data'; - -describe('functionsComponent', () => { - let component; - let store; - let localVue; - - beforeEach(() => { - localVue = createLocalVue(); - localVue.use(Vuex); - - store = createStore(); - }); - - afterEach(() => { - component.vm.$destroy(); - }); - - it('should render empty state when Knative is not installed', () => { - component = shallowMount(functionsComponent, { - localVue, - store, - propsData: { - installed: false, - clustersPath: '', - helpPath: '', - statusPath: '', - }, - sync: false, - }); - - expect(component.vm.$el.querySelector('emptystate-stub')).not.toBe(null); - }); - - it('should render a loading component', () => { - store.dispatch('requestFunctionsLoading'); - component = shallowMount(functionsComponent, { - localVue, - store, - propsData: { - installed: true, - clustersPath: '', - helpPath: '', - statusPath: '', - }, - sync: false, - }); - - expect(component.vm.$el.querySelector('glloadingicon-stub')).not.toBe(null); - }); - - it('should render empty state when there is no function data', () => { - store.dispatch('receiveFunctionsNoDataSuccess'); - component = shallowMount(functionsComponent, { - localVue, - store, - propsData: { - installed: true, - clustersPath: '', - helpPath: '', - statusPath: '', - }, - sync: false, - }); - - expect( - component.vm.$el - .querySelector('.empty-state, .js-empty-state') - .classList.contains('js-empty-state'), - ).toBe(true); - - expect(component.vm.$el.querySelector('.state-title, .text-center').innerHTML.trim()).toEqual( - 'No functions available', - ); - }); - - it('should render the functions list', () => { - component = shallowMount(functionsComponent, { - localVue, - store, - propsData: { - installed: true, - clustersPath: '', - helpPath: '', - statusPath: '', - }, - sync: false, - }); - - component.vm.$store.dispatch('receiveFunctionsSuccess', mockServerlessFunctions); - - return component.vm.$nextTick().then(() => { - expect(component.vm.$el.querySelector('environmentrow-stub')).not.toBe(null); - }); - }); -}); diff --git a/spec/javascripts/serverless/components/missing_prometheus_spec.js b/spec/javascripts/serverless/components/missing_prometheus_spec.js deleted file mode 100644 index 77aca03772b..00000000000 --- a/spec/javascripts/serverless/components/missing_prometheus_spec.js +++ /dev/null @@ -1,37 +0,0 @@ -import missingPrometheusComponent from '~/serverless/components/missing_prometheus.vue'; -import { shallowMount } from '@vue/test-utils'; - -const createComponent = missingData => - shallowMount(missingPrometheusComponent, { - propsData: { - clustersPath: '/clusters', - helpPath: '/help', - missingData, - }, - }).vm; - -describe('missingPrometheusComponent', () => { - let vm; - - afterEach(() => { - vm.$destroy(); - }); - - it('should render missing prometheus message', () => { - vm = createComponent(false); - - expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( - 'Function invocation metrics require Prometheus to be installed first.', - ); - - expect(vm.$el.querySelector('glbutton-stub').getAttribute('variant')).toEqual('success'); - }); - - it('should render no prometheus data message', () => { - vm = createComponent(true); - - expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( - 'Invocation metrics loading or not available at this time.', - ); - }); -}); diff --git a/spec/javascripts/serverless/components/pod_box_spec.js b/spec/javascripts/serverless/components/pod_box_spec.js deleted file mode 100644 index 69ac1a2bb5f..00000000000 --- a/spec/javascripts/serverless/components/pod_box_spec.js +++ /dev/null @@ -1,22 +0,0 @@ -import podBoxComponent from '~/serverless/components/pod_box.vue'; -import { shallowMount } from '@vue/test-utils'; - -const createComponent = count => - shallowMount(podBoxComponent, { - propsData: { - count, - }, - }).vm; - -describe('podBoxComponent', () => { - it('should render three boxes', () => { - const count = 3; - const vm = createComponent(count); - const rects = vm.$el.querySelectorAll('rect'); - - expect(rects.length).toEqual(3); - expect(parseInt(rects[2].getAttribute('x'), 10)).toEqual(40); - - vm.$destroy(); - }); -}); diff --git a/spec/javascripts/serverless/components/url_spec.js b/spec/javascripts/serverless/components/url_spec.js deleted file mode 100644 index 08c3e4146b1..00000000000 --- a/spec/javascripts/serverless/components/url_spec.js +++ /dev/null @@ -1,24 +0,0 @@ -import Vue from 'vue'; -import urlComponent from '~/serverless/components/url.vue'; -import { shallowMount } from '@vue/test-utils'; - -const createComponent = uri => - shallowMount(Vue.extend(urlComponent), { - propsData: { - uri, - }, - }).vm; - -describe('urlComponent', () => { - it('should render correctly', () => { - const uri = 'http://testfunc.apps.example.com'; - const vm = createComponent(uri); - - expect(vm.$el.classList.contains('clipboard-group')).toBe(true); - expect(vm.$el.querySelector('clipboardbutton-stub').getAttribute('text')).toEqual(uri); - - expect(vm.$el.querySelector('.url-text-field').innerHTML).toEqual(uri); - - vm.$destroy(); - }); -}); diff --git a/spec/javascripts/serverless/mock_data.js b/spec/javascripts/serverless/mock_data.js deleted file mode 100644 index a2c18616324..00000000000 --- a/spec/javascripts/serverless/mock_data.js +++ /dev/null @@ -1,136 +0,0 @@ -export const mockServerlessFunctions = [ - { - name: 'testfunc1', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', - podcount: null, - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc1.tm-example.apps.example.com', - description: 'A test service', - image: 'knative-test-container-buildtemplate', - }, - { - name: 'testfunc2', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc2', - podcount: null, - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc2.tm-example.apps.example.com', - description: 'A second test service\nThis one with additional descriptions', - image: 'knative-test-echo-buildtemplate', - }, -]; - -export const mockServerlessFunctionsDiffEnv = [ - { - name: 'testfunc1', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', - podcount: null, - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc1.tm-example.apps.example.com', - description: 'A test service', - image: 'knative-test-container-buildtemplate', - }, - { - name: 'testfunc2', - namespace: 'tm-example', - environment_scope: 'test', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc2', - podcount: null, - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc2.tm-example.apps.example.com', - description: 'A second test service\nThis one with additional descriptions', - image: 'knative-test-echo-buildtemplate', - }, -]; - -export const mockServerlessFunction = { - name: 'testfunc1', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', - podcount: '3', - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc1.tm-example.apps.example.com', - description: 'A test service', - image: 'knative-test-container-buildtemplate', -}; - -export const mockMultilineServerlessFunction = { - name: 'testfunc1', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', - podcount: '3', - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc1.tm-example.apps.example.com', - description: 'testfunc1\nA test service line\\nWith additional services', - image: 'knative-test-container-buildtemplate', -}; - -export const mockMetrics = { - success: true, - last_update: '2019-02-28T19:11:38.926Z', - metrics: { - id: 22, - title: 'Knative function invocations', - required_metrics: ['container_memory_usage_bytes', 'container_cpu_usage_seconds_total'], - weight: 0, - y_label: 'Invocations', - queries: [ - { - query_range: - 'floor(sum(rate(istio_revision_request_count{destination_configuration="%{function_name}", destination_namespace="%{kube_namespace}"}[1m])*30))', - unit: 'requests', - label: 'invocations / minute', - result: [ - { - metric: {}, - values: [[1551352298.756, '0'], [1551352358.756, '0']], - }, - ], - }, - ], - }, -}; - -export const mockNormalizedMetrics = { - id: 22, - title: 'Knative function invocations', - required_metrics: ['container_memory_usage_bytes', 'container_cpu_usage_seconds_total'], - weight: 0, - y_label: 'Invocations', - queries: [ - { - query_range: - 'floor(sum(rate(istio_revision_request_count{destination_configuration="%{function_name}", destination_namespace="%{kube_namespace}"}[1m])*30))', - unit: 'requests', - label: 'invocations / minute', - result: [ - { - metric: {}, - values: [ - { - time: '2019-02-28T11:11:38.756Z', - value: 0, - }, - { - time: '2019-02-28T11:12:38.756Z', - value: 0, - }, - ], - }, - ], - }, - ], -}; diff --git a/spec/javascripts/serverless/store/actions_spec.js b/spec/javascripts/serverless/store/actions_spec.js deleted file mode 100644 index 602798573e9..00000000000 --- a/spec/javascripts/serverless/store/actions_spec.js +++ /dev/null @@ -1,88 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import statusCodes from '~/lib/utils/http_status'; -import { fetchFunctions, fetchMetrics } from '~/serverless/store/actions'; -import { mockServerlessFunctions, mockMetrics } from '../mock_data'; -import axios from '~/lib/utils/axios_utils'; -import testAction from '../../helpers/vuex_action_helper'; -import { adjustMetricQuery } from '../utils'; - -describe('ServerlessActions', () => { - describe('fetchFunctions', () => { - it('should successfully fetch functions', done => { - const endpoint = '/functions'; - const mock = new MockAdapter(axios); - mock.onGet(endpoint).reply(statusCodes.OK, JSON.stringify(mockServerlessFunctions)); - - testAction( - fetchFunctions, - { functionsPath: endpoint }, - {}, - [], - [ - { type: 'requestFunctionsLoading' }, - { type: 'receiveFunctionsSuccess', payload: mockServerlessFunctions }, - ], - () => { - mock.restore(); - done(); - }, - ); - }); - - it('should successfully retry', done => { - const endpoint = '/functions'; - const mock = new MockAdapter(axios); - mock.onGet(endpoint).reply(statusCodes.NO_CONTENT); - - testAction( - fetchFunctions, - { functionsPath: endpoint }, - {}, - [], - [{ type: 'requestFunctionsLoading' }], - () => { - mock.restore(); - done(); - }, - ); - }); - }); - - describe('fetchMetrics', () => { - it('should return no prometheus', done => { - const endpoint = '/metrics'; - const mock = new MockAdapter(axios); - mock.onGet(endpoint).reply(statusCodes.NO_CONTENT); - - testAction( - fetchMetrics, - { metricsPath: endpoint, hasPrometheus: false }, - {}, - [], - [{ type: 'receiveMetricsNoPrometheus' }], - () => { - mock.restore(); - done(); - }, - ); - }); - - it('should successfully fetch metrics', done => { - const endpoint = '/metrics'; - const mock = new MockAdapter(axios); - mock.onGet(endpoint).reply(statusCodes.OK, JSON.stringify(mockMetrics)); - - testAction( - fetchMetrics, - { metricsPath: endpoint, hasPrometheus: true }, - {}, - [], - [{ type: 'receiveMetricsSuccess', payload: adjustMetricQuery(mockMetrics) }], - () => { - mock.restore(); - done(); - }, - ); - }); - }); -}); diff --git a/spec/javascripts/serverless/store/getters_spec.js b/spec/javascripts/serverless/store/getters_spec.js deleted file mode 100644 index fb549c8f153..00000000000 --- a/spec/javascripts/serverless/store/getters_spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import serverlessState from '~/serverless/store/state'; -import * as getters from '~/serverless/store/getters'; -import { mockServerlessFunctions } from '../mock_data'; - -describe('Serverless Store Getters', () => { - let state; - - beforeEach(() => { - state = serverlessState; - }); - - describe('hasPrometheusMissingData', () => { - it('should return false if Prometheus is not installed', () => { - state.hasPrometheus = false; - - expect(getters.hasPrometheusMissingData(state)).toEqual(false); - }); - - it('should return false if Prometheus is installed and there is data', () => { - state.hasPrometheusData = true; - - expect(getters.hasPrometheusMissingData(state)).toEqual(false); - }); - - it('should return true if Prometheus is installed and there is no data', () => { - state.hasPrometheus = true; - state.hasPrometheusData = false; - - expect(getters.hasPrometheusMissingData(state)).toEqual(true); - }); - }); - - describe('getFunctions', () => { - it('should translate the raw function array to group the functions per environment scope', () => { - state.functions = mockServerlessFunctions; - - const funcs = getters.getFunctions(state); - - expect(Object.keys(funcs)).toContain('*'); - expect(funcs['*'].length).toEqual(2); - }); - }); -}); diff --git a/spec/javascripts/serverless/store/mutations_spec.js b/spec/javascripts/serverless/store/mutations_spec.js deleted file mode 100644 index ca3053e5c38..00000000000 --- a/spec/javascripts/serverless/store/mutations_spec.js +++ /dev/null @@ -1,86 +0,0 @@ -import mutations from '~/serverless/store/mutations'; -import * as types from '~/serverless/store/mutation_types'; -import { mockServerlessFunctions, mockMetrics } from '../mock_data'; - -describe('ServerlessMutations', () => { - describe('Functions List Mutations', () => { - it('should ensure loading is true', () => { - const state = {}; - - mutations[types.REQUEST_FUNCTIONS_LOADING](state); - - expect(state.isLoading).toEqual(true); - }); - - it('should set proper state once functions are loaded', () => { - const state = {}; - - mutations[types.RECEIVE_FUNCTIONS_SUCCESS](state, mockServerlessFunctions); - - expect(state.isLoading).toEqual(false); - expect(state.hasFunctionData).toEqual(true); - expect(state.functions).toEqual(mockServerlessFunctions); - }); - - it('should ensure loading has stopped and hasFunctionData is false when there are no functions available', () => { - const state = {}; - - mutations[types.RECEIVE_FUNCTIONS_NODATA_SUCCESS](state); - - expect(state.isLoading).toEqual(false); - expect(state.hasFunctionData).toEqual(false); - expect(state.functions).toBe(undefined); - }); - - it('should ensure loading has stopped, and an error is raised', () => { - const state = {}; - - mutations[types.RECEIVE_FUNCTIONS_ERROR](state, 'sample error'); - - expect(state.isLoading).toEqual(false); - expect(state.hasFunctionData).toEqual(false); - expect(state.functions).toBe(undefined); - expect(state.error).not.toBe(undefined); - }); - }); - - describe('Function Details Metrics Mutations', () => { - it('should ensure isLoading and hasPrometheus data flags indicate data is loaded', () => { - const state = {}; - - mutations[types.RECEIVE_METRICS_SUCCESS](state, mockMetrics); - - expect(state.isLoading).toEqual(false); - expect(state.hasPrometheusData).toEqual(true); - expect(state.graphData).toEqual(mockMetrics); - }); - - it('should ensure isLoading and hasPrometheus data flags are cleared indicating no functions available', () => { - const state = {}; - - mutations[types.RECEIVE_METRICS_NODATA_SUCCESS](state); - - expect(state.isLoading).toEqual(false); - expect(state.hasPrometheusData).toEqual(false); - expect(state.graphData).toBe(undefined); - }); - - it('should properly indicate an error', () => { - const state = {}; - - mutations[types.RECEIVE_METRICS_ERROR](state, 'sample error'); - - expect(state.hasPrometheusData).toEqual(false); - expect(state.error).not.toBe(undefined); - }); - - it('should properly indicate when prometheus is installed', () => { - const state = {}; - - mutations[types.RECEIVE_METRICS_NO_PROMETHEUS](state); - - expect(state.hasPrometheus).toEqual(false); - expect(state.hasPrometheusData).toEqual(false); - }); - }); -}); diff --git a/spec/javascripts/serverless/utils.js b/spec/javascripts/serverless/utils.js deleted file mode 100644 index 5ce2e37d493..00000000000 --- a/spec/javascripts/serverless/utils.js +++ /dev/null @@ -1,20 +0,0 @@ -export const adjustMetricQuery = data => { - const updatedMetric = data.metrics; - - const queries = data.metrics.queries.map(query => ({ - ...query, - result: query.result.map(result => ({ - ...result, - values: result.values.map(([timestamp, value]) => ({ - time: new Date(timestamp * 1000).toISOString(), - value: Number(value), - })), - })), - })); - - updatedMetric.queries = queries; - return updatedMetric; -}; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; -- cgit v1.2.1 From 083e896d5e8651c082349ed5f82597fa95d3265d Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 7 Apr 2019 04:00:28 +0100 Subject: Fix unmocked requests in serverless tests --- spec/frontend/serverless/components/area_spec.js | 2 +- spec/frontend/serverless/components/function_row_spec.js | 3 ++- spec/frontend/serverless/components/functions_spec.js | 15 ++++++++++----- spec/frontend/serverless/store/actions_spec.js | 4 +++- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/spec/frontend/serverless/components/area_spec.js b/spec/frontend/serverless/components/area_spec.js index 2be6ac3d268..8b6f664ae25 100644 --- a/spec/frontend/serverless/components/area_spec.js +++ b/spec/frontend/serverless/components/area_spec.js @@ -67,7 +67,7 @@ describe('Area component', () => { const mockWidth = 233; beforeEach(() => { - spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({ + jest.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(() => ({ width: mockWidth, })); areaChart.vm.onResize(); diff --git a/spec/frontend/serverless/components/function_row_spec.js b/spec/frontend/serverless/components/function_row_spec.js index 3987e1753bd..414fdc5cd82 100644 --- a/spec/frontend/serverless/components/function_row_spec.js +++ b/spec/frontend/serverless/components/function_row_spec.js @@ -3,7 +3,8 @@ import { shallowMount } from '@vue/test-utils'; import { mockServerlessFunction } from '../mock_data'; -const createComponent = func => shallowMount(functionRowComponent, { propsData: { func } }).vm; +const createComponent = func => + shallowMount(functionRowComponent, { propsData: { func }, sync: false }).vm; describe('functionRowComponent', () => { it('Parses the function details correctly', () => { diff --git a/spec/frontend/serverless/components/functions_spec.js b/spec/frontend/serverless/components/functions_spec.js index c32978ea58a..5533de1a70a 100644 --- a/spec/frontend/serverless/components/functions_spec.js +++ b/spec/frontend/serverless/components/functions_spec.js @@ -1,5 +1,6 @@ import Vuex from 'vuex'; - +import AxiosMockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import functionsComponent from '~/serverless/components/functions.vue'; import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createStore } from '~/serverless/store'; @@ -79,15 +80,19 @@ describe('functionsComponent', () => { ); }); - it('should render the functions list', () => { + fit('should render the functions list', () => { + const statusPath = 'statusPath'; + const axiosMock = new AxiosMockAdapter(axios); + axiosMock.onGet(statusPath).reply(200); + component = shallowMount(functionsComponent, { localVue, store, propsData: { installed: true, - clustersPath: '', - helpPath: '', - statusPath: '', + clustersPath: 'clustersPath', + helpPath: 'helpPath', + statusPath, }, sync: false, }); diff --git a/spec/frontend/serverless/store/actions_spec.js b/spec/frontend/serverless/store/actions_spec.js index 602798573e9..aac57c75a4f 100644 --- a/spec/frontend/serverless/store/actions_spec.js +++ b/spec/frontend/serverless/store/actions_spec.js @@ -32,7 +32,9 @@ describe('ServerlessActions', () => { it('should successfully retry', done => { const endpoint = '/functions'; const mock = new MockAdapter(axios); - mock.onGet(endpoint).reply(statusCodes.NO_CONTENT); + mock + .onGet(endpoint) + .reply(() => new Promise(resolve => setTimeout(() => resolve(200), Infinity))); testAction( fetchFunctions, -- cgit v1.2.1 From 2daba22b12ab1a94bf6643ab201ff6eeac2fe05f Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 7 Apr 2019 04:11:14 +0100 Subject: Disable sync mode for serverless tests Sync mode by default will soon be removed. --- spec/frontend/serverless/components/area_spec.js | 1 + spec/frontend/serverless/components/environment_row_spec.js | 2 +- spec/frontend/serverless/components/function_details_spec.js | 4 ++++ spec/frontend/serverless/components/missing_prometheus_spec.js | 1 + spec/frontend/serverless/components/pod_box_spec.js | 1 + spec/frontend/serverless/components/url_spec.js | 1 + 6 files changed, 9 insertions(+), 1 deletion(-) diff --git a/spec/frontend/serverless/components/area_spec.js b/spec/frontend/serverless/components/area_spec.js index 8b6f664ae25..62005e1981a 100644 --- a/spec/frontend/serverless/components/area_spec.js +++ b/spec/frontend/serverless/components/area_spec.js @@ -16,6 +16,7 @@ describe('Area component', () => { slots: { default: mockWidgets, }, + sync: false, }); }); diff --git a/spec/frontend/serverless/components/environment_row_spec.js b/spec/frontend/serverless/components/environment_row_spec.js index 932d712dbec..161a637dd75 100644 --- a/spec/frontend/serverless/components/environment_row_spec.js +++ b/spec/frontend/serverless/components/environment_row_spec.js @@ -5,7 +5,7 @@ import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock import { translate } from '~/serverless/utils'; const createComponent = (localVue, env, envName) => - shallowMount(environmentRowComponent, { localVue, propsData: { env, envName } }).vm; + shallowMount(environmentRowComponent, { localVue, propsData: { env, envName }, sync: false }).vm; describe('environment row component', () => { describe('default global cluster case', () => { diff --git a/spec/frontend/serverless/components/function_details_spec.js b/spec/frontend/serverless/components/function_details_spec.js index a29d4a296ef..31348ff1194 100644 --- a/spec/frontend/serverless/components/function_details_spec.js +++ b/spec/frontend/serverless/components/function_details_spec.js @@ -41,6 +41,7 @@ describe('functionDetailsComponent', () => { clustersPath: '/clusters', helpPath: '/help', }, + sync: false, }); expect( @@ -68,6 +69,7 @@ describe('functionDetailsComponent', () => { clustersPath: '/clusters', helpPath: '/help', }, + sync: false, }); expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('1 pod in use'); @@ -85,6 +87,7 @@ describe('functionDetailsComponent', () => { clustersPath: '/clusters', helpPath: '/help', }, + sync: false, }); expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('3 pods in use'); @@ -102,6 +105,7 @@ describe('functionDetailsComponent', () => { clustersPath: '/clusters', helpPath: '/help', }, + sync: false, }); expect( diff --git a/spec/frontend/serverless/components/missing_prometheus_spec.js b/spec/frontend/serverless/components/missing_prometheus_spec.js index 77aca03772b..d0df6125290 100644 --- a/spec/frontend/serverless/components/missing_prometheus_spec.js +++ b/spec/frontend/serverless/components/missing_prometheus_spec.js @@ -8,6 +8,7 @@ const createComponent = missingData => helpPath: '/help', missingData, }, + sync: false, }).vm; describe('missingPrometheusComponent', () => { diff --git a/spec/frontend/serverless/components/pod_box_spec.js b/spec/frontend/serverless/components/pod_box_spec.js index 69ac1a2bb5f..d82825d8f62 100644 --- a/spec/frontend/serverless/components/pod_box_spec.js +++ b/spec/frontend/serverless/components/pod_box_spec.js @@ -6,6 +6,7 @@ const createComponent = count => propsData: { count, }, + sync: false, }).vm; describe('podBoxComponent', () => { diff --git a/spec/frontend/serverless/components/url_spec.js b/spec/frontend/serverless/components/url_spec.js index 08c3e4146b1..d05a9bba103 100644 --- a/spec/frontend/serverless/components/url_spec.js +++ b/spec/frontend/serverless/components/url_spec.js @@ -7,6 +7,7 @@ const createComponent = uri => propsData: { uri, }, + sync: false, }).vm; describe('urlComponent', () => { -- cgit v1.2.1