gitlab-ce/spec/frontend/environments/kubernetes_status_bar_spec.js

305 lines
10 KiB
JavaScript

import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { GlLoadingIcon, GlPopover, GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import KubernetesStatusBar from '~/environments/components/kubernetes_status_bar.vue';
import {
CLUSTER_STATUS_HEALTHY_TEXT,
CLUSTER_STATUS_UNHEALTHY_TEXT,
SYNC_STATUS_BADGES,
} from '~/environments/constants';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
import { s__ } from '~/locale';
import { mockKasTunnelUrl } from './mock_data';
Vue.use(VueApollo);
const configuration = {
basePath: mockKasTunnelUrl.replace(/\/$/, ''),
baseOptions: {
headers: { 'GitLab-Agent-Id': '1' },
withCredentials: true,
},
};
const environmentName = 'environment_name';
describe('~/environments/components/kubernetes_status_bar.vue', () => {
let wrapper;
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findHealthBadge = () => wrapper.findByTestId('health-badge');
const findSyncBadge = () => wrapper.findByTestId('sync-badge');
const findPopover = () => wrapper.findComponent(GlPopover);
const fluxKustomizationStatusQuery = jest.fn().mockReturnValue([]);
const fluxHelmReleaseStatusQuery = jest.fn().mockReturnValue([]);
const createApolloProvider = () => {
const mockResolvers = {
Query: {
fluxKustomizationStatus: fluxKustomizationStatusQuery,
fluxHelmReleaseStatus: fluxHelmReleaseStatusQuery,
},
};
return createMockApollo([], mockResolvers);
};
const createWrapper = ({
apolloProvider = createApolloProvider(),
clusterHealthStatus = '',
namespace = '',
fluxResourcePath = '',
} = {}) => {
wrapper = shallowMountExtended(KubernetesStatusBar, {
propsData: {
clusterHealthStatus,
configuration,
environmentName,
namespace,
fluxResourcePath,
},
apolloProvider,
stubs: { GlSprintf },
});
};
describe('health badge', () => {
it('shows loading icon when cluster health is not present', () => {
createWrapper();
expect(findLoadingIcon().exists()).toBe(true);
});
it.each([
['success', 'success', CLUSTER_STATUS_HEALTHY_TEXT],
['error', 'danger', CLUSTER_STATUS_UNHEALTHY_TEXT],
])(
'when clusterHealthStatus is %s shows health badge with variant %s and text %s',
(status, variant, text) => {
createWrapper({ clusterHealthStatus: status });
expect(findLoadingIcon().exists()).toBe(false);
expect(findHealthBadge().props('variant')).toBe(variant);
expect(findHealthBadge().text()).toBe(text);
},
);
});
describe('sync badge', () => {
describe('when no namespace is provided', () => {
beforeEach(() => {
createWrapper();
});
it("doesn't request Kustomizations and HelmReleases", () => {
expect(fluxKustomizationStatusQuery).not.toHaveBeenCalled();
expect(fluxHelmReleaseStatusQuery).not.toHaveBeenCalled();
});
it('renders sync status as Unavailable', () => {
expect(findSyncBadge().text()).toBe(s__('Deployment|Unavailable'));
});
});
describe('when flux resource path is provided', () => {
const namespace = 'my-namespace';
let fluxResourcePath;
describe('if the provided resource is a Kustomization', () => {
beforeEach(() => {
fluxResourcePath =
'kustomize.toolkit.fluxcd.io/v1beta1/namespaces/my-namespace/kustomizations/app';
createWrapper({ namespace, fluxResourcePath });
});
it('requests the Kustomization resource status', () => {
expect(fluxKustomizationStatusQuery).toHaveBeenCalledWith(
{},
expect.objectContaining({
configuration,
namespace,
environmentName,
fluxResourcePath,
}),
expect.any(Object),
expect.any(Object),
);
});
it("doesn't request HelmRelease resource status", () => {
expect(fluxHelmReleaseStatusQuery).not.toHaveBeenCalled();
});
});
describe('if the provided resource is a helmRelease', () => {
beforeEach(() => {
fluxResourcePath =
'helm.toolkit.fluxcd.io/v2beta1/namespaces/my-namespace/helmreleases/app';
createWrapper({ namespace, fluxResourcePath });
});
it('requests the HelmRelease resource status', () => {
expect(fluxHelmReleaseStatusQuery).toHaveBeenCalledWith(
{},
expect.objectContaining({
configuration,
namespace,
environmentName,
fluxResourcePath,
}),
expect.any(Object),
expect.any(Object),
);
});
it("doesn't request Kustomization resource status", () => {
expect(fluxKustomizationStatusQuery).not.toHaveBeenCalled();
});
});
});
describe('when namespace is provided', () => {
describe('with no Flux resources found', () => {
beforeEach(() => {
createWrapper({ namespace: 'my-namespace' });
});
it('requests Kustomizations', () => {
expect(fluxKustomizationStatusQuery).toHaveBeenCalled();
});
it('requests HelmReleases when there were no Kustomizations found', async () => {
await waitForPromises();
expect(fluxHelmReleaseStatusQuery).toHaveBeenCalled();
});
it('renders sync status as Unavailable when no Kustomizations and HelmReleases found', async () => {
await waitForPromises();
expect(findSyncBadge().text()).toBe(s__('Deployment|Unavailable'));
});
});
describe('with Flux Kustomizations available', () => {
const createApolloProviderWithKustomizations = ({
result = { status: 'True', type: 'Ready', message: '' },
} = {}) => {
const mockResolvers = {
Query: {
fluxKustomizationStatus: jest.fn().mockReturnValue([result]),
fluxHelmReleaseStatus: fluxHelmReleaseStatusQuery,
},
};
return createMockApollo([], mockResolvers);
};
it("doesn't request HelmReleases when the Kustomizations were found", async () => {
createWrapper({
apolloProvider: createApolloProviderWithKustomizations(),
namespace: 'my-namespace',
});
await waitForPromises();
expect(fluxHelmReleaseStatusQuery).not.toHaveBeenCalled();
});
it.each`
status | type | badgeType
${'True'} | ${'Stalled'} | ${'stalled'}
${'True'} | ${'Reconciling'} | ${'reconciling'}
${'True'} | ${'Ready'} | ${'reconciled'}
${'False'} | ${'Ready'} | ${'failed'}
${'True'} | ${'Unknown'} | ${'unknown'}
`(
'renders $badgeType when status is $status and type is $type',
async ({ status, type, badgeType }) => {
createWrapper({
apolloProvider: createApolloProviderWithKustomizations({
result: { status, type, message: '' },
}),
namespace: 'my-namespace',
});
await waitForPromises();
const badge = SYNC_STATUS_BADGES[badgeType];
expect(findSyncBadge().text()).toBe(badge.text);
expect(findSyncBadge().props()).toMatchObject({
icon: badge.icon,
variant: badge.variant,
});
},
);
it.each`
status | type | message | popoverTitle | popoverText
${'True'} | ${'Stalled'} | ${'stalled reason'} | ${s__('Deployment|Flux sync stalled')} | ${'stalled reason'}
${'True'} | ${'Reconciling'} | ${''} | ${undefined} | ${s__('Deployment|Flux sync reconciling')}
${'True'} | ${'Ready'} | ${''} | ${undefined} | ${s__('Deployment|Flux sync reconciled successfully')}
${'False'} | ${'Ready'} | ${'failed reason'} | ${s__('Deployment|Flux sync failed')} | ${'failed reason'}
${'True'} | ${'Unknown'} | ${''} | ${s__('Deployment|Flux sync status is unknown')} | ${s__('Deployment|Unable to detect state. %{linkStart}How are states detected?%{linkEnd}')}
`(
'renders correct popover text when status is $status and type is $type',
async ({ status, type, message, popoverTitle, popoverText }) => {
createWrapper({
apolloProvider: createApolloProviderWithKustomizations({
result: { status, type, message },
}),
namespace: 'my-namespace',
});
await waitForPromises();
expect(findPopover().text()).toMatchInterpolatedText(popoverText);
expect(findPopover().props('title')).toBe(popoverTitle);
},
);
});
describe('when Flux API errored', () => {
const error = new Error('Error from the cluster_client API');
const createApolloProviderWithErrors = () => {
const mockResolvers = {
Query: {
fluxKustomizationStatus: jest.fn().mockRejectedValueOnce(error),
fluxHelmReleaseStatus: jest.fn().mockRejectedValueOnce(error),
},
};
return createMockApollo([], mockResolvers);
};
beforeEach(async () => {
createWrapper({
apolloProvider: createApolloProviderWithErrors(),
namespace: 'my-namespace',
});
await waitForPromises();
});
it('renders sync badge as unavailable', () => {
const badge = SYNC_STATUS_BADGES.unavailable;
expect(findSyncBadge().text()).toBe(badge.text);
expect(findSyncBadge().props()).toMatchObject({
icon: badge.icon,
variant: badge.variant,
});
});
it('renders popover with an API error message', () => {
expect(findPopover().text()).toBe(error.message);
expect(findPopover().props('title')).toBe(
s__('Deployment|Flux sync status is unavailable'),
);
});
});
});
});
});