зеркало из https://github.com/go-gitea/u2f-api.git
Added unit tests
This commit is contained in:
Родитель
6ef703d0f9
Коммит
4e263f703f
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
test
|
||||
|
|
@ -23,7 +23,7 @@ function getBackend( Promise )
|
|||
|
||||
if ( isSafari )
|
||||
// Safari doesn't support U2F, and the Safari-FIDO-U2F
|
||||
// extension lacks full support (Multi-factet apps), so we
|
||||
// extension lacks full support (Multi-facet apps), so we
|
||||
// block it until proper support.
|
||||
return notSupported( );
|
||||
|
||||
|
@ -43,6 +43,10 @@ function getBackend( Promise )
|
|||
// U2F isn't supported over http, only https
|
||||
return notSupported( );
|
||||
|
||||
if ( typeof MessageChannel === 'undefined' )
|
||||
// Unsupported browser, the chrome hack would throw
|
||||
return notSupported( );
|
||||
|
||||
// Test for google extension support
|
||||
chromeApi.isSupported( function( ok )
|
||||
{
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
17
package.json
17
package.json
|
@ -8,6 +8,12 @@
|
|||
"type": "git",
|
||||
"url": "https://github.com/grantila/u2f-api.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "scripts/test.sh"
|
||||
},
|
||||
"pre-commit": [
|
||||
"test"
|
||||
],
|
||||
"keywords": [
|
||||
"u2f",
|
||||
"api",
|
||||
|
@ -18,5 +24,14 @@
|
|||
"yubikey",
|
||||
"promise"
|
||||
],
|
||||
"types": "./index.d.ts"
|
||||
"types": "./index.d.ts",
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^2.2.43",
|
||||
"already": "^0.6.0",
|
||||
"chai": "^4.1.2",
|
||||
"jsdom": "^11.2.0",
|
||||
"mocha": "^3.5.3",
|
||||
"pre-commit": "^1.2.2",
|
||||
"typescript": "^2.5.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
rm -rf test
|
||||
cp -r test.in test
|
||||
|
||||
node_modules/.bin/tsc -p test
|
||||
|
||||
node_modules/.bin/mocha
|
|
@ -0,0 +1 @@
|
|||
import 'mocha';
|
|
@ -0,0 +1,226 @@
|
|||
'use strict';
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __generator = (this && this.__generator) || function (thisArg, body) {
|
||||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t;
|
||||
return { next: verb(0), "throw": verb(1), "return": verb(2) };
|
||||
function verb(n) { return function (v) { return step([n, v]); }; }
|
||||
function step(op) {
|
||||
if (f) throw new TypeError("Generator is already executing.");
|
||||
while (_) try {
|
||||
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
|
||||
if (y = 0, t) op = [0, t.value];
|
||||
switch (op[0]) {
|
||||
case 0: case 1: t = op; break;
|
||||
case 4: _.label++; return { value: op[1], done: false };
|
||||
case 5: _.label++; y = op[1]; op = [0]; continue;
|
||||
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
||||
default:
|
||||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
||||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
||||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
||||
if (t[2]) _.ops.pop();
|
||||
_.trys.pop(); continue;
|
||||
}
|
||||
op = body.call(thisArg, _);
|
||||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
||||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
||||
}
|
||||
};
|
||||
var _this = this;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
require("mocha");
|
||||
var chai_1 = require("chai");
|
||||
var already_1 = require("already");
|
||||
var jsdom = require("jsdom");
|
||||
var _1 = require("../../");
|
||||
var JSDOM = jsdom.JSDOM;
|
||||
var appId = "https://example.org/";
|
||||
var MonkeyPatcher = (function () {
|
||||
function MonkeyPatcher(obj) {
|
||||
this._object = obj;
|
||||
this.clear();
|
||||
}
|
||||
MonkeyPatcher.prototype.patch = function (obj, overwrite) {
|
||||
var _this = this;
|
||||
if (overwrite === void 0) { overwrite = false; }
|
||||
Object.keys(obj).forEach(function (key) {
|
||||
var ownProp = _this._object.hasOwnProperty(key);
|
||||
if (!ownProp || overwrite) {
|
||||
var value = obj[key];
|
||||
if (!ownProp)
|
||||
_this._values.push(key);
|
||||
else
|
||||
_this._overwrittenValues.push({ key: key, value: _this._object[key] });
|
||||
_this._object[key] = value;
|
||||
}
|
||||
});
|
||||
};
|
||||
MonkeyPatcher.prototype.restore = function () {
|
||||
var _this = this;
|
||||
this._values.forEach(function (key) {
|
||||
delete _this._object[key];
|
||||
});
|
||||
this._overwrittenValues.forEach(function (_a) {
|
||||
var key = _a.key, value = _a.value;
|
||||
_this._object[key] = value;
|
||||
});
|
||||
this.clear();
|
||||
};
|
||||
MonkeyPatcher.prototype.clear = function () {
|
||||
this._values = [];
|
||||
this._overwrittenValues = [];
|
||||
};
|
||||
return MonkeyPatcher;
|
||||
}());
|
||||
var GlobalMonkeyPatcher = (function (_super) {
|
||||
__extends(GlobalMonkeyPatcher, _super);
|
||||
function GlobalMonkeyPatcher() {
|
||||
return _super.call(this, global) || this;
|
||||
}
|
||||
return GlobalMonkeyPatcher;
|
||||
}(MonkeyPatcher));
|
||||
function deleteModule(moduleName) {
|
||||
try {
|
||||
var solvedName = require.resolve(moduleName);
|
||||
var nodeModule = require.cache[solvedName];
|
||||
if (nodeModule) {
|
||||
for (var i = 0; i < nodeModule.children.length; ++i) {
|
||||
var child = nodeModule.children[i];
|
||||
deleteModule(child.filename);
|
||||
}
|
||||
delete require.cache[solvedName];
|
||||
}
|
||||
}
|
||||
catch (err) { }
|
||||
}
|
||||
function getNewU2FApi() {
|
||||
deleteModule('../../');
|
||||
return require('../../');
|
||||
}
|
||||
function handleTimeout(delay, timeout, fn) {
|
||||
var timeoutPromise = timeout
|
||||
? delay(timeout).then(function () {
|
||||
return ({ errorCode: _1.ErrorCodesEnum.TIMEOUT });
|
||||
})
|
||||
: null;
|
||||
var flowPromise = delay(delay || 0).then(fn);
|
||||
return Promise.race([
|
||||
timeoutPromise,
|
||||
flowPromise,
|
||||
]
|
||||
.filter(function (exists) { return exists; }));
|
||||
}
|
||||
function u2fMock(props) {
|
||||
if (props === void 0) { props = {}; }
|
||||
var store = {};
|
||||
return {
|
||||
sign: function (appId, challenge, signRequests, cbNative, timeout) {
|
||||
handleTimeout(props.delay, timeout, function () {
|
||||
if (props.appId && props.appId !== appId)
|
||||
return { errorCode: _1.ErrorCodesEnum.BAD_REQUEST };
|
||||
})
|
||||
.then(cbNative);
|
||||
},
|
||||
register: function (appId, registerRequests, signRequests, cbNative, timeout) {
|
||||
handleTimeout(props.delay, timeout, function () {
|
||||
if (props.appId && props.appId !== appId)
|
||||
return { errorCode: _1.ErrorCodesEnum.BAD_REQUEST };
|
||||
var secret = Math.random();
|
||||
registerRequests.forEach(function (req) {
|
||||
});
|
||||
})
|
||||
.then(function (value) { return value || {}; })
|
||||
.then(cbNative);
|
||||
},
|
||||
};
|
||||
}
|
||||
function wrappedTest(props, fn) {
|
||||
return function () {
|
||||
var dom = new JSDOM("", Object.assign({
|
||||
url: appId,
|
||||
userAgent: "FakeBrowser/1",
|
||||
}, props));
|
||||
if (!props || !props.mock || !props.mock.disable) {
|
||||
var mock = (props || {}).mock || {};
|
||||
dom.window.u2f = u2fMock();
|
||||
}
|
||||
var gmp = new GlobalMonkeyPatcher();
|
||||
gmp.patch(dom.window);
|
||||
var api = getNewU2FApi();
|
||||
return (_a = already_1.Try(function () { return fn(api); })).then.apply(_a, already_1.Finally(function () {
|
||||
gmp.restore();
|
||||
}));
|
||||
var _a;
|
||||
};
|
||||
}
|
||||
describe('general', function () {
|
||||
it('isSupported should be false for unsupported browsers', wrappedTest({ mock: { disable: true } }, function (api) { return __awaiter(_this, void 0, void 0, function () {
|
||||
var supported;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0: return [4 /*yield*/, api.isSupported()];
|
||||
case 1:
|
||||
supported = _a.sent();
|
||||
chai_1.expect(supported).to.be.false;
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
}); }));
|
||||
it('isSupported should be false for Safari', wrappedTest({ userAgent: "Safari/10" }, function (api) { return __awaiter(_this, void 0, void 0, function () {
|
||||
var supported;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0: return [4 /*yield*/, api.isSupported()];
|
||||
case 1:
|
||||
supported = _a.sent();
|
||||
chai_1.expect(supported).to.be.false;
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
}); }));
|
||||
it('isSupported should be true with fake window.u2f', wrappedTest({}, function (api) { return __awaiter(_this, void 0, void 0, function () {
|
||||
var supported;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0: return [4 /*yield*/, api.isSupported()];
|
||||
case 1:
|
||||
supported = _a.sent();
|
||||
chai_1.expect(supported).to.be.true;
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
}); }));
|
||||
it('isSupported should be true with fake window.u2f', wrappedTest({}, function (api) { return __awaiter(_this, void 0, void 0, function () {
|
||||
var request;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0: return [4 /*yield*/, api.ensureSupport()];
|
||||
case 1:
|
||||
_a.sent();
|
||||
request = "req";
|
||||
api.register({ appId: appId, request: request });
|
||||
chai_1.expect(supported).to.be.true;
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
}); }));
|
||||
});
|
||||
//# sourceMappingURL=index.js.map
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1 @@
|
|||
--recursive
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
var chai = require( 'chai' );
|
||||
|
||||
global.expect = chai.expect;
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"lib": [ "es2015", "es2017", "dom" ],
|
||||
"moduleResolution": "node",
|
||||
"outDir": "compiled",
|
||||
"pretty": true,
|
||||
"sourceMap": true,
|
||||
"target": "es5",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": [
|
||||
"u2f-api"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
'use strict';
|
||||
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { Finally, Try, delay } from 'already';
|
||||
|
||||
import * as jsdom from "jsdom";
|
||||
|
||||
import { API } from '../../';
|
||||
|
||||
|
||||
const ErrorCodesEnum = {
|
||||
CANCELLED: -1,
|
||||
OK: 0,
|
||||
OTHER_ERROR: 1,
|
||||
BAD_REQUEST: 2,
|
||||
CONFIGURATION_UNSUPPORTED: 3,
|
||||
DEVICE_INELIGIBLE: 4,
|
||||
TIMEOUT: 5,
|
||||
};
|
||||
|
||||
const { JSDOM } = jsdom;
|
||||
|
||||
const appId = "https://example.org/";
|
||||
|
||||
interface KeyValue
|
||||
{
|
||||
key: string;
|
||||
value: any;
|
||||
}
|
||||
type KeyValues = Array< KeyValue >;
|
||||
|
||||
class MonkeyPatcher
|
||||
{
|
||||
_object: any;
|
||||
_values: Array< string >;
|
||||
_overwrittenValues: KeyValues;
|
||||
|
||||
constructor( obj: any )
|
||||
{
|
||||
this._object = obj;
|
||||
this.clear( );
|
||||
}
|
||||
|
||||
patch( obj: any, overwrite = false )
|
||||
{
|
||||
Object.keys( obj ).forEach( key =>
|
||||
{
|
||||
const ownProp = this._object.hasOwnProperty( key );
|
||||
|
||||
if ( !ownProp || overwrite )
|
||||
{
|
||||
const value = obj[ key ];
|
||||
|
||||
if ( !ownProp )
|
||||
this._values.push( key );
|
||||
else
|
||||
this._overwrittenValues.push(
|
||||
{ key, value: this._object[ key ] } );
|
||||
|
||||
this._object[ key ] = value;
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
restore( )
|
||||
{
|
||||
this._values.forEach( key =>
|
||||
{
|
||||
delete this._object[ key ];
|
||||
} );
|
||||
this._overwrittenValues.forEach( ( { key, value } ) =>
|
||||
{
|
||||
this._object[ key ] = value;
|
||||
} );
|
||||
this.clear( );
|
||||
}
|
||||
|
||||
clear( )
|
||||
{
|
||||
this._values = [ ];
|
||||
this._overwrittenValues = [ ];
|
||||
}
|
||||
}
|
||||
|
||||
class GlobalMonkeyPatcher extends MonkeyPatcher
|
||||
{
|
||||
constructor( )
|
||||
{
|
||||
super( global );
|
||||
}
|
||||
}
|
||||
|
||||
function deleteModule( moduleName: string ): void
|
||||
{
|
||||
try
|
||||
{
|
||||
const solvedName = require.resolve( moduleName );
|
||||
const nodeModule = require.cache[ solvedName ];
|
||||
|
||||
if ( nodeModule )
|
||||
{
|
||||
for ( let i = 0; i < nodeModule.children.length; ++i )
|
||||
{
|
||||
const child = nodeModule.children[ i ];
|
||||
deleteModule( child.filename );
|
||||
}
|
||||
delete require.cache[ solvedName ];
|
||||
}
|
||||
}
|
||||
catch ( err ) { }
|
||||
}
|
||||
|
||||
function getNewU2FApi( ): API
|
||||
{
|
||||
deleteModule( '../../' );
|
||||
return require( '../../' );
|
||||
}
|
||||
|
||||
interface MockProps
|
||||
{
|
||||
appId?: string;
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
function handleTimeout(
|
||||
props: MockProps,
|
||||
timeout,
|
||||
fn: ( ) => any
|
||||
)
|
||||
: Promise< any >
|
||||
{
|
||||
const timeoutPromise = timeout
|
||||
? delay( timeout ).then( ( ) =>
|
||||
( { errorCode: ErrorCodesEnum.TIMEOUT } ) )
|
||||
: null;
|
||||
|
||||
const flowPromise = delay( props.delay || 0 ).then( fn );
|
||||
|
||||
return Promise.race(
|
||||
[
|
||||
timeoutPromise,
|
||||
flowPromise,
|
||||
]
|
||||
.filter( exists => exists )
|
||||
);
|
||||
}
|
||||
|
||||
type FakeRequest = { request: string; appId: string; };
|
||||
function u2fMock( props: MockProps = { } )
|
||||
{
|
||||
const store: Array< FakeRequest > = [ ];
|
||||
|
||||
return {
|
||||
sign(
|
||||
appId,
|
||||
challenge,
|
||||
signRequests: Array< FakeRequest >,
|
||||
cbNative,
|
||||
timeout
|
||||
)
|
||||
{
|
||||
return handleTimeout( props, timeout, ( ) =>
|
||||
{
|
||||
if ( props.appId && props.appId !== appId )
|
||||
return { errorCode: ErrorCodesEnum.BAD_REQUEST };
|
||||
|
||||
const found = signRequests.some( req =>
|
||||
store.some( storeReq =>
|
||||
storeReq.request === req.request
|
||||
&&
|
||||
storeReq.appId === req.appId
|
||||
)
|
||||
);
|
||||
|
||||
if ( !found )
|
||||
return { errorCode: ErrorCodesEnum.BAD_REQUEST };
|
||||
} )
|
||||
.then( value => value || { } )
|
||||
.then( cbNative );
|
||||
},
|
||||
register(
|
||||
appId,
|
||||
registerRequests: Array< FakeRequest >,
|
||||
signRequests: Array< FakeRequest >,
|
||||
cbNative,
|
||||
timeout
|
||||
)
|
||||
{
|
||||
return handleTimeout( props, timeout, ( ) =>
|
||||
{
|
||||
if ( props.appId && props.appId !== appId )
|
||||
return { errorCode: ErrorCodesEnum.BAD_REQUEST };
|
||||
|
||||
registerRequests.forEach( req =>
|
||||
{
|
||||
store.push( req );
|
||||
} );
|
||||
} )
|
||||
.then( value => value || { } )
|
||||
.then( cbNative );
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function wrappedTest(
|
||||
props: any,
|
||||
fn: ( api: API ) => Promise< void >
|
||||
)
|
||||
: ( ) => Promise< void >
|
||||
{
|
||||
return ( ): Promise< void > =>
|
||||
{
|
||||
const dom = new JSDOM(
|
||||
"",
|
||||
Object.assign( {
|
||||
url: appId,
|
||||
userAgent: "FakeBrowser/1",
|
||||
}, props )
|
||||
);
|
||||
|
||||
if ( !props || !props.mock || !props.mock.disable )
|
||||
{
|
||||
const mock = ( props || { } ).mock || { };
|
||||
|
||||
dom.window.u2f = u2fMock( );
|
||||
}
|
||||
|
||||
const gmp = new GlobalMonkeyPatcher( );
|
||||
gmp.patch( dom.window );
|
||||
|
||||
const api = getNewU2FApi( );
|
||||
|
||||
return Try( ( ) => fn( api ) )
|
||||
.then( ...Finally( ( ) =>
|
||||
{
|
||||
gmp.restore( );
|
||||
} ) );
|
||||
};
|
||||
}
|
||||
|
||||
describe( 'general', ( ) =>
|
||||
{
|
||||
it( 'isSupported should be false for unsupported browsers',
|
||||
wrappedTest( { mock: { disable: true } }, async api =>
|
||||
{
|
||||
const supported = await api.isSupported( );
|
||||
expect( supported ).to.be.false;
|
||||
} ) );
|
||||
|
||||
it( 'isSupported should be false for Safari',
|
||||
wrappedTest( { userAgent: "Safari/10" }, async api =>
|
||||
{
|
||||
const supported = await api.isSupported( );
|
||||
expect( supported ).to.be.false;
|
||||
} ) );
|
||||
|
||||
it( 'isSupported should be true with fake window.u2f',
|
||||
wrappedTest( { }, async api =>
|
||||
{
|
||||
const supported = await api.isSupported( );
|
||||
expect( supported ).to.be.true;
|
||||
} ) );
|
||||
|
||||
it( 'the flow of register + sign should run through',
|
||||
wrappedTest( { }, async api =>
|
||||
{
|
||||
await api.ensureSupport( );
|
||||
|
||||
const request = "req";
|
||||
|
||||
await api.register( { appId, request } );
|
||||
|
||||
await api.sign( { appId, request } );
|
||||
} ) );
|
||||
} );
|
Загрузка…
Ссылка в новой задаче