Skip to content

Commit a79a871

Browse files
committed
test(Provider): Wrote some tests, testing integration with travisCi
1 parent d8e6d59 commit a79a871

File tree

14 files changed

+597
-71
lines changed

14 files changed

+597
-71
lines changed

.eslintrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"env": {
33
"commonjs": true,
44
"es6": true,
5-
"node": true
5+
"node": true,
6+
"mocha": true
67
},
78
"extends": "eslint:recommended",
89
"globals": {

.travis.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
language: node_js
2+
node_js:
3+
- "10"
4+
services: mongodb
5+
cache:
6+
directories:
7+
- node_modules
8+
before_script:
9+
- sleep 15
10+
- mongo mydb_test --eval 'db.createUser({user:"travis",pwd:"test",roles:["readWrite"]});'
11+
jobs:
12+
include:
13+
- stage: "Testing"
14+
script: npm test
15+
if: branch = master

dist/Provider/Provider.js

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,12 @@ class Provider {
227227
let isApiRequest = false;
228228
let cookies = req.signedCookies; // Validate issuer_code to see if its a route or normal request
229229

230-
if (issuer.search('plat') === -1) isApiRequest = true;
230+
if (issuer.indexOf('plat') === -1) isApiRequest = true;
231231

232232
if (!isApiRequest) {
233233
try {
234234
let decode = Buffer.from(decodeURIComponent(issuer.split('plat')[1]), 'base64').toString('ascii');
235-
if (!validator.isURL(decode) && decode.search('localhost') === -1) isApiRequest = true;
235+
if (!validator.isURL(decode) && decode.indexOf('localhost') === -1) isApiRequest = true;
236236
} catch (err) {
237237
provMainDebug(err);
238238
isApiRequest = true;
@@ -327,7 +327,7 @@ class Provider {
327327
for (let key of Object.keys(cookies).sort((a, b) => b.length - a.length)) {
328328
if (key === issuer) continue;
329329

330-
if (path.search(key) !== -1) {
330+
if (path.indexOf(key) !== -1) {
331331
isPath = cookies[key];
332332
break;
333333
}
@@ -391,6 +391,7 @@ class Provider {
391391
/**
392392
* @description Starts listening to a given port for LTI requests and opens connection to the database.
393393
* @param {number} port - The port the Provider should listen to.
394+
* @returns {Promise<true| false>}
394395
*/
395396

396397

@@ -425,8 +426,8 @@ class Provider {
425426
});
426427
if (this.db.readyState === 0) await mongoose.connect((0, _classPrivateFieldGet2.default)(this, _dbConnection).url, (0, _classPrivateFieldGet2.default)(this, _dbConnection).options);
427428
} catch (err) {
428-
provMainDebug('Error in MongoDb connection: ' + err);
429-
throw new Error('Unable to connect to database');
429+
provMainDebug(err);
430+
return false;
430431
}
431432
/* In case no port is provided uses 3000 */
432433

@@ -436,6 +437,22 @@ class Provider {
436437
(0, _classPrivateFieldGet2.default)(this, _server).listen(port, 'Lti Provider tool is listening on port ' + port + '!\n\nLTI provider config: \n>Initiate login URL: ' + (0, _classPrivateFieldGet2.default)(this, _loginUrl) + '\n>App Url: ' + (0, _classPrivateFieldGet2.default)(this, _appUrl) + '\n>Session Timeout Url: ' + (0, _classPrivateFieldGet2.default)(this, _sessionTimeoutUrl) + '\n>Invalid Token Url: ' + (0, _classPrivateFieldGet2.default)(this, _invalidTokenUrl));
437438
return true;
438439
}
440+
/**
441+
* @description Closes connection to database and stops server.
442+
* @returns {Promise<true | false>}
443+
*/
444+
445+
446+
async close() {
447+
try {
448+
await (0, _classPrivateFieldGet2.default)(this, _server).close();
449+
this.db.removeAllListeners();
450+
await this.db.close();
451+
return true;
452+
} catch (err) {
453+
return false;
454+
}
455+
}
439456
/**
440457
* @description Sets the callback function called whenever theres a sucessfull connection, exposing a Conection object containing the id_token decoded parameters.
441458
* @param {Function} _connectCallback - Function that is going to be called everytime a platform sucessfully connects to the provider.
@@ -444,6 +461,7 @@ class Provider {
444461
* @param {Function} [options.sessionTimeout] - Route function executed everytime the session expires. It must in the end return a 401 status, even if redirects ((req, res, next) => {res.sendStatus(401)}).
445462
* @param {Function} [options.invalidToken] - Route function executed everytime the system receives an invalid token or cookie. It must in the end return a 401 status, even if redirects ((req, res, next) => {res.sendStatus(401)}).
446463
* @example .onConnect((conection, response)=>{response.send(connection)}, {secure: true})
464+
* @returns {true}
447465
*/
448466

449467

@@ -454,51 +472,64 @@ class Provider {
454472
if (options.invalidToken) (0, _classPrivateFieldSet2.default)(this, _invalidToken, options.invalidToken);
455473
}
456474

457-
(0, _classPrivateFieldSet2.default)(this, _connectCallback2, _connectCallback);
475+
if (_connectCallback) {
476+
(0, _classPrivateFieldSet2.default)(this, _connectCallback2, _connectCallback);
477+
return true;
478+
}
479+
480+
throw new Error('Missing callback');
458481
}
459482
/**
460483
* @description Gets/Sets login Url responsible for dealing with the OIDC login flow. If no value is set "/login" is used.
461484
* @param {string} url - Login url.
462485
* @example provider.loginUrl('/login')
486+
* @returns {String}
463487
*/
464488

465489

466490
loginUrl(url) {
467491
if (!url) return (0, _classPrivateFieldGet2.default)(this, _loginUrl);
468492
(0, _classPrivateFieldSet2.default)(this, _loginUrl, url);
493+
return (0, _classPrivateFieldGet2.default)(this, _loginUrl);
469494
}
470495
/**
471496
* @description Gets/Sets main application Url that will receive the final decoded Idtoken. If no value is set "/" (root) is used.
472497
* @param {string} url - App url.
473498
* @example provider.appUrl('/app')
499+
* @returns {String}
474500
*/
475501

476502

477503
appUrl(url) {
478504
if (!url) return (0, _classPrivateFieldGet2.default)(this, _appUrl);
479505
(0, _classPrivateFieldSet2.default)(this, _appUrl, url);
506+
return (0, _classPrivateFieldGet2.default)(this, _appUrl);
480507
}
481508
/**
482509
* @description Gets/Sets session timeout Url that will be called whenever the system encounters a session timeout. If no value is set "/sessionTimeout" is used.
483510
* @param {string} url - Session timeout url.
484511
* @example provider.sessionTimeoutUrl('/sesstimeout')
512+
* @returns {String}
485513
*/
486514

487515

488516
sessionTimeoutUrl(url) {
489517
if (!url) return (0, _classPrivateFieldGet2.default)(this, _sessionTimeoutUrl);
490518
(0, _classPrivateFieldSet2.default)(this, _sessionTimeoutUrl, url);
519+
return (0, _classPrivateFieldGet2.default)(this, _sessionTimeoutUrl);
491520
}
492521
/**
493522
* @description Gets/Sets invalid token Url that will be called whenever the system encounters a invalid token or cookie. If no value is set "/invalidToken" is used.
494523
* @param {string} url - Invalid token url.
495524
* @example provider.invalidTokenUrl('/invtoken')
525+
* @returns {String}
496526
*/
497527

498528

499529
invalidTokenUrl(url) {
500530
if (!url) return (0, _classPrivateFieldGet2.default)(this, _invalidTokenUrl);
501531
(0, _classPrivateFieldSet2.default)(this, _invalidTokenUrl, url);
532+
return (0, _classPrivateFieldGet2.default)(this, _invalidTokenUrl);
502533
}
503534
/**
504535
* @description Registers a platform.
@@ -509,6 +540,7 @@ class Provider {
509540
* @param {object} authConfig - Authentication method and key for verifying messages from the platform. {method: "RSA_KEY", key:"PUBLIC KEY..."}
510541
* @param {String} authConfig.method - Method of authorization "RSA_KEY" or "JWK_KEY" or "JWK_SET".
511542
* @param {String} authConfig.key - Either the RSA public key provided by the platform, or the JWK key, or the JWK keyset address.
543+
* @returns {Promise<Platform|false>}
512544
*/
513545

514546

@@ -552,6 +584,7 @@ class Provider {
552584
* @description Gets a platform.
553585
* @param {String} url - Platform url.
554586
* @param {String} [ENCRYPTIONKEY] - Encryption key. THIS PARAMETER IS ONLY IN A FEW SPECIFIC CALLS, DO NOT USE IN YOUR APPLICATION.
587+
* @returns {Promise<Platform | false>}
555588
*/
556589

557590

@@ -582,6 +615,7 @@ class Provider {
582615
/**
583616
* @description Deletes a platform.
584617
* @param {string} url - Platform url.
618+
* @returns {Promise<true | false>}
585619
*/
586620

587621

@@ -593,6 +627,7 @@ class Provider {
593627
}
594628
/**
595629
* @description Gets all platforms.
630+
* @returns {Promise<Array<Platform>| false>}
596631
*/
597632

598633

@@ -639,14 +674,17 @@ class Provider {
639674
let lineitemRes = await got.get(lineitemsEndpoint, {
640675
headers: {
641676
Authorization: tokenRes.token_type + ' ' + tokenRes.access_token
642-
}
677+
},
678+
body: JSON.stringify({
679+
request: 'lineitems'
680+
})
643681
});
644682
let resourceId = idtoken.platformContext.resource;
645683
let lineitem = find(JSON.parse(lineitemRes.body), ['resourceLinkId', resourceId.id]);
646684
let lineitemUrl = lineitem.id;
647685
let scoreUrl = lineitemUrl + '/scores';
648686

649-
if (lineitemUrl.split('\?') !== -1) {
687+
if (lineitemUrl.indexOf('?') !== -1) {
650688
let query = lineitemUrl.split('\?')[1];
651689
let url = lineitemUrl.split('\?')[0];
652690
scoreUrl = url + '/scores?' + query;
@@ -657,15 +695,20 @@ class Provider {
657695
message.timestamp = new Date(Date.now()).toISOString();
658696
message.scoreMaximum = lineitem.scoreMaximum;
659697
provMainDebug(message);
660-
await got.post(scoreUrl, {
698+
let finalRes = await got.post(scoreUrl, {
661699
headers: {
662700
Authorization: tokenRes.token_type + ' ' + tokenRes.access_token,
663701
'Content-Type': 'application/vnd.ims.lis.v1.score+json'
664702
},
665703
body: JSON.stringify(message)
666704
});
667-
provMainDebug('Message successfully sent');
668-
return true;
705+
706+
if (finalRes.statusCode === 200) {
707+
provMainDebug('Message successfully sent');
708+
return true;
709+
}
710+
711+
return false;
669712
} catch (err) {
670713
provMainDebug(err);
671714
return false;
@@ -675,7 +718,7 @@ class Provider {
675718
* @description Redirect to a new location and sets it's cookie if the location represents a separate resource
676719
* @param {Object} res - Express response object
677720
* @param {String} path - Redirect path
678-
* @param {Boolea} [isNewResource = false] - If true creates new resource and its cookie
721+
* @param {Boolean} [isNewResource = false] - If true creates new resource and its cookie
679722
* @example lti.generatePathCookie(response, '/path', true)
680723
*/
681724

dist/Utils/Auth.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,11 @@ class Auth {
8989
provAuthDebug('Retrieving key from jwk_set');
9090
if (!kid) throw new Error('NoKidFoundInToken');
9191
let keysEndpoint = authConfig.key;
92-
let res = await got.get(keysEndpoint);
92+
let res = await got.get(keysEndpoint, {
93+
body: JSON.stringify({
94+
request: 'keyset'
95+
})
96+
});
9397
let keyset = JSON.parse(res.body).keys;
9498
if (!keyset) throw new Error('NoKeySetFound');
9599
let key = jwk.jwk2pem(find(keyset, ['kid', kid]));

dist/Utils/Platform.js

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -278,29 +278,17 @@ class Platform {
278278
if (!token) {
279279
provPlatformDebug('Access_token for ' + (0, _classPrivateFieldGet2.default)(this, _platformUrl) + ' not found');
280280
provPlatformDebug('Attempting to generate new access_token for ' + (0, _classPrivateFieldGet2.default)(this, _platformUrl));
281-
282-
try {
283-
let res = await Auth.getAccessToken(this, (0, _classPrivateFieldGet2.default)(this, _ENCRYPTIONKEY2));
284-
return res;
285-
} catch (err) {
286-
provPlatformDebug(err);
287-
return false;
288-
}
281+
let res = await Auth.getAccessToken(this, (0, _classPrivateFieldGet2.default)(this, _ENCRYPTIONKEY2));
282+
return res;
289283
} else {
290284
provPlatformDebug('Access_token found');
291285

292286
if ((Date.now() - token[0].createdAt) / 1000 > token[0].expires_in) {
293287
provPlatformDebug('Token expired');
294288
provPlatformDebug('Access_token for ' + (0, _classPrivateFieldGet2.default)(this, _platformUrl) + ' not found');
295289
provPlatformDebug('Attempting to generate new access_token for ' + (0, _classPrivateFieldGet2.default)(this, _platformUrl));
296-
297-
try {
298-
let res = await Auth.getAccessToken(this, (0, _classPrivateFieldGet2.default)(this, _ENCRYPTIONKEY2));
299-
return res;
300-
} catch (err) {
301-
provPlatformDebug(err);
302-
return false;
303-
}
290+
let res = await Auth.getAccessToken(this, (0, _classPrivateFieldGet2.default)(this, _ENCRYPTIONKEY2));
291+
return res;
304292
}
305293

306294
return token[0].token;

dist/Utils/Server.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ const cookieParser = require('cookie-parser');
1313

1414
const cors = require('cors');
1515

16+
const serverdebug = require('debug')('provider:server');
17+
1618
class Server {
1719
constructor(https, ssl, ENCRYPTIONKEY) {
1820
this.app = express();
21+
this.server = false;
1922
this.ssl = false;
2023
if (https) this.ssl = ssl;
2124
this.app.use(helmet({
@@ -32,13 +35,17 @@ class Server {
3235
}
3336

3437
listen(port, message) {
35-
if (this.ssl) https.createServer(this.ssl, this.app).listen(port, () => console.log(message));else this.app.listen(port, () => console.log(message));
38+
if (this.ssl) this.server = https.createServer(this.ssl, this.app).listen(port, () => serverdebug(message));else this.server = this.app.listen(port, () => serverdebug(message));
3639
}
3740

3841
setStaticPath(path) {
3942
this.app.use('/', express.static(path));
4043
}
4144

45+
close() {
46+
if (this.server) this.server.close();
47+
}
48+
4249
}
4350

4451
module.exports = Server;

docs/provider.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ Express server object.
140140

141141
**Type**: ```Express```
142142

143-
#### Provider.constructor(encryptionkey, database, [, options])
143+
#### Provider.constructor(encryptionkey, database [, options])
144144

145145
Exposes methods for easy manipulation of the LTI 1.3 standard as a LTI Provider and a "app" object to manipulate the [Express](https://expressjs.com/) server.
146146

@@ -185,6 +185,18 @@ Starts listening to a given port for LTI requests and opens connection to the co
185185

186186

187187

188+
189+
#### async Provider.close()
190+
191+
Closes connection to database and stops server.
192+
193+
194+
##### Returns
195+
196+
- Promise that resolves ```true``` when it's done or ```false``` if something fails.
197+
198+
199+
188200
#### Provider.onConnect(_connectCallback[, options])
189201

190202
Sets the callback function called whenever theres a sucessfull connection, exposing a Conection object containing the id_token decoded parameters.
@@ -416,7 +428,7 @@ Sends a grade message to [Platform](platform.md).
416428
- Promise that resolves ```true``` if it succeeds and ```false``` if it fails.
417429

418430

419-
#### async Provider.redirect(idToken, message)
431+
#### async Provider.redirect(response, path [, isNewResource])
420432

421433
Redirects to another route, handles the context in the url and if the route is a specific resource, generates the context cookie for it.
422434

@@ -427,7 +439,7 @@ Redirects to another route, handles the context in the url and if the route is a
427439

428440
| Name | Type | Description | |
429441
| ---- | ---- | ----------- | -------- |
430-
| res | `Object` | Espress response object.| &nbsp; |
442+
| response | `Object` | Espress response object.| &nbsp; |
431443
| path | `String` | Redirect path. | &nbsp; |
432444
| isNewResource | `Boolean` | = false] Set to true if path is a resource, the tool will create a new context cookie. | *Optional* |
433445

0 commit comments

Comments
 (0)