Skip to content

Commit

Permalink
Cypress Test Case: Login Via SSO
Browse files Browse the repository at this point in the history
  • Loading branch information
SmitGala committed Mar 13, 2024
1 parent 71c71e0 commit b6f7af3
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 27 deletions.
1 change: 1 addition & 0 deletions .github/workflows/e2e-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
env:
CYPRESS_TEST_USERNAME: ${{ secrets.CYPRESS_TEST_USERNAME}}
CYPRESS_TEST_PASSWORD: ${{ secrets.CYPRESS_TEST_PASSWORD }}
CYPRESS_TEST_GOOGLE_PASSWORD: ${{ secrets.CYPRESS_TEST_GOOGLE_PASSWORD }}
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_TEST_RECORD_ID}}
CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_TEST_PROJECT_ID }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion app/components/login-component/sso/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from='irene/components/login-component/index.scss'
}}'
>
<AkIcon @iconName='person' class="input-icon" />
<AkIcon @iconName='person' class='input-icon' />
</label>
<Input
data-test-login-sso-forced-username-input
Expand Down
103 changes: 84 additions & 19 deletions cypress/support/Actions/auth/LoginActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,51 @@ export default class LoginActions {
cy.findByPlaceholderText('Username / Email').type(username); // Username/Email field
cy.findByLabelText('login-user-check-icon').click();

cy.wait('@checkUserRoute', { timeout: 30000 });
cy.wait('@checkUserRoute');

cy.findByPlaceholderText('Password').type(password); // Password field
cy.findByLabelText('login-submit-button').click();

cy.wait('@loginAPIReq', { timeout: 30000 });
cy.wait('@loginAPIReq');
}

/**
* Logs user in via SSO using the UI
*/
doLoginViaSSO({ username }: Omit<UserLoginCredentialProps, 'password'>) {
// Intercepts user check request
cy.intercept(API_ROUTES.check.route).as('checkUserRoute');

// Intercepts frontend config request
cy.intercept(API_ROUTES.frontendConfig.route).as('frontendConfig');

cy.intercept(API_ROUTES.saml2Login.route).as('saml2LoginApiReq');

cy.visit(APPLICATION_ROUTES.login);

// Wait for frontend config request to resolve
cy.wait('@frontendConfig');

cy.findByPlaceholderText('Username / Email').type(username); // Username/Email field

cy.findByLabelText('login-user-check-icon').click();

cy.wait('@checkUserRoute');

cy.contains(APP_TRANSLATIONS.ssoLogin).should('exist').click();

cy.origin(
'https://accounts.google.com',
{ args: { username, password: Cypress.env('TEST_GOOGLE_PASSWORD') } },
({ username, password }) => {
cy.get('input[type="email"]').type(`${username}{enter}`);

// Enter password
cy.get('input[type="password"]').type(`${password}{enter}`);
}
);

cy.wait('@saml2LoginApiReq');
}

/**
Expand Down Expand Up @@ -73,6 +112,18 @@ export default class LoginActions {
cy.findAllByLabelText('login-user-check-icon').should('exist'); // User check button;
}

validateCurrentUserSession() {
// If these elements are displayed, token is expired
cy.findByPlaceholderText('Username / Email').should('not.exist'); // Username/Email field
cy.findByLabelText('login-user-check-icon').should('not.exist'); // User check button;s

// Validate presence of access token in localStorage.
cy.window()
.its('localStorage')
.invoke('getItem', 'ember_simple_auth-session')
.should('exist');
}

/**
* Logs in with user credentials and caches session across specs
*/
Expand All @@ -85,24 +136,38 @@ export default class LoginActions {
() => this.doLoginWithUI(userCreds),
{
cacheAcrossSpecs,
validate: () => {
// If these elements are displayed, token is expired
cy.findByPlaceholderText('Username / Email').should('not.exist'); // Username/Email field
cy.findByLabelText('login-user-check-icon').should('not.exist'); // User check button;s

// Validate presence of access token in localStorage.
cy.window()
.its('localStorage')
.invoke('getItem', 'ember_simple_auth-session')
.then((authInfo) => {
const authDetails = authInfo ? JSON.parse(authInfo) : {};

expect(authDetails)
.and.to.have.property('authenticated')
.to.have.any.keys('authenticator', 'b64token', 'token');
});
},
validate: this.validateCurrentUserSession.bind(this),
}
);
}

/**
* Logs in with SSO and caches session across specs
*/
loginWithSSOAndSaveSession(
userCreds: Omit<UserLoginCredentialProps, 'password'>,
cacheAcrossSpecs = true
) {
cy.session(
session.createSessionIdWithSSO(userCreds),
() => this.doLoginViaSSO(userCreds),
{
cacheAcrossSpecs,
validate: this.validateCurrentUserSession.bind(this),
}
);
}

/**
* Assertions after Login
*/
verifyDashboardElements() {
// Assertion for different dashboard elements
cy.findByText(APP_TRANSLATIONS.startNewScan).should('exist');
cy.findByText(APP_TRANSLATIONS.uploadApp).should('exist');
cy.findByText(APP_TRANSLATIONS.allProjects).should('exist');
cy.findByText(APP_TRANSLATIONS.allProjectsDescription).should('exist');
cy.findByText(APP_TRANSLATIONS.support).should('exist');
cy.findByText(APP_TRANSLATIONS.knowledgeBase).should('exist');
}
}
6 changes: 6 additions & 0 deletions cypress/support/Actions/common/SessionActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ export default class SessionActions {
return `${username}-${password}`;
}

createSessionIdWithSSO({
username,
}: Omit<UserLoginCredentialProps, 'password'>) {
return `SSO-${username}`;
}

resetCurrentSession() {
return Cypress.session.clearCurrentSessionData();
}
Expand Down
4 changes: 4 additions & 0 deletions cypress/support/api.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export const API_ROUTES = {

// Auth
login: { route: '/api/login' },
saml2Login: {
route: '/api/sso/saml2/login',
alias: 'saml2LoginReq',
},

// Listing Routes
sbomProjectList: {
Expand Down
90 changes: 83 additions & 7 deletions cypress/tests/auth.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,59 @@ const password = Cypress.env('TEST_PASSWORD');
describe('User Login', () => {
beforeEach(() => {
networkActions.hideNetworkLogsFor({ ...API_ROUTES.websockets });

cy.intercept(API_ROUTES.check.route).as('checkUserRoute');
cy.intercept(API_ROUTES.userInfo.route).as('userInfoRoute');

// Intercept vulnerabilities route
networkActions.mockNetworkReq({
...API_ROUTES.vulnerabilityList,
dataOverride: {
data: mirageServer.createRecordList('vulnerability', 1).map((_) => ({
id: _.id,
type: 'vulnerabilities',
attributes: _,
relationships: {},
})),
},
});

// Intercept unknown analysis request
const unknownAnalysisStatus = mirageServer.createRecord(
'unknown-analysis-status'
);

networkActions.mockNetworkReq({
...API_ROUTES.unknownAnalysisStatus,
dataOverride: unknownAnalysisStatus,
});

// Intercept file request
const file = mirageServer.createRecord('file');

networkActions.mockNetworkReq({
...API_ROUTES.file,
dataOverride: file,
});

// Intercept projects route
networkActions.mockPaginatedNetworkReq({
...API_ROUTES.projectList,
resDataOverride: {
results: mirageServer
.createRecordList('project', 1)
.map((_) => ({ ..._, file: file.id })),
},
});

// Return empty submissions data
networkActions.mockPaginatedNetworkReq({
route: API_ROUTES.submissionList.route,
alias: 'submissionList',
resDataOverride: {
results: [],
},
});
});

it('should redirect unauthenticated user to login page', function () {
Expand Down Expand Up @@ -158,12 +211,35 @@ describe('User Login', () => {
}
});

// Assertion for different dashboard elements
cy.findByText(APP_TRANSLATIONS.startNewScan).should('exist');
cy.findByText(APP_TRANSLATIONS.uploadApp).should('exist');
cy.findByText(APP_TRANSLATIONS.allProjects).should('exist');
cy.findByText(APP_TRANSLATIONS.allProjectsDescription).should('exist');
cy.findByText(APP_TRANSLATIONS.support).should('exist');
cy.findByText(APP_TRANSLATIONS.knowledgeBase).should('exist');
loginActions.verifyDashboardElements();
});

it('should redirect authenticated user to dashboard after logging in via SSO', () => {
// Logs user to dashboard via API
loginActions.loginWithSSOAndSaveSession({ username });

cy.visit('/');

// Navigates to project listing route
cy.url({ timeout: 15000 }).should('include', APPLICATION_ROUTES.projects);

// Necessary API call before showing dashboard elements
cy.wait('@submissionList', { timeout: 15000 });

// Programmatically check for page elements based on user information.
cy.wait('@userInfoRoute').then(({ response }) => {
const orgUser = response?.body?.data?.attributes as Partial<
MirageFactoryDefProps['user']
>;

const username = orgUser?.username;

// Programmatically check for user name in navbar
if (username) {
cy.findByText(username).should('exist');
}
});

loginActions.verifyDashboardElements();
});
});

0 comments on commit b6f7af3

Please sign in to comment.