|
|
@@ -26,31 +26,56 @@ describe( 'Token', () => {
|
|
|
} );
|
|
|
|
|
|
describe( 'constructor()', () => {
|
|
|
- it( 'should throw error when no tokenUrl provided', () => {
|
|
|
+ it( 'should throw an error when no tokenUrl provided', () => {
|
|
|
expect( () => new Token() ).to.throw(
|
|
|
CKEditorError,
|
|
|
'token-missing-token-url'
|
|
|
);
|
|
|
} );
|
|
|
|
|
|
- it( 'should set a init token value', () => {
|
|
|
- const token = new Token( 'http://token-endpoint', { initValue: 'initValue', autoRefresh: false } );
|
|
|
+ it( 'should throw an error if the token passed in options is not a string', () => {
|
|
|
+ expect( () => new Token( 'http://token-endpoint', { initValue: 123456 } ) ).to.throw(
|
|
|
+ CKEditorError,
|
|
|
+ 'token-not-in-jwt-format'
|
|
|
+ );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should throw an error if the token passed in options is wrapped in additional quotes', () => {
|
|
|
+ const tokenInitValue = getTestTokenValue();
|
|
|
+
|
|
|
+ expect( () => new Token( 'http://token-endpoint', { initValue: `"${ tokenInitValue }"` } ) ).to.throw(
|
|
|
+ CKEditorError,
|
|
|
+ 'token-not-in-jwt-format'
|
|
|
+ );
|
|
|
+ } );
|
|
|
|
|
|
- expect( token.value ).to.equal( 'initValue' );
|
|
|
+ it( 'should throw an error if the token passed in options is not a valid JWT token', () => {
|
|
|
+ expect( () => new Token( 'http://token-endpoint', { initValue: 'token' } ) ).to.throw(
|
|
|
+ CKEditorError,
|
|
|
+ 'token-not-in-jwt-format'
|
|
|
+ );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should set token value if the token passed in options is valid', () => {
|
|
|
+ const tokenInitValue = getTestTokenValue();
|
|
|
+ const token = new Token( 'http://token-endpoint', { initValue: tokenInitValue } );
|
|
|
+
|
|
|
+ expect( token.value ).to.equal( tokenInitValue );
|
|
|
} );
|
|
|
|
|
|
it( 'should fire `change:value` event if the value of the token has changed', done => {
|
|
|
+ const tokenValue = getTestTokenValue();
|
|
|
const token = new Token( 'http://token-endpoint', { autoRefresh: false } );
|
|
|
|
|
|
token.on( 'change:value', ( event, name, newValue ) => {
|
|
|
- expect( newValue ).to.equal( 'token-value' );
|
|
|
+ expect( newValue ).to.equal( tokenValue );
|
|
|
|
|
|
done();
|
|
|
} );
|
|
|
|
|
|
token.init();
|
|
|
|
|
|
- requests[ 0 ].respond( 200, '', 'token-value' );
|
|
|
+ requests[ 0 ].respond( 200, '', tokenValue );
|
|
|
} );
|
|
|
|
|
|
it( 'should accept the callback in the constructor', () => {
|
|
|
@@ -62,98 +87,205 @@ describe( 'Token', () => {
|
|
|
} );
|
|
|
|
|
|
describe( 'init()', () => {
|
|
|
- it( 'should get a token value from endpoint', done => {
|
|
|
+ it( 'should get a token value from the endpoint', done => {
|
|
|
+ const tokenValue = getTestTokenValue();
|
|
|
const token = new Token( 'http://token-endpoint', { autoRefresh: false } );
|
|
|
|
|
|
token.init()
|
|
|
.then( () => {
|
|
|
- expect( token.value ).to.equal( 'token-value' );
|
|
|
+ expect( token.value ).to.equal( tokenValue );
|
|
|
|
|
|
done();
|
|
|
} );
|
|
|
|
|
|
- requests[ 0 ].respond( 200, '', 'token-value' );
|
|
|
+ requests[ 0 ].respond( 200, '', tokenValue );
|
|
|
} );
|
|
|
|
|
|
it( 'should get a token from the refreshToken function when is provided', () => {
|
|
|
- const token = new Token( () => Promise.resolve( 'token-value' ), { autoRefresh: false } );
|
|
|
+ const tokenValue = getTestTokenValue();
|
|
|
+ const token = new Token( () => Promise.resolve( tokenValue ), { autoRefresh: false } );
|
|
|
|
|
|
return token.init()
|
|
|
.then( () => {
|
|
|
- expect( token.value ).to.equal( 'token-value' );
|
|
|
+ expect( token.value ).to.equal( tokenValue );
|
|
|
} );
|
|
|
} );
|
|
|
|
|
|
- it( 'should start token refresh every 1 hour', done => {
|
|
|
- const clock = sinon.useFakeTimers( { toFake: [ 'setInterval' ] } );
|
|
|
+ it( 'should not refresh token if autoRefresh is disabled in options', async () => {
|
|
|
+ const clock = sinon.useFakeTimers( { toFake: [ 'setTimeout' ] } );
|
|
|
+ const tokenInitValue = getTestTokenValue();
|
|
|
|
|
|
- const token = new Token( 'http://token-endpoint', { initValue: 'initValue' } );
|
|
|
+ const token = new Token( 'http://token-endpoint', { initValue: tokenInitValue, autoRefresh: false } );
|
|
|
|
|
|
- token.init()
|
|
|
- .then( () => {
|
|
|
- clock.tick( 3600000 );
|
|
|
- clock.tick( 3600000 );
|
|
|
- clock.tick( 3600000 );
|
|
|
- clock.tick( 3600000 );
|
|
|
- clock.tick( 3600000 );
|
|
|
+ await token.init();
|
|
|
|
|
|
- expect( requests.length ).to.equal( 5 );
|
|
|
+ await clock.tickAsync( 3600000 );
|
|
|
|
|
|
- clock.restore();
|
|
|
+ expect( requests ).to.be.empty;
|
|
|
|
|
|
- done();
|
|
|
- } );
|
|
|
+ clock.restore();
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should refresh token with the time specified in token `exp` payload property', async () => {
|
|
|
+ const clock = sinon.useFakeTimers( { toFake: [ 'setTimeout' ] } );
|
|
|
+ const tokenInitValue = getTestTokenValue();
|
|
|
+
|
|
|
+ const token = new Token( 'http://token-endpoint', { initValue: tokenInitValue } );
|
|
|
+
|
|
|
+ await token.init();
|
|
|
+
|
|
|
+ await clock.tickAsync( 1800000 );
|
|
|
+ requests[ 0 ].respond( 200, '', getTestTokenValue( 1500 ) );
|
|
|
+
|
|
|
+ await clock.tickAsync( 750000 );
|
|
|
+ requests[ 1 ].respond( 200, '', getTestTokenValue( 900 ) );
|
|
|
+
|
|
|
+ await clock.tickAsync( 450000 );
|
|
|
+ requests[ 2 ].respond( 200, '', getTestTokenValue( 450 ) );
|
|
|
+
|
|
|
+ await clock.tickAsync( 225000 );
|
|
|
+ requests[ 3 ].respond( 200, '', getTestTokenValue( 20 ) );
|
|
|
+
|
|
|
+ await clock.tickAsync( 10000 );
|
|
|
+ requests[ 4 ].respond( 200, '', getTestTokenValue( 20 ) );
|
|
|
+
|
|
|
+ expect( requests.length ).to.equal( 5 );
|
|
|
+
|
|
|
+ clock.restore();
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should refresh the token with the default time if getting token expiration time failed', async () => {
|
|
|
+ const clock = sinon.useFakeTimers( { toFake: [ 'setTimeout' ] } );
|
|
|
+ const tokenValue = 'header.test.signature';
|
|
|
+
|
|
|
+ const token = new Token( 'http://token-endpoint', { initValue: tokenValue } );
|
|
|
+
|
|
|
+ await token.init();
|
|
|
+
|
|
|
+ await clock.tickAsync( 3600000 );
|
|
|
+ requests[ 0 ].respond( 200, '', tokenValue );
|
|
|
+
|
|
|
+ await clock.tickAsync( 3600000 );
|
|
|
+ requests[ 1 ].respond( 200, '', tokenValue );
|
|
|
+
|
|
|
+ expect( requests.length ).to.equal( 2 );
|
|
|
+
|
|
|
+ clock.restore();
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should refresh the token with the default time if the token payload does not contain `exp` property', async () => {
|
|
|
+ const clock = sinon.useFakeTimers( { toFake: [ 'setTimeout' ] } );
|
|
|
+ const tokenValue = `header.${ btoa( JSON.stringify( {} ) ) }.signature`;
|
|
|
+
|
|
|
+ const token = new Token( 'http://token-endpoint', { initValue: tokenValue } );
|
|
|
+
|
|
|
+ await token.init();
|
|
|
+
|
|
|
+ await clock.tickAsync( 3600000 );
|
|
|
+ requests[ 0 ].respond( 200, '', tokenValue );
|
|
|
+
|
|
|
+ await clock.tickAsync( 3600000 );
|
|
|
+ requests[ 1 ].respond( 200, '', tokenValue );
|
|
|
+
|
|
|
+ await clock.tickAsync( 3600000 );
|
|
|
+ requests[ 2 ].respond( 200, '', tokenValue );
|
|
|
+
|
|
|
+ expect( requests.length ).to.equal( 3 );
|
|
|
+
|
|
|
+ clock.restore();
|
|
|
} );
|
|
|
} );
|
|
|
|
|
|
describe( 'destroy', () => {
|
|
|
- it( 'should stop refreshing the token', () => {
|
|
|
- const clock = sinon.useFakeTimers( { toFake: [ 'setInterval', 'clearInterval' ] } );
|
|
|
- const token = new Token( 'http://token-endpoint', { initValue: 'initValue' } );
|
|
|
+ it( 'should stop refreshing the token', async () => {
|
|
|
+ const clock = sinon.useFakeTimers( { toFake: [ 'setTimeout', 'clearTimeout' ] } );
|
|
|
+ const tokenInitValue = getTestTokenValue();
|
|
|
|
|
|
- return token.init()
|
|
|
- .then( () => {
|
|
|
- clock.tick( 3600000 );
|
|
|
- clock.tick( 3600000 );
|
|
|
+ const token = new Token( 'http://token-endpoint', { initValue: tokenInitValue } );
|
|
|
|
|
|
- expect( requests.length ).to.equal( 2 );
|
|
|
+ await token.init();
|
|
|
|
|
|
- token.destroy();
|
|
|
+ await clock.tickAsync( 1800000 );
|
|
|
+ requests[ 0 ].respond( 200, '', getTestTokenValue( 1500 ) );
|
|
|
+ await clock.tickAsync( 100 );
|
|
|
|
|
|
- clock.tick( 3600000 );
|
|
|
- clock.tick( 3600000 );
|
|
|
- clock.tick( 3600000 );
|
|
|
+ await clock.tickAsync( 750000 );
|
|
|
+ requests[ 1 ].respond( 200, '', getTestTokenValue( 900 ) );
|
|
|
+ await clock.tickAsync( 100 );
|
|
|
|
|
|
- expect( requests.length ).to.equal( 2 );
|
|
|
- } );
|
|
|
+ token.destroy();
|
|
|
+
|
|
|
+ await clock.tickAsync( 3600000 );
|
|
|
+ await clock.tickAsync( 3600000 );
|
|
|
+ await clock.tickAsync( 3600000 );
|
|
|
+
|
|
|
+ expect( requests.length ).to.equal( 2 );
|
|
|
+
|
|
|
+ clock.restore();
|
|
|
} );
|
|
|
} );
|
|
|
|
|
|
describe( 'refreshToken()', () => {
|
|
|
it( 'should get a token from the specified address', done => {
|
|
|
- const token = new Token( 'http://token-endpoint', { initValue: 'initValue', autoRefresh: false } );
|
|
|
+ const tokenValue = getTestTokenValue();
|
|
|
+ const token = new Token( 'http://token-endpoint', { autoRefresh: false } );
|
|
|
|
|
|
token.refreshToken()
|
|
|
.then( newToken => {
|
|
|
- expect( newToken.value ).to.equal( 'token-value' );
|
|
|
+ expect( newToken.value ).to.equal( tokenValue );
|
|
|
|
|
|
done();
|
|
|
} );
|
|
|
|
|
|
- requests[ 0 ].respond( 200, '', 'token-value' );
|
|
|
+ requests[ 0 ].respond( 200, '', tokenValue );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should throw an error if the returned token is wrapped in additional quotes', done => {
|
|
|
+ const tokenValue = getTestTokenValue();
|
|
|
+ const token = new Token( 'http://token-endpoint', { autoRefresh: false } );
|
|
|
+
|
|
|
+ token.refreshToken()
|
|
|
+ .then( () => {
|
|
|
+ done( new Error( 'Promise should be rejected' ) );
|
|
|
+ } )
|
|
|
+ .catch( error => {
|
|
|
+ expect( error.constructor ).to.equal( CKEditorError );
|
|
|
+ expect( error ).to.match( /token-not-in-jwt-format/ );
|
|
|
+ done();
|
|
|
+ } );
|
|
|
+
|
|
|
+ requests[ 0 ].respond( 200, '', `"${ tokenValue }"` );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should throw an error if the returned token is not a valid JWT token', done => {
|
|
|
+ const token = new Token( 'http://token-endpoint', { autoRefresh: false } );
|
|
|
+
|
|
|
+ token.refreshToken()
|
|
|
+ .then( () => {
|
|
|
+ done( new Error( 'Promise should be rejected' ) );
|
|
|
+ } )
|
|
|
+ .catch( error => {
|
|
|
+ expect( error.constructor ).to.equal( CKEditorError );
|
|
|
+ expect( error ).to.match( /token-not-in-jwt-format/ );
|
|
|
+ done();
|
|
|
+ } );
|
|
|
+
|
|
|
+ requests[ 0 ].respond( 200, '', 'token' );
|
|
|
} );
|
|
|
|
|
|
it( 'should get a token from the specified callback function', () => {
|
|
|
- const token = new Token( () => Promise.resolve( 'token-value' ), { initValue: 'initValue', autoRefresh: false } );
|
|
|
+ const tokenValue = getTestTokenValue();
|
|
|
+ const token = new Token( () => Promise.resolve( tokenValue ), { autoRefresh: false } );
|
|
|
|
|
|
return token.refreshToken()
|
|
|
.then( newToken => {
|
|
|
- expect( newToken.value ).to.equal( 'token-value' );
|
|
|
+ expect( newToken.value ).to.equal( tokenValue );
|
|
|
} );
|
|
|
} );
|
|
|
|
|
|
- it( 'should throw an error when cannot download new token', () => {
|
|
|
- const token = new Token( 'http://token-endpoint', { initValue: 'initValue', autoRefresh: false } );
|
|
|
+ it( 'should throw an error when cannot download a new token', () => {
|
|
|
+ const tokenInitValue = getTestTokenValue();
|
|
|
+ const token = new Token( 'http://token-endpoint', { initValue: tokenInitValue, autoRefresh: false } );
|
|
|
const promise = token._refresh();
|
|
|
|
|
|
requests[ 0 ].respond( 401 );
|
|
|
@@ -167,7 +299,8 @@ describe( 'Token', () => {
|
|
|
} );
|
|
|
|
|
|
it( 'should throw an error when the response is aborted', () => {
|
|
|
- const token = new Token( 'http://token-endpoint', { initValue: 'initValue', autoRefresh: false } );
|
|
|
+ const tokenInitValue = getTestTokenValue();
|
|
|
+ const token = new Token( 'http://token-endpoint', { initValue: tokenInitValue, autoRefresh: false } );
|
|
|
const promise = token._refresh();
|
|
|
|
|
|
requests[ 0 ].abort();
|
|
|
@@ -180,7 +313,8 @@ describe( 'Token', () => {
|
|
|
} );
|
|
|
|
|
|
it( 'should throw an error when network error occurs', () => {
|
|
|
- const token = new Token( 'http://token-endpoint', { initValue: 'initValue', autoRefresh: false } );
|
|
|
+ const tokenInitValue = getTestTokenValue();
|
|
|
+ const token = new Token( 'http://token-endpoint', { initValue: tokenInitValue, autoRefresh: false } );
|
|
|
const promise = token._refresh();
|
|
|
|
|
|
requests[ 0 ].error();
|
|
|
@@ -192,8 +326,9 @@ describe( 'Token', () => {
|
|
|
} );
|
|
|
} );
|
|
|
|
|
|
- it( 'should throw an error when the callback throws error', () => {
|
|
|
- const token = new Token( () => Promise.reject( 'Custom error occurred' ), { initValue: 'initValue', autoRefresh: false } );
|
|
|
+ it( 'should throw an error when the callback throws an error', () => {
|
|
|
+ const tokenInitValue = getTestTokenValue();
|
|
|
+ const token = new Token( () => Promise.reject( 'Custom error occurred' ), { initValue: tokenInitValue, autoRefresh: false } );
|
|
|
|
|
|
token.refreshToken()
|
|
|
.catch( error => {
|
|
|
@@ -202,75 +337,39 @@ describe( 'Token', () => {
|
|
|
} );
|
|
|
} );
|
|
|
|
|
|
- describe( '_startRefreshing()', () => {
|
|
|
- it( 'should start refreshing', () => {
|
|
|
- const clock = sinon.useFakeTimers( { toFake: [ 'setInterval' ] } );
|
|
|
-
|
|
|
- const token = new Token( 'http://token-endpoint', { initValue: 'initValue', autoRefresh: false } );
|
|
|
-
|
|
|
- token._startRefreshing();
|
|
|
-
|
|
|
- clock.tick( 3600000 );
|
|
|
- clock.tick( 3600000 );
|
|
|
- clock.tick( 3600000 );
|
|
|
- clock.tick( 3600000 );
|
|
|
- clock.tick( 3600000 );
|
|
|
-
|
|
|
- expect( requests.length ).to.equal( 5 );
|
|
|
-
|
|
|
- clock.restore();
|
|
|
- } );
|
|
|
- } );
|
|
|
-
|
|
|
- describe( '_stopRefreshing()', () => {
|
|
|
- it( 'should stop refreshing', done => {
|
|
|
- const clock = sinon.useFakeTimers( { toFake: [ 'setInterval', 'clearInterval' ] } );
|
|
|
-
|
|
|
- const token = new Token( 'http://token-endpoint', { initValue: 'initValue' } );
|
|
|
-
|
|
|
- token.init()
|
|
|
- .then( () => {
|
|
|
- clock.tick( 3600000 );
|
|
|
- clock.tick( 3600000 );
|
|
|
- clock.tick( 3600000 );
|
|
|
-
|
|
|
- token._stopRefreshing();
|
|
|
-
|
|
|
- clock.tick( 3600000 );
|
|
|
- clock.tick( 3600000 );
|
|
|
-
|
|
|
- expect( requests.length ).to.equal( 3 );
|
|
|
-
|
|
|
- clock.restore();
|
|
|
-
|
|
|
- done();
|
|
|
- } );
|
|
|
- } );
|
|
|
- } );
|
|
|
-
|
|
|
describe( 'static create()', () => {
|
|
|
- it( 'should return a initialized token', done => {
|
|
|
+ it( 'should return an initialized token', done => {
|
|
|
+ const tokenValue = getTestTokenValue();
|
|
|
+
|
|
|
Token.create( 'http://token-endpoint', { autoRefresh: false } )
|
|
|
.then( token => {
|
|
|
- expect( token.value ).to.equal( 'token-value' );
|
|
|
+ expect( token.value ).to.equal( tokenValue );
|
|
|
|
|
|
done();
|
|
|
} );
|
|
|
|
|
|
- requests[ 0 ].respond( 200, '', 'token-value' );
|
|
|
+ requests[ 0 ].respond( 200, '', tokenValue );
|
|
|
} );
|
|
|
|
|
|
it( 'should use default options when none passed', done => {
|
|
|
- const intervalSpy = sinon.spy( window, 'setInterval' );
|
|
|
+ const tokenValue = getTestTokenValue();
|
|
|
|
|
|
Token.create( 'http://token-endpoint' )
|
|
|
- .then( () => {
|
|
|
- expect( intervalSpy.args[ 0 ][ 1 ] ).to.equal( 3600000 );
|
|
|
+ .then( token => {
|
|
|
+ expect( token._options ).to.eql( { autoRefresh: true } );
|
|
|
|
|
|
done();
|
|
|
} );
|
|
|
|
|
|
- requests[ 0 ].respond( 200, '', 'token-value' );
|
|
|
+ requests[ 0 ].respond( 200, '', tokenValue );
|
|
|
} );
|
|
|
} );
|
|
|
} );
|
|
|
+
|
|
|
+// Returns valid token for tests with given expiration time offset.
|
|
|
+//
|
|
|
+// @param {Number} [timeOffset=3600000]
|
|
|
+// @returns {String}
|
|
|
+function getTestTokenValue( timeOffset = 3600 ) {
|
|
|
+ return `header.${ btoa( JSON.stringify( { exp: ( Math.floor( Date.now() / 1000 ) ) + timeOffset } ) ) }.signature`;
|
|
|
+}
|