diff --git a/cypress/fixtures/new_community.json b/cypress/fixtures/new_community.json new file mode 100644 index 00000000000..0662a414771 --- /dev/null +++ b/cypress/fixtures/new_community.json @@ -0,0 +1,10 @@ +{ + "type":{"value":"community"}, + "metadata":{ + "dc.title":[{"value":"e2e testing Community"}], + "dc.description":[{"value":"This is the introductory text for a test community"}], + "dc.description.abstract":[{"value":"All communities need a short description"}], + "dc.rights":[{"value":"This community includes a custom license that you must agree to."}], + "dc.description.tableofcontents":[{"value":"Great news! This Community is used for e2e testing and can be deleted safely."}] + } +} \ No newline at end of file diff --git a/cypress/plugins/index.ts b/cypress/plugins/index.ts index cc3dccba38e..6b84d8061ef 100644 --- a/cypress/plugins/index.ts +++ b/cypress/plugins/index.ts @@ -6,6 +6,8 @@ const fs = require('fs'); let REST_BASE_URL: string; let REST_DOMAIN: string; +let E2E_TEST_COMMUNITIES = []; + // Plugins enable you to tap into, modify, or extend the internal behavior of Cypress // For more info, visit https://on.cypress.io/plugins-api module.exports = (on, config) => { @@ -54,6 +56,16 @@ module.exports = (on, config) => { // Retrieve currently saved value of REST Domain getRestBaseDomain() { return REST_DOMAIN ; + }, + + saveCommunityData(data: string): number { + return E2E_TEST_COMMUNITIES.push(data); + }, + getCommunityData(): string[] { + return E2E_TEST_COMMUNITIES; + }, + resetCommunityData() { + return (E2E_TEST_COMMUNITIES = []); } }); }; diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 235838fd9aa..3e594dde359 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -6,6 +6,10 @@ import { AuthTokenInfo, TOKENITEM } from 'src/app/core/auth/models/auth-token-info.model'; import { DSPACE_XSRF_COOKIE, XSRF_REQUEST_HEADER } from 'src/app/core/xsrf/xsrf.constants'; import { v4 as uuidv4 } from 'uuid'; +import { TEST_ADMIN_PASSWORD, TEST_ADMIN_USER } from './e2e'; + +// Flag to mark e2e content to be deleted/cleaned up +const TYPE_E2E_CONTENT = 'e2e-content-to-delete'; // Declare Cypress namespace to help with Intellisense & code completion in IDEs // ALL custom commands MUST be listed here for code completion to work @@ -43,7 +47,21 @@ declare global { * in chainable in order to allow it to be sent also in required CSRF header. * @returns Chainable reference to allow CSRF token to also be sent in header. */ - createCSRFCookie(): Chainable; + createCSRFCookie(): Chainable; + + /** + * Create a new DSpace Community with given name and optional parent Community index. + * + * If name is not specified, a default name is given. + * @param name name of Community + * @param parentIndex index of a previously created community to create this community under. + */ + createCommunity(name?: string, parentIndex?: number): Chainable; + + /** + * Delete any test content created + */ + cleanupTestContent(): typeof cleanupTestContent; } } } @@ -58,30 +76,32 @@ declare global { */ function login(email: string, password: string): void { // Create a fake CSRF cookie/token to use in POST - cy.createCSRFCookie().then((csrfToken: string) => { - // get our REST API's base URL, also needed for POST - cy.task('getRestBaseURL').then((baseRestUrl: string) => { - // Now, send login POST request including that CSRF token - cy.request({ - method: 'POST', - url: baseRestUrl + '/api/authn/login', - headers: { [XSRF_REQUEST_HEADER]: 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)); - }); + cy.createCSRFCookie().as('csrf_token'); + + // get our REST API's base URL, also needed for POST + cy.task('getRestBaseURL').then((baseRestUrl: string) => { + // Now, send login POST request including that CSRF token + cy.request({ + method: 'POST', + url: baseRestUrl + '/api/authn/login', + headers: { [XSRF_REQUEST_HEADER]: this.csrf_token}, + 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)); + + cy.setCookie('AUTH_HEADER', authheader); }); }); } @@ -118,24 +138,24 @@ Cypress.Commands.add('loginViaForm', loginViaForm); */ function generateViewEvent(uuid: string, dsoType: string): void { // Create a fake CSRF cookie/token to use in POST - cy.createCSRFCookie().then((csrfToken: string) => { - // get our REST API's base URL, also needed for POST - cy.task('getRestBaseURL').then((baseRestUrl: string) => { - // Now, send 'statistics/viewevents' POST request including that fake CSRF token in required header - cy.request({ - method: 'POST', - url: baseRestUrl + '/api/statistics/viewevents', - headers: { - [XSRF_REQUEST_HEADER] : csrfToken, - // use a known public IP address to avoid being seen as a "bot" - 'X-Forwarded-For': '1.1.1.1', - }, - //form: true, // indicates the body should be form urlencoded - body: { targetId: uuid, targetType: dsoType }, - }).then((resp) => { - // We expect a 201 (which means statistics event was created) - expect(resp.status).to.eq(201); - }); + cy.createCSRFCookie().as('csrf_token'); + + // get our REST API's base URL, needed for POST + cy.task('getRestBaseURL').then((baseRestUrl: string) => { + // Now, send 'statistics/viewevents' POST request including that fake CSRF token in required header + cy.request({ + method: 'POST', + url: baseRestUrl + '/api/statistics/viewevents', + headers: { + [XSRF_REQUEST_HEADER] : this.csrf_token, + // use a known public IP address to avoid being seen as a "bot" + 'X-Forwarded-For': '1.1.1.1', + }, + //form: true, // indicates the body should be form urlencoded + body: { targetId: uuid, targetType: dsoType }, + }).then((resp) => { + // We expect a 201 (which means statistics event was created) + expect(resp.status).to.eq(201); }); }); } @@ -165,3 +185,104 @@ function createCSRFCookie(): Cypress.Chainable { } // Add as a Cypress command (i.e. assign to 'cy.createCSRFCookie') Cypress.Commands.add('createCSRFCookie', createCSRFCookie); + + +//TODO FIX THIS TIM TO CREATE TEST DATA! +export function createCommunity(name?: string, parentIndex?: number): void { + // First login as an Admin + cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); + + cy.getCookie('AUTH_HEADER').as('auth_cookie'); + + cy.createCSRFCookie().as('csrf_token'); + + // get our REST API's base URL, also needed for POST + cy.task('getRestBaseURL').then((baseRestUrl: string) => { + // Get the "new_community.json" fixture which can be sent to create a community + cy.fixture('new_community.json').then((json) => { + // if name specified, override dc.title value in fixture + if (name !== undefined) { json.metadata['dc.title'][0].value = name; } + + let postUrl = baseRestUrl + '/api/core/communities'; + if (parentIndex !== undefined) { + postUrl += '?parent=' + String(Cypress.env('communities')[parentIndex].uuid); + } else { + // If top level community, save 'dc.type' flag to note this + // community was created by e2e tests & can be deleted later + json.metadata['dc.type'] = [{value:TYPE_E2E_CONTENT}]; + } + + cy.request({ + method: 'POST', + url: postUrl, + headers: { + [XSRF_REQUEST_HEADER] : this.csrf_token, + // use a known public IP address to avoid being seen as a "bot" + 'X-Forwarded-For': '1.1.1.1', + 'Authorization': this.auth_cookie.value, + }, + //form: true, // indicates the body should be form urlencoded + body: json, + }).then((resp) => { + // We expect a 201 (which means community was created) + expect(resp.status).to.eq(201); + + // Save this response body to our list of created communities + // in our environment (this is initialized in e2e.ts) + cy.task('saveCommunityData', JSON.stringify(resp.body)); + }); + + }); + }); +} +// Add as a Cypress command (i.e. assign to 'cy.createCommunity') +Cypress.Commands.add('createCommunity', createCommunity); + +// TODO create "deleteContent" +// Loop through 'e2e_test_communities' and look for ones flagged "to_delete"=true in JSON +// SEND DELETE request for each!!! + +/** + * Cleanup all test content by looping through created test communities + * looking for those with our "dc.type" flag with the "delete" flag + */ +export function cleanupTestContent(): void { + // First login as an Admin + cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); + + cy.getCookie('AUTH_HEADER').as('auth_cookie'); + + cy.createCSRFCookie().as('csrf_token'); + + // Loop through all test communities + cy.task('getCommunityData').then((communities: string[]) => { + for (const community of communities) { + const object = JSON.parse(community); + // Get id of community to delete + const id: string = object.id; + const type: string = object.metadata['dc.type'][0].value; + + if (id !== null && type === TYPE_E2E_CONTENT) { + console.log('Deleting test Community ' + id); + // get our REST API's base URL, neded for delete + cy.task('getRestBaseURL').then((baseRestUrl: string) => { + cy.request({ + method: 'DELETE', + url: baseRestUrl + '/api/core/communities/' + id, + headers: { + [XSRF_REQUEST_HEADER] : this.csrf_token, + // use a known public IP address to avoid being seen as a "bot" + 'X-Forwarded-For': '1.1.1.1', + 'Authorization': this.auth_cookie.value, + }, + failOnStatusCode: false, + }); + }); + } + } + }); + + cy.task('resetCommunityData'); +} +// Add as a Cypress command (i.e. assign to 'cy.cleanupTestContent') +Cypress.Commands.add('cleanupTestContent', cleanupTestContent); diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index 5e4fddb8059..2d3e015f73d 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -54,6 +54,7 @@ before(() => { }); // Runs once before the first test in each "block" +// (In Cypress, a test "block" starts with "describe()") beforeEach(() => { // Pre-agree to all Klaro cookies by setting the klaro-anonymous cookie // This just ensures it doesn't get in the way of matching other objects in the page. @@ -61,6 +62,11 @@ beforeEach(() => { // Remove any CSRF cookies saved from prior tests cy.clearCookie(DSPACE_XSRF_COOKIE); + + // TODO: These methods *WORK*. However, they end up crashing/overwhelming the UI in tests + // (request timed out). NEED TO DEBUG WHY. Maybe it's several calls to login or several CSRF tokens? + //cy.cleanupTestContent(); + //cy.createCommunity(); }); // Global constants used in tests