js: First testing and improvements

--

* Introduced Mocha/chai as testsuite. After development build
  'npm test' should run the unit tests. Functionality exclusive to
  Browsers/WebExtensions cannot be run this way, so some other testing
  is still needed.

  - package.json: Added required development packages
  - .babelrc indirect configuration for mocha. ES6 transpiling
    needs some babel configuration, but mocha has no setting for it.
  - test/mocha.opts Vonfiguration for mocha runs

* Fixed errors:
  - Helpers.js toKeyIdArray; isLongId is now exported
  - Key.js Key constructor failed
  - Message.js will not throw an Error during construction, a new
    message is now created with createMessage, which can return an
    Error or a GPGME_Message object

* Tests:
  - test/Helpers: exports from Helpers.js, GPGME_Error handling
  - test/Message: first init test with bad parameters
This commit is contained in:
Maximilian Krambach 2018-04-25 19:45:39 +02:00
parent 1fb310cabe
commit 3685913bf5
12 changed files with 227 additions and 56 deletions

1
lang/js/.babelrc Normal file
View File

@ -0,0 +1 @@
{ "presets": ["es2015"] }

View File

@ -5,13 +5,15 @@
"main": "src/index.js",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "mocha"
},
"keywords": [],
"author": "",
"license": "",
"devDependencies": {
"webpack": "^4.5.0",
"webpack-cli": "^2.0.14"
"webpack-cli": "^2.0.14",
"chai": "^4.1.2",
"mocha": "^5.1.1"
}
}

View File

