mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-21 07:56:16 -04:00
tests: Include the filename in the test output
Also some minor consistency cleanups.
This commit is contained in:
parent
50e402193b
commit
3e14016214
13 changed files with 2296 additions and 2276 deletions
|
@ -18,44 +18,46 @@ var apiVersion = 1;
|
||||||
|
|
||||||
var testPadId = makeid();
|
var testPadId = makeid();
|
||||||
|
|
||||||
describe('API Versioning', function() {
|
describe(__filename, function() {
|
||||||
it('errors if can not connect', function(done) {
|
describe('API Versioning', function() {
|
||||||
api
|
it('errors if can not connect', function(done) {
|
||||||
.get('/api/')
|
api
|
||||||
.expect(function(res) {
|
.get('/api/')
|
||||||
apiVersion = res.body.currentVersion;
|
.expect(function(res) {
|
||||||
if (!res.body.currentVersion) throw new Error('No version set in API');
|
apiVersion = res.body.currentVersion;
|
||||||
return;
|
if (!res.body.currentVersion) throw new Error('No version set in API');
|
||||||
})
|
return;
|
||||||
.expect(200, done);
|
})
|
||||||
|
.expect(200, done);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('OpenAPI definition', function() {
|
describe('OpenAPI definition', function() {
|
||||||
it('generates valid openapi definition document', function(done) {
|
it('generates valid openapi definition document', function(done) {
|
||||||
api
|
api
|
||||||
.get('/api/openapi.json')
|
.get('/api/openapi.json')
|
||||||
.expect(function(res) {
|
.expect(function(res) {
|
||||||
const { valid, errors } = validateOpenAPI(res.body, 3);
|
const { valid, errors } = validateOpenAPI(res.body, 3);
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
const prettyErrors = JSON.stringify(errors, null, 2);
|
const prettyErrors = JSON.stringify(errors, null, 2);
|
||||||
throw new Error(`Document is not valid OpenAPI. ${errors.length} validation errors:\n${prettyErrors}`);
|
throw new Error(`Document is not valid OpenAPI. ${errors.length} validation errors:\n${prettyErrors}`);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
})
|
})
|
||||||
.expect(200, done);
|
.expect(200, done);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('jsonp support', function() {
|
describe('jsonp support', function() {
|
||||||
it('supports jsonp calls', function(done) {
|
it('supports jsonp calls', function(done) {
|
||||||
api
|
api
|
||||||
.get(endPoint('createPad') + '&jsonp=jsonp_1&padID=' + testPadId)
|
.get(endPoint('createPad') + '&jsonp=jsonp_1&padID=' + testPadId)
|
||||||
.expect(function(res) {
|
.expect(function(res) {
|
||||||
if (!res.text.match('jsonp_1')) throw new Error('no jsonp call seen');
|
if (!res.text.match('jsonp_1')) throw new Error('no jsonp call seen');
|
||||||
})
|
})
|
||||||
.expect('Content-Type', /javascript/)
|
.expect('Content-Type', /javascript/)
|
||||||
.expect(200, done);
|
.expect(200, done);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -14,76 +14,78 @@ const apiKey = common.apiKey;
|
||||||
var apiVersion = 1;
|
var apiVersion = 1;
|
||||||
var testPadId = makeid();
|
var testPadId = makeid();
|
||||||
|
|
||||||
describe('Connectivity For Character Encoding', function(){
|
describe(__filename, function() {
|
||||||
it('can connect', function(done) {
|
describe('Connectivity For Character Encoding', function() {
|
||||||
api.get('/api/')
|
it('can connect', function(done) {
|
||||||
.expect('Content-Type', /json/)
|
api.get('/api/')
|
||||||
.expect(200, done)
|
.expect('Content-Type', /json/)
|
||||||
});
|
.expect(200, done)
|
||||||
})
|
|
||||||
|
|
||||||
describe('API Versioning', function(){
|
|
||||||
it('finds the version tag', function(done) {
|
|
||||||
api.get('/api/')
|
|
||||||
.expect(function(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
|
|
||||||
var 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(function(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', function(err, html) {
|
|
||||||
api.post(endPoint('setHTML'))
|
|
||||||
.send({
|
|
||||||
"padID": testPadId,
|
|
||||||
"html": html,
|
|
||||||
})
|
|
||||||
.expect(function(res){
|
|
||||||
if(res.body.code !== 0) throw new Error("Can't set HTML properly");
|
|
||||||
})
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect(200, done);
|
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
})
|
|
||||||
|
|
||||||
describe('getHTML', function(){
|
describe('API Versioning', function() {
|
||||||
it('get the HTML of Pad with emojis', function(done) {
|
it('finds the version tag', function(done) {
|
||||||
api.get(endPoint('getHTML')+"&padID="+testPadId)
|
api.get('/api/')
|
||||||
.expect(function(res){
|
.expect(function(res){
|
||||||
if (res.body.data.html.indexOf("🇼") === -1) {
|
apiVersion = res.body.currentVersion;
|
||||||
throw new Error("Unable to get the HTML");
|
if (!res.body.currentVersion) throw new Error("No version set in API");
|
||||||
}
|
return;
|
||||||
})
|
})
|
||||||
.expect('Content-Type', /json/)
|
.expect(200, done)
|
||||||
.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
|
||||||
|
var 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(function(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', function(err, html) {
|
||||||
|
api.post(endPoint('setHTML'))
|
||||||
|
.send({
|
||||||
|
"padID": testPadId,
|
||||||
|
"html": html,
|
||||||
|
})
|
||||||
|
.expect(function(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(function(res){
|
||||||
|
if (res.body.data.html.indexOf("🇼") === -1) {
|
||||||
|
throw new Error("Unable to get the HTML");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200, done)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
|
|
@ -11,89 +11,91 @@ var authorID = "";
|
||||||
var padID = makeid();
|
var padID = makeid();
|
||||||
var timestamp = Date.now();
|
var timestamp = Date.now();
|
||||||
|
|
||||||
describe('API Versioning', function(){
|
describe(__filename, function() {
|
||||||
it('errors if can not connect', function(done) {
|
describe('API Versioning', function(){
|
||||||
api.get('/api/')
|
it('errors if can not connect', function(done) {
|
||||||
.expect(function(res){
|
api.get('/api/')
|
||||||
apiVersion = res.body.currentVersion;
|
.expect(function(res){
|
||||||
if (!res.body.currentVersion) throw new Error("No version set in API");
|
apiVersion = res.body.currentVersion;
|
||||||
return;
|
if (!res.body.currentVersion) throw new Error("No version set in API");
|
||||||
})
|
return;
|
||||||
.expect(200, done)
|
})
|
||||||
});
|
.expect(200, done)
|
||||||
})
|
});
|
||||||
|
})
|
||||||
|
|
||||||
// BEGIN GROUP AND AUTHOR TESTS
|
// BEGIN GROUP AND AUTHOR TESTS
|
||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
|
|
||||||
/* Tests performed
|
/* Tests performed
|
||||||
-> createPad(padID)
|
-> createPad(padID)
|
||||||
-> createAuthor([name]) -- should return an authorID
|
-> createAuthor([name]) -- should return an authorID
|
||||||
-> appendChatMessage(padID, text, authorID, time)
|
-> appendChatMessage(padID, text, authorID, time)
|
||||||
-> getChatHead(padID)
|
-> getChatHead(padID)
|
||||||
-> getChatHistory(padID)
|
-> getChatHistory(padID)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
describe('createPad', function(){
|
describe('createPad', function(){
|
||||||
it('creates a new Pad', function(done) {
|
it('creates a new Pad', function(done) {
|
||||||
api.get(endPoint('createPad')+"&padID="+padID)
|
api.get(endPoint('createPad')+"&padID="+padID)
|
||||||
.expect(function(res){
|
.expect(function(res){
|
||||||
if(res.body.code !== 0) throw new Error("Unable to create new Pad");
|
if(res.body.code !== 0) throw new Error("Unable to create new Pad");
|
||||||
})
|
})
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200, done)
|
.expect(200, done)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('createAuthor', function(){
|
describe('createAuthor', function(){
|
||||||
it('Creates an author with a name set', function(done) {
|
it('Creates an author with a name set', function(done) {
|
||||||
api.get(endPoint('createAuthor'))
|
api.get(endPoint('createAuthor'))
|
||||||
.expect(function(res){
|
.expect(function(res){
|
||||||
if(res.body.code !== 0 || !res.body.data.authorID) throw new Error("Unable to create author");
|
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
|
authorID = res.body.data.authorID; // we will be this author for the rest of the tests
|
||||||
})
|
})
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200, done)
|
.expect(200, done)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('appendChatMessage', function(){
|
describe('appendChatMessage', function(){
|
||||||
it('Adds a chat message to the pad', function(done) {
|
it('Adds a chat message to the pad', function(done) {
|
||||||
api.get(endPoint('appendChatMessage')+"&padID="+padID+"&text=blalblalbha&authorID="+authorID+"&time="+timestamp)
|
api.get(endPoint('appendChatMessage')+"&padID="+padID+"&text=blalblalbha&authorID="+authorID+"&time="+timestamp)
|
||||||
.expect(function(res){
|
.expect(function(res){
|
||||||
if(res.body.code !== 0) throw new Error("Unable to create chat message");
|
if(res.body.code !== 0) throw new Error("Unable to create chat message");
|
||||||
})
|
})
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200, done)
|
.expect(200, done)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
describe('getChatHead', function(){
|
describe('getChatHead', function(){
|
||||||
it('Gets the head of chat', function(done) {
|
it('Gets the head of chat', function(done) {
|
||||||
api.get(endPoint('getChatHead')+"&padID="+padID)
|
api.get(endPoint('getChatHead')+"&padID="+padID)
|
||||||
.expect(function(res){
|
.expect(function(res){
|
||||||
if(res.body.data.chatHead !== 0) throw new Error("Chat Head Length is wrong");
|
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");
|
if(res.body.code !== 0) throw new Error("Unable to get chat head");
|
||||||
})
|
})
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200, done)
|
.expect(200, done)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getChatHistory', function(){
|
describe('getChatHistory', function(){
|
||||||
it('Gets Chat History of a Pad', function(done) {
|
it('Gets Chat History of a Pad', function(done) {
|
||||||
api.get(endPoint('getChatHistory')+"&padID="+padID)
|
api.get(endPoint('getChatHistory')+"&padID="+padID)
|
||||||
.expect(function(res){
|
.expect(function(res){
|
||||||
if(res.body.data.messages.length !== 1) throw new Error("Chat History Length is wrong");
|
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");
|
if(res.body.code !== 0) throw new Error("Unable to get chat history");
|
||||||
})
|
})
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200, done)
|
.expect(200, done)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
});
|
||||||
|
|
||||||
var endPoint = function(point){
|
var endPoint = function(point){
|
||||||
return '/api/'+apiVersion+'/'+point+'?apikey='+apiKey;
|
return '/api/'+apiVersion+'/'+point+'?apikey='+apiKey;
|
||||||
|
|
|
@ -56,38 +56,39 @@ var testImports = {
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(testImports).forEach(function (testName) {
|
describe(__filename, function() {
|
||||||
var testPadId = makeid();
|
Object.keys(testImports).forEach(function(testName) {
|
||||||
test = testImports[testName];
|
var testPadId = makeid();
|
||||||
describe('createPad', function(){
|
test = testImports[testName];
|
||||||
it('creates a new Pad', function(done) {
|
describe('createPad', function() {
|
||||||
api.get(endPoint('createPad')+"&padID="+testPadId)
|
it('creates a new Pad', function(done) {
|
||||||
.expect(function(res){
|
api.get(endPoint('createPad') + "&padID=" + testPadId)
|
||||||
if(res.body.code !== 0) throw new Error("Unable to create new Pad");
|
.expect(function(res) {
|
||||||
})
|
if(res.body.code !== 0) throw new Error("Unable to create new Pad");
|
||||||
.expect('Content-Type', /json/)
|
})
|
||||||
.expect(200, done)
|
.expect('Content-Type', /json/)
|
||||||
});
|
.expect(200, done)
|
||||||
})
|
});
|
||||||
|
})
|
||||||
|
|
||||||
describe('setHTML', function(){
|
describe('setHTML', function() {
|
||||||
it('Sets the HTML', function(done) {
|
it('Sets the HTML', function(done) {
|
||||||
api.get(endPoint('setHTML')+"&padID="+testPadId+"&html="+test.input)
|
api.get(endPoint('setHTML') + "&padID=" + testPadId + "&html=" + test.input)
|
||||||
.expect(function(res){
|
.expect(function(res) {
|
||||||
if(res.body.code !== 0) throw new Error("Error:"+testName)
|
if(res.body.code !== 0) throw new Error("Error:" + testName)
|
||||||
})
|
})
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200, done)
|
.expect(200, done)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getHTML', function(){
|
describe('getHTML', function() {
|
||||||
it('Gets back the HTML of a Pad', function(done) {
|
it('Gets back the HTML of a Pad', function(done) {
|
||||||
api.get(endPoint('getHTML')+"&padID="+testPadId)
|
api.get(endPoint('getHTML') + "&padID=" + testPadId)
|
||||||
.expect(function(res){
|
.expect(function(res) {
|
||||||
var receivedHtml = res.body.data.html;
|
var receivedHtml = res.body.data.html;
|
||||||
if (receivedHtml !== test.expectedHTML) {
|
if (receivedHtml !== test.expectedHTML) {
|
||||||
throw new Error(`HTML received from export is not the one we were expecting.
|
throw new Error(`HTML received from export is not the one we were expecting.
|
||||||
Test Name:
|
Test Name:
|
||||||
${testName}
|
${testName}
|
||||||
|
|
||||||
|
@ -99,20 +100,20 @@ Object.keys(testImports).forEach(function (testName) {
|
||||||
|
|
||||||
Which is a different version of the originally imported one:
|
Which is a different version of the originally imported one:
|
||||||
${test.input}`);
|
${test.input}`);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200, done)
|
.expect(200, done)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getText', function(){
|
describe('getText', function() {
|
||||||
it('Gets back the Text of a Pad', function(done) {
|
it('Gets back the Text of a Pad', function(done) {
|
||||||
api.get(endPoint('getText')+"&padID="+testPadId)
|
api.get(endPoint('getText') + "&padID=" + testPadId)
|
||||||
.expect(function(res){
|
.expect(function(res) {
|
||||||
var receivedText = res.body.data.text;
|
var receivedText = res.body.data.text;
|
||||||
if (receivedText !== test.expectedText) {
|
if (receivedText !== test.expectedText) {
|
||||||
throw new Error(`Text received from export is not the one we were expecting.
|
throw new Error(`Text received from export is not the one we were expecting.
|
||||||
Test Name:
|
Test Name:
|
||||||
${testName}
|
${testName}
|
||||||
|
|
||||||
|
@ -124,12 +125,13 @@ Object.keys(testImports).forEach(function (testName) {
|
||||||
|
|
||||||
Which is a different version of the originally imported one:
|
Which is a different version of the originally imported one:
|
||||||
${test.input}`);
|
${test.input}`);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200, done)
|
.expect(200, done)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,326 +23,327 @@ var apiVersion = 1;
|
||||||
const testPadId = makeid();
|
const testPadId = makeid();
|
||||||
const testPadIdEnc = encodeURIComponent(testPadId);
|
const testPadIdEnc = encodeURIComponent(testPadId);
|
||||||
|
|
||||||
before(async function() { agent = await common.init(); });
|
describe(__filename, function() {
|
||||||
|
before(async function() { agent = await common.init(); });
|
||||||
|
|
||||||
describe('Connectivity', function(){
|
describe('Connectivity', function(){
|
||||||
it('can connect', async function() {
|
it('can connect', async function() {
|
||||||
await agent.get('/api/')
|
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() {
|
|
||||||
// Note: This is a shallow copy.
|
|
||||||
backups.settings = Object.assign({}, settings);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async function() {
|
|
||||||
// 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(){
|
|
||||||
let ro = await agent.get(endPoint('getReadOnlyID')+"&padID="+testPadId)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => assert.ok(JSON.parse(res.text).data.readOnlyID));
|
|
||||||
let 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(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(200)
|
||||||
.expect(/FrameCall\('undefined', 'ok'\);/);
|
.expect('Content-Type', /json/);
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
|
||||||
it('exports DOC', async function() {
|
describe('API Versioning', function(){
|
||||||
await agent.get(`/p/${testPadId}/export/doc`)
|
it('finds the version tag', async function() {
|
||||||
.buffer(true).parse(superagent.parse['application/octet-stream'])
|
await agent.get('/api/')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect((res) => assert(res.body.length >= 9000));
|
.expect((res) => assert(res.body.currentVersion));
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
|
||||||
it('Tries to import .docx that uses soffice or abiword', async function() {
|
/*
|
||||||
await agent.post(`/p/${testPadId}/import`)
|
Tests
|
||||||
.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() {
|
Test.
|
||||||
await agent.get(`/p/${testPadId}/export/doc`)
|
/ Create a pad
|
||||||
.buffer(true).parse(superagent.parse['application/octet-stream'])
|
/ Set pad contents
|
||||||
.expect(200)
|
/ Try export pad in various formats
|
||||||
.expect((res) => assert(res.body.length >= 9100));
|
/ Get pad contents and ensure it matches imported contents
|
||||||
});
|
|
||||||
|
|
||||||
it('Tries to import .pdf that uses soffice or abiword', async function() {
|
Test.
|
||||||
await agent.post(`/p/${testPadId}/import`)
|
/ Try to export a pad that doesn't exist // Expect failure
|
||||||
.attach('file', pdfDoc, {filename: '/test.pdf', contentType: 'application/pdf'})
|
|
||||||
.expect(200)
|
|
||||||
.expect(/FrameCall\('undefined', 'ok'\);/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('exports PDF', async function() {
|
Test.
|
||||||
await agent.get(`/p/${testPadId}/export/pdf`)
|
/ Try to import an unsupported file to a pad that exists
|
||||||
.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() {
|
-- TODO: Test.
|
||||||
await agent.post(`/p/${testPadId}/import`)
|
Try to import to a file and abort it half way through
|
||||||
.attach('file', odtDoc, {filename: '/test.odt', contentType: 'application/odt'})
|
|
||||||
.expect(200)
|
|
||||||
.expect(/FrameCall\('undefined', 'ok'\);/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('exports ODT', async function() {
|
Test.
|
||||||
await agent.get(`/p/${testPadId}/export/odt`)
|
Try to import to files of varying size.
|
||||||
.buffer(true).parse(superagent.parse['application/octet-stream'])
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => assert(res.body.length >= 7000));
|
|
||||||
});
|
|
||||||
|
|
||||||
}); // End of AbiWord/LibreOffice tests.
|
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
|
||||||
|
*/
|
||||||
|
|
||||||
it('Tries to import .etherpad', async function() {
|
describe('Imports and Exports', function(){
|
||||||
await agent.post(`/p/${testPadId}/import`)
|
const backups = {};
|
||||||
.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() {
|
beforeEach(async function() {
|
||||||
await deleteTestPad();
|
// Note: This is a shallow copy.
|
||||||
settings.requireAuthentication = false;
|
backups.settings = Object.assign({}, settings);
|
||||||
settings.requireAuthorization = true;
|
|
||||||
settings.users = {user: {password: 'user-password'}};
|
|
||||||
authorize = () => true;
|
|
||||||
backups.hooks = {};
|
|
||||||
backups.hooks.authorize = plugins.hooks.authorize || [];
|
|
||||||
plugins.hooks.authorize = [{hook_fn: (hookName, {req}, cb) => cb([authorize(req)])}];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async function() {
|
afterEach(async function() {
|
||||||
await deleteTestPad();
|
// Note: This does not unset settings that were added.
|
||||||
Object.assign(plugins.hooks, backups.hooks);
|
Object.assign(settings, backups.settings);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('!authn !exist -> create', async function() {
|
it('creates a new Pad, imports content to it, checks that content', async function() {
|
||||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
await agent.get(endPoint('createPad') + `&padID=${testPadId}`)
|
||||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
.expect(200)
|
||||||
.expect(200);
|
.expect('Content-Type', /json/)
|
||||||
assert(await padManager.doesPadExist(testPadId));
|
.expect((res) => assert.equal(res.body.code, 0));
|
||||||
const pad = await padManager.getPad(testPadId);
|
await agent.post(`/p/${testPadId}/import`)
|
||||||
assert.equal(pad.text(), padText.toString());
|
.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('!authn exist -> replace', async function() {
|
it('gets read only pad Id and exports the html and text for this pad', async function(){
|
||||||
const pad = await createTestPad('before import');
|
let ro = await agent.get(endPoint('getReadOnlyID')+"&padID="+testPadId)
|
||||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
.expect(200)
|
||||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
.expect((res) => assert.ok(JSON.parse(res.text).data.readOnlyID));
|
||||||
.expect(200);
|
let readOnlyId = JSON.parse(ro.text).data.readOnlyID;
|
||||||
assert(await padManager.doesPadExist(testPadId));
|
|
||||||
assert.equal(pad.text(), padText.toString());
|
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));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('authn anonymous !exist -> fail', async function() {
|
|
||||||
settings.requireAuthentication = true;
|
describe('Import/Export tests requiring AbiWord/LibreOffice', function() {
|
||||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
before(function() {
|
||||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
if ((!settings.abiword || settings.abiword.indexOf('/') === -1) &&
|
||||||
.expect(401);
|
(!settings.soffice || settings.soffice.indexOf('/') === -1)) {
|
||||||
assert(!(await padManager.doesPadExist(testPadId)));
|
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('authn anonymous exist -> fail', async function() {
|
it('exports Etherpad', async function() {
|
||||||
settings.requireAuthentication = true;
|
await agent.get(`/p/${testPadId}/export/etherpad`)
|
||||||
const pad = await createTestPad('before import\n');
|
.buffer(true).parse(superagent.parse.text)
|
||||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
.expect(200)
|
||||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
.expect(/hello/);
|
||||||
.expect(401);
|
|
||||||
assert.equal(pad.text(), 'before import\n');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('authn user create !exist -> create', async function() {
|
it('exports HTML for this Etherpad file', async function() {
|
||||||
settings.requireAuthentication = true;
|
await agent.get(`/p/${testPadId}/export/html`)
|
||||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
.expect(200)
|
||||||
.auth('user', 'user-password')
|
.expect('content-type', 'text/html; charset=utf-8')
|
||||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
.expect(/<ul class="bullet"><li><ul class="bullet"><li>hello<\/ul><\/li><\/ul>/);
|
||||||
.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() {
|
it('Tries to import unsupported file type', async function() {
|
||||||
settings.requireAuthentication = true;
|
settings.allowUnknownFileEnds = false;
|
||||||
authorize = () => 'modify';
|
await agent.post(`/p/${testPadId}/import`)
|
||||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
.attach('file', padText, {filename: '/test.xasdasdxx', contentType: 'weirdness/jobby'})
|
||||||
.auth('user', 'user-password')
|
.expect(200)
|
||||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
.expect((res) => assert.doesNotMatch(res.text, /FrameCall\('undefined', 'ok'\);/));
|
||||||
.expect(403);
|
|
||||||
assert(!(await padManager.doesPadExist(testPadId)));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('authn user readonly !exist -> fail', async function() {
|
describe('Import authorization checks', function() {
|
||||||
settings.requireAuthentication = true;
|
let authorize;
|
||||||
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() {
|
const deleteTestPad = async () => {
|
||||||
settings.requireAuthentication = true;
|
if (await padManager.doesPadExist(testPadId)) {
|
||||||
const pad = await createTestPad('before import\n');
|
const pad = await padManager.getPad(testPadId);
|
||||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
await pad.remove();
|
||||||
.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() {
|
const createTestPad = async (text) => {
|
||||||
settings.requireAuthentication = true;
|
const pad = await padManager.getPad(testPadId);
|
||||||
authorize = () => 'modify';
|
if (text) await pad.setText(text);
|
||||||
const pad = await createTestPad('before import\n');
|
return pad;
|
||||||
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() {
|
beforeEach(async function() {
|
||||||
const pad = await createTestPad('before import\n');
|
await deleteTestPad();
|
||||||
settings.requireAuthentication = true;
|
settings.requireAuthentication = false;
|
||||||
authorize = () => 'readOnly';
|
settings.requireAuthorization = true;
|
||||||
await agent.post(`/p/${testPadIdEnc}/import`)
|
settings.users = {user: {password: 'user-password'}};
|
||||||
.auth('user', 'user-password')
|
authorize = () => true;
|
||||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
backups.hooks = {};
|
||||||
.expect(403);
|
backups.hooks.authorize = plugins.hooks.authorize || [];
|
||||||
assert.equal(pad.text(), 'before import\n');
|
plugins.hooks.authorize = [{hook_fn: (hookName, {req}, cb) => cb([authorize(req)])}];
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async function() {
|
||||||
|
await deleteTestPad();
|
||||||
|
Object.assign(plugins.hooks, backups.hooks);
|
||||||
|
});
|
||||||
|
|
||||||
|
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.
|
}); // End of tests.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,34 +11,36 @@ const api = supertest('http://'+settings.ip+":"+settings.port);
|
||||||
const apiKey = common.apiKey;
|
const apiKey = common.apiKey;
|
||||||
var apiVersion = '1.2.14';
|
var apiVersion = '1.2.14';
|
||||||
|
|
||||||
describe('Connectivity for instance-level API tests', function() {
|
describe(__filename, function() {
|
||||||
it('can connect', function(done) {
|
describe('Connectivity for instance-level API tests', function() {
|
||||||
api.get('/api/')
|
it('can connect', function(done) {
|
||||||
.expect('Content-Type', /json/)
|
api.get('/api/')
|
||||||
.expect(200, done)
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200, done)
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('getStats', function(){
|
describe('getStats', function() {
|
||||||
it('Gets the stats of a running instance', function(done) {
|
it('Gets the stats of a running instance', function(done) {
|
||||||
api.get(endPoint('getStats'))
|
api.get(endPoint('getStats'))
|
||||||
.expect(function(res){
|
.expect(function(res) {
|
||||||
if (res.body.code !== 0) throw new Error("getStats() failed");
|
if (res.body.code !== 0) throw new Error("getStats() failed");
|
||||||
|
|
||||||
if (!(('totalPads' in res.body.data) && (typeof res.body.data.totalPads === 'number'))) {
|
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)}`);
|
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'))) {
|
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)}`);
|
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'))) {
|
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)}`);
|
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('Content-Type', /json/)
|
||||||
.expect(200, done);
|
.expect(200, done);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,273 +11,275 @@ let authorID = '';
|
||||||
let sessionID = '';
|
let sessionID = '';
|
||||||
let padID = makeid();
|
let padID = makeid();
|
||||||
|
|
||||||
describe('API Versioning', function() {
|
describe(__filename, function() {
|
||||||
it('errors if can not connect', async function() {
|
describe('API Versioning', function() {
|
||||||
await api.get('/api/')
|
it('errors if can not connect', async function() {
|
||||||
.expect(200)
|
await api.get('/api/')
|
||||||
.expect((res) => {
|
.expect(200)
|
||||||
assert(res.body.currentVersion);
|
.expect((res) => {
|
||||||
apiVersion = res.body.currentVersion;
|
assert(res.body.currentVersion);
|
||||||
});
|
apiVersion = res.body.currentVersion;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// BEGIN GROUP AND AUTHOR TESTS
|
// BEGIN GROUP AND AUTHOR TESTS
|
||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
|
|
||||||
/* Tests performed
|
/* Tests performed
|
||||||
-> createGroup() -- should return a groupID
|
-> createGroup() -- should return a groupID
|
||||||
-> listSessionsOfGroup(groupID) -- should be 0
|
-> listSessionsOfGroup(groupID) -- should be 0
|
||||||
-> deleteGroup(groupID)
|
-> deleteGroup(groupID)
|
||||||
-> createGroupIfNotExistsFor(groupMapper) -- should return a groupID
|
-> createGroupIfNotExistsFor(groupMapper) -- should return a groupID
|
||||||
|
|
||||||
-> createAuthor([name]) -- should return an authorID
|
-> createAuthor([name]) -- should return an authorID
|
||||||
-> createAuthorIfNotExistsFor(authorMapper [, name]) -- should return an authorID
|
-> createAuthorIfNotExistsFor(authorMapper [, name]) -- should return an authorID
|
||||||
-> getAuthorName(authorID) -- should return a name IE "john"
|
-> getAuthorName(authorID) -- should return a name IE "john"
|
||||||
|
|
||||||
-> createSession(groupID, authorID, validUntil)
|
-> createSession(groupID, authorID, validUntil)
|
||||||
-> getSessionInfo(sessionID)
|
-> getSessionInfo(sessionID)
|
||||||
-> listSessionsOfGroup(groupID) -- should be 1
|
-> listSessionsOfGroup(groupID) -- should be 1
|
||||||
-> deleteSession(sessionID)
|
-> deleteSession(sessionID)
|
||||||
-> getSessionInfo(sessionID) -- should have author id etc in
|
-> getSessionInfo(sessionID) -- should have author id etc in
|
||||||
|
|
||||||
-> listPads(groupID) -- should be empty array
|
|
||||||
-> createGroupPad(groupID, padName [, text])
|
|
||||||
-> listPads(groupID) -- should be empty array
|
-> listPads(groupID) -- should be empty array
|
||||||
-> getPublicStatus(padId)
|
-> createGroupPad(groupID, padName [, text])
|
||||||
-> setPublicStatus(padId, status)
|
-> listPads(groupID) -- should be empty array
|
||||||
-> getPublicStatus(padId)
|
-> getPublicStatus(padId)
|
||||||
|
-> setPublicStatus(padId, status)
|
||||||
|
-> getPublicStatus(padId)
|
||||||
|
|
||||||
-> listPadsOfAuthor(authorID)
|
-> listPadsOfAuthor(authorID)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
describe('API: Group creation and deletion', function() {
|
describe('API: Group creation and deletion', function() {
|
||||||
it('createGroup', async function() {
|
it('createGroup', async function() {
|
||||||
await api.get(endPoint('createGroup'))
|
await api.get(endPoint('createGroup'))
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect((res) => {
|
.expect((res) => {
|
||||||
assert.equal(res.body.code, 0);
|
assert.equal(res.body.code, 0);
|
||||||
assert(res.body.data.groupID);
|
assert(res.body.data.groupID);
|
||||||
groupID = 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('listSessionsOfGroup for empty group', async function() {
|
describe('API: Author creation', function() {
|
||||||
await api.get(endPoint('listSessionsOfGroup') + `&groupID=${groupID}`)
|
it('createGroup', async function() {
|
||||||
.expect(200)
|
await api.get(endPoint('createGroup'))
|
||||||
.expect('Content-Type', /json/)
|
.expect(200)
|
||||||
.expect((res) => {
|
.expect('Content-Type', /json/)
|
||||||
assert.equal(res.body.code, 0);
|
.expect((res) => {
|
||||||
assert.equal(res.body.data, null);
|
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');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('deleteGroup', async function() {
|
describe('API: Sessions', function() {
|
||||||
await api.get(endPoint('deleteGroup') + `&groupID=${groupID}`)
|
it('createSession', async function() {
|
||||||
.expect(200)
|
await api.get(endPoint('createSession') +
|
||||||
.expect('Content-Type', /json/)
|
`&authorID=${authorID}&groupID=${groupID}&validUntil=999999999999`)
|
||||||
.expect((res) => {
|
.expect(200)
|
||||||
assert.equal(res.body.code, 0);
|
.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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('createGroupIfNotExistsFor', async function() {
|
describe('API: Group pad management', function() {
|
||||||
await api.get(endPoint('createGroupIfNotExistsFor') + '&groupMapper=management')
|
it('listPads', async function() {
|
||||||
.expect(200)
|
await api.get(endPoint('listPads') + `&groupID=${groupID}`)
|
||||||
.expect('Content-Type', /json/)
|
.expect(200)
|
||||||
.expect((res) => {
|
.expect('Content-Type', /json/)
|
||||||
assert.equal(res.body.code, 0);
|
.expect((res) => {
|
||||||
assert(res.body.data.groupID);
|
assert.equal(res.body.code, 0);
|
||||||
});
|
assert.equal(res.body.data.padIDs.length, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('API: Author creation', function() {
|
it('createGroupPad', async function() {
|
||||||
it('createGroup', async function() {
|
await api.get(endPoint('createGroupPad') + `&groupID=${groupID}&padName=${padID}`)
|
||||||
await api.get(endPoint('createGroup'))
|
.expect(200)
|
||||||
.expect(200)
|
.expect('Content-Type', /json/)
|
||||||
.expect('Content-Type', /json/)
|
.expect((res) => {
|
||||||
.expect((res) => {
|
assert.equal(res.body.code, 0);
|
||||||
assert.equal(res.body.code, 0);
|
padID = res.body.data.padID;
|
||||||
assert(res.body.data.groupID);
|
});
|
||||||
groupID = res.body.data.groupID;
|
});
|
||||||
});
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('createAuthor', async function() {
|
describe('API: Pad security', function() {
|
||||||
await api.get(endPoint('createAuthor'))
|
it('getPublicStatus', async function() {
|
||||||
.expect(200)
|
await api.get(endPoint('getPublicStatus') + `&padID=${padID}`)
|
||||||
.expect('Content-Type', /json/)
|
.expect(200)
|
||||||
.expect((res) => {
|
.expect('Content-Type', /json/)
|
||||||
assert.equal(res.body.code, 0);
|
.expect((res) => {
|
||||||
assert(res.body.data.authorID);
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('createAuthor with name', async function() {
|
// NOT SURE HOW TO POPULAT THIS /-_-\
|
||||||
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() {
|
describe('API: Misc', function() {
|
||||||
await api.get(endPoint('createAuthorIfNotExistsFor') + '&authorMapper=chris')
|
it('listPadsOfAuthor', async function() {
|
||||||
.expect(200)
|
await api.get(endPoint('listPadsOfAuthor') + `&authorID=${authorID}`)
|
||||||
.expect('Content-Type', /json/)
|
.expect(200)
|
||||||
.expect((res) => {
|
.expect('Content-Type', /json/)
|
||||||
assert.equal(res.body.code, 0);
|
.expect((res) => {
|
||||||
assert(res.body.data.authorID);
|
assert.equal(res.body.code, 0);
|
||||||
});
|
assert.equal(res.body.data.padIDs.length, 0);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,63 +8,65 @@ var assert = require('assert')
|
||||||
var npm = require("../../../../src/node_modules/npm/lib/npm.js");
|
var npm = require("../../../../src/node_modules/npm/lib/npm.js");
|
||||||
var nodeify = require('../../../../src/node_modules/nodeify');
|
var nodeify = require('../../../../src/node_modules/nodeify');
|
||||||
|
|
||||||
describe('tidyHtml', function() {
|
describe(__filename, function() {
|
||||||
before(function(done) {
|
describe('tidyHtml', function() {
|
||||||
npm.load({}, function(err) {
|
before(function(done) {
|
||||||
assert.ok(!err);
|
npm.load({}, function(err) {
|
||||||
TidyHtml = require('../../../../src/node/utils/TidyHtml');
|
assert.ok(!err);
|
||||||
Settings = require('../../../../src/node/utils/Settings');
|
TidyHtml = require('../../../../src/node/utils/TidyHtml');
|
||||||
return done()
|
Settings = require('../../../../src/node/utils/Settings');
|
||||||
|
return done()
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
function tidy(file, callback) {
|
function tidy(file, callback) {
|
||||||
return nodeify(TidyHtml.tidy(file), callback);
|
return 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
|
it('Tidies HTML', function(done) {
|
||||||
const tmpDir = os.tmpdir();
|
// If the user hasn't configured Tidy, we skip this tests as it's required for this test
|
||||||
|
if (!Settings.tidyHtml) {
|
||||||
|
this.skip();
|
||||||
|
}
|
||||||
|
|
||||||
var tmpFile = path.join(tmpDir, 'tmp_' + (Math.floor(Math.random() * 1000000)) + '.html')
|
// Try to tidy up a bad HTML file
|
||||||
fs.writeFileSync(tmpFile, '<html><body><p>a paragraph</p><li>List without outer UL</li>trailing closing p</p></body></html>');
|
const tmpDir = os.tmpdir();
|
||||||
tidy(tmpFile, function(err){
|
|
||||||
assert.ok(!err);
|
|
||||||
|
|
||||||
// Read the file again
|
var tmpFile = path.join(tmpDir, 'tmp_' + (Math.floor(Math.random() * 1000000)) + '.html')
|
||||||
var cleanedHtml = fs.readFileSync(tmpFile).toString();
|
fs.writeFileSync(tmpFile, '<html><body><p>a paragraph</p><li>List without outer UL</li>trailing closing p</p></body></html>');
|
||||||
|
tidy(tmpFile, function(err){
|
||||||
|
assert.ok(!err);
|
||||||
|
|
||||||
var expectedHtml = [
|
// Read the file again
|
||||||
'<title></title>',
|
var cleanedHtml = fs.readFileSync(tmpFile).toString();
|
||||||
'</head>',
|
|
||||||
'<body>',
|
var expectedHtml = [
|
||||||
'<p>a paragraph</p>',
|
'<title></title>',
|
||||||
'<ul>',
|
'</head>',
|
||||||
'<li>List without outer UL</li>',
|
'<body>',
|
||||||
'<li style="list-style: none">trailing closing p</li>',
|
'<p>a paragraph</p>',
|
||||||
'</ul>',
|
'<ul>',
|
||||||
'</body>',
|
'<li>List without outer UL</li>',
|
||||||
'</html>',
|
'<li style="list-style: none">trailing closing p</li>',
|
||||||
].join('\n');
|
'</ul>',
|
||||||
assert.notStrictEqual(cleanedHtml.indexOf(expectedHtml), -1);
|
'</body>',
|
||||||
return done();
|
'</html>',
|
||||||
|
].join('\n');
|
||||||
|
assert.notStrictEqual(cleanedHtml.indexOf(expectedHtml), -1);
|
||||||
|
return done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('can deal with errors', function(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 the user hasn't configured Tidy, we skip this tests as it's required for this test
|
||||||
if (!Settings.tidyHtml) {
|
if (!Settings.tidyHtml) {
|
||||||
this.skip();
|
this.skip();
|
||||||
}
|
}
|
||||||
|
|
||||||
tidy('/some/none/existing/file.html', function(err) {
|
tidy('/some/none/existing/file.html', function(err) {
|
||||||
assert.ok(err);
|
assert.ok(err);
|
||||||
return done();
|
return done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -105,55 +105,53 @@ const tests = {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For each test..
|
describe(__filename, function() {
|
||||||
for (let test in tests){
|
for (let test in tests) {
|
||||||
let testObj = tests[test];
|
let testObj = tests[test];
|
||||||
|
describe(test, function() {
|
||||||
describe(test, function() {
|
if (testObj.disabled) {
|
||||||
if(testObj.disabled){
|
return xit("DISABLED:", test, function(done){
|
||||||
return xit("DISABLED:", test, function(done){
|
done();
|
||||||
done();
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
it(testObj.description, function(done) {
|
|
||||||
var $ = cheerio.load(testObj.html); // Load HTML into Cheerio
|
|
||||||
var doc = $('html')[0]; // Creates a dom-like representation of HTML
|
|
||||||
// Create an empty attribute pool
|
|
||||||
var apool = new AttributePool();
|
|
||||||
// Convert a dom tree into a list of lines and attribute liens
|
|
||||||
// using the content collector object
|
|
||||||
var cc = contentcollector.makeContentCollector(true, null, apool);
|
|
||||||
cc.collectContent(doc);
|
|
||||||
var result = cc.finish();
|
|
||||||
var recievedAttributes = result.lineAttribs;
|
|
||||||
var expectedAttributes = testObj.expectedLineAttribs;
|
|
||||||
var recievedText = new Array(result.lines)
|
|
||||||
var expectedText = testObj.expectedText;
|
|
||||||
|
|
||||||
// Check recieved text matches the expected text
|
|
||||||
if(arraysEqual(recievedText[0], expectedText)){
|
|
||||||
// console.log("PASS: Recieved Text did match Expected Text\nRecieved:", recievedText[0], "\nExpected:", testObj.expectedText)
|
|
||||||
}else{
|
|
||||||
console.error("FAIL: Recieved Text did not match Expected Text\nRecieved:", recievedText[0], "\nExpected:", testObj.expectedText)
|
|
||||||
throw new Error();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check recieved attributes matches the expected attributes
|
it(testObj.description, function(done) {
|
||||||
if(arraysEqual(recievedAttributes, expectedAttributes)){
|
var $ = cheerio.load(testObj.html); // Load HTML into Cheerio
|
||||||
// console.log("PASS: Recieved Attributes matched Expected Attributes");
|
var doc = $('html')[0]; // Creates a dom-like representation of HTML
|
||||||
done();
|
// Create an empty attribute pool
|
||||||
}else{
|
var apool = new AttributePool();
|
||||||
console.error("FAIL", test, testObj.description);
|
// Convert a dom tree into a list of lines and attribute liens
|
||||||
console.error("FAIL: Recieved Attributes did not match Expected Attributes\nRecieved: ", recievedAttributes, "\nExpected: ", expectedAttributes)
|
// using the content collector object
|
||||||
console.error("FAILING HTML", testObj.html);
|
var cc = contentcollector.makeContentCollector(true, null, apool);
|
||||||
throw new Error();
|
cc.collectContent(doc);
|
||||||
}
|
var result = cc.finish();
|
||||||
|
var recievedAttributes = result.lineAttribs;
|
||||||
|
var expectedAttributes = testObj.expectedLineAttribs;
|
||||||
|
var recievedText = new Array(result.lines)
|
||||||
|
var expectedText = testObj.expectedText;
|
||||||
|
|
||||||
|
// Check recieved text matches the expected text
|
||||||
|
if (arraysEqual(recievedText[0], expectedText)) {
|
||||||
|
// console.log("PASS: Recieved Text did match Expected Text\nRecieved:", recievedText[0], "\nExpected:", testObj.expectedText)
|
||||||
|
} else {
|
||||||
|
console.error("FAIL: Recieved Text did not match Expected Text\nRecieved:", recievedText[0], "\nExpected:", testObj.expectedText)
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check recieved attributes matches the expected attributes
|
||||||
|
if (arraysEqual(recievedAttributes, expectedAttributes)) {
|
||||||
|
// console.log("PASS: Recieved Attributes matched Expected Attributes");
|
||||||
|
done();
|
||||||
|
} else {
|
||||||
|
console.error("FAIL", test, testObj.description);
|
||||||
|
console.error("FAIL: Recieved Attributes did not match Expected Attributes\nRecieved: ", recievedAttributes, "\nExpected: ", expectedAttributes)
|
||||||
|
console.error("FAILING HTML", testObj.html);
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,83 +3,85 @@ function m(mod) { return __dirname + '/../../../src/' + mod; }
|
||||||
const assert = require('assert').strict;
|
const assert = require('assert').strict;
|
||||||
const promises = require(m('node/utils/promises'));
|
const promises = require(m('node/utils/promises'));
|
||||||
|
|
||||||
describe('promises.timesLimit', async () => {
|
describe(__filename, function() {
|
||||||
let wantIndex = 0;
|
describe('promises.timesLimit', function() {
|
||||||
const testPromises = [];
|
let wantIndex = 0;
|
||||||
const makePromise = (index) => {
|
const testPromises = [];
|
||||||
// Make sure index increases by one each time.
|
const makePromise = (index) => {
|
||||||
assert.equal(index, wantIndex++);
|
// Make sure index increases by one each time.
|
||||||
// Save the resolve callback (so the test can trigger resolution)
|
assert.equal(index, wantIndex++);
|
||||||
// and the promise itself (to wait for resolve to take effect).
|
// Save the resolve callback (so the test can trigger resolution)
|
||||||
const p = {};
|
// and the promise itself (to wait for resolve to take effect).
|
||||||
const promise = new Promise((resolve) => {
|
const p = {};
|
||||||
p.resolve = resolve;
|
const promise = new Promise((resolve) => {
|
||||||
});
|
p.resolve = resolve;
|
||||||
p.promise = promise;
|
});
|
||||||
testPromises.push(p);
|
p.promise = promise;
|
||||||
return p.promise;
|
testPromises.push(p);
|
||||||
};
|
return p.promise;
|
||||||
|
};
|
||||||
|
|
||||||
const total = 11;
|
const total = 11;
|
||||||
const concurrency = 7;
|
const concurrency = 7;
|
||||||
const timesLimitPromise = promises.timesLimit(total, concurrency, makePromise);
|
|
||||||
|
|
||||||
it('honors concurrency', async () => {
|
|
||||||
assert.equal(wantIndex, concurrency);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('creates another when one completes', async () => {
|
|
||||||
const {promise, resolve} = testPromises.shift();
|
|
||||||
resolve();
|
|
||||||
await promise;
|
|
||||||
assert.equal(wantIndex, concurrency + 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('creates the expected total number of promises', async () => {
|
|
||||||
while (testPromises.length > 0) {
|
|
||||||
// Resolve them in random order to ensure that the resolution order doesn't matter.
|
|
||||||
const i = Math.floor(Math.random() * Math.floor(testPromises.length));
|
|
||||||
const {promise, resolve} = testPromises.splice(i, 1)[0];
|
|
||||||
resolve();
|
|
||||||
await promise;
|
|
||||||
}
|
|
||||||
assert.equal(wantIndex, total);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('resolves', async () => {
|
|
||||||
await timesLimitPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not create too many promises if total < concurrency', async () => {
|
|
||||||
wantIndex = 0;
|
|
||||||
assert.equal(testPromises.length, 0);
|
|
||||||
const total = 7;
|
|
||||||
const concurrency = 11;
|
|
||||||
const timesLimitPromise = promises.timesLimit(total, concurrency, makePromise);
|
const timesLimitPromise = promises.timesLimit(total, concurrency, makePromise);
|
||||||
while (testPromises.length > 0) {
|
|
||||||
const {promise, resolve} = testPromises.pop();
|
it('honors concurrency', async function() {
|
||||||
|
assert.equal(wantIndex, concurrency);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates another when one completes', async function() {
|
||||||
|
const {promise, resolve} = testPromises.shift();
|
||||||
resolve();
|
resolve();
|
||||||
await promise;
|
await promise;
|
||||||
}
|
assert.equal(wantIndex, concurrency + 1);
|
||||||
await timesLimitPromise;
|
});
|
||||||
assert.equal(wantIndex, total);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('accepts total === 0, concurrency > 0', async () => {
|
it('creates the expected total number of promises', async function() {
|
||||||
wantIndex = 0;
|
while (testPromises.length > 0) {
|
||||||
assert.equal(testPromises.length, 0);
|
// Resolve them in random order to ensure that the resolution order doesn't matter.
|
||||||
await promises.timesLimit(0, concurrency, makePromise);
|
const i = Math.floor(Math.random() * Math.floor(testPromises.length));
|
||||||
assert.equal(wantIndex, 0);
|
const {promise, resolve} = testPromises.splice(i, 1)[0];
|
||||||
});
|
resolve();
|
||||||
|
await promise;
|
||||||
|
}
|
||||||
|
assert.equal(wantIndex, total);
|
||||||
|
});
|
||||||
|
|
||||||
it('accepts total === 0, concurrency === 0', async () => {
|
it('resolves', async function() {
|
||||||
wantIndex = 0;
|
await timesLimitPromise;
|
||||||
assert.equal(testPromises.length, 0);
|
});
|
||||||
await promises.timesLimit(0, 0, makePromise);
|
|
||||||
assert.equal(wantIndex, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('rejects total > 0, concurrency === 0', async () => {
|
it('does not create too many promises if total < concurrency', async function() {
|
||||||
await assert.rejects(promises.timesLimit(total, 0, makePromise), RangeError);
|
wantIndex = 0;
|
||||||
|
assert.equal(testPromises.length, 0);
|
||||||
|
const total = 7;
|
||||||
|
const concurrency = 11;
|
||||||
|
const timesLimitPromise = promises.timesLimit(total, concurrency, makePromise);
|
||||||
|
while (testPromises.length > 0) {
|
||||||
|
const {promise, resolve} = testPromises.pop();
|
||||||
|
resolve();
|
||||||
|
await promise;
|
||||||
|
}
|
||||||
|
await timesLimitPromise;
|
||||||
|
assert.equal(wantIndex, total);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts total === 0, concurrency > 0', async function() {
|
||||||
|
wantIndex = 0;
|
||||||
|
assert.equal(testPromises.length, 0);
|
||||||
|
await promises.timesLimit(0, concurrency, makePromise);
|
||||||
|
assert.equal(wantIndex, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts total === 0, concurrency === 0', async function() {
|
||||||
|
wantIndex = 0;
|
||||||
|
assert.equal(testPromises.length, 0);
|
||||||
|
await promises.timesLimit(0, 0, makePromise);
|
||||||
|
assert.equal(wantIndex, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects total > 0, concurrency === 0', async function() {
|
||||||
|
await assert.rejects(promises.timesLimit(total, 0, makePromise), RangeError);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,9 +9,6 @@ const setCookieParser = require(m('node_modules/set-cookie-parser'));
|
||||||
const settings = require(m('node/utils/Settings'));
|
const settings = require(m('node/utils/Settings'));
|
||||||
|
|
||||||
const logger = common.logger;
|
const logger = common.logger;
|
||||||
let agent;
|
|
||||||
|
|
||||||
before(async function() { agent = await common.init(); });
|
|
||||||
|
|
||||||
// Waits for and returns the next named socket.io event. Rejects if there is any error while waiting
|
// Waits for and returns the next named socket.io event. Rejects if there is any error while waiting
|
||||||
// (unless waiting for that error event).
|
// (unless waiting for that error event).
|
||||||
|
@ -92,264 +89,269 @@ const handshake = async (socket, padID) => {
|
||||||
return msg;
|
return msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('socket.io access checks', function() {
|
describe(__filename, function() {
|
||||||
let authorize;
|
let agent;
|
||||||
let authorizeHooksBackup;
|
before(async function() { agent = await common.init(); });
|
||||||
const cleanUpPads = async () => {
|
|
||||||
const padIds = ['pad', 'other-pad', 'päd'];
|
|
||||||
await Promise.all(padIds.map(async (padId) => {
|
|
||||||
if (await padManager.doesPadExist(padId)) {
|
|
||||||
const pad = await padManager.getPad(padId);
|
|
||||||
await pad.remove();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
const settingsBackup = {};
|
|
||||||
let socket;
|
|
||||||
|
|
||||||
beforeEach(async function() {
|
describe('socket.io access checks', function() {
|
||||||
Object.assign(settingsBackup, settings);
|
let authorize;
|
||||||
assert(socket == null);
|
let authorizeHooksBackup;
|
||||||
settings.editOnly = false;
|
const cleanUpPads = async () => {
|
||||||
settings.requireAuthentication = false;
|
const padIds = ['pad', 'other-pad', 'päd'];
|
||||||
settings.requireAuthorization = false;
|
await Promise.all(padIds.map(async (padId) => {
|
||||||
settings.users = {
|
if (await padManager.doesPadExist(padId)) {
|
||||||
admin: {password: 'admin-password', is_admin: true},
|
const pad = await padManager.getPad(padId);
|
||||||
user: {password: 'user-password'},
|
await pad.remove();
|
||||||
|
}
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
authorize = () => true;
|
const settingsBackup = {};
|
||||||
authorizeHooksBackup = plugins.hooks.authorize;
|
let socket;
|
||||||
plugins.hooks.authorize = [{hook_fn: (hookName, {req}, cb) => {
|
|
||||||
return cb([authorize(req)]);
|
|
||||||
}}];
|
|
||||||
await cleanUpPads();
|
|
||||||
});
|
|
||||||
afterEach(async function() {
|
|
||||||
Object.assign(settings, settingsBackup);
|
|
||||||
if (socket) socket.close();
|
|
||||||
socket = null;
|
|
||||||
plugins.hooks.authorize = authorizeHooksBackup;
|
|
||||||
await cleanUpPads();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Normal accesses', function() {
|
|
||||||
it('!authn anonymous cookie /p/pad -> 200, ok', async function() {
|
|
||||||
const res = await agent.get('/p/pad').expect(200);
|
|
||||||
socket = await connect(res);
|
|
||||||
const clientVars = await handshake(socket, 'pad');
|
|
||||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
|
||||||
});
|
|
||||||
it('!authn !cookie -> ok', async function() {
|
|
||||||
socket = await connect(null);
|
|
||||||
const clientVars = await handshake(socket, 'pad');
|
|
||||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
|
||||||
});
|
|
||||||
it('!authn user /p/pad -> 200, ok', async function() {
|
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
|
||||||
socket = await connect(res);
|
|
||||||
const clientVars = await handshake(socket, 'pad');
|
|
||||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
|
||||||
});
|
|
||||||
it('authn user /p/pad -> 200, ok', async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
|
||||||
socket = await connect(res);
|
|
||||||
const clientVars = await handshake(socket, 'pad');
|
|
||||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
|
||||||
});
|
|
||||||
it('authz user /p/pad -> 200, ok', async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
settings.requireAuthorization = true;
|
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
|
||||||
socket = await connect(res);
|
|
||||||
const clientVars = await handshake(socket, 'pad');
|
|
||||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
|
||||||
});
|
|
||||||
it('supports pad names with characters that must be percent-encoded', async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
// requireAuthorization is set to true here to guarantee that the user's padAuthorizations
|
|
||||||
// object is populated. Technically this isn't necessary because the user's padAuthorizations
|
|
||||||
// is currently populated even if requireAuthorization is false, but setting this to true
|
|
||||||
// ensures the test remains useful if the implementation ever changes.
|
|
||||||
settings.requireAuthorization = true;
|
|
||||||
const encodedPadId = encodeURIComponent('päd');
|
|
||||||
const res = await agent.get(`/p/${encodedPadId}`).auth('user', 'user-password').expect(200);
|
|
||||||
socket = await connect(res);
|
|
||||||
const clientVars = await handshake(socket, 'päd');
|
|
||||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Abnormal access attempts', function() {
|
|
||||||
it('authn anonymous /p/pad -> 401, error', async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
const res = await agent.get('/p/pad').expect(401);
|
|
||||||
// Despite the 401, try to create the pad via a socket.io connection anyway.
|
|
||||||
socket = await connect(res);
|
|
||||||
const message = await handshake(socket, 'pad');
|
|
||||||
assert.equal(message.accessStatus, 'deny');
|
|
||||||
});
|
|
||||||
it('authn !cookie -> error', async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
socket = await connect(null);
|
|
||||||
const message = await handshake(socket, 'pad');
|
|
||||||
assert.equal(message.accessStatus, 'deny');
|
|
||||||
});
|
|
||||||
it('authorization bypass attempt -> error', async function() {
|
|
||||||
// Only allowed to access /p/pad.
|
|
||||||
authorize = (req) => req.path === '/p/pad';
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
settings.requireAuthorization = true;
|
|
||||||
// First authenticate and establish a session.
|
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
|
||||||
socket = await connect(res);
|
|
||||||
// Accessing /p/other-pad should fail, despite the successful fetch of /p/pad.
|
|
||||||
const message = await handshake(socket, 'other-pad');
|
|
||||||
assert.equal(message.accessStatus, 'deny');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Authorization levels via authorize hook', function() {
|
|
||||||
beforeEach(async function() {
|
beforeEach(async function() {
|
||||||
settings.requireAuthentication = true;
|
Object.assign(settingsBackup, settings);
|
||||||
settings.requireAuthorization = true;
|
assert(socket == null);
|
||||||
});
|
|
||||||
|
|
||||||
it("level='create' -> can create", async function() {
|
|
||||||
authorize = () => 'create';
|
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
|
||||||
socket = await connect(res);
|
|
||||||
const clientVars = await handshake(socket, 'pad');
|
|
||||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
|
||||||
assert.equal(clientVars.data.readonly, false);
|
|
||||||
});
|
|
||||||
it('level=true -> can create', async function() {
|
|
||||||
authorize = () => true;
|
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
|
||||||
socket = await connect(res);
|
|
||||||
const clientVars = await handshake(socket, 'pad');
|
|
||||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
|
||||||
assert.equal(clientVars.data.readonly, false);
|
|
||||||
});
|
|
||||||
it("level='modify' -> can modify", async function() {
|
|
||||||
await padManager.getPad('pad'); // Create the pad.
|
|
||||||
authorize = () => 'modify';
|
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
|
||||||
socket = await connect(res);
|
|
||||||
const clientVars = await handshake(socket, 'pad');
|
|
||||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
|
||||||
assert.equal(clientVars.data.readonly, false);
|
|
||||||
});
|
|
||||||
it("level='create' settings.editOnly=true -> unable to create", async function() {
|
|
||||||
authorize = () => 'create';
|
|
||||||
settings.editOnly = true;
|
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
|
||||||
socket = await connect(res);
|
|
||||||
const message = await handshake(socket, 'pad');
|
|
||||||
assert.equal(message.accessStatus, 'deny');
|
|
||||||
});
|
|
||||||
it("level='modify' settings.editOnly=false -> unable to create", async function() {
|
|
||||||
authorize = () => 'modify';
|
|
||||||
settings.editOnly = false;
|
settings.editOnly = false;
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
settings.requireAuthentication = false;
|
||||||
socket = await connect(res);
|
settings.requireAuthorization = false;
|
||||||
const message = await handshake(socket, 'pad');
|
settings.users = {
|
||||||
assert.equal(message.accessStatus, 'deny');
|
admin: {password: 'admin-password', is_admin: true},
|
||||||
|
user: {password: 'user-password'},
|
||||||
|
};
|
||||||
|
authorize = () => true;
|
||||||
|
authorizeHooksBackup = plugins.hooks.authorize;
|
||||||
|
plugins.hooks.authorize = [{hook_fn: (hookName, {req}, cb) => {
|
||||||
|
return cb([authorize(req)]);
|
||||||
|
}}];
|
||||||
|
await cleanUpPads();
|
||||||
});
|
});
|
||||||
it("level='readOnly' -> unable to create", async function() {
|
afterEach(async function() {
|
||||||
authorize = () => 'readOnly';
|
Object.assign(settings, settingsBackup);
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
if (socket) socket.close();
|
||||||
socket = await connect(res);
|
socket = null;
|
||||||
const message = await handshake(socket, 'pad');
|
plugins.hooks.authorize = authorizeHooksBackup;
|
||||||
assert.equal(message.accessStatus, 'deny');
|
await cleanUpPads();
|
||||||
});
|
|
||||||
it("level='readOnly' -> unable to modify", async function() {
|
|
||||||
await padManager.getPad('pad'); // Create the pad.
|
|
||||||
authorize = () => 'readOnly';
|
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
|
||||||
socket = await connect(res);
|
|
||||||
const clientVars = await handshake(socket, 'pad');
|
|
||||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
|
||||||
assert.equal(clientVars.data.readonly, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Authorization levels via user settings', function() {
|
|
||||||
beforeEach(async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('user.canCreate = true -> can create and modify', async function() {
|
describe('Normal accesses', function() {
|
||||||
settings.users.user.canCreate = true;
|
it('!authn anonymous cookie /p/pad -> 200, ok', async function() {
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
const res = await agent.get('/p/pad').expect(200);
|
||||||
socket = await connect(res);
|
socket = await connect(res);
|
||||||
const clientVars = await handshake(socket, 'pad');
|
const clientVars = await handshake(socket, 'pad');
|
||||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||||
assert.equal(clientVars.data.readonly, false);
|
});
|
||||||
});
|
it('!authn !cookie -> ok', async function() {
|
||||||
it('user.canCreate = false -> unable to create', async function() {
|
socket = await connect(null);
|
||||||
settings.users.user.canCreate = false;
|
const clientVars = await handshake(socket, 'pad');
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||||
socket = await connect(res);
|
});
|
||||||
const message = await handshake(socket, 'pad');
|
it('!authn user /p/pad -> 200, ok', async function() {
|
||||||
assert.equal(message.accessStatus, 'deny');
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
});
|
socket = await connect(res);
|
||||||
it('user.readOnly = true -> unable to create', async function() {
|
const clientVars = await handshake(socket, 'pad');
|
||||||
settings.users.user.readOnly = true;
|
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
});
|
||||||
socket = await connect(res);
|
it('authn user /p/pad -> 200, ok', async function() {
|
||||||
const message = await handshake(socket, 'pad');
|
settings.requireAuthentication = true;
|
||||||
assert.equal(message.accessStatus, 'deny');
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
});
|
socket = await connect(res);
|
||||||
it('user.readOnly = true -> unable to modify', async function() {
|
const clientVars = await handshake(socket, 'pad');
|
||||||
await padManager.getPad('pad'); // Create the pad.
|
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||||
settings.users.user.readOnly = true;
|
});
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
it('authz user /p/pad -> 200, ok', async function() {
|
||||||
socket = await connect(res);
|
settings.requireAuthentication = true;
|
||||||
const clientVars = await handshake(socket, 'pad');
|
settings.requireAuthorization = true;
|
||||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
assert.equal(clientVars.data.readonly, true);
|
socket = await connect(res);
|
||||||
});
|
const clientVars = await handshake(socket, 'pad');
|
||||||
it('user.readOnly = false -> can create and modify', async function() {
|
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||||
settings.users.user.readOnly = false;
|
});
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
it('supports pad names with characters that must be percent-encoded', async function() {
|
||||||
socket = await connect(res);
|
settings.requireAuthentication = true;
|
||||||
const clientVars = await handshake(socket, 'pad');
|
// requireAuthorization is set to true here to guarantee that the user's padAuthorizations
|
||||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
// object is populated. Technically this isn't necessary because the user's padAuthorizations
|
||||||
assert.equal(clientVars.data.readonly, false);
|
// is currently populated even if requireAuthorization is false, but setting this to true
|
||||||
});
|
// ensures the test remains useful if the implementation ever changes.
|
||||||
it('user.readOnly = true, user.canCreate = true -> unable to create', async function() {
|
settings.requireAuthorization = true;
|
||||||
settings.users.user.canCreate = true;
|
const encodedPadId = encodeURIComponent('päd');
|
||||||
settings.users.user.readOnly = true;
|
const res = await agent.get(`/p/${encodedPadId}`).auth('user', 'user-password').expect(200);
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
socket = await connect(res);
|
||||||
socket = await connect(res);
|
const clientVars = await handshake(socket, 'päd');
|
||||||
const message = await handshake(socket, 'pad');
|
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||||
assert.equal(message.accessStatus, 'deny');
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Authorization level interaction between authorize hook and user settings', function() {
|
|
||||||
beforeEach(async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
settings.requireAuthorization = true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('authorize hook does not elevate level from user settings', async function() {
|
describe('Abnormal access attempts', function() {
|
||||||
settings.users.user.readOnly = true;
|
it('authn anonymous /p/pad -> 401, error', async function() {
|
||||||
authorize = () => 'create';
|
settings.requireAuthentication = true;
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
const res = await agent.get('/p/pad').expect(401);
|
||||||
socket = await connect(res);
|
// Despite the 401, try to create the pad via a socket.io connection anyway.
|
||||||
const message = await handshake(socket, 'pad');
|
socket = await connect(res);
|
||||||
assert.equal(message.accessStatus, 'deny');
|
const message = await handshake(socket, 'pad');
|
||||||
|
assert.equal(message.accessStatus, 'deny');
|
||||||
|
});
|
||||||
|
it('authn !cookie -> error', async function() {
|
||||||
|
settings.requireAuthentication = true;
|
||||||
|
socket = await connect(null);
|
||||||
|
const message = await handshake(socket, 'pad');
|
||||||
|
assert.equal(message.accessStatus, 'deny');
|
||||||
|
});
|
||||||
|
it('authorization bypass attempt -> error', async function() {
|
||||||
|
// Only allowed to access /p/pad.
|
||||||
|
authorize = (req) => req.path === '/p/pad';
|
||||||
|
settings.requireAuthentication = true;
|
||||||
|
settings.requireAuthorization = true;
|
||||||
|
// First authenticate and establish a session.
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
// Accessing /p/other-pad should fail, despite the successful fetch of /p/pad.
|
||||||
|
const message = await handshake(socket, 'other-pad');
|
||||||
|
assert.equal(message.accessStatus, 'deny');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
it('user settings does not elevate level from authorize hook', async function() {
|
|
||||||
settings.users.user.readOnly = false;
|
describe('Authorization levels via authorize hook', function() {
|
||||||
settings.users.user.canCreate = true;
|
beforeEach(async function() {
|
||||||
authorize = () => 'readOnly';
|
settings.requireAuthentication = true;
|
||||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
settings.requireAuthorization = true;
|
||||||
socket = await connect(res);
|
});
|
||||||
const message = await handshake(socket, 'pad');
|
|
||||||
assert.equal(message.accessStatus, 'deny');
|
it("level='create' -> can create", async function() {
|
||||||
|
authorize = () => 'create';
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const clientVars = await handshake(socket, 'pad');
|
||||||
|
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||||
|
assert.equal(clientVars.data.readonly, false);
|
||||||
|
});
|
||||||
|
it('level=true -> can create', async function() {
|
||||||
|
authorize = () => true;
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const clientVars = await handshake(socket, 'pad');
|
||||||
|
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||||
|
assert.equal(clientVars.data.readonly, false);
|
||||||
|
});
|
||||||
|
it("level='modify' -> can modify", async function() {
|
||||||
|
await padManager.getPad('pad'); // Create the pad.
|
||||||
|
authorize = () => 'modify';
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const clientVars = await handshake(socket, 'pad');
|
||||||
|
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||||
|
assert.equal(clientVars.data.readonly, false);
|
||||||
|
});
|
||||||
|
it("level='create' settings.editOnly=true -> unable to create", async function() {
|
||||||
|
authorize = () => 'create';
|
||||||
|
settings.editOnly = true;
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const message = await handshake(socket, 'pad');
|
||||||
|
assert.equal(message.accessStatus, 'deny');
|
||||||
|
});
|
||||||
|
it("level='modify' settings.editOnly=false -> unable to create", async function() {
|
||||||
|
authorize = () => 'modify';
|
||||||
|
settings.editOnly = false;
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const message = await handshake(socket, 'pad');
|
||||||
|
assert.equal(message.accessStatus, 'deny');
|
||||||
|
});
|
||||||
|
it("level='readOnly' -> unable to create", async function() {
|
||||||
|
authorize = () => 'readOnly';
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const message = await handshake(socket, 'pad');
|
||||||
|
assert.equal(message.accessStatus, 'deny');
|
||||||
|
});
|
||||||
|
it("level='readOnly' -> unable to modify", async function() {
|
||||||
|
await padManager.getPad('pad'); // Create the pad.
|
||||||
|
authorize = () => 'readOnly';
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const clientVars = await handshake(socket, 'pad');
|
||||||
|
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||||
|
assert.equal(clientVars.data.readonly, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Authorization levels via user settings', function() {
|
||||||
|
beforeEach(async function() {
|
||||||
|
settings.requireAuthentication = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('user.canCreate = true -> can create and modify', async function() {
|
||||||
|
settings.users.user.canCreate = true;
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const clientVars = await handshake(socket, 'pad');
|
||||||
|
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||||
|
assert.equal(clientVars.data.readonly, false);
|
||||||
|
});
|
||||||
|
it('user.canCreate = false -> unable to create', async function() {
|
||||||
|
settings.users.user.canCreate = false;
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const message = await handshake(socket, 'pad');
|
||||||
|
assert.equal(message.accessStatus, 'deny');
|
||||||
|
});
|
||||||
|
it('user.readOnly = true -> unable to create', async function() {
|
||||||
|
settings.users.user.readOnly = true;
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const message = await handshake(socket, 'pad');
|
||||||
|
assert.equal(message.accessStatus, 'deny');
|
||||||
|
});
|
||||||
|
it('user.readOnly = true -> unable to modify', async function() {
|
||||||
|
await padManager.getPad('pad'); // Create the pad.
|
||||||
|
settings.users.user.readOnly = true;
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const clientVars = await handshake(socket, 'pad');
|
||||||
|
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||||
|
assert.equal(clientVars.data.readonly, true);
|
||||||
|
});
|
||||||
|
it('user.readOnly = false -> can create and modify', async function() {
|
||||||
|
settings.users.user.readOnly = false;
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const clientVars = await handshake(socket, 'pad');
|
||||||
|
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||||
|
assert.equal(clientVars.data.readonly, false);
|
||||||
|
});
|
||||||
|
it('user.readOnly = true, user.canCreate = true -> unable to create', async function() {
|
||||||
|
settings.users.user.canCreate = true;
|
||||||
|
settings.users.user.readOnly = true;
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const message = await handshake(socket, 'pad');
|
||||||
|
assert.equal(message.accessStatus, 'deny');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Authorization level interaction between authorize hook and user settings', function() {
|
||||||
|
beforeEach(async function() {
|
||||||
|
settings.requireAuthentication = true;
|
||||||
|
settings.requireAuthorization = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('authorize hook does not elevate level from user settings', async function() {
|
||||||
|
settings.users.user.readOnly = true;
|
||||||
|
authorize = () => 'create';
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const message = await handshake(socket, 'pad');
|
||||||
|
assert.equal(message.accessStatus, 'deny');
|
||||||
|
});
|
||||||
|
it('user settings does not elevate level from authorize hook', async function() {
|
||||||
|
settings.users.user.readOnly = false;
|
||||||
|
settings.users.user.canCreate = true;
|
||||||
|
authorize = () => 'readOnly';
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const message = await handshake(socket, 'pad');
|
||||||
|
assert.equal(message.accessStatus, 'deny');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,440 +5,441 @@ const common = require('../common');
|
||||||
const plugins = require(m('static/js/pluginfw/plugin_defs'));
|
const plugins = require(m('static/js/pluginfw/plugin_defs'));
|
||||||
const settings = require(m('node/utils/Settings'));
|
const settings = require(m('node/utils/Settings'));
|
||||||
|
|
||||||
let agent;
|
describe(__filename, function() {
|
||||||
|
let agent;
|
||||||
|
before(async function() { agent = await common.init(); });
|
||||||
|
|
||||||
before(async function() { agent = await common.init(); });
|
describe('webaccess: without plugins', function() {
|
||||||
|
const backup = {};
|
||||||
|
|
||||||
describe('webaccess: without plugins', function() {
|
before(async function() {
|
||||||
const backup = {};
|
Object.assign(backup, settings);
|
||||||
|
settings.users = {
|
||||||
before(async function() {
|
admin: {password: 'admin-password', is_admin: true},
|
||||||
Object.assign(backup, settings);
|
user: {password: 'user-password'},
|
||||||
settings.users = {
|
};
|
||||||
admin: {password: 'admin-password', is_admin: true},
|
|
||||||
user: {password: 'user-password'},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async function() {
|
|
||||||
Object.assign(settings, backup);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('!authn !authz anonymous / -> 200', async function() {
|
|
||||||
settings.requireAuthentication = false;
|
|
||||||
settings.requireAuthorization = false;
|
|
||||||
await agent.get('/').expect(200);
|
|
||||||
});
|
|
||||||
it('!authn !authz anonymous /admin/ -> 401', async function() {
|
|
||||||
settings.requireAuthentication = false;
|
|
||||||
settings.requireAuthorization = false;
|
|
||||||
await agent.get('/admin/').expect(401);
|
|
||||||
});
|
|
||||||
it('authn !authz anonymous / -> 401', async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
settings.requireAuthorization = false;
|
|
||||||
await agent.get('/').expect(401);
|
|
||||||
});
|
|
||||||
it('authn !authz user / -> 200', async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
settings.requireAuthorization = false;
|
|
||||||
await agent.get('/').auth('user', 'user-password').expect(200);
|
|
||||||
});
|
|
||||||
it('authn !authz user /admin/ -> 403', async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
settings.requireAuthorization = false;
|
|
||||||
await agent.get('/admin/').auth('user', 'user-password').expect(403);
|
|
||||||
});
|
|
||||||
it('authn !authz admin / -> 200', async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
settings.requireAuthorization = false;
|
|
||||||
await agent.get('/').auth('admin', 'admin-password').expect(200);
|
|
||||||
});
|
|
||||||
it('authn !authz admin /admin/ -> 200', async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
settings.requireAuthorization = false;
|
|
||||||
await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
|
|
||||||
});
|
|
||||||
it('authn authz user / -> 403', async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
settings.requireAuthorization = true;
|
|
||||||
await agent.get('/').auth('user', 'user-password').expect(403);
|
|
||||||
});
|
|
||||||
it('authn authz user /admin/ -> 403', async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
settings.requireAuthorization = true;
|
|
||||||
await agent.get('/admin/').auth('user', 'user-password').expect(403);
|
|
||||||
});
|
|
||||||
it('authn authz admin / -> 200', async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
settings.requireAuthorization = true;
|
|
||||||
await agent.get('/').auth('admin', 'admin-password').expect(200);
|
|
||||||
});
|
|
||||||
it('authn authz admin /admin/ -> 200', async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
settings.requireAuthorization = true;
|
|
||||||
await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('webaccess: preAuthorize, authenticate, and authorize hooks', function() {
|
|
||||||
let callOrder;
|
|
||||||
const Handler = class {
|
|
||||||
constructor(hookName, suffix) {
|
|
||||||
this.called = false;
|
|
||||||
this.hookName = hookName;
|
|
||||||
this.innerHandle = () => [];
|
|
||||||
this.id = hookName + suffix;
|
|
||||||
this.checkContext = () => {};
|
|
||||||
}
|
|
||||||
handle(hookName, context, cb) {
|
|
||||||
assert.equal(hookName, this.hookName);
|
|
||||||
assert(context != null);
|
|
||||||
assert(context.req != null);
|
|
||||||
assert(context.res != null);
|
|
||||||
assert(context.next != null);
|
|
||||||
this.checkContext(context);
|
|
||||||
assert(!this.called);
|
|
||||||
this.called = true;
|
|
||||||
callOrder.push(this.id);
|
|
||||||
return cb(this.innerHandle(context.req));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handlers = {};
|
|
||||||
const hookNames = ['preAuthorize', 'authenticate', 'authorize'];
|
|
||||||
const hooksBackup = {};
|
|
||||||
const settingsBackup = {};
|
|
||||||
|
|
||||||
beforeEach(async function() {
|
|
||||||
callOrder = [];
|
|
||||||
hookNames.forEach((hookName) => {
|
|
||||||
// Create two handlers for each hook to test deferral to the next function.
|
|
||||||
const h0 = new Handler(hookName, '_0');
|
|
||||||
const h1 = new Handler(hookName, '_1');
|
|
||||||
handlers[hookName] = [h0, h1];
|
|
||||||
hooksBackup[hookName] = plugins.hooks[hookName] || [];
|
|
||||||
plugins.hooks[hookName] = [{hook_fn: h0.handle.bind(h0)}, {hook_fn: h1.handle.bind(h1)}];
|
|
||||||
});
|
});
|
||||||
hooksBackup.preAuthzFailure = plugins.hooks.preAuthzFailure || [];
|
|
||||||
Object.assign(settingsBackup, settings);
|
|
||||||
settings.users = {
|
|
||||||
admin: {password: 'admin-password', is_admin: true},
|
|
||||||
user: {password: 'user-password'},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
afterEach(async function() {
|
|
||||||
Object.assign(plugins.hooks, hooksBackup);
|
|
||||||
Object.assign(settings, settingsBackup);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('preAuthorize', function() {
|
after(async function() {
|
||||||
beforeEach(async function() {
|
Object.assign(settings, backup);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('!authn !authz anonymous / -> 200', async function() {
|
||||||
settings.requireAuthentication = false;
|
settings.requireAuthentication = false;
|
||||||
settings.requireAuthorization = false;
|
settings.requireAuthorization = false;
|
||||||
});
|
|
||||||
|
|
||||||
it('defers if it returns []', async function() {
|
|
||||||
await agent.get('/').expect(200);
|
await agent.get('/').expect(200);
|
||||||
// Note: The preAuthorize hook always runs even if requireAuthorization is false.
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1']);
|
|
||||||
});
|
});
|
||||||
it('bypasses authenticate and authorize hooks when true is returned', async function() {
|
it('!authn !authz anonymous /admin/ -> 401', async function() {
|
||||||
settings.requireAuthentication = true;
|
settings.requireAuthentication = false;
|
||||||
settings.requireAuthorization = true;
|
settings.requireAuthorization = false;
|
||||||
handlers.preAuthorize[0].innerHandle = () => [true];
|
|
||||||
await agent.get('/').expect(200);
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0']);
|
|
||||||
});
|
|
||||||
it('bypasses authenticate and authorize hooks when false is returned', async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
settings.requireAuthorization = true;
|
|
||||||
handlers.preAuthorize[0].innerHandle = () => [false];
|
|
||||||
await agent.get('/').expect(403);
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0']);
|
|
||||||
});
|
|
||||||
it('bypasses authenticate and authorize hooks for static content, defers', async function() {
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
settings.requireAuthorization = true;
|
|
||||||
await agent.get('/static/robots.txt').expect(200);
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1']);
|
|
||||||
});
|
|
||||||
it('cannot grant access to /admin', async function() {
|
|
||||||
handlers.preAuthorize[0].innerHandle = () => [true];
|
|
||||||
await agent.get('/admin/').expect(401);
|
await agent.get('/admin/').expect(401);
|
||||||
// Notes:
|
|
||||||
// * preAuthorize[1] is called despite preAuthorize[0] returning a non-empty list because
|
|
||||||
// 'true' entries are ignored for /admin/* requests.
|
|
||||||
// * The authenticate hook always runs for /admin/* requests even if
|
|
||||||
// settings.requireAuthentication is false.
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
|
||||||
'authenticate_0', 'authenticate_1']);
|
|
||||||
});
|
});
|
||||||
it('can deny access to /admin', async function() {
|
it('authn !authz anonymous / -> 401', async function() {
|
||||||
handlers.preAuthorize[0].innerHandle = () => [false];
|
|
||||||
await agent.get('/admin/').auth('admin', 'admin-password').expect(403);
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0']);
|
|
||||||
});
|
|
||||||
it('runs preAuthzFailure hook when access is denied', async function() {
|
|
||||||
handlers.preAuthorize[0].innerHandle = () => [false];
|
|
||||||
let called = false;
|
|
||||||
plugins.hooks.preAuthzFailure = [{hook_fn: (hookName, {req, res}, cb) => {
|
|
||||||
assert.equal(hookName, 'preAuthzFailure');
|
|
||||||
assert(req != null);
|
|
||||||
assert(res != null);
|
|
||||||
assert(!called);
|
|
||||||
called = true;
|
|
||||||
res.status(200).send('injected');
|
|
||||||
return cb([true]);
|
|
||||||
}}];
|
|
||||||
await agent.get('/admin/').auth('admin', 'admin-password').expect(200, 'injected');
|
|
||||||
assert(called);
|
|
||||||
});
|
|
||||||
it('returns 500 if an exception is thrown', async function() {
|
|
||||||
handlers.preAuthorize[0].innerHandle = () => { throw new Error('exception test'); };
|
|
||||||
await agent.get('/').expect(500);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('authenticate', function() {
|
|
||||||
beforeEach(async function() {
|
|
||||||
settings.requireAuthentication = true;
|
settings.requireAuthentication = true;
|
||||||
settings.requireAuthorization = false;
|
settings.requireAuthorization = false;
|
||||||
});
|
|
||||||
|
|
||||||
it('is not called if !requireAuthentication and not /admin/*', async function() {
|
|
||||||
settings.requireAuthentication = false;
|
|
||||||
await agent.get('/').expect(200);
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1']);
|
|
||||||
});
|
|
||||||
it('is called if !requireAuthentication and /admin/*', async function() {
|
|
||||||
settings.requireAuthentication = false;
|
|
||||||
await agent.get('/admin/').expect(401);
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
|
||||||
'authenticate_0', 'authenticate_1']);
|
|
||||||
});
|
|
||||||
it('defers if empty list returned', async function() {
|
|
||||||
await agent.get('/').expect(401);
|
await agent.get('/').expect(401);
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
|
||||||
'authenticate_0', 'authenticate_1']);
|
|
||||||
});
|
});
|
||||||
it('does not defer if return [true], 200', async function() {
|
it('authn !authz user / -> 200', async function() {
|
||||||
handlers.authenticate[0].innerHandle = (req) => { req.session.user = {}; return [true]; };
|
|
||||||
await agent.get('/').expect(200);
|
|
||||||
// Note: authenticate_1 was not called because authenticate_0 handled it.
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
|
|
||||||
});
|
|
||||||
it('does not defer if return [false], 401', async function() {
|
|
||||||
handlers.authenticate[0].innerHandle = (req) => [false];
|
|
||||||
await agent.get('/').expect(401);
|
|
||||||
// Note: authenticate_1 was not called because authenticate_0 handled it.
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
|
|
||||||
});
|
|
||||||
it('falls back to HTTP basic auth', async function() {
|
|
||||||
await agent.get('/').auth('user', 'user-password').expect(200);
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
|
||||||
'authenticate_0', 'authenticate_1']);
|
|
||||||
});
|
|
||||||
it('passes settings.users in context', async function() {
|
|
||||||
handlers.authenticate[0].checkContext = ({users}) => {
|
|
||||||
assert.equal(users, settings.users);
|
|
||||||
};
|
|
||||||
await agent.get('/').expect(401);
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
|
||||||
'authenticate_0', 'authenticate_1']);
|
|
||||||
});
|
|
||||||
it('passes user, password in context if provided', async function() {
|
|
||||||
handlers.authenticate[0].checkContext = ({username, password}) => {
|
|
||||||
assert.equal(username, 'user');
|
|
||||||
assert.equal(password, 'user-password');
|
|
||||||
};
|
|
||||||
await agent.get('/').auth('user', 'user-password').expect(200);
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
|
||||||
'authenticate_0', 'authenticate_1']);
|
|
||||||
});
|
|
||||||
it('does not pass user, password in context if not provided', async function() {
|
|
||||||
handlers.authenticate[0].checkContext = ({username, password}) => {
|
|
||||||
assert(username == null);
|
|
||||||
assert(password == null);
|
|
||||||
};
|
|
||||||
await agent.get('/').expect(401);
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
|
||||||
'authenticate_0', 'authenticate_1']);
|
|
||||||
});
|
|
||||||
it('errors if req.session.user is not created', async function() {
|
|
||||||
handlers.authenticate[0].innerHandle = () => [true];
|
|
||||||
await agent.get('/').expect(500);
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
|
|
||||||
});
|
|
||||||
it('returns 500 if an exception is thrown', async function() {
|
|
||||||
handlers.authenticate[0].innerHandle = () => { throw new Error('exception test'); };
|
|
||||||
await agent.get('/').expect(500);
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('authorize', function() {
|
|
||||||
beforeEach(async function() {
|
|
||||||
settings.requireAuthentication = true;
|
settings.requireAuthentication = true;
|
||||||
settings.requireAuthorization = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('is not called if !requireAuthorization (non-/admin)', async function() {
|
|
||||||
settings.requireAuthorization = false;
|
settings.requireAuthorization = false;
|
||||||
await agent.get('/').auth('user', 'user-password').expect(200);
|
await agent.get('/').auth('user', 'user-password').expect(200);
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
|
||||||
'authenticate_0', 'authenticate_1']);
|
|
||||||
});
|
});
|
||||||
it('is not called if !requireAuthorization (/admin)', async function() {
|
it('authn !authz user /admin/ -> 403', async function() {
|
||||||
|
settings.requireAuthentication = true;
|
||||||
|
settings.requireAuthorization = false;
|
||||||
|
await agent.get('/admin/').auth('user', 'user-password').expect(403);
|
||||||
|
});
|
||||||
|
it('authn !authz admin / -> 200', async function() {
|
||||||
|
settings.requireAuthentication = true;
|
||||||
|
settings.requireAuthorization = false;
|
||||||
|
await agent.get('/').auth('admin', 'admin-password').expect(200);
|
||||||
|
});
|
||||||
|
it('authn !authz admin /admin/ -> 200', async function() {
|
||||||
|
settings.requireAuthentication = true;
|
||||||
settings.requireAuthorization = false;
|
settings.requireAuthorization = false;
|
||||||
await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
|
await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
|
||||||
'authenticate_0', 'authenticate_1']);
|
|
||||||
});
|
});
|
||||||
it('defers if empty list returned', async function() {
|
it('authn authz user / -> 403', async function() {
|
||||||
|
settings.requireAuthentication = true;
|
||||||
|
settings.requireAuthorization = true;
|
||||||
await agent.get('/').auth('user', 'user-password').expect(403);
|
await agent.get('/').auth('user', 'user-password').expect(403);
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
|
||||||
'authenticate_0', 'authenticate_1',
|
|
||||||
'authorize_0', 'authorize_1']);
|
|
||||||
});
|
});
|
||||||
it('does not defer if return [true], 200', async function() {
|
it('authn authz user /admin/ -> 403', async function() {
|
||||||
handlers.authorize[0].innerHandle = () => [true];
|
settings.requireAuthentication = true;
|
||||||
await agent.get('/').auth('user', 'user-password').expect(200);
|
settings.requireAuthorization = true;
|
||||||
// Note: authorize_1 was not called because authorize_0 handled it.
|
await agent.get('/admin/').auth('user', 'user-password').expect(403);
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
|
||||||
'authenticate_0', 'authenticate_1',
|
|
||||||
'authorize_0']);
|
|
||||||
});
|
});
|
||||||
it('does not defer if return [false], 403', async function() {
|
it('authn authz admin / -> 200', async function() {
|
||||||
handlers.authorize[0].innerHandle = (req) => [false];
|
settings.requireAuthentication = true;
|
||||||
await agent.get('/').auth('user', 'user-password').expect(403);
|
settings.requireAuthorization = true;
|
||||||
// Note: authorize_1 was not called because authorize_0 handled it.
|
await agent.get('/').auth('admin', 'admin-password').expect(200);
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
|
||||||
'authenticate_0', 'authenticate_1',
|
|
||||||
'authorize_0']);
|
|
||||||
});
|
});
|
||||||
it('passes req.path in context', async function() {
|
it('authn authz admin /admin/ -> 200', async function() {
|
||||||
handlers.authorize[0].checkContext = ({resource}) => {
|
settings.requireAuthentication = true;
|
||||||
assert.equal(resource, '/');
|
settings.requireAuthorization = true;
|
||||||
};
|
await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
|
||||||
await agent.get('/').auth('user', 'user-password').expect(403);
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
|
||||||
'authenticate_0', 'authenticate_1',
|
|
||||||
'authorize_0', 'authorize_1']);
|
|
||||||
});
|
|
||||||
it('returns 500 if an exception is thrown', async function() {
|
|
||||||
handlers.authorize[0].innerHandle = () => { throw new Error('exception test'); };
|
|
||||||
await agent.get('/').auth('user', 'user-password').expect(500);
|
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
|
||||||
'authenticate_0', 'authenticate_1',
|
|
||||||
'authorize_0']);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('webaccess: authnFailure, authzFailure, authFailure hooks', function() {
|
describe('webaccess: preAuthorize, authenticate, and authorize hooks', function() {
|
||||||
const Handler = class {
|
let callOrder;
|
||||||
constructor(hookName) {
|
const Handler = class {
|
||||||
this.hookName = hookName;
|
constructor(hookName, suffix) {
|
||||||
this.shouldHandle = false;
|
this.called = false;
|
||||||
this.called = false;
|
this.hookName = hookName;
|
||||||
}
|
this.innerHandle = () => [];
|
||||||
handle(hookName, context, cb) {
|
this.id = hookName + suffix;
|
||||||
assert.equal(hookName, this.hookName);
|
this.checkContext = () => {};
|
||||||
assert(context != null);
|
}
|
||||||
assert(context.req != null);
|
handle(hookName, context, cb) {
|
||||||
assert(context.res != null);
|
assert.equal(hookName, this.hookName);
|
||||||
assert(!this.called);
|
assert(context != null);
|
||||||
this.called = true;
|
assert(context.req != null);
|
||||||
if (this.shouldHandle) {
|
assert(context.res != null);
|
||||||
context.res.status(200).send(this.hookName);
|
assert(context.next != null);
|
||||||
return cb([true]);
|
this.checkContext(context);
|
||||||
|
assert(!this.called);
|
||||||
|
this.called = true;
|
||||||
|
callOrder.push(this.id);
|
||||||
|
return cb(this.innerHandle(context.req));
|
||||||
}
|
}
|
||||||
return cb([]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handlers = {};
|
|
||||||
const hookNames = ['authnFailure', 'authzFailure', 'authFailure'];
|
|
||||||
const settingsBackup = {};
|
|
||||||
const hooksBackup = {};
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
Object.assign(settingsBackup, settings);
|
|
||||||
hookNames.forEach((hookName) => {
|
|
||||||
if (plugins.hooks[hookName] == null) plugins.hooks[hookName] = [];
|
|
||||||
});
|
|
||||||
Object.assign(hooksBackup, plugins.hooks);
|
|
||||||
hookNames.forEach((hookName) => {
|
|
||||||
const handler = new Handler(hookName);
|
|
||||||
handlers[hookName] = handler;
|
|
||||||
plugins.hooks[hookName] = [{hook_fn: handler.handle.bind(handler)}];
|
|
||||||
});
|
|
||||||
settings.requireAuthentication = true;
|
|
||||||
settings.requireAuthorization = true;
|
|
||||||
settings.users = {
|
|
||||||
admin: {password: 'admin-password', is_admin: true},
|
|
||||||
user: {password: 'user-password'},
|
|
||||||
};
|
};
|
||||||
});
|
const handlers = {};
|
||||||
afterEach(function() {
|
const hookNames = ['preAuthorize', 'authenticate', 'authorize'];
|
||||||
Object.assign(settings, settingsBackup);
|
const hooksBackup = {};
|
||||||
Object.assign(plugins.hooks, hooksBackup);
|
const settingsBackup = {};
|
||||||
|
|
||||||
|
beforeEach(async function() {
|
||||||
|
callOrder = [];
|
||||||
|
hookNames.forEach((hookName) => {
|
||||||
|
// Create two handlers for each hook to test deferral to the next function.
|
||||||
|
const h0 = new Handler(hookName, '_0');
|
||||||
|
const h1 = new Handler(hookName, '_1');
|
||||||
|
handlers[hookName] = [h0, h1];
|
||||||
|
hooksBackup[hookName] = plugins.hooks[hookName] || [];
|
||||||
|
plugins.hooks[hookName] = [{hook_fn: h0.handle.bind(h0)}, {hook_fn: h1.handle.bind(h1)}];
|
||||||
|
});
|
||||||
|
hooksBackup.preAuthzFailure = plugins.hooks.preAuthzFailure || [];
|
||||||
|
Object.assign(settingsBackup, settings);
|
||||||
|
settings.users = {
|
||||||
|
admin: {password: 'admin-password', is_admin: true},
|
||||||
|
user: {password: 'user-password'},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
afterEach(async function() {
|
||||||
|
Object.assign(plugins.hooks, hooksBackup);
|
||||||
|
Object.assign(settings, settingsBackup);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('preAuthorize', function() {
|
||||||
|
beforeEach(async function() {
|
||||||
|
settings.requireAuthentication = false;
|
||||||
|
settings.requireAuthorization = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defers if it returns []', async function() {
|
||||||
|
await agent.get('/').expect(200);
|
||||||
|
// Note: The preAuthorize hook always runs even if requireAuthorization is false.
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1']);
|
||||||
|
});
|
||||||
|
it('bypasses authenticate and authorize hooks when true is returned', async function() {
|
||||||
|
settings.requireAuthentication = true;
|
||||||
|
settings.requireAuthorization = true;
|
||||||
|
handlers.preAuthorize[0].innerHandle = () => [true];
|
||||||
|
await agent.get('/').expect(200);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0']);
|
||||||
|
});
|
||||||
|
it('bypasses authenticate and authorize hooks when false is returned', async function() {
|
||||||
|
settings.requireAuthentication = true;
|
||||||
|
settings.requireAuthorization = true;
|
||||||
|
handlers.preAuthorize[0].innerHandle = () => [false];
|
||||||
|
await agent.get('/').expect(403);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0']);
|
||||||
|
});
|
||||||
|
it('bypasses authenticate and authorize hooks for static content, defers', async function() {
|
||||||
|
settings.requireAuthentication = true;
|
||||||
|
settings.requireAuthorization = true;
|
||||||
|
await agent.get('/static/robots.txt').expect(200);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1']);
|
||||||
|
});
|
||||||
|
it('cannot grant access to /admin', async function() {
|
||||||
|
handlers.preAuthorize[0].innerHandle = () => [true];
|
||||||
|
await agent.get('/admin/').expect(401);
|
||||||
|
// Notes:
|
||||||
|
// * preAuthorize[1] is called despite preAuthorize[0] returning a non-empty list because
|
||||||
|
// 'true' entries are ignored for /admin/* requests.
|
||||||
|
// * The authenticate hook always runs for /admin/* requests even if
|
||||||
|
// settings.requireAuthentication is false.
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
||||||
|
'authenticate_0', 'authenticate_1']);
|
||||||
|
});
|
||||||
|
it('can deny access to /admin', async function() {
|
||||||
|
handlers.preAuthorize[0].innerHandle = () => [false];
|
||||||
|
await agent.get('/admin/').auth('admin', 'admin-password').expect(403);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0']);
|
||||||
|
});
|
||||||
|
it('runs preAuthzFailure hook when access is denied', async function() {
|
||||||
|
handlers.preAuthorize[0].innerHandle = () => [false];
|
||||||
|
let called = false;
|
||||||
|
plugins.hooks.preAuthzFailure = [{hook_fn: (hookName, {req, res}, cb) => {
|
||||||
|
assert.equal(hookName, 'preAuthzFailure');
|
||||||
|
assert(req != null);
|
||||||
|
assert(res != null);
|
||||||
|
assert(!called);
|
||||||
|
called = true;
|
||||||
|
res.status(200).send('injected');
|
||||||
|
return cb([true]);
|
||||||
|
}}];
|
||||||
|
await agent.get('/admin/').auth('admin', 'admin-password').expect(200, 'injected');
|
||||||
|
assert(called);
|
||||||
|
});
|
||||||
|
it('returns 500 if an exception is thrown', async function() {
|
||||||
|
handlers.preAuthorize[0].innerHandle = () => { throw new Error('exception test'); };
|
||||||
|
await agent.get('/').expect(500);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('authenticate', function() {
|
||||||
|
beforeEach(async function() {
|
||||||
|
settings.requireAuthentication = true;
|
||||||
|
settings.requireAuthorization = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is not called if !requireAuthentication and not /admin/*', async function() {
|
||||||
|
settings.requireAuthentication = false;
|
||||||
|
await agent.get('/').expect(200);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1']);
|
||||||
|
});
|
||||||
|
it('is called if !requireAuthentication and /admin/*', async function() {
|
||||||
|
settings.requireAuthentication = false;
|
||||||
|
await agent.get('/admin/').expect(401);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
||||||
|
'authenticate_0', 'authenticate_1']);
|
||||||
|
});
|
||||||
|
it('defers if empty list returned', async function() {
|
||||||
|
await agent.get('/').expect(401);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
||||||
|
'authenticate_0', 'authenticate_1']);
|
||||||
|
});
|
||||||
|
it('does not defer if return [true], 200', async function() {
|
||||||
|
handlers.authenticate[0].innerHandle = (req) => { req.session.user = {}; return [true]; };
|
||||||
|
await agent.get('/').expect(200);
|
||||||
|
// Note: authenticate_1 was not called because authenticate_0 handled it.
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
|
||||||
|
});
|
||||||
|
it('does not defer if return [false], 401', async function() {
|
||||||
|
handlers.authenticate[0].innerHandle = (req) => [false];
|
||||||
|
await agent.get('/').expect(401);
|
||||||
|
// Note: authenticate_1 was not called because authenticate_0 handled it.
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
|
||||||
|
});
|
||||||
|
it('falls back to HTTP basic auth', async function() {
|
||||||
|
await agent.get('/').auth('user', 'user-password').expect(200);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
||||||
|
'authenticate_0', 'authenticate_1']);
|
||||||
|
});
|
||||||
|
it('passes settings.users in context', async function() {
|
||||||
|
handlers.authenticate[0].checkContext = ({users}) => {
|
||||||
|
assert.equal(users, settings.users);
|
||||||
|
};
|
||||||
|
await agent.get('/').expect(401);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
||||||
|
'authenticate_0', 'authenticate_1']);
|
||||||
|
});
|
||||||
|
it('passes user, password in context if provided', async function() {
|
||||||
|
handlers.authenticate[0].checkContext = ({username, password}) => {
|
||||||
|
assert.equal(username, 'user');
|
||||||
|
assert.equal(password, 'user-password');
|
||||||
|
};
|
||||||
|
await agent.get('/').auth('user', 'user-password').expect(200);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
||||||
|
'authenticate_0', 'authenticate_1']);
|
||||||
|
});
|
||||||
|
it('does not pass user, password in context if not provided', async function() {
|
||||||
|
handlers.authenticate[0].checkContext = ({username, password}) => {
|
||||||
|
assert(username == null);
|
||||||
|
assert(password == null);
|
||||||
|
};
|
||||||
|
await agent.get('/').expect(401);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
||||||
|
'authenticate_0', 'authenticate_1']);
|
||||||
|
});
|
||||||
|
it('errors if req.session.user is not created', async function() {
|
||||||
|
handlers.authenticate[0].innerHandle = () => [true];
|
||||||
|
await agent.get('/').expect(500);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
|
||||||
|
});
|
||||||
|
it('returns 500 if an exception is thrown', async function() {
|
||||||
|
handlers.authenticate[0].innerHandle = () => { throw new Error('exception test'); };
|
||||||
|
await agent.get('/').expect(500);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('authorize', function() {
|
||||||
|
beforeEach(async function() {
|
||||||
|
settings.requireAuthentication = true;
|
||||||
|
settings.requireAuthorization = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is not called if !requireAuthorization (non-/admin)', async function() {
|
||||||
|
settings.requireAuthorization = false;
|
||||||
|
await agent.get('/').auth('user', 'user-password').expect(200);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
||||||
|
'authenticate_0', 'authenticate_1']);
|
||||||
|
});
|
||||||
|
it('is not called if !requireAuthorization (/admin)', async function() {
|
||||||
|
settings.requireAuthorization = false;
|
||||||
|
await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
||||||
|
'authenticate_0', 'authenticate_1']);
|
||||||
|
});
|
||||||
|
it('defers if empty list returned', async function() {
|
||||||
|
await agent.get('/').auth('user', 'user-password').expect(403);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
||||||
|
'authenticate_0', 'authenticate_1',
|
||||||
|
'authorize_0', 'authorize_1']);
|
||||||
|
});
|
||||||
|
it('does not defer if return [true], 200', async function() {
|
||||||
|
handlers.authorize[0].innerHandle = () => [true];
|
||||||
|
await agent.get('/').auth('user', 'user-password').expect(200);
|
||||||
|
// Note: authorize_1 was not called because authorize_0 handled it.
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
||||||
|
'authenticate_0', 'authenticate_1',
|
||||||
|
'authorize_0']);
|
||||||
|
});
|
||||||
|
it('does not defer if return [false], 403', async function() {
|
||||||
|
handlers.authorize[0].innerHandle = (req) => [false];
|
||||||
|
await agent.get('/').auth('user', 'user-password').expect(403);
|
||||||
|
// Note: authorize_1 was not called because authorize_0 handled it.
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
||||||
|
'authenticate_0', 'authenticate_1',
|
||||||
|
'authorize_0']);
|
||||||
|
});
|
||||||
|
it('passes req.path in context', async function() {
|
||||||
|
handlers.authorize[0].checkContext = ({resource}) => {
|
||||||
|
assert.equal(resource, '/');
|
||||||
|
};
|
||||||
|
await agent.get('/').auth('user', 'user-password').expect(403);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
||||||
|
'authenticate_0', 'authenticate_1',
|
||||||
|
'authorize_0', 'authorize_1']);
|
||||||
|
});
|
||||||
|
it('returns 500 if an exception is thrown', async function() {
|
||||||
|
handlers.authorize[0].innerHandle = () => { throw new Error('exception test'); };
|
||||||
|
await agent.get('/').auth('user', 'user-password').expect(500);
|
||||||
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1',
|
||||||
|
'authenticate_0', 'authenticate_1',
|
||||||
|
'authorize_0']);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// authn failure tests
|
describe('webaccess: authnFailure, authzFailure, authFailure hooks', function() {
|
||||||
it('authn fail, no hooks handle -> 401', async function() {
|
const Handler = class {
|
||||||
await agent.get('/').expect(401);
|
constructor(hookName) {
|
||||||
assert(handlers['authnFailure'].called);
|
this.hookName = hookName;
|
||||||
assert(!handlers['authzFailure'].called);
|
this.shouldHandle = false;
|
||||||
assert(handlers['authFailure'].called);
|
this.called = false;
|
||||||
});
|
}
|
||||||
it('authn fail, authnFailure handles', async function() {
|
handle(hookName, context, cb) {
|
||||||
handlers['authnFailure'].shouldHandle = true;
|
assert.equal(hookName, this.hookName);
|
||||||
await agent.get('/').expect(200, 'authnFailure');
|
assert(context != null);
|
||||||
assert(handlers['authnFailure'].called);
|
assert(context.req != null);
|
||||||
assert(!handlers['authzFailure'].called);
|
assert(context.res != null);
|
||||||
assert(!handlers['authFailure'].called);
|
assert(!this.called);
|
||||||
});
|
this.called = true;
|
||||||
it('authn fail, authFailure handles', async function() {
|
if (this.shouldHandle) {
|
||||||
handlers['authFailure'].shouldHandle = true;
|
context.res.status(200).send(this.hookName);
|
||||||
await agent.get('/').expect(200, 'authFailure');
|
return cb([true]);
|
||||||
assert(handlers['authnFailure'].called);
|
}
|
||||||
assert(!handlers['authzFailure'].called);
|
return cb([]);
|
||||||
assert(handlers['authFailure'].called);
|
}
|
||||||
});
|
};
|
||||||
it('authnFailure trumps authFailure', async function() {
|
const handlers = {};
|
||||||
handlers['authnFailure'].shouldHandle = true;
|
const hookNames = ['authnFailure', 'authzFailure', 'authFailure'];
|
||||||
handlers['authFailure'].shouldHandle = true;
|
const settingsBackup = {};
|
||||||
await agent.get('/').expect(200, 'authnFailure');
|
const hooksBackup = {};
|
||||||
assert(handlers['authnFailure'].called);
|
|
||||||
assert(!handlers['authFailure'].called);
|
|
||||||
});
|
|
||||||
|
|
||||||
// authz failure tests
|
beforeEach(function() {
|
||||||
it('authz fail, no hooks handle -> 403', async function() {
|
Object.assign(settingsBackup, settings);
|
||||||
await agent.get('/').auth('user', 'user-password').expect(403);
|
hookNames.forEach((hookName) => {
|
||||||
assert(!handlers['authnFailure'].called);
|
if (plugins.hooks[hookName] == null) plugins.hooks[hookName] = [];
|
||||||
assert(handlers['authzFailure'].called);
|
});
|
||||||
assert(handlers['authFailure'].called);
|
Object.assign(hooksBackup, plugins.hooks);
|
||||||
});
|
hookNames.forEach((hookName) => {
|
||||||
it('authz fail, authzFailure handles', async function() {
|
const handler = new Handler(hookName);
|
||||||
handlers['authzFailure'].shouldHandle = true;
|
handlers[hookName] = handler;
|
||||||
await agent.get('/').auth('user', 'user-password').expect(200, 'authzFailure');
|
plugins.hooks[hookName] = [{hook_fn: handler.handle.bind(handler)}];
|
||||||
assert(!handlers['authnFailure'].called);
|
});
|
||||||
assert(handlers['authzFailure'].called);
|
settings.requireAuthentication = true;
|
||||||
assert(!handlers['authFailure'].called);
|
settings.requireAuthorization = true;
|
||||||
});
|
settings.users = {
|
||||||
it('authz fail, authFailure handles', async function() {
|
admin: {password: 'admin-password', is_admin: true},
|
||||||
handlers['authFailure'].shouldHandle = true;
|
user: {password: 'user-password'},
|
||||||
await agent.get('/').auth('user', 'user-password').expect(200, 'authFailure');
|
};
|
||||||
assert(!handlers['authnFailure'].called);
|
});
|
||||||
assert(handlers['authzFailure'].called);
|
afterEach(function() {
|
||||||
assert(handlers['authFailure'].called);
|
Object.assign(settings, settingsBackup);
|
||||||
});
|
Object.assign(plugins.hooks, hooksBackup);
|
||||||
it('authzFailure trumps authFailure', async function() {
|
});
|
||||||
handlers['authzFailure'].shouldHandle = true;
|
|
||||||
handlers['authFailure'].shouldHandle = true;
|
// authn failure tests
|
||||||
await agent.get('/').auth('user', 'user-password').expect(200, 'authzFailure');
|
it('authn fail, no hooks handle -> 401', async function() {
|
||||||
assert(handlers['authzFailure'].called);
|
await agent.get('/').expect(401);
|
||||||
assert(!handlers['authFailure'].called);
|
assert(handlers['authnFailure'].called);
|
||||||
|
assert(!handlers['authzFailure'].called);
|
||||||
|
assert(handlers['authFailure'].called);
|
||||||
|
});
|
||||||
|
it('authn fail, authnFailure handles', async function() {
|
||||||
|
handlers['authnFailure'].shouldHandle = true;
|
||||||
|
await agent.get('/').expect(200, 'authnFailure');
|
||||||
|
assert(handlers['authnFailure'].called);
|
||||||
|
assert(!handlers['authzFailure'].called);
|
||||||
|
assert(!handlers['authFailure'].called);
|
||||||
|
});
|
||||||
|
it('authn fail, authFailure handles', async function() {
|
||||||
|
handlers['authFailure'].shouldHandle = true;
|
||||||
|
await agent.get('/').expect(200, 'authFailure');
|
||||||
|
assert(handlers['authnFailure'].called);
|
||||||
|
assert(!handlers['authzFailure'].called);
|
||||||
|
assert(handlers['authFailure'].called);
|
||||||
|
});
|
||||||
|
it('authnFailure trumps authFailure', async function() {
|
||||||
|
handlers['authnFailure'].shouldHandle = true;
|
||||||
|
handlers['authFailure'].shouldHandle = true;
|
||||||
|
await agent.get('/').expect(200, 'authnFailure');
|
||||||
|
assert(handlers['authnFailure'].called);
|
||||||
|
assert(!handlers['authFailure'].called);
|
||||||
|
});
|
||||||
|
|
||||||
|
// authz failure tests
|
||||||
|
it('authz fail, no hooks handle -> 403', async function() {
|
||||||
|
await agent.get('/').auth('user', 'user-password').expect(403);
|
||||||
|
assert(!handlers['authnFailure'].called);
|
||||||
|
assert(handlers['authzFailure'].called);
|
||||||
|
assert(handlers['authFailure'].called);
|
||||||
|
});
|
||||||
|
it('authz fail, authzFailure handles', async function() {
|
||||||
|
handlers['authzFailure'].shouldHandle = true;
|
||||||
|
await agent.get('/').auth('user', 'user-password').expect(200, 'authzFailure');
|
||||||
|
assert(!handlers['authnFailure'].called);
|
||||||
|
assert(handlers['authzFailure'].called);
|
||||||
|
assert(!handlers['authFailure'].called);
|
||||||
|
});
|
||||||
|
it('authz fail, authFailure handles', async function() {
|
||||||
|
handlers['authFailure'].shouldHandle = true;
|
||||||
|
await agent.get('/').auth('user', 'user-password').expect(200, 'authFailure');
|
||||||
|
assert(!handlers['authnFailure'].called);
|
||||||
|
assert(handlers['authzFailure'].called);
|
||||||
|
assert(handlers['authFailure'].called);
|
||||||
|
});
|
||||||
|
it('authzFailure trumps authFailure', async function() {
|
||||||
|
handlers['authzFailure'].shouldHandle = true;
|
||||||
|
handlers['authFailure'].shouldHandle = true;
|
||||||
|
await agent.get('/').auth('user', 'user-password').expect(200, 'authzFailure');
|
||||||
|
assert(handlers['authzFailure'].called);
|
||||||
|
assert(!handlers['authFailure'].called);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue