diff --git a/assets/src/bundles/add_forge/add-request-history-item.ejs b/assets/src/bundles/add_forge/add-request-history-item.ejs
index 16de3716..57b43972 100644
--- a/assets/src/bundles/add_forge/add-request-history-item.ejs
+++ b/assets/src/bundles/add_forge/add-request-history-item.ejs
@@ -1,31 +1,31 @@
<%#
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
%>
-
diff --git a/cypress/fixtures/add-forge-now-request.json b/cypress/fixtures/add-forge-now-request.json
deleted file mode 100644
index 127f5e93..00000000
--- a/cypress/fixtures/add-forge-now-request.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "request":{
- "id":1,
- "status":"PENDING",
- "submission_date":"2022-03-17T13:35:24.324848Z",
- "submitter_name":"admin",
- "submitter_email":"admin@swh-web.org",
- "forge_type":"bitbucket",
- "forge_url":"test.com",
- "forge_contact_email":"test@example.com",
- "forge_contact_name":"test user",
- "forge_contact_comment":"test comment"
- },"history":[
- {
- "id":1,
- "text":"",
- "actor":"admin",
- "actor_role":"SUBMITTER",
- "date":"2022-03-17T13:35:24.326190Z",
- "new_status":"PENDING"
- }
- ]
-}
diff --git a/cypress/integration/add-forge-now-request-dashboard.spec.js b/cypress/integration/add-forge-now-request-dashboard.spec.js
index ef57d9cf..70bdaeca 100644
--- a/cypress/integration/add-forge-now-request-dashboard.spec.js
+++ b/cypress/integration/add-forge-now-request-dashboard.spec.js
@@ -1,77 +1,229 @@
/**
* 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 requestId = 1;
+let requestId;
+
+function createDummyRequest(urls) {
+ cy.task('db:add_forge_now:delete');
+ cy.userLogin();
+
+ cy.getCookie('csrftoken').its('value').then((token) => {
+ cy.request({
+ method: 'POST',
+ url: urls.api_1_add_forge_request_create(),
+ body: {
+ 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'
+ },
+ headers: {
+ 'X-CSRFToken': token
+ }
+ }).then((response) => {
+ // setting requestId from response
+ requestId = response.body.id;
+ // logout the user
+ cy.visit(urls.swh_web_homepage());
+ cy.contains('a', 'logout').click();
+ });
+ });
+}
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)}**`,
- {fixture: 'add-forge-now-request'}).as('forgeAddRequest');
+ cy.intercept(`${this.Urls.api_1_add_forge_request_get(requestId)}**`).as('forgeRequestGet');
cy.visit(url);
});
it('should load add forge request details', function() {
- cy.wait('@forgeAddRequest');
+ cy.wait('@forgeRequestGet');
cy.get('#requestStatus')
.should('contain', 'Pending');
cy.get('#requestType')
.should('contain', 'bitbucket');
cy.get('#requestURL')
- .should('contain', 'test.com');
+ .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', `[swh-add_forge_now] Request ${requestId}`);
});
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() {
- const invalidRequestId = 2;
+ // 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 forge now request update', function() {
+
+ beforeEach(function() {
+ createDummyRequest(this.Urls);
+
+ const 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(url);
+ });
+
+ it('should submit correct details', function() {
+ cy.wait('@forgeRequestGet');
+ 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() {
+ cy.wait('@forgeRequestGet');
+ 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() {
+ cy.wait('@forgeRequestGet');
+ 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 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/plugins/index.js b/cypress/plugins/index.js
index 70c084e7..fac4b035 100644
--- a/cypress/plugins/index.js
+++ b/cypress/plugins/index.js
@@ -1,160 +1,165 @@
/**
* 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 axios = require('axios');
const fs = require('fs');
const sqlite3 = require('sqlite3').verbose();
async function httpGet(url) {
const response = await axios.get(url);
return response.data;
}
async function getMetadataForOrigin(originUrl, baseUrl) {
const originVisitsApiUrl = `${baseUrl}/api/1/origin/${originUrl}/visits`;
const originVisits = await httpGet(originVisitsApiUrl);
const lastVisit = originVisits[0];
const snapshotApiUrl = `${baseUrl}/api/1/snapshot/${lastVisit.snapshot}`;
const lastOriginSnapshot = await httpGet(snapshotApiUrl);
let revision = lastOriginSnapshot.branches.HEAD.target;
if (lastOriginSnapshot.branches.HEAD.target_type === 'alias') {
revision = lastOriginSnapshot.branches[revision].target;
}
const revisionApiUrl = `${baseUrl}/api/1/revision/${revision}`;
const lastOriginHeadRevision = await httpGet(revisionApiUrl);
return {
'directory': lastOriginHeadRevision.directory,
'revision': lastOriginHeadRevision.id,
'snapshot': lastOriginSnapshot.id
};
};
function getDatabase() {
return new sqlite3.Database('./swh-web-test.sqlite3');
}
module.exports = (on, config) => {
require('@cypress/code-coverage/task')(on, config);
// produce JSON files prior launching browser in order to dynamically generate tests
on('before:browser:launch', function(browser, launchOptions) {
return new Promise((resolve) => {
const p1 = axios.get(`${config.baseUrl}/tests/data/content/code/extensions/`);
const p2 = axios.get(`${config.baseUrl}/tests/data/content/code/filenames/`);
Promise.all([p1, p2])
.then(function(responses) {
fs.writeFileSync('cypress/fixtures/source-file-extensions.json', JSON.stringify(responses[0].data));
fs.writeFileSync('cypress/fixtures/source-file-names.json', JSON.stringify(responses[1].data));
resolve();
});
});
});
on('task', {
getSwhTestsData: async() => {
if (!global.swhTestsData) {
const swhTestsData = {};
swhTestsData.unarchivedRepo = {
url: 'https://github.com/SoftwareHeritage/swh-web',
type: 'git',
revision: '7bf1b2f489f16253527807baead7957ca9e8adde',
snapshot: 'd9829223095de4bb529790de8ba4e4813e38672d',
rootDirectory: '7d887d96c0047a77e2e8c4ee9bb1528463677663',
content: [{
sha1git: 'b203ec39300e5b7e97b6e20986183cbd0b797859'
}]
};
swhTestsData.origin = [{
url: 'https://github.com/memononen/libtess2',
type: 'git',
content: [{
path: 'Source/tess.h'
}, {
path: 'premake4.lua'
}],
directory: [{
path: 'Source',
id: 'cd19126d815470b28919d64b2a8e6a3e37f900dd'
}],
revisions: [],
invalidSubDir: 'Source1'
}, {
url: 'https://github.com/wcoder/highlightjs-line-numbers.js',
type: 'git',
content: [{
path: 'src/highlightjs-line-numbers.js'
}],
directory: [],
revisions: ['1c480a4573d2a003fc2630c21c2b25829de49972'],
release: {
name: 'v2.6.0',
id: '6877028d6e5412780517d0bfa81f07f6c51abb41',
directory: '5b61d50ef35ca9a4618a3572bde947b8cccf71ad'
}
}];
for (const origin of swhTestsData.origin) {
const metadata = await getMetadataForOrigin(origin.url, config.baseUrl);
const directoryApiUrl = `${config.baseUrl}/api/1/directory/${metadata.directory}`;
origin.dirContent = await httpGet(directoryApiUrl);
origin.rootDirectory = metadata.directory;
origin.revisions.push(metadata.revision);
origin.snapshot = metadata.snapshot;
for (const content of origin.content) {
const contentPathApiUrl = `${config.baseUrl}/api/1/directory/${origin.rootDirectory}/${content.path}`;
const contentMetaData = await httpGet(contentPathApiUrl);
content.name = contentMetaData.name.split('/').slice(-1)[0];
content.sha1git = contentMetaData.target;
content.directory = contentMetaData.dir_id;
const rawFileUrl = `${config.baseUrl}/browse/content/sha1_git:${content.sha1git}/raw/?filename=${content.name}`;
const fileText = await httpGet(rawFileUrl);
const fileLines = fileText.split('\n');
content.numberLines = fileLines.length;
if (!fileLines[content.numberLines - 1]) {
// If last line is empty its not shown
content.numberLines -= 1;
}
}
}
global.swhTestsData = swhTestsData;
}
return global.swhTestsData;
},
'db:user_mailmap:delete': () => {
const db = getDatabase();
db.serialize(function() {
db.run('DELETE FROM user_mailmap');
db.run('DELETE FROM user_mailmap_event');
});
db.close();
return true;
},
'db:user_mailmap:mark_processed': () => {
const db = getDatabase();
db.serialize(function() {
db.run('UPDATE user_mailmap SET mailmap_last_processing_date=datetime("now", "+1 hour")');
});
db.close();
return true;
},
'db:add_forge_now:delete': () => {
const db = getDatabase();
+ db.serialize(function() {
+ db.run('DELETE FROM add_forge_request_history');
+ db.run('DELETE FROM sqlite_sequence WHERE name="add_forge_request_history"');
+ });
db.serialize(function() {
db.run('DELETE FROM add_forge_request');
+ db.run('DELETE FROM sqlite_sequence WHERE name="add_forge_request"');
});
db.close();
return true;
}
});
return config;
};