mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-22 00:16:15 -04:00
restructure: move bin/ and tests/ to src/
Also add symlinks from the old `bin/` and `tests/` locations to avoid breaking scripts and other tools. Motivations: * Scripts and tests no longer have to do dubious things like: require('ep_etherpad-lite/node_modules/foo') to access packages installed as dependencies in `src/package.json`. * Plugins can access the backend test helper library in a non-hacky way: require('ep_etherpad-lite/tests/backend/common') * We can delete the top-level `package.json` without breaking our ability to lint the files in `bin/` and `tests/`. Deleting the top-level `package.json` has downsides: It will cause `npm` to print warnings whenever plugins are installed, npm will no longer be able to enforce a plugin's peer dependency on ep_etherpad-lite, and npm will keep deleting the `node_modules/ep_etherpad-lite` symlink that points to `../src`. But there are significant upsides to deleting the top-level `package.json`: It will drastically speed up plugin installation because `npm` doesn't have to recursively walk the dependencies in `src/package.json`. Also, deleting the top-level `package.json` avoids npm's horrible dependency hoisting behavior (where it moves stuff from `src/node_modules/` to the top-level `node_modules/` directory). Dependency hoisting causes numerous mysterious problems such as silent failures in `npm outdated` and `npm update`. Dependency hoisting also breaks plugins that do: require('ep_etherpad-lite/node_modules/foo')
This commit is contained in:
parent
efde0b787a
commit
2ea8ea1275
146 changed files with 191 additions and 1161 deletions
75
src/tests/backend/specs/api/api.js
Normal file
75
src/tests/backend/specs/api/api.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* API specs
|
||||
*
|
||||
* Tests for generic overarching HTTP API related features not related to any
|
||||
* specific part of the data model or domain. For example: tests for versioning
|
||||
* and openapi definitions.
|
||||
*/
|
||||
|
||||
const common = require('../../common');
|
||||
const settings = require('../../../../node/utils/Settings');
|
||||
const supertest = require('supertest');
|
||||
const validateOpenAPI = require('openapi-schema-validation').validate;
|
||||
|
||||
const api = supertest(`http://${settings.ip}:${settings.port}`);
|
||||
const apiKey = common.apiKey;
|
||||
let apiVersion = 1;
|
||||
|
||||
const testPadId = makeid();
|
||||
|
||||
describe(__filename, function () {
|
||||
describe('API Versioning', function () {
|
||||
it('errors if can not connect', function (done) {
|
||||
api
|
||||
.get('/api/')
|
||||
.expect((res) => {
|
||||
apiVersion = res.body.currentVersion;
|
||||
if (!res.body.currentVersion) throw new Error('No version set in API');
|
||||
return;
|
||||
})
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('OpenAPI definition', function () {
|
||||
it('generates valid openapi definition document', function (done) {
|
||||
api
|
||||
.get('/api/openapi.json')
|
||||
.expect((res) => {
|
||||
const {valid, errors} = validateOpenAPI(res.body, 3);
|
||||
if (!valid) {
|
||||
const prettyErrors = JSON.stringify(errors, null, 2);
|
||||
throw new Error(`Document is not valid OpenAPI. ${errors.length} validation errors:\n${prettyErrors}`);
|
||||
}
|
||||
return;
|
||||
})
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('jsonp support', function () {
|
||||
it('supports jsonp calls', function (done) {
|
||||
api
|
||||
.get(`${endPoint('createPad')}&jsonp=jsonp_1&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (!res.text.match('jsonp_1')) throw new Error('no jsonp call seen');
|
||||
})
|
||||
.expect('Content-Type', /javascript/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var endPoint = function (point) {
|
||||
return `/api/${apiVersion}/${point}?apikey=${apiKey}`;
|
||||
};
|
||||
|
||||
function makeid() {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
111
src/tests/backend/specs/api/characterEncoding.js
Normal file
111
src/tests/backend/specs/api/characterEncoding.js
Normal file
|
@ -0,0 +1,111 @@
|
|||
'use strict';
|
||||
|
||||
/*
|
||||
* This file is copied & modified from <basedir>/tests/backend/specs/api/pad.js
|
||||
*
|
||||
* TODO: maybe unify those two files and merge in a single one.
|
||||
*/
|
||||
|
||||
const common = require('../../common');
|
||||
const fs = require('fs');
|
||||
const settings = require('../../../../node/utils/Settings');
|
||||
const supertest = require('supertest');
|
||||
|
||||
const api = supertest(`http://${settings.ip}:${settings.port}`);
|
||||
const apiKey = common.apiKey;
|
||||
let apiVersion = 1;
|
||||
const testPadId = makeid();
|
||||
|
||||
describe(__filename, function () {
|
||||
describe('Connectivity For Character Encoding', function () {
|
||||
it('can connect', function (done) {
|
||||
api.get('/api/')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('API Versioning', function () {
|
||||
it('finds the version tag', function (done) {
|
||||
api.get('/api/')
|
||||
.expect((res) => {
|
||||
apiVersion = res.body.currentVersion;
|
||||
if (!res.body.currentVersion) throw new Error('No version set in API');
|
||||
return;
|
||||
})
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Permission', function () {
|
||||
it('errors with invalid APIKey', function (done) {
|
||||
// This is broken because Etherpad doesn't handle HTTP codes properly see #2343
|
||||
// If your APIKey is password you deserve to fail all tests anyway
|
||||
const permErrorURL = `/api/${apiVersion}/createPad?apikey=password&padID=test`;
|
||||
api.get(permErrorURL)
|
||||
.expect(401, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createPad', function () {
|
||||
it('creates a new Pad', function (done) {
|
||||
api.get(`${endPoint('createPad')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Unable to create new Pad');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setHTML', function () {
|
||||
it('Sets the HTML of a Pad attempting to weird utf8 encoded content', function (done) {
|
||||
fs.readFile('../tests/backend/specs/api/emojis.html', 'utf8', (err, html) => {
|
||||
api.post(endPoint('setHTML'))
|
||||
.send({
|
||||
padID: testPadId,
|
||||
html,
|
||||
})
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error("Can't set HTML properly");
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHTML', function () {
|
||||
it('get the HTML of Pad with emojis', function (done) {
|
||||
api.get(`${endPoint('getHTML')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.data.html.indexOf('🇼') === -1) {
|
||||
throw new Error('Unable to get the HTML');
|
||||
}
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
End of test
|
||||
|
||||
*/
|
||||
|
||||
var endPoint = function (point, version) {
|
||||
version = version || apiVersion;
|
||||
return `/api/${version}/${point}?apikey=${apiKey}`;
|
||||
};
|
||||
|
||||
function makeid() {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
110
src/tests/backend/specs/api/chat.js
Normal file
110
src/tests/backend/specs/api/chat.js
Normal file
|
@ -0,0 +1,110 @@
|
|||
const common = require('../../common');
|
||||
const settings = require('../../../../node/utils/Settings');
|
||||
const supertest = require('supertest');
|
||||
|
||||
const api = supertest(`http://${settings.ip}:${settings.port}`);
|
||||
const apiKey = common.apiKey;
|
||||
let apiVersion = 1;
|
||||
let authorID = '';
|
||||
const padID = makeid();
|
||||
const timestamp = Date.now();
|
||||
|
||||
describe(__filename, function () {
|
||||
describe('API Versioning', function () {
|
||||
it('errors if can not connect', function (done) {
|
||||
api.get('/api/')
|
||||
.expect((res) => {
|
||||
apiVersion = res.body.currentVersion;
|
||||
if (!res.body.currentVersion) throw new Error('No version set in API');
|
||||
return;
|
||||
})
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
// BEGIN GROUP AND AUTHOR TESTS
|
||||
// ///////////////////////////////////
|
||||
// ///////////////////////////////////
|
||||
|
||||
/* Tests performed
|
||||
-> createPad(padID)
|
||||
-> createAuthor([name]) -- should return an authorID
|
||||
-> appendChatMessage(padID, text, authorID, time)
|
||||
-> getChatHead(padID)
|
||||
-> getChatHistory(padID)
|
||||
*/
|
||||
|
||||
describe('createPad', function () {
|
||||
it('creates a new Pad', function (done) {
|
||||
api.get(`${endPoint('createPad')}&padID=${padID}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Unable to create new Pad');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createAuthor', function () {
|
||||
it('Creates an author with a name set', function (done) {
|
||||
api.get(endPoint('createAuthor'))
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0 || !res.body.data.authorID) throw new Error('Unable to create author');
|
||||
authorID = res.body.data.authorID; // we will be this author for the rest of the tests
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('appendChatMessage', function () {
|
||||
it('Adds a chat message to the pad', function (done) {
|
||||
api.get(`${endPoint('appendChatMessage')}&padID=${padID}&text=blalblalbha&authorID=${authorID}&time=${timestamp}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Unable to create chat message');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('getChatHead', function () {
|
||||
it('Gets the head of chat', function (done) {
|
||||
api.get(`${endPoint('getChatHead')}&padID=${padID}`)
|
||||
.expect((res) => {
|
||||
if (res.body.data.chatHead !== 0) throw new Error('Chat Head Length is wrong');
|
||||
|
||||
if (res.body.code !== 0) throw new Error('Unable to get chat head');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getChatHistory', function () {
|
||||
it('Gets Chat History of a Pad', function (done) {
|
||||
api.get(`${endPoint('getChatHistory')}&padID=${padID}`)
|
||||
.expect((res) => {
|
||||
if (res.body.data.messages.length !== 1) throw new Error('Chat History Length is wrong');
|
||||
if (res.body.code !== 0) throw new Error('Unable to get chat history');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var endPoint = function (point) {
|
||||
return `/api/${apiVersion}/${point}?apikey=${apiKey}`;
|
||||
};
|
||||
|
||||
function makeid() {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
10
src/tests/backend/specs/api/emojis.html
Normal file
10
src/tests/backend/specs/api/emojis.html
Normal file
File diff suppressed because one or more lines are too long
71
src/tests/backend/specs/api/fuzzImportTest.js
Normal file
71
src/tests/backend/specs/api/fuzzImportTest.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Fuzz testing the import endpoint
|
||||
*/
|
||||
/*
|
||||
const common = require('../../common');
|
||||
const froth = require('mocha-froth');
|
||||
const request = require('request');
|
||||
const settings = require('../../../container/loadSettings.js').loadSettings();
|
||||
|
||||
const host = "http://" + settings.ip + ":" + settings.port;
|
||||
|
||||
const apiKey = common.apiKey;
|
||||
var apiVersion = 1;
|
||||
var testPadId = "TEST_fuzz" + makeid();
|
||||
|
||||
var endPoint = function(point, version){
|
||||
version = version || apiVersion;
|
||||
return '/api/'+version+'/'+point+'?apikey='+apiKey;
|
||||
}
|
||||
|
||||
//console.log("Testing against padID", testPadId);
|
||||
//console.log("To watch the test live visit " + host + "/p/" + testPadId);
|
||||
//console.log("Tests will start in 5 seconds, click the URL now!");
|
||||
|
||||
setTimeout(function(){
|
||||
for (let i=1; i<5; i++) { // 5000 runs
|
||||
setTimeout( function timer(){
|
||||
runTest(i);
|
||||
}, i*100 ); // 100 ms
|
||||
}
|
||||
process.exit(0);
|
||||
},5000); // wait 5 seconds
|
||||
|
||||
function runTest(number){
|
||||
request(host + endPoint('createPad') + '&padID=' + testPadId, function(err, res, body){
|
||||
var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) {
|
||||
if (err) {
|
||||
throw new Error("FAILURE", err);
|
||||
}else{
|
||||
console.log("Success");
|
||||
}
|
||||
});
|
||||
|
||||
var fN = '/tmp/fuzztest.txt';
|
||||
var cT = 'text/plain';
|
||||
|
||||
if (number % 2 == 0) {
|
||||
fN = froth().toString();
|
||||
cT = froth().toString();
|
||||
}
|
||||
|
||||
let form = req.form();
|
||||
|
||||
form.append('file', froth().toString(), {
|
||||
filename: fN,
|
||||
contentType: cT
|
||||
});
|
||||
console.log("here");
|
||||
});
|
||||
}
|
||||
|
||||
function makeid() {
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
for( var i=0; i < 5; i++ ){
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
*/
|
BIN
src/tests/backend/specs/api/image.png
Normal file
BIN
src/tests/backend/specs/api/image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 355 KiB |
319
src/tests/backend/specs/api/importexport.js
Normal file
319
src/tests/backend/specs/api/importexport.js
Normal file
|
@ -0,0 +1,319 @@
|
|||
'use strict';
|
||||
/*
|
||||
* ACHTUNG: there is a copied & modified version of this file in
|
||||
* <basedir>/tests/container/spacs/api/pad.js
|
||||
*
|
||||
* TODO: unify those two files, and merge in a single one.
|
||||
*/
|
||||
|
||||
const common = require('../../common');
|
||||
const settings = require('../../../container/loadSettings.js').loadSettings();
|
||||
const supertest = require('supertest');
|
||||
|
||||
const api = supertest(`http://${settings.ip}:${settings.port}`);
|
||||
const apiKey = common.apiKey;
|
||||
const apiVersion = 1;
|
||||
|
||||
const testImports = {
|
||||
'malformed': {
|
||||
input: '<html><body><li>wtf</ul></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>wtf<br><br></body></html>',
|
||||
wantText: 'wtf\n\n',
|
||||
disabled: true,
|
||||
},
|
||||
'nonelistiteminlist #3620': {
|
||||
input: '<html><body><ul>test<li>FOO</li></ul></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body><ul class="bullet">test<li>FOO</ul><br></body></html>',
|
||||
wantText: '\ttest\n\t* FOO\n\n',
|
||||
disabled: true,
|
||||
},
|
||||
'whitespaceinlist #3620': {
|
||||
input: '<html><body><ul> <li>FOO</li></ul></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body><ul class="bullet"><li>FOO</ul><br></body></html>',
|
||||
wantText: '\t* FOO\n\n',
|
||||
},
|
||||
'prefixcorrectlinenumber': {
|
||||
input: '<html><body><ol><li>should be 1</li><li>should be 2</li></ol></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body><ol start="1" class="number"><li>should be 1</li><li>should be 2</ol><br></body></html>',
|
||||
wantText: '\t1. should be 1\n\t2. should be 2\n\n',
|
||||
},
|
||||
'prefixcorrectlinenumbernested': {
|
||||
input: '<html><body><ol><li>should be 1</li><ol><li>foo</li></ol><li>should be 2</li></ol></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body><ol start="1" class="number"><li>should be 1<ol start="2" class="number"><li>foo</ol><li>should be 2</ol><br></body></html>',
|
||||
wantText: '\t1. should be 1\n\t\t1.1. foo\n\t2. should be 2\n\n',
|
||||
},
|
||||
|
||||
/*
|
||||
"prefixcorrectlinenumber when introduced none list item - currently not supported see #3450": {
|
||||
input: '<html><body><ol><li>should be 1</li>test<li>should be 2</li></ol></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body><ol start="1" class="number"><li>should be 1</li>test<li>should be 2</li></ol><br></body></html>',
|
||||
wantText: '\t1. should be 1\n\ttest\n\t2. should be 2\n\n',
|
||||
}
|
||||
,
|
||||
"newlinesshouldntresetlinenumber #2194": {
|
||||
input: '<html><body><ol><li>should be 1</li>test<li>should be 2</li></ol></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body><ol class="number"><li>should be 1</li>test<li>should be 2</li></ol><br></body></html>',
|
||||
wantText: '\t1. should be 1\n\ttest\n\t2. should be 2\n\n',
|
||||
}
|
||||
*/
|
||||
'ignoreAnyTagsOutsideBody': {
|
||||
description: 'Content outside body should be ignored',
|
||||
input: '<html><head><title>title</title><style></style></head><body>empty<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>empty<br><br></body></html>',
|
||||
wantText: 'empty\n\n',
|
||||
},
|
||||
'indentedListsAreNotBullets': {
|
||||
description: 'Indented lists are represented with tabs and without bullets',
|
||||
input: '<html><body><ul class="indent"><li>indent</li><li>indent</ul></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body><ul class="indent"><li>indent</li><li>indent</ul><br></body></html>',
|
||||
wantText: '\tindent\n\tindent\n\n',
|
||||
},
|
||||
'lineWithMultipleSpaces': {
|
||||
description: 'Multiple spaces should be collapsed',
|
||||
input: '<html><body>Text with more than one space.<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>Text with more than one space.<br><br></body></html>',
|
||||
wantText: 'Text with more than one space.\n\n',
|
||||
},
|
||||
'lineWithMultipleNonBreakingAndNormalSpaces': {
|
||||
// XXX the HTML between "than" and "one" looks strange
|
||||
description: 'non-breaking space should be preserved, but can be replaced when it',
|
||||
input: '<html><body>Text with more than one space.<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>Text with more than one space.<br><br></body></html>',
|
||||
wantText: 'Text with more than one space.\n\n',
|
||||
},
|
||||
'multiplenbsp': {
|
||||
description: 'Multiple non-breaking space should be preserved',
|
||||
input: '<html><body> <br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body> <br><br></body></html>',
|
||||
wantText: ' \n\n',
|
||||
},
|
||||
'multipleNonBreakingSpaceBetweenWords': {
|
||||
description: 'A normal space is always inserted before a word',
|
||||
input: '<html><body> word1 word2 word3<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body> word1 word2 word3<br><br></body></html>',
|
||||
wantText: ' word1 word2 word3\n\n',
|
||||
},
|
||||
'nonBreakingSpacePreceededBySpaceBetweenWords': {
|
||||
description: 'A non-breaking space preceded by a normal space',
|
||||
input: '<html><body> word1 word2 word3<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body> word1 word2 word3<br><br></body></html>',
|
||||
wantText: ' word1 word2 word3\n\n',
|
||||
},
|
||||
'nonBreakingSpaceFollowededBySpaceBetweenWords': {
|
||||
description: 'A non-breaking space followed by a normal space',
|
||||
input: '<html><body> word1 word2 word3<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body> word1 word2 word3<br><br></body></html>',
|
||||
wantText: ' word1 word2 word3\n\n',
|
||||
},
|
||||
'spacesAfterNewline': {
|
||||
description: 'Collapse spaces that follow a newline',
|
||||
input: '<!doctype html><html><body>something<br> something<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>something<br>something<br><br></body></html>',
|
||||
wantText: 'something\nsomething\n\n',
|
||||
},
|
||||
'spacesAfterNewlineP': {
|
||||
description: 'Collapse spaces that follow a paragraph',
|
||||
input: '<!doctype html><html><body>something<p></p> something<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>something<br><br>something<br><br></body></html>',
|
||||
wantText: 'something\n\nsomething\n\n',
|
||||
},
|
||||
'spacesAtEndOfLine': {
|
||||
description: 'Collapse spaces that preceed/follow a newline',
|
||||
input: '<html><body>something <br> something<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>something<br>something<br><br></body></html>',
|
||||
wantText: 'something\nsomething\n\n',
|
||||
},
|
||||
'spacesAtEndOfLineP': {
|
||||
description: 'Collapse spaces that preceed/follow a paragraph',
|
||||
input: '<html><body>something <p></p> something<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>something<br><br>something<br><br></body></html>',
|
||||
wantText: 'something\n\nsomething\n\n',
|
||||
},
|
||||
'nonBreakingSpacesAfterNewlines': {
|
||||
description: 'Don\'t collapse non-breaking spaces that follow a newline',
|
||||
input: '<html><body>something<br> something<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>something<br> something<br><br></body></html>',
|
||||
wantText: 'something\n something\n\n',
|
||||
},
|
||||
'nonBreakingSpacesAfterNewlinesP': {
|
||||
description: 'Don\'t collapse non-breaking spaces that follow a paragraph',
|
||||
input: '<html><body>something<p></p> something<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>something<br><br> something<br><br></body></html>',
|
||||
wantText: 'something\n\n something\n\n',
|
||||
},
|
||||
'collapseSpacesInsideElements': {
|
||||
description: 'Preserve only one space when multiple are present',
|
||||
input: '<html><body>Need <span> more </span> space<i> s </i> !<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>Need more space<em> s </em>!<br><br></body></html>',
|
||||
wantText: 'Need more space s !\n\n',
|
||||
},
|
||||
'collapseSpacesAcrossNewlines': {
|
||||
description: 'Newlines and multiple spaces across newlines should be collapsed',
|
||||
input: `
|
||||
<html><body>Need
|
||||
<span> more </span>
|
||||
space
|
||||
<i> s </i>
|
||||
!<br></body></html>`,
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>Need more space <em>s </em>!<br><br></body></html>',
|
||||
wantText: 'Need more space s !\n\n',
|
||||
},
|
||||
'multipleNewLinesAtBeginning': {
|
||||
description: 'Multiple new lines and paragraphs at the beginning should be preserved',
|
||||
input: '<html><body><br><br><p></p><p></p>first line<br><br>second line<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body><br><br><br><br>first line<br><br>second line<br><br></body></html>',
|
||||
wantText: '\n\n\n\nfirst line\n\nsecond line\n\n',
|
||||
},
|
||||
'multiLineParagraph': {
|
||||
description: 'A paragraph with multiple lines should not loose spaces when lines are combined',
|
||||
input: `<html><body>
|
||||
<p>
|
||||
а б в г ґ д е є ж з и і ї й к л м н о
|
||||
п р с т у ф х ц ч ш щ ю я ь
|
||||
</p>
|
||||
</body></html>`,
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>а б в г ґ д е є ж з и і ї й к л м н о п р с т у ф х ц ч ш щ ю я ь<br><br></body></html>',
|
||||
wantText: 'а б в г ґ д е є ж з и і ї й к л м н о п р с т у ф х ц ч ш щ ю я ь\n\n',
|
||||
},
|
||||
'multiLineParagraphWithPre': {
|
||||
// XXX why is there before "in"?
|
||||
description: 'lines in preformatted text should be kept intact',
|
||||
input: `<html><body>
|
||||
<p>
|
||||
а б в г ґ д е є ж з и і ї й к л м н о<pre>multiple
|
||||
lines
|
||||
in
|
||||
pre
|
||||
</pre></p><p>п р с т у ф х ц ч ш щ ю я
|
||||
ь</p>
|
||||
</body></html>`,
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>а б в г ґ д е є ж з и і ї й к л м н о<br>multiple<br> lines<br> in<br> pre<br><br>п р с т у ф х ц ч ш щ ю я ь<br><br></body></html>',
|
||||
wantText: 'а б в г ґ д е є ж з и і ї й к л м н о\nmultiple\n lines\n in\n pre\n\nп р с т у ф х ц ч ш щ ю я ь\n\n',
|
||||
},
|
||||
'preIntroducesASpace': {
|
||||
description: 'pre should be on a new line not preceded by a space',
|
||||
input: `<html><body><p>
|
||||
1
|
||||
<pre>preline
|
||||
</pre></p></body></html>`,
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>1<br>preline<br><br><br></body></html>',
|
||||
wantText: '1\npreline\n\n\n',
|
||||
},
|
||||
'dontDeleteSpaceInsideElements': {
|
||||
description: 'Preserve spaces inside elements',
|
||||
input: '<html><body>Need<span> more </span>space<i> s </i>!<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>Need more space<em> s </em>!<br><br></body></html>',
|
||||
wantText: 'Need more space s !\n\n',
|
||||
},
|
||||
'dontDeleteSpaceOutsideElements': {
|
||||
description: 'Preserve spaces outside elements',
|
||||
input: '<html><body>Need <span>more</span> space <i>s</i> !<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>Need more space <em>s</em> !<br><br></body></html>',
|
||||
wantText: 'Need more space s !\n\n',
|
||||
},
|
||||
'dontDeleteSpaceAtEndOfElement': {
|
||||
description: 'Preserve spaces at the end of an element',
|
||||
input: '<html><body>Need <span>more </span>space <i>s </i>!<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>Need more space <em>s </em>!<br><br></body></html>',
|
||||
wantText: 'Need more space s !\n\n',
|
||||
},
|
||||
'dontDeleteSpaceAtBeginOfElements': {
|
||||
description: 'Preserve spaces at the start of an element',
|
||||
input: '<html><body>Need<span> more</span> space<i> s</i> !<br></body></html>',
|
||||
wantHTML: '<!DOCTYPE HTML><html><body>Need more space<em> s</em> !<br><br></body></html>',
|
||||
wantText: 'Need more space s !\n\n',
|
||||
},
|
||||
};
|
||||
|
||||
describe(__filename, function () {
|
||||
Object.keys(testImports).forEach((testName) => {
|
||||
describe(testName, function () {
|
||||
const testPadId = makeid();
|
||||
const test = testImports[testName];
|
||||
if (test.disabled) {
|
||||
return xit(`DISABLED: ${testName}`, function (done) {
|
||||
done();
|
||||
});
|
||||
}
|
||||
it('createPad', function (done) {
|
||||
api.get(`${endPoint('createPad')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Unable to create new Pad');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
|
||||
it('setHTML', function (done) {
|
||||
api.get(`${endPoint('setHTML')}&padID=${testPadId}&html=${encodeURIComponent(test.input)}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error(`Error:${testName}`);
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
|
||||
it('getHTML', function (done) {
|
||||
api.get(`${endPoint('getHTML')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
const gotHtml = res.body.data.html;
|
||||
if (gotHtml !== test.wantHTML) {
|
||||
throw new Error(`HTML received from export is not the one we were expecting.
|
||||
Test Name:
|
||||
${testName}
|
||||
|
||||
Got:
|
||||
${JSON.stringify(gotHtml)}
|
||||
|
||||
Want:
|
||||
${JSON.stringify(test.wantHTML)}
|
||||
|
||||
Which is a different version of the originally imported one:
|
||||
${test.input}`);
|
||||
}
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
|
||||
it('getText', function (done) {
|
||||
api.get(`${endPoint('getText')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
const gotText = res.body.data.text;
|
||||
if (gotText !== test.wantText) {
|
||||
throw new Error(`Text received from export is not the one we were expecting.
|
||||
Test Name:
|
||||
${testName}
|
||||
|
||||
Got:
|
||||
${JSON.stringify(gotText)}
|
||||
|
||||
Want:
|
||||
${JSON.stringify(test.wantText)}
|
||||
|
||||
Which is a different version of the originally imported one:
|
||||
${test.input}`);
|
||||
}
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function endPoint(point, version) {
|
||||
version = version || apiVersion;
|
||||
return `/api/${version}/${point}?apikey=${apiKey}`;
|
||||
}
|
||||
|
||||
function makeid() {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
369
src/tests/backend/specs/api/importexportGetPost.js
Normal file
369
src/tests/backend/specs/api/importexportGetPost.js
Normal file
|
@ -0,0 +1,369 @@
|
|||
'use strict';
|
||||
|
||||
/*
|
||||
* Import and Export tests for the /p/whateverPadId/import and /p/whateverPadId/export endpoints.
|
||||
*/
|
||||
|
||||
const assert = require('assert').strict;
|
||||
const common = require('../../common');
|
||||
const fs = require('fs');
|
||||
const settings = require('../../../../node/utils/Settings');
|
||||
const superagent = require('superagent');
|
||||
const padManager = require('../../../../node/db/PadManager');
|
||||
const plugins = require('../../../../static/js/pluginfw/plugin_defs');
|
||||
|
||||
const padText = fs.readFileSync('../tests/backend/specs/api/test.txt');
|
||||
const etherpadDoc = fs.readFileSync('../tests/backend/specs/api/test.etherpad');
|
||||
const wordDoc = fs.readFileSync('../tests/backend/specs/api/test.doc');
|
||||
const wordXDoc = fs.readFileSync('../tests/backend/specs/api/test.docx');
|
||||
const odtDoc = fs.readFileSync('../tests/backend/specs/api/test.odt');
|
||||
const pdfDoc = fs.readFileSync('../tests/backend/specs/api/test.pdf');
|
||||
|
||||
let agent;
|
||||
const apiKey = common.apiKey;
|
||||
const apiVersion = 1;
|
||||
const testPadId = makeid();
|
||||
const testPadIdEnc = encodeURIComponent(testPadId);
|
||||
|
||||
describe(__filename, function () {
|
||||
before(async function () { agent = await common.init(); });
|
||||
|
||||
describe('Connectivity', function () {
|
||||
it('can connect', async function () {
|
||||
await agent.get('/api/')
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('API Versioning', function () {
|
||||
it('finds the version tag', async function () {
|
||||
await agent.get('/api/')
|
||||
.expect(200)
|
||||
.expect((res) => assert(res.body.currentVersion));
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
Tests
|
||||
-----
|
||||
|
||||
Test.
|
||||
/ Create a pad
|
||||
/ Set pad contents
|
||||
/ Try export pad in various formats
|
||||
/ Get pad contents and ensure it matches imported contents
|
||||
|
||||
Test.
|
||||
/ Try to export a pad that doesn't exist // Expect failure
|
||||
|
||||
Test.
|
||||
/ Try to import an unsupported file to a pad that exists
|
||||
|
||||
-- TODO: Test.
|
||||
Try to import to a file and abort it half way through
|
||||
|
||||
Test.
|
||||
Try to import to files of varying size.
|
||||
|
||||
Example Curl command for testing import URI:
|
||||
curl -s -v --form file=@/home/jose/test.txt http://127.0.0.1:9001/p/foo/import
|
||||
*/
|
||||
|
||||
describe('Imports and Exports', function () {
|
||||
const backups = {};
|
||||
|
||||
beforeEach(async function () {
|
||||
backups.hooks = {};
|
||||
for (const hookName of ['preAuthorize', 'authenticate', 'authorize']) {
|
||||
backups.hooks[hookName] = plugins.hooks[hookName];
|
||||
plugins.hooks[hookName] = [];
|
||||
}
|
||||
// Note: This is a shallow copy.
|
||||
backups.settings = Object.assign({}, settings);
|
||||
settings.requireAuthentication = false;
|
||||
settings.requireAuthorization = false;
|
||||
settings.users = {user: {password: 'user-password'}};
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
Object.assign(plugins.hooks, backups.hooks);
|
||||
// Note: This does not unset settings that were added.
|
||||
Object.assign(settings, backups.settings);
|
||||
});
|
||||
|
||||
it('creates a new Pad, imports content to it, checks that content', async function () {
|
||||
await agent.get(`${endPoint('createPad')}&padID=${testPadId}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => assert.equal(res.body.code, 0));
|
||||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
||||
.expect(200);
|
||||
await agent.get(`${endPoint('getText')}&padID=${testPadId}`)
|
||||
.expect(200)
|
||||
.expect((res) => assert.equal(res.body.data.text, padText.toString()));
|
||||
});
|
||||
|
||||
it('gets read only pad Id and exports the html and text for this pad', async function () {
|
||||
const ro = await agent.get(`${endPoint('getReadOnlyID')}&padID=${testPadId}`)
|
||||
.expect(200)
|
||||
.expect((res) => assert.ok(JSON.parse(res.text).data.readOnlyID));
|
||||
const readOnlyId = JSON.parse(ro.text).data.readOnlyID;
|
||||
|
||||
await agent.get(`/p/${readOnlyId}/export/html`)
|
||||
.expect(200)
|
||||
.expect((res) => assert(res.text.indexOf('This is the') !== -1));
|
||||
|
||||
await agent.get(`/p/${readOnlyId}/export/txt`)
|
||||
.expect(200)
|
||||
.expect((res) => assert(res.text.indexOf('This is the') !== -1));
|
||||
});
|
||||
|
||||
|
||||
describe('Import/Export tests requiring AbiWord/LibreOffice', function () {
|
||||
before(async function () {
|
||||
if ((!settings.abiword || settings.abiword.indexOf('/') === -1) &&
|
||||
(!settings.soffice || settings.soffice.indexOf('/') === -1)) {
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
|
||||
// For some reason word import does not work in testing..
|
||||
// TODO: fix support for .doc files..
|
||||
it('Tries to import .doc that uses soffice or abiword', async function () {
|
||||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', wordDoc, {filename: '/test.doc', contentType: 'application/msword'})
|
||||
.expect(200)
|
||||
.expect(/FrameCall\('undefined', 'ok'\);/);
|
||||
});
|
||||
|
||||
it('exports DOC', async function () {
|
||||
await agent.get(`/p/${testPadId}/export/doc`)
|
||||
.buffer(true).parse(superagent.parse['application/octet-stream'])
|
||||
.expect(200)
|
||||
.expect((res) => assert(res.body.length >= 9000));
|
||||
});
|
||||
|
||||
it('Tries to import .docx that uses soffice or abiword', async function () {
|
||||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', wordXDoc, {
|
||||
filename: '/test.docx',
|
||||
contentType:
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
})
|
||||
.expect(200)
|
||||
.expect(/FrameCall\('undefined', 'ok'\);/);
|
||||
});
|
||||
|
||||
it('exports DOC from imported DOCX', async function () {
|
||||
await agent.get(`/p/${testPadId}/export/doc`)
|
||||
.buffer(true).parse(superagent.parse['application/octet-stream'])
|
||||
.expect(200)
|
||||
.expect((res) => assert(res.body.length >= 9100));
|
||||
});
|
||||
|
||||
it('Tries to import .pdf that uses soffice or abiword', async function () {
|
||||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', pdfDoc, {filename: '/test.pdf', contentType: 'application/pdf'})
|
||||
.expect(200)
|
||||
.expect(/FrameCall\('undefined', 'ok'\);/);
|
||||
});
|
||||
|
||||
it('exports PDF', async function () {
|
||||
await agent.get(`/p/${testPadId}/export/pdf`)
|
||||
.buffer(true).parse(superagent.parse['application/octet-stream'])
|
||||
.expect(200)
|
||||
.expect((res) => assert(res.body.length >= 1000));
|
||||
});
|
||||
|
||||
it('Tries to import .odt that uses soffice or abiword', async function () {
|
||||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', odtDoc, {filename: '/test.odt', contentType: 'application/odt'})
|
||||
.expect(200)
|
||||
.expect(/FrameCall\('undefined', 'ok'\);/);
|
||||
});
|
||||
|
||||
it('exports ODT', async function () {
|
||||
await agent.get(`/p/${testPadId}/export/odt`)
|
||||
.buffer(true).parse(superagent.parse['application/octet-stream'])
|
||||
.expect(200)
|
||||
.expect((res) => assert(res.body.length >= 7000));
|
||||
});
|
||||
}); // End of AbiWord/LibreOffice tests.
|
||||
|
||||
it('Tries to import .etherpad', async function () {
|
||||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', etherpadDoc, {
|
||||
filename: '/test.etherpad',
|
||||
contentType: 'application/etherpad',
|
||||
})
|
||||
.expect(200)
|
||||
.expect(/FrameCall\('true', 'ok'\);/);
|
||||
});
|
||||
|
||||
it('exports Etherpad', async function () {
|
||||
await agent.get(`/p/${testPadId}/export/etherpad`)
|
||||
.buffer(true).parse(superagent.parse.text)
|
||||
.expect(200)
|
||||
.expect(/hello/);
|
||||
});
|
||||
|
||||
it('exports HTML for this Etherpad file', async function () {
|
||||
await agent.get(`/p/${testPadId}/export/html`)
|
||||
.expect(200)
|
||||
.expect('content-type', 'text/html; charset=utf-8')
|
||||
.expect(/<ul class="bullet"><li><ul class="bullet"><li>hello<\/ul><\/li><\/ul>/);
|
||||
});
|
||||
|
||||
it('Tries to import unsupported file type', async function () {
|
||||
settings.allowUnknownFileEnds = false;
|
||||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', padText, {filename: '/test.xasdasdxx', contentType: 'weirdness/jobby'})
|
||||
.expect(200)
|
||||
.expect((res) => assert.doesNotMatch(res.text, /FrameCall\('undefined', 'ok'\);/));
|
||||
});
|
||||
|
||||
describe('Import authorization checks', function () {
|
||||
let authorize;
|
||||
|
||||
const deleteTestPad = async () => {
|
||||
if (await padManager.doesPadExist(testPadId)) {
|
||||
const pad = await padManager.getPad(testPadId);
|
||||
await pad.remove();
|
||||
}
|
||||
};
|
||||
|
||||
const createTestPad = async (text) => {
|
||||
const pad = await padManager.getPad(testPadId);
|
||||
if (text) await pad.setText(text);
|
||||
return pad;
|
||||
};
|
||||
|
||||
beforeEach(async function () {
|
||||
await deleteTestPad();
|
||||
settings.requireAuthorization = true;
|
||||
authorize = () => true;
|
||||
plugins.hooks.authorize = [{hook_fn: (hookName, {req}, cb) => cb([authorize(req)])}];
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
await deleteTestPad();
|
||||
});
|
||||
|
||||
it('!authn !exist -> create', async function () {
|
||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
||||
.expect(200);
|
||||
assert(await padManager.doesPadExist(testPadId));
|
||||
const pad = await padManager.getPad(testPadId);
|
||||
assert.equal(pad.text(), padText.toString());
|
||||
});
|
||||
|
||||
it('!authn exist -> replace', async function () {
|
||||
const pad = await createTestPad('before import');
|
||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
||||
.expect(200);
|
||||
assert(await padManager.doesPadExist(testPadId));
|
||||
assert.equal(pad.text(), padText.toString());
|
||||
});
|
||||
|
||||
it('authn anonymous !exist -> fail', async function () {
|
||||
settings.requireAuthentication = true;
|
||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
||||
.expect(401);
|
||||
assert(!(await padManager.doesPadExist(testPadId)));
|
||||
});
|
||||
|
||||
it('authn anonymous exist -> fail', async function () {
|
||||
settings.requireAuthentication = true;
|
||||
const pad = await createTestPad('before import\n');
|
||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
||||
.expect(401);
|
||||
assert.equal(pad.text(), 'before import\n');
|
||||
});
|
||||
|
||||
it('authn user create !exist -> create', async function () {
|
||||
settings.requireAuthentication = true;
|
||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
||||
.auth('user', 'user-password')
|
||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
||||
.expect(200);
|
||||
assert(await padManager.doesPadExist(testPadId));
|
||||
const pad = await padManager.getPad(testPadId);
|
||||
assert.equal(pad.text(), padText.toString());
|
||||
});
|
||||
|
||||
it('authn user modify !exist -> fail', async function () {
|
||||
settings.requireAuthentication = true;
|
||||
authorize = () => 'modify';
|
||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
||||
.auth('user', 'user-password')
|
||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
||||
.expect(403);
|
||||
assert(!(await padManager.doesPadExist(testPadId)));
|
||||
});
|
||||
|
||||
it('authn user readonly !exist -> fail', async function () {
|
||||
settings.requireAuthentication = true;
|
||||
authorize = () => 'readOnly';
|
||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
||||
.auth('user', 'user-password')
|
||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
||||
.expect(403);
|
||||
assert(!(await padManager.doesPadExist(testPadId)));
|
||||
});
|
||||
|
||||
it('authn user create exist -> replace', async function () {
|
||||
settings.requireAuthentication = true;
|
||||
const pad = await createTestPad('before import\n');
|
||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
||||
.auth('user', 'user-password')
|
||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
||||
.expect(200);
|
||||
assert.equal(pad.text(), padText.toString());
|
||||
});
|
||||
|
||||
it('authn user modify exist -> replace', async function () {
|
||||
settings.requireAuthentication = true;
|
||||
authorize = () => 'modify';
|
||||
const pad = await createTestPad('before import\n');
|
||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
||||
.auth('user', 'user-password')
|
||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
||||
.expect(200);
|
||||
assert.equal(pad.text(), padText.toString());
|
||||
});
|
||||
|
||||
it('authn user readonly exist -> fail', async function () {
|
||||
const pad = await createTestPad('before import\n');
|
||||
settings.requireAuthentication = true;
|
||||
authorize = () => 'readOnly';
|
||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
||||
.auth('user', 'user-password')
|
||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
||||
.expect(403);
|
||||
assert.equal(pad.text(), 'before import\n');
|
||||
});
|
||||
});
|
||||
});
|
||||
}); // End of tests.
|
||||
|
||||
|
||||
const endPoint = (point, version) => {
|
||||
version = version || apiVersion;
|
||||
return `/api/${version}/${point}?apikey=${apiKey}`;
|
||||
};
|
||||
|
||||
function makeid() {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
51
src/tests/backend/specs/api/instance.js
Normal file
51
src/tests/backend/specs/api/instance.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Tests for the instance-level APIs
|
||||
*
|
||||
* Section "GLOBAL FUNCTIONS" in src/node/db/API.js
|
||||
*/
|
||||
const common = require('../../common');
|
||||
const settings = require('../../../../node/utils/Settings');
|
||||
const supertest = require('supertest');
|
||||
|
||||
const api = supertest(`http://${settings.ip}:${settings.port}`);
|
||||
|
||||
const apiKey = common.apiKey;
|
||||
const apiVersion = '1.2.14';
|
||||
|
||||
describe(__filename, function () {
|
||||
describe('Connectivity for instance-level API tests', function () {
|
||||
it('can connect', function (done) {
|
||||
api.get('/api/')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStats', function () {
|
||||
it('Gets the stats of a running instance', function (done) {
|
||||
api.get(endPoint('getStats'))
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('getStats() failed');
|
||||
|
||||
if (!(('totalPads' in res.body.data) && (typeof res.body.data.totalPads === 'number'))) {
|
||||
throw new Error(`Response to getStats() does not contain field totalPads, or it's not a number: ${JSON.stringify(res.body.data)}`);
|
||||
}
|
||||
|
||||
if (!(('totalSessions' in res.body.data) && (typeof res.body.data.totalSessions === 'number'))) {
|
||||
throw new Error(`Response to getStats() does not contain field totalSessions, or it's not a number: ${JSON.stringify(res.body.data)}`);
|
||||
}
|
||||
|
||||
if (!(('totalActivePads' in res.body.data) && (typeof res.body.data.totalActivePads === 'number'))) {
|
||||
throw new Error(`Response to getStats() does not contain field totalActivePads, or it's not a number: ${JSON.stringify(res.body.data)}`);
|
||||
}
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var endPoint = function (point, version) {
|
||||
version = version || apiVersion;
|
||||
return `/api/${version}/${point}?apikey=${apiKey}`;
|
||||
};
|
845
src/tests/backend/specs/api/pad.js
Normal file
845
src/tests/backend/specs/api/pad.js
Normal file
|
@ -0,0 +1,845 @@
|
|||
/*
|
||||
* ACHTUNG: there is a copied & modified version of this file in
|
||||
* <basedir>/tests/container/specs/api/pad.js
|
||||
*
|
||||
* TODO: unify those two files, and merge in a single one.
|
||||
*/
|
||||
|
||||
const async = require('async');
|
||||
const common = require('../../common');
|
||||
const settings = require('../../../../node/utils/Settings');
|
||||
const supertest = require('supertest');
|
||||
|
||||
const api = supertest(`http://${settings.ip}:${settings.port}`);
|
||||
|
||||
const apiKey = common.apiKey;
|
||||
let apiVersion = 1;
|
||||
const testPadId = makeid();
|
||||
let lastEdited = '';
|
||||
const text = generateLongText();
|
||||
|
||||
/*
|
||||
* Html document with nested lists of different types, to test its import and
|
||||
* verify it is exported back correctly
|
||||
*/
|
||||
const ulHtml = '<!doctype html><html><body><ul class="bullet"><li>one</li><li>two</li><li>0</li><li>1</li><li>2<ul class="bullet"><li>3</li><li>4</li></ul></li></ul><ol class="number"><li>item<ol class="number"><li>item1</li><li>item2</li></ol></li></ol></body></html>';
|
||||
|
||||
/*
|
||||
* When exported back, Etherpad produces an html which is not exactly the same
|
||||
* textually, but at least it remains standard compliant and has an equal DOM
|
||||
* structure.
|
||||
*/
|
||||
const expectedHtml = '<!doctype html><html><body><ul class="bullet"><li>one</li><li>two</li><li>0</li><li>1</li><li>2<ul class="bullet"><li>3</li><li>4</ul></li></ul><ol start="1" class="number"><li>item<ol start="2" class="number"><li>item1</li><li>item2</ol></li></ol></body></html>';
|
||||
|
||||
/*
|
||||
* Html document with space between list items, to test its import and
|
||||
* verify it is exported back correctly
|
||||
*/
|
||||
const ulSpaceHtml = '<!doctype html><html><body><ul class="bullet"> <li>one</li></ul></body></html>';
|
||||
|
||||
/*
|
||||
* When exported back, Etherpad produces an html which is not exactly the same
|
||||
* textually, but at least it remains standard compliant and has an equal DOM
|
||||
* structure.
|
||||
*/
|
||||
const expectedSpaceHtml = '<!doctype html><html><body><ul class="bullet"><li>one</ul></body></html>';
|
||||
|
||||
describe(__filename, function () {
|
||||
describe('Connectivity', function () {
|
||||
it('can connect', function (done) {
|
||||
api.get('/api/')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('API Versioning', function () {
|
||||
it('finds the version tag', function (done) {
|
||||
api.get('/api/')
|
||||
.expect((res) => {
|
||||
apiVersion = res.body.currentVersion;
|
||||
if (!res.body.currentVersion) throw new Error('No version set in API');
|
||||
return;
|
||||
})
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Permission', function () {
|
||||
it('errors with invalid APIKey', function (done) {
|
||||
// This is broken because Etherpad doesn't handle HTTP codes properly see #2343
|
||||
// If your APIKey is password you deserve to fail all tests anyway
|
||||
const permErrorURL = `/api/${apiVersion}/createPad?apikey=password&padID=test`;
|
||||
api.get(permErrorURL)
|
||||
.expect(401, done);
|
||||
});
|
||||
});
|
||||
|
||||
/* Pad Tests Order of execution
|
||||
-> deletePad -- This gives us a guaranteed clear environment
|
||||
-> createPad
|
||||
-> getRevisions -- Should be 0
|
||||
-> getSavedRevisionsCount(padID) -- Should be 0
|
||||
-> listSavedRevisions(padID) -- Should be an empty array
|
||||
-> getHTML -- Should be the default pad text in HTML format
|
||||
-> deletePad -- Should just delete a pad
|
||||
-> getHTML -- Should return an error
|
||||
-> createPad(withText)
|
||||
-> getText -- Should have the text specified above as the pad text
|
||||
-> setText
|
||||
-> getText -- Should be the text set before
|
||||
-> getRevisions -- Should be 0 still?
|
||||
-> saveRevision
|
||||
-> getSavedRevisionsCount(padID) -- Should be 0 still?
|
||||
-> listSavedRevisions(padID) -- Should be an empty array still ?
|
||||
-> padUsersCount -- Should be 0
|
||||
-> getReadOnlyId -- Should be a value
|
||||
-> listAuthorsOfPad(padID) -- should be empty array?
|
||||
-> getLastEdited(padID) -- Should be when pad was made
|
||||
-> setText(padId)
|
||||
-> getLastEdited(padID) -- Should be when setText was performed
|
||||
-> padUsers(padID) -- Should be when setText was performed
|
||||
|
||||
-> setText(padId, "hello world")
|
||||
-> getLastEdited(padID) -- Should be when pad was made
|
||||
-> getText(padId) -- Should be "hello world"
|
||||
-> movePad(padID, newPadId) -- Should provide consistent pad data
|
||||
-> getText(newPadId) -- Should be "hello world"
|
||||
-> movePad(newPadID, originalPadId) -- Should provide consistent pad data
|
||||
-> getText(originalPadId) -- Should be "hello world"
|
||||
-> getLastEdited(padID) -- Should not be 0
|
||||
-> appendText(padID, "hello")
|
||||
-> getText(padID) -- Should be "hello worldhello"
|
||||
-> setHTML(padID) -- Should fail on invalid HTML
|
||||
-> setHTML(padID) *3 -- Should fail on invalid HTML
|
||||
-> getHTML(padID) -- Should return HTML close to posted HTML
|
||||
-> createPad -- Tries to create pads with bad url characters
|
||||
|
||||
*/
|
||||
|
||||
describe('deletePad', function () {
|
||||
it('deletes a Pad', function (done) {
|
||||
api.get(`${endPoint('deletePad')}&padID=${testPadId}`)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done); // @TODO: we shouldn't expect 200 here since the pad may not exist
|
||||
});
|
||||
});
|
||||
|
||||
describe('createPad', function () {
|
||||
it('creates a new Pad', function (done) {
|
||||
api.get(`${endPoint('createPad')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Unable to create new Pad');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRevisionsCount', function () {
|
||||
it('gets revision count of Pad', function (done) {
|
||||
api.get(`${endPoint('getRevisionsCount')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Unable to get Revision Count');
|
||||
if (res.body.data.revisions !== 0) throw new Error('Incorrect Revision Count');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSavedRevisionsCount', function () {
|
||||
it('gets saved revisions count of Pad', function (done) {
|
||||
api.get(`${endPoint('getSavedRevisionsCount')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Unable to get Saved Revisions Count');
|
||||
if (res.body.data.savedRevisions !== 0) throw new Error('Incorrect Saved Revisions Count');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listSavedRevisions', function () {
|
||||
it('gets saved revision list of Pad', function (done) {
|
||||
api.get(`${endPoint('listSavedRevisions')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Unable to get Saved Revisions List');
|
||||
if (!res.body.data.savedRevisions.equals([])) throw new Error('Incorrect Saved Revisions List');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHTML', function () {
|
||||
it('get the HTML of Pad', function (done) {
|
||||
api.get(`${endPoint('getHTML')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.data.html.length <= 1) throw new Error('Unable to get the HTML');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listAllPads', function () {
|
||||
it('list all pads', function (done) {
|
||||
api.get(endPoint('listAllPads'))
|
||||
.expect((res) => {
|
||||
if (res.body.data.padIDs.includes(testPadId) !== true) {
|
||||
throw new Error('Unable to find pad in pad list');
|
||||
}
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deletePad', function () {
|
||||
it('deletes a Pad', function (done) {
|
||||
api.get(`${endPoint('deletePad')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Pad Deletion failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listAllPads', function () {
|
||||
it('list all pads', function (done) {
|
||||
api.get(endPoint('listAllPads'))
|
||||
.expect((res) => {
|
||||
if (res.body.data.padIDs.includes(testPadId) !== false) {
|
||||
throw new Error('Test pad should not be in pads list');
|
||||
}
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHTML', function () {
|
||||
it('get the HTML of a Pad -- Should return a failure', function (done) {
|
||||
api.get(`${endPoint('getHTML')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 1) throw new Error('Pad deletion failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createPad', function () {
|
||||
it('creates a new Pad with text', function (done) {
|
||||
api.get(`${endPoint('createPad')}&padID=${testPadId}&text=testText`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Pad Creation failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getText', function () {
|
||||
it('gets the Pad text and expect it to be testText with \n which is a line break', function (done) {
|
||||
api.get(`${endPoint('getText')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.data.text !== 'testText\n') throw new Error('Pad Creation with text');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setText', function () {
|
||||
it('creates a new Pad with text', function (done) {
|
||||
api.post(endPoint('setText'))
|
||||
.send({
|
||||
padID: testPadId,
|
||||
text: 'testTextTwo',
|
||||
})
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Pad setting text failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getText', function () {
|
||||
it('gets the Pad text', function (done) {
|
||||
api.get(`${endPoint('getText')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.data.text !== 'testTextTwo\n') throw new Error('Setting Text');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRevisionsCount', function () {
|
||||
it('gets Revision Count of a Pad', function (done) {
|
||||
api.get(`${endPoint('getRevisionsCount')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.data.revisions !== 1) throw new Error('Unable to get text revision count');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('saveRevision', function () {
|
||||
it('saves Revision', function (done) {
|
||||
api.get(`${endPoint('saveRevision')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Unable to save Revision');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSavedRevisionsCount', function () {
|
||||
it('gets saved revisions count of Pad', function (done) {
|
||||
api.get(`${endPoint('getSavedRevisionsCount')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Unable to get Saved Revisions Count');
|
||||
if (res.body.data.savedRevisions !== 1) throw new Error('Incorrect Saved Revisions Count');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listSavedRevisions', function () {
|
||||
it('gets saved revision list of Pad', function (done) {
|
||||
api.get(`${endPoint('listSavedRevisions')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Unable to get Saved Revisions List');
|
||||
if (!res.body.data.savedRevisions.equals([1])) throw new Error('Incorrect Saved Revisions List');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
describe('padUsersCount', function () {
|
||||
it('gets User Count of a Pad', function (done) {
|
||||
api.get(`${endPoint('padUsersCount')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.data.padUsersCount !== 0) throw new Error('Incorrect Pad User count');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getReadOnlyID', function () {
|
||||
it('Gets the Read Only ID of a Pad', function (done) {
|
||||
api.get(`${endPoint('getReadOnlyID')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (!res.body.data.readOnlyID) throw new Error('No Read Only ID for Pad');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listAuthorsOfPad', function () {
|
||||
it('Get Authors of the Pad', function (done) {
|
||||
api.get(`${endPoint('listAuthorsOfPad')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.data.authorIDs.length !== 0) throw new Error('# of Authors of pad is not 0');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLastEdited', function () {
|
||||
it('Get When Pad was left Edited', function (done) {
|
||||
api.get(`${endPoint('getLastEdited')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (!res.body.data.lastEdited) {
|
||||
throw new Error('# of Authors of pad is not 0');
|
||||
} else {
|
||||
lastEdited = res.body.data.lastEdited;
|
||||
}
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setText', function () {
|
||||
it('creates a new Pad with text', function (done) {
|
||||
api.post(endPoint('setText'))
|
||||
.send({
|
||||
padID: testPadId,
|
||||
text: 'testTextTwo',
|
||||
})
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Pad setting text failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLastEdited', function () {
|
||||
it('Get When Pad was left Edited', function (done) {
|
||||
api.get(`${endPoint('getLastEdited')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.data.lastEdited <= lastEdited) {
|
||||
throw new Error('Editing A Pad is not updating when it was last edited');
|
||||
}
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('padUsers', function () {
|
||||
it('gets User Count of a Pad', function (done) {
|
||||
api.get(`${endPoint('padUsers')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.data.padUsers.length !== 0) throw new Error('Incorrect Pad Users');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deletePad', function () {
|
||||
it('deletes a Pad', function (done) {
|
||||
api.get(`${endPoint('deletePad')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Pad Deletion failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
const originalPadId = testPadId;
|
||||
const newPadId = makeid();
|
||||
const copiedPadId = makeid();
|
||||
|
||||
describe('createPad', function () {
|
||||
it('creates a new Pad with text', function (done) {
|
||||
api.get(`${endPoint('createPad')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Pad Creation failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setText', function () {
|
||||
it('Sets text on a pad Id', function (done) {
|
||||
api.post(`${endPoint('setText')}&padID=${testPadId}`)
|
||||
.field({text})
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Pad Set Text failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getText', function () {
|
||||
it('Gets text on a pad Id', function (done) {
|
||||
api.get(`${endPoint('getText')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Pad Get Text failed');
|
||||
if (res.body.data.text !== `${text}\n`) throw new Error('Pad Text not set properly');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setText', function () {
|
||||
it('Sets text on a pad Id including an explicit newline', function (done) {
|
||||
api.post(`${endPoint('setText')}&padID=${testPadId}`)
|
||||
.field({text: `${text}\n`})
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Pad Set Text failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getText', function () {
|
||||
it("Gets text on a pad Id and doesn't have an excess newline", function (done) {
|
||||
api.get(`${endPoint('getText')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Pad Get Text failed');
|
||||
if (res.body.data.text !== `${text}\n`) throw new Error('Pad Text not set properly');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLastEdited', function () {
|
||||
it('Gets when pad was last edited', function (done) {
|
||||
api.get(`${endPoint('getLastEdited')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.lastEdited === 0) throw new Error('Get Last Edited Failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('movePad', function () {
|
||||
it('Move a Pad to a different Pad ID', function (done) {
|
||||
api.get(`${endPoint('movePad')}&sourceID=${testPadId}&destinationID=${newPadId}&force=true`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Moving Pad Failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getText', function () {
|
||||
it('Gets text on a pad Id', function (done) {
|
||||
api.get(`${endPoint('getText')}&padID=${newPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.data.text !== `${text}\n`) throw new Error('Pad Get Text failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('movePad', function () {
|
||||
it('Move a Pad to a different Pad ID', function (done) {
|
||||
api.get(`${endPoint('movePad')}&sourceID=${newPadId}&destinationID=${testPadId}&force=false`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Moving Pad Failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getText', function () {
|
||||
it('Gets text on a pad Id', function (done) {
|
||||
api.get(`${endPoint('getText')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.data.text !== `${text}\n`) throw new Error('Pad Get Text failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLastEdited', function () {
|
||||
it('Gets when pad was last edited', function (done) {
|
||||
api.get(`${endPoint('getLastEdited')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.lastEdited === 0) throw new Error('Get Last Edited Failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('appendText', function () {
|
||||
it('Append text to a pad Id', function (done) {
|
||||
api.get(`${endPoint('appendText', '1.2.13')}&padID=${testPadId}&text=hello`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Pad Append Text failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getText', function () {
|
||||
it('Gets text on a pad Id', function (done) {
|
||||
api.get(`${endPoint('getText')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Pad Get Text failed');
|
||||
if (res.body.data.text !== `${text}hello\n`) throw new Error('Pad Text not set properly');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('setHTML', function () {
|
||||
it('Sets the HTML of a Pad attempting to pass ugly HTML', function (done) {
|
||||
const html = '<div><b>Hello HTML</title></head></div>';
|
||||
api.post(endPoint('setHTML'))
|
||||
.send({
|
||||
padID: testPadId,
|
||||
html,
|
||||
})
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error("Crappy HTML Can't be Imported[we weren't able to sanitize it']");
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setHTML', function () {
|
||||
it('Sets the HTML of a Pad with complex nested lists of different types', function (done) {
|
||||
api.post(endPoint('setHTML'))
|
||||
.send({
|
||||
padID: testPadId,
|
||||
html: ulHtml,
|
||||
})
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('List HTML cant be imported');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHTML', function () {
|
||||
it('Gets back the HTML of a Pad with complex nested lists of different types', function (done) {
|
||||
api.get(`${endPoint('getHTML')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
const receivedHtml = res.body.data.html.replace('<br></body>', '</body>').toLowerCase();
|
||||
|
||||
if (receivedHtml !== expectedHtml) {
|
||||
throw new Error(`HTML received from export is not the one we were expecting.
|
||||
Received:
|
||||
${receivedHtml}
|
||||
|
||||
Expected:
|
||||
${expectedHtml}
|
||||
|
||||
Which is a slightly modified version of the originally imported one:
|
||||
${ulHtml}`);
|
||||
}
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setHTML', function () {
|
||||
it('Sets the HTML of a Pad with white space between list items', function (done) {
|
||||
api.get(`${endPoint('setHTML')}&padID=${testPadId}&html=${ulSpaceHtml}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('List HTML cant be imported');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHTML', function () {
|
||||
it('Gets back the HTML of a Pad with complex nested lists of different types', function (done) {
|
||||
api.get(`${endPoint('getHTML')}&padID=${testPadId}`)
|
||||
.expect((res) => {
|
||||
const receivedHtml = res.body.data.html.replace('<br></body>', '</body>').toLowerCase();
|
||||
if (receivedHtml !== expectedSpaceHtml) {
|
||||
throw new Error(`HTML received from export is not the one we were expecting.
|
||||
Received:
|
||||
${receivedHtml}
|
||||
|
||||
Expected:
|
||||
${expectedSpaceHtml}
|
||||
|
||||
Which is a slightly modified version of the originally imported one:
|
||||
${ulSpaceHtml}`);
|
||||
}
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createPad', function () {
|
||||
it('errors if pad can be created', function (done) {
|
||||
const badUrlChars = ['/', '%23', '%3F', '%26'];
|
||||
async.map(
|
||||
badUrlChars,
|
||||
(badUrlChar, cb) => {
|
||||
api.get(`${endPoint('createPad')}&padID=${badUrlChar}`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 1) throw new Error('Pad with bad characters was created');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.end(cb);
|
||||
},
|
||||
done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('copyPad', function () {
|
||||
it('copies the content of a existent pad', function (done) {
|
||||
api.get(`${endPoint('copyPad')}&sourceID=${testPadId}&destinationID=${copiedPadId}&force=true`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Copy Pad Failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('copyPadWithoutHistory', function () {
|
||||
const sourcePadId = makeid();
|
||||
let newPad;
|
||||
|
||||
before(function (done) {
|
||||
createNewPadWithHtml(sourcePadId, ulHtml, done);
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
newPad = makeid();
|
||||
});
|
||||
|
||||
it('returns a successful response', function (done) {
|
||||
api.get(`${endPoint('copyPadWithoutHistory')}&sourceID=${sourcePadId}&destinationID=${newPad}&force=false`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Copy Pad Without History Failed');
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
|
||||
// this test validates if the source pad's text and attributes are kept
|
||||
it('creates a new pad with the same content as the source pad', function (done) {
|
||||
api.get(`${endPoint('copyPadWithoutHistory')}&sourceID=${sourcePadId}&destinationID=${newPad}&force=false`)
|
||||
.expect((res) => {
|
||||
if (res.body.code !== 0) throw new Error('Copy Pad Without History Failed');
|
||||
})
|
||||
.end(() => {
|
||||
api.get(`${endPoint('getHTML')}&padID=${newPad}`)
|
||||
.expect((res) => {
|
||||
const receivedHtml = res.body.data.html.replace('<br><br></body>', '</body>').toLowerCase();
|
||||
|
||||
if (receivedHtml !== expectedHtml) {
|
||||
throw new Error(`HTML received from export is not the one we were expecting.
|
||||
Received:
|
||||
${receivedHtml}
|
||||
|
||||
Expected:
|
||||
${expectedHtml}
|
||||
|
||||
Which is a slightly modified version of the originally imported one:
|
||||
${ulHtml}`);
|
||||
}
|
||||
})
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
context('when try copy a pad with a group that does not exist', function () {
|
||||
const padId = makeid();
|
||||
const padWithNonExistentGroup = `notExistentGroup$${padId}`;
|
||||
it('throws an error', function (done) {
|
||||
api.get(`${endPoint('copyPadWithoutHistory')}&sourceID=${sourcePadId}&destinationID=${padWithNonExistentGroup}&force=true`)
|
||||
.expect((res) => {
|
||||
// code 1, it means an error has happened
|
||||
if (res.body.code !== 1) throw new Error('It should report an error');
|
||||
})
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
context('when try copy a pad and destination pad already exist', function () {
|
||||
const padIdExistent = makeid();
|
||||
|
||||
before(function (done) {
|
||||
createNewPadWithHtml(padIdExistent, ulHtml, done);
|
||||
});
|
||||
|
||||
context('and force is false', function () {
|
||||
it('throws an error', function (done) {
|
||||
api.get(`${endPoint('copyPadWithoutHistory')}&sourceID=${sourcePadId}&destinationID=${padIdExistent}&force=false`)
|
||||
.expect((res) => {
|
||||
// code 1, it means an error has happened
|
||||
if (res.body.code !== 1) throw new Error('It should report an error');
|
||||
})
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
|
||||
context('and force is true', function () {
|
||||
it('returns a successful response', function (done) {
|
||||
api.get(`${endPoint('copyPadWithoutHistory')}&sourceID=${sourcePadId}&destinationID=${padIdExistent}&force=true`)
|
||||
.expect((res) => {
|
||||
// code 1, it means an error has happened
|
||||
if (res.body.code !== 0) throw new Error('Copy pad without history with force true failed');
|
||||
})
|
||||
.expect(200, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
-> movePadForce Test
|
||||
|
||||
*/
|
||||
|
||||
var createNewPadWithHtml = function (padId, html, cb) {
|
||||
api.get(`${endPoint('createPad')}&padID=${padId}`)
|
||||
.end(() => {
|
||||
api.post(endPoint('setHTML'))
|
||||
.send({
|
||||
padID: padId,
|
||||
html,
|
||||
})
|
||||
.end(cb);
|
||||
});
|
||||
};
|
||||
|
||||
var endPoint = function (point, version) {
|
||||
version = version || apiVersion;
|
||||
return `/api/${version}/${point}?apikey=${apiKey}`;
|
||||
};
|
||||
|
||||
function makeid() {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function generateLongText() {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
for (let i = 0; i < 80000; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
// Need this to compare arrays (listSavedRevisions test)
|
||||
Array.prototype.equals = function (array) {
|
||||
// if the other array is a falsy value, return
|
||||
if (!array) return false;
|
||||
// compare lengths - can save a lot of time
|
||||
if (this.length != array.length) return false;
|
||||
for (let i = 0, l = this.length; i < l; i++) {
|
||||
// Check if we have nested arrays
|
||||
if (this[i] instanceof Array && array[i] instanceof Array) {
|
||||
// recurse into the nested arrays
|
||||
if (!this[i].equals(array[i])) return false;
|
||||
} else if (this[i] != array[i]) {
|
||||
// Warning - two different object instances will never be equal: {x:20} != {x:20}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
377
src/tests/backend/specs/api/sessionsAndGroups.js
Normal file
377
src/tests/backend/specs/api/sessionsAndGroups.js
Normal file
|
@ -0,0 +1,377 @@
|
|||
const assert = require('assert').strict;
|
||||
const common = require('../../common');
|
||||
const settings = require('../../../../node/utils/Settings');
|
||||
const supertest = require('supertest');
|
||||
|
||||
const api = supertest(`http://${settings.ip}:${settings.port}`);
|
||||
|
||||
const apiKey = common.apiKey;
|
||||
let apiVersion = 1;
|
||||
let groupID = '';
|
||||
let authorID = '';
|
||||
let sessionID = '';
|
||||
let padID = makeid();
|
||||
|
||||
describe(__filename, function () {
|
||||
describe('API Versioning', function () {
|
||||
it('errors if can not connect', async function () {
|
||||
await api.get('/api/')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
assert(res.body.currentVersion);
|
||||
apiVersion = res.body.currentVersion;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// BEGIN GROUP AND AUTHOR TESTS
|
||||
// ///////////////////////////////////
|
||||
// ///////////////////////////////////
|
||||
|
||||
/* Tests performed
|
||||
-> createGroup() -- should return a groupID
|
||||
-> listSessionsOfGroup(groupID) -- should be 0
|
||||
-> deleteGroup(groupID)
|
||||
-> createGroupIfNotExistsFor(groupMapper) -- should return a groupID
|
||||
|
||||
-> createAuthor([name]) -- should return an authorID
|
||||
-> createAuthorIfNotExistsFor(authorMapper [, name]) -- should return an authorID
|
||||
-> getAuthorName(authorID) -- should return a name IE "john"
|
||||
|
||||
-> createSession(groupID, authorID, validUntil)
|
||||
-> getSessionInfo(sessionID)
|
||||
-> listSessionsOfGroup(groupID) -- should be 1
|
||||
-> deleteSession(sessionID)
|
||||
-> getSessionInfo(sessionID) -- should have author id etc in
|
||||
|
||||
-> listPads(groupID) -- should be empty array
|
||||
-> createGroupPad(groupID, padName [, text])
|
||||
-> listPads(groupID) -- should be empty array
|
||||
-> getPublicStatus(padId)
|
||||
-> setPublicStatus(padId, status)
|
||||
-> getPublicStatus(padId)
|
||||
|
||||
-> listPadsOfAuthor(authorID)
|
||||
*/
|
||||
|
||||
describe('API: Group creation and deletion', function () {
|
||||
it('createGroup', async function () {
|
||||
await api.get(endPoint('createGroup'))
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert(res.body.data.groupID);
|
||||
groupID = res.body.data.groupID;
|
||||
});
|
||||
});
|
||||
|
||||
it('listSessionsOfGroup for empty group', async function () {
|
||||
await api.get(`${endPoint('listSessionsOfGroup')}&groupID=${groupID}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert.equal(res.body.data, null);
|
||||
});
|
||||
});
|
||||
|
||||
it('deleteGroup', async function () {
|
||||
await api.get(`${endPoint('deleteGroup')}&groupID=${groupID}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
});
|
||||
});
|
||||
|
||||
it('createGroupIfNotExistsFor', async function () {
|
||||
await api.get(`${endPoint('createGroupIfNotExistsFor')}&groupMapper=management`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert(res.body.data.groupID);
|
||||
});
|
||||
});
|
||||
|
||||
// Test coverage for https://github.com/ether/etherpad-lite/issues/4227
|
||||
// Creates a group, creates 2 sessions, 2 pads and then deletes the group.
|
||||
it('createGroup', async function () {
|
||||
await api.get(endPoint('createGroup'))
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert(res.body.data.groupID);
|
||||
groupID = res.body.data.groupID;
|
||||
});
|
||||
});
|
||||
|
||||
it('createAuthor', async function () {
|
||||
await api.get(endPoint('createAuthor'))
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert(res.body.data.authorID);
|
||||
authorID = res.body.data.authorID;
|
||||
});
|
||||
});
|
||||
|
||||
it('createSession', async function () {
|
||||
await api.get(`${endPoint('createSession')
|
||||
}&authorID=${authorID}&groupID=${groupID}&validUntil=999999999999`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert(res.body.data.sessionID);
|
||||
sessionID = res.body.data.sessionID;
|
||||
});
|
||||
});
|
||||
|
||||
it('createSession', async function () {
|
||||
await api.get(`${endPoint('createSession')
|
||||
}&authorID=${authorID}&groupID=${groupID}&validUntil=999999999999`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert(res.body.data.sessionID);
|
||||
sessionID = res.body.data.sessionID;
|
||||
});
|
||||
});
|
||||
|
||||
it('createGroupPad', async function () {
|
||||
await api.get(`${endPoint('createGroupPad')}&groupID=${groupID}&padName=x1234567`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
});
|
||||
});
|
||||
|
||||
it('createGroupPad', async function () {
|
||||
await api.get(`${endPoint('createGroupPad')}&groupID=${groupID}&padName=x12345678`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
});
|
||||
});
|
||||
|
||||
it('deleteGroup', async function () {
|
||||
await api.get(`${endPoint('deleteGroup')}&groupID=${groupID}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
});
|
||||
});
|
||||
// End of coverage for https://github.com/ether/etherpad-lite/issues/4227
|
||||
|
||||
});
|
||||
|
||||
describe('API: Author creation', function () {
|
||||
it('createGroup', async function () {
|
||||
await api.get(endPoint('createGroup'))
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert(res.body.data.groupID);
|
||||
groupID = res.body.data.groupID;
|
||||
});
|
||||
});
|
||||
|
||||
it('createAuthor', async function () {
|
||||
await api.get(endPoint('createAuthor'))
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert(res.body.data.authorID);
|
||||
});
|
||||
});
|
||||
|
||||
it('createAuthor with name', async function () {
|
||||
await api.get(`${endPoint('createAuthor')}&name=john`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert(res.body.data.authorID);
|
||||
authorID = res.body.data.authorID; // we will be this author for the rest of the tests
|
||||
});
|
||||
});
|
||||
|
||||
it('createAuthorIfNotExistsFor', async function () {
|
||||
await api.get(`${endPoint('createAuthorIfNotExistsFor')}&authorMapper=chris`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert(res.body.data.authorID);
|
||||
});
|
||||
});
|
||||
|
||||
it('getAuthorName', async function () {
|
||||
await api.get(`${endPoint('getAuthorName')}&authorID=${authorID}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert.equal(res.body.data, 'john');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('API: Sessions', function () {
|
||||
it('createSession', async function () {
|
||||
await api.get(`${endPoint('createSession')
|
||||
}&authorID=${authorID}&groupID=${groupID}&validUntil=999999999999`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert(res.body.data.sessionID);
|
||||
sessionID = res.body.data.sessionID;
|
||||
});
|
||||
});
|
||||
|
||||
it('getSessionInfo', async function () {
|
||||
await api.get(`${endPoint('getSessionInfo')}&sessionID=${sessionID}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert(res.body.data.groupID);
|
||||
assert(res.body.data.authorID);
|
||||
assert(res.body.data.validUntil);
|
||||
});
|
||||
});
|
||||
|
||||
it('listSessionsOfGroup', async function () {
|
||||
await api.get(`${endPoint('listSessionsOfGroup')}&groupID=${groupID}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert.equal(typeof res.body.data, 'object');
|
||||
});
|
||||
});
|
||||
|
||||
it('deleteSession', async function () {
|
||||
await api.get(`${endPoint('deleteSession')}&sessionID=${sessionID}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
});
|
||||
});
|
||||
|
||||
it('getSessionInfo of deleted session', async function () {
|
||||
await api.get(`${endPoint('getSessionInfo')}&sessionID=${sessionID}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('API: Group pad management', function () {
|
||||
it('listPads', async function () {
|
||||
await api.get(`${endPoint('listPads')}&groupID=${groupID}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert.equal(res.body.data.padIDs.length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
it('createGroupPad', async function () {
|
||||
await api.get(`${endPoint('createGroupPad')}&groupID=${groupID}&padName=${padID}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
padID = res.body.data.padID;
|
||||
});
|
||||
});
|
||||
|
||||
it('listPads after creating a group pad', async function () {
|
||||
await api.get(`${endPoint('listPads')}&groupID=${groupID}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert.equal(res.body.data.padIDs.length, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('API: Pad security', function () {
|
||||
it('getPublicStatus', async function () {
|
||||
await api.get(`${endPoint('getPublicStatus')}&padID=${padID}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert.equal(res.body.data.publicStatus, false);
|
||||
});
|
||||
});
|
||||
|
||||
it('setPublicStatus', async function () {
|
||||
await api.get(`${endPoint('setPublicStatus')}&padID=${padID}&publicStatus=true`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
});
|
||||
});
|
||||
|
||||
it('getPublicStatus after changing public status', async function () {
|
||||
await api.get(`${endPoint('getPublicStatus')}&padID=${padID}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert.equal(res.body.data.publicStatus, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// NOT SURE HOW TO POPULAT THIS /-_-\
|
||||
// /////////////////////////////////////
|
||||
// /////////////////////////////////////
|
||||
|
||||
describe('API: Misc', function () {
|
||||
it('listPadsOfAuthor', async function () {
|
||||
await api.get(`${endPoint('listPadsOfAuthor')}&authorID=${authorID}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 0);
|
||||
assert.equal(res.body.data.padIDs.length, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
const endPoint = function (point) {
|
||||
return `/api/${apiVersion}/${point}?apikey=${apiKey}`;
|
||||
};
|
||||
|
||||
function makeid() {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
BIN
src/tests/backend/specs/api/test.doc
Normal file
BIN
src/tests/backend/specs/api/test.doc
Normal file
Binary file not shown.
BIN
src/tests/backend/specs/api/test.docx
Normal file
BIN
src/tests/backend/specs/api/test.docx
Normal file
Binary file not shown.
1
src/tests/backend/specs/api/test.etherpad
Normal file
1
src/tests/backend/specs/api/test.etherpad
Normal file
|
@ -0,0 +1 @@
|
|||
{"pad:Pd4b1Kgvv9qHZZtj8yzl":{"atext":{"text":"*hello\n","attribs":"*0*1*5*3*4+1*0+5|1+1"},"pool":{"numToAttrib":{"0":["author","a.ElbBWNTxmtRrfFqn"],"1":["insertorder","first"],"2":["list","bullet1"],"3":["lmkr","1"],"4":["start","1"],"5":["list","bullet2"]},"nextNum":6},"head":5,"chatHead":-1,"publicStatus":false,"passwordHash":null,"savedRevisions":[]},"globalAuthor:a.ElbBWNTxmtRrfFqn":{"colorId":50,"name":null,"timestamp":1595111151414,"padIDs":"Pd4b1Kgvv9qHZZtj8yzl"},"pad:Pd4b1Kgvv9qHZZtj8yzl:revs:0":{"changeset":"Z:1>bj|7+bj$Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at https://etherpad.org\n\nWarning: DirtyDB is used. This is fine for testing but not recommended for production. -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n","meta":{"author":"","timestamp":1595111092400,"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0},"atext":{"text":"Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at https://etherpad.org\n\nWarning: DirtyDB is used. This is fine for testing but not recommended for production. -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n\n","attribs":"|8+bk"}}},"pad:Pd4b1Kgvv9qHZZtj8yzl:revs:1":{"changeset":"Z:bk<bj|7-bj$","meta":{"author":"a.ElbBWNTxmtRrfFqn","timestamp":1595111112138}},"pad:Pd4b1Kgvv9qHZZtj8yzl:revs:2":{"changeset":"Z:1>1*0*1*2*3*4+1$*","meta":{"author":"a.ElbBWNTxmtRrfFqn","timestamp":1595111119434}},"pad:Pd4b1Kgvv9qHZZtj8yzl:revs:3":{"changeset":"Z:2>0*5*4=1$","meta":{"author":"a.ElbBWNTxmtRrfFqn","timestamp":1595111127471}},"pad:Pd4b1Kgvv9qHZZtj8yzl:revs:4":{"changeset":"Z:2>2=1*0+2$he","meta":{"author":"a.ElbBWNTxmtRrfFqn","timestamp":1595111128230}},"pad:Pd4b1Kgvv9qHZZtj8yzl:revs:5":{"changeset":"Z:4>3=3*0+3$llo","meta":{"author":"a.ElbBWNTxmtRrfFqn","timestamp":1595111128727}}}
|
BIN
src/tests/backend/specs/api/test.odt
Normal file
BIN
src/tests/backend/specs/api/test.odt
Normal file
Binary file not shown.
BIN
src/tests/backend/specs/api/test.pdf
Normal file
BIN
src/tests/backend/specs/api/test.pdf
Normal file
Binary file not shown.
1
src/tests/backend/specs/api/test.txt
Normal file
1
src/tests/backend/specs/api/test.txt
Normal file
|
@ -0,0 +1 @@
|
|||
This is the contents we insert into pads when testing the import functions. Thanks for using Etherpad!
|
71
src/tests/backend/specs/api/tidy.js
Normal file
71
src/tests/backend/specs/api/tidy.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let TidyHtml;
|
||||
let Settings;
|
||||
const npm = require('npm/lib/npm.js');
|
||||
const nodeify = require('nodeify');
|
||||
|
||||
describe(__filename, function () {
|
||||
describe('tidyHtml', function () {
|
||||
before(function (done) {
|
||||
npm.load({}, (err) => {
|
||||
assert.ok(!err);
|
||||
TidyHtml = require('../../../../node/utils/TidyHtml');
|
||||
Settings = require('../../../../node/utils/Settings');
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
const tidy = (file, callback) => nodeify(TidyHtml.tidy(file), callback);
|
||||
|
||||
it('Tidies HTML', function (done) {
|
||||
// If the user hasn't configured Tidy, we skip this tests as it's required for this test
|
||||
if (!Settings.tidyHtml) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
// Try to tidy up a bad HTML file
|
||||
const tmpDir = os.tmpdir();
|
||||
|
||||
const tmpFile = path.join(tmpDir, `tmp_${Math.floor(Math.random() * 1000000)}.html`);
|
||||
fs.writeFileSync(tmpFile, '<html><body><p>a paragraph</p><li>List without outer UL</li>trailing closing p</p></body></html>');
|
||||
tidy(tmpFile, (err) => {
|
||||
assert.ok(!err);
|
||||
|
||||
// Read the file again
|
||||
const cleanedHtml = fs.readFileSync(tmpFile).toString();
|
||||
|
||||
const expectedHtml = [
|
||||
'<title></title>',
|
||||
'</head>',
|
||||
'<body>',
|
||||
'<p>a paragraph</p>',
|
||||
'<ul>',
|
||||
'<li>List without outer UL</li>',
|
||||
'<li style="list-style: none">trailing closing p</li>',
|
||||
'</ul>',
|
||||
'</body>',
|
||||
'</html>',
|
||||
].join('\n');
|
||||
assert.notStrictEqual(cleanedHtml.indexOf(expectedHtml), -1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can deal with errors', function (done) {
|
||||
// If the user hasn't configured Tidy, we skip this tests as it's required for this test
|
||||
if (!Settings.tidyHtml) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
tidy('/some/none/existing/file.html', (err) => {
|
||||
assert.ok(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue