Skip to content

Commit

Permalink
Tenant change related integration tests - allow tenant change and ret…
Browse files Browse the repository at this point in the history
…ain tenant in shortlink (opensearch-project#736)

* Test to ensure when a shortlink is copied, tenant is changed and short link is visited. The tenant from the link is visited.

Signed-off-by: leanneeliatra
  • Loading branch information
leanneeliatra committed Jan 24, 2024
1 parent b78dea3 commit 2a7fc8b
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { CURRENT_TENANT } from '../../../utils/commands';
import { switchTenantTo } from './switch_tenant';

if (Cypress.env('SECURITY_ENABLED')) {
describe('Switch tenants when visiting copied links: ', () => {
const tenantName = 'private';

before(() => {
cy.server();
});
it('Checks that the tenant switcher can switch tenants despite a different tenant being present in the tenant query parameter.', function () {
CURRENT_TENANT.newTenant = tenantName;

cy.visit('/app/home').then(() => {
cy.waitForLoader();
switchTenantTo('global');
cy.waitForLoader();
cy.getElementByTestId('account-popover').click();
cy.get('#tenantName').should('contain.text', 'Global');
});
});
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { CURRENT_TENANT } from '../../../utils/commands';
import { switchTenantTo } from './switch_tenant';
import indexPatternGlobalTenantHeaderSetUp from '../../../fixtures/plugins/security-dashboards-plugin/indexpatterns/indexPatternGlobalTenantHeader.json';
import indexPatternPrivateTenantHeaderSetUp from '../../../fixtures/plugins/security-dashboards-plugin/indexpatterns/indexPatternPrivateTenantHeader.json';

if (Cypress.env('SECURITY_ENABLED')) {
describe('Multi Tenancy Tests: ', () => {
before(() => {
cy.server();

cy.createIndexPattern(
'index-pattern1',
{
title: 's*',
timeFieldName: 'timestamp',
},
indexPatternGlobalTenantHeaderSetUp
);

cy.createIndexPattern(
'index-pattern2',
{
title: 'se*',
timeFieldName: 'timestamp',
},
indexPatternPrivateTenantHeaderSetUp
);
});

it('Tests that when the short URL is copied and pasted, it will route correctly with the right tenant', function () {
const randomNumber = Cypress._.random(0, 1e6);
const dashboardName = 'Cypress dashboard - ' + randomNumber;
// We are programmatically creating a dashboard so that the test
// always have the same view. An empty list would show the empty prompt.
// Also, this saves us some typing, clicking and waiting in the test.
cy.createDashboard(
{
title: dashboardName,
},
{
security_tenant: 'private',
}
);

// When creating the shortUrl, we don't want to have the security_tenant
// parameter in the url - otherwise it will be stored in the shortUrl
// itself, which would make this test obsolete.
// But it is also hard to get the tests running reliably when opening
// Dashboards without the parameter (tenant selector popup etc.).
// Hence, we do some navigation to "lose" the query parameter.
CURRENT_TENANT.newTenant = 'private';
cy.visit('/app/home', {
waitForGetTenant: true,
onBeforeLoad(window) {
// set up session storage as we would expect to emulate browser
window.sessionStorage.setItem(
'opendistro::security::tenant::show_popup',
false
);

window.localStorage.setItem(
'opendistro::security::tenant::saved',
'__user__'
);
},
});
// Navigate to the Dashboards app
cy.getElementByTestId('toggleNavButton').should('be.visible').click();
// After clicking the navigation, the security_tenant parameter should be gone
cy.get('[href$="/app/dashboards#/list"]').should('be.visible').click();

// The test subj seems to replace spaces with a dash, so we convert the dashboard name here too.
// Go to the dashboard we have created
const selectorDashboardName = dashboardName.split(' ').join('-');
cy.getElementByTestId(
'dashboardListingTitleLink-' + selectorDashboardName
)
.should('be.visible')
.click();

cy.getElementByTestId('savedObjectTitle').type(dashboardName);

cy.intercept({
method: 'POST',
url: '/api/saved_objects/_bulk_get',
}).as('waitForReloadingDashboard');
cy.getElementByTestId('confirmSaveSavedObjectButton').click();
cy.wait('@waitForReloadingDashboard');
cy.wait(2000);

// 2. Open top share navigation to access copy short url
cy.getElementByTestId('shareTopNavButton').click();
cy.getElementByTestId('sharePanel-Permalinks').click();

// 3. Create the short url, wait for response
cy.intercept('POST', '/api/shorten_url').as('getShortUrl');
// If the url already contains the tenant parameter, it will be stored in the short url. That will work in the app
// but would render this test useless. We're testing that resolved short urls without the tenant parameter work as well.
cy.url().should('not.contain', 'security_tenant');
cy.getElementByTestId('createShortUrl').click();
cy.wait('@getShortUrl');

//4. Switch tenant & visit shortURL link to ensure tenant from short URL is retained
cy.getElementByTestId('copyShareUrlButton')
.invoke('attr', 'data-share-url')
.should('contain', '/goto/')
.then((shortUrl) => {
cy.log('Short url is ' + shortUrl);
// Navigate away to avoid the non existing dashboard in the next tenant.
switchTenantTo('global');

// Since we can't reliably read the clipboard data, we have to append the tenant parameter manually
cy.visit(shortUrl + '?security_tenant=private', {
excludeTenant: true, // We are passing the tenant as a query parameter. Mainly because of readability.
onBeforeLoad(window) {
// Here we are simulating the new tab scenario which isn't supported by Cypress
window.sessionStorage.clear();
},
});

cy.url({ timeout: 10000 }).should('contain', 'security_tenant=');
cy.getElementByTestId('breadcrumb last').should(
'contain.text',
dashboardName
);
});
});
after(() => {
cy.deleteIndexPattern('index-pattern1', {
headers: {
securitytenant: ['global'],
'osd-xsrf': true,
},
});
cy.deleteIndexPattern('index-pattern2', {
headers: {
securitytenant: ['private'],
'osd-xsrf': true,
},
});
});
});
}
25 changes: 24 additions & 1 deletion cypress/utils/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,13 @@ Cypress.Commands.overwrite('visit', (orig, url, options) => {
auth: ADMIN_AUTH,
};
}
newOptions.qs = { security_tenant: CURRENT_TENANT.defaultTenant };
if (!newOptions.excludeTenant) {
newOptions.qs = {
...newOptions.qs,
security_tenant: CURRENT_TENANT.defaultTenant,
};
}

if (waitForGetTenant) {
cy.intercept('GET', '/api/v1/multitenancy/tenant').as('getTenant');
orig(url, newOptions);
Expand Down Expand Up @@ -377,6 +383,23 @@ Cypress.Commands.add('createIndexPattern', (id, attributes, header = {}) => {
});
});

Cypress.Commands.add('createDashboard', (attributes = {}, headers = {}) => {
const url = `${Cypress.config().baseUrl}/api/saved_objects/dashboard`;

cy.request({
method: 'POST',
url,
headers: {
'content-type': 'application/json;charset=UTF-8',
'osd-xsrf': true,
...headers,
},
body: JSON.stringify({
attributes,
}),
});
});

Cypress.Commands.add('changeDefaultTenant', (attributes, header = {}) => {
const url =
Cypress.env('openSearchUrl') + '/_plugins/_security/api/tenancy/config';
Expand Down
14 changes: 14 additions & 0 deletions cypress/utils/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,20 @@ declare namespace Cypress {
header: string,
): Chainable<S>;

/**
* Adds a dashboard
* @example
* cy.createDashboard({ title: 'My dashboard'})
*/
createDashboard<S = any>(
attributes: {
title: string;
[key: string]: any;
},
headers?: {
[key: string]: any;
}
): Chainable<S>;

/**
* Changes the Default tenant for the domain.
Expand Down

0 comments on commit 2a7fc8b

Please sign in to comment.