diff --git a/ee/app/assets/javascripts/threat_monitoring/components/alerts/alerts_list.vue b/ee/app/assets/javascripts/threat_monitoring/components/alerts/alerts_list.vue index 05776022755d85c28a6e7b7aa02484763ab57ae7..a499f7dc3ab7a01f21daccaa99a4870a2693739b 100644 --- a/ee/app/assets/javascripts/threat_monitoring/components/alerts/alerts_list.vue +++ b/ee/app/assets/javascripts/threat_monitoring/components/alerts/alerts_list.vue @@ -56,7 +56,7 @@ export default { }, update: ({ project }) => project?.alertManagementAlerts.nodes || [], result({ data }) { - this.pageInfo = data?.project?.alertManagementAlerts?.pageInfo; + this.pageInfo = data?.project?.alertManagementAlerts?.pageInfo || {}; }, error() { this.errored = true; diff --git a/ee/changelogs/unreleased/294185-update-alert-list-tests.yml b/ee/changelogs/unreleased/294185-update-alert-list-tests.yml new file mode 100644 index 0000000000000000000000000000000000000000..e8824de7d7433bfe8b50d2a4515e0a057c14f0c9 --- /dev/null +++ b/ee/changelogs/unreleased/294185-update-alert-list-tests.yml @@ -0,0 +1,5 @@ +--- +title: Update alerts list to fail gracefully on GraphQL failure +merge_request: 55817 +author: +type: fixed diff --git a/ee/spec/frontend/threat_monitoring/components/alerts/alert_status_spec.js b/ee/spec/frontend/threat_monitoring/components/alerts/alert_status_spec.js index 858ad6ac00c53a8d80172d7f6d811f56f157cd47..ea060ced8595a6b76117f0eab1f8c23f8ad1df42 100644 --- a/ee/spec/frontend/threat_monitoring/components/alerts/alert_status_spec.js +++ b/ee/spec/frontend/threat_monitoring/components/alerts/alert_status_spec.js @@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils'; import AlertStatus from 'ee/threat_monitoring/components/alerts/alert_status.vue'; import waitForPromises from 'helpers/wait_for_promises'; import updateAlertStatusMutation from '~/graphql_shared/mutations/alert_status_update.mutation.graphql'; -import { mockAlerts } from '../../mock_data'; +import { mockAlerts } from '../../mocks/mock_data'; const mockAlert = mockAlerts[0]; diff --git a/ee/spec/frontend/threat_monitoring/components/alerts/alerts_list_spec.js b/ee/spec/frontend/threat_monitoring/components/alerts/alerts_list_spec.js index 4cce5ddfd1028d0fae3c00a8b17daa38f71d0fb3..c6ccbbf5ba63b0ecee246be00b02b3630c681049 100644 --- a/ee/spec/frontend/threat_monitoring/components/alerts/alerts_list_spec.js +++ b/ee/spec/frontend/threat_monitoring/components/alerts/alerts_list_spec.js @@ -1,25 +1,33 @@ import { GlIntersectionObserver, GlSkeletonLoading } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; +import { createLocalVue, mount } from '@vue/test-utils'; +import VueApollo from 'vue-apollo'; import AlertFilters from 'ee/threat_monitoring/components/alerts/alert_filters.vue'; import AlertStatus from 'ee/threat_monitoring/components/alerts/alert_status.vue'; import AlertsList from 'ee/threat_monitoring/components/alerts/alerts_list.vue'; import { DEFAULT_FILTERS } from 'ee/threat_monitoring/components/alerts/constants'; +import createMockApollo from 'helpers/mock_apollo_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import { mockAlerts } from '../../mock_data'; +import getAlertsQuery from '~/graphql_shared/queries/get_alerts.query.graphql'; +import { defaultQuerySpy, emptyQuerySpy, loadingQuerySpy } from '../../mocks/mock_apollo'; +import { mockAlerts, mockPageInfo } from '../../mocks/mock_data'; -const alerts = mockAlerts; - -const pageInfo = { - endCursor: 'eyJpZCI6IjIwIiwic3RhcnRlZF9hdCI6IjIwMjAtMTItMDMgMjM6MTI6NDkuODM3Mjc1MDAwIFVUQyJ9', - hasNextPage: true, - hasPreviousPage: false, - startCursor: 'eyJpZCI6IjM5Iiwic3RhcnRlZF9hdCI6IjIwMjAtMTItMDQgMTg6MDE6MDcuNzY1ODgyMDAwIFVUQyJ9', -}; +let localVue; describe('AlertsList component', () => { let wrapper; const refetchSpy = jest.fn(); - const apolloMock = { + const DEFAULT_PROJECT_PATH = '#'; + const DEFAULT_SORT = 'STARTED_AT_DESC'; + const PAGE_SIZE = 20; + const defaultProps = { filters: DEFAULT_FILTERS }; + let querySpy; + + const createMockApolloProvider = (query) => { + localVue.use(VueApollo); + return createMockApollo([[getAlertsQuery, query]]); + }; + + const shallowApolloMock = { queries: { alerts: { fetchMore: jest.fn().mockResolvedValue(), @@ -28,7 +36,6 @@ describe('AlertsList component', () => { }, }, }; - const defaultProps = { filters: DEFAULT_FILTERS }; const findAlertFilters = () => wrapper.findComponent(AlertFilters); const findUnconfiguredAlert = () => wrapper.findByTestId('threat-alerts-unconfigured'); @@ -43,16 +50,31 @@ describe('AlertsList component', () => { const findGlIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver); const findGlSkeletonLoading = () => wrapper.findComponent(GlSkeletonLoading); - const createWrapper = ({ $apollo = apolloMock, data = {}, stubs = {} } = {}) => { - wrapper = extendedWrapper( - mount(AlertsList, { + const createWrapper = ({ $apollo, apolloSpy = defaultQuerySpy, data, stubs = {} } = {}) => { + let apolloOptions; + if ($apollo) { + apolloOptions = { mocks: { $apollo, }, + data, + }; + } else { + localVue = createLocalVue(); + querySpy = apolloSpy; + const mockApollo = createMockApolloProvider(querySpy); + apolloOptions = { + localVue, + apolloProvider: mockApollo, + }; + } + + wrapper = extendedWrapper( + mount(AlertsList, { propsData: defaultProps, provide: { documentationPath: '#', - projectPath: '#', + projectPath: DEFAULT_PROJECT_PATH, }, stubs: { AlertStatus: true, @@ -62,9 +84,7 @@ describe('AlertsList component', () => { GlIntersectionObserver: true, ...stubs, }, - data() { - return data; - }, + ...apolloOptions, }), ); }; @@ -76,7 +96,7 @@ describe('AlertsList component', () => { describe('default state', () => { beforeEach(() => { - createWrapper({ data: { alerts, pageInfo } }); + createWrapper(); }); it('shows threat monitoring alert filters', () => { @@ -84,7 +104,15 @@ describe('AlertsList component', () => { }); it('does have the default filters initially', () => { - expect(wrapper.vm.filters).toEqual(DEFAULT_FILTERS); + expect(querySpy).toHaveBeenCalledTimes(1); + expect(querySpy).toHaveBeenCalledWith( + expect.objectContaining({ + firstPageSize: PAGE_SIZE, + projectPath: DEFAULT_PROJECT_PATH, + sort: DEFAULT_SORT, + ...DEFAULT_FILTERS, + }), + ); }); it('does update its filters on filter event emitted', async () => { @@ -146,7 +174,7 @@ describe('AlertsList component', () => { describe('empty state', () => { beforeEach(() => { - createWrapper({ data: { alerts: [] } }); + createWrapper({ apolloSpy: emptyQuerySpy }); }); it('does show the empty state', () => { @@ -160,10 +188,7 @@ describe('AlertsList component', () => { describe('loading state', () => { beforeEach(() => { - const apolloMockLoading = { - queries: { alerts: { loading: true } }, - }; - createWrapper({ $apollo: apolloMockLoading }); + createWrapper({ apolloSpy: loadingQuerySpy }); }); it('does show the loading state', () => { @@ -207,10 +232,11 @@ describe('AlertsList component', () => { describe('loading more alerts', () => { it('does request more data', async () => { createWrapper({ - data: { - alerts, - pageInfo, - }, + $apollo: shallowApolloMock, + data: () => ({ + alerts: mockAlerts, + pageInfo: mockPageInfo, + }), }); findGlIntersectionObserver().vm.$emit('appear'); await wrapper.vm.$nextTick(); @@ -220,9 +246,12 @@ describe('AlertsList component', () => { describe('changing alert status', () => { beforeEach(() => { - createWrapper(); - wrapper.setData({ - alerts, + createWrapper({ + $apollo: shallowApolloMock, + data: () => ({ + alerts: mockAlerts, + pageInfo: mockPageInfo, + }), }); }); diff --git a/ee/spec/frontend/threat_monitoring/components/environment_picker_spec.js b/ee/spec/frontend/threat_monitoring/components/environment_picker_spec.js index b8e025a3493c4792dc2d4717005fb59a23291fc4..825062392a14a8da82851c6e3841c3f9bcc97518 100644 --- a/ee/spec/frontend/threat_monitoring/components/environment_picker_spec.js +++ b/ee/spec/frontend/threat_monitoring/components/environment_picker_spec.js @@ -6,7 +6,7 @@ import { ALL_ENVIRONMENT_NAME, } from 'ee/threat_monitoring/constants'; import createStore from 'ee/threat_monitoring/store'; -import { mockEnvironmentsResponse } from '../mock_data'; +import { mockEnvironmentsResponse } from '../mocks/mock_data'; const mockEnvironments = mockEnvironmentsResponse.environments; const currentEnvironment = mockEnvironments[1]; diff --git a/ee/spec/frontend/threat_monitoring/components/network_policy_list_spec.js b/ee/spec/frontend/threat_monitoring/components/network_policy_list_spec.js index 3f12b79ba2c60f3c4bcc1b1ccf38c0e1ba92cd56..c45bbe1a24771bd5353185eef901082867947df6 100644 --- a/ee/spec/frontend/threat_monitoring/components/network_policy_list_spec.js +++ b/ee/spec/frontend/threat_monitoring/components/network_policy_list_spec.js @@ -5,7 +5,7 @@ import PolicyDrawer from 'ee/threat_monitoring/components/policy_editor/policy_d import { PREDEFINED_NETWORK_POLICIES } from 'ee/threat_monitoring/constants'; import createStore from 'ee/threat_monitoring/store'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; -import { mockPoliciesResponse } from '../mock_data'; +import { mockPoliciesResponse } from '../mocks/mock_data'; const mockData = mockPoliciesResponse.map((policy) => convertObjectPropsToCamelCase(policy)); diff --git a/ee/spec/frontend/threat_monitoring/components/statistics_history_spec.js b/ee/spec/frontend/threat_monitoring/components/statistics_history_spec.js index b49d23c15e2ccfa9da836f33ef5774b4d469a985..2dccccabc101045fa24a2847b99ef99d3f79fb02 100644 --- a/ee/spec/frontend/threat_monitoring/components/statistics_history_spec.js +++ b/ee/spec/frontend/threat_monitoring/components/statistics_history_spec.js @@ -2,7 +2,7 @@ import { GlAreaChart } from '@gitlab/ui/dist/charts'; import { createLocalVue, shallowMount } from '@vue/test-utils'; import { TOTAL_REQUESTS, ANOMALOUS_REQUESTS } from 'ee/threat_monitoring/components/constants'; import StatisticsHistory from 'ee/threat_monitoring/components/statistics_history.vue'; -import { mockNominalHistory, mockAnomalousHistory } from '../mock_data'; +import { mockNominalHistory, mockAnomalousHistory } from '../mocks/mock_data'; let resizeCallback = null; const MockResizeObserverDirective = { diff --git a/ee/spec/frontend/threat_monitoring/components/threat_monitoring_filters_spec.js b/ee/spec/frontend/threat_monitoring/components/threat_monitoring_filters_spec.js index 101eaee86c95a1cb615b6eca32371d20de01aff4..db0d7aede6a36b1ec63d213b47e69a62d99d8f26 100644 --- a/ee/spec/frontend/threat_monitoring/components/threat_monitoring_filters_spec.js +++ b/ee/spec/frontend/threat_monitoring/components/threat_monitoring_filters_spec.js @@ -4,7 +4,7 @@ import ThreatMonitoringFilters from 'ee/threat_monitoring/components/threat_moni import createStore from 'ee/threat_monitoring/store'; import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; import { timeRanges, defaultTimeRange } from '~/vue_shared/constants'; -import { mockEnvironmentsResponse } from '../mock_data'; +import { mockEnvironmentsResponse } from '../mocks/mock_data'; const mockEnvironments = mockEnvironmentsResponse.environments; diff --git a/ee/spec/frontend/threat_monitoring/components/threat_monitoring_section_spec.js b/ee/spec/frontend/threat_monitoring/components/threat_monitoring_section_spec.js index 5c3e31069ce2161a10ca6c7fe0796b7764eb2cae..3c831e2b9d0dd03d3669c66dbd0501bdd91e424e 100644 --- a/ee/spec/frontend/threat_monitoring/components/threat_monitoring_section_spec.js +++ b/ee/spec/frontend/threat_monitoring/components/threat_monitoring_section_spec.js @@ -5,7 +5,7 @@ import StatisticsSummary from 'ee/threat_monitoring/components/statistics_summar import ThreatMonitoringSection from 'ee/threat_monitoring/components/threat_monitoring_section.vue'; import createStore from 'ee/threat_monitoring/store'; -import { mockNominalHistory, mockAnomalousHistory } from '../mock_data'; +import { mockNominalHistory, mockAnomalousHistory } from '../mocks/mock_data'; describe('ThreatMonitoringSection component', () => { let store; diff --git a/ee/spec/frontend/threat_monitoring/mocks/mock_apollo.js b/ee/spec/frontend/threat_monitoring/mocks/mock_apollo.js new file mode 100644 index 0000000000000000000000000000000000000000..de11dba39fc4b923429913755fe764e06719aa8e --- /dev/null +++ b/ee/spec/frontend/threat_monitoring/mocks/mock_apollo.js @@ -0,0 +1,18 @@ +import { mockAlerts, mockPageInfo } from './mock_data'; + +export const defaultQuerySpy = jest.fn().mockResolvedValue({ + data: { project: { alertManagementAlerts: { nodes: mockAlerts, pageInfo: mockPageInfo } } }, +}); + +export const emptyQuerySpy = jest.fn().mockResolvedValue({ + data: { + project: { + alertManagementAlerts: { + nodes: [], + pageInfo: { endCursor: '', hasNextPage: false, hasPreviousPage: false, startCursor: '' }, + }, + }, + }, +}); + +export const loadingQuerySpy = jest.fn().mockReturnValue(new Promise(() => {})); diff --git a/ee/spec/frontend/threat_monitoring/mock_data.js b/ee/spec/frontend/threat_monitoring/mocks/mock_data.js similarity index 75% rename from ee/spec/frontend/threat_monitoring/mock_data.js rename to ee/spec/frontend/threat_monitoring/mocks/mock_data.js index d8ea6457aa8e4a1d27128924284c33252240cadf..fb855d165b66258d87f200df05891dfb78a84312 100644 --- a/ee/spec/frontend/threat_monitoring/mock_data.js +++ b/ee/spec/frontend/threat_monitoring/mocks/mock_data.js @@ -94,14 +94,22 @@ export const formattedMockNetworkPolicyStatisticsResponse = { export const mockAlerts = [ { iid: '01', + assignees: { nodes: [] }, eventCount: '1', + issueIid: null, + issue: { iid: '1', state: '', title: '' }, title: 'Issue 01', + severity: 'HIGH', status: 'TRIGGERED', startedAt: '2020-11-19T18:36:23Z', }, { iid: '02', eventCount: '2', + assignees: { nodes: [] }, + issueIid: null, + issue: { iid: '2', state: '', title: '' }, + severity: 'CRITICAL', title: 'Issue 02', status: 'ACKNOWLEDGED', startedAt: '2020-11-16T21:59:28Z', @@ -109,15 +117,30 @@ export const mockAlerts = [ { iid: '03', eventCount: '3', + assignees: { nodes: [] }, + issueIid: null, + issue: { iid: '3', state: '', title: '' }, + severity: 'MEDIUM', title: 'Issue 03', status: 'RESOLVED', startedAt: '2020-11-13T20:03:04Z', }, { iid: '04', + assignees: { nodes: [] }, + issueIid: null, + issue: { iid: '4', state: '', title: '' }, + severity: 'LOW', eventCount: '4', title: 'Issue 04', status: 'IGNORED', startedAt: '2020-10-29T13:37:55Z', }, ]; + +export const mockPageInfo = { + endCursor: 'eyJpZCI6IjIwIiwic3RhcnRlZF9hdCI6IjIwMjAtMTItMDMgMjM6MTI6NDkuODM3Mjc1MDAwIFVUQyJ9', + hasNextPage: true, + hasPreviousPage: false, + startCursor: 'eyJpZCI6IjM5Iiwic3RhcnRlZF9hdCI6IjIwMjAtMTItMDQgMTg6MDE6MDcuNzY1ODgyMDAwIFVUQyJ9', +}; diff --git a/ee/spec/frontend/threat_monitoring/store/modules/network_policies/actions_spec.js b/ee/spec/frontend/threat_monitoring/store/modules/network_policies/actions_spec.js index 97e7859225ad601eeae424fe1d00559b5a26531f..a4e6e4931a6a4eecfe431c9ca294b23eb7085215 100644 --- a/ee/spec/frontend/threat_monitoring/store/modules/network_policies/actions_spec.js +++ b/ee/spec/frontend/threat_monitoring/store/modules/network_policies/actions_spec.js @@ -9,7 +9,7 @@ import httpStatus from '~/lib/utils/http_status'; import { joinPaths } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; -import { mockPoliciesResponse } from '../../../mock_data'; +import { mockPoliciesResponse } from '../../../mocks/mock_data'; jest.mock('~/flash'); diff --git a/ee/spec/frontend/threat_monitoring/store/modules/threat_monitoring/actions_spec.js b/ee/spec/frontend/threat_monitoring/store/modules/threat_monitoring/actions_spec.js index fc348530e1fafaff2fb6628c2afc9859c1d9437d..29559d8df4c669935fc853d0d24e05ed96106696 100644 --- a/ee/spec/frontend/threat_monitoring/store/modules/threat_monitoring/actions_spec.js +++ b/ee/spec/frontend/threat_monitoring/store/modules/threat_monitoring/actions_spec.js @@ -8,7 +8,7 @@ import { deprecatedCreateFlash as createFlash } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import httpStatus from '~/lib/utils/http_status'; -import { mockEnvironmentsResponse } from '../../../mock_data'; +import { mockEnvironmentsResponse } from '../../../mocks/mock_data'; jest.mock('~/flash'); diff --git a/ee/spec/frontend/threat_monitoring/store/modules/threat_monitoring_statistics/actions_spec.js b/ee/spec/frontend/threat_monitoring/store/modules/threat_monitoring_statistics/actions_spec.js index ae59b01e589de801b34647b6e95c610e8b4389b7..5029afe25e2f371974f8b381b883adca73063b91 100644 --- a/ee/spec/frontend/threat_monitoring/store/modules/threat_monitoring_statistics/actions_spec.js +++ b/ee/spec/frontend/threat_monitoring/store/modules/threat_monitoring_statistics/actions_spec.js @@ -8,7 +8,7 @@ import { deprecatedCreateFlash as createFlash } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import httpStatus from '~/lib/utils/http_status'; -import { mockWafStatisticsResponse } from '../../../mock_data'; +import { mockWafStatisticsResponse } from '../../../mocks/mock_data'; jest.mock('~/flash'); diff --git a/ee/spec/frontend/threat_monitoring/store/modules/threat_monitoring_statistics/mutations_spec.js b/ee/spec/frontend/threat_monitoring/store/modules/threat_monitoring_statistics/mutations_spec.js index 4ef972bb2ae3aebb36b3042c09530da26a2045a5..63d47ca8903ee766ad8eefce15603645b2470e2f 100644 --- a/ee/spec/frontend/threat_monitoring/store/modules/threat_monitoring_statistics/mutations_spec.js +++ b/ee/spec/frontend/threat_monitoring/store/modules/threat_monitoring_statistics/mutations_spec.js @@ -1,6 +1,6 @@ import * as types from 'ee/threat_monitoring/store/modules/threat_monitoring_statistics/mutation_types'; import mutationsFactory from 'ee/threat_monitoring/store/modules/threat_monitoring_statistics/mutations'; -import { mockWafStatisticsResponse } from '../../../mock_data'; +import { mockWafStatisticsResponse } from '../../../mocks/mock_data'; describe('threatMonitoringStatistics mutations', () => { let state;