UNPKG

@kui-shell/plugin-kubectl

Version:

Kubernetes visualization plugin for kubernetes

274 lines 16.1 kB
/* * Copyright 2018 The Kubernetes Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import * as assert from 'assert'; import { Common, CLI, ReplExpect, SidecarExpect, Selectors, Keys, Util } from '@kui-shell/test'; import { waitForGreen, createNS, allocateNS, deleteNS, defaultModeForGet } from '@kui-shell/plugin-kubectl/tests/lib/k8s/utils'; import { dirname } from 'path'; const ROOT = dirname(require.resolve('@kui-shell/plugin-kubectl/tests/package.json')); const commands = ['kubectl']; commands.forEach(command => { describe(`${command} edit ${process.env.MOCHA_RUN_TARGET || ''}`, function () { before(Common.before(this)); after(Common.after(this)); const ns = createNS(); const inNamespace = `-n ${ns}`; let res; const create = (name, source = 'pod.yaml') => { it(`should create sample pod ${name} from URL via ${command}`, () => __awaiter(this, void 0, void 0, function* () { try { const res = yield CLI.command(`${command} create -f ${ROOT}/data/k8s/headless/${source} ${inNamespace}`, this.app); const selector = yield ReplExpect.okWithCustom({ selector: Selectors.BY_NAME(name) })(res); // wait for the badge to become green yield waitForGreen(this.app, selector); } catch (err) { yield Common.oops(this, true)(err); } })); }; const edit = (name, kind = 'Pod', nameAsShown = name, mode = 'raw', clickable) => { it(`should edit it via ${command} edit with name=${name || 'no-name'}`, () => __awaiter(this, void 0, void 0, function* () { try { console.error('E1'); res = yield CLI.command(`${command} edit pod ${name || ''} ${inNamespace}`, this.app); console.error('E2'); yield ReplExpect.ok(res); console.error('E3'); yield SidecarExpect.open(res); console.error('E4'); yield SidecarExpect.showing(nameAsShown, undefined, undefined, ns, undefined, undefined, undefined, clickable)(res); console.error('E5'); yield SidecarExpect.mode(mode)(res); console.error('E6'); yield SidecarExpect.yaml({ kind })(res); } catch (err) { yield Common.oops(this, true)(err); } })); }; const modifyWithError = (title, where, expectedError, revert) => { it(`should modify the content, introducing a ${title}`, () => __awaiter(this, void 0, void 0, function* () { try { const actualText = yield Util.getValueFromMonaco(res); const labelsLineIdx = actualText.split(/\n/).indexOf(' labels:'); // +1 here because nth-child is indexed from 1 const lineSelector = `${Selectors.SIDECAR(res.count)} .view-lines > .view-line:nth-child(${labelsLineIdx + 1}) .mtk22`; yield this.app.client.$(lineSelector).then(_ => _.click()); // we'll inject some garbage that we expect to fail validation const garbage = 'zzzzzz'; console.error('ME1'); yield new Promise(resolve => setTimeout(resolve, 2000)); yield this.app.client.keys(`${where}${garbage}`); // <-- injecting garbage yield new Promise(resolve => setTimeout(resolve, 2000)); yield this.app.client.$(Selectors.SIDECAR_TOOLBAR_BUTTON(res.count, 'Save')).then(_ => _.click()); console.error('ME2'); // an error state and the garbage text had better appear in the toolbar text yield SidecarExpect.toolbarAlert({ type: 'error', text: expectedError || garbage, exact: false })(res); console.error('ME3'); // expect line number to be highlighted, and for that line to be visible yield this.app.client .$(`${Selectors.SIDECAR_TAB_CONTENT(res.count)} .kui--editor-line-highlight`) .then(_ => _.waitForDisplayed()); console.error('ME4'); if (revert) { yield this.app.client.$(Selectors.SIDECAR_TOOLBAR_BUTTON(res.count, 'Revert')).then(_ => _.click()); console.error('ME5'); let idx = 0; yield this.app.client.waitUntil(() => __awaiter(this, void 0, void 0, function* () { const revertedText = yield Util.getValueFromMonaco(res); if (++idx > 5) { console.error(`still waiting for revertedText=${revertedText} actualText=${actualText}`); } return revertedText === actualText; }), { timeout: CLI.waitTimeout }); } console.error('ME6'); } catch (err) { yield Common.oops(this, true)(err); } })); }; // go to spec: line, insert garbage at the beginning (Keys.Home), // expect to find garbage text in error message; the last // "undefined" means use garbage text as the expected error const validationError = modifyWithError.bind(undefined, 'validation error', Keys.Home, undefined); // go to spec: line, insert garbage at the end (Keys.End), expect // to find "could not find expected..." in error message const parseError = modifyWithError.bind(undefined, 'parse error', Keys.End, 'could not find expected'); /** modify pod {name} so as to add a label of key=value */ const modify = (name, key = 'foo', value = 'bar', needsUnfold = false) => { it(`should modify the content by adding label ${key}=${value}`, () => __awaiter(this, void 0, void 0, function* () { try { const actualText = yield Util.getValueFromMonaco(res); const labelsLineIdx = actualText.split(/\n/).indexOf(' labels:'); if (needsUnfold) { yield Util.clickToExpandMonacoFold(res, labelsLineIdx); } // +2 here because nth-child is indexed from 1, and we want the line after that const lineSelector = `${Selectors.SIDECAR(res.count)} .view-lines > .view-line:nth-child(${labelsLineIdx + 2}) .mtk5:last-child`; yield this.app.client.$(lineSelector).then(_ => _.click()); yield new Promise(resolve => setTimeout(resolve, 2000)); yield this.app.client.keys(`${Keys.End}${Keys.ENTER}${key}: ${value}${Keys.ENTER}`); yield new Promise(resolve => setTimeout(resolve, 2000)); const saveButton = yield this.app.client.$(Selectors.SIDECAR_TOOLBAR_BUTTON(res.count, 'Save')); yield saveButton.click(); // await SidecarExpect.toolbarAlert({ type: 'success', text: 'Successfully Applied', exact: false })(res) console.error('M1'); yield saveButton.waitForExist({ timeout: 10000, reverse: true }); console.error('M2'); } catch (err) { yield Common.oops(this, true)(err); } })); it('should show the modified content in the current yaml tab', () => { return SidecarExpect.yaml({ metadata: { labels: { [key]: value } } })(res).catch(Common.oops(this, true)); }); }; /** kubectl get pod ${name} */ const get = (name) => { it(`should get pod ${name}`, () => __awaiter(this, void 0, void 0, function* () { try { res = yield CLI.command(`${command} get pod ${name} ${inNamespace} -o yaml`, this.app) .then(ReplExpect.ok) .then(SidecarExpect.open) .then(SidecarExpect.showing(name, undefined, undefined, ns)); } catch (err) { yield Common.oops(this, true)(err); } })); }; /** click Edit button */ const clickToEdit = (name) => { it(`should click the edit button to edit ${name}`, () => __awaiter(this, void 0, void 0, function* () { try { // start with the default mode showing yield SidecarExpect.mode(defaultModeForGet)(res); // click the edit button yield this.app.client.$(Selectors.SIDECAR_TOOLBAR_BUTTON(res.count, 'edit-button')).then((_) => __awaiter(this, void 0, void 0, function* () { yield _.waitForDisplayed(); yield _.click(); })); console.error('CE1'); yield new Promise(resolve => setTimeout(resolve, 5000)); // edit button should not exist yield this.app.client .$(Selectors.SIDECAR_TOOLBAR_BUTTON(res.count, 'edit-button')) .then(_ => _.waitForExist({ timeout: 5000, reverse: true })); // should still be showing pod {name}, but now with the yaml tab selected console.error('CE2'); yield SidecarExpect.showing(name, undefined, undefined, ns)(res); console.error('CE3'); yield SidecarExpect.mode('raw')(res); // also: no back/forward buttons should be visible console.error('CE4'); yield this.app.client .$(Selectors.SIDECAR_BACK_BUTTON(res.count)) .then(_ => _.waitForExist({ timeout: 5000, reverse: true })); console.error('CE5'); yield this.app.client .$(Selectors.SIDECAR_FORWARD_BUTTON(res.count)) .then(_ => _.waitForExist({ timeout: 5000, reverse: true })); console.error('CE6'); } catch (err) { yield Common.oops(this, true)(err); } })); modify(name, 'clickfoo1', 'clickbar1'); modify(name, 'clickfoo2', 'clickbar2'); // after success, should re-modify the resource in the current tab successfully validationError(true); // do unsupported edits in the current tab, validate the error alert, and then undo the changes modify(name, 'clickfoo3', 'clickbar3', false); // after error, should re-modify the resource in the current tab successfully // no longer needed, with DescriptionList summary xit('should switch to summary tab, expect no alerts and not editable', () => __awaiter(this, void 0, void 0, function* () { try { yield this.app.client.$(Selectors.SIDECAR_MODE_BUTTON(res.count, 'summary')).then((_) => __awaiter(this, void 0, void 0, function* () { yield _.waitForDisplayed(); yield _.click(); })); yield this.app.client .$(Selectors.SIDECAR_MODE_BUTTON_SELECTED(res.count, 'summary')) .then(_ => _.waitForDisplayed()); // toolbar alert should not exist yield this.app.client .$(Selectors.SIDECAR_ALERT(res.count, 'success')) .then(_ => _.waitForExist({ timeout: CLI.waitTimeout, reverse: true })); // edit button should not exist yield this.app.client .$(Selectors.SIDECAR_TOOLBAR_BUTTON(res.count, 'edit-button')) .then(_ => _.waitForExist({ timeout: 5000, reverse: true })); // try editing the summary mode const actualText = yield Util.getValueFromMonaco(res); const labelsLineIdx = actualText.split(/\n/).indexOf('Name:'); // +2 here because nth-child is indexed from 1, and we want the line after that const lineSelector = `${Selectors.SIDECAR(res.count)} .view-lines > .view-line:nth-child(${labelsLineIdx + 2}) .mtk5:last-child`; yield this.app.client.$(lineSelector).then(_ => _.click()); yield new Promise(resolve => setTimeout(resolve, 2000)); yield this.app.client.keys('x'); // random key yield new Promise(resolve => setTimeout(resolve, 2000)); // should have same text const actualText2 = yield Util.getValueFromMonaco(res); assert.ok(actualText === actualText2); yield this.app.client .$(Selectors.SIDECAR_TOOLBAR_BUTTON(res.count, 'Save')) .then(_ => _.waitForExist({ timeout: 10000, reverse: true })); // should not have apply button } catch (err) { yield Common.oops(this, true)(err); } })); }; // // here come the tests // allocateNS(this, ns); const nginx = 'nginx'; create(nginx); const name2 = 'nginx2'; create(name2, 'pod2.yaml'); edit('', /List$/, '2 items', 'edit', false); edit(nginx); modify(nginx); modify(nginx, 'foo1', 'bar1'); // successfully re-modify the resource in the current tab validationError(true); // do unsupported edits in the current tab, and then undo the changes modify(nginx, 'foo2', 'bar2', false); // after error, successfully re-modify the resource in the current tab parseError(); // after sucess, do unsupported edits it('should refresh', () => Common.refresh(this)); // FIXME: after this, the test is not working edit(nginx); validationError(true); // do unsupported edits in the current tab, then undo the changes parseError(); // after error, do another unsupported edits in the current tab it('should refresh', () => Common.refresh(this)); get(nginx); clickToEdit(nginx); deleteNS(this, ns); }); }); //# sourceMappingURL=edit.js.map