diff --git a/cypress/e2e/add-forge-now-request-dashboard.cy.js b/cypress/e2e/add-forge-now-request-dashboard.cy.js index c546f493..6c62d231 100644 --- a/cypress/e2e/add-forge-now-request-dashboard.cy.js +++ b/cypress/e2e/add-forge-now-request-dashboard.cy.js @@ -1,315 +1,312 @@ /** * Copyright (C) 2022 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ let requestId; let requestForgeDomain; let requestInboundEmailAddress; const requestData = { forge_type: 'bitbucket', forge_url: 'test.example.com', forge_contact_email: 'test@example.com', forge_contact_name: 'test user', submitter_forward_username: true, forge_contact_comment: 'test comment' }; function createDummyRequest(urls) { cy.task('db:add_forge_now:delete'); cy.userLogin(); return cy.getCookie('csrftoken').its('value').then((token) => { cy.request({ method: 'POST', url: urls.api_1_add_forge_request_create(), body: requestData, headers: { 'X-CSRFToken': token } }).then((response) => { // setting requestId and forgeDomain from response requestId = response.body.id; requestForgeDomain = response.body.forge_domain; requestInboundEmailAddress = response.body.inbound_email_address; // logout the user cy.visit(urls.swh_web_homepage()); cy.contains('a', 'logout').click(); }); }); } function genEmailSrc() { return `Message-ID: Date: Tue, 19 Apr 2022 14:00:56 +0200 MIME-Version: 1.0 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Thunderbird/91.8.0 To: ${requestData.forge_contact_email} Cc: ${requestInboundEmailAddress} Reply-To: ${requestInboundEmailAddress} Subject: Software Heritage archival notification for test.example.com Content-Language: en-US From: Test Admin Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit Dear forge administrator, The mission of Software Heritage is to collect, preserve and share all the publicly available source code (see https://www.softwareheritage.org for more information). We just received a request to add the forge hosted at <%= forgeUrl %> to the list of software origins that are archived, and it is our understanding that you are the contact person for this forge. In order to archive the forge contents, we will have to periodically pull the public repositories it contains and clone them into the Software Heritage archive. Please let us know if there are any technical issues to consider before we launch the archival of the public repositories hosted on your infrastructure.(use "Reply all" to ensure our system will process your answer properly) In the absence of an answer to this message, we will start to archive your forge in the coming weeks (only the publicly accessible repositories will be archived) Thank you in advance for your help. Kind regards, The Software Heritage team `; } describe('Test add forge now request dashboard load', function() { - before(function() { - // Create an add-forge-request object in the DB - createDummyRequest(this.Urls); - }); - beforeEach(function() { - const url = this.Urls.add_forge_now_request_dashboard(requestId); - // request dashboard require admin permissions to view - cy.adminLogin(); - cy.intercept(`${this.Urls.api_1_add_forge_request_get(requestId)}**`).as('forgeRequestGet'); - cy.visit(url); - cy.get('.swh-add-forge-now-moderation-item') - .should('have.class', 'active'); + createDummyRequest(this.Urls).then(() => { + const url = this.Urls.add_forge_now_request_dashboard(requestId); + // request dashboard require admin permissions to view + cy.adminLogin(); + cy.intercept(`${this.Urls.api_1_add_forge_request_get(requestId)}**`).as('forgeRequestGet'); + cy.visit(url); + cy.get('.swh-add-forge-now-moderation-item') + .should('have.class', 'active'); + }); }); it('should load add forge request details', function() { cy.wait('@forgeRequestGet'); cy.get('#requestStatus') .should('contain', 'Pending'); cy.get('#requestType') .should('contain', 'bitbucket'); cy.get('#requestURL') .should('contain', 'test.example.com'); cy.get('#requestContactEmail') .should('contain', 'test@example.com'); cy.get('#requestContactName') .should('contain', 'test user'); cy.get('#requestContactEmail') .should('contain', 'test@example.com'); cy.get('#requestContactConsent') .should('contain', 'true'); cy.get('#submitterMessage') .should('contain', 'test comment'); }); it('should show send message link', function() { cy.wait('@forgeRequestGet'); cy.get('#contactForgeAdmin') .should('have.attr', 'emailto') .and('include', 'test@example.com'); cy.get('#contactForgeAdmin') .should('have.attr', 'emailsubject') .and('include', `Software Heritage archival notification for ${requestForgeDomain}`); }); it('should not show any error message', function() { cy.wait('@forgeRequestGet'); cy.get('#fetchError') .should('have.class', 'd-none'); cy.get('#requestDetails') .should('not.have.class', 'd-none'); }); it('should show error message for an api error', function() { // requesting with a non existing request ID const invalidRequestId = requestId + 10; const url = this.Urls.add_forge_now_request_dashboard(invalidRequestId); cy.intercept(`${this.Urls.api_1_add_forge_request_get(invalidRequestId)}**`, {statusCode: 400}).as('forgeAddInvalidRequest'); cy.visit(url); cy.wait('@forgeAddInvalidRequest'); cy.get('#fetchError') .should('not.have.class', 'd-none'); cy.get('#requestDetails') .should('have.class', 'd-none'); }); it('should load add forge request history', function() { cy.wait('@forgeRequestGet'); cy.get('#requestHistory') .children() .should('have.length', 1); cy.get('#requestHistory') .children() .should('contain', 'New status: Pending'); cy.get('#requestHistory') .should('contain', 'From user (SUBMITTER)'); }); it('should load possible next status', function() { cy.wait('@forgeRequestGet'); // 3 possible next status and the comment option cy.get('#decisionOptions') .children() .should('have.length', 4); }); }); function populateAndSubmitForm() { cy.get('#decisionOptions').select('WAITING_FOR_FEEDBACK'); cy.get('#updateComment').type('This is an update comment'); cy.get('#updateRequestForm').submit(); } describe('Test add forge now request update', function() { beforeEach(function() { createDummyRequest(this.Urls).then(() => { this.url = this.Urls.add_forge_now_request_dashboard(requestId); cy.adminLogin(); // intercept GET API on page load cy.intercept(`${this.Urls.api_1_add_forge_request_get(requestId)}**`).as('forgeRequestGet'); // intercept update POST API cy.intercept('POST', `${this.Urls.api_1_add_forge_request_update(requestId)}**`).as('forgeRequestUpdate'); cy.visit(this.url).then(() => { cy.wait('@forgeRequestGet'); }); }); }); it('should submit correct details', function() { populateAndSubmitForm(); // Making sure posting the right data cy.wait('@forgeRequestUpdate').its('request.body') .should('include', 'new_status') .should('include', 'text') .should('include', 'WAITING_FOR_FEEDBACK'); }); it('should show success message', function() { populateAndSubmitForm(); // Making sure showing the success message cy.wait('@forgeRequestUpdate'); cy.get('#userMessage') .should('contain', 'The request status has been updated') .should('not.have.class', 'badge-danger') .should('have.class', 'badge-success'); }); it('should update the dashboard after submit', function() { populateAndSubmitForm(); // Making sure the UI is updated after the submit cy.wait('@forgeRequestGet'); cy.get('#requestStatus') .should('contain', 'Waiting for feedback'); cy.get('#requestHistory') .children() .should('have.length', 2); cy.get('#requestHistory') .children() .should('contain', 'New status: Waiting for feedback'); cy.get('#requestHistory') .children() .should('contain', 'This is an update comment'); cy.get('#requestHistory') .children() .should('contain', 'Status changed to: Waiting for feedback'); cy.get('#decisionOptions') .children() .should('have.length', 2); }); it('should update the dashboard after receiving email', function() { const emailSrc = genEmailSrc(); cy.task('processAddForgeNowInboundEmail', emailSrc); // Refresh page and wait for the async request to complete cy.visit(this.url); cy.wait('@forgeRequestGet'); cy.get('#requestHistory') .children() .should('have.length', 2); cy.get('#historyItem1') .click() .should('contain', 'New status: Waiting for feedback'); cy.get('#historyItemBody1') .find('a') .should('contain', 'Open original message in email client') .should('have.prop', 'href').and('contain', '/message-source/').then(function(href) { cy.request(href).then((response) => { expect(response.headers['content-type']) .to.equal('text/email'); expect(response.headers['content-disposition']) .to.match(/filename="add-forge-now-test.example.com-message\d+.eml"/); expect(response.body) .to.equal(emailSrc); }); }); }); it('should show an error on API failure', function() { cy.intercept('POST', `${this.Urls.api_1_add_forge_request_update(requestId)}**`, {forceNetworkError: true}) .as('updateFailedRequest'); cy.get('#updateComment').type('This is an update comment'); cy.get('#updateRequestForm').submit(); cy.wait('@updateFailedRequest'); cy.get('#userMessage') .should('contain', 'Sorry; Updating the request failed') .should('have.class', 'badge-danger') .should('not.have.class', 'badge-success'); }); }); diff --git a/cypress/e2e/add-forge-now-requests-moderation.cy.js b/cypress/e2e/add-forge-now-requests-moderation.cy.js index 8ead52d6..0cb5ad42 100644 --- a/cypress/e2e/add-forge-now-requests-moderation.cy.js +++ b/cypress/e2e/add-forge-now-requests-moderation.cy.js @@ -1,125 +1,120 @@ /** * Copyright (C) 2022 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ -const defaultRedirect = '/login/'; - -let addForgeModerationUrl; -let listAddForgeRequestsUrl; - function logout() { cy.contains('a', 'logout') .click(); } describe('Test "Add Forge Now" moderation Login/logout', function() { - before(function() { - addForgeModerationUrl = this.Urls.add_forge_now_requests_moderation(); + beforeEach(function() { + this.addForgeModerationUrl = this.Urls.add_forge_now_requests_moderation(); }); it('should redirect to default page', function() { - cy.visit(addForgeModerationUrl) + cy.visit(this.addForgeModerationUrl) .get('input[name="username"]') .type('admin') .get('input[name="password"]') .type('admin') .get('.container form') .submit(); cy.location('pathname') - .should('be.equal', addForgeModerationUrl); + .should('be.equal', this.addForgeModerationUrl); }); it('should redirect to correct page after login', function() { - cy.visit(addForgeModerationUrl) + cy.visit(this.addForgeModerationUrl) .location('pathname') - .should('be.equal', defaultRedirect); + .should('be.equal', this.Urls.login()); cy.adminLogin(); - cy.visit(addForgeModerationUrl) + cy.visit(this.addForgeModerationUrl) .location('pathname') - .should('be.equal', addForgeModerationUrl); + .should('be.equal', this.addForgeModerationUrl); logout(); }); it('should not display moderation link in sidebar when anonymous', function() { - cy.visit(addForgeModerationUrl); - cy.get(`.sidebar a[href="${addForgeModerationUrl}"]`) + cy.visit(this.addForgeModerationUrl); + cy.get(`.sidebar a[href="${this.addForgeModerationUrl}"]`) .should('not.exist'); }); it('should not display moderation link when connected as unprivileged user', function() { cy.userLogin(); - cy.visit(addForgeModerationUrl); + cy.visit(this.addForgeModerationUrl); - cy.get(`.sidebar a[href="${addForgeModerationUrl}"]`) + cy.get(`.sidebar a[href="${this.addForgeModerationUrl}"]`) .should('not.exist'); }); it('should display moderation link in sidebar when connected as privileged user', function() { cy.addForgeModeratorLogin(); - cy.visit(addForgeModerationUrl); + cy.visit(this.addForgeModerationUrl); - cy.get(`.sidebar a[href="${addForgeModerationUrl}"]`) + cy.get(`.sidebar a[href="${this.addForgeModerationUrl}"]`) .should('exist'); }); it('should display moderation link in sidebar when connected as staff member', function() { cy.adminLogin(); - cy.visit(addForgeModerationUrl); + cy.visit(this.addForgeModerationUrl); - cy.get(`.sidebar a[href="${addForgeModerationUrl}"]`) + cy.get(`.sidebar a[href="${this.addForgeModerationUrl}"]`) .should('exist'); }); }); describe('Test "Add Forge Now" moderation listing', function() { - before(function() { - addForgeModerationUrl = this.Urls.add_forge_now_requests_moderation(); - listAddForgeRequestsUrl = this.Urls.add_forge_request_list_datatables(); + beforeEach(function() { + this.addForgeModerationUrl = this.Urls.add_forge_now_requests_moderation(); + this.listAddForgeRequestsUrl = this.Urls.add_forge_request_list_datatables(); }); it('should list add-forge-now requests', function() { - cy.intercept(`${listAddForgeRequestsUrl}**`, {fixture: 'add-forge-now-requests'}).as('listRequests'); + cy.intercept(`${this.listAddForgeRequestsUrl}**`, {fixture: 'add-forge-now-requests'}).as('listRequests'); let expectedRequests; cy.readFile('cypress/fixtures/add-forge-now-requests.json').then((result) => { expectedRequests = result['data']; }); cy.addForgeModeratorLogin(); - cy.visit(addForgeModerationUrl); + cy.visit(this.addForgeModerationUrl); cy.get('.swh-add-forge-now-moderation-item') .should('have.class', 'active'); cy.wait('@listRequests').then((xhr) => { cy.log('response:', xhr.response); cy.log(xhr.response.body); const requests = xhr.response.body.data; cy.log('Requests: ', requests); expect(requests.length).to.equal(expectedRequests.length); cy.get('#swh-add-forge-now-moderation-list').find('tbody > tr').as('rows'); // only 2 entries cy.get('@rows').each((row, idx, collection) => { const request = requests[idx]; const expectedRequest = expectedRequests[idx]; assert.isNotNull(request); assert.isNotNull(expectedRequest); expect(request.id).to.be.equal(expectedRequest['id']); expect(request.status).to.be.equal(expectedRequest['status']); expect(request.submission_date).to.be.equal(expectedRequest['submission_date']); expect(request.forge_type).to.be.equal(expectedRequest['forge_type']); expect(request.forge_url).to.be.equal(expectedRequest['forge_url']); }); }); }); }); diff --git a/cypress/e2e/admin.cy.js b/cypress/e2e/admin.cy.js index f720d5ea..bbe90d01 100644 --- a/cypress/e2e/admin.cy.js +++ b/cypress/e2e/admin.cy.js @@ -1,300 +1,296 @@ /** * Copyright (C) 2019-2022 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ const $ = Cypress.$; -const defaultRedirect = '/'; - -let url; - function logout() { cy.contains('a', 'logout') .click(); } describe('Test Admin Login/logout', function() { - before(function() { - url = this.Urls.login(); + beforeEach(function() { + this.url = this.Urls.login(); }); it('should redirect to default page', function() { - cy.visit(url) + cy.visit(this.url) .get('input[name="username"]') .type('admin') .get('input[name="password"]') .type('admin') .get('.container form') .submit(); cy.location('pathname') - .should('be.equal', defaultRedirect); + .should('be.equal', this.Urls.swh_web_homepage()); logout(); }); it('should display admin-origin-save and deposit in sidebar', function() { cy.adminLogin(); - cy.visit(url); + cy.visit(this.url); cy.get(`.sidebar a[href="${this.Urls.admin_origin_save_requests()}"]`) .should('be.visible'); cy.get(`.sidebar a[href="${this.Urls.admin_deposit()}"]`) .should('be.visible'); logout(); }); it('should display username on top-right', function() { cy.adminLogin(); - cy.visit(url); + cy.visit(this.url); cy.get('.swh-position-right') .should('contain', 'admin'); logout(); }); it('should get info about a user logged in from javascript', function() { cy.window().then(win => { expect(win.swh.webapp.isUserLoggedIn()).to.be.false; }); cy.adminLogin(); - cy.visit(url); + cy.visit(this.url); cy.window().then(win => { expect(win.swh.webapp.isUserLoggedIn()).to.be.true; }); logout(); - cy.visit(url); + cy.visit(this.url); cy.window().then(win => { expect(win.swh.webapp.isUserLoggedIn()).to.be.false; }); }); it('should prevent unauthorized access after logout', function() { cy.visit(this.Urls.admin_origin_save_requests()) .location('pathname') .should('be.equal', '/login/'); cy.visit(this.Urls.admin_deposit()) .location('pathname') .should('be.equal', '/login/'); }); it('should redirect to correct page after login', function() { // mock calls to deposit list api to avoid possible errors // while running the test cy.intercept(`${this.Urls.admin_deposit_list()}**`, { body: { data: [], recordsTotal: 0, recordsFiltered: 0, draw: 1 } }); cy.visit(this.Urls.admin_deposit()) .location('search') .should('contain', `next=${this.Urls.admin_deposit()}`); cy.adminLogin(); cy.visit(this.Urls.admin_deposit()); cy.location('pathname') .should('be.equal', this.Urls.admin_deposit()); logout(); }); }); const existingRowToSelect = 'https://bitbucket.org/'; const originUrlListTestData = [ { listType: 'authorized', originToAdd: 'git://git.archlinux.org/', originToRemove: 'https://github.com/' }, { listType: 'unauthorized', originToAdd: 'https://random.org', originToRemove: 'https://gitlab.com' } ]; const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1); describe('Test Admin Origin Save Urls Filtering', function() { beforeEach(function() { cy.adminLogin(); cy.visit(this.Urls.admin_origin_save_requests()); cy.contains('a', 'Origin urls filtering') .click() .wait(500); }); it(`should select or unselect a table row by clicking on it`, function() { cy.contains(`#swh-authorized-origin-urls tr`, existingRowToSelect) .click() .should('have.class', 'selected') .click() .should('not.have.class', 'selected'); }); originUrlListTestData.forEach(testData => { it(`should add a new origin url prefix in the ${testData.listType} list`, function() { const tabName = capitalize(testData.listType) + ' urls'; cy.contains('a', tabName) .click() .wait(500); cy.get(`#swh-${testData.listType}-origin-urls tr`).each(elt => { if ($(elt).text() === testData.originToAdd) { cy.get(elt).click(); cy.get(`#swh-remove-${testData.listType}-origin-url`).click(); } }); cy.get(`#swh-${testData.listType}-url-prefix`) .type(testData.originToAdd); cy.get(`#swh-add-${testData.listType}-origin-url`) .click(); cy.contains(`#swh-${testData.listType}-origin-urls tr`, testData.originToAdd) .should('be.visible'); cy.contains('.alert-success', `The origin url prefix has been successfully added in the ${testData.listType} list.`) .should('be.visible'); cy.get(`#swh-add-${testData.listType}-origin-url`) .click(); cy.contains('.alert-warning', `The provided origin url prefix is already registered in the ${testData.listType} list.`) .should('be.visible'); }); it(`should remove an origin url prefix from the ${testData.listType} list`, function() { const tabName = capitalize(testData.listType) + ' urls'; cy.contains('a', tabName) .click(); let originUrlMissing = true; cy.get(`#swh-${testData.listType}-origin-urls tr`).each(elt => { if ($(elt).text() === testData.originToRemove) { originUrlMissing = false; } }); if (originUrlMissing) { cy.get(`#swh-${testData.listType}-url-prefix`) .type(testData.originToRemove); cy.get(`#swh-add-${testData.listType}-origin-url`) .click(); cy.get('.alert-dismissible button').click(); } cy.contains(`#swh-${testData.listType}-origin-urls tr`, testData.originToRemove) .click(); cy.get(`#swh-remove-${testData.listType}-origin-url`).click(); cy.contains(`#swh-${testData.listType}-origin-urls tr`, testData.originToRemove) .should('not.exist'); }); }); }); describe('Test Admin Origin Save', function() { it(`should reject a save code now request with note`, function() { const originUrl = `https://example.org/${Date.now()}`; const rejectionNote = 'The provided URL does not target a git repository.'; const rejectUrl = this.Urls.admin_origin_save_request_reject('git', originUrl); cy.intercept('POST', rejectUrl).as('rejectSaveRequest'); // anonymous user creates a request put in pending state cy.visit(this.Urls.origin_save()); cy.get('#swh-input-origin-url') .type(originUrl); cy.get('#swh-input-origin-save-submit') .click(); // admin user logs in and visits save code now admin page cy.adminLogin(); cy.visit(this.Urls.admin_origin_save_requests()); // admin rejects the save request and adds a rejection note cy.contains('#swh-origin-save-pending-requests', originUrl) .click(); cy.get('#swh-reject-save-origin-request') .click(); cy.get('#swh-rejection-text') .then(textarea => { textarea.val(rejectionNote); }); cy.get('#swh-rejection-submit') .click(); cy.get('#swh-web-modal-confirm-ok-btn') .click(); cy.wait('@rejectSaveRequest'); // checks rejection note has been saved to swh-web database cy.request(this.Urls.api_1_save_origin('git', originUrl)) .then(response => { expect(response.body[0]['note']).to.equal(rejectionNote); }); // check rejection note is displayed by clicking on the info icon // in requests table from public save code now page cy.visit(this.Urls.origin_save()); cy.get('#swh-origin-save-requests-list-tab') .click(); cy.contains('#swh-origin-save-requests tr', originUrl); cy.get('.swh-save-request-info') .eq(0) .click(); cy.get('.popover-body') .should('have.text', rejectionNote); // remove rejected request from swh-web database to avoid side effects // in tests located in origin-save.spec.js cy.visit(this.Urls.admin_origin_save_requests()); cy.get('#swh-save-requests-rejected-tab') .click(); cy.contains('#swh-origin-save-rejected-requests', originUrl) .click(); cy.get('#swh-remove-rejected-save-origin-request') .click(); cy.get('#swh-web-modal-confirm-ok-btn') .click(); }); });