Improve unit test coverage

This commit is contained in:
Nick O'Leary
2021-10-04 17:53:14 +01:00
parent 1419729458
commit 012e1cbcc5
9 changed files with 380 additions and 21 deletions

View File

@@ -28,7 +28,7 @@ var NR_TEST_UTILS = require("nr-test-utils");
var comms = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/comms");
var Users = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/users");
var Tokens = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/tokens");
var Strategies = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/strategies");
var address = '127.0.0.1';
var listenPort = 0; // use ephemeral port
@@ -113,7 +113,6 @@ describe("api/editor/comms", function() {
connections[0].send('topic3', 'correct');
});
ws.on('message', function(msg) {
console.log(msg);
msg.should.equal('[{"topic":"topic3","data":"correct"}]');
ws.close();
done();
@@ -343,6 +342,11 @@ describe("api/editor/comms", function() {
var getUser;
var getToken;
var getUserToken;
var getUserTokenHeader;
var authenticateUserToken;
var onSessionExpiry;
var onSessionExpiryCallback;
before(function(done) {
getDefaultUser = sinon.stub(Users,"default").callsFake(function() { return Promise.resolve(null);});
getUser = sinon.stub(Users,"get").callsFake(function(username) {
@@ -368,8 +372,19 @@ describe("api/editor/comms", function() {
return Promise.resolve(null);
}
});
getUserTokenHeader = sinon.stub(Users,"tokenHeader").callsFake(function() {
return "custom-header"
})
authenticateUserToken = sinon.stub(Strategies, "authenticateUserToken").callsFake(async function(req) {
var token = req.headers['custom-header'];
if (token === "knock-knock") {
return {user:"fred",scope:["*"]}
}
throw new Error("Invalid user");
})
onSessionExpiry = sinon.stub(Tokens,"onSessionExpiry").callsFake(function(cb) {
onSessionExpiryCallback = cb;
});
server = stoppable(http.createServer(function(req,res){app(req,res)}));
comms.init(server, {adminAuth:{}}, {comms: mockComms});
server.listen(listenPort, address);
@@ -385,6 +400,9 @@ describe("api/editor/comms", function() {
getUser.restore();
getToken.restore();
getUserToken.restore();
getUserTokenHeader.restore();
authenticateUserToken.restore();
onSessionExpiry.restore();
comms.stop();
server.stop(done);
});
@@ -428,6 +446,32 @@ describe("api/editor/comms", function() {
}
});
});
it('allows connections that do authenticate - header-provided-token',function(done) {
var ws = new WebSocket(url,{
headers: { "custom-header": "knock-knock" }
});
var received = 0;
ws.on('open', function() {
ws.send('{"subscribe":"foo"}');
connections.should.have.length(1);
connections[0].send('foo', 'correct');
});
ws.on('message', function(msg) {
received++;
if (received == 1) {
msg.should.equal('[{"topic":"foo","data":"correct"}]');
ws.close();
}
});
ws.on('close', function() {
try {
received.should.equal(1);
done();
} catch(err) {
done(err);
}
});
});
it('allows connections that do authenticate - user-provided-token',function(done) {
var ws = new WebSocket(url);
var received = 0;
@@ -475,6 +519,50 @@ describe("api/editor/comms", function() {
done();
});
});
it('rejects connections for invalid token - header-provided-token',function(done) {
var ws = new WebSocket(url,{
headers: { "custom-header": "bad token" }
});
var received = 0;
ws.on('open', function() {
ws.send('{"subscribe":"foo"}');
});
ws.on('error', function() {
done();
})
});
it("expires websocket sessions", function(done) {
var ws = new WebSocket(url);
var received = 0;
ws.on('open', function() {
ws.send('{"auth":"1234"}');
});
ws.on('message', function(msg) {
received++;
if (received == 3) {
msg.should.equal('{"auth":"fail"}');
} else if (received == 1) {
msg.should.equal('{"auth":"ok"}');
ws.send('{"subscribe":"foo"}');
connections[0].send('foo', 'correct');
} else {
msg.should.equal('[{"topic":"foo","data":"correct"}]');
setTimeout(function() {
onSessionExpiryCallback({accessToken:"1234"})
},50);
}
});
ws.on('close', function() {
try {
received.should.equal(3);
done();
} catch(err) {
done(err);
}
});
})
});
describe('authentication required, anonymous enabled',function() {

View File

@@ -15,6 +15,7 @@
**/
var should = require("should");
var request = require("supertest");
var express = require('express');
var sinon = require('sinon');
var fs = require("fs");
@@ -50,10 +51,36 @@ describe("api/editor/theme", function () {
context.should.have.a.property("asset");
context.asset.should.have.a.property("red", "red/red.min.js");
context.asset.should.have.a.property("main", "red/main.min.js");
context.asset.should.have.a.property("vendorMonaco", "");
should.not.exist(theme.settings());
});
it("uses non-minified js files when in dev mode", async function () {
const previousEnv = process.env.NODE_ENV;
try {
process.env.NODE_ENV = 'development'
theme.init({});
var context = await theme.context();
context.asset.should.have.a.property("red", "red/red.js");
context.asset.should.have.a.property("main", "red/main.js");
} finally {
process.env.NODE_ENV = previousEnv;
}
});
it("Adds monaco bootstrap when enabled", async function () {
theme.init({
editorTheme: {
codeEditor: {
lib: 'monaco'
}
}
});
var context = await theme.context();
context.asset.should.have.a.property("vendorMonaco", "vendor/monaco/monaco-bootstrap.js");
});
it("picks up custom theme", async function () {
theme.init({
editorTheme: {
@@ -64,7 +91,9 @@ describe("api/editor/theme", function () {
icon: "/absolute/path/to/theme/tabicon",
colour: "#8f008f"
},
css: "/absolute/path/to/custom/css/file.css",
css: [
"/absolute/path/to/custom/css/file.css"
],
scripts: "/absolute/path/to/script.js"
},
header: {
@@ -185,4 +214,62 @@ describe("api/editor/theme", function () {
});
it("includes list of plugin themes", function(done) {
theme.init({},{
plugins: { getPluginsByType: _ => [{id:"theme-plugin"}] }
});
const app = theme.app();
request(app)
.get("/")
.end(function(err,res) {
if (err) {
return done(err);
}
try {
const response = JSON.parse(res.text);
response.should.have.property("themes");
response.themes.should.eql(["theme-plugin"])
done();
} catch(err) {
done(err);
}
});
});
it("includes theme plugin settings", async function () {
theme.init({
editorTheme: {
theme: 'test-theme'
}
},{
plugins: { getPlugin: t => {
return ({'test-theme':{
path: '/abosolute/path/to/plugin',
css: [
"path/to/custom/css/file1.css",
"/invalid/path/to/file2.css",
"../another/invalid/path/file3.css"
],
scripts: [
"path/to/custom/js/file1.js",
"/invalid/path/to/file2.js",
"../another/invalid/path/file3.js"
]
}})[t.id];
} }
});
theme.app();
var context = await theme.context();
context.should.have.a.property("page");
context.page.should.have.a.property("css");
context.page.css.should.have.lengthOf(1);
context.page.css[0].should.eql('theme/css/file1.css');
context.page.should.have.a.property("scripts");
context.page.scripts.should.have.lengthOf(1);
context.page.scripts[0].should.eql('theme/scripts/file1.js');
});
});

View File

@@ -33,10 +33,21 @@ describe("api/editor/ui", function() {
nodes: {
getIcon: function(opts) {
return new Promise(function(resolve,reject) {
fs.readFile(NR_TEST_UTILS.resolve("@node-red/editor-client/src/images/icons/arrow-in.svg"), function(err,data) {
resolve(data);
})
if (opts.icon === "icon.png") {
fs.readFile(NR_TEST_UTILS.resolve("@node-red/editor-client/src/images/icons/arrow-in.svg"), function(err,data) {
resolve(data);
})
} else {
resolve(null);
}
});
},
getModuleResource: async function(opts) {
if (opts.module !== "test-module" || opts.path !== "a/path/text.txt") {
return null;
} else {
return "Some text data";
}
}
}
});
@@ -110,6 +121,53 @@ describe("api/editor/ui", function() {
});
});
it('returns the default icon for invalid paths', function(done) {
var defaultIcon = fs.readFileSync(NR_TEST_UTILS.resolve("@node-red/editor-client/src/images/icons/arrow-in.svg"));
request(app)
.get("/icons/module/unreal.png")
.expect("Content-Type", /image\/svg/)
.expect(200)
.parse(binaryParser)
.end(function(err,res) {
if (err){
return done(err);
}
Buffer.isBuffer(res.body).should.be.true();
compareBuffers(res.body,defaultIcon);
done();
});
});
});
describe("module resource handler", function() {
before(function() {
app = express();
app.get(/^\/resources\/((?:@[^\/]+\/)?[^\/]+)\/(.+)$/,ui.moduleResource);
});
it('returns the requested resource', function(done) {
request(app)
.get("/resources/test-module/a/path/text.txt")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.text.should.eql('Some text data');
done();
});
});
it('404s invalid paths', function(done) {
request(app)
.get("/resources/test-module/../a/path/text.txt")
.expect(404)
.end(function(err,res) {
if (err) {
return done(err);
}
done();
});
});
});
describe("editor ui handler", function() {