@ -83,7 +83,7 @@ export class Connection{
*/
post(message){
if (!this.isConnected){
return Promise.reject(gpgme_error('CONN_NO_CONNECT'));
return Promise.reject(gpgme_error('CONN_DISCONNECTED'));
}
if (!message || !message instanceof GPGME_Message){
return Promise.reject(gpgme_error('PARAM_WRONG'), message);

View File

@ -55,14 +55,9 @@ const err_list = {
msg: 'The Message is empty.',
type: 'error'
},
'MSG_OP_PENDING': {
msg: 'There is no operation specified yet. The parameter cannot'
+ ' be set',
type: 'warning'
},
'MSG_WRONG_OP': {
msg: 'The operation requested could not be found',
type: 'warning'
type: 'error'
},
'MSG_NO_KEYS' : {
msg: 'There were no valid keys provided.',
@ -78,7 +73,7 @@ const err_list = {
},
// generic
'PARAM_WRONG':{
msg: 'invalid parameter was found',
msg: 'Invalid parameter was found',
type: 'error'
},
'PARAM_IGNORED': {
@ -111,7 +106,7 @@ export function gpgme_error(code = 'GENERIC_ERROR', info){
return new GPGME_Error(code);
}
if (err_list[code].type === 'warning'){
console.log(new GPGME_Error(code));
console.warn(code + ': ' + err_list[code].msg);
}
return null;
} else if (code === 'GNUPG_ERROR'){

View File

@ -18,6 +18,7 @@
* SPDX-License-Identifier: LGPL-2.1+
*/
import { gpgme_error } from "./Errors";
import { GPGME_Key } from "./Key";
/**
* Tries to return an array of fingerprints, either from input fingerprints or
@ -26,7 +27,7 @@ import { gpgme_error } from "./Errors";
* @returns {Array<String>} Array of fingerprints.
*/
export function toKeyIdArray(input, nocheck){
export function toKeyIdArray(input){
if (!input){
gpgme_error('MSG_NO_KEYS');
return [];
@ -46,7 +47,7 @@ export function toKeyIdArray(input, nocheck){
let fpr = '';
if (input[i] instanceof GPGME_Key){
fpr = input[i].fingerprint;
} else if (input[i].hasOwnProperty(primaryKey) &&
} else if (input[i].hasOwnProperty('primaryKey') &&
input[i].primaryKey.hasOwnProperty(getFingerprint)){
fpr = input[i].primaryKey.getFingerprint();
}
@ -92,7 +93,7 @@ export function isFingerprint(string){
/**
* check if the input is a valid Hex string with a length of 16
*/
function isLongId(string){
export function isLongId(string){
return hextest(string, 16);
};

View File

@ -26,9 +26,9 @@
*
*/
import {isFingerprint} from './Helpers'
import {gpgme_error} from './Errors'
import { GPGME_Message } from './Message';
import { isFingerprint } from './Helpers'
import { gpgme_error } from './Errors'
import { createMessage } from './Message';
import { permittedOperations } from './permittedOperations';
export class GPGME_Key {
@ -39,7 +39,7 @@ export class GPGME_Key {
set fingerprint(fpr){
if (isFingerprint(fpr) === true && !this._fingerprint){
this._fingerprint = fingerprint;
this._fingerprint = fpr;
}
}
@ -181,9 +181,12 @@ function checkKey(fingerprint, property){
}
return new Promise(function(resolve, reject){
if (!isFingerprint(fingerprint)){
reject('KEY_INVALID');
reject(gpgme_error('KEY_INVALID'));
}
let msg = createMessage ('keyinfo');
if (msg instanceof Error){
reject(gpgme_error('PARAM_WRONG'));
}
let msg = new GPGME_Message('keyinfo');
msg.setParameter('fingerprint', this.fingerprint);
return (this.connection.post(msg)).then(function(result){
if (result.hasOwnProperty(property)){

View File

@ -18,7 +18,7 @@
* SPDX-License-Identifier: LGPL-2.1+
*/
import {GPGME_Message} from './Message'
import {createMessage} from './Message'
import {GPGME_Key} from './Key'
import { isFingerprint, isLongId } from './Helpers';
import { gpgme_error } from './Errors';
@ -50,7 +50,10 @@ export class GPGME_Keyring {
*
*/
getKeys(pattern, include_secret){
let msg = new GPGME_Message('listkeys');
let msg = createMessage('listkeys');
if (msg instanceof Error){
return Promise.reject(msg);
}
if (pattern && typeof(pattern) === 'string'){
msg.setParameter('pattern', pattern);
}

View File

@ -19,13 +19,34 @@
*/
import { permittedOperations } from './permittedOperations'
import { gpgme_error } from './Errors'
export class GPGME_Message {
export function createMessage(operation){
if (typeof(operation) !== 'string'){
return gpgme_error('PARAM_WRONG');
}
if (permittedOperations.hasOwnProperty(operation)){
return new GPGME_Message(operation);
} else {
return gpgme_error('MSG_WRONG_OP');
}
}
/**
* Prepares a communication request. It checks operations and parameters in
* ./permittedOperations.
* @param {String} operation
*/
class GPGME_Message {
//TODO getter
constructor(operation){
setOperation(this, operation);
this.operation = operation;
}
set operation (op){
}
get operation(){
return this._msg.op;
}
@ -41,9 +62,6 @@ export class GPGME_Message {
if (!param || typeof(param) !== 'string'){
return gpgme_error('PARAM_WRONG');
}
if (!this._msg || !this._msg.op){
return gpgme_error('MSG_OP_PENDING');
}
let po = permittedOperations[this._msg.op];
if (!po){
return gpgme_error('MSG_WRONG_OP');
@ -90,22 +108,3 @@ export class GPGME_Message {
}
}
/**
* Defines the operation this message will have
* @param {String} operation Must be defined in permittedOperations
* TODO: move to constructor?
*/
function setOperation (scope, operation){
if (!operation || typeof(operation) !== 'string'){
return gpgme_error('PARAM_WRONG');
}
if (permittedOperations.hasOwnProperty(operation)){
if (!scope._msg){
scope._msg = {};
}
scope._msg.op = operation;
} else {
return gpgme_error('MSG_WRONG_OP');
}
}

View File

@ -19,7 +19,7 @@
*/
import {Connection} from "./Connection"
import {GPGME_Message} from './Message'
import {GPGME_Message, createMessage} from './Message'
import {toKeyIdArray} from "./Helpers"
import { gpgme_error } from "./Errors"
import { GPGME_Keyring } from "./Keyring";
@ -71,8 +71,10 @@ export class GpgME {
*/
encrypt(data, publicKeys, wildcard=false){
let msg = new GPGME_Message('encrypt');
let msg = createMessage('encrypt');
if (msg instanceof Error){
return Promise.reject(msg)
}
// TODO temporary
msg.setParameter('armor', true);
msg.setParameter('always-trust', true);
@ -101,7 +103,10 @@ export class GpgME {
if (data === undefined){
return Promise.reject(gpgme_error('MSG_EMPTY'));
}
let msg = new GPGME_Message('decrypt');
let msg = createMessage('decrypt');
if (msg instanceof Error){
return Promise.reject(msg);
}
putData(msg, data);
return this.connection.post(msg);
@ -109,21 +114,27 @@ export class GpgME {
deleteKey(key, delete_secret = false, no_confirm = false){
return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED'));
let msg = new GPGME_Message('deletekey');
let msg = createMessage('deletekey');
if (msg instanceof Error){
return Promise.reject(msg);
}
let key_arr = toKeyIdArray(key);
if (key_arr.length !== 1){
throw('TODO');
//should always be ONE key
return Promise.reject(
gpgme_error('GENERIC_ERROR'));
// TBD should always be ONE key?
}
msg.setParameter('key', key_arr[0]);
if (delete_secret === true){
msg.setParameter('allow_secret', true); //TBD
msg.setParameter('allow_secret', true);
// TBD
}
if (no_confirm === true){ //TODO: Do we want this hidden deep in the code?
msg.setParameter('delete_force', true); //TBD
msg.setParameter('delete_force', true);
// TBD
}
this.connection.post(msg).then(function(success){
//TODO: it seems that there is always errors coming back:
// TODO: it seems that there is always errors coming back:
}, function(error){
switch (error.msg){
case 'ERR_NO_ERROR':

110
lang/js/test/Helpers.js Normal file
View File

@ -0,0 +1,110 @@
/* gpgme.js - Javascript integration for gpgme
* Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
*
* This file is part of GPGME.
*
* GPGME is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* GPGME is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, see <http://www.gnu.org/licenses/>.
* SPDX-License-Identifier: LGPL-2.1+
*/
import { expect } from "../node_modules/chai/chai";
import { gpgme_error} from "../src/Errors";
import { GPGME_Key } from "../src/Key";
import { isLongId, isFingerprint, toKeyIdArray } from "../src/Helpers"
const helper_params = {
validLongId: '0A0A0A0A0A0A0A0A',
validGPGME_Key: new GPGME_Key('ADDBC303B6D31026F5EB4591A27EABDF283121BB'),
validKeys: [new GPGME_Key('A1E3BC45BDC8E87B74F4392D53B151A1368E50F3'),
'ADDBC303B6D31026F5EB4591A27EABDF283121BB',
new GPGME_Key('EE17AEE730F88F1DE7713C54BBE0A4FF7851650A')],
validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A',
invalidLongId: '9A9A7A7A8A9A9A7A7A8A',
invalidFingerprint: [{hello:'World'}],
invalidKeyArray: {curiosity:'uncat'},
invalidKeyArray_OneBad: [
new GPGME_Key('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08'),
'E1D18E6E994FA9FE9360Bx0E687B940FEFEB095A',
'3AEA7FE4F5F416ED18CEC63DD519450D9C0FAEE5'],
invalidErrorCode: 'Please type in all your passwords.'
}
describe('Error Object handling', function(){
it('check the Timeout error', function(){
let test0 = gpgme_error('CONN_TIMEOUT');
expect(test0).to.be.an.instanceof(Error);
expect(test0.code).to.equal('CONN_TIMEOUT');
});
it('Error Object returns generic code if code is not listed', function(){
let test0 = gpgme_error(helper_params.invalidErrorCode);
expect(test0).to.be.an.instanceof(Error);
expect(test0.code).to.equal('GENERIC_ERROR');
});
it('Warnings like PARAM_IGNORED should not return errors', function(){
let test0 = gpgme_error('PARAM_IGNORED');
expect(test0).to.be.null;
});
});
describe('Fingerprint checking', function(){
it('isFingerprint(): valid Fingerprint', function(){
let test0 = isFingerprint(helper_params.validFingerprint);
expect(test0).to.be.true;
});
it('isFingerprint(): invalid Fingerprint', function(){
let test0 = isFingerprint(helper_params.invalidFingerprint);
expect(test0).to.be.false;
});
});
describe('Converting to Fingerprint', function(){
it('Correct Inputs', function(){
it('Fingerprint string', function(){
let test0 = toKeyIdArray(helper_params.validFingerprint);
expect(test0).to.be.an('array');
expect(test0).to.include(helper_params.validFingerprint);
});
it('GPGME_Key', function(){
expect(helper_params.validGPGME_Key).to.be.an.instanceof(GPGME_Key);
let test0 = toKeyIdArray(helper_params.validGPGME_Key);
expect(test0).to.be.an('array');
expect(test0).to.include(helper_params.validGPGME_Key.fingerprint);
});
it('Array of valid inputs', function(){
let test0 = toKeyIdArray(helper_params.validKeys);
expect(test0).to.be.an('array');
expect(test0).to.have.lengthOf(helper_params.validKeys.length);
});
});
describe('Incorrect inputs', function(){
it('valid Long ID', function(){
let test0 = toKeyIdArray(helper_params.validLongId);
expect(test0).to.be.empty;
});
it('invalidFingerprint', function(){
let test0 = toKeyIdArray(helper_params.invalidFingerprint);
expect(test0).to.be.empty;
});
it('invalidKeyArray', function(){
let test0 = toKeyIdArray(helper_params.invalidKeyArray);
expect(test0).to.be.empty;
});
it('Partially invalid array', function(){
let test0 = toKeyIdArray(helper_params.invalidKeyArray_OneBad);
expect(test0).to.be.an('array');
expect(test0).to.have.lengthOf(
helper_params.invalidKeyArray_OneBad.length - 1);
});
});
});

42
lang/js/test/Message.js Normal file
View File

@ -0,0 +1,42 @@
/* gpgme.js - Javascript integration for gpgme
* Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
*
* This file is part of GPGME.
*
* GPGME is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* GPGME is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, see <http://www.gnu.org/licenses/>.
* SPDX-License-Identifier: LGPL-2.1+
*/
import { expect } from "../node_modules/chai/chai";
import { GPGME_Message, createMessage } from "../src/Message";
const message_params = {
invalid_op_action : 'dance',
invalid_op_type : [234, 34, '<>'],
}
describe('Message Object', function(){
describe('incorrect initialization', function(){
it('non-allowed operation', function(){
let test0 = createMessage(message_params.invalid_op_action);
expect(test0).to.be.an.instanceof(Error);
expect(test0.code).to.equal('MSG_WRONG_OP');
});
it('wrong parameter type in constructor', function(){
let test0 = createMessage(message_params.invalid_op_type);
expect(test0).to.be.an.instanceof(Error);
expect(test0.code).to.equal('PARAM_WRONG');
});
});
});

4
lang/js/test/mocha.opts Normal file
View File

@ -0,0 +1,4 @@
--require babel-register
--reporter spec
--ui bdd
--colors