diff --git a/cypress/e2e/add-forge-now-request-create.cy.js b/cypress/e2e/add-forge-now-request-create.cy.js index 60edb88f..ba72ccd8 100644 --- a/cypress/e2e/add-forge-now-request-create.cy.js +++ b/cypress/e2e/add-forge-now-request-create.cy.js @@ -1,283 +1,293 @@ /** * 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 */ function populateForm(type, url, contact, email, consent, comment) { cy.get('#swh-input-forge-type').select(type); - cy.get('#swh-input-forge-url').clear().type(url); + if (url) { + cy.get('#swh-input-forge-url').clear().type(url); + } cy.get('#swh-input-forge-contact-name').clear().type(contact); cy.get('#swh-input-forge-contact-email').clear().type(email); if (comment) { cy.get('#swh-input-forge-comment').clear().type(comment); } cy.get('#swh-input-consent-check').click({force: consent === 'on'}); } +function submitForm() { + cy.get('#requestCreateForm input[type=submit]').click(); + cy.get('#requestCreateForm').then($form => { + if ($form[0].checkValidity()) { + cy.wait('@addForgeRequestCreate'); + } + }); +} + +function initTest(testEnv) { + testEnv.addForgeNowUrl = testEnv.Urls.forge_add_create(); + testEnv.addForgeNowRequestCreateUrl = testEnv.Urls.api_1_add_forge_request_create(); + testEnv.listAddForgeRequestsUrl = testEnv.Urls.add_forge_request_list_datatables(); + cy.intercept('POST', testEnv.addForgeNowRequestCreateUrl + '**') + .as('addForgeRequestCreate'); + cy.intercept(testEnv.listAddForgeRequestsUrl + '**') + .as('addForgeRequestsList'); +} + describe('Browse requests list tests', function() { beforeEach(function() { - this.addForgeNowUrl = this.Urls.forge_add_create(); - this.listAddForgeRequestsUrl = this.Urls.add_forge_request_list_datatables(); + initTest(this); }); it('should not show user requests filter checkbox for anonymous users', function() { cy.visit(this.addForgeNowUrl); cy.get('#swh-add-forge-requests-list-tab').click(); cy.get('#swh-add-forge-user-filter').should('not.exist'); }); it('should show user requests filter checkbox for authenticated users', function() { cy.userLogin(); cy.visit(this.addForgeNowUrl); cy.get('#swh-add-forge-requests-list-tab').click(); cy.get('#swh-add-forge-user-filter').should('exist').should('be.checked'); }); it('should only display user requests when filter is activated', function() { // Clean up previous state cy.task('db:add_forge_now:delete'); // 'user2' logs in and create requests cy.user2Login(); cy.visit(this.addForgeNowUrl); // create requests for the user 'user' populateForm('gitlab', 'https://gitlab.org', 'admin', 'admin@example.org', 'on', ''); - cy.get('#requestCreateForm').submit(); + submitForm(); // user requests filter checkbox should be in the DOM cy.get('#swh-add-forge-requests-list-tab').click(); cy.get('#swh-add-forge-user-filter').should('exist').should('be.checked'); // check unfiltered user requests cy.get('tbody tr').then(rows => { expect(rows.length).to.eq(1); }); // user1 logout cy.contains('a', 'logout').click(); // user logs in cy.userLogin(); cy.visit(this.addForgeNowUrl); populateForm('gitea', 'https://gitea.org', 'admin', 'admin@example.org', 'on', ''); - cy.get('#requestCreateForm').submit(); + submitForm(); populateForm('cgit', 'https://cgit.org', 'admin', 'admin@example.org', 'on', ''); - cy.get('#requestCreateForm').submit(); - - cy.intercept(this.Urls.add_forge_request_list_datatables() + '**') - .as('addForgeRequestsList'); + submitForm(); // user requests filter checkbox should be in the DOM cy.get('#swh-add-forge-requests-list-tab').click(); cy.get('#swh-add-forge-user-filter').should('exist').should('be.checked'); cy.wait('@addForgeRequestsList'); // check unfiltered user requests cy.get('tbody tr').then(rows => { expect(rows.length).to.eq(2); }); cy.get('#swh-add-forge-user-filter') .uncheck({force: true}); cy.wait('@addForgeRequestsList'); // Users now sees everything cy.get('tbody tr').then(rows => { expect(rows.length).to.eq(2 + 1); }); }); }); describe('Test add-forge-request creation', function() { beforeEach(function() { - this.addForgeNowUrl = this.Urls.forge_add_create(); + initTest(this); }); it('should show all the tabs for every user', function() { cy.visit(this.addForgeNowUrl); cy.get('#swh-add-forge-tab') .should('have.class', 'nav-link'); cy.get('#swh-add-forge-requests-list-tab') .should('have.class', 'nav-link'); cy.get('#swh-add-forge-requests-help-tab') .should('have.class', 'nav-link'); }); it('should show create forge tab by default', function() { cy.visit(this.addForgeNowUrl); cy.get('#swh-add-forge-tab') .should('have.class', 'active'); cy.get('#swh-add-forge-requests-list-tab') .should('not.have.class', 'active'); }); it('should show login link for anonymous user', function() { cy.visit(this.addForgeNowUrl); cy.get('#loginLink') .should('be.visible') .should('contain', 'log in'); }); it('should bring back after login', function() { cy.visit(this.addForgeNowUrl); cy.get('#loginLink') .should('have.attr', 'href') .and('include', `${this.Urls.login()}?next=${this.Urls.forge_add_create()}`); }); it('should change tabs on click', function() { cy.visit(this.addForgeNowUrl); cy.get('#swh-add-forge-requests-list-tab').click(); cy.get('#swh-add-forge-tab') .should('not.have.class', 'active'); cy.get('#swh-add-forge-requests-list-tab') .should('have.class', 'active'); cy.get('#swh-add-forge-requests-help-tab') .should('not.have.class', 'active'); cy.url() .should('include', `${this.Urls.forge_add_list()}`); cy.get('.swh-add-forge-now-item') .should('have.class', 'active'); cy.get('#swh-add-forge-requests-help-tab').click(); cy.get('#swh-add-forge-tab') .should('not.have.class', 'active'); cy.get('#swh-add-forge-requests-list-tab') .should('not.have.class', 'active'); cy.get('#swh-add-forge-requests-help-tab') .should('have.class', 'active'); cy.url() .should('include', `${this.Urls.forge_add_help()}`); cy.get('.swh-add-forge-now-item') .should('have.class', 'active'); cy.get('#swh-add-forge-tab').click(); cy.get('#swh-add-forge-tab') .should('have.class', 'active'); cy.get('#swh-add-forge-requests-list-tab') .should('not.have.class', 'active'); cy.get('#swh-add-forge-requests-help-tab') .should('not.have.class', 'active'); cy.url() .should('include', `${this.Urls.forge_add_create()}`); cy.get('.swh-add-forge-now-item') .should('have.class', 'active'); }); it('should show create form elements to authenticated user', function() { cy.userLogin(); cy.visit(this.addForgeNowUrl); cy.get('#swh-input-forge-type') .should('be.visible'); cy.get('#swh-input-forge-url') .should('be.visible'); cy.get('#swh-input-forge-contact-name') .should('be.visible'); cy.get('#swh-input-consent-check') .should('be.visible'); cy.get('#swh-input-forge-comment') .should('be.visible'); cy.get('#swh-input-form-submit') .should('be.visible'); }); it('should show browse requests table for every user', function() { // testing only for anonymous cy.visit(this.addForgeNowUrl); cy.get('#swh-add-forge-requests-list-tab').click(); cy.get('#add-forge-request-browse') .should('be.visible'); cy.get('#loginLink') .should('not.exist'); }); it('should update browse list on successful submission', function() { cy.userLogin(); cy.visit(this.addForgeNowUrl); populateForm('bitbucket', 'https://gitlab.com', 'test', 'test@example.com', 'on', 'test comment'); - cy.get('#requestCreateForm').submit(); + submitForm(); cy.visit(this.addForgeNowUrl); cy.get('#swh-add-forge-requests-list-tab').click(); + + cy.wait('@addForgeRequestsList'); + cy.get('#add-forge-request-browse') .should('be.visible') .should('contain', 'gitlab.com'); cy.get('#add-forge-request-browse') .should('be.visible') .should('contain', 'Pending'); }); it('should show error message on conflict', function() { cy.userLogin(); cy.visit(this.addForgeNowUrl); populateForm('bitbucket', 'https://gitlab.com', 'test', 'test@example.com', 'on', 'test comment'); - cy.get('#requestCreateForm').submit(); + submitForm(); - cy.get('#requestCreateForm').submit(); // Submitting the same data again + submitForm(); // Submitting the same data again cy.get('#userMessage') .should('have.class', 'badge-danger') .should('contain', 'already exists'); }); it('should show error message', function() { cy.userLogin(); - cy.intercept('POST', `${this.Urls.api_1_add_forge_request_create()}**`, - { - body: { - 'exception': 'BadInputExc', - 'reason': '{"add-forge-comment": ["This field is required"]}' - }, - statusCode: 400 - }).as('errorRequest'); - cy.visit(this.addForgeNowUrl); populateForm( - 'bitbucket', 'https://gitlab.com', 'test', 'test@example.com', 'off', 'comment' + 'bitbucket', '', 'test', 'test@example.com', 'off', 'comment' + ); + submitForm(); + + cy.get('#requestCreateForm').then( + $form => expect($form[0].checkValidity()).to.be.false ); - cy.get('#requestCreateForm').submit(); - cy.wait('@errorRequest').then((xhr) => { - cy.get('#userMessage') - .should('have.class', 'badge-danger') - .should('contain', 'field is required'); - }); }); - it('should bot validate form when forge URL is invalid', function() { + it('should not validate form when forge URL is invalid', function() { cy.userLogin(); cy.visit(this.addForgeNowUrl); populateForm('bitbucket', 'bitbucket.org', 'test', 'test@example.com', 'on', 'test comment'); - cy.get('#requestCreateForm').submit(); + submitForm(); cy.get('#swh-input-forge-url') .then(input => { assert.isFalse(input[0].checkValidity()); }); }); }); diff --git a/cypress/e2e/add-forge-now-request-dashboard.cy.js b/cypress/e2e/add-forge-now-request-dashboard.cy.js index 6c62d231..65609d32 100644 --- a/cypress/e2e/add-forge-now-request-dashboard.cy.js +++ b/cypress/e2e/add-forge-now-request-dashboard.cy.js @@ -1,312 +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() { beforeEach(function() { 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(); + cy.get('#updateRequestForm button[type=submit]').click(); } 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.get('#updateRequestForm button[type=submit]').click(); 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 0cb5ad42..c1ced073 100644 --- a/cypress/e2e/add-forge-now-requests-moderation.cy.js +++ b/cypress/e2e/add-forge-now-requests-moderation.cy.js @@ -1,120 +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 */ function logout() { cy.contains('a', 'logout') .click(); } describe('Test "Add Forge Now" moderation Login/logout', function() { beforeEach(function() { this.addForgeModerationUrl = this.Urls.add_forge_now_requests_moderation(); }); it('should redirect to default page', function() { cy.visit(this.addForgeModerationUrl) .get('input[name="username"]') .type('admin') .get('input[name="password"]') .type('admin') - .get('.container form') - .submit(); + .get('.container form button[type=submit]') + .click(); cy.location('pathname') .should('be.equal', this.addForgeModerationUrl); }); it('should redirect to correct page after login', function() { cy.visit(this.addForgeModerationUrl) .location('pathname') .should('be.equal', this.Urls.login()); cy.adminLogin(); cy.visit(this.addForgeModerationUrl) .location('pathname') .should('be.equal', this.addForgeModerationUrl); logout(); }); it('should not display moderation link in sidebar when anonymous', function() { 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(this.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(this.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(this.addForgeModerationUrl); cy.get(`.sidebar a[href="${this.addForgeModerationUrl}"]`) .should('exist'); }); }); describe('Test "Add Forge Now" moderation listing', function() { 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(`${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(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 bbe90d01..4d45bbc5 100644 --- a/cypress/e2e/admin.cy.js +++ b/cypress/e2e/admin.cy.js @@ -1,296 +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.$; function logout() { cy.contains('a', 'logout') .click(); } describe('Test Admin Login/logout', function() { beforeEach(function() { this.url = this.Urls.login(); }); it('should redirect to default page', function() { cy.visit(this.url) .get('input[name="username"]') .type('admin') .get('input[name="password"]') .type('admin') - .get('.container form') - .submit(); + .get('.container form button[type=submit]') + .click(); cy.location('pathname') .should('be.equal', this.Urls.swh_web_homepage()); logout(); }); it('should display admin-origin-save and deposit in sidebar', function() { cy.adminLogin(); 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(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(this.url); cy.window().then(win => { expect(win.swh.webapp.isUserLoggedIn()).to.be.true; }); logout(); 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(); }); }); diff --git a/cypress/e2e/layout.cy.js b/cypress/e2e/layout.cy.js index 3c02e548..8892b69b 100644 --- a/cypress/e2e/layout.cy.js +++ b/cypress/e2e/layout.cy.js @@ -1,224 +1,224 @@ /** * Copyright (C) 2019-2021 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 url = '/browse/help/'; const statusUrl = 'https://status.softwareheritage.org'; describe('Test top-bar', function() { beforeEach(function() { cy.clearLocalStorage(); cy.visit(url); }); it('should should contain all navigation links', function() { cy.get('.swh-top-bar a') .should('have.length.of.at.least', 4) .and('be.visible') .and('have.attr', 'href'); }); it('should show donate button on lg screen', function() { cy.get('.swh-donate-link') .should('be.visible'); }); it('should hide donate button on sm screen', function() { cy.viewport(600, 800); cy.get('.swh-donate-link') .should('not.be.visible'); }); it('should hide full width switch on small screens', function() { cy.viewport(360, 740); cy.get('#swh-full-width-switch-container') .should('not.be.visible'); cy.viewport(600, 800); cy.get('#swh-full-width-switch-container') .should('not.be.visible'); cy.viewport(800, 600); cy.get('#swh-full-width-switch-container') .should('not.be.visible'); }); it('should show full width switch on large screens', function() { cy.viewport(1024, 768); cy.get('#swh-full-width-switch-container') .should('be.visible'); cy.viewport(1920, 1080); cy.get('#swh-full-width-switch-container') .should('be.visible'); }); it('should change container width when toggling Full width switch', function() { cy.get('#swh-web-content') .should('have.class', 'container') .should('not.have.class', 'container-fluid'); cy.should(() => { expect(JSON.parse(localStorage.getItem('swh-web-full-width'))).to.be.null; }); cy.get('#swh-full-width-switch') .click({force: true}); cy.get('#swh-web-content') .should('not.have.class', 'container') .should('have.class', 'container-fluid'); cy.should(() => { expect(JSON.parse(localStorage.getItem('swh-web-full-width'))).to.be.true; }); cy.get('#swh-full-width-switch') .click({force: true}); cy.get('#swh-web-content') .should('have.class', 'container') .should('not.have.class', 'container-fluid'); cy.should(() => { expect(JSON.parse(localStorage.getItem('swh-web-full-width'))).to.be.false; }); }); it('should restore container width when loading page again', function() { cy.visit(url) .get('#swh-web-content') .should('have.class', 'container') .should('not.have.class', 'container-fluid'); cy.get('#swh-full-width-switch') .click({force: true}); cy.visit(url) .get('#swh-web-content') .should('not.have.class', 'container') .should('have.class', 'container-fluid'); cy.get('#swh-full-width-switch') .click({force: true}); cy.visit(url) .get('#swh-web-content') .should('have.class', 'container') .should('not.have.class', 'container-fluid'); }); function genStatusResponse(status, statusCode) { return { 'result': { 'status': [ { 'id': '5f7c4c567f50b304c1e7bd5f', 'name': 'Save Code Now', 'updated': '2020-11-30T13:51:21.151Z', 'status': 'Operational', 'status_code': 100 }, { 'id': '5f7c4c6f8338bc04b7f476fe', 'name': 'Source Code Crawlers', 'updated': '2020-11-30T13:51:21.151Z', 'status': status, 'status_code': statusCode } ] } }; } it('should display swh status widget when data are available', function() { const statusTestData = [ { status: 'Operational', statusCode: 100, color: 'green' }, { status: 'Scheduled Maintenance', statusCode: 200, color: 'blue' }, { status: 'Degraded Performance', statusCode: 300, color: 'yellow' }, { status: 'Partial Service Disruption', statusCode: 400, color: 'yellow' }, { status: 'Service Disruption', statusCode: 500, color: 'red' }, { status: 'Security Event', statusCode: 600, color: 'red' } ]; for (let i = 0; i < statusTestData.length; ++i) { cy.intercept(`${statusUrl}/**`, { body: genStatusResponse(statusTestData[i].status, statusTestData[i].statusCode) }).as(`getSwhStatusData`); cy.visit(url); cy.wait(`@getSwhStatusData`); cy.get('.swh-current-status-indicator').should('have.class', statusTestData[i].color); cy.get('#swh-current-status-description').should('have.text', statusTestData[i].status); } }); it('should not display swh status widget when data are not available', function() { cy.intercept(`${statusUrl}/**`, { body: {} }).as('getSwhStatusData'); cy.visit(url); cy.wait('@getSwhStatusData'); cy.get('.swh-current-status').should('not.exist'); }); }); describe('Test navbar', function() { it('should redirect to search page when submitting search form in navbar', function() { const keyword = 'python'; cy.get('#swh-origins-search-top-input') .type(keyword); - cy.get('.swh-search-navbar') - .submit(); + cy.get('#swh-origins-search-top button[type=submit]') + .click({force: true}); cy.url() .should('include', `${this.Urls.browse_search()}?q=${keyword}`); }); }); describe('Test footer', function() { beforeEach(function() { cy.visit(url); }); it('should be visible', function() { cy.get('footer') .should('be.visible'); }); it('should have correct copyright years', function() { const currentYear = new Date().getFullYear(); const copyrightText = '(C) 2015–' + currentYear.toString(); cy.get('footer') .should('contain', copyrightText); }); it('should contain link to Web API', function() { cy.get('footer') .get(`a[href="${this.Urls.api_1_homepage()}"]`) .should('contain', 'Web API'); }); }); diff --git a/cypress/e2e/origin-save.cy.js b/cypress/e2e/origin-save.cy.js index 5728ca5e..df34ff28 100644 --- a/cypress/e2e/origin-save.cy.js +++ b/cypress/e2e/origin-save.cy.js @@ -1,882 +1,882 @@ /** * 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 */ let url; const origin = { type: 'git', url: 'https://git.example.org/user/repo' }; const $ = Cypress.$; const saveCodeMsg = { 'success': 'The "save code now" request has been accepted and will be processed as soon as possible.', 'warning': 'The "save code now" request has been put in pending state and may be accepted for processing after manual review.', 'rejected': 'The "save code now" request has been rejected because the provided origin url is blacklisted.', 'rateLimit': 'The rate limit for "save code now" requests has been reached. Please try again later.', 'not-found': 'The provided url does not exist', 'unknownError': 'An unexpected error happened when submitting the "save code now request', 'csrfError': 'CSRF Failed: Referrer checking failed - no Referrer.' }; const anonymousVisitTypes = ['bzr', 'cvs', 'git', 'hg', 'svn']; const allVisitTypes = ['archives', 'bzr', 'cvs', 'git', 'hg', 'svn']; function makeOriginSaveRequest(originType, originUrl) { cy.get('#swh-input-origin-url') .type(originUrl) .get('#swh-input-visit-type') .select(originType) - .get('#swh-save-origin-form') - .submit(); + .get('#swh-save-origin-form button[type=submit]') + .click(); } function checkAlertVisible(alertType, msg) { cy.get('#swh-origin-save-request-status') .should('be.visible') .find(`.alert-${alertType}`) .should('be.visible') .and('contain', msg); } // Stub requests to save an origin function stubSaveRequest({ requestUrl, visitType = 'git', saveRequestStatus, originUrl, saveTaskStatus, responseStatus = 200, // For error code with the error message in the 'reason' key response errorMessage = '', saveRequestDate = new Date(), visitDate = new Date(), visitStatus = null } = {}) { let response; if (responseStatus !== 200 && errorMessage) { response = { 'reason': errorMessage }; } else { response = genOriginSaveResponse({visitType: visitType, saveRequestStatus: saveRequestStatus, originUrl: originUrl, saveRequestDate: saveRequestDate, saveTaskStatus: saveTaskStatus, visitDate: visitDate, visitStatus: visitStatus }); } cy.intercept('POST', requestUrl, {body: response, statusCode: responseStatus}) .as('saveRequest'); } // Mocks API response : /save/(:visit_type)/(:origin_url) // visit_type : {'git', 'hg', 'svn', ...} function genOriginSaveResponse({ visitType = 'git', saveRequestStatus, originUrl, saveRequestDate = new Date(), saveTaskStatus, visitDate = new Date(), visitStatus } = {}) { return { 'visit_type': visitType, 'save_request_status': saveRequestStatus, 'origin_url': originUrl, 'id': 1, 'save_request_date': saveRequestDate ? saveRequestDate.toISOString() : null, 'save_task_status': saveTaskStatus, 'visit_date': visitDate ? visitDate.toISOString() : null, 'visit_status': visitStatus }; }; function loadSaveRequestsListPage() { // click on tab to visit requests list page cy.get('#swh-origin-save-requests-list-tab').click(); // two XHR requests are sent by datatables when initializing requests table cy.wait(['@saveRequestsList', '@saveRequestsList']); // ensure datatable got rendered cy.wait(100); } describe('Origin Save Tests', function() { before(function() { url = this.Urls.origin_save(); this.originSaveUrl = this.Urls.api_1_save_origin(origin.type, origin.url); }); beforeEach(function() { cy.fixture('origin-save').as('originSaveJSON'); cy.fixture('save-task-info').as('saveTaskInfoJSON'); cy.visit(url); }); it('should format appropriately values depending on their type', function() { const inputValues = [ // null values stay null {type: 'json', value: null, expectedValue: null}, {type: 'date', value: null, expectedValue: null}, {type: 'raw', value: null, expectedValue: null}, {type: 'duration', value: null, expectedValue: null}, // non null values formatted depending on their type {type: 'json', value: '{}', expectedValue: '"{}"'}, {type: 'date', value: '04/04/2021 01:00:00', expectedValue: '4/4/2021, 1:00:00 AM'}, {type: 'raw', value: 'value-for-identity', expectedValue: 'value-for-identity'}, {type: 'duration', value: '10', expectedValue: '10 seconds'}, {type: 'duration', value: 100, expectedValue: '100 seconds'} ]; cy.window().then(win => { inputValues.forEach(function(input, index, array) { const actualValue = win.swh.save_code_now.formatValuePerType(input.type, input.value); assert.equal(actualValue, input.expectedValue); }); }); }); it('should display accepted message when accepted', function() { stubSaveRequest({requestUrl: this.originSaveUrl, saveRequestStatus: 'accepted', originUrl: origin.url, saveTaskStatus: 'not yet scheduled'}); makeOriginSaveRequest(origin.type, origin.url); cy.wait('@saveRequest').then(() => { checkAlertVisible('success', saveCodeMsg['success']); }); }); it('should validate gitlab subproject url', function() { const gitlabSubProjectUrl = 'https://gitlab.com/user/project/sub/'; const originSaveUrl = this.Urls.api_1_save_origin('git', gitlabSubProjectUrl); stubSaveRequest({requestUrl: originSaveUrl, saveRequestStatus: 'accepted', originurl: gitlabSubProjectUrl, saveTaskStatus: 'not yet scheduled'}); makeOriginSaveRequest('git', gitlabSubProjectUrl); cy.wait('@saveRequest').then(() => { checkAlertVisible('success', saveCodeMsg['success']); }); }); it('should validate project url with _ in username', function() { const gitlabSubProjectUrl = 'https://gitlab.com/user_name/project.git'; const originSaveUrl = this.Urls.api_1_save_origin('git', gitlabSubProjectUrl); stubSaveRequest({requestUrl: originSaveUrl, saveRequestStatus: 'accepted', originurl: gitlabSubProjectUrl, saveTaskStatus: 'not yet scheduled'}); makeOriginSaveRequest('git', gitlabSubProjectUrl); cy.wait('@saveRequest').then(() => { checkAlertVisible('success', saveCodeMsg['success']); }); }); it('should validate git repo url starting with https://git.code.sf.net/u/', function() { const sfUserGirProjectUrl = 'https://git.code.sf.net/u/username/project.git'; const originSaveUrl = this.Urls.api_1_save_origin('git', sfUserGirProjectUrl); stubSaveRequest({requestUrl: originSaveUrl, saveRequestStatus: 'accepted', originurl: sfUserGirProjectUrl, saveTaskStatus: 'not yet scheduled'}); makeOriginSaveRequest('git', sfUserGirProjectUrl); cy.wait('@saveRequest').then(() => { checkAlertVisible('success', saveCodeMsg['success']); }); }); it('should display warning message when pending', function() { stubSaveRequest({requestUrl: this.originSaveUrl, saveRequestStatus: 'pending', originUrl: origin.url, saveTaskStatus: 'not created'}); makeOriginSaveRequest(origin.type, origin.url); cy.wait('@saveRequest').then(() => { checkAlertVisible('warning', saveCodeMsg['warning']); }); }); it('should show error when the origin does not exist (status: 400)', function() { stubSaveRequest({requestUrl: this.originSaveUrl, originUrl: origin.url, responseStatus: 400, errorMessage: saveCodeMsg['not-found']}); makeOriginSaveRequest(origin.type, origin.url); cy.wait('@saveRequest').then(() => { checkAlertVisible('danger', saveCodeMsg['not-found']); }); }); it('should show error when csrf validation failed (status: 403)', function() { stubSaveRequest({requestUrl: this.originSaveUrl, saveRequestStatus: 'rejected', originUrl: origin.url, saveTaskStatus: 'not created', responseStatus: 403, errorMessage: saveCodeMsg['csrfError']}); makeOriginSaveRequest(origin.type, origin.url); cy.wait('@saveRequest').then(() => { checkAlertVisible('danger', saveCodeMsg['csrfError']); }); }); it('should show error when origin is rejected (status: 403)', function() { stubSaveRequest({requestUrl: this.originSaveUrl, saveRequestStatus: 'rejected', originUrl: origin.url, saveTaskStatus: 'not created', responseStatus: 403, errorMessage: saveCodeMsg['rejected']}); makeOriginSaveRequest(origin.type, origin.url); cy.wait('@saveRequest').then(() => { checkAlertVisible('danger', saveCodeMsg['rejected']); }); }); it('should show error when rate limited (status: 429)', function() { stubSaveRequest({requestUrl: this.originSaveUrl, saveRequestStatus: 'Request was throttled. Expected available in 60 seconds.', originUrl: origin.url, saveTaskStatus: 'not created', responseStatus: 429}); makeOriginSaveRequest(origin.type, origin.url); cy.wait('@saveRequest').then(() => { checkAlertVisible('danger', saveCodeMsg['rateLimit']); }); }); it('should show error when unknown error occurs (status other than 200, 403, 429)', function() { stubSaveRequest({requestUrl: this.originSaveUrl, saveRequestStatus: 'Error', originUrl: origin.url, saveTaskStatus: 'not created', responseStatus: 406}); makeOriginSaveRequest(origin.type, origin.url); cy.wait('@saveRequest').then(() => { checkAlertVisible('danger', saveCodeMsg['unknownError']); }); }); it('should display origin save info in the requests table', function() { cy.intercept('/save/requests/list/**', {fixture: 'origin-save'}) .as('saveRequestsList'); loadSaveRequestsListPage(); cy.get('tbody tr').then(rows => { let i = 0; for (const row of rows) { const cells = row.cells; const requestDateStr = new Date(this.originSaveJSON.data[i].save_request_date).toLocaleString(); const saveStatus = this.originSaveJSON.data[i].save_task_status; assert.equal($(cells[0]).text(), requestDateStr); assert.equal($(cells[1]).text(), this.originSaveJSON.data[i].visit_type); let html = ''; if (saveStatus === 'succeeded') { let browseOriginUrl = `${this.Urls.browse_origin()}?origin_url=${encodeURIComponent(this.originSaveJSON.data[i].origin_url)}`; browseOriginUrl += `&timestamp=${encodeURIComponent(this.originSaveJSON.data[i].visit_date)}`; html += `${this.originSaveJSON.data[i].origin_url}`; } else { html += this.originSaveJSON.data[i].origin_url; } html += ` `; html += ''; assert.equal($(cells[2]).html(), html); assert.equal($(cells[3]).text(), this.originSaveJSON.data[i].save_request_status); assert.equal($(cells[4]).text(), saveStatus); ++i; } }); }); it('should not add timestamp to the browse origin URL is no visit date has been found', function() { const originUrl = 'https://git.example.org/example.git'; const saveRequestData = genOriginSaveResponse({ saveRequestStatus: 'accepted', originUrl: originUrl, saveTaskStatus: 'succeeded', visitDate: null, visitStatus: 'full' }); const saveRequestsListData = { 'recordsTotal': 1, 'draw': 2, 'recordsFiltered': 1, 'data': [saveRequestData] }; cy.intercept('/save/requests/list/**', {body: saveRequestsListData}) .as('saveRequestsList'); loadSaveRequestsListPage(); cy.get('tbody tr').then(rows => { const firstRowCells = rows[0].cells; const browseOriginUrl = `${this.Urls.browse_origin()}?origin_url=${encodeURIComponent(originUrl)}`; const browseOriginLink = `${originUrl}`; expect($(firstRowCells[2]).html()).to.have.string(browseOriginLink); }); }); it('should not add link to browse an origin when there is no visit status', function() { const originUrl = 'https://git.example.org/example.git'; const saveRequestData = genOriginSaveResponse({ saveRequestStatus: 'accepted', originUrl: originUrl, saveTaskStatus: 'succeeded', visitDate: null, visitStatus: null }); const saveRequestsListData = { 'recordsTotal': 1, 'draw': 2, 'recordsFiltered': 1, 'data': [saveRequestData] }; cy.intercept('/save/requests/list/**', {body: saveRequestsListData}) .as('saveRequestsList'); loadSaveRequestsListPage(); cy.get('tbody tr').then(rows => { const firstRowCells = rows[0].cells; const tooltip = 'origin was successfully loaded, waiting for data to be available in database'; const expectedContent = `${originUrl}`; expect($(firstRowCells[2]).html()).to.have.string(expectedContent); }); }); it('should display/close task info popover when clicking on the info button', function() { cy.intercept('/save/requests/list/**', {fixture: 'origin-save'}) .as('saveRequestsList'); cy.intercept('/save/task/info/**', {fixture: 'save-task-info'}) .as('saveTaskInfo'); loadSaveRequestsListPage(); cy.get('.swh-save-request-info') .eq(0) .click(); cy.wait('@saveTaskInfo'); cy.get('.swh-save-request-info-popover') .should('be.visible'); cy.get('.swh-save-request-info') .eq(0) .click(); cy.get('.swh-save-request-info-popover') .should('not.exist'); }); it('should hide task info popover when clicking on the close button', function() { cy.intercept('/save/requests/list/**', {fixture: 'origin-save'}) .as('saveRequestsList'); cy.intercept('/save/task/info/**', {fixture: 'save-task-info'}) .as('saveTaskInfo'); loadSaveRequestsListPage(); cy.get('.swh-save-request-info') .eq(0) .click(); cy.wait('@saveTaskInfo'); cy.get('.swh-save-request-info-popover') .should('be.visible'); cy.get('.swh-save-request-info-close') .click(); cy.get('.swh-save-request-info-popover') .should('not.exist'); }); it('should fill save request form when clicking on "Save again" button', function() { cy.intercept('/save/requests/list/**', {fixture: 'origin-save'}) .as('saveRequestsList'); loadSaveRequestsListPage(); cy.get('.swh-save-origin-again') .eq(0) .click(); cy.get('tbody tr').eq(0).then(row => { const cells = row[0].cells; cy.get('#swh-input-visit-type') .should('have.value', $(cells[1]).text()); cy.get('#swh-input-origin-url') .should('have.value', $(cells[2]).text().slice(0, -1)); }); }); it('should select correct visit type if possible when clicking on "Save again" button', function() { const originUrl = 'https://gitlab.inria.fr/solverstack/maphys/maphys/'; const badVisitType = 'hg'; const goodVisitType = 'git'; cy.intercept('/save/requests/list/**', {fixture: 'origin-save'}) .as('saveRequestsList'); stubSaveRequest({requestUrl: this.Urls.api_1_save_origin(badVisitType, originUrl), visitType: badVisitType, saveRequestStatus: 'accepted', originUrl: originUrl, saveTaskStatus: 'failed', visitStatus: 'failed', responseStatus: 200, errorMessage: saveCodeMsg['accepted']}); makeOriginSaveRequest(badVisitType, originUrl); loadSaveRequestsListPage(); cy.wait('@saveRequest').then(() => { cy.get('.swh-save-origin-again') .eq(0) .click(); cy.get('tbody tr').eq(0).then(row => { const cells = row[0].cells; cy.get('#swh-input-visit-type') .should('have.value', goodVisitType); cy.get('#swh-input-origin-url') .should('have.value', $(cells[2]).text().slice(0, -1)); }); }); }); it('should create save request for authenticated user', function() { cy.userLogin(); cy.visit(url); const originUrl = 'https://git.example.org/account/repo'; stubSaveRequest({requestUrl: this.Urls.api_1_save_origin('git', originUrl), saveRequestStatus: 'accepted', originUrl: origin.url, saveTaskStatus: 'not yet scheduled'}); makeOriginSaveRequest('git', originUrl); cy.wait('@saveRequest').then(() => { checkAlertVisible('success', saveCodeMsg['success']); }); }); it('should not show user requests filter checkbox for anonymous users', function() { cy.get('#swh-origin-save-requests-list-tab').click(); cy.get('#swh-save-requests-user-filter').should('not.exist'); }); it('should show user requests filter checkbox for authenticated users', function() { cy.userLogin(); cy.visit(url); cy.get('#swh-origin-save-requests-list-tab').click(); cy.get('#swh-save-requests-user-filter').should('exist'); }); it('should show only user requests when filter is activated', function() { cy.intercept('POST', '/api/1/origin/save/**') .as('saveRequest'); cy.intercept(this.Urls.origin_save_requests_list('all') + '**') .as('saveRequestsList'); const originAnonymousUser = 'https://some.git.server/project/'; const originAuthUser = 'https://other.git.server/project/'; // anonymous user creates a save request makeOriginSaveRequest('git', originAnonymousUser); cy.wait('@saveRequest'); // authenticated user creates another save request cy.userLogin(); cy.visit(url); makeOriginSaveRequest('git', originAuthUser); cy.wait('@saveRequest'); // user requests filter checkbox should be in the DOM cy.get('#swh-origin-save-requests-list-tab').click(); cy.get('#swh-save-requests-user-filter').should('exist'); // check unfiltered user requests cy.get('tbody tr').then(rows => { expect(rows.length).to.eq(2); expect($(rows[0].cells[2]).text()).to.contain(originAuthUser); expect($(rows[1].cells[2]).text()).to.contain(originAnonymousUser); }); // activate filter and check filtered user requests cy.get('#swh-save-requests-user-filter') .click({force: true}); cy.wait('@saveRequestsList'); cy.get('tbody tr').then(rows => { expect(rows.length).to.eq(1); expect($(rows[0].cells[2]).text()).to.contain(originAuthUser); }); // deactivate filter and check unfiltered user requests cy.get('#swh-save-requests-user-filter') .click({force: true}); cy.wait('@saveRequestsList'); cy.get('tbody tr').then(rows => { expect(rows.length).to.eq(2); }); }); it('should list unprivileged visit types when not connected', function() { cy.visit(url); cy.get('#swh-input-visit-type').children('option').then(options => { const actual = [...options].map(o => o.value); expect(actual).to.deep.eq(anonymousVisitTypes); }); }); it('should list unprivileged visit types when connected as unprivileged user', function() { cy.userLogin(); cy.visit(url); cy.get('#swh-input-visit-type').children('option').then(options => { const actual = [...options].map(o => o.value); expect(actual).to.deep.eq(anonymousVisitTypes); }); }); it('should list privileged visit types when connected as ambassador', function() { cy.ambassadorLogin(); cy.visit(url); cy.get('#swh-input-visit-type').children('option').then(options => { const actual = [...options].map(o => o.value); expect(actual).to.deep.eq(allVisitTypes); }); }); it('should display extra inputs when dealing with \'archives\' visit type', function() { cy.ambassadorLogin(); cy.visit(url); for (const visitType of anonymousVisitTypes) { cy.get('#swh-input-visit-type').select(visitType); cy.get('.swh-save-origin-archives-form').should('not.be.visible'); } // this should display more inputs with the 'archives' type cy.get('#swh-input-visit-type').select('archives'); cy.get('.swh-save-origin-archives-form').should('be.visible'); }); it('should be allowed to submit \'archives\' save request when connected as ambassador', function() { const originUrl = 'https://github.com/chromium/chromium/tags'; const artifactUrl = 'https://github.com/chromium/chromium/archive/refs/tags/104.0.5106.1.tar.gz'; const artifactVersion = '104.0.5106.1'; stubSaveRequest({ requestUrl: this.Urls.api_1_save_origin('archives', originUrl), saveRequestStatus: 'accepted', originUrl: originUrl, saveTaskStatus: 'not yet scheduled' }); cy.ambassadorLogin(); cy.visit(url); // input new 'archives' information and submit cy.get('#swh-input-origin-url') .type(originUrl) .get('#swh-input-visit-type') .select('archives') .get('#swh-input-artifact-url-0') .type(artifactUrl) .get('#swh-input-artifact-version-0') .clear() .type(artifactVersion) - .get('#swh-save-origin-form') - .submit(); + .get('#swh-save-origin-form button[type=submit]') + .click(); cy.wait('@saveRequest').then(() => { checkAlertVisible('success', saveCodeMsg['success']); }); }); it('should submit multiple artifacts for the archives visit type', function() { const originUrl = 'https://ftp.gnu.org/pub/pub/gnu/3dldf'; const artifactUrl = 'https://ftp.gnu.org/pub/pub/gnu/3dldf/3DLDF-1.1.4.tar.gz'; const artifactVersion = '1.1.4'; const artifact2Url = 'https://ftp.gnu.org/pub/pub/gnu/3dldf/3DLDF-1.1.5.tar.gz'; const artifact2Version = '1.1.5'; cy.ambassadorLogin(); cy.visit(url); cy.get('#swh-input-origin-url') .type(originUrl) .get('#swh-input-visit-type') .select('archives'); // fill first artifact info cy.get('#swh-input-artifact-url-0') .type(artifactUrl) .get('#swh-input-artifact-version-0') .clear() .type(artifactVersion); // add new artifact form row cy.get('#swh-add-archive-artifact') .click(); // check new row is displayed cy.get('#swh-input-artifact-url-1') .should('exist'); // request removal of newly added row cy.get('#swh-remove-archive-artifact-1') .click(); // check row has been removed cy.get('#swh-input-artifact-url-1') .should('not.exist'); // add new artifact form row cy.get('#swh-add-archive-artifact') .click(); // fill second artifact info cy.get('#swh-input-artifact-url-1') .type(artifact2Url) .get('#swh-input-artifact-version-1') .clear() .type(artifact2Version); // setup request interceptor to check POST data and stub response cy.intercept('POST', this.Urls.api_1_save_origin('archives', originUrl), (req) => { expect(req.body).to.deep.equal({ archives_data: [ {artifact_url: artifactUrl, artifact_version: artifactVersion}, {artifact_url: artifact2Url, artifact_version: artifact2Version} ] }); req.reply(genOriginSaveResponse({ visitType: 'archives', saveRequestStatus: 'accepted', originUrl: originUrl, saveRequestDate: new Date(), saveTaskStatus: 'not yet scheduled', visitDate: null, visitStatus: null })); }).as('saveRequest'); // submit form - cy.get('#swh-save-origin-form') - .submit(); + cy.get('#swh-save-origin-form button[type=submit]') + .click(); // submission should be successful cy.wait('@saveRequest').then(() => { checkAlertVisible('success', saveCodeMsg['success']); }); }); it('should autofill artifact version when pasting artifact url', function() { const originUrl = 'https://ftp.gnu.org/pub/pub/gnu/3dldf'; const artifactUrl = 'https://ftp.gnu.org/pub/pub/gnu/3dldf/3DLDF-1.1.4.tar.gz'; const artifactVersion = '3DLDF-1.1.4'; const artifact2Url = 'https://example.org/artifact/test/1.3.0.zip'; const artifact2Version = '1.3.0'; cy.ambassadorLogin(); cy.visit(url); cy.get('#swh-input-origin-url') .type(originUrl) .get('#swh-input-visit-type') .select('archives'); // fill first artifact info cy.get('#swh-input-artifact-url-0') .type(artifactUrl); // check autofilled version cy.get('#swh-input-artifact-version-0') .should('have.value', artifactVersion); // add new artifact form row cy.get('#swh-add-archive-artifact') .click(); // fill second artifact info cy.get('#swh-input-artifact-url-1') .type(artifact2Url); // check autofilled version cy.get('#swh-input-artifact-version-1') .should('have.value', artifact2Version); }); it('should use canonical URL for github repository to save', function() { const ownerRepo = 'BIC-MNI/mni_autoreg'; const canonicalOriginUrl = 'https://github.com/BIC-MNI/mni_autoreg'; // stub call to github Web API fetching canonical repo URL cy.intercept(`https://api.github.com/repos/${ownerRepo.toLowerCase()}`, (req) => { req.reply({html_url: canonicalOriginUrl}); }).as('ghWebApiRequest'); // stub save request creation with canonical URL of github repo cy.intercept('POST', this.Urls.api_1_save_origin('git', canonicalOriginUrl), (req) => { req.reply(genOriginSaveResponse({ visitType: 'git', saveRequestStatus: 'accepted', originUrl: canonicalOriginUrl, saveRequestDate: new Date(), saveTaskStatus: 'not yet scheduled', visitDate: null, visitStatus: null })); }).as('saveRequest'); for (const originUrl of ['https://github.com/BiC-MnI/MnI_AuToReG', 'https://github.com/BiC-MnI/MnI_AuToReG.git', 'https://github.com/BiC-MnI/MnI_AuToReG/', 'https://BiC-MnI.github.io/MnI_AuToReG/' ]) { // enter non canonical URL of github repo cy.get('#swh-input-origin-url') .clear() .type(originUrl); // submit form - cy.get('#swh-save-origin-form') - .submit(); + cy.get('#swh-save-origin-form button[type=submit]') + .click(); // submission should be successful cy.wait('@ghWebApiRequest') .wait('@saveRequest').then(() => { checkAlertVisible('success', saveCodeMsg['success']); }); } }); it('should switch tabs when playing with browser history', function() { cy.intercept('/save/requests/list/**', {fixture: 'origin-save'}); cy.intercept('/save/task/info/**', {fixture: 'save-task-info'}); cy.get('#swh-origin-save-request-help-tab') .should('have.class', 'active'); cy.get('#swh-origin-save-requests-list-tab') .click(); cy.get('#swh-origin-save-requests-list-tab') .should('have.class', 'active'); cy.go('back') .get('#swh-origin-save-request-help-tab') .should('have.class', 'active'); cy.go('forward') .get('#swh-origin-save-requests-list-tab') .should('have.class', 'active'); }); it('should not accept origin URL with password', function() { makeOriginSaveRequest('git', 'https://user:password@git.example.org/user/repo'); cy.get('.invalid-feedback') .should('contain', 'The origin url contains a password and cannot be accepted for security reasons'); }); it('should accept origin URL with username but without password', function() { cy.adminLogin(); cy.visit(url); const originUrl = 'https://user@git.example.org/user/repo'; stubSaveRequest({requestUrl: this.Urls.api_1_save_origin('git', originUrl), saveRequestStatus: 'accepted', originUrl: originUrl, saveTaskStatus: 'not yet scheduled'}); makeOriginSaveRequest('git', originUrl); cy.wait('@saveRequest').then(() => { checkAlertVisible('success', saveCodeMsg['success']); }); }); it('should accept origin URL with anonymous credentials', function() { cy.adminLogin(); cy.visit(url); const originUrl = 'https://anonymous:anonymous@git.example.org/user/repo'; stubSaveRequest({requestUrl: this.Urls.api_1_save_origin('git', originUrl), saveRequestStatus: 'accepted', originUrl: originUrl, saveTaskStatus: 'not yet scheduled'}); makeOriginSaveRequest('git', originUrl); cy.wait('@saveRequest').then(() => { checkAlertVisible('success', saveCodeMsg['success']); }); }); it('should accept origin URL with empty password', function() { cy.adminLogin(); cy.visit(url); const originUrl = 'https://anonymous:@git.example.org/user/repo'; stubSaveRequest({requestUrl: this.Urls.api_1_save_origin('git', originUrl), saveRequestStatus: 'accepted', originUrl: originUrl, saveTaskStatus: 'not yet scheduled'}); makeOriginSaveRequest('git', originUrl); cy.wait('@saveRequest').then(() => { checkAlertVisible('success', saveCodeMsg['success']); }); }); });