Skip to content

Commit

Permalink
Add console integration tests that better exercise auth
Browse files Browse the repository at this point in the history
  • Loading branch information
TheRealJon committed Mar 20, 2019
1 parent c4e8426 commit d0e1963
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 39 deletions.
26 changes: 26 additions & 0 deletions frontend/integration-tests/data/htpasswd-idp.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Secret with htpasswd data for username: test, password: test
apiVersion: v1
kind: Secret
metadata:
name: htpass-secret
namespace: openshift-config
data:
htpasswd: dGVzdDokYXByMSRxa0Zvb203dCRSWFIuNHhTV0lhL3h6dkRRUUFFUG8w

---

# Create htpasswd identity provider named 'test'
apiVersion: config.openshift.io/v1
kind: OAuth
metadata:
name: cluster
spec:
identityProviders:
- name: test
challenge: true
login: true
mappingMethod: claim
type: HTPasswd
htpasswd:
fileData:
name: htpass-secret
106 changes: 89 additions & 17 deletions frontend/integration-tests/protractor.conf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@ import * as _ from 'lodash';
import { TapReporter, JUnitXmlReporter } from 'jasmine-reporters';
import * as ConsoleReporter from 'jasmine-console-reporter';
import * as failFast from 'protractor-fail-fast';
import { createWriteStream} from 'fs';
import { createWriteStream } from 'fs';
import { format } from 'util';

const tap = !!process.env.TAP;

export const BROWSER_TIMEOUT = 15000;
export const appHost = `${process.env.BRIDGE_BASE_ADDRESS || 'http://localhost:9000'}${(process.env.BRIDGE_BASE_PATH || '/').replace(/\/$/, '')}`;
export const testName = `test-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5)}`;

const htmlReporter = new HtmlScreenshotReporter({dest: './gui_test_screenshots', inlineImages: true, captureOnlyFailedSpecs: true, filename: 'test-gui-report.html'});
const junitReporter = new JUnitXmlReporter({savePath: './gui_test_screenshots', consolidateAll: true});
const htmlReporter = new HtmlScreenshotReporter({ dest: './gui_test_screenshots', inlineImages: true, captureOnlyFailedSpecs: true, filename: 'test-gui-report.html' });
const junitReporter = new JUnitXmlReporter({ savePath: './gui_test_screenshots', consolidateAll: true });
const browserLogs: logging.Entry[] = [];

export const config: Config = {
Expand Down Expand Up @@ -63,7 +64,7 @@ export const config: Config = {
onComplete: async() => {
const consoleLogStream = createWriteStream('gui_test_screenshots/browser.log', { flags: 'a' });
browserLogs.forEach(log => {
const {level, message} = log;
const { level, message } = log;
const messageStr = _.isArray(message) ? message.join(' ') : message;
consoleLogStream.write(`${format.apply(null, [`[${level.name}]`, messageStr])}\n`);
});
Expand All @@ -82,29 +83,96 @@ export const config: Config = {
return new Promise(resolve => htmlReporter.afterLaunch(resolve.bind(this, exitCode)));
},
suites: {
filter: ['tests/base.scenario.ts', 'tests/filter.scenario.ts'],
annotation: ['tests/base.scenario.ts', 'tests/modal-annotations.scenario.ts'],
environment: ['tests/base.scenario.ts', 'tests/environment.scenario.ts'],
secrets: ['tests/base.scenario.ts', 'tests/secrets.scenario.ts'],
crud: ['tests/base.scenario.ts', 'tests/crud.scenario.ts', 'tests/secrets.scenario.ts', 'tests/filter.scenario.ts', 'tests/modal-annotations.scenario.ts', 'tests/environment.scenario.ts'],
monitoring: ['tests/base.scenario.ts', 'tests/monitoring.scenario.ts'],
newApp: ['tests/base.scenario.ts', 'tests/overview/overview.scenario.ts', 'tests/source-to-image.scenario.ts', 'tests/deploy-image.scenario.ts'],
olm: ['tests/base.scenario.ts', 'tests/olm/descriptors.scenario.ts', 'tests/olm/catalog.scenario.ts', 'tests/olm/prometheus.scenario.ts', 'tests/olm/etcd.scenario.ts'],
olmUpgrade: ['tests/base.scenario.ts', 'tests/olm/update-channel-approval.scenario.ts'],
operatorHub: ['tests/base.scenario.ts', 'tests/operator-hub/operator-hub.scenario.ts'],
filter: [
'tests/login.scenario.ts',
'tests/base.scenario.ts',
'tests/filter.scenario.ts',
],
annotation: [
'tests/login.scenario.ts',
'tests/base.scenario.ts',
'tests/modal-annotations.scenario.ts',
],
environment: [
'tests/login.scenario.ts',
'tests/base.scenario.ts',
'tests/environment.scenario.ts',
],
secrets: [
'tests/login.scenario.ts',
'tests/base.scenario.ts',
'tests/secrets.scenario.ts',
],
crud: [
'tests/login.scenario.ts',
'tests/base.scenario.ts',
'tests/crud.scenario.ts',
'tests/secrets.scenario.ts',
'tests/filter.scenario.ts',
'tests/modal-annotations.scenario.ts',
'tests/environment.scenario.ts',
],
monitoring: [
'tests/login.scenario.ts',
'tests/base.scenario.ts',
'tests/monitoring.scenario.ts',
],
newApp: [
'tests/login.scenario.ts',
'tests/base.scenario.ts',
'tests/overview/overview.scenario.ts',
'tests/source-to-image.scenario.ts',
'tests/deploy-image.scenario.ts',
],
olm: [
'tests/login.scenario.ts',
'tests/base.scenario.ts',
'tests/olm/descriptors.scenario.ts',
'tests/olm/catalog.scenario.ts',
'tests/olm/prometheus.scenario.ts',
'tests/olm/etcd.scenario.ts',
],
olmUpgrade: [
'tests/login.scenario.ts',
'tests/base.scenario.ts',
'tests/olm/update-channel-approval.scenario.ts',
],
operatorHub: [
'tests/login.scenario.ts',
'tests/base.scenario.ts',
'tests/operator-hub/operator-hub.scenario.ts',
],
// OLM and OperatorHub
olmFull: [
'tests/login.scenario.ts',
'tests/base.scenario.ts',
'tests/operator-hub/operator-hub.scenario.ts',
'tests/olm/descriptors.scenario.ts',
'tests/olm/catalog.scenario.ts',
'tests/olm/prometheus.scenario.ts',
'tests/olm/etcd.scenario.ts',
],
performance: ['tests/base.scenario.ts', 'tests/performance.scenario.ts'],
serviceCatalog: ['tests/base.scenario.ts', 'tests/service-catalog/service-catalog.scenario.ts', 'tests/service-catalog/service-broker.scenario.ts', 'tests/service-catalog/service-class.scenario.ts', 'tests/service-catalog/service-binding.scenario.ts', 'tests/developer-catalog.scenario.ts'],
overview: ['tests/base.scenario.ts', 'tests/overview/overview.scenario.ts'],
performance: [
'tests/login.scenario.ts',
'tests/base.scenario.ts',
'tests/performance.scenario.ts',
],
serviceCatalog: [
'tests/login.scenario.ts',
'tests/base.scenario.ts',
'tests/service-catalog/service-catalog.scenario.ts',
'tests/service-catalog/service-broker.scenario.ts',
'tests/service-catalog/service-class.scenario.ts',
'tests/service-catalog/service-binding.scenario.ts',
'tests/developer-catalog.scenario.ts',
],
overview: [
'tests/login.scenario.ts',
'tests/base.scenario.ts',
'tests/overview/overview.scenario.ts',
],
e2e: [
'tests/login.scenario.ts',
'tests/base.scenario.ts',
'tests/crud.scenario.ts',
'tests/secrets.scenario.ts',
Expand All @@ -117,6 +185,7 @@ export const config: Config = {
'tests/performance.scenario.ts',
],
all: [
'tests/login.scenario.ts',
'tests/base.scenario.ts',
'tests/crud.scenario.ts',
'tests/overview/overview.scenareio.ts',
Expand All @@ -130,6 +199,9 @@ export const config: Config = {
'tests/operator-hub/operator-hub.scenario.ts',
'tests/developer-catalog.scenario.ts',
],
login: [
'tests/login.scenario.ts',
],
},
params: {
// Set to 'true' to enable OpenShift resources in the crud scenario.
Expand Down
20 changes: 1 addition & 19 deletions frontend/integration-tests/tests/base.scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,10 @@ import { browser, ExpectedConditions as until, $, $$ } from 'protractor';

import { appHost, testName } from '../protractor.conf';
import * as crudView from '../views/crud.view';
import * as loginView from '../views/login.view';

const BROWSER_TIMEOUT = 15000;

describe('Basic console test', () => {

it('logs into console if necessary', async() => {
await browser.get(appHost);

const {BRIDGE_AUTH_USERNAME, BRIDGE_AUTH_PASSWORD} = process.env;
if (BRIDGE_AUTH_USERNAME && BRIDGE_AUTH_PASSWORD) {
await browser.wait(until.visibilityOf(loginView.nameInput), BROWSER_TIMEOUT);
await loginView.nameInput.sendKeys(BRIDGE_AUTH_USERNAME);
await loginView.passwordInput.sendKeys(BRIDGE_AUTH_PASSWORD);
await loginView.submitButton.click();
await browser.wait(until.visibilityOf($('.pf-c-page__header')), BROWSER_TIMEOUT);
}

expect(browser.getCurrentUrl()).toContain(appHost);
});

describe('Create a test namespace', () => {
it(`creates test namespace ${testName} if necessary`, async() => {
// Use projects if OpenShift so non-admin users can run tests.
const resource = browser.params.openshift === 'true' ? 'projects' : 'namespaces';
Expand All @@ -37,7 +20,6 @@ describe('Basic console test', () => {
await $('.modal-content').$('#confirm-action').click();
await browser.wait(until.urlContains(`/${testName}`), BROWSER_TIMEOUT);
}

expect(browser.getCurrentUrl()).toContain(appHost);
});
});
74 changes: 74 additions & 0 deletions frontend/integration-tests/tests/login.scenario.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { $, browser, ExpectedConditions as until } from 'protractor';
import { appHost } from '../protractor.conf';
import * as loginView from '../views/login.view';
import * as sidenavView from '../views/sidenav.view';
import * as clusterSettingsView from '../views/cluster-settings.view';

const JASMINE_DEFAULT_TIMEOUT_INTERVAL = jasmine.DEFAULT_TIMEOUT_INTERVAL;
const JASMINE_EXTENDED_TIMEOUT_INTERVAL = 1000 * 60 * 3;
const KUBEADMIN_IDP = 'kube:admin';
const KUBEADMIN_USERNAME = 'kubeadmin';
const {
HTPASSWD_IDP = 'test',
HTPASSWD_USERNAME = 'test',
HTPASSWD_PASSWORD = 'test',
KUBEADMIN_PASSWORD,
} = process.env;

describe('Auth test', () => {
beforeAll(async() => {
await browser.get(appHost);
await browser.sleep(3000); // Wait long enough for the login redirect to complete
});

if (KUBEADMIN_PASSWORD) {
describe('Login test', async() => {
beforeAll(() => {
// Extend the default jasmine timeout interval just in case it takes a while for the htpasswd idp to be ready
jasmine.DEFAULT_TIMEOUT_INTERVAL = JASMINE_EXTENDED_TIMEOUT_INTERVAL;
});

afterAll(() => {
// Set jasmine timeout interval back to the original value after these tests are done
jasmine.DEFAULT_TIMEOUT_INTERVAL = JASMINE_DEFAULT_TIMEOUT_INTERVAL;
});

it('logs in via htpasswd identity provider', async() => {
await loginView.login(HTPASSWD_IDP, HTPASSWD_USERNAME, HTPASSWD_PASSWORD);
expect(browser.getCurrentUrl()).toContain(appHost);
expect(loginView.userDropdown.getText()).toContain('test');
});

it('logs out htpasswd user', async() => {
await loginView.logout();
expect(browser.getCurrentUrl()).toContain('openshift-authentication');
expect($('.login-pf').isPresent()).toBeTruthy();
});

it('logs in as kubeadmin user', async() => {
await loginView.login(KUBEADMIN_IDP, KUBEADMIN_USERNAME, KUBEADMIN_PASSWORD);
expect(browser.getCurrentUrl()).toContain(appHost);
expect(loginView.userDropdown.getText()).toContain('kube:admin');
await browser.wait(until.presenceOf($('.co-global-notification')));
expect($('.co-global-notifications').getText()).toContain('You are logged in as a temporary administrative user. Update the cluster OAuth configuration to allow others to log in.');
});

it('logs out kubeadmin user', async() => {
await loginView.logout();
expect(browser.getCurrentUrl()).toContain('openshift-authentication');
expect($('.login-pf').isPresent()).toBeTruthy();

// Log back in so that remaining tests can be run
await loginView.login(KUBEADMIN_IDP, KUBEADMIN_USERNAME, KUBEADMIN_PASSWORD);
expect(loginView.userDropdown.getText()).toContain('kube:admin');
});
});
}
it('is authenticated as cluster admin user', async() => {
expect(await browser.getCurrentUrl()).toContain(appHost);
await browser.wait(until.visibilityOf(sidenavView.navSectionFor('Administration')));
await sidenavView.clickNavLink(['Administration', 'Cluster Settings']);
await clusterSettingsView.isLoaded();
expect(clusterSettingsView.heading.isDisplayed()).toBeTruthy();
});
});
5 changes: 5 additions & 0 deletions frontend/integration-tests/views/cluster-settings.view.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { element, by, browser, $$ } from 'protractor';
import { waitForNone } from '../protractor.conf';

export const heading = element(by.cssContainingText('h1.co-m-pane__heading', 'Cluster Settings'));
export const isLoaded = async() => await browser.wait(waitForNone($$('.co-m-loader')));
33 changes: 32 additions & 1 deletion frontend/integration-tests/views/login.view.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,38 @@
/* eslint-disable no-undef, no-unused-vars */

import { $ } from 'protractor';
import { $, browser, ExpectedConditions as until, by, element } from 'protractor';
import { appHost } from '../protractor.conf';

export const nameInput = $('#inputUsername');
export const passwordInput = $('#inputPassword');
export const submitButton = $('button[type=submit]');
export const logOutLink = element(by.linkText('Log out'));
export const userDropdown = $('[data-test=user-dropdown] .pf-c-dropdown__toggle');

export const selectProvider = async(provider: string) => {
const idpLink = element(by.cssContainingText('.idp', provider));
while (!(await idpLink.isPresent())) {
await browser.get(appHost);
await browser.sleep(3000);
}
await idpLink.click();
};

export const login = async(providerName: string, username: string, password: string) => {
if (providerName) {
await selectProvider(providerName);
}
await browser.wait(until.visibilityOf(nameInput));
await nameInput.sendKeys(username);
await passwordInput.sendKeys(password);
await submitButton.click();
await browser.wait(until.presenceOf(userDropdown));
};

export const logout = async() => {
await browser.wait(until.presenceOf(userDropdown));
await userDropdown.click();
await browser.wait(until.presenceOf(logOutLink));
await logOutLink.click();
await browser.wait(until.presenceOf($('.login-pf')));
};
1 change: 1 addition & 0 deletions frontend/public/components/masthead-toolbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ class MastheadToolbar_ extends React.Component {

return (
<Dropdown
data-test="user-dropdown"
isPlain
position="right"
onSelect={this._onUserDropdownSelect}
Expand Down
6 changes: 4 additions & 2 deletions test-prow-e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ function copyArtifacts {

trap copyArtifacts EXIT

export BRIDGE_AUTH_USERNAME=kubeadmin
# don't log kubeadmin-password
set +x
export BRIDGE_AUTH_PASSWORD="$(cat "${INSTALLER_DIR}/auth/kubeadmin-password")"
export KUBEADMIN_PASSWORD="$(cat "${INSTALLER_DIR}/auth/kubeadmin-password")"
set -x
export BRIDGE_BASE_ADDRESS="$(oc get consoles.config.openshift.io cluster -o jsonpath='{.status.consoleURL}')"

# Add htpasswd IDP
oc apply -f ./frontend/integration-tests/data/htpasswd-idp.yaml

./test-gui.sh ${1:-e2e}

0 comments on commit d0e1963

Please sign in to comment.