2020-01-01 13:55:28 +05:30
|
|
|
import MockAdapter from 'axios-mock-adapter';
|
|
|
|
import { loadHTMLFixture } from 'helpers/fixtures';
|
|
|
|
import { setTestTimeout } from 'helpers/timeout';
|
2018-03-17 18:26:18 +05:30
|
|
|
import Clusters from '~/clusters/clusters_bundle';
|
2020-10-24 23:57:45 +05:30
|
|
|
import { APPLICATION_STATUS, APPLICATIONS, RUNNER } from '~/clusters/constants';
|
2019-07-07 11:18:12 +05:30
|
|
|
import axios from '~/lib/utils/axios_utils';
|
2019-12-26 22:10:19 +05:30
|
|
|
import initProjectSelectDropdown from '~/project_select';
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2019-12-04 20:38:33 +05:30
|
|
|
jest.mock('~/lib/utils/poll');
|
2019-12-26 22:10:19 +05:30
|
|
|
jest.mock('~/project_select');
|
2019-12-04 20:38:33 +05:30
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
const { INSTALLING, INSTALLABLE, INSTALLED, UNINSTALLING } = APPLICATION_STATUS;
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
describe('Clusters', () => {
|
2019-07-31 22:56:46 +05:30
|
|
|
setTestTimeout(1000);
|
2019-07-07 11:18:12 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
let cluster;
|
2019-07-07 11:18:12 +05:30
|
|
|
let mock;
|
|
|
|
|
|
|
|
const mockGetClusterStatusRequest = () => {
|
|
|
|
const { statusPath } = document.querySelector('.js-edit-cluster-form').dataset;
|
|
|
|
|
|
|
|
mock = new MockAdapter(axios);
|
|
|
|
|
|
|
|
mock.onGet(statusPath).reply(200);
|
|
|
|
};
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
loadHTMLFixture('clusters/show_cluster.html');
|
|
|
|
});
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
mockGetClusterStatusRequest();
|
|
|
|
});
|
2018-03-17 18:26:18 +05:30
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
cluster = new Clusters();
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
cluster.destroy();
|
2019-07-07 11:18:12 +05:30
|
|
|
mock.restore();
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
|
2019-12-04 20:38:33 +05:30
|
|
|
describe('class constructor', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
jest.spyOn(Clusters.prototype, 'initPolling');
|
|
|
|
cluster = new Clusters();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should call initPolling on construct', () => {
|
|
|
|
expect(cluster.initPolling).toHaveBeenCalled();
|
|
|
|
});
|
2019-12-26 22:10:19 +05:30
|
|
|
|
|
|
|
it('should call initProjectSelectDropdown on construct', () => {
|
|
|
|
expect(initProjectSelectDropdown).toHaveBeenCalled();
|
|
|
|
});
|
2019-12-04 20:38:33 +05:30
|
|
|
});
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
describe('checkForNewInstalls', () => {
|
|
|
|
const INITIAL_APP_MAP = {
|
|
|
|
helm: { status: null, title: 'Helm Tiller' },
|
|
|
|
ingress: { status: null, title: 'Ingress' },
|
|
|
|
runner: { status: null, title: 'GitLab Runner' },
|
|
|
|
};
|
|
|
|
|
|
|
|
it('does not show alert when things transition from initial null state to something', () => {
|
|
|
|
cluster.checkForNewInstalls(INITIAL_APP_MAP, {
|
|
|
|
...INITIAL_APP_MAP,
|
2019-07-31 22:56:46 +05:30
|
|
|
helm: { status: INSTALLABLE, title: 'Helm Tiller' },
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
|
2018-12-13 13:39:08 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
expect(flashMessage).toBeNull();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('shows an alert when something gets newly installed', () => {
|
2018-12-13 13:39:08 +05:30
|
|
|
cluster.checkForNewInstalls(
|
|
|
|
{
|
|
|
|
...INITIAL_APP_MAP,
|
2019-07-31 22:56:46 +05:30
|
|
|
helm: { status: INSTALLING, title: 'Helm Tiller' },
|
2018-12-13 13:39:08 +05:30
|
|
|
},
|
|
|
|
{
|
|
|
|
...INITIAL_APP_MAP,
|
2019-07-31 22:56:46 +05:30
|
|
|
helm: { status: INSTALLED, title: 'Helm Tiller' },
|
2018-12-13 13:39:08 +05:30
|
|
|
},
|
|
|
|
);
|
2018-03-17 18:26:18 +05:30
|
|
|
|
|
|
|
const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
|
2018-12-13 13:39:08 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
expect(flashMessage).not.toBeNull();
|
2018-12-13 13:39:08 +05:30
|
|
|
expect(flashMessage.textContent.trim()).toEqual(
|
|
|
|
'Helm Tiller was successfully installed on your Kubernetes cluster',
|
|
|
|
);
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('shows an alert when multiple things gets newly installed', () => {
|
2018-12-13 13:39:08 +05:30
|
|
|
cluster.checkForNewInstalls(
|
|
|
|
{
|
|
|
|
...INITIAL_APP_MAP,
|
2019-07-31 22:56:46 +05:30
|
|
|
helm: { status: INSTALLING, title: 'Helm Tiller' },
|
|
|
|
ingress: { status: INSTALLABLE, title: 'Ingress' },
|
2018-12-13 13:39:08 +05:30
|
|
|
},
|
|
|
|
{
|
|
|
|
...INITIAL_APP_MAP,
|
2019-07-31 22:56:46 +05:30
|
|
|
helm: { status: INSTALLED, title: 'Helm Tiller' },
|
|
|
|
ingress: { status: INSTALLED, title: 'Ingress' },
|
2018-12-13 13:39:08 +05:30
|
|
|
},
|
|
|
|
);
|
2018-03-17 18:26:18 +05:30
|
|
|
|
|
|
|
const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
|
2018-12-13 13:39:08 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
expect(flashMessage).not.toBeNull();
|
2018-12-13 13:39:08 +05:30
|
|
|
expect(flashMessage.textContent.trim()).toEqual(
|
|
|
|
'Helm Tiller, Ingress was successfully installed on your Kubernetes cluster',
|
|
|
|
);
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('updateContainer', () => {
|
2019-10-12 21:52:04 +05:30
|
|
|
const { location } = window;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
delete window.location;
|
|
|
|
window.location = {
|
|
|
|
reload: jest.fn(),
|
|
|
|
hash: location.hash,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
window.location = location;
|
|
|
|
});
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
describe('when creating cluster', () => {
|
|
|
|
it('should show the creating container', () => {
|
|
|
|
cluster.updateContainer(null, 'creating');
|
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy();
|
|
|
|
expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
|
|
|
|
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
|
2019-10-12 21:52:04 +05:30
|
|
|
expect(window.location.reload).not.toHaveBeenCalled();
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('should continue to show `creating` banner with subsequent updates of the same status', () => {
|
2019-10-12 21:52:04 +05:30
|
|
|
cluster.updateContainer(null, 'creating');
|
2018-03-17 18:26:18 +05:30
|
|
|
cluster.updateContainer('creating', 'creating');
|
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy();
|
|
|
|
expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
|
|
|
|
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
|
2019-10-12 21:52:04 +05:30
|
|
|
expect(window.location.reload).not.toHaveBeenCalled();
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('when cluster is created', () => {
|
2019-10-12 21:52:04 +05:30
|
|
|
it('should hide the "creating" banner and refresh the page', () => {
|
|
|
|
jest.spyOn(cluster, 'setClusterNewlyCreated');
|
|
|
|
cluster.updateContainer(null, 'creating');
|
|
|
|
cluster.updateContainer('creating', 'created');
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
|
2019-10-12 21:52:04 +05:30
|
|
|
expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
|
|
|
|
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
|
|
|
|
expect(window.location.reload).toHaveBeenCalled();
|
|
|
|
expect(cluster.setClusterNewlyCreated).toHaveBeenCalledWith(true);
|
|
|
|
});
|
2018-12-13 13:39:08 +05:30
|
|
|
|
2019-10-12 21:52:04 +05:30
|
|
|
it('when the page is refreshed, it should show the "success" banner', () => {
|
|
|
|
jest.spyOn(cluster, 'setClusterNewlyCreated');
|
|
|
|
jest.spyOn(cluster, 'isClusterNewlyCreated').mockReturnValue(true);
|
|
|
|
|
|
|
|
cluster.updateContainer(null, 'created');
|
|
|
|
cluster.updateContainer('created', 'created');
|
2018-12-13 13:39:08 +05:30
|
|
|
|
2019-10-12 21:52:04 +05:30
|
|
|
expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
|
|
|
|
expect(cluster.successContainer.classList.contains('hidden')).toBeFalsy();
|
2018-12-13 13:39:08 +05:30
|
|
|
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
|
2019-10-12 21:52:04 +05:30
|
|
|
expect(window.location.reload).not.toHaveBeenCalled();
|
|
|
|
expect(cluster.setClusterNewlyCreated).toHaveBeenCalledWith(false);
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('should not show a banner when status is already `created`', () => {
|
2019-10-12 21:52:04 +05:30
|
|
|
jest.spyOn(cluster, 'setClusterNewlyCreated');
|
|
|
|
jest.spyOn(cluster, 'isClusterNewlyCreated').mockReturnValue(false);
|
|
|
|
|
|
|
|
cluster.updateContainer(null, 'created');
|
2018-03-17 18:26:18 +05:30
|
|
|
cluster.updateContainer('created', 'created');
|
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
|
|
|
|
expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
|
|
|
|
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
|
2019-10-12 21:52:04 +05:30
|
|
|
expect(window.location.reload).not.toHaveBeenCalled();
|
|
|
|
expect(cluster.setClusterNewlyCreated).not.toHaveBeenCalled();
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('when cluster has error', () => {
|
|
|
|
it('should show the error container', () => {
|
|
|
|
cluster.updateContainer(null, 'errored', 'this is an error');
|
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
|
|
|
|
|
|
|
|
expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
|
|
|
|
|
|
|
|
expect(cluster.errorContainer.classList.contains('hidden')).toBeFalsy();
|
|
|
|
|
|
|
|
expect(cluster.errorReasonContainer.textContent).toContain('this is an error');
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('should show `error` banner when previously `creating`', () => {
|
|
|
|
cluster.updateContainer('creating', 'errored');
|
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
|
|
|
|
|
|
|
|
expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
|
|
|
|
|
|
|
|
expect(cluster.errorContainer.classList.contains('hidden')).toBeFalsy();
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
});
|
2019-09-04 21:01:54 +05:30
|
|
|
|
|
|
|
describe('when cluster is unreachable', () => {
|
|
|
|
it('should show the unreachable warning container', () => {
|
|
|
|
cluster.updateContainer(null, 'unreachable');
|
|
|
|
|
|
|
|
expect(cluster.unreachableContainer.classList.contains('hidden')).toBe(false);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('when cluster has an authentication failure', () => {
|
|
|
|
it('should show the authentication failure warning container', () => {
|
|
|
|
cluster.updateContainer(null, 'authentication_failure');
|
|
|
|
|
|
|
|
expect(cluster.authenticationFailureContainer.classList.contains('hidden')).toBe(false);
|
|
|
|
});
|
|
|
|
});
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
describe('installApplication', () => {
|
2019-12-26 22:10:19 +05:30
|
|
|
it.each(APPLICATIONS)('tries to install %s', (applicationId, done) => {
|
|
|
|
jest.spyOn(cluster.service, 'installApplication').mockResolvedValue();
|
2018-12-13 13:39:08 +05:30
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
cluster.store.state.applications[applicationId].status = INSTALLABLE;
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2020-06-23 00:09:42 +05:30
|
|
|
const params = {};
|
|
|
|
if (applicationId === 'knative') {
|
|
|
|
params.hostname = 'test-example.com';
|
|
|
|
}
|
|
|
|
|
2019-12-26 22:10:19 +05:30
|
|
|
// eslint-disable-next-line promise/valid-params
|
|
|
|
cluster
|
2020-06-23 00:09:42 +05:30
|
|
|
.installApplication({ id: applicationId, params })
|
2019-12-26 22:10:19 +05:30
|
|
|
.then(() => {
|
|
|
|
expect(cluster.store.state.applications[applicationId].status).toEqual(INSTALLING);
|
|
|
|
expect(cluster.store.state.applications[applicationId].requestReason).toEqual(null);
|
2020-06-23 00:09:42 +05:30
|
|
|
expect(cluster.service.installApplication).toHaveBeenCalledWith(applicationId, params);
|
2019-12-26 22:10:19 +05:30
|
|
|
done();
|
|
|
|
})
|
|
|
|
.catch();
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
it('sets error request status when the request fails', () => {
|
|
|
|
jest
|
|
|
|
.spyOn(cluster.service, 'installApplication')
|
|
|
|
.mockRejectedValueOnce(new Error('STUBBED ERROR'));
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
cluster.store.state.applications.helm.status = INSTALLABLE;
|
2018-12-13 13:39:08 +05:30
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
const promise = cluster.installApplication({ id: 'helm' });
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
return promise.then(() => {
|
|
|
|
expect(cluster.store.state.applications.helm.status).toEqual(INSTALLABLE);
|
|
|
|
expect(cluster.store.state.applications.helm.installFailed).toBe(true);
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
expect(cluster.store.state.applications.helm.requestReason).toBeDefined();
|
|
|
|
});
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|
2019-07-31 22:56:46 +05:30
|
|
|
});
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
describe('uninstallApplication', () => {
|
|
|
|
it.each(APPLICATIONS)('tries to uninstall %s', applicationId => {
|
|
|
|
jest.spyOn(cluster.service, 'uninstallApplication').mockResolvedValueOnce();
|
2018-12-13 13:39:08 +05:30
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
cluster.store.state.applications[applicationId].status = INSTALLED;
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
cluster.uninstallApplication({ id: applicationId });
|
|
|
|
|
|
|
|
expect(cluster.store.state.applications[applicationId].status).toEqual(UNINSTALLING);
|
|
|
|
expect(cluster.store.state.applications[applicationId].requestReason).toEqual(null);
|
|
|
|
expect(cluster.service.uninstallApplication).toHaveBeenCalledWith(applicationId);
|
2018-11-08 19:23:39 +05:30
|
|
|
});
|
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
it('sets error request status when the uninstall request fails', () => {
|
2019-07-07 11:18:12 +05:30
|
|
|
jest
|
2019-07-31 22:56:46 +05:30
|
|
|
.spyOn(cluster.service, 'uninstallApplication')
|
2019-07-07 11:18:12 +05:30
|
|
|
.mockRejectedValueOnce(new Error('STUBBED ERROR'));
|
2018-12-13 13:39:08 +05:30
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
cluster.store.state.applications.helm.status = INSTALLED;
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
const promise = cluster.uninstallApplication({ id: 'helm' });
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2019-07-07 11:18:12 +05:30
|
|
|
return promise.then(() => {
|
2019-07-31 22:56:46 +05:30
|
|
|
expect(cluster.store.state.applications.helm.status).toEqual(INSTALLED);
|
|
|
|
expect(cluster.store.state.applications.helm.uninstallFailed).toBe(true);
|
2019-07-07 11:18:12 +05:30
|
|
|
expect(cluster.store.state.applications.helm.requestReason).toBeDefined();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-12-04 20:38:33 +05:30
|
|
|
describe('fetch cluster environments success', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
jest.spyOn(cluster.store, 'toggleFetchEnvironments').mockReturnThis();
|
|
|
|
jest.spyOn(cluster.store, 'updateEnvironments').mockReturnThis();
|
|
|
|
|
|
|
|
cluster.handleClusterEnvironmentsSuccess({ data: {} });
|
|
|
|
});
|
|
|
|
|
|
|
|
it('toggles the cluster environments loading icon', () => {
|
|
|
|
expect(cluster.store.toggleFetchEnvironments).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('updates the store when cluster environments is retrieved', () => {
|
|
|
|
expect(cluster.store.updateEnvironments).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('handleClusterStatusSuccess', () => {
|
2019-07-07 11:18:12 +05:30
|
|
|
beforeEach(() => {
|
|
|
|
jest.spyOn(cluster.store, 'updateStateFromServer').mockReturnThis();
|
|
|
|
jest.spyOn(cluster, 'checkForNewInstalls').mockReturnThis();
|
|
|
|
jest.spyOn(cluster, 'updateContainer').mockReturnThis();
|
2019-12-04 20:38:33 +05:30
|
|
|
cluster.handleClusterStatusSuccess({ data: {} });
|
2019-07-07 11:18:12 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('updates clusters store', () => {
|
|
|
|
expect(cluster.store.updateStateFromServer).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('checks for new installable apps', () => {
|
|
|
|
expect(cluster.checkForNewInstalls).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('updates message containers', () => {
|
|
|
|
expect(cluster.updateContainer).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-09-04 21:01:54 +05:30
|
|
|
describe('updateApplication', () => {
|
|
|
|
const params = { version: '1.0.0' };
|
|
|
|
let storeUpdateApplication;
|
|
|
|
let installApplication;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
storeUpdateApplication = jest.spyOn(cluster.store, 'updateApplication');
|
|
|
|
installApplication = jest.spyOn(cluster.service, 'installApplication');
|
|
|
|
|
|
|
|
cluster.updateApplication({ id: RUNNER, params });
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
storeUpdateApplication.mockRestore();
|
|
|
|
installApplication.mockRestore();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('calls store updateApplication method', () => {
|
|
|
|
expect(storeUpdateApplication).toHaveBeenCalledWith(RUNNER);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('sends installApplication request', () => {
|
|
|
|
expect(installApplication).toHaveBeenCalledWith(RUNNER, params);
|
|
|
|
});
|
|
|
|
});
|
2018-03-17 18:26:18 +05:30
|
|
|
});
|