From 8f01abe12e0d8164ad9120fc482a3a85e8219526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Palancher?= Date: Mon, 13 Jan 2025 21:39:46 +0100 Subject: [PATCH] tests(front): cover ClustersView Add unit tests to cover ClustersView and ClustersListItem component. --- .../clusters/ClustersListItem.spec.ts | 105 ++++++++++++++++++ frontend/tests/views/ClustersView.spec.ts | 61 ++++++++++ 2 files changed, 166 insertions(+) create mode 100644 frontend/tests/components/clusters/ClustersListItem.spec.ts create mode 100644 frontend/tests/views/ClustersView.spec.ts diff --git a/frontend/tests/components/clusters/ClustersListItem.spec.ts b/frontend/tests/components/clusters/ClustersListItem.spec.ts new file mode 100644 index 00000000..66b22a7a --- /dev/null +++ b/frontend/tests/components/clusters/ClustersListItem.spec.ts @@ -0,0 +1,105 @@ +import { describe, test, expect, beforeEach, vi } from 'vitest' +import { flushPromises, shallowMount } from '@vue/test-utils' +import ClusterListItem from '@/components/clusters/ClustersListItem.vue' +import stats from '../../assets/stats.json' +import { init_plugins } from '../../lib/common' +import { useRuntimeStore } from '@/stores/runtime' +import LoadingSpinner from '@/components/LoadingSpinner.vue' +import { APIServerError, AuthenticationError } from '@/composables/HTTPErrors' +import cluster from 'cluster' +import LoadingSpinner from '@/components/LoadingSpinner.vue' + +const mockGatewayAPI = { + stats: vi.fn() +} + +vi.mock('@/composables/GatewayAPI', () => ({ + useGatewayAPI: () => mockGatewayAPI +})) + +let router + +describe('ClustersView.vue', () => { + beforeEach(() => { + router = init_plugins() + useRuntimeStore().availableClusters = [ + { + name: 'foo', + permissions: { roles: [], actions: [] }, + racksdb: true, + infrastructure: 'foo', + metrics: true + } + ] + mockGatewayAPI.stats.mockReset() + }) + test('cluster with permission', async () => { + useRuntimeStore().availableClusters[0].permissions.actions = ['view-stats', 'view-jobs'] + mockGatewayAPI.stats.mockReturnValueOnce(Promise.resolve(stats)) + const wrapper = shallowMount(ClusterListItem, { + props: { + cluster: useRuntimeStore().availableClusters[0] + } + }) + // Wait for result of clusters requests + await flushPromises() + // assert stats have been retrieved + expect(mockGatewayAPI.stats).toBeCalled() + // Check presence of Slurm version + expect(wrapper.get('span span').text()).toContain('Slurm ') + // Check presence of nodes/jobs stats + const statsElements = wrapper.findAll('p') + expect(statsElements[0].text()).toContain('nodes') + expect(statsElements[1].text()).toContain('jobs') + // Check cluster status is available + expect(wrapper.get('div div p').text()).toBe('Available') + }) + test('cluster loading', async () => { + useRuntimeStore().availableClusters[0].permissions.actions = ['view-stats', 'view-jobs'] + mockGatewayAPI.stats.mockReturnValueOnce(Promise.resolve(stats)) + const wrapper = shallowMount(ClusterListItem, { + props: { + cluster: useRuntimeStore().availableClusters[0] + } + }) + // assert stats are being retrieved + expect(mockGatewayAPI.stats).toBeCalled() + // Check presence of loading spinner + expect(wrapper.findComponent(LoadingSpinner).exists()).toBeTruthy() + // Check cluster status is loading + expect(wrapper.get('div div p').text()).toBe('Loading') + }) + test('cluster without permission', async () => { + mockGatewayAPI.stats.mockReturnValueOnce(Promise.resolve(stats)) + const wrapper = shallowMount(ClusterListItem, { + props: { + cluster: useRuntimeStore().availableClusters[0] + } + }) + // Wait for result of clusters requests + await flushPromises() + // assert stats not retrieved + expect(mockGatewayAPI.stats).not.toBeCalled() + // Check Slurm version is absent + expect(wrapper.find('span span').exists()).toBeFalsy() + // Check there is only one paragraph for cluster status, not for stats + expect(wrapper.findAll('p').length).toBe(1) + expect(wrapper.get('div div p').text()).toBe('Denied') + }) + test('cluster without view-stats permission', async () => { + useRuntimeStore().availableClusters[0].permissions.actions = ['view-jobs'] + const wrapper = shallowMount(ClusterListItem, { + props: { + cluster: useRuntimeStore().availableClusters[0] + } + }) + await flushPromises() + // assert stats not retrieved + expect(mockGatewayAPI.stats).not.toBeCalled() + // Check Slurm version is absent + expect(wrapper.find('span span').exists()).toBeFalsy() + // Check there is only one paragraph for cluster status, not for stats + expect(wrapper.findAll('p').length).toBe(1) + expect(wrapper.get('div div p').text()).toBe('Available') + }) +}) diff --git a/frontend/tests/views/ClustersView.spec.ts b/frontend/tests/views/ClustersView.spec.ts new file mode 100644 index 00000000..f3568f8c --- /dev/null +++ b/frontend/tests/views/ClustersView.spec.ts @@ -0,0 +1,61 @@ +import { describe, test, expect, beforeEach, vi } from 'vitest' +import { flushPromises, shallowMount } from '@vue/test-utils' +import ClustersView from '@/views/ClustersView.vue' +import clusters from '../assets/clusters.json' +import { init_plugins } from '../lib/common' +import LoadingSpinner from '@/components/LoadingSpinner.vue' +import ClusterListItem from '@/components/clusters/ClustersListItem.vue' +import { APIServerError, AuthenticationError } from '@/composables/HTTPErrors' + +const mockGatewayAPI = { + clusters: vi.fn() +} + +vi.mock('@/composables/GatewayAPI', () => ({ + useGatewayAPI: () => mockGatewayAPI +})) + +let router + +describe('ClustersView.vue', () => { + beforeEach(() => { + router = init_plugins() + }) + test('display clusters list', async () => { + // Check at least one cluster is present in test asset or the test is pointless. + expect(clusters.length).toBeGreaterThan(0) + mockGatewayAPI.clusters.mockReturnValueOnce(Promise.resolve(clusters)) + const wrapper = shallowMount(ClustersView) + // Wait for result of clusters requests + await flushPromises() + // Check page title + expect(wrapper.get('h1').text()).toBe('Select a cluster') + // Check there are as many ClusterListItem as the number of clusters in test asset. + expect(wrapper.findAllComponents(ClusterListItem).length).toBe(clusters.length) + }) + test('show loading spinner before loaded', async () => { + const wrapper = shallowMount(ClustersView) + wrapper.getComponent(LoadingSpinner) + expect(wrapper.get('div').text()).toBe('Loading clusters…') + }) + test('authentication error', async () => { + mockGatewayAPI.clusters.mockImplementationOnce(() => { + throw new AuthenticationError('fake authentication error') + }) + const wrapper = shallowMount(ClustersView) + // Wait for result of clusters requests + await flushPromises() + // Check redirect to signout on authentication error + expect(router.push).toHaveBeenCalledTimes(1) + expect(router.push).toHaveBeenCalledWith({ name: 'signout' }) + }) + test('server error', async () => { + mockGatewayAPI.clusters.mockImplementationOnce(() => { + throw new APIServerError(500, 'fake error') + }) + const wrapper = shallowMount(ClustersView) + // Wait for result of clusters requests + await flushPromises() + expect(wrapper.get('div h3').text()).toBe('Unable to load cluster list') + }) +})