diff --git a/cypress.json b/cypress.json index 104a672377..549485eb9e 100644 --- a/cypress.json +++ b/cypress.json @@ -11,6 +11,7 @@ "openMode": 0 }, "env": { + "DSPACE_TEST_REST_BASE_URL": "http://localhost:8080", "DSPACE_TEST_ADMIN_USER": "dspacedemo+admin@gmail.com", "DSPACE_TEST_ADMIN_PASSWORD": "dspace", "DSPACE_TEST_COMMUNITY": "0958c910-2037-42a9-81c7-dca80e3892b4", diff --git a/cypress/integration/login-modal.spec.ts b/cypress/integration/login-modal.spec.ts index 82b322c4a9..fbdbeba3e8 100644 --- a/cypress/integration/login-modal.spec.ts +++ b/cypress/integration/login-modal.spec.ts @@ -9,33 +9,57 @@ const page = { // Once logged in, click the User menu in header cy.get('ds-themed-navbar [data-e2e="user-menu"]').click(); }, + submitLoginAndPasswordByPressingButton(email, password) { + // Enter email + cy.get('ds-themed-navbar [data-e2e="email"]').type(email); + // Enter password + cy.get('ds-themed-navbar [data-e2e="password"]').type(password); + // Click login button + cy.get('ds-themed-navbar [data-e2e="login-button"]').click(); + }, submitLoginAndPasswordByPressingEnter(email, password) { // In opened Login modal, fill out email & password, then click Enter cy.get('ds-themed-navbar [data-e2e="email"]').type(email); cy.get('ds-themed-navbar [data-e2e="password"]').type(password); cy.get('ds-themed-navbar [data-e2e="password"]').type('{enter}'); + }, + submitLogoutByPressingButton() { + // This is the POST command that will actually log us out + cy.intercept('POST', '/server/api/authn/logout').as('logout'); + // Click logout button + cy.get('ds-themed-navbar [data-e2e="logout-button"]').click(); + // Wait until above POST command responds before continuing + // (This ensures next action waits until logout completes) + cy.wait('@logout'); } }; describe('Login Modal', () => { - it('should login when clicking button', () => { - cy.visit('/'); + it('should login when clicking button & stay on same page', () => { + const ENTITYPAGE = '/entities/publication/' + TEST_ENTITY_PUBLICATION; + cy.visit(ENTITYPAGE); // Login menu should exist cy.get('ds-log-in').should('exist'); // Login, and the tag should no longer exist - cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); + page.openLoginMenu(); + cy.get('.form-login').should('be.visible'); + + page.submitLoginAndPasswordByPressingButton(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); cy.get('ds-log-in').should('not.exist'); + // Verify we are still on the same page + cy.url().should('include', ENTITYPAGE); + // Open user menu, verify user menu & logout button now available page.openUserMenu(); cy.get('ds-user-menu').should('be.visible'); cy.get('ds-log-out').should('be.visible'); }); - it('should login when clicking enter key', () => { - cy.visit('/'); + it('should login when clicking enter key & stay on same page', () => { + cy.visit('/home'); // Open login menu in header & verify tag is visible page.openLoginMenu(); @@ -45,39 +69,29 @@ describe('Login Modal', () => { page.submitLoginAndPasswordByPressingEnter(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); cy.get('.form-login').should('not.exist'); + // Verify we are still on homepage + cy.url().should('include', '/home'); + // Open user menu, verify user menu & logout button now available page.openUserMenu(); cy.get('ds-user-menu').should('be.visible'); cy.get('ds-log-out').should('be.visible'); }); - it('should stay on same page after login', () => { - const ENTITYPAGE = '/entities/publication/' + TEST_ENTITY_PUBLICATION; - cy.visit(ENTITYPAGE); - - // Login, and the tag should no longer exist - cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); - cy.get('ds-log-in').should('not.exist'); - - // Verify we are still on the same page - cy.url().should('include', ENTITYPAGE); - }); - it('should support logout', () => { + // First authenticate & access homepage + cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); cy.visit('/'); - cy.get('ds-log-in').should('exist'); - cy.get('ds-log-out').should('not.exist'); - - // Click login - cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); - + // Verify ds-log-in tag doesn't exist, but ds-log-out tag does exist cy.get('ds-log-in').should('not.exist'); cy.get('ds-log-out').should('exist'); - // Click logout - cy.logout(); + // Click logout button + page.openUserMenu(); + page.submitLogoutByPressingButton(); + // Verify ds-log-in tag now exists cy.get('ds-log-in').should('exist'); cy.get('ds-log-out').should('not.exist'); }); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 67e604d2e7..dcac5aaaac 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -2,48 +2,63 @@ // This File is for Custom Cypress commands. // See docs at https://docs.cypress.io/api/cypress-api/custom-commands // *********************************************** + +import { AuthTokenInfo, TOKENITEM } from 'src/app/core/auth/models/auth-token-info.model'; +import { TEST_REST_BASE_URL } from '.'; + // Declare Cypress namespace to help with Intellisense & code completion in IDEs // ALL custom commands MUST be listed here for code completion to work // tslint:disable-next-line:no-namespace -declare namespace Cypress { - interface Chainable { - login(email: string, password: string): typeof login; - logout(): typeof logout; +declare global { + namespace Cypress { + interface Chainable { + /** + * Login to backend before accessing the next page. Ensures that the next + * call to "cy.visit()" will be authenticated as this user. + * @param email email to login as + * @param password password to login as + */ + login(email: string, password: string): typeof login; + } } } /** - * Login from any page via DSpace's header menu + * Login user via REST API directly, and pass authentication token to UI via + * the UI's dsAuthInfo cookie. * @param email email to login as * @param password password to login as */ function login(email: string, password: string): void { - // Click the closed "Log In" dropdown menu (to open Login menu) - cy.get('ds-themed-navbar [data-e2e="login-menu"]').click(); - // Enter email - cy.get('ds-themed-navbar [data-e2e="email"]').type(email); - // Enter password - cy.get('ds-themed-navbar [data-e2e="password"]').type(password); - // Click login button - cy.get('ds-themed-navbar [data-e2e="login-button"]').click(); + // To login via REST, first we have to do a GET to obtain a valid CSRF token + cy.request( TEST_REST_BASE_URL + '/server/api/authn/status' ) + .then((response) => { + // We should receive a CSRF token returned in a response header + expect(response.headers).to.have.property('dspace-xsrf-token'); + const csrfToken = response.headers['dspace-xsrf-token']; + + // Now, send login POST request including that CSRF token + cy.request({ + method: 'POST', + url: TEST_REST_BASE_URL + '/server/api/authn/login', + headers: { 'X-XSRF-TOKEN' : csrfToken}, + form: true, // indicates the body should be form urlencoded + body: { user: email, password: password } + }).then((resp) => { + // We expect a successful login + expect(resp.status).to.eq(200); + // We expect to have a valid authorization header returned (with our auth token) + expect(resp.headers).to.have.property('authorization'); + + // Initialize our AuthTokenInfo object from the authorization header. + const authheader = resp.headers.authorization as string; + const authinfo: AuthTokenInfo = new AuthTokenInfo(authheader); + + // Save our AuthTokenInfo object to our dsAuthInfo UI cookie + // This ensures the UI will recognize we are logged in on next "visit()" + cy.setCookie(TOKENITEM, JSON.stringify(authinfo)); + }); + }); } // Add as a Cypress command (i.e. assign to 'cy.login') Cypress.Commands.add('login', login); - - -/** - * Logout from any page via DSpace's header menu. - * NOTE: Also waits until logout completes before next command will be run. - */ - function logout(): void { - // Click the closed User dropdown menu (to open user menu in header) - cy.get('ds-themed-navbar [data-e2e="user-menu"]').click(); - // This is the POST command that will actually log us out - cy.intercept('POST', '/server/api/authn/logout').as('logout'); - // Click logout button - cy.get('ds-themed-navbar [data-e2e="logout-button"]').click(); - // Wait until above POST command responds before continuing - cy.wait('@logout'); -} -// Add as a Cypress command (i.e. assign to 'cy.logout') -Cypress.Commands.add('logout', logout); diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 9c29d45e3e..442f00b9fe 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -33,6 +33,7 @@ before(() => { // Default UUIDs listed here are all in the Demo Entities Data set available at // https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data // (This is the data set used in our CI environment) +export const TEST_REST_BASE_URL = Cypress.env('DSPACE_TEST_REST_BASE_URL') || 'http://localhost:8080'; export const TEST_ADMIN_USER = Cypress.env('DSPACE_TEST_ADMIN_USER') || 'dspacedemo+admin@gmail.com'; export const TEST_ADMIN_PASSWORD = Cypress.env('DSPACE_TEST_ADMIN_PASSWORD') || 'dspace'; export const TEST_COLLECTION = Cypress.env('DSPACE_TEST_COLLECTION') || '282164f5-d325-4740-8dd1-fa4d6d3e7200';