@kui-shell/plugin-kubectl
Version:
Kubernetes visualization plugin for kubernetes
274 lines • 16.1 kB
JavaScript
/*
* 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