diff --git a/cypress.json b/cypress.json index 31613afc..90a03961 100644 --- a/cypress.json +++ b/cypress.json @@ -1,19 +1,20 @@ { "baseUrl": "http://localhost:5004", "video": false, "viewportWidth": 1920, "viewportHeight": 1080, "defaultCommandTimeout": 10000, + "requestTimeout": 10000, "numTestsKeptInMemory": 500, "reporter": "cypress-multi-reporters", "reporterOptions": { "reporterEnabled": "mochawesome", "mochawesomeReporterOptions": { "reportDir": "cypress/mochawesome/results", "quiet": true, "overwrite": false, "html": false, "json": true } } } diff --git a/cypress/fixtures/1c480a4573d2a003fc2630c21c2b25829de49972.gitfast.gz b/cypress/fixtures/1c480a4573d2a003fc2630c21c2b25829de49972.gitfast.gz new file mode 100644 index 00000000..aa70433e Binary files /dev/null and b/cypress/fixtures/1c480a4573d2a003fc2630c21c2b25829de49972.gitfast.gz differ diff --git a/cypress/fixtures/cd19126d815470b28919d64b2a8e6a3e37f900dd.tar.gz b/cypress/fixtures/cd19126d815470b28919d64b2a8e6a3e37f900dd.tar.gz new file mode 100644 index 00000000..0a1c6988 Binary files /dev/null and b/cypress/fixtures/cd19126d815470b28919d64b2a8e6a3e37f900dd.tar.gz differ diff --git a/cypress/integration/vault.spec.js b/cypress/integration/vault.spec.js new file mode 100644 index 00000000..bc8771be --- /dev/null +++ b/cypress/integration/vault.spec.js @@ -0,0 +1,151 @@ +/** + * Copyright (C) 2019 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 createVaultCookingTask(objectType) { + cy.contains('button', 'Actions') + .click(); + + cy.contains('.dropdown-item', 'Download') + .click(); + + cy.contains('.dropdown-item', objectType) + .click(); + + cy.get('.modal-dialog') + .contains('button', 'Ok') + .click(); +} + +function genVaultCookingResponse(objectType, objectId, status, message, fetchUrl) { + return { + 'obj_type': objectType, + 'id': 1, + 'progress_message': message, + 'status': status, + 'obj_id': objectId, + 'fetch_url': fetchUrl + }; +}; + +describe('Vault Cooking User Interface Tests', function() { + + before(function() { + this.directory = this.origin[0].directory[0].id; + this.directoryUrl = this.Urls.browse_directory(this.directory); + this.vaultDirectoryUrl = this.Urls.api_1_vault_cook_directory(this.directory); + this.vaultFetchDirectoryUrl = this.Urls.api_1_vault_fetch_directory(this.directory); + this.genVaultDirCookingResponse = (status, message = null) => { + return genVaultCookingResponse('directory', this.directory, status, + message, this.vaultFetchDirectoryUrl); + }; + + this.revision = this.origin[1].revision[0]; + this.revisionUrl = this.Urls.browse_revision(this.revision); + this.vaultRevisionUrl = this.Urls.api_1_vault_cook_revision_gitfast(this.revision); + this.vaultFetchRevisionUrl = this.Urls.api_1_vault_fetch_revision_gitfast(this.revision); + this.genVaultRevCookingResponse = (status, message = null) => { + return genVaultCookingResponse('revision', this.revision, status, + message, this.vaultFetchRevisionUrl); + }; + + cy.server(); + }); + + it('should create a directory cooking task and report its status', function() { + + // Browse a directory + cy.visit(this.directoryUrl); + + // Stub responses when requesting the vault API to simulate + // a task has been created + cy.route({ + method: 'POST', + url: this.vaultDirectoryUrl, + response: this.genVaultDirCookingResponse('new') + }).as('createVaultCookingTask'); + + cy.route({ + method: 'GET', + url: this.vaultDirectoryUrl, + response: this.genVaultDirCookingResponse('new') + }).as('checkVaultCookingTask'); + + // Create a vault cooking task through the GUI + createVaultCookingTask('Directory'); + + cy.wait('@createVaultCookingTask'); + + // Check that a redirection to the vault UI has been performed + cy.url().should('eq', Cypress.config().baseUrl + this.Urls.browse_vault()); + + cy.wait('@checkVaultCookingTask'); + + // TODO: - check that a row has been created for the task in + // the displayed table + // + // - check progress bar state and color + + // Stub response to the vault API indicating the task is processing + cy.route({ + method: 'GET', + url: this.vaultDirectoryUrl, + response: this.genVaultDirCookingResponse('pending', 'Processing...') + }).as('checkVaultCookingTask'); + + cy.wait('@checkVaultCookingTask'); + + // TODO: check progress bar state and color + + // Stub response to the vault API indicating the task is finished + cy.route({ + method: 'GET', + url: this.vaultDirectoryUrl, + response: this.genVaultDirCookingResponse('done') + }).as('checkVaultCookingTask'); + + cy.wait('@checkVaultCookingTask'); + + // TODO: check progress bar state and color and that the download + // button appeared + + // Stub response to the vault API indicating to simulate archive + // download + cy.route({ + method: 'GET', + url: this.vaultFetchDirectoryUrl, + response: `fx:${this.directory}.tar.gz,binary`, + headers: { + 'Content-disposition': `attachment; filename=${this.directory}.tar.gz`, + 'Content-Type': 'application/gzip' + } + }).as('fetchCookedArchive'); + + cy.get(`#vault-task-${this.directory} .vault-dl-link button`) + .click(); + + cy.wait('@fetchCookedArchive').then((xhr) => { + assert.isNotNull(xhr.response.body); + }); + + }); + + it('should create a revision cooking task and report its status', function() { + // TODO: The above test must be factorized to handle the revision cooking test + }); + + it('should offer to recook an archive if no more available to download', function() { + // TODO: + // - Simulate an already executed task by filling the 'swh-vault-cooking-tasks' + // entry in browser localStorage (see vault-ui.js). + // + // - Stub the response to the archive fetch url to return a 404 error. + // + // - Check that the dialog offering to recook the archive is displayed + // and that the cooking task can be created from it. + }); + +}); diff --git a/cypress/support/index.js b/cypress/support/index.js index a8d12d61..cbe946d5 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -1,95 +1,124 @@ /** * Copyright (C) 2019 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 */ import '@cypress/code-coverage/support'; import {httpGetJson} from '../utils'; Cypress.Screenshot.defaults({ screenshotOnRunFailure: false }); before(function() { this.unarchivedRepo = { url: 'https://github.com/SoftwareHeritage/swh-web', revision: '7bf1b2f489f16253527807baead7957ca9e8adde', snapshot: 'd9829223095de4bb529790de8ba4e4813e38672d', rootDirectory: '7d887d96c0047a77e2e8c4ee9bb1528463677663', content: [{ sha1git: 'b203ec39300e5b7e97b6e20986183cbd0b797859' }] }; this.origin = [{ url: 'https://github.com/memononen/libtess2', content: [{ path: 'Source/tess.h' }, { path: 'premake4.lua' }], + directory: [{ + path: 'Source', + id: 'cd19126d815470b28919d64b2a8e6a3e37f900dd' + }], + revision: [], invalidSubDir: 'Source1' }, { url: 'https://github.com/wcoder/highlightjs-line-numbers.js', - content: [] + content: [], + directory: [], + revision: ['1c480a4573d2a003fc2630c21c2b25829de49972'] }]; const getMetadataForOrigin = async originUrl => { const originVisitsApiUrl = this.Urls.api_1_origin_visits(originUrl); const originVisits = await httpGetJson(originVisitsApiUrl); const lastVisit = originVisits[0]; const snapshotApiUrl = this.Urls.api_1_snapshot(lastVisit.snapshot); const lastOriginSnapshot = await httpGetJson(snapshotApiUrl); const revisionApiUrl = this.Urls.api_1_revision(lastOriginSnapshot.branches.HEAD.target); const lastOriginHeadRevision = await httpGetJson(revisionApiUrl); return { 'directory': lastOriginHeadRevision.directory, 'revision': lastOriginHeadRevision.id, 'snapshot': lastOriginSnapshot.id }; }; cy.visit('/').window().then(async win => { this.Urls = win.Urls; for (let origin of this.origin) { const metadata = await getMetadataForOrigin(origin.url); const directoryApiUrl = this.Urls.api_1_directory(metadata.directory); origin.dirContent = await httpGetJson(directoryApiUrl); origin.rootDirectory = metadata.directory; origin.revision = metadata.revision; origin.snapshot = metadata.snapshot; for (let content of origin.content) { cy.visit(this.Urls.browse_origin_content(origin.url, content.path)) .window().then(win => { const contentMetaData = win.swh.webapp.getBrowsedSwhObjectMetadata(); content.name = contentMetaData.filename; content.sha1git = contentMetaData.sha1_git; content.directory = contentMetaData.directory; content.rawFilePath = this.Urls.browse_content_raw(`sha1_git:${content.sha1git}`) + `?filename=${encodeURIComponent(content.name)}`; cy.request(content.rawFilePath) .then((response) => { const fileText = response.body; const fileLines = fileText.split('\n'); content.numberLines = fileLines.length; // If last line is empty its not shown if (!fileLines[content.numberLines - 1]) content.numberLines -= 1; }); }); } } }); }); + +// force the use of fetch polyfill wrapping XmlHttpRequest +// in order for cypress to be able to intercept and stub them +Cypress.on('window:before:load', win => { + win.fetch = null; +}); + +// Ensure code coverage data do not get lost each time a new +// page is loaded during a single test execution +let coverage = {}; + +Cypress.on('window:before:unload', e => { + coverage = Object.assign(coverage, e.currentTarget.__coverage__); +}); + +beforeEach(function() { + coverage = {}; +}); + +afterEach(function() { + cy.task('combineCoverage', coverage); +});