diff --git a/package.json b/package.json index 804d6425f..61dc020b1 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ }, "jest": { "clearMocks": true, - "testURL": "https://www.example.com/" + "testMatch": ["**/test/unit/*.test.ts"] }, "engines": { "node": ">=10" diff --git a/test/unit/AccessTokenEvents.spec.js b/test/unit/AccessTokenEvents.spec.js deleted file mode 100644 index 43005a720..000000000 --- a/test/unit/AccessTokenEvents.spec.js +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -import { AccessTokenEvents } from '../../src/AccessTokenEvents'; - -import chai from 'chai'; -chai.should(); -let assert = chai.assert; - -class StubTimer { - - constructor() { - this.cancelWasCalled = false; - } - - init(duration) { - this.duration = duration; - } - - cancel() { - this.cancelWasCalled = true; - } - - addHandler(){} - removeHandler(){} -} - -describe("AccessTokenEvents", function () { - - let subject; - let accessTokenExpiringTimer; - let accessTokenExpiredTimer; - - beforeEach(function () { - accessTokenExpiringTimer = new StubTimer(); - accessTokenExpiredTimer = new StubTimer(); - subject = new AccessTokenEvents({ - accessTokenExpiringTimer, accessTokenExpiredTimer - }); - }); - - describe("constructor", function () { - - it("should use default expiringNotificationTime", function () { - subject._accessTokenExpiringNotificationTime.should.equal(60); - }); - - }); - - describe("load", function () { - - it("should cancel existing timers", function () { - subject.load({}); - - accessTokenExpiringTimer.cancelWasCalled.should.be.true; - accessTokenExpiredTimer.cancelWasCalled.should.be.true; - }); - - it("should initialize timers", function () { - subject.load({ - access_token:"token", - expires_in : 70 - }); - - accessTokenExpiringTimer.duration.should.equal(10); - accessTokenExpiredTimer.duration.should.equal(71); - }); - - it("should immediately schedule expiring timer if expiration is soon", function () { - subject.load({ - access_token:"token", - expires_in : 10 - }); - - accessTokenExpiringTimer.duration.should.equal(1); - }); - - it("should not initialize expiring timer if already expired", function () { - subject.load({ - access_token:"token", - expires_in : 0 - }); - - assert.isUndefined(accessTokenExpiringTimer.duration); - }); - - it("should initialize expired timer if already expired", function () { - subject.load({ - access_token:"token", - expires_in : 0 - }); - - accessTokenExpiredTimer.duration.should.equal(1); - }); - - it("should not initialize timers if no access token", function () { - subject.load({ - expires_in : 70 - }); - - assert.isUndefined(accessTokenExpiringTimer.duration); - assert.isUndefined(accessTokenExpiredTimer.duration); - }); - - it("should not initialize timers if no expiration on access token", function () { - subject.load({ - access_token:"token" - }); - - assert.isUndefined(accessTokenExpiringTimer.duration); - assert.isUndefined(accessTokenExpiredTimer.duration); - }); - }); - - describe("unload", function () { - - it("should cancel timers", function () { - - subject.unload(); - - accessTokenExpiringTimer.cancelWasCalled.should.be.true; - accessTokenExpiredTimer.cancelWasCalled.should.be.true; - }); - - }); - -}); diff --git a/test/unit/AccessTokenEvents.test.ts b/test/unit/AccessTokenEvents.test.ts new file mode 100644 index 000000000..dd6470cf0 --- /dev/null +++ b/test/unit/AccessTokenEvents.test.ts @@ -0,0 +1,142 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +import { AccessTokenEvents } from '../../src/AccessTokenEvents'; +import { Timer } from '../../src/Timer'; +import { User } from '../../src/User'; + +describe("AccessTokenEvents", () => { + + let subject: AccessTokenEvents; + let accessTokenExpiringTimer: StubTimer; + let accessTokenExpiredTimer: StubTimer; + + beforeEach(() => { + accessTokenExpiringTimer = new StubTimer("stub expiring timer"); + accessTokenExpiredTimer = new StubTimer("stub expired timer"); + subject = new AccessTokenEvents({ + accessTokenExpiringTimer, accessTokenExpiredTimer + }); + }); + + /* TODO: port-ts + describe("constructor", () => { + + it("should use default expiringNotificationTime", () => { + subject._accessTokenExpiringNotificationTime.should.equal(60); + }); + + });*/ + + describe("load", () => { + + it("should cancel existing timers", () => { + // act + subject.load({} as User); + + // assert + expect(accessTokenExpiringTimer.cancelWasCalled).toEqual(true); + expect(accessTokenExpiredTimer.cancelWasCalled).toEqual(true); + }); + + it("should initialize timers", () => { + // act + subject.load({ + access_token:"token", + expires_in : 70 + } as User); + + // assert + expect(accessTokenExpiringTimer.duration).toEqual(10); + expect(accessTokenExpiredTimer.duration).toEqual(71); + }); + + it("should immediately schedule expiring timer if expiration is soon", () => { + // act + subject.load({ + access_token:"token", + expires_in : 10 + } as User); + + // assert + expect(accessTokenExpiringTimer.duration).toEqual(1); + }); + + it("should not initialize expiring timer if already expired", () => { + // act + subject.load({ + access_token:"token", + expires_in : 0 + } as User); + + // assert + expect(accessTokenExpiringTimer.duration).toEqual(undefined); + }); + + it("should initialize expired timer if already expired", () => { + // act + subject.load({ + access_token:"token", + expires_in : 0 + } as User); + + // assert + expect(accessTokenExpiredTimer.duration).toEqual(1); + }); + + it("should not initialize timers if no access token", () => { + // act + subject.load({ + expires_in : 70 + } as User); + + // assert + expect(accessTokenExpiringTimer.duration).toEqual(undefined); + expect(accessTokenExpiredTimer.duration).toEqual(undefined); + }); + + it("should not initialize timers if no expiration on access token", () => { + // act + subject.load({ + access_token:"token" + } as User); + + // assert + expect(accessTokenExpiringTimer.duration).toEqual(undefined); + expect(accessTokenExpiredTimer.duration).toEqual(undefined); + }); + }); + + describe("unload", () => { + + it("should cancel timers", () => { + // act + subject.unload(); + + // assert + expect(accessTokenExpiringTimer.cancelWasCalled).toEqual(true); + expect(accessTokenExpiredTimer.cancelWasCalled).toEqual(true); + }); + }); +}); + +class StubTimer extends Timer { + cancelWasCalled: boolean; + duration: any; + + constructor(name: string) { + super(name) + this.cancelWasCalled = false; + } + + init(duration: number) { + this.duration = duration; + } + + cancel() { + this.cancelWasCalled = true; + } + + addHandler() {} + removeHandler() {} +} diff --git a/test/unit/ErrorResponse.spec.js b/test/unit/ErrorResponse.spec.js deleted file mode 100644 index 3ae611b8e..000000000 --- a/test/unit/ErrorResponse.spec.js +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -import { Log } from '../../src/Log'; -import { ErrorResponse } from '../../src/ErrorResponse'; - -import chai from 'chai'; -chai.should(); -let assert = chai.assert; -let expect = chai.expect; - -describe("ErrorResponse", function() { - - describe("constructor", function() { - - it("should require a error param", function() { - try { - new ErrorResponse({}); - } - catch (e) { - e.message.should.contain('error'); - return; - } - assert.fail(); - }); - - it("should read error", function() { - let subject = new ErrorResponse({error:"foo"}); - subject.error.should.equal("foo"); - }); - - it("should read error_description", function() { - let subject = new ErrorResponse({error:"error", error_description:"foo"}); - subject.error_description.should.equal("foo"); - }); - - it("should read error_uri", function() { - let subject = new ErrorResponse({error:"error", error_uri:"foo"}); - subject.error_uri.should.equal("foo"); - }); - - it("should read state", function() { - let subject = new ErrorResponse({error:"error", state:"foo"}); - subject.state.should.equal("foo"); - }); - }); - - describe("message", function() { - it("should be description if set", function() { - let subject = new ErrorResponse({error:"error", error_description:"foo"}); - subject.message.should.equal("foo"); - }); - - it("should be error if description not set", function() { - let subject = new ErrorResponse({error:"error"}); - subject.message.should.equal("error"); - }); - - }); - - describe("name", function() { - it("should be class name", function() { - let subject = new ErrorResponse({error:"error"}); - subject.name.should.equal("ErrorResponse"); - }); - - }); - - describe("stack", function() { - - it("should be set", function() { - let subject = new ErrorResponse({error:"error"}); - subject.stack.should.be.ok; - }); - - }); -}); diff --git a/test/unit/ErrorResponse.test.ts b/test/unit/ErrorResponse.test.ts new file mode 100644 index 000000000..b7808ae42 --- /dev/null +++ b/test/unit/ErrorResponse.test.ts @@ -0,0 +1,94 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +import { ErrorResponse } from '../../src/ErrorResponse'; + +describe("ErrorResponse", () => { + + describe("constructor", () => { + + it("should require a error param", () => { + // act + try { + new ErrorResponse({}); + } + catch (e) { + expect(e.message).toContain("error"); + return; + } + + fail("should not come here"); + }); + + it("should read error", () => { + // act + let subject = new ErrorResponse({error:"foo"}); + + // assert + expect(subject.error).toEqual("foo"); + }); + + it("should read error_description", () => { + // act + let subject = new ErrorResponse({error:"error", error_description:"foo"}); + + // assert + expect(subject.error_description).toEqual("foo"); + }); + + it("should read error_uri", () => { + // act + let subject = new ErrorResponse({error:"error", error_uri:"foo"}); + + // assert + expect(subject.error_uri).toEqual("foo"); + }); + + it("should read state", () => { + // act + let subject = new ErrorResponse({error:"error", state:"foo"}); + + // assert + expect(subject.state).toEqual("foo"); + }); + + }); + + describe("message", () => { + it("should be description if set", () => { + // act + let subject = new ErrorResponse({error:"error", error_description:"foo"}); + + // assert + expect(subject.message).toEqual("foo"); + }); + + it("should be error if description not set", () => { + // act + let subject = new ErrorResponse({error:"error"}); + + // assert + expect(subject.message).toEqual("error"); + }); + }); + + describe("name", () => { + it("should be class name", () => { + // act + let subject = new ErrorResponse({error:"error"}); + + // assert + expect(subject.name).toEqual("ErrorResponse"); + }); + }); + + describe("stack", () => { + it("should be set", () => { + // act + let subject = new ErrorResponse({error:"error"}); + + // assert + expect(subject.stack).not.toBeNull(); + }); + }); +}); diff --git a/test/unit/Event.spec.js b/test/unit/Event.spec.js deleted file mode 100644 index 20bfa19a0..000000000 --- a/test/unit/Event.spec.js +++ /dev/null @@ -1,113 +0,0 @@ - -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -import { Event } from '../../src/Event'; - -import chai from 'chai'; -chai.should(); -let assert = chai.assert; - -describe("Event", function () { - - let subject; - - beforeEach(function () { - subject = new Event("test name"); - }); - - describe("addHandler", function () { - - it("should allow callback to be invoked", function () { - var cb = function () { - cb.wasCalled = true; - }; - subject.addHandler(cb); - - subject.raise(); - - cb.wasCalled.should.be.true; - }); - - it("should allow multiple callbacks", function () { - var count = 0; - var cb = function () { - count++; - }; - subject.addHandler(cb); - subject.addHandler(cb); - subject.addHandler(cb); - subject.addHandler(cb); - - subject.raise(); - - count.should.equal(4); - }); - - }); - - describe("removeHandler", function () { - - it("should remove callback from being invoked", function () { - var cb = function () { - cb.wasCalled = true; - }; - cb.wasCalled = false; - - subject.addHandler(cb); - subject.removeHandler(cb); - subject.raise(); - - cb.wasCalled.should.be.false; - }); - - it("should remove individual callback", function () { - var count = 0; - var cb1 = function () { - count++; - }; - var cb2 = function () { - cb2.wasCalled = true; - }; - - subject.addHandler(cb1); - subject.addHandler(cb2); - subject.addHandler(cb1); - subject.removeHandler(cb1); - subject.removeHandler(cb1); - - subject.raise(); - - count.should.equal(0); - cb2.wasCalled.should.be.true; - }); - - }); - - describe("raise", function () { - - it("should pass params", function () { - var cb = function (a,b,c) { - a.should.equal(1); - b.should.equal(2); - c.should.equal(3); - }; - subject.addHandler(cb); - - subject.raise(1,2,3); - }); - - it("should allow passing no params", function () { - var cb = function (a,b,c) { - assert.isUndefined(a); - assert.isUndefined(b); - assert.isUndefined(c); - }; - subject.addHandler(cb); - - subject.raise(); - }); - - }); - -}); diff --git a/test/unit/Event.test.ts b/test/unit/Event.test.ts new file mode 100644 index 000000000..290b5f88c --- /dev/null +++ b/test/unit/Event.test.ts @@ -0,0 +1,122 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +import { Event } from '../../src/Event'; + +describe("Event", () => { + + let subject: Event; + + beforeEach(() => { + subject = new Event("test name"); + }); + + describe("addHandler", () => { + + it("should allow callback to be invoked", () => { + // arrange + var cb = jest.fn(); + + // act + subject.addHandler(cb); + subject.raise(); + + // assert + expect(cb).toBeCalled(); + }); + + it("should allow multiple callbacks", () => { + // arrange + var cb = jest.fn(); + + // act + subject.addHandler(cb); + subject.addHandler(cb); + subject.addHandler(cb); + subject.addHandler(cb); + subject.raise(); + + // assert + expect(cb).toBeCalledTimes(4); + }); + }); + + describe("removeHandler", () => { + + it("should remove callback from being invoked", () => { + // arrange + var cb = jest.fn(); + + // act + subject.addHandler(cb); + subject.removeHandler(cb); + subject.raise(); + + // assert + expect(cb).toBeCalledTimes(0); + }); + + it("should remove individual callback", () => { + // arrange + var cb1 = jest.fn(); + var cb2 = jest.fn(); + + // act + subject.addHandler(cb1); + subject.addHandler(cb2); + subject.addHandler(cb1); + subject.removeHandler(cb1); + subject.removeHandler(cb1); + + subject.raise(); + + // assert + expect(cb1).toBeCalledTimes(0); + expect(cb2).toBeCalledTimes(1); + }); + }); + + describe("raise", () => { + + it("should pass params", () => { + // arrange + let a: any = 10; + let b: any = 11; + let c: any = 12; + var cb = function (arg_a: any, arg_b: any, arg_c: any) { + a = arg_a; + b = arg_b; + c = arg_c; + }; + subject.addHandler(cb); + + // act + subject.raise(1, 2, 3); + + // assert + expect(a).toEqual(1); + expect(b).toEqual(2); + expect(c).toEqual(3); + }); + + it("should allow passing no params", () => { + // arrange + let a: any = 10; + let b: any = 11; + let c: any = 12; + var cb = function (arg_a: any, arg_b: any, arg_c: any) { + a = arg_a; + b = arg_b; + c = arg_c; + }; + subject.addHandler(cb); + + subject.raise(); + + // assert + expect(a).toEqual(undefined); + expect(b).toEqual(undefined); + expect(c).toEqual(undefined); + }); + }); +}); diff --git a/test/unit/JsonService.spec.js b/test/unit/JsonService.spec.js deleted file mode 100644 index 4c1d20cc1..000000000 --- a/test/unit/JsonService.spec.js +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -import { JsonService } from '../../src/JsonService'; - -import chai from 'chai'; -chai.should(); -let assert = chai.assert; - -describe("JsonService", function() { - let subject; - - let stubHttpRequest; - - beforeEach(function(){ - stubHttpRequest = new StubXMLHttpRequest(); - subject = new JsonService(null, ()=>stubHttpRequest); - }); - - describe("getJson", function() { - - it("should require a url parameter", function() { - try { - subject.getJson(); - } - catch(e) { - return; - } - - assert.fail(); - }); - - it("should return a promise", function() { - let p = subject.getJson("http://test"); - p.should.be.instanceof(Promise); - p.catch(e=>{}); - }); - - it("should make GET request to url", function() { - let p = subject.getJson("http://test"); - stubHttpRequest.method.should.be.equal('GET'); - stubHttpRequest.url.should.be.equal('http://test'); - }); - - it("should set token as authorization header", function() { - let p = subject.getJson("http://test", "token"); - stubHttpRequest.headers.has('Authorization').should.be.true; - stubHttpRequest.headers.get('Authorization').should.be.equal('Bearer token'); - }); - - it("should fulfill promise when http response is 200", function(done) { - let p = subject.getJson("http://test"); - - p.then(result => { - result.should.not.be.undefined; - result.foo.should.be.equal(1); - result.bar.should.be.equal('test'); - - done(); - }); - - stubHttpRequest.status = 200; - stubHttpRequest.responseHeaders.set('Content-Type', 'application/json'); - stubHttpRequest.responseText = JSON.stringify({foo:1, bar:'test'}); - stubHttpRequest.onload(); - }); - - it("should reject promise when http response is not 200", function(done) { - let p = subject.getJson("http://test"); - - p.then(result => { - assert.fail(); - }, error => { - error.should.be.instanceof(Error); - error.message.should.contain('500'); - error.message.should.contain('server error'); - done(); - }); - - stubHttpRequest.status = 500; - stubHttpRequest.statusText = "server error"; - stubHttpRequest.onload(); - }); - - it("should reject promise when http response is error", function(done) { - let p = subject.getJson("http://test"); - - p.then(result => { - assert.fail(); - }, error => { - error.should.be.instanceof(Error); - error.message.should.be.equal('Network Error'); - done(); - }); - - stubHttpRequest.onerror(); - }); - - it("should reject promise when http response content type is not json", function(done) { - let p = subject.getJson("http://test"); - - p.then(result => { - assert.fail(); - }, error => { - error.should.be.instanceof(Error); - error.message.indexOf('text/html').should.be.above(-1); - done(); - }); - - stubHttpRequest.status = 200; - stubHttpRequest.responseHeaders.set('Content-Type', 'text/html'); - stubHttpRequest.responseText = JSON.stringify({foo:1, bar:'test'}); - stubHttpRequest.onload(); - }); - - it("should accept custom content type in response", function(done) { - subject = new JsonService(['foo/bar'], ()=>stubHttpRequest); - let p = subject.getJson("http://test"); - - p.then(result => { - result.foo.should.equal(1); - done(); - }); - - stubHttpRequest.status = 200; - stubHttpRequest.responseHeaders.set('Content-Type', 'foo/bar'); - stubHttpRequest.responseText = JSON.stringify({foo:1, bar:'test'}); - stubHttpRequest.onload(); - }); - }); -}); - -class StubXMLHttpRequest { - constructor() { - this.headers = new Map(); - this.responseHeaders = new Map(); - } - - open(method, url) { - this.method = method; - this.url = url; - } - - setRequestHeader(header, value){ - this.headers.set(header, value); - } - - getResponseHeader(name){ - return this.responseHeaders.get(name); - } - - send() { - } -} diff --git a/test/unit/JsonService.test.ts b/test/unit/JsonService.test.ts new file mode 100644 index 000000000..8bfd852bc --- /dev/null +++ b/test/unit/JsonService.test.ts @@ -0,0 +1,176 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +import { Log } from '../../src/Log'; +import { JsonService } from '../../src/JsonService'; + +class XMLHttpRequestMock { + method: any; + headers: Map; + url: any; + + responseHeaders: Map; + responseText?: string; + status?: number; + statusText?: string; + + constructor() { + this.headers = new Map(); + this.responseHeaders = new Map(); + this.responseText = undefined; + this.status = undefined; + this.statusText = undefined; + + xmlHttpRequestMock = this; + } + + open(method: string, url: string) { + this.method = method; + this.url = url; + } + + setRequestHeader(header: string, value: string) { + this.headers.set(header, value); + } + + getResponseHeader(name: string) { + return this.responseHeaders.get(name); + } + + send(_body?: Document | BodyInit | null) { + } + onload(_ev: ProgressEvent) { + } + onerror(_ev: ProgressEvent) { + } +} +let xmlHttpRequestMock = new XMLHttpRequestMock(); + +describe("JsonService", () => { + let subject: JsonService; + + beforeEach(() =>{ + Log.logger = console; + Log.level = Log.NONE; + + subject = new JsonService(null, XMLHttpRequestMock as unknown as typeof XMLHttpRequest); + }); + + describe("getJson", () => { + + /* TODO: port-ts + it("should require a url parameter", () => { + try { + subject.getJson(); + } + catch(e) { + return; + } + + assert.fail(); + }); + */ + + it("should return a promise", () => { + // act + let p = subject.getJson("http://test"); + + // assert + expect(p).toBeInstanceOf(Promise); + }); + + it("should make GET request to url", () => { + // act + subject.getJson("http://test"); + + // assert + expect(xmlHttpRequestMock.method).toEqual('GET'); + expect(xmlHttpRequestMock.url).toEqual('http://test'); + }); + + it("should set token as authorization header", () => { + // act + subject.getJson("http://test", "token"); + + // assert + expect(xmlHttpRequestMock.headers.has('Authorization')).toEqual(true); + expect(xmlHttpRequestMock.headers.get('Authorization')).toEqual('Bearer token'); + }); + + it("should fulfill promise when http response is 200", async () => { + // act + let p = subject.getJson("http://test"); + xmlHttpRequestMock.status = 200; + xmlHttpRequestMock.responseHeaders.set('Content-Type', 'application/json'); + xmlHttpRequestMock.responseText = JSON.stringify({foo:1, bar:'test'}); + xmlHttpRequestMock.onload(new ProgressEvent("dummy")); + let result = await p; + + // assert + expect(result).not.toBeUndefined(); + expect(result.foo).toEqual(1); + expect(result.bar).toEqual("test"); + }); + + it("should reject promise when http response is not 200", async () => { + // act + let p = subject.getJson("http://test"); + xmlHttpRequestMock.status = 500; + xmlHttpRequestMock.statusText = "server error"; + xmlHttpRequestMock.onload(new ProgressEvent("dummy")); + try { + await p; + fail("should not come here"); + } catch (error) { + expect(error).toBeInstanceOf(Error); + expect(error.message).toContain('500'); + expect(error.message).toContain('server error'); + } + }); + + it("should reject promise when http response is error", async () => { + // act + let p = subject.getJson("http://test"); + xmlHttpRequestMock.onerror(new ProgressEvent("dummy")); + try { + await p; + fail("should not come here"); + } catch (error) { + expect(error).toBeInstanceOf(Error); + expect(error.message).toEqual("Network Error"); + } + }); + + it("should reject promise when http response content type is not json", async () => { + // act + let p = subject.getJson("http://test"); + xmlHttpRequestMock.status = 200; + xmlHttpRequestMock.responseHeaders.set('Content-Type', 'text/html'); + xmlHttpRequestMock.responseText = JSON.stringify({foo:1, bar:'test'}); + xmlHttpRequestMock.onload(new ProgressEvent("dummy")); + try { + await p; + fail("should not come here"); + } catch (error) { + expect(error).toBeInstanceOf(Error); + expect(error.message).toContain("Invalid response Content-Type: text/html"); + } + }); + + it("should accept custom content type in response", async () => { + // arrange + subject = new JsonService(['foo/bar'], XMLHttpRequestMock as unknown as typeof XMLHttpRequest); + + // act + let p = subject.getJson("http://test"); + xmlHttpRequestMock.status = 200; + xmlHttpRequestMock.responseHeaders.set('Content-Type', 'foo/bar'); + xmlHttpRequestMock.responseText = JSON.stringify({foo:1, bar:'test'}); + xmlHttpRequestMock.onload(new ProgressEvent("dummy")); + let result = await p; + + // assert + expect(result.foo).toEqual(1); + }); + }); +}); diff --git a/test/unit/Log.spec.js b/test/unit/Log.spec.js deleted file mode 100644 index d6c560a7f..000000000 --- a/test/unit/Log.spec.js +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -import { Log } from '../../src/Log'; - -import chai from 'chai'; -chai.should(); -let assert = chai.assert; - -describe("Log", function() { - - beforeEach(function() { - Log.reset(); - Log.level = Log.INFO; - }); - - describe("level", function() { - - it("should not log when set to NONE", function() { - let stub = new StubLog(); - Log.logger = stub; - Log.level = Log.NONE; - - Log.info("test info"); - Log.warn("test warn"); - Log.error("test error"); - - stub.infoWasCalled.should.be.false; - stub.warnWasCalled.should.be.false; - stub.errorWasCalled.should.be.false; - }); - - it("should not log info or warn for ERROR level", function() { - let stub = new StubLog(); - - Log.logger = stub; - Log.level = Log.ERROR; - - Log.info("test info"); - Log.warn("test warn"); - Log.error("test error"); - - stub.infoWasCalled.should.be.false; - stub.warnWasCalled.should.be.false; - stub.errorWasCalled.should.be.true; - }); - - it("should not log info for WARN level", function() { - let stub = new StubLog(); - - Log.logger =stub; - Log.level = Log.WARN; - - Log.info("test info"); - Log.warn("test warn"); - Log.error("test error"); - - stub.infoWasCalled.should.be.false; - stub.warnWasCalled.should.be.true; - stub.errorWasCalled.should.be.true; - }); - - it("should log to all for INFO level", function() { - let stub = new StubLog(); - - Log.logger = stub; - Log.level = Log.INFO; - - Log.info("test info"); - Log.warn("test warn"); - Log.error("test error"); - - stub.infoWasCalled.should.be.true; - stub.warnWasCalled.should.be.true; - stub.errorWasCalled.should.be.true; - }); - - }); - - describe("logger", function() { - - it("should use the logger specified", function() { - let stub = new StubLog(); - Log.logger = stub; - - Log.info("test info"); - Log.warn("test warn"); - Log.error("test error"); - - stub.infoParam.should.equal("test info"); - stub.warnParam.should.equal("test warn"); - stub.errorParam.should.equal("test error"); - }); - - }); - - describe("info", function() { - - it("should work with no config", function() { - Log.info("test"); - }); - - }); - - describe("warn", function() { - - it("should work with no config", function() { - Log.warn("test"); - }); - - }); - - describe("error", function() { - - it("should work with no config", function() { - Log.error("test"); - }); - - }); -}); - -class StubLog { - constructor(){ - this.infoWasCalled = false; - this.warnWasCalled = false; - this.errorWasCalled = false; - } - info(arg) { - this.infoParam = arg; - this.infoWasCalled = true; - } - warn(arg) { - this.warnParam = arg; - this.warnWasCalled = true; - } - error(arg) { - this.errorParam = arg; - this.errorWasCalled = true; - } -} diff --git a/test/unit/Log.test.ts b/test/unit/Log.test.ts new file mode 100644 index 000000000..656fa51b9 --- /dev/null +++ b/test/unit/Log.test.ts @@ -0,0 +1,157 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +import { Log, Logger } from '../../src/Log'; + +describe("Log", () => { + beforeEach(() => { + Log.reset(); + Log.level = Log.INFO; + }); + + describe("level", () => { + + it("should not log when set to NONE", () => { + // arrange + let stub = new StubLog(); + Log.logger = stub; + Log.level = Log.NONE; + + // act + Log.info("test info"); + Log.warn("test warn"); + Log.error("test error"); + + // assert + expect(stub.infoWasCalled).toEqual(false); + expect(stub.warnWasCalled).toEqual(false); + expect(stub.errorWasCalled).toEqual(false); + }); + + it("should not log info or warn for ERROR level", () => { + // arrange + let stub = new StubLog(); + Log.logger = stub; + Log.level = Log.ERROR; + + // act + Log.info("test info"); + Log.warn("test warn"); + Log.error("test error"); + + // assert + expect(stub.infoWasCalled).toEqual(false); + expect(stub.warnWasCalled).toEqual(false); + expect(stub.errorWasCalled).toEqual(true); + }); + + it("should not log info for WARN level", () => { + // arrange + let stub = new StubLog(); + Log.logger = stub; + Log.level = Log.WARN; + + // act + Log.info("test info"); + Log.warn("test warn"); + Log.error("test error"); + + // assert + expect(stub.infoWasCalled).toEqual(false); + expect(stub.warnWasCalled).toEqual(true); + expect(stub.errorWasCalled).toEqual(true); + }); + + it("should log to all for INFO level", () => { + // arrange + let stub = new StubLog(); + Log.logger = stub; + Log.level = Log.INFO; + + // act + Log.info("test info"); + Log.warn("test warn"); + Log.error("test error"); + + // assert + expect(stub.infoWasCalled).toEqual(true); + expect(stub.warnWasCalled).toEqual(true); + expect(stub.errorWasCalled).toEqual(true); + }); + }); + + describe("logger", () => { + + it("should use the logger specified", () => { + // arrange + let stub = new StubLog(); + Log.logger = stub; + + // act + Log.info("test info"); + Log.warn("test warn"); + Log.error("test error"); + + // assert + expect(stub.infoParam).toEqual("test info"); + expect(stub.warnParam).toEqual("test warn"); + expect(stub.errorParam).toEqual("test error"); + }); + }); + + describe("info", () => { + + it("should work with no config", () => { + Log.info("test"); + }); + }); + + describe("warn", () => { + + it("should work with no config", () => { + Log.warn("test"); + }); + + }); + + describe("error", () => { + + it("should work with no config", () => { + Log.error("test"); + }); + }); +}); + +class StubLog implements Logger { + debugWasCalled: boolean; + infoWasCalled: boolean; + warnWasCalled: boolean; + errorWasCalled: boolean; + debugParam: any; + infoParam: any; + warnParam: any; + errorParam: any; + + constructor() { + this.debugWasCalled = false; + this.infoWasCalled = false; + this.warnWasCalled = false; + this.errorWasCalled = false; + } + debug(arg: any) { + this.debugParam = arg; + this.debugWasCalled = true; + } + info(arg: any) { + this.infoParam = arg; + this.infoWasCalled = true; + } + warn(arg: any) { + this.warnParam = arg; + this.warnWasCalled = true; + } + error(arg: any) { + this.errorParam = arg; + this.errorWasCalled = true; + } +} diff --git a/test/unit/MetadataService.spec.js b/test/unit/MetadataService.spec.js deleted file mode 100644 index fc9c432f1..000000000 --- a/test/unit/MetadataService.spec.js +++ /dev/null @@ -1,406 +0,0 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -import { Log } from '../../src/Log'; -import { MetadataService } from '../../src/MetadataService'; - -import { StubJsonService } from './StubJsonService'; - -import chai from 'chai'; -import { Z_NO_COMPRESSION } from 'zlib'; -chai.should(); -let assert = chai.assert; - -describe("MetadataService", function() { - let subject; - let settings; - let stubJsonService; - - beforeEach(function() { - Log.logger = console; - Log.level = Log.NONE; - - settings = {}; - stubJsonService = new StubJsonService(); - subject = new MetadataService(settings, ()=>stubJsonService); - }); - - describe("constructor", function() { - - it("should require a settings param", function() { - try { - new MetadataService(); - } - catch (e) { - Log.debug(e.message); - e.message.should.contain('settings'); - return; - } - assert.fail(); - }); - - }); - - describe("getMetadata", function() { - - it("should return a promise", function() { - var p = subject.getMetadata(); - p.should.be.instanceof(Promise); - p.catch(e=>{}); - }); - - it("should use metadata on settings", function(done) { - settings.metadata = "test"; - - let p = subject.getMetadata(); - - p.then(result => { - result.should.equal("test"); - done(); - }); - }); - - it("should require metadataUrl", function(done) { - delete settings.metadataUrl; - - let p = subject.getMetadata(); - - p.then(null, err => { - err.message.should.contain('metadataUrl'); - done(); - }); - }); - - it("should require metadataUrl", function(done) { - delete settings.metadataUrl; - - let p = subject.getMetadata(); - - p.then(null, err => { - err.message.should.contain('metadataUrl'); - done(); - }); - }); - - it("should use metadataUrl to make json call", function() { - settings.metadataUrl = "http://sts/metadata"; - stubJsonService.result = Promise.resolve('test'); - - subject.getMetadata(); - - stubJsonService.url.should.equal("http://sts/metadata"); - }); - - it("should return metadata from json call", function(done) { - settings.metadataUrl = "http://sts/metadata"; - stubJsonService.result = Promise.resolve({"test":"data"}); - - let p = subject.getMetadata(); - - p.then(result => { - result.should.deep.equal({"test":"data"}); - done(); - }); - }); - - it("should cache metadata from json call", function(done) { - settings.metadataUrl = "http://sts/metadata"; - stubJsonService.result = Promise.resolve({test:"value"}); - - let p = subject.getMetadata(); - - p.then(result => { - settings.metadata.should.deep.equal({test:"value"}); - done(); - }); - }); - - it("should merge metadata from seed", function(done) { - settings.metadataUrl = "http://sts/metadata"; - settings.metadataSeed = {test1:"one"}; - stubJsonService.result = Promise.resolve({test2:"two"}); - - let p = subject.getMetadata(); - - p.then(result => { - result.should.deep.equal({test1:"one", test2:"two"}); - settings.metadata.should.deep.equal({test1:"one", test2:"two"}); - done(); - }); - }); - - it("should fail if json call fails", function(done) { - settings.metadataUrl = "http://sts/metadata"; - stubJsonService.result = Promise.reject(new Error("test")); - - let p = subject.getMetadata(); - - p.then(null, err => { - err.message.should.contain("test"); - done(); - }); - }); - - }); - - describe("_getMetadataProperty", function() { - - it("should return a promise", function() { - var p = subject._getMetadataProperty(); - p.should.be.instanceof(Promise); - p.catch(e=>{}); - }); - - it("should use metadata on settings", function(done) { - settings.metadata = { - foo: "test" - }; - - let p = subject._getMetadataProperty("foo"); - - p.then(result => { - result.should.equal("test"); - done(); - }); - }); - - it("should fail if no data on metadata", function(done) { - settings.metadata = { - }; - - let p = subject._getMetadataProperty("foo"); - - p.then(null, err => { - err.message.should.contain("foo"); - done(); - }); - }); - - it("should fail if json call to load metadata fails", function(done) { - - settings.metadataUrl = "http://sts/metadata"; - stubJsonService.result = Promise.reject(new Error("test")); - - let p = subject._getMetadataProperty("foo"); - - p.then(null, err => { - err.message.should.contain("test"); - done(); - }); - }); - - }); - - describe("getAuthorizationEndpoint", function() { - - it("should return value from metadata", function(done) { - settings.metadata = { - authorization_endpoint: "http://sts/authorize" - }; - - let p = subject.getAuthorizationEndpoint(); - - p.then(result => { - result.should.equal("http://sts/authorize"); - done(); - }); - }); - - }); - - describe("getUserInfoEndpoint", function() { - - it("should return value from", function(done) { - settings.metadata = { - userinfo_endpoint: "http://sts/userinfo" - }; - - let p = subject.getUserInfoEndpoint(); - - p.then(result => { - result.should.equal("http://sts/userinfo"); - done(); - }); - }); - - }); - - describe("getEndSessionEndpoint", function() { - - it("should return value from", function(done) { - settings.metadata = { - end_session_endpoint: "http://sts/signout" - }; - - let p = subject.getEndSessionEndpoint(); - - p.then(result => { - result.should.equal("http://sts/signout"); - done(); - }); - }); - - it("should support optional value", function(done) { - settings.metadata = { - }; - - let p = subject.getEndSessionEndpoint(); - - p.then(result => { - assert.isUndefined(result); - done(); - }); - }); - - }); - - describe("getCheckSessionIframe", function() { - - it("should return value from", function(done) { - settings.metadata = { - check_session_iframe: "http://sts/check_session" - }; - - let p = subject.getCheckSessionIframe(); - - p.then(result => { - result.should.equal("http://sts/check_session"); - done(); - }); - }); - - it("should support optional value", function(done) { - settings.metadata = { - }; - - let p = subject.getCheckSessionIframe(); - - p.then(result => { - assert.isUndefined(result); - done(); - }); - }); - - }); - - describe("getIssuer", function() { - - it("should return value from", function(done) { - settings.metadata = { - issuer: "http://sts" - }; - - let p = subject.getIssuer(); - - p.then(result => { - result.should.equal("http://sts"); - done(); - }); - }); - - }); - - describe("getSigningKeys", function() { - - it("should return a promise", function() { - var p = subject.getSigningKeys(); - p.should.be.instanceof(Promise); - p.catch(e=>{}); - }); - - it("should use signingKeys on settings", function(done) { - settings.signingKeys = "test"; - - let p = subject.getSigningKeys(); - - p.then(result => { - result.should.equal("test"); - done(); - }); - }); - - it("should fail if metadata does not have jwks_uri", function(done) { - settings.metadata = "test"; - - let p = subject.getSigningKeys(); - - p.then(null, err => { - err.message.should.contain('jwks_uri'); - done(); - }); - }); - - it("should fail if keys missing on keyset from jwks_uri", function(done) { - settings.metadata = { - jwks_uri: "http://sts/metadata/keys" - }; - stubJsonService.result = Promise.resolve({}); - - let p = subject.getSigningKeys(); - - p.then(null, err => { - err.message.should.contain("keyset"); - done(); - }) - }); - - it("should make json call to jwks_uri", function(done) { - settings.metadata = { - jwks_uri: "http://sts/metadata/keys" - }; - stubJsonService.result = Promise.resolve({keys:[{ - use:'sig', - kid:"test" - }]}); - - let p = subject.getSigningKeys(); - - p.then(result => { - stubJsonService.url.should.equal("http://sts/metadata/keys"); - done(); - }); - }); - - it("should return keys from jwks_uri", function(done) { - - settings.metadata = { - jwks_uri: "http://sts/metadata/keys" - }; - stubJsonService.result = Promise.resolve({keys:[{ - use:'sig', - kid:"test" - }]}); - - let p = subject.getSigningKeys(); - - p.then(keys => { - keys.should.deep.equal([{ - use:'sig', - kid:"test" - }]); - done(); - }); - }); - - it("should cache keys in settings", function(done) { - settings.metadata = { - jwks_uri: "http://sts/metadata/keys" - }; - stubJsonService.result = Promise.resolve({keys:[{ - use:'sig', - kid:"test" - }]}); - - let p = subject.getSigningKeys(); - - p.then(keys => { - settings.signingKeys.should.deep.equal([{ - use:'sig', - kid:"test" - }]); - done(); - }) - }); - - }); -}); diff --git a/test/unit/MetadataService.test.ts b/test/unit/MetadataService.test.ts new file mode 100644 index 000000000..ec77988a6 --- /dev/null +++ b/test/unit/MetadataService.test.ts @@ -0,0 +1,413 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +import { Log } from '../../src/Log'; +import { MetadataService } from '../../src/MetadataService'; + +import { StubJsonService } from './StubJsonService'; + +describe("MetadataService", () => { + let subject: MetadataService; + let stubJsonService: any; + let settings: any + + beforeEach(() => { + Log.logger = console; + Log.level = Log.NONE; + + settings = {}; + stubJsonService = new StubJsonService(); + // @ts-ignore + subject = new MetadataService(settings, () => stubJsonService); + }); + + /* TODO: port-ts + describe("constructor", () => { + + it("should require a settings param", () => { + try { + new MetadataService(); + } + catch (e) { + Log.debug(e.message); + e.message.should.contain('settings'); + return; + } + assert.fail(); + }); + + }); + */ + + describe("getMetadata", () => { + + it("should return a promise", async () => { + // act + const p = subject.getMetadata(); + + // assert + expect(p).toBeInstanceOf(Promise); + try { await p; } catch {} + }); + + it("should use metadata on settings", async () => { + // arrange + settings.metadata = "test"; + + // act + const result = await subject.getMetadata(); + + // assert + expect(result).toEqual("test"); + }); + + it("should require metadataUrl", async () => { + // arrange + delete settings.metadataUrl; + + // act + try { + await subject.getMetadata(); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain('metadataUrl'); + } + }); + + it("should use metadataUrl to make json call", () => { + // arrange + settings.metadataUrl = "http://sts/metadata"; + stubJsonService.result = Promise.resolve('test'); + + // act + subject.getMetadata(); + + // assert + expect(stubJsonService.url).toEqual("http://sts/metadata"); + }); + + it("should return metadata from json call", async () => { + // arrange + settings.metadataUrl = "http://sts/metadata"; + stubJsonService.result = Promise.resolve({"test":"data"}); + + // act + const result = await subject.getMetadata(); + + // assert + expect(result).toEqual({"test":"data"}); + }); + + it("should cache metadata from json call", async () => { + // arrange + settings.metadataUrl = "http://sts/metadata"; + stubJsonService.result = Promise.resolve({test:"value"}); + + // act + await subject.getMetadata(); + + // assert + expect(settings.metadata).toEqual({test:"value"}); + }); + + it("should merge metadata from seed", async () => { + // arrange + settings.metadataUrl = "http://sts/metadata"; + settings.metadataSeed = {test1:"one"}; + stubJsonService.result = Promise.resolve({test2:"two"}); + + // act + const result = await subject.getMetadata(); + + // assert + expect(result).toEqual({test1:"one", test2:"two"}); + expect(settings.metadata).toEqual({test1:"one", test2:"two"}); + }); + + it("should fail if json call fails", async () => { + // arrange + settings.metadataUrl = "http://sts/metadata"; + stubJsonService.result = Promise.reject(new Error("test")); + + // act + try { + await subject.getMetadata(); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain("test"); + } + }); + }); + + describe("_getMetadataProperty", () => { + + it("should return a promise", async () => { + // act + var p = subject._getMetadataProperty("issuer"); + + // assert + expect(p).toBeInstanceOf(Promise); + try { await p; } catch {} + }); + + it("should use metadata on settings", async () => { + // arrange + settings.metadata = { + issuer: "test" + }; + + // act + const result = await subject._getMetadataProperty("issuer"); + + // assert + expect(result).toEqual("test"); + }); + + it("should fail if no data on metadata", async () => { + // arrange + settings.metadata = { + }; + + // act + try { + await subject._getMetadataProperty("issuer"); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain("issuer"); + } + }); + + it("should fail if json call to load metadata fails", async () => { + // arrange + settings.metadataUrl = "http://sts/metadata"; + stubJsonService.result = Promise.reject(new Error("test")); + + // act + try { + await subject._getMetadataProperty("issuer"); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain("test"); + } + }); + + }); + + describe("getAuthorizationEndpoint", () => { + + it("should return value from metadata", async () => { + // arrange + settings.metadata = { + authorization_endpoint: "http://sts/authorize" + }; + + // act + const result = await subject.getAuthorizationEndpoint(); + + // assert + expect(result).toEqual("http://sts/authorize"); + }); + + }); + + describe("getUserInfoEndpoint", () => { + + it("should return value from", async () => { + // arrange + settings.metadata = { + userinfo_endpoint: "http://sts/userinfo" + }; + + // act + const result = await subject.getUserInfoEndpoint(); + + // assert + expect(result).toEqual("http://sts/userinfo"); + }); + + }); + + describe("getEndSessionEndpoint", () => { + + it("should return value from", async () => { + // arrange + settings.metadata = { + end_session_endpoint: "http://sts/signout" + }; + + // act + const result = await subject.getEndSessionEndpoint(); + + // assert + expect(result).toEqual("http://sts/signout"); + }); + + it("should support optional value", async () => { + // arrange + settings.metadata = { + }; + + // act + const result = await subject.getEndSessionEndpoint(); + + // assert + expect(result).toBeUndefined(); + }); + + }); + + describe("getCheckSessionIframe", () => { + + it("should return value from", async () => { + // arrange + settings.metadata = { + check_session_iframe: "http://sts/check_session" + }; + + // act + const result = await subject.getCheckSessionIframe(); + + // assert + expect(result).toEqual("http://sts/check_session"); + }); + + it("should support optional value", async () => { + // arrange + settings.metadata = { + }; + + // act + const result = await subject.getCheckSessionIframe(); + + // assert + expect(result).toBeUndefined(); + }); + + }); + + describe("getIssuer", () => { + + it("should return value from", async () => { + // arrange + settings.metadata = { + issuer: "http://sts" + }; + + // act + const result = await subject.getIssuer(); + + // assert + expect(result).toEqual("http://sts"); + }); + + }); + + describe("getSigningKeys", () => { + + it("should return a promise", async () => { + // act + var p = subject.getSigningKeys(); + + // assert + expect(p).toBeInstanceOf(Promise); + try { await p; } catch {} + }); + + it("should use signingKeys on settings", async () => { + // arrange + settings.signingKeys = "test"; + + // act + const result = await subject.getSigningKeys(); + + // assert + expect(result).toEqual("test"); + }); + + it("should fail if metadata does not have jwks_uri", async () => { + // arrange + settings.metadata = "test"; + + // act + try { + await subject.getSigningKeys(); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain('jwks_uri'); + } + }); + + it("should fail if keys missing on keyset from jwks_uri", async () => { + // arrange + settings.metadata = { + jwks_uri: "http://sts/metadata/keys" + }; + stubJsonService.result = Promise.resolve({}); + + // act + try { + await subject.getSigningKeys(); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain('keyset'); + } + }); + + it("should make json call to jwks_uri", async () => { + // arrange + settings.metadata = { + jwks_uri: "http://sts/metadata/keys" + }; + stubJsonService.result = Promise.resolve({keys:[{ + use:'sig', + kid:"test" + }]}); + + // act + await subject.getSigningKeys(); + + // assert + expect(stubJsonService.url).toEqual("http://sts/metadata/keys"); + }); + + it("should return keys from jwks_uri", async () => { + // arrange + settings.metadata = { + jwks_uri: "http://sts/metadata/keys" + }; + const expectedKeys = [{ + use:'sig', + kid:"test" + }] + stubJsonService.result = Promise.resolve({ + keys: expectedKeys + }); + + // act + const result = await subject.getSigningKeys(); + + // assert + expect(result).toEqual(expectedKeys); + }); + + it("should cache keys in settings", async () => { + // arrange + settings.metadata = { + jwks_uri: "http://sts/metadata/keys" + }; + const expectedKeys = [{ + use:'sig', + kid:"test" + }] + stubJsonService.result = Promise.resolve({ + keys: expectedKeys + }); + + // act + await subject.getSigningKeys(); + + // assert + expect(settings.signingKeys).toEqual(expectedKeys); + }); + }); +}); diff --git a/test/unit/OidcClient.spec.js b/test/unit/OidcClient.spec.js deleted file mode 100644 index cdd7228df..000000000 --- a/test/unit/OidcClient.spec.js +++ /dev/null @@ -1,582 +0,0 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -import { Global } from '../../src/Global'; -import { OidcClient } from '../../src/OidcClient'; -import { SigninRequest } from '../../src/SigninRequest'; -import { SigninResponse } from '../../src/SigninResponse'; -import { ErrorResponse } from '../../src/ErrorResponse'; -import { SignoutRequest } from '../../src/SignoutRequest'; -import { SignoutResponse } from '../../src/SignoutResponse'; -import { State } from '../../src/State'; -import { SigninState } from '../../src/SigninState'; -import { OidcClientSettings } from '../../src/OidcClientSettings'; -import { MetadataService } from '../../src/MetadataService'; - -import { Log } from '../../src/Log'; - -import { StubMetadataService } from './StubMetadataService'; -import { StubStateStore } from './StubStateStore'; -import { StubResponseValidator } from './StubResponseValidator'; - -import chai from 'chai'; -chai.should(); -let assert = chai.assert; - -describe("OidcClient", function () { - let settings; - let subject; - let stubMetadataService; - let stubStore; - let stubValidator; - - beforeEach(function () { - - Global._testing(); - - Log.logger = console; - Log.level = Log.NONE; - - stubStore = new StubStateStore(); - stubValidator = new StubResponseValidator(); - stubMetadataService = new StubMetadataService(); - - settings = { - authority: 'authority', - client_id: 'client', - redirect_uri: "http://app", - post_logout_redirect_uri: "http://app", - stateStore: stubStore, - ResponseValidatorCtor: () => stubValidator, - MetadataServiceCtor: () => stubMetadataService - }; - subject = new OidcClient(settings); - }); - - describe("constructor", function () { - - it("should allow no settings", function () { - let subject = new OidcClientSettings(); - }); - - it("should expose settings", function () { - subject.settings.should.be.ok; - subject.settings.client_id.should.equal("client"); - }); - - it("should accept OidcClientSettings", function () { - let settings = new OidcClientSettings({ client_id: "client" }); - - let subject = new OidcClient(settings, { - stateStore: stubStore, - ResponseValidatorCtor: () => stubValidator, - MetadataServiceCtor: () => stubMetadataService - }); - - subject.settings.should.equal(settings); - }); - - }); - - describe("settings", function () { - - it("should be OidcClientSettings", function () { - subject.settings.should.be.instanceof(OidcClientSettings); - }); - - }); - - describe("metadataService", function () { - - it("should be MetadataService", function () { - subject.metadataService.should.be.equal(stubMetadataService); - }); - - }); - - describe("createSigninRequest", function () { - - it("should return a promise", function () { - stubMetadataService.getAuthorizationEndpointResult = Promise.resolve("http://sts/authorize"); - var p = subject.createSigninRequest(); - p.should.be.instanceof(Promise); - p.catch(e=>{}); - }); - - it("should return SigninRequest", function (done) { - stubMetadataService.getAuthorizationEndpointResult = Promise.resolve("http://sts/authorize"); - - var p = subject.createSigninRequest(); - - p.then(request => { - request.should.be.instanceof(SigninRequest); - done(); - }); - }); - - it("should pass params to SigninRequest", function (done) { - stubMetadataService.getAuthorizationEndpointResult = Promise.resolve("http://sts/authorize"); - - var p = subject.createSigninRequest({ - data: 'foo', - response_type: 'bar', - response_mode: 'mode', - scope: 'baz', - redirect_uri: 'quux', - prompt: 'p', - display: 'd', - max_age: 'm', - ui_locales: 'u', - id_token_hint: 'ith', - login_hint: 'lh', - acr_values: 'av', - resource: 'res', - request: 'req', - request_uri: 'req_uri' - }); - - p.then(request => { - request.state.data.should.equal('foo'); - - var url = request.url; - url.should.contain("http://sts/authorize"); - url.should.contain("response_type=bar"); - url.should.contain("scope=baz"); - url.should.contain("redirect_uri=quux"); - url.should.contain("prompt=p"); - url.should.contain("display=d"); - url.should.contain("max_age=m"); - url.should.contain("ui_locales=u"); - url.should.contain("id_token_hint=ith"); - url.should.contain("login_hint=lh"); - url.should.contain("acr_values=av"); - url.should.contain("resource=res"); - url.should.contain("request=req"); - url.should.contain("request_uri=req_uri"); - url.should.contain("response_mode=mode"); - - done(); - }); - }); - - it("should pass state in place of data to SigninRequest", function (done) { - stubMetadataService.getAuthorizationEndpointResult = Promise.resolve("http://sts/authorize"); - - var p = subject.createSigninRequest({ - state: 'foo', - response_type: 'bar', - scope: 'baz', - redirect_uri: 'quux', - prompt: 'p', - display: 'd', - max_age: 'm', - ui_locales: 'u', - id_token_hint: 'ith', - login_hint: 'lh', - acr_values: 'av', - resource: 'res' - }); - - p.then(request => { - request.state.data.should.equal('foo'); - - var url = request.url; - url.should.contain("http://sts/authorize"); - url.should.contain("response_type=bar"); - url.should.contain("scope=baz"); - url.should.contain("redirect_uri=quux"); - url.should.contain("prompt=p"); - url.should.contain("display=d"); - url.should.contain("max_age=m"); - url.should.contain("ui_locales=u"); - url.should.contain("id_token_hint=ith"); - url.should.contain("login_hint=lh"); - url.should.contain("acr_values=av"); - url.should.contain("resource=res"); - - done(); - }); - }); - - it("should fail if hybrid code id_token requested", function (done) { - var p = subject.createSigninRequest({response_type:"code id_token"}); - p.then(null, err => { - err.message.should.contain("hybrid"); - done(); - }); - }); - - it("should fail if hybrid code token requested", function (done) { - var p = subject.createSigninRequest({response_type:"code token"}); - p.then(null, err => { - err.message.should.contain("hybrid"); - done(); - }); - }); - - it("should fail if hybrid code id_token token requested", function (done) { - var p = subject.createSigninRequest({response_type:"code id_token token"}); - p.then(null, err => { - err.message.should.contain("hybrid"); - done(); - }); - }); - - it("should fail if metadata fails", function (done) { - - stubMetadataService.getAuthorizationEndpointResult = Promise.reject(new Error("test")); - - var p = subject.createSigninRequest(); - - p.then(null, err => { - err.message.should.contain("test"); - done(); - }); - }); - - it("should fail if seting state into store fails", function (done) { - stubMetadataService.getAuthorizationEndpointResult = Promise.resolve("http://sts/authorize"); - stubStore.error = "foo"; - - var p = subject.createSigninRequest(); - - p.then(null, err => { - err.message.should.contain("foo"); - done(); - }); - }); - - it("should store state", function (done) { - stubMetadataService.getAuthorizationEndpointResult = Promise.resolve("http://sts/authorize"); - - var p = subject.createSigninRequest(); - - p.then(request => { - stubStore.item.should.be.ok; - done(); - }); - }); - - }); - - describe("readSigninResponseState", function () { - - it("should return a promise", function () { - var p = subject.readSigninResponseState("state=state"); - p.should.be.instanceof(Promise); - p.catch(e=>{}); - }); - - it("should fail if no state on response", function (done) { - stubStore.item = "state"; - subject.readSigninResponseState("").then(null, err => { - err.message.should.contain('state'); - done(); - }); - }); - - it("should fail if storage fails", function (done) { - stubStore.error = "fail"; - subject.readSigninResponseState("state=state").then(null, err => { - err.message.should.contain('fail'); - done(); - }); - }); - - it("should deserialize stored state and return state and response", function (done) { - stubStore.item = new SigninState({ id: '1', nonce: '2', authority:'authority', client_id:'client', request_type:'type' }).toStorageString(); - - subject.readSigninResponseState("state=1").then(({state, response}) => { - state.id.should.equal('1'); - state.nonce.should.equal('2'); - state.authority.should.equal('authority'); - state.client_id.should.equal('client'); - state.request_type.should.equal('type'); - response.state.should.equal('1'); - done(); - }); - }); - - }); - - describe("processSigninResponse", function () { - - it("should return a promise", function () { - var p = subject.processSigninResponse("state=state"); - p.should.be.instanceof(Promise); - p.catch(e=>{}); - }); - - it("should fail if no state on response", function (done) { - stubStore.item = "state"; - subject.processSigninResponse("").then(null, err => { - err.message.should.contain('state'); - done(); - }); - }); - - it("should fail if storage fails", function (done) { - stubStore.error = "fail"; - subject.processSigninResponse("state=state").then(null, err => { - err.message.should.contain('fail'); - done(); - }); - }); - - it("should deserialize stored state and call validator", function (done) { - stubStore.item = new SigninState({ id: '1', nonce: '2', authority:'authority', client_id:'client' }).toStorageString(); - - subject.processSigninResponse("state=1").then(response => { - stubValidator.signinState.id.should.equal('1'); - stubValidator.signinState.nonce.should.equal('2'); - stubValidator.signinState.authority.should.equal('authority'); - stubValidator.signinState.client_id.should.equal('client'); - stubValidator.signinResponse.should.be.deep.equal(response); - done(); - }); - }); - - }); - - describe("createSignoutRequest", function () { - - it("should return a promise", function () { - stubMetadataService.getEndSessionEndpointResult = Promise.resolve("http://sts/signout"); - var p = subject.createSignoutRequest(); - p.should.be.instanceof(Promise); - p.catch(e=>{}); - }); - - it("should return SignoutRequest", function (done) { - stubMetadataService.getEndSessionEndpointResult = Promise.resolve("http://sts/signout"); - - var p = subject.createSignoutRequest(); - - p.then(request => { - request.should.be.instanceof(SignoutRequest); - done(); - }); - }); - - it("should pass state in place of data to SignoutRequest", function (done) { - stubMetadataService.getEndSessionEndpointResult = Promise.resolve("http://sts/signout"); - - var p = subject.createSignoutRequest({ - state: 'foo', - post_logout_redirect_uri: "bar", - id_token_hint: "baz" - }); - - p.then(request => { - request.state.data.should.equal('foo'); - var url = request.url; - url.should.contain("http://sts/signout"); - url.should.contain("post_logout_redirect_uri=bar"); - url.should.contain("id_token_hint=baz"); - done(); - }); - }); - - it("should pass params to SignoutRequest", function (done) { - stubMetadataService.getEndSessionEndpointResult = Promise.resolve("http://sts/signout"); - - var p = subject.createSignoutRequest({ - data: 'foo', - post_logout_redirect_uri: "bar", - id_token_hint: "baz" - }); - - p.then(request => { - request.state.data.should.equal('foo'); - var url = request.url; - url.should.contain("http://sts/signout"); - url.should.contain("post_logout_redirect_uri=bar"); - url.should.contain("id_token_hint=baz"); - done(); - }); - }); - - it("should fail if metadata fails", function (done) { - stubMetadataService.getEndSessionEndpointResult = Promise.reject(new Error("test")); - - var p = subject.createSignoutRequest(); - - p.then(null, err => { - err.message.should.contain("test"); - done(); - }); - }); - - it("should fail if no signout endpoint on metadata", function (done) { - stubMetadataService.getEndSessionEndpointResult = Promise.resolve(undefined); - - var p = subject.createSignoutRequest(); - - p.catch(err => { - err.message.should.contain("no end session endpoint"); - done(); - }); - }); - - it("should store state", function (done) { - stubMetadataService.getEndSessionEndpointResult = Promise.resolve("http://sts/signout"); - - var p = subject.createSignoutRequest({ - data:"foo", id_token_hint:'hint' - }); - - p.then(request => { - stubStore.item.should.be.ok; - done(); - }); - }); - - it("should not generate state if no data", function (done) { - stubMetadataService.getEndSessionEndpointResult = Promise.resolve("http://sts/signout"); - - var p = subject.createSignoutRequest(); - - p.then(request => { - assert.isUndefined(stubStore.item); - done(); - }); - }); - }); - - describe("readSignoutResponseState", function () { - - it("should return a promise", function () { - var p = subject.readSignoutResponseState("state=state"); - p.should.be.instanceof(Promise); - p.catch(e=>{}); - }); - - it("should return result if no state on response", function (done) { - subject.readSignoutResponseState("").then(({state, response}) => { - response.should.be.ok; - done(); - }); - }); - - it("should return error", function (done) { - subject.readSignoutResponseState("error=foo").then(null, err => { - err.error.should.equal("foo"); - done(); - }); - }); - - it("should fail if storage fails", function (done) { - stubStore.error = "fail"; - subject.readSignoutResponseState("state=state").then(null, err => { - err.message.should.contain('fail'); - done(); - }); - }); - - it("should deserialize stored state and return state and response", function (done) { - - stubStore.item = new State({ id: '1', request_type:'type' }).toStorageString(); - - subject.readSignoutResponseState("state=1").then(({state, response}) => { - state.id.should.equal('1'); - state.request_type.should.equal('type'); - response.state.should.be.equal('1'); - done(); - }); - }); - - it("should call validator with state even if error in response", function (done) { - - stubStore.item = new State({ id: '1', data:"bar" }).toStorageString(); - - subject.processSignoutResponse("state=1&error=foo").then(response => { - stubValidator.signoutState.id.should.equal('1'); - stubValidator.signoutResponse.should.be.deep.equal(response); - done(); - }); - }); - - }); - - describe("processSignoutResponse", function () { - - it("should return a promise", function () { - var p = subject.processSignoutResponse("state=state"); - p.should.be.instanceof(Promise); - p.catch(e=>{}); - }); - - it("should return result if no state on response", function (done) { - subject.processSignoutResponse("").then(response => { - response.should.be.ok; - done(); - }); - }); - - it("should return error", function (done) { - subject.processSignoutResponse("error=foo").then(null, err => { - err.error.should.equal("foo"); - done(); - }); - }); - - it("should fail if storage fails", function (done) { - stubStore.error = "fail"; - subject.processSignoutResponse("state=state").then(null, err => { - err.message.should.contain('fail'); - done(); - }); - }); - - it("should deserialize stored state and call validator", function (done) { - - stubStore.item = new State({ id: '1' }).toStorageString(); - - subject.processSignoutResponse("state=1").then(response => { - stubValidator.signoutState.id.should.equal('1'); - stubValidator.signoutResponse.should.be.deep.equal(response); - done(); - }); - }); - - it("should call validator with state even if error in response", function (done) { - - stubStore.item = new State({ id: '1', data:"bar" }).toStorageString(); - - subject.processSignoutResponse("state=1&error=foo").then(response => { - stubValidator.signoutState.id.should.equal('1'); - stubValidator.signoutResponse.should.be.deep.equal(response); - done(); - }); - }); - - }); - - describe("clearStaleState", function () { - - it("should return a promise", function () { - var p = subject.clearStaleState(); - p.should.be.instanceof(Promise); - p.catch(e=>{}); - }); - - it("should call State.clearStaleState", function () { - var oldState = State.clearStaleState; - - State.clearStaleState = function (store, age) { - State.clearStaleState.wasCalled = true; - State.clearStaleState.store = store; - State.clearStaleState.age = age; - }; - subject.clearStaleState(); - - State.clearStaleState.wasCalled.should.be.true; - State.clearStaleState.store.should.equal(subject._stateStore); - State.clearStaleState.age.should.equal(subject.settings.staleStateAge); - - State.clearStaleState = oldState; - }); - - }); - -}); diff --git a/test/unit/OidcClient.test.ts b/test/unit/OidcClient.test.ts new file mode 100644 index 000000000..c3e025cb1 --- /dev/null +++ b/test/unit/OidcClient.test.ts @@ -0,0 +1,648 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +import { Log } from '../../src/Log'; +import { OidcClient } from '../../src/OidcClient'; +import { OidcClientSettings } from '../../src/OidcClientSettings'; +import { SigninState } from '../../src/SigninState'; +import { State } from '../../src/State'; +import { SigninRequest } from '../../src/SigninRequest'; +import { SignoutRequest } from '../../src/SignoutRequest'; +import { SignoutResponse } from '../../src/SignoutResponse'; + +import { StubStateStore } from './StubStateStore'; +import { StubResponseValidator } from './StubResponseValidator'; +import { StubMetadataService } from './StubMetadataService'; + +// workaround jest parse error +jest.mock('../../jsrsasign/dist/jsrsasign.js', () => { + return { + jws: jest.fn(), + KEYUTIL: jest.fn(), + X509: jest.fn(), + crypto: jest.fn(), + hextob64u: jest.fn(), + b64tohex: jest.fn() + }; +}); + +describe("OidcClient", () => { + let stubStore: any; + let stubResponseValidator: any; + let stubMetadataService: any; + let settings: any; + let subject: OidcClient; + + beforeEach(() => { + Log.logger = console; + Log.level = Log.NONE; + + stubStore = new StubStateStore(); + stubResponseValidator = new StubResponseValidator(); + stubMetadataService = new StubMetadataService(); + + settings = { + authority: 'authority', + client_id: 'client', + redirect_uri: "http://app", + post_logout_redirect_uri: "http://app", + stateStore: stubStore, + ResponseValidatorCtor: () => stubResponseValidator, + MetadataServiceCtor: () => stubMetadataService + }; + subject = new OidcClient(settings); + }); + + describe("constructor", () => { + + it("should allow no settings", () => { + // act + new OidcClient(); + }); + + it("should expose settings", () => { + // assert + expect(subject.settings).not.toBeNull(); + expect(subject.settings.client_id).toEqual("client"); + }); + + it("should accept OidcClientSettings", () => { + // arrange + let settings = new OidcClientSettings({ client_id: "client" }); + + // act + let subject = new OidcClient(settings); + + // assert + expect(subject.settings).toEqual(settings); + }); + }); + + describe("settings", () => { + + it("should be OidcClientSettings", () => { + // assert + expect(subject.settings).toBeInstanceOf(OidcClientSettings); + }); + + }); + + describe("metadataService", () => { + + it("should be MetadataService", () => { + // assert + expect(subject.metadataService).toEqual(stubMetadataService); + }); + + }); + + describe("createSigninRequest", () => { + + it("should return a promise", () => { + // arrange + stubMetadataService.getAuthorizationEndpointResult = Promise.resolve("http://sts/authorize"); + + // act + var p = subject.createSigninRequest(); + + // assert + expect(p).toBeInstanceOf(Promise); + }); + + it("should return SigninRequest", async () => { + // arrange + stubMetadataService.getAuthorizationEndpointResult = Promise.resolve("http://sts/authorize"); + + // act + var request = await subject.createSigninRequest(); + + // assert + expect(request).toBeInstanceOf(SigninRequest); + }); + + it("should pass params to SigninRequest", async () => { + // arrange + stubMetadataService.getAuthorizationEndpointResult = Promise.resolve("http://sts/authorize"); + + // act + var request = await subject.createSigninRequest({ + data: 'foo', + response_type: 'bar', + response_mode: 'mode', + scope: 'baz', + redirect_uri: 'quux', + prompt: 'p', + display: 'd', + max_age: 'm', + ui_locales: 'u', + id_token_hint: 'ith', + login_hint: 'lh', + acr_values: 'av', + resource: 'res', + request: 'req', + request_uri: 'req_uri' + }); + + // assert + expect(request.state.data).toEqual('foo'); + const url = request.url; + expect(url).toContain("http://sts/authorize"); + expect(url).toContain("response_type=bar"); + expect(url).toContain("scope=baz"); + expect(url).toContain("redirect_uri=quux"); + expect(url).toContain("prompt=p"); + expect(url).toContain("display=d"); + expect(url).toContain("max_age=m"); + expect(url).toContain("ui_locales=u"); + expect(url).toContain("id_token_hint=ith"); + expect(url).toContain("login_hint=lh"); + expect(url).toContain("acr_values=av"); + expect(url).toContain("resource=res"); + expect(url).toContain("request=req"); + expect(url).toContain("request_uri=req_uri"); + expect(url).toContain("response_mode=mode"); + }); + + it("should pass state in place of data to SigninRequest", async () => { + // arrange + stubMetadataService.getAuthorizationEndpointResult = Promise.resolve("http://sts/authorize"); + + // act + var request = await subject.createSigninRequest({ + state: 'foo', + response_type: 'bar', + scope: 'baz', + redirect_uri: 'quux', + prompt: 'p', + display: 'd', + max_age: 'm', + ui_locales: 'u', + id_token_hint: 'ith', + login_hint: 'lh', + acr_values: 'av', + resource: 'res' + }); + + // assert + expect(request.state.data).toEqual('foo'); + const url = request.url; + expect(url).toContain("http://sts/authorize"); + expect(url).toContain("response_type=bar"); + expect(url).toContain("scope=baz"); + expect(url).toContain("redirect_uri=quux"); + expect(url).toContain("prompt=p"); + expect(url).toContain("display=d"); + expect(url).toContain("max_age=m"); + expect(url).toContain("ui_locales=u"); + expect(url).toContain("id_token_hint=ith"); + expect(url).toContain("login_hint=lh"); + expect(url).toContain("acr_values=av"); + expect(url).toContain("resource=res"); + }); + + it("should fail if hybrid code id_token requested", async () => { + // act + try { + await subject.createSigninRequest({response_type:"code id_token"}); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain("hybrid"); + } + }); + + it("should fail if hybrid code token requested", async () => { + // act + try { + await subject.createSigninRequest({response_type:"code token"}); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain("hybrid"); + } + }); + + it("should fail if hybrid code id_token token requested", async () => { + // act + try { + await subject.createSigninRequest({response_type:"code id_token token"}); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain("hybrid"); + } + }); + + it("should fail if metadata fails", async () => { + // arrange + stubMetadataService.getAuthorizationEndpointResult = Promise.reject(new Error("test")); + + // act + try { + await subject.createSigninRequest(); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain("test"); + } + }); + + it("should fail if seting state into store fails", async () => { + // arrange + stubMetadataService.getAuthorizationEndpointResult = Promise.resolve("http://sts/authorize"); + stubStore.error = "foo"; + + // act + try { + await subject.createSigninRequest(); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain("foo"); + } + }); + + it("should store state", async () => { + // arrange + stubMetadataService.getAuthorizationEndpointResult = Promise.resolve("http://sts/authorize"); + + // act + await subject.createSigninRequest(); + + // assert + expect(stubStore.item).toBeDefined(); + }); + }); + + describe("readSigninResponseState", () => { + + it("should return a promise", () => { + // act + var p = subject.readSigninResponseState("state=state"); + + // asssert + expect(p).toBeInstanceOf(Promise); + }); + + it("should fail if no state on response", async () => { + // arrange + stubStore.item = "state"; + + // act + try { + await subject.readSigninResponseState(""); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain('state'); + } + }); + + it("should fail if storage fails", async () => { + // arrange + stubStore.error = "fail"; + + // act + try { + await subject.readSigninResponseState("state=state") + fail("should not come here"); + } catch (err) { + expect(err.message).toContain('fail'); + } + }); + + it("should deserialize stored state and return state and response", async () => { + // arrange + stubStore.item = new SigninState({ id: '1', nonce: '2', authority:'authority', client_id:'client', request_type:'type' }).toStorageString(); + + // act + let { state, response } = await subject.readSigninResponseState("state=1") + + // assert + expect(state.id).toEqual('1'); + expect(state.nonce).toEqual('2'); + expect(state.authority).toEqual('authority'); + expect(state.client_id).toEqual('client'); + expect(state.request_type).toEqual('type'); + expect(response.state).toEqual('1'); + }); + }); + + describe("processSigninResponse", () => { + + it("should return a promise", () => { + // act + var p = subject.processSigninResponse("state=state"); + + // assert + expect(p).toBeInstanceOf(Promise); + }); + + it("should fail if no state on response", async () => { + // arrange + stubStore.item = "state"; + + // act + try { + await subject.processSigninResponse(""); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain('state'); + } + }); + + it("should fail if storage fails", async () => { + // arrange + stubStore.error = "fail"; + + // act + try { + await subject.processSigninResponse("state=state"); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain('fail'); + } + }); + + it("should deserialize stored state and call validator", async () => { + // arrange + stubStore.item = new SigninState({ id: '1', nonce: '2', authority:'authority', client_id:'client' }).toStorageString(); + + // act + let response = await subject.processSigninResponse("state=1"); + + // assert + expect(stubResponseValidator.signinState.id).toEqual('1'); + expect(stubResponseValidator.signinState.nonce).toEqual('2'); + expect(stubResponseValidator.signinState.authority).toEqual('authority'); + expect(stubResponseValidator.signinState.client_id).toEqual('client'); + expect(stubResponseValidator.signinResponse).toEqual(response); + }); + }); + + describe("createSignoutRequest", () => { + + it("should return a promise", () => { + // arrange + stubMetadataService.getEndSessionEndpointResult = Promise.resolve("http://sts/signout"); + + // act + var p = subject.createSignoutRequest(); + + // assert + expect(p).toBeInstanceOf(Promise); + }); + + it("should return SignoutRequest", async () => { + // arrange + stubMetadataService.getEndSessionEndpointResult = Promise.resolve("http://sts/signout"); + + // act + var request = await subject.createSignoutRequest(); + + // assert + expect(request).toBeInstanceOf(SignoutRequest); + }); + + it("should pass state in place of data to SignoutRequest", async () => { + // arrange + stubMetadataService.getEndSessionEndpointResult = Promise.resolve("http://sts/signout"); + + // act + const request = await subject.createSignoutRequest({ + state: 'foo', + post_logout_redirect_uri: "bar", + id_token_hint: "baz" + }); + + // assert + expect(request.state).toBeDefined(); + expect(request.state!.data).toEqual('foo'); + const url = request.url; + expect(url).toContain("http://sts/signout"); + expect(url).toContain("post_logout_redirect_uri=bar"); + expect(url).toContain("id_token_hint=baz"); + }); + + it("should pass params to SignoutRequest", async () => { + // arrange + stubMetadataService.getEndSessionEndpointResult = Promise.resolve("http://sts/signout"); + + // act + const request = await subject.createSignoutRequest({ + data: 'foo', + post_logout_redirect_uri: "bar", + id_token_hint: "baz" + }); + + // assert + expect(request.state).toBeDefined(); + expect(request.state!.data).toEqual('foo'); + const url = request.url; + expect(url).toContain("http://sts/signout"); + expect(url).toContain("post_logout_redirect_uri=bar"); + expect(url).toContain("id_token_hint=baz"); + }); + + it("should fail if metadata fails", async () => { + // arrange + stubMetadataService.getEndSessionEndpointResult = Promise.reject(new Error("test")); + + // act + try { + await subject.createSignoutRequest(); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain("test"); + } + }); + + it("should fail if no signout endpoint on metadata", async () => { + // arrange + stubMetadataService.getEndSessionEndpointResult = Promise.resolve(undefined); + + // act + try { + await subject.createSignoutRequest(); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain("no end session endpoint"); + } + }); + + it("should store state", async () => { + // arrange + stubMetadataService.getEndSessionEndpointResult = Promise.resolve("http://sts/signout"); + + // act + await subject.createSignoutRequest({ + data:"foo", id_token_hint:'hint' + }); + + // assert + expect(stubStore.item).toBeDefined(); + }); + + it("should not generate state if no data", async () => { + // arrange + stubMetadataService.getEndSessionEndpointResult = Promise.resolve("http://sts/signout"); + + // act + await subject.createSignoutRequest(); + + // assert + expect(stubStore.item).toBeUndefined(); + }); + }); + + describe("readSignoutResponseState", () => { + it("should return a promise", () => { + // act + const p = subject.readSignoutResponseState("state=state"); + + // assert + expect(p).toBeInstanceOf(Promise); + }); + + it("should return result if no state on response", async () => { + // act + const { response } = await subject.readSignoutResponseState(""); + + // assert + expect(response).toBeInstanceOf(SignoutResponse); + }); + + it("should return error", async () => { + // act + try { + await subject.readSignoutResponseState("error=foo"); + fail("should not come here"); + } catch (err) { + expect(err.error).toEqual("foo"); + } + }); + + it("should fail if storage fails", async () => { + // arrange + stubStore.error = "fail"; + + // act + try { + await subject.readSignoutResponseState("state=state"); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain("fail"); + } + }); + + it("should deserialize stored state and return state and response", async () => { + // arrange + stubStore.item = new State({ id: '1', request_type:'type' }).toStorageString(); + + // act + const { state, response } = await subject.readSignoutResponseState("state=1"); + + // assert + expect(state).toBeDefined(); + expect(state!.id).toEqual('1'); + expect(state!.request_type).toEqual('type'); + expect(response.state).toEqual('1'); + }); + + it("should call validator with state even if error in response", async () => { + // arrange + stubStore.item = new State({ id: '1', data:"bar" }).toStorageString(); + + // act + const response = await subject.processSignoutResponse("state=1&error=foo"); + + // assert + expect(stubResponseValidator.signoutState.id).toEqual('1'); + expect(stubResponseValidator.signoutResponse).toEqual(response); + }); + }); + + describe("processSignoutResponse", () => { + + it("should return a promise", () => { + // act + var p = subject.processSignoutResponse("state=state"); + + // assert + expect(p).toBeInstanceOf(Promise); + }); + + it("should return result if no state on response", async () => { + // act + const response = await subject.processSignoutResponse(""); + + // assert + expect(response).toBeInstanceOf(SignoutResponse); + }); + + it("should return error", async () => { + // act + try { + await subject.processSignoutResponse("error=foo"); + fail("should not come here"); + } catch (err) { + expect(err.error).toEqual("foo"); + } + }); + + it("should fail if storage fails", async () => { + // arrange + stubStore.error = "fail"; + + // act + try { + await subject.processSignoutResponse("state=state"); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain("fail"); + } + }); + + it("should deserialize stored state and call validator", async () => { + // arrange + stubStore.item = new State({ id: '1' }).toStorageString(); + + // act + const response = await subject.processSignoutResponse("state=1"); + + // assert + expect(stubResponseValidator.signoutState.id).toEqual('1'); + expect(stubResponseValidator.signoutResponse).toEqual(response); + }); + + it("should call validator with state even if error in response", async () => { + // arrange + stubStore.item = new State({ id: '1', data:"bar" }).toStorageString(); + + // act + const response = await subject.processSignoutResponse("state=1&error=foo"); + + // assert + expect(stubResponseValidator.signoutState.id).toEqual('1'); + expect(stubResponseValidator.signoutResponse).toEqual(response); + }); + }); + + describe("clearStaleState", () => { + + it("should return a promise", () => { + // act + var p = subject.clearStaleState(); + + // assert + expect(p).toBeInstanceOf(Promise); + }); + + it("should call State.clearStaleState", () => { + // arrange + var oldState = State.clearStaleState; + State.clearStaleState = jest.fn(); + + // act + subject.clearStaleState(); + + // assert + expect(State.clearStaleState).toBeCalled(); + State.clearStaleState = oldState; + }); + }); +}); diff --git a/test/unit/OidcClientSettings.spec.js b/test/unit/OidcClientSettings.spec.js deleted file mode 100644 index 1d9cd6d25..000000000 --- a/test/unit/OidcClientSettings.spec.js +++ /dev/null @@ -1,509 +0,0 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -import { Log } from '../../src/Log'; -import { OidcClientSettings } from '../../src/OidcClientSettings'; -import { Global } from '../../src/Global'; - -import chai from 'chai'; -chai.should(); -let assert = chai.assert; - -describe("OidcClientSettings", function () { - - beforeEach(function () { - Global._testing(); - Log.logger = console; - Log.level = Log.NONE; - }); - - describe("client_id", function () { - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client' - }); - subject.client_id.should.equal("client"); - }); - - it("should not allow setting if previously set", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - authority: "http://sts" - }); - try { - subject.client_id = "diff"; - assert.fail(); - } - catch (e) { - e.message.should.contain("client_id"); - } - }); - - it("should allow setting if not previously set", function () { - let subject = new OidcClientSettings({ - }); - subject.client_id = "test"; - subject.client_id.should.equal("test"); - }); - }); - - describe("client_secret", function () { - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_secret: 'secret' - }); - subject.client_secret.should.equal("secret"); - }); - }); - - describe("response_type", function () { - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - response_type: "foo" - }); - subject.response_type.should.equal("foo"); - }); - - it("should use default value", function () { - let subject = new OidcClientSettings({ - client_id: 'client' - }); - subject.response_type.should.equal("id_token"); - }); - - }); - - describe("scope", function () { - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - scope: "foo" - }); - subject.scope.should.equal("foo"); - }); - - it("should use default value", function () { - let subject = new OidcClientSettings({ - client_id: 'client' - }); - subject.scope.should.equal("openid"); - }); - - }); - - describe("redirect_uri", function () { - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - redirect_uri: "foo" - }); - subject.redirect_uri.should.equal("foo"); - }); - - }); - - describe("post_logout_redirect_uri", function () { - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - post_logout_redirect_uri: "http://app/loggedout" - }); - subject.post_logout_redirect_uri.should.equal("http://app/loggedout"); - }); - }); - - describe("prompt", function () { - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - prompt: "foo" - }); - subject.prompt.should.equal("foo"); - }); - }); - - describe("display", function () { - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - display: "foo" - }); - subject.display.should.equal("foo"); - }); - }); - - describe("max_age", function () { - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - max_age: "foo" - }); - subject.max_age.should.equal("foo"); - }); - }); - - describe("ui_locales", function () { - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - ui_locales: "foo" - }); - subject.ui_locales.should.equal("foo"); - }); - }); - - describe("acr_values", function () { - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - acr_values: "foo" - }); - subject.acr_values.should.equal("foo"); - }); - }); - - describe("resource", function () { - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - resource: "foo" - }); - subject.resource.should.equal("foo"); - }); - }); - - describe("response_mode", function () { - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - response_mode: "foo" - }); - subject.response_mode.should.equal("foo"); - }); - }); - - describe("authority", function () { - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - authority: "http://sts" - }); - subject.authority.should.equal("http://sts"); - }); - - it("should not allow setting if previously set", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - authority: "http://sts" - }); - try { - subject.authority = "http://sts2"; - assert.fail(); - } - catch (e) { - e.message.should.contain("authority"); - } - }); - - it("should allow setting if not previously set", function () { - let subject = new OidcClientSettings({ - client_id: 'client' - }); - subject.authority = "http://sts"; - subject.authority.should.equal("http://sts"); - }); - }); - - describe("metadataUrl", function () { - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - metadataUrl: "http://sts/metadata" - }); - subject.metadataUrl.should.equal("http://sts/metadata"); - }); - - it("should infer value from authority", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - authority: "http://sts" - }); - subject.metadataUrl.should.equal("http://sts/.well-known/openid-configuration"); - - subject = new OidcClientSettings({ - client_id: 'client', - authority: "http://sts/" - }); - subject.metadataUrl.should.equal("http://sts/.well-known/openid-configuration"); - }); - - }); - - describe("metadata", function () { - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - metadata: "test" - }); - subject.metadata.should.equal("test"); - }); - - it("should store value", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - }); - subject.metadata = "test"; - subject.metadata.should.equal("test"); - }); - - }); - - describe("signingKeys", function () { - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - signingKeys: "test" - }); - subject.signingKeys.should.equal("test"); - }); - - it("should store value", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - }); - subject.signingKeys = "test"; - subject.signingKeys.should.equal("test"); - }); - - }); - - describe("filterProtocolClaims", function () { - - it("should use default value", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - }); - subject.filterProtocolClaims.should.equal(true); - }); - - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - filterProtocolClaims: true - }); - subject.filterProtocolClaims.should.equal(true); - - subject = new OidcClientSettings({ - client_id: 'client', - filterProtocolClaims: false - }); - subject.filterProtocolClaims.should.equal(false); - }); - }); - - describe("loadUserInfo", function () { - - it("should use default value", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - }); - subject.loadUserInfo.should.equal(true); - }); - - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - loadUserInfo: true - }); - subject.loadUserInfo.should.equal(true); - - subject = new OidcClientSettings({ - client_id: 'client', - loadUserInfo: false - }); - subject.loadUserInfo.should.equal(false); - }); - }); - - describe("staleStateAge", function () { - - it("should use default value", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - }); - subject.staleStateAge.should.equal(900); - }); - - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - staleStateAge: 100 - }); - subject.staleStateAge.should.equal(100); - }); - }); - - describe("clockSkew", function () { - - it("should use default value", function () { - let subject = new OidcClientSettings({ - client_id: 'client' - }); - subject.clockSkew.should.equal(5 * 60); // 5 mins - }); - - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - clockSkew: 10 - }); - subject.clockSkew.should.equal(10); - }); - }); - - describe("stateStore", function () { - - it("should return value from initial settings", function () { - let temp = {}; - let subject = new OidcClientSettings({ - client_id: 'client', - stateStore: temp - }); - subject.stateStore.should.equal(temp); - }); - }); - - describe("stateStore", function () { - - it("should return value from initial settings", function () { - let temp = {}; - let subject = new OidcClientSettings({ - client_id: 'client', - stateStore: temp - }); - subject.stateStore.should.equal(temp); - }); - }); - - describe("validator", function () { - - it("should return value from initial settings", function () { - - let temp = {}; - let subject = new OidcClientSettings({ - client_id: 'client', - ResponseValidatorCtor: function () { return temp } - }); - subject.validator.should.equal(temp); - }); - }); - - describe("metadataServiceCtor", function () { - - it("should return value from initial settings", function () { - - let temp = {}; - let subject = new OidcClientSettings({ - client_id: 'client', - MetadataServiceCtor: function () { return temp } - }); - subject.metadataService.should.equal(temp); - }); - }); - - describe("extraQueryParams", function() { - - it("should use default value", function () { - let subject = new OidcClientSettings({ - client_id: 'client' - }); - subject.extraQueryParams.should.deep.equal({}); - }); - - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - extraQueryParams: { - 'hd': 'domain.com' - } - }); - subject.extraQueryParams.should.deep.equal({ 'hd': 'domain.com' }); - }); - - it("should not set value from initial settings if not object, but set default value ({})", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - extraQueryParams: 123456 - }); - subject.extraQueryParams.should.deep.equal({}); - }); - - it("should set it if object", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - }); - subject.extraQueryParams = { 'hd': 'domain.com' }; - subject.extraQueryParams.should.deep.equal({ 'hd': 'domain.com' }); - }); - - it("should clear it if not object", function() { - let subject = new OidcClientSettings({ - client_id: 'client', - extraQueryParams: { - 'hd': 'domain.com', - } - }); - subject.extraQueryParams = undefined; - subject.extraQueryParams.should.deep.equal({}); - }); - }) - - describe("extraTokenParams", function() { - - it("should use default value", function () { - let subject = new OidcClientSettings({ - client_id: 'client' - }); - subject.extraTokenParams.should.deep.equal({}); - }); - - it("should return value from initial settings", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - extraTokenParams: { - 'resourceServer': 'abc' - } - }); - subject.extraTokenParams.should.deep.equal({ 'resourceServer': 'abc' }); - }); - - it("should not set value from initial settings if not object, but set default value ({})", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - extraTokenParams: 123456 - }); - subject.extraTokenParams.should.deep.equal({}); - }); - - it("should set it if object", function () { - let subject = new OidcClientSettings({ - client_id: 'client', - }); - subject.extraTokenParams = { 'resourceServer': 'abc' }; - subject.extraTokenParams.should.deep.equal({ 'resourceServer': 'abc' }); - }); - - it("should clear it if not object", function() { - let subject = new OidcClientSettings({ - client_id: 'client', - extraTokenParams: { - 'resourceServer': 'abc', - } - }); - subject.extraTokenParams = undefined; - subject.extraTokenParams.should.deep.equal({}); - }); - }) - -}); diff --git a/test/unit/OidcClientSettings.test.ts b/test/unit/OidcClientSettings.test.ts new file mode 100644 index 000000000..b9515cf47 --- /dev/null +++ b/test/unit/OidcClientSettings.test.ts @@ -0,0 +1,681 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +import { Log } from '../../src/Log'; +import { MetadataService } from '../../src/MetadataService'; +import { OidcClientSettings } from '../../src/OidcClientSettings'; +import { ResponseValidator } from '../../src/ResponseValidator'; +import { StateStore } from '../../src/StateStore'; +import { mocked } from 'ts-jest/utils'; + +// workaround jest parse error +jest.mock('../../jsrsasign/dist/jsrsasign.js', () => { + return { + jws: jest.fn(), + KEYUTIL: jest.fn(), + X509: jest.fn(), + crypto: jest.fn(), + hextob64u: jest.fn(), + b64tohex: jest.fn() + }; +}); + +describe("OidcClientSettings", () => { + + beforeEach(() => { + Log.logger = console; + Log.level = Log.NONE; + }); + + describe("client_id", () => { + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client' + }); + + // assert + expect(subject.client_id).toEqual("client"); + }); + + it("should not allow setting if previously set", () => { + // arrange + let subject = new OidcClientSettings({ + client_id: 'client', + authority: "http://sts" + }); + + // act + try { + subject.client_id = "diff"; + fail("should not come here"); + } + catch (e) { + expect(e.message).toContain("client_id"); + } + }); + + it("should allow setting if not previously set", () => { + // arrange + let subject = new OidcClientSettings({ + }); + + // act + subject.client_id = "test"; + + // assert + expect(subject.client_id).toEqual("test"); + }); + }); + + describe("client_secret", () => { + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_secret: 'secret' + }); + + // assert + expect(subject.client_secret).toEqual("secret"); + }); + }); + + describe("response_type", () => { + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + response_type: "foo" + }); + + // assert + expect(subject.response_type).toEqual("foo"); + }); + + it("should use default value", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client' + }); + + // assert + expect(subject.response_type).toEqual("id_token"); + }); + + }); + + describe("scope", () => { + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + scope: "foo" + }); + + // assert + expect(subject.scope).toEqual("foo"); + }); + + it("should use default value", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client' + }); + + // assert + expect(subject.scope).toEqual("openid"); + }); + + }); + + describe("redirect_uri", () => { + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + redirect_uri: "foo" + }); + + // assert + expect(subject.redirect_uri).toEqual("foo"); + }); + + }); + + describe("post_logout_redirect_uri", () => { + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + post_logout_redirect_uri: "http://app/loggedout" + }); + + // assert + expect(subject.post_logout_redirect_uri).toEqual("http://app/loggedout"); + }); + }); + + describe("prompt", () => { + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + prompt: "foo" + }); + + // assert + expect(subject.prompt).toEqual("foo"); + }); + }); + + describe("display", () => { + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + display: "foo" + }); + + // assert + expect(subject.display).toEqual("foo"); + }); + }); + + describe("max_age", () => { + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + max_age: 22 + }); + + // assert + expect(subject.max_age).toEqual(22); + }); + }); + + describe("ui_locales", () => { + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + ui_locales: "foo" + }); + + // assert + expect(subject.ui_locales).toEqual("foo"); + }); + }); + + describe("acr_values", () => { + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + acr_values: "foo" + }); + + // assert + expect(subject.acr_values).toEqual("foo"); + }); + }); + + describe("resource", () => { + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + resource: "foo" + }); + + // assert + expect(subject.resource).toEqual("foo"); + }); + }); + + describe("response_mode", () => { + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + response_mode: "foo" + }); + + // assert + expect(subject.response_mode).toEqual("foo"); + }); + }); + + describe("authority", () => { + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + authority: "http://sts" + }); + + // assert + expect(subject.authority).toEqual("http://sts"); + }); + + it("should not allow setting if previously set", () => { + // arrange + let subject = new OidcClientSettings({ + client_id: 'client', + authority: "http://sts" + }); + + // act + try { + subject.authority = "http://sts2"; + fail("should not come here"); + } + catch (e) { + expect(e.message).toContain("authority"); + } + }); + + it("should allow setting if not previously set", () => { + // arrange + let subject = new OidcClientSettings({ + client_id: 'client' + }); + + // act + subject.authority = "http://sts"; + + // assert + expect(subject.authority).toEqual("http://sts"); + }); + }); + + describe("metadataUrl", () => { + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + metadataUrl: "http://sts/metadata" + }); + + // assert + expect(subject.metadataUrl).toEqual("http://sts/metadata"); + }); + + it("should infer value from authority", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + authority: "http://sts" + }); + + // assert + expect(subject.metadataUrl).toEqual("http://sts/.well-known/openid-configuration"); + }); + }); + + describe("metadata", () => { + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + metadata: { issuer: "test" } + }); + + // assert + expect(subject.metadata).toEqual({ issuer: "test" }); + }); + + it("should store value", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + }); + subject.metadata = { issuer: "test" }; + + // assert + expect(subject.metadata).toEqual({ issuer: "test" }); + }); + + }); + + describe("signingKeys", () => { + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + signingKeys: ["test"] + }); + + // assert + expect(subject.signingKeys).toEqual(["test"]); + }); + + it("should store value", () => { + // arrange + let subject = new OidcClientSettings({ + client_id: 'client', + }); + + // act + subject.signingKeys = ["test"]; + + // assert + expect(subject.signingKeys).toEqual(["test"]); + }); + + }); + + describe("filterProtocolClaims", () => { + + it("should use default value", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + }); + + // assert + expect(subject.filterProtocolClaims).toEqual(true); + }); + + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + filterProtocolClaims: true + }); + + // assert + expect(subject.filterProtocolClaims).toEqual(true); + + // act + subject = new OidcClientSettings({ + client_id: 'client', + filterProtocolClaims: false + }); + + // assert + expect(subject.filterProtocolClaims).toEqual(false); + }); + }); + + describe("loadUserInfo", () => { + + it("should use default value", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + }); + + // assert + expect(subject.loadUserInfo).toEqual(true); + }); + + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + loadUserInfo: true + }); + + // assert + expect(subject.loadUserInfo).toEqual(true); + + // act + subject = new OidcClientSettings({ + client_id: 'client', + loadUserInfo: false + }); + + // assert + expect(subject.loadUserInfo).toEqual(false); + }); + }); + + describe("staleStateAge", () => { + + it("should use default value", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + }); + + // assert + expect(subject.staleStateAge).toEqual(900); + }); + + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + staleStateAge: 100 + }); + + // assert + expect(subject.staleStateAge).toEqual(100); + }); + }); + + describe("clockSkew", () => { + + it("should use default value", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client' + }); + + // assert + expect(subject.clockSkew).toEqual(5 * 60); // 5 mins + }); + + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + clockSkew: 10 + }); + + // assert + expect(subject.clockSkew).toEqual(10); + }); + }); + + describe("stateStore", () => { + + it("should return value from initial settings", () => { + // arrange + let temp = {} as StateStore; + + // act + let subject = new OidcClientSettings({ + client_id: 'client', + stateStore: temp + }); + + // assert + expect(subject.stateStore).toEqual(temp); + }); + }); + + describe("stateStore", () => { + + it("should return value from initial settings", () => { + // arrange + let temp = {} as StateStore; + + // act + let subject = new OidcClientSettings({ + client_id: 'client', + stateStore: temp + }); + + // assert + expect(subject.stateStore).toEqual(temp); + }); + }); + + describe("validator", () => { + + it("should return value from initial settings", () => { + // arrange + const dummy = new OidcClientSettings(); + const mock = mocked(new ResponseValidator(dummy)); + const settings: any = { + client_id: 'client', + ResponseValidatorCtor: () => mock + }; + + // act + let subject = new OidcClientSettings(settings); + + // assert + expect(subject.validator).toEqual(mock); + }); + }); + + describe("metadataServiceCtor", () => { + + it("should return value from initial settings", () => { + // arrange + const dummy = new OidcClientSettings(); + const mock = mocked(new MetadataService(dummy)); + const settings: any = { + client_id: 'client', + MetadataServiceCtor: () => mock + }; + + // act + let subject = new OidcClientSettings(settings); + + // assert + expect(subject.metadataService).toEqual(mock); + }); + }); + + describe("extraQueryParams", () => { + + it("should use default value", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client' + }); + + // assert + expect(subject.extraQueryParams).toEqual({}); + }); + + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + extraQueryParams: { + 'hd': 'domain.com' + } + }); + + // assert + expect(subject.extraQueryParams).toEqual({ 'hd': 'domain.com' }); + }); + + it("should not set value from initial settings if not object, but set default value ({})", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + extraQueryParams: 123456 as unknown as Record + }); + + // assert + expect(subject.extraQueryParams).toEqual({}); + }); + + it("should set it if object", () => { + // arrange + let subject = new OidcClientSettings({ + client_id: 'client', + }); + + // act + subject.extraQueryParams = { 'hd': 'domain.com' }; + + // assert + expect(subject.extraQueryParams).toEqual({ 'hd': 'domain.com' }); + }); + + it("should clear it if not object", () => { + // arrange + let subject = new OidcClientSettings({ + client_id: 'client', + extraQueryParams: { + 'hd': 'domain.com', + } + }); + + // act + subject.extraQueryParams = undefined; + + // assert + expect(subject.extraQueryParams).toEqual({}); + }); + }) + + describe("extraTokenParams", () => { + + it("should use default value", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client' + }); + + // assert + expect(subject.extraTokenParams).toEqual({}); + }); + + it("should return value from initial settings", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + extraTokenParams: { + 'resourceServer': 'abc' + } + }); + + // assert + expect(subject.extraTokenParams).toEqual({ 'resourceServer': 'abc' }); + }); + + it("should not set value from initial settings if not object, but set default value ({})", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + extraTokenParams: 123456 as unknown as Record + }); + + // assert + expect(subject.extraTokenParams).toEqual({}); + }); + + it("should set it if object", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + }); + subject.extraTokenParams = { 'resourceServer': 'abc' }; + + // assert + expect(subject.extraTokenParams).toEqual({ 'resourceServer': 'abc' }); + }); + + it("should clear it if not object", () => { + // act + let subject = new OidcClientSettings({ + client_id: 'client', + extraTokenParams: { + 'resourceServer': 'abc', + } + }); + subject.extraTokenParams = undefined; + + // assert + expect(subject.extraTokenParams).toEqual({}); + }); + }); +}); diff --git a/test/unit/SigninState.spec.js b/test/unit/SigninState.spec.js deleted file mode 100644 index ed2583bfa..000000000 --- a/test/unit/SigninState.spec.js +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -import { Log } from '../../src/Log'; -import { SigninState } from '../../src/SigninState'; - -import chai from 'chai'; -chai.should(); -let assert = chai.assert; - -describe("SigninState", function() { - - beforeEach(function(){ - Log.level = Log.NONE; - Log.logger = console; - }); - - describe("constructor", function() { - - it("should call base ctor", function() { - var subject = new SigninState({ id: 5, created:6, data:7 }); - subject.id.should.be.equal(5); - subject.created.should.be.equal(6); - subject.data.should.be.equal(7); - }); - - it("should accept nonce", function() { - var subject = new SigninState({ nonce: 5 }); - subject.nonce.should.be.equal(5); - }); - - it("should generate nonce", function() { - var subject = new SigninState({ nonce: true }); - subject.nonce.should.be.ok; - }); - - it("should accept redirect_uri", function() { - var subject = new SigninState({ redirect_uri: "http://cb" }); - subject.redirect_uri.should.be.equal("http://cb"); - }); - - it("should accept code_verifier", function() { - var subject = new SigninState({ code_verifier: 5 }); - subject.code_verifier.should.be.equal(5); - }); - - it("should generate code_verifier", function() { - var subject = new SigninState({ code_verifier: true }); - subject.code_verifier.should.be.ok; - }); - - it("should generate code_challenge", function() { - var subject = new SigninState({ code_verifier: true }); - subject.code_challenge.should.be.ok; - }); - - it("should accept client_id", function() { - var subject = new SigninState({ client_id: "client" }); - subject.client_id.should.be.equal("client"); - }); - - it("should accept authority", function() { - var subject = new SigninState({ authority: "test" }); - subject.authority.should.be.equal("test"); - }); - - it("should accept request_type", function() { - var subject = new SigninState({ request_type: 'xoxo' }); - subject.request_type.should.be.equal('xoxo'); - }); - - it("should accept extraTokenParams", function() { - var subject = new SigninState({ extraTokenParams: { 'resourceServer' : 'abc' } }); - assert.deepEqual(subject.extraTokenParams, { 'resourceServer' : 'abc' }); - }); - }); - - it("can serialize and then deserialize", function() { - var subject1 = new SigninState({ nonce: true, data: { foo: "test" }, created: 1000, client_id:"client", authority:"authority", redirect_uri:"http://cb", code_verifier:true, request_type:'type' }); - - var storage = subject1.toStorageString(); - var subject2 = SigninState.fromStorageString(storage); - - subject2.should.be.deep.equal(subject1); - }); - -}); diff --git a/test/unit/SigninState.test.ts b/test/unit/SigninState.test.ts new file mode 100644 index 000000000..17fb088b4 --- /dev/null +++ b/test/unit/SigninState.test.ts @@ -0,0 +1,141 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +import { Log } from '../../src/Log'; +import { SigninState } from '../../src/SigninState'; + +// workaround jest parse error +jest.mock('../../jsrsasign/dist/jsrsasign.js', () => { + return { + jws: jest.fn(), + KEYUTIL: jest.fn(), + X509: jest.fn(), + crypto: jest.fn(), + hextob64u: jest.fn(), + b64tohex: jest.fn() + }; +}); + +describe("SigninState", () => { + + beforeEach(() =>{ + Log.level = Log.NONE; + Log.logger = console; + }); + + describe("constructor", () => { + + it("should call base ctor", () => { + // act + var subject = new SigninState({ id: 5, created:6, data:7 }); + + // assert + expect(subject.id).toEqual(5); + expect(subject.created).toEqual(6); + expect(subject.data).toEqual(7); + }); + + it("should accept nonce", () => { + // act + var subject = new SigninState({ nonce: 5 }); + + // assert + expect(subject.nonce).toEqual(5); + }); + + it("should generate nonce", () => { + // act + var subject = new SigninState({ nonce: true }); + + // assert + expect(subject.nonce).toBeDefined(); + }); + + it("should accept redirect_uri", () => { + // act + var subject = new SigninState({ redirect_uri: "http://cb" }); + + // assert + expect(subject.redirect_uri).toEqual("http://cb"); + }); + + it("should accept code_verifier", () => { + // act + var subject = new SigninState({ code_verifier: 5 }); + + // assert + expect(subject.code_verifier).toEqual(5); + }); + + it("should generate code_verifier", () => { + // act + var subject = new SigninState({ code_verifier: true }); + + // assert + expect(subject.code_verifier).toBeDefined(); + }); + + it("should generate code_challenge", () => { + // act + var subject = new SigninState({ code_verifier: true }); + + // assert + expect(subject.code_challenge).toBeDefined(); + }); + + it("should accept client_id", () => { + // act + var subject = new SigninState({ client_id: "client" }); + + // assert + expect(subject.client_id).toEqual("client"); + }); + + it("should accept authority", () => { + // act + var subject = new SigninState({ authority: "test" }); + + // assert + expect(subject.authority).toEqual("test"); + }); + + it("should accept request_type", () => { + // act + var subject = new SigninState({ request_type: 'xoxo' }); + + // assert + expect(subject.request_type).toEqual('xoxo'); + }); + + it("should accept extraTokenParams", () => { + // act + var subject = new SigninState({ + extraTokenParams: { 'resourceServer' : 'abc' } + }); + + // assert + expect(subject.extraTokenParams).toEqual({ 'resourceServer' : 'abc' }); + }); + }); + + it("can serialize and then deserialize", () => { + // arrange + var subject1 = new SigninState({ + nonce: true, + data: { foo: "test" }, + created: 1000, + client_id: "client", + authority: "authority", + redirect_uri: "http://cb", + code_verifier: true, + request_type: 'type' + }); + + // act + var storage = subject1.toStorageString(); + var subject2 = SigninState.fromStorageString(storage); + + // assert + expect(subject2).toEqual(subject1); + }); +}); diff --git a/test/unit/State.spec.js b/test/unit/State.test.ts similarity index 59% rename from test/unit/State.spec.js rename to test/unit/State.test.ts index d87b6fb30..a4a2b675b 100644 --- a/test/unit/State.spec.js +++ b/test/unit/State.test.ts @@ -7,74 +7,98 @@ import { State } from '../../src/State'; import { InMemoryWebStorage } from '../../src/InMemoryWebStorage'; import { WebStorageStateStore } from '../../src/WebStorageStateStore'; -import chai from 'chai'; -chai.should(); -let assert = chai.assert; +describe("State", () => { -describe("State", function() { - - beforeEach(function(){ + beforeEach(() =>{ Log.level = Log.NONE; Log.logger = console; }); - describe("constructor", function() { + describe("constructor", () => { - it("should generate id", function() { + it("should generate id", () => { + // act var subject = new State(); - subject.id.should.be.ok; + + // assert + expect(subject.id).toBeDefined(); }); - it("should accept id", function() { + it("should accept id", () => { + // act var subject = new State({ id: 5 }); - subject.id.should.be.equal(5); + + // assert + expect(subject.id).toEqual(5); }); - it("should accept data", function() { + it("should accept data", () => { + // act var subject = new State({ data: "test" }); - subject.data.should.be.equal("test"); + + // assert + expect(subject.data).toEqual("test"); }); - it("should accept data as objects", function() { + it("should accept data as objects", () => { + // act var subject = new State({ data: { foo: "test" } }); - subject.data.should.be.deep.equal({ foo: "test" }); + + // assert + expect(subject.data).toEqual({ foo: "test" }); }); - it("should accept created", function() { + it("should accept created", () => { + // act var subject = new State({ created: 1000 }); - subject.created.should.be.equal(1000); + + // assert + expect(subject.created).toEqual(1000); }); - it("should use date.now for created", function() { + it("should use date.now for created", () => { + // arrange var oldNow = Date.now; - Date.now = function() { + Date.now = () => { return 123 * 1000; // ms }; + + // act var subject = new State(); - subject.created.should.be.equal(123); + + // assert + expect(subject.created).toEqual(123); Date.now = oldNow; }); - it("should accept request_type", function() { + + it("should accept request_type", () => { + // act var subject = new State({ request_type: 'xoxo' }); - subject.request_type.should.be.equal('xoxo'); + + // assert + expect(subject.request_type).toEqual('xoxo'); }); }); - it("can serialize and then deserialize", function() { + it("can serialize and then deserialize", () => { + // arrange var subject1 = new State({ data: { foo: "test" }, created: 1000, request_type:'type' }); + // act var storage = subject1.toStorageString(); var subject2 = State.fromStorageString(storage); - subject2.should.be.deep.equal(subject1); - }); - describe("clearStaleState", function() { + // assert + expect(subject2).toEqual(subject1); + }); - it("should remove old state entries", function(done) { + describe("clearStaleState", () => { + it("should remove old state entries", async () => { + // arrange let oldNow = Date.now; - Date.now = function() { + Date.now = () => { return 200 * 1000; // ms }; @@ -100,20 +124,14 @@ describe("State", function() { inMemStore.setItem(prefix + s5.id, s5.toStorageString()); inMemStore.setItem("junk5", "junk"); - State.clearStaleState(store, 100).then(() => { - Log.debug("clearStaleState done"); - - inMemStore.length.should.equal(8); - inMemStore.getItem(prefix + "s4").should.be.ok; - inMemStore.getItem(prefix + "s5").should.be.ok; - - Date.now = oldNow; - done(); - }); + // act + await State.clearStaleState(store, 100); + // assert + expect(inMemStore.length).toEqual(8); + expect(inMemStore.getItem(prefix + "s4")).toBeDefined(); + expect(inMemStore.getItem(prefix + "s5")).toBeDefined(); + Date.now = oldNow; }); - }); - - }); diff --git a/test/unit/StubJsonService.js b/test/unit/StubJsonService.ts similarity index 81% rename from test/unit/StubJsonService.js rename to test/unit/StubJsonService.ts index 51e79a1d3..8eda70dbb 100644 --- a/test/unit/StubJsonService.js +++ b/test/unit/StubJsonService.ts @@ -4,7 +4,11 @@ import { Log } from '../../src/Log'; export class StubJsonService { - getJson(url, token) { + url: any; + token: any; + result: any; + + getJson(url: string, token?: string) { Log.debug("StubJsonService.getJson", this.result); this.url = url; diff --git a/test/unit/StubMetadataService.js b/test/unit/StubMetadataService.ts similarity index 53% rename from test/unit/StubMetadataService.js rename to test/unit/StubMetadataService.ts index 2d2e90b1b..9252a27a1 100644 --- a/test/unit/StubMetadataService.js +++ b/test/unit/StubMetadataService.ts @@ -1,24 +1,31 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -export class StubMetadataService{ - resetSigningKeys(){ } - getMetadata(){ +export class StubMetadataService { + getMetadataResult: any; + getIssuerResult: any; + getAuthorizationEndpointResult: any; + getEndSessionEndpointResult: any; + userInfoEndpointResult: any; + getSigningKeysResult: any; + + resetSigningKeys() { } + getMetadata() { return this.getMetadataResult; } - getIssuer(){ + getIssuer() { return this.getIssuerResult; } - getAuthorizationEndpoint(){ + getAuthorizationEndpoint() { return this.getAuthorizationEndpointResult; } - getEndSessionEndpoint(){ + getEndSessionEndpoint() { return this.getEndSessionEndpointResult; } - getUserInfoEndpoint(){ + getUserInfoEndpoint() { return this.userInfoEndpointResult; } - getSigningKeys(){ + getSigningKeys() { return this.getSigningKeysResult; } } diff --git a/test/unit/StubResponseValidator.js b/test/unit/StubResponseValidator.ts similarity index 50% rename from test/unit/StubResponseValidator.js rename to test/unit/StubResponseValidator.ts index 7e8ad9e9a..32a03e859 100644 --- a/test/unit/StubResponseValidator.js +++ b/test/unit/StubResponseValidator.ts @@ -1,20 +1,27 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. +import { SigninResponse } from "../../src/SigninResponse"; +import { SignoutResponse } from "../../src/SignoutResponse"; +import { SigninState } from "../../src/SigninState"; +import { State } from "../../src/State"; + export class StubResponseValidator { - validateSigninResponse(state, response) { + signinState: any; + signinResponse: any; + + signoutState: any; + signoutResponse: any; + validateSigninResponse(state: SigninState, response: SigninResponse) { this.signinState = state; this.signinResponse = response; - return Promise.resolve(response); } - validateSignoutResponse(state, response) { - + validateSignoutResponse(state: State, response: SignoutResponse) { this.signoutState = state; this.signoutResponse = response; - return Promise.resolve(response); } } diff --git a/test/unit/StubStateStore.js b/test/unit/StubStateStore.ts similarity index 77% rename from test/unit/StubStateStore.js rename to test/unit/StubStateStore.ts index db138a362..aca8b98a7 100644 --- a/test/unit/StubStateStore.js +++ b/test/unit/StubStateStore.ts @@ -2,7 +2,10 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. export class StubStateStore { - set(key, value) { + error: any; + item: any; + + set(_key: string, value: string) { if (this.error) { return Promise.reject(new Error(this.error)); } @@ -10,27 +13,27 @@ export class StubStateStore { return Promise.resolve(); } - get(key) { + get(_key: string) { if (this.error) { return Promise.reject(new Error(this.error)); } return Promise.resolve(this.item); } - remove(key) { + remove(_key: string) { if (this.error) { return Promise.reject(new Error(this.error)); } return Promise.resolve(this.item); } - getAllKeys(){ - if (this.item){ + getAllKeys() { + if (this.item) { return Promise.resolve(["key"]); } - return Promise.resolve([]); + return Promise.resolve([]); } - get length(){ + get length() { return this.item ? 1 : 0; } } diff --git a/test/unit/Timer.spec.js b/test/unit/Timer.spec.js deleted file mode 100644 index 5789ac7aa..000000000 --- a/test/unit/Timer.spec.js +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -import { Timer } from '../../src/Timer'; - -import chai from 'chai'; -chai.should(); -let assert = chai.assert; - -class StubWindowTimer { - - constructor() { - this.clearTimeoutWasCalled = false; - } - - setInterval(cb, duration) { - this.callback = cb; - this.duration = duration; - return 5; - } - - clearInterval() { - this.clearTimeoutWasCalled = true; - } -} - -describe("Timer", function () { - - let subject; - let stubWindowTimer; - let now = Date.now() / 1000; - - beforeEach(function () { - stubWindowTimer = new StubWindowTimer(); - subject = new Timer("test name", stubWindowTimer, () => now); - }); - - describe("init", function () { - - it("should setup a timer", function () { - subject.init(10); - stubWindowTimer.callback.should.be.ok; - }); - - it("should use 1 second if duration is too low", function () { - subject.init(0); - stubWindowTimer.duration.should.equal(1000); - subject.init(-1); - stubWindowTimer.duration.should.equal(1000); - subject.init(-5); - stubWindowTimer.duration.should.equal(1000); - }); - - it("should use duration if less than default", function () { - subject.init(2); - stubWindowTimer.duration.should.equal(2000); - subject.init(3); - stubWindowTimer.duration.should.equal(3000); - }); - - it("should cancel previous timer if new time is not the same", function () { - subject.init(10); - stubWindowTimer.clearTimeoutWasCalled.should.be.false; - - now = now + 1; - subject.init(10); - - stubWindowTimer.clearTimeoutWasCalled.should.be.true; - }); - - it("should not cancel previous timer if new time is same", function () { - subject.init(10); - stubWindowTimer.clearTimeoutWasCalled.should.be.false; - - subject.init(10); - stubWindowTimer.clearTimeoutWasCalled.should.be.false; - }); - }); - - describe("_callback", function () { - - it("should fire when timer expires", function () { - var cb = function () { - cb.wasCalled = true; - }; - cb.wasCalled = false; - subject.addHandler(cb); - - subject._nowFunc = () => 100; - subject.init(10); - - subject._nowFunc = () => 109; - stubWindowTimer.callback(); - cb.wasCalled.should.be.false; - - subject._nowFunc = () => 110; - stubWindowTimer.callback(); - cb.wasCalled.should.be.true; - }); - - - it("should fire if timer late", function () { - var cb = function () { - cb.wasCalled = true; - }; - cb.wasCalled = false; - subject.addHandler(cb); - - subject._nowFunc = () => 100; - subject.init(10); - - subject._nowFunc = () => 109; - stubWindowTimer.callback(); - cb.wasCalled.should.be.false; - - subject._nowFunc = () => 111; - stubWindowTimer.callback(); - cb.wasCalled.should.be.true; - }); - - it("should cancel window timer", function () { - subject._nowFunc = () => 100; - subject.init(10); - - subject._nowFunc = () => 110; - stubWindowTimer.callback(); - - stubWindowTimer.clearTimeoutWasCalled.should.be.true; - }); - }); - - describe("cancel", function () { - - it("should cancel timer", function () { - subject.init(10); - stubWindowTimer.clearTimeoutWasCalled.should.be.false; - - subject.cancel(); - - stubWindowTimer.clearTimeoutWasCalled.should.be.true; - }); - - it("should do nothing if no existing timer", function () { - subject.cancel(); - - stubWindowTimer.clearTimeoutWasCalled.should.be.false; - }); - }); - - describe("addHandler", function () { - - it("should allow callback to be invoked", function () { - var cb = function () { - cb.wasCalled = true; - }; - subject.addHandler(cb); - - subject._nowFunc = () => 100; - subject.init(10); - subject._nowFunc = () => 110; - stubWindowTimer.callback(); - - cb.wasCalled.should.be.true; - }); - - it("should allow multiple callbacks", function () { - var count = 0; - var cb = function () { - count++; - }; - subject.addHandler(cb); - subject.addHandler(cb); - subject.addHandler(cb); - subject.addHandler(cb); - - subject._nowFunc = () => 100; - subject.init(10); - subject._nowFunc = () => 110; - stubWindowTimer.callback(); - - count.should.equal(4); - }); - - }); - - describe("removeHandler", function () { - - it("should remove callback from being invoked", function () { - var cb = function () { - cb.wasCalled = true; - }; - cb.wasCalled = false; - - subject._nowFunc = () => 100; - subject.addHandler(cb); - subject.init(10); - subject.removeHandler(cb); - - subject._nowFunc = () => 110; - stubWindowTimer.callback(); - - cb.wasCalled.should.be.false; - }); - - it("should remove individual callback", function () { - var count = 0; - var cb1 = function () { - count++; - }; - var cb2 = function () { - cb2.wasCalled = true; - }; - - subject.addHandler(cb1); - subject.addHandler(cb2); - subject.addHandler(cb1); - - subject._nowFunc = () => 100; - subject.init(10); - subject.removeHandler(cb1); - subject.removeHandler(cb1); - - subject._nowFunc = () => 110; - stubWindowTimer.callback(); - - count.should.equal(0); - cb2.wasCalled.should.be.true; - }); - - }); - -}); diff --git a/test/unit/Timer.test.ts b/test/unit/Timer.test.ts new file mode 100644 index 000000000..ecffdf35b --- /dev/null +++ b/test/unit/Timer.test.ts @@ -0,0 +1,285 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +import { IntervalTimer, Timer } from '../../src/Timer'; + +class IntervalTimerMock implements IntervalTimer { + callback: ((...args: any[]) => void); + duration?: number; + + clearTimeoutWasCalled: boolean; + clearHandle: number | null; + + constructor() { + this.callback = () => { fail("should not come here"); }; + this.duration = undefined; + + this.clearTimeoutWasCalled = false; + this.clearHandle = null; + } + + setInterval(cb: (...args: any[]) => void, duration?: number) { + this.callback = cb; + this.duration = duration; + return 5; + } + + clearInterval(handle: number) { + this.clearTimeoutWasCalled = true; + this.clearHandle = handle; + } +} + +describe("Timer", () => { + + let subject: Timer; + let intervalTimerMock: IntervalTimerMock; + let now = Date.now() / 1000; + + beforeEach(() => { + intervalTimerMock = new IntervalTimerMock(); + subject = new Timer("test name", intervalTimerMock, () => now); + }); + + describe("init", () => { + + it("should setup a timer", () => { + // act + subject.init(10); + + // assert + expect(intervalTimerMock.callback).not.toBeNull(); + }); + + it("should use 1 second if duration is too low", () => { + // act + subject.init(0); + + // assert + expect(intervalTimerMock.duration).toEqual(1000); + + // act + subject.init(-1); + + // assert + expect(intervalTimerMock.duration).toEqual(1000); + + // act + subject.init(-5); + + // assert + expect(intervalTimerMock.duration).toEqual(1000); + }); + + it("should use duration if less than default", () => { + // act + subject.init(2); + + // assert + expect(intervalTimerMock.duration).toEqual(2000); + + // act + subject.init(3); + + // assert + expect(intervalTimerMock.duration).toEqual(3000); + }); + + it("should cancel previous timer if new time is not the same", () => { + // act + subject.init(10); + + // assert + expect(intervalTimerMock.clearTimeoutWasCalled).toEqual(false); + + // act + now = now + 1; + subject.init(10); + + // assert + expect(intervalTimerMock.clearTimeoutWasCalled).toEqual(true); + }); + + it("should not cancel previous timer if new time is same", () => { + // act + subject.init(10); + + // assert + expect(intervalTimerMock.clearTimeoutWasCalled).toEqual(false); + + // act + subject.init(10); + + // assert + expect(intervalTimerMock.clearTimeoutWasCalled).toEqual(false); + }); + }); + + describe("_callback", () => { + + it("should fire when timer expires", () => { + // arrange + var cb = jest.fn(); + subject.addHandler(cb); + + now = 100; + subject.init(10); + + // assert + expect(intervalTimerMock.callback).not.toBeNull(); + + // act + now = 109; + intervalTimerMock.callback(); + + // assert + expect(cb).toBeCalledTimes(0); + + // act + now = 110; + intervalTimerMock.callback(); + + // assert + expect(cb).toBeCalledTimes(1); + }); + + it("should fire if timer late", () => { + // arrange + var cb = jest.fn(); + subject.addHandler(cb); + + now = 100; + subject.init(10); + + // assert + expect(intervalTimerMock.callback).not.toBeNull(); + + now = 109; + intervalTimerMock.callback(); + + // assert + expect(cb).toBeCalledTimes(0); + + now = 111; + intervalTimerMock.callback(); + + // assert + expect(cb).toBeCalledTimes(1); + }); + + it("should cancel window timer", () => { + now = 100; + subject.init(10); + + // assert + expect(intervalTimerMock.callback).not.toBeNull(); + + now = 110; + intervalTimerMock.callback(); + + // assert + expect(intervalTimerMock.clearTimeoutWasCalled).toEqual(true); + }); + }); + + describe("cancel", () => { + + it("should cancel timer", () => { + // act + subject.init(10); + + // assert + expect(intervalTimerMock.clearTimeoutWasCalled).toEqual(false); + + // act + subject.cancel(); + + // assert + expect(intervalTimerMock.clearTimeoutWasCalled).toEqual(true); + }); + + it("should do nothing if no existing timer", () => { + // act + subject.cancel(); + + // assert + expect(intervalTimerMock.clearTimeoutWasCalled).toEqual(false); + }); + }); + + describe("addHandler", () => { + + it("should allow callback to be invoked", () => { + // arrange + var cb = jest.fn(); + + // act + subject.addHandler(cb); + now = 100; + subject.init(10); + now = 110; + intervalTimerMock.callback(); + + // assert + expect(cb).toBeCalled(); + }); + + it("should allow multiple callbacks", () => { + // arrange + var cb = jest.fn(); + + // act + subject.addHandler(cb); + subject.addHandler(cb); + subject.addHandler(cb); + subject.addHandler(cb); + now = 100; + subject.init(10); + now = 110; + intervalTimerMock.callback(); + + // assert + expect(cb).toBeCalledTimes(4); + }); + }); + + describe("removeHandler", () => { + + it("should remove callback from being invoked", () => { + // arrange + var cb = jest.fn(); + now = 100; + subject.addHandler(cb); + subject.init(10); + + // act + subject.removeHandler(cb); + now = 110; + intervalTimerMock.callback(); + + // assert + expect(cb).toBeCalledTimes(0); + }); + + it("should remove individual callback", () => { + // arrange + var cb1 = jest.fn(); + var cb2 = jest.fn(); + subject.addHandler(cb1); + subject.addHandler(cb2); + subject.addHandler(cb1); + + // act + now = 100; + subject.init(10); + subject.removeHandler(cb1); + subject.removeHandler(cb1); + now = 110; + intervalTimerMock.callback(); + + // assert + expect(cb1).toBeCalledTimes(0); + expect(cb2).toBeCalledTimes(1); + }); + }); +}); diff --git a/test/unit/UrlUtility.spec.js b/test/unit/UrlUtility.spec.js deleted file mode 100644 index bc9437672..000000000 --- a/test/unit/UrlUtility.spec.js +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -import { UrlUtility } from '../../src/UrlUtility'; - -import chai from 'chai'; -chai.should(); -let assert = chai.assert; - -describe("UrlUtility", function() { - - describe("addQueryParam", function() { - - it("should add ? if not present", function() { - UrlUtility.addQueryParam("url", "foo", "test").should.equal("url?foo=test"); - }); - - it("should not add ? if already present", function() { - UrlUtility.addQueryParam("url?", "foo", "test").should.equal("url?foo=test"); - }); - - it("should add & if needed", function() { - UrlUtility.addQueryParam("url?x=1", "foo", "test").should.equal("url?x=1&foo=test"); - }); - - it("should urlencode key and value", function() { - UrlUtility.addQueryParam("url", "#", "#").should.equal("url?%23=%23"); - }); - }); - - describe("parseUrlFragment", function() { - - it("should parse key/value pairs", function() { - let result = UrlUtility.parseUrlFragment("a=apple&b=banana&c=carrot"); - result.should.deep.equal({ a: "apple", b: "banana", c: "carrot" }); - }); - - it("should parse any order", function() { - let result = UrlUtility.parseUrlFragment("b=banana&c=carrot&a=apple"); - result.should.deep.equal({ a: "apple", b: "banana", c: "carrot" }); - }); - - it("should parse past host name and hash fragment", function() { - let result = UrlUtility.parseUrlFragment("http://server?test1=xoxo&test2=xoxo/#a=apple&b=banana&c=carrot"); - result.should.deep.equal({ a: "apple", b: "banana", c: "carrot" }); - }); - - it("should parse query string", function() { - let result = UrlUtility.parseUrlFragment("http://server?test1=xoxo&test2=yoyo", "?"); - result.should.deep.equal({ test1: "xoxo", test2: "yoyo" }); - }); - - it("should parse query string up to hash", function() { - let result = UrlUtility.parseUrlFragment("http://server?test1=xoxo&test2=yoyo#a=apple&b=banana&c=carrot", "?"); - result.should.deep.equal({ test1: "xoxo", test2: "yoyo" }); - }); - - it("should return error for long values", function() { - let result = UrlUtility.parseUrlFragment("a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple"); - result.should.have.property('error'); - }); - - it("should use Global.location when no value is passed", function() { - let w = { - location: { - href : "a=apple&b=banana&c=carrot" - } - }; - let result = UrlUtility.parseUrlFragment(null, "#", w); - result.should.deep.equal({ a: "apple", b: "banana", c: "carrot" }); - }); - - it("should return empty object for empty string", function() { - let result = UrlUtility.parseUrlFragment(""); - result.should.deep.equal({}); - }); - }); - -}); diff --git a/test/unit/UrlUtility.test.ts b/test/unit/UrlUtility.test.ts new file mode 100644 index 000000000..d61d3ff39 --- /dev/null +++ b/test/unit/UrlUtility.test.ts @@ -0,0 +1,118 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +import { UrlUtility } from '../../src/UrlUtility'; + +describe("UrlUtility", () => { + + describe("addQueryParam", () => { + + it("should add ? if not present", () => { + // act + let result = UrlUtility.addQueryParam("url", "foo", "test"); + + // assert + expect(result).toEqual("url?foo=test"); + }); + + it("should not add ? if already present", () => { + // act + let result = UrlUtility.addQueryParam("url?", "foo", "test"); + + // assert + expect(result).toEqual("url?foo=test"); + }); + + it("should add & if needed", () => { + // act + let result = UrlUtility.addQueryParam("url?x=1", "foo", "test"); + + // assert + expect(result).toEqual("url?x=1&foo=test"); + }); + + it("should urlencode key and value", () => { + // act + let result = UrlUtility.addQueryParam("url", "#", "#"); + + // assert + expect(result).toEqual("url?%23=%23"); + }); + }); + + describe("parseUrlFragment", () => { + + it("should parse key/value pairs", () => { + // act + let result = UrlUtility.parseUrlFragment("a=apple&b=banana&c=carrot"); + + // assert + expect(result).toEqual({ a: "apple", b: "banana", c: "carrot" }); + }); + + it("should parse any order", () => { + // act + let result = UrlUtility.parseUrlFragment("b=banana&c=carrot&a=apple"); + + // assert + expect(result).toEqual({ a: "apple", b: "banana", c: "carrot" }); + }); + + it("should parse past host name and hash fragment", () => { + // act + let result = UrlUtility.parseUrlFragment("http://server?test1=xoxo&test2=xoxo/#a=apple&b=banana&c=carrot"); + + // assert + expect(result).toEqual({ a: "apple", b: "banana", c: "carrot" }); + }); + + it("should parse query string", () => { + // act + let result = UrlUtility.parseUrlFragment("http://server?test1=xoxo&test2=yoyo", "?"); + + // assert + expect(result).toEqual({ test1: "xoxo", test2: "yoyo" }); + }); + + it("should parse query string up to hash", () => { + // act + let result = UrlUtility.parseUrlFragment("http://server?test1=xoxo&test2=yoyo#a=apple&b=banana&c=carrot", "?"); + + // assert + expect(result).toEqual({ test1: "xoxo", test2: "yoyo" }); + }); + + it("should return error for long values", () => { + // act + let result = UrlUtility.parseUrlFragment("a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple&a=apple"); + + // assert + expect(result).toHaveProperty("error"); + }); + + /* TODO: port-ts + it("should use Global.location when no value is passed", () => { + // arrange + let w = { + location: { + href : "a=apple&b=banana&c=carrot" + } + }; + + // act + let result = UrlUtility.parseUrlFragment(null, "#", w); + + // assert + expect(result).toEqual({ a: "apple", b: "banana", c: "carrot" }); + }); + */ + + it("should return empty object for empty string", () => { + // act + let result = UrlUtility.parseUrlFragment(""); + + // assert + expect(result).toEqual({}); + }); + }); +}); diff --git a/test/unit/UserInfoService.spec.js b/test/unit/UserInfoService.spec.js deleted file mode 100644 index 3577e596e..000000000 --- a/test/unit/UserInfoService.spec.js +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -import { Log } from '../../src/Log'; -import { UserInfoService } from '../../src/UserInfoService'; - -import { StubJsonService } from './StubJsonService'; -import { StubMetadataService } from './StubMetadataService'; - -import chai from 'chai'; -chai.should(); -let assert = chai.assert; - -describe("UserInfoService", function() { - let subject; - let settings; - let stubJsonService; - let stubMetadataService; - - beforeEach(function() { - settings = {}; - stubJsonService = new StubJsonService(); - stubMetadataService = new StubMetadataService(); - subject = new UserInfoService(settings, () => stubJsonService, () => stubMetadataService); - }); - - describe("constructor", function() { - - it("should require a settings param", function() { - try { - new UserInfoService(); - } - catch (e) { - e.message.should.contain('settings'); - return; - } - assert.fail(); - }); - - }); - - describe("getClaims", function() { - - it("should return a promise", function() { - var p = subject.getClaims(); - p.should.be.instanceof(Promise); - p.catch(e=>{}); - }); - - it("should require a token", function(done) { - subject.getClaims().catch(err => { - err.message.should.contain("token"); - done(); - }); - }); - - it("should call userinfo endpoint and pass token", function(done) { - stubMetadataService.userInfoEndpointResult = Promise.resolve("http://sts/userinfo"); - stubJsonService.result = Promise.resolve("test"); - - subject.getClaims("token").then(claims => { - stubJsonService.url.should.equal("http://sts/userinfo"); - stubJsonService.token.should.equal("token"); - done(); - }); - - }); - - it("should fail when dependencies fail", function(done) { - stubMetadataService.userInfoEndpointResult = Promise.reject(new Error("test")); - - subject.getClaims("token").then(null, - err => { - err.message.should.contain('test'); - done(); - } - ); - - }); - - it("should return claims", function(done) { - stubMetadataService.userInfoEndpointResult = Promise.resolve("http://sts/userinfo"); - stubJsonService.result = Promise.resolve({ - foo: 1, bar: 'test', - aud:'some_aud', iss:'issuer', - sub:'123', email:'foo@gmail.com', - role:['admin', 'dev'], - nonce:'nonce', at_hash:"athash", - iat:5, nbf:10, exp:20 - }); - - subject.getClaims("token").then(claims => { - claims.should.deep.equal({ - foo: 1, bar: 'test', - aud:'some_aud', iss:'issuer', - sub:'123', email:'foo@gmail.com', - role:['admin', 'dev'], - nonce:'nonce', at_hash:"athash", - iat:5, nbf:10, exp:20 - }); - done(); - }); - - }); - }); -}); diff --git a/test/unit/UserInfoService.test.ts b/test/unit/UserInfoService.test.ts new file mode 100644 index 000000000..6d5e72159 --- /dev/null +++ b/test/unit/UserInfoService.test.ts @@ -0,0 +1,117 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +import { UserInfoService } from '../../src/UserInfoService'; + +import { StubJsonService } from './StubJsonService'; +import { StubMetadataService } from './StubMetadataService'; + +// workaround jest parse error +jest.mock('../../jsrsasign/dist/jsrsasign.js', () => { + return { + jws: jest.fn(), + KEYUTIL: jest.fn(), + X509: jest.fn(), + crypto: jest.fn(), + hextob64u: jest.fn(), + b64tohex: jest.fn() + }; +}); + +describe("UserInfoService", () => { + let subject: UserInfoService; + let stubJsonService: any; + let stubMetadataService: any; + + beforeEach(() => { + const settings: any = {}; + stubJsonService = new StubJsonService(); + stubMetadataService = new StubMetadataService(); + // @ts-ignore + subject = new UserInfoService(settings, () => stubJsonService, () => stubMetadataService); + }); + + /* TODO: port-ts + describe("constructor", () => { + + it("should require a settings param", () => { + try { + new UserInfoService(); + } + catch (e) { + e.message.should.contain('settings'); + return; + } + assert.fail(); + }); + }); + */ + + describe("getClaims", () => { + + it("should return a promise", async () => { + // act + var p = subject.getClaims(); + + // assert + expect(p).toBeInstanceOf(Promise); + p.catch(_e => {}); + }); + + it("should require a token", async () => { + // act + try { + await subject.getClaims(); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain("token"); + } + }); + + it("should call userinfo endpoint and pass token", async () => { + // arrange + stubMetadataService.userInfoEndpointResult = Promise.resolve("http://sts/userinfo"); + stubJsonService.result = Promise.resolve("test"); + + // act + await subject.getClaims("token"); + + // assert + expect(stubJsonService.url).toEqual("http://sts/userinfo"); + expect(stubJsonService.token).toEqual("token"); + }); + + it("should fail when dependencies fail", async () => { + // arrange + stubMetadataService.userInfoEndpointResult = Promise.reject(new Error("test")); + + // act + try { + await subject.getClaims("token"); + fail("should not come here"); + } catch (err) { + expect(err.message).toContain("test"); + } + }); + + it("should return claims", async () => { + // arrange + stubMetadataService.userInfoEndpointResult = Promise.resolve("http://sts/userinfo"); + const expectedClaims = { + foo: 1, bar: 'test', + aud:'some_aud', iss:'issuer', + sub:'123', email:'foo@gmail.com', + role:['admin', 'dev'], + nonce:'nonce', at_hash:"athash", + iat:5, nbf:10, exp:20 + } + stubJsonService.result = Promise.resolve(expectedClaims); + + // act + const claims = await subject.getClaims("token"); + + // assert + expect(claims).toEqual(expectedClaims); + }); + }); +}); diff --git a/test/unit/UserManager.spec.js b/test/unit/UserManager.spec.js deleted file mode 100644 index dba949f21..000000000 --- a/test/unit/UserManager.spec.js +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -import { UserManager } from '../../src/UserManager'; -import { Log } from '../../src/Log'; -import { Global } from '../../src/Global'; -import { UserManagerSettings } from '../../src/UserManagerSettings'; -import { User } from '../../src/User'; - -import { StubMetadataService } from './StubMetadataService'; -import { StubSilentRenewService } from './StubSilentRenewService'; -import { StubStateStore } from './StubStateStore'; -import { StubResponseValidator } from './StubResponseValidator'; -import { StubTokenRevocationClient } from './StubTokenRevocationClient'; - -import chai from 'chai'; -chai.should(); -let assert = chai.assert; - -describe("UserManager", function () { - let settings; - let subject; - let stubMetadataService; - let stubStateStore; - let stubValidator; - let stubSilentRenewService; - let stubNavigator; - let stubUserStore; - let stubTokenRevocationClient; - - beforeEach(function () { - - Global._testing(); - - Log.logger = console; - Log.level = Log.NONE; - - stubNavigator = {}; - stubUserStore = new StubStateStore(); - stubStateStore = new StubStateStore(); - stubValidator = new StubResponseValidator(); - stubSilentRenewService = new StubSilentRenewService(); - stubMetadataService = new StubMetadataService(); - stubTokenRevocationClient = new StubTokenRevocationClient(); - - settings = { - authority: 'http://sts/oidc', - client_id: 'client', - monitorSession : false, - navigator: stubNavigator, - userStore: stubUserStore, - stateStore: stubStateStore, - ResponseValidatorCtor: () => stubValidator, - MetadataServiceCtor: () => stubMetadataService - }; - - subject = new UserManager(settings, - () => stubSilentRenewService, - null, - () => stubTokenRevocationClient); - }); - - describe("constructor", function () { - - it("should accept settings", function () { - subject.settings.client_id.should.equal('client'); - }); - - }); - - describe("settings", function () { - - it("should be UserManagerSettings", function () { - subject.settings.should.be.instanceof(UserManagerSettings); - }); - - }); - - describe("userLoaded", function () { - - it("should be able to call getUser without recursion", function (done) { - - stubUserStore.item = new User({id_token:"id_token"}).toStorageString(); - - subject.events.addUserLoaded(user => { - subject.getUser().then(user => { - done(); - }); - }); - - subject.events.load({}); - }); - - }); - - describe("signinSilent", function(){ - - it("should pass silentRequestTimeout from settings", function(done) { - - stubUserStore.item = new User({id_token:"id_token"}).toStorageString(); - - settings.silentRequestTimeout = 123; - settings.silent_redirect_uri = "http://client/silent_callback"; - subject = new UserManager(settings); - - subject._signin = function(args, nav, navArgs){ - Log.debug("_signin", args, nav, navArgs); - - navArgs.silentRequestTimeout.should.equal(123); - done(); - return Promise.resolve() - } - subject.signinSilent(); - }); - - it("should pass silentRequestTimeout from params", function(done){ - - stubUserStore.item = new User({id_token:"id_token"}).toStorageString(); - - settings.silent_redirect_uri = "http://client/silent_callback"; - subject = new UserManager(settings); - - subject._signin = function(args, nav, navArgs){ - navArgs.silentRequestTimeout.should.equal(234); - done(); - return Promise.resolve() - } - subject.signinSilent({silentRequestTimeout:234}); - }); - - it("should pass prompt from params", function(done){ - - stubUserStore.item = new User({id_token:"id_token"}).toStorageString(); - - settings.silent_redirect_uri = "http://client/silent_callback"; - subject = new UserManager(settings); - - subject._signin = function(args, nav, navArgs){ - args.prompt.should.equal("foo"); - done(); - return Promise.resolve() - } - subject.signinSilent({prompt:"foo"}); - }); - - it("should work when having no User present", function(done) { - settings.silent_redirect_uri = "http://client/silent_callback"; - subject = new UserManager(settings); - - subject._signin = function(){ - done(); - return Promise.resolve() - } - subject.signinSilent({prompt:"foo"}); - }) - }); - -}); diff --git a/test/unit/UserManager.test.ts b/test/unit/UserManager.test.ts new file mode 100644 index 000000000..295405c3b --- /dev/null +++ b/test/unit/UserManager.test.ts @@ -0,0 +1,176 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +import { Log } from '../../src/Log'; +import { UserManager } from '../../src/UserManager'; +import { UserManagerSettings } from '../../src/UserManagerSettings'; +import { User } from '../../src/User'; +import { WebStorageStateStore } from '../../src/WebStorageStateStore'; +import { mocked } from 'ts-jest/utils'; + +// workaround jest parse error +jest.mock('../../jsrsasign/dist/jsrsasign.js', () => { + return { + jws: jest.fn(), + KEYUTIL: jest.fn(), + X509: jest.fn(), + crypto: jest.fn(), + hextob64u: jest.fn(), + b64tohex: jest.fn() + }; +}); + +describe("UserManager", () => { + let settings: UserManagerSettings; + let userStoreMock: any; + let subject: UserManager; + + beforeEach(() => { + Log.logger = console; + Log.level = Log.NONE; + + userStoreMock = mocked(new WebStorageStateStore()) + + settings = { + authority: "http://sts/oidc", + client_id: "client", + monitorSession : false, + userStore: userStoreMock, + } as unknown as UserManagerSettings; + + subject = new UserManager(settings); + }); + + describe("constructor", () => { + + it("should accept settings", () => { + // act + expect(subject.settings.client_id).toEqual("client"); + }); + }); + + describe("settings", () => { + + it("should be UserManagerSettings", () => { + // act + expect(subject.settings).toBeInstanceOf(UserManagerSettings); + }); + }); + + describe("userLoaded", () => { + + it("should be able to call getUser without recursion", async () => { + // arrange + const user = new User({ id_token: "id_token" }); + userStoreMock.item = user.toStorageString(); + + subject.events.addUserLoaded(async (_user) => { + await subject.getUser(); + }); + + // act + subject.events.load({} as User); + }); + }); + + describe("signinSilent", function(){ + + it("should pass silentRequestTimeout from settings", async () => { + // arrange + const user = new User({id_token:"id_token"}); + userStoreMock.item = user.toStorageString(); + + settings = { + ...settings, + silentRequestTimeout: 123, + silent_redirect_uri: "http://client/silent_callback" + } as unknown as UserManagerSettings; + subject = new UserManager(settings); + + let navArgs: any = null; + + // @ts-ignore + subject._signin = function(args: any, nav: any, arg_navArgs: any) { + Log.debug("_signin", args, nav, navArgs); + + navArgs = arg_navArgs; + return Promise.resolve() + } + + // act + await subject.signinSilent(); + + // assert + expect(navArgs.silentRequestTimeout).toEqual(123); + }); + + it("should pass silentRequestTimeout from params", async () =>{ + // arrange + const user = new User({id_token:"id_token"}); + userStoreMock.item = user.toStorageString(); + + settings = { + ...settings, + silent_redirect_uri: "http://client/silent_callback" + } as unknown as UserManagerSettings; + subject = new UserManager(settings); + + let navArgs: any = null; + + // @ts-ignore + subject._signin = function(args: any, nav: any, arg_navArgs: any) { + navArgs = arg_navArgs; + return Promise.resolve() + } + + // act + await subject.signinSilent({ silentRequestTimeout: 234 }); + + // assert + expect(navArgs.silentRequestTimeout).toEqual(234); + }); + + it("should pass prompt from params", async () =>{ + // arrange + const user = new User({id_token:"id_token"}) + userStoreMock.item = user.toStorageString(); + + settings = { + ...settings, + silent_redirect_uri: "http://client/silent_callback" + } as unknown as UserManagerSettings; + subject = new UserManager(settings); + + let args: any = null; + + // @ts-ignore + subject._signin = function(arg_args: any, nav: any, navArgs: any) { + args = arg_args; + return Promise.resolve() + } + + // act + await subject.signinSilent({ prompt:"foo" }); + + // assert + expect(args.prompt).toEqual("foo"); + }); + + it("should work when having no User present", async () => { + // arrange + settings = { + ...settings, + silent_redirect_uri: "http://client/silent_callback" + } as unknown as UserManagerSettings; + subject = new UserManager(settings); + + // @ts-ignore + subject._signin = function(args: any, nav: any, arg_navArgs: any) { + return Promise.resolve() + } + + // act + await subject.signinSilent({ prompt:"foo" }); + }); + }); +}); diff --git a/test/unit/UserManagerEvents.spec.js b/test/unit/UserManagerEvents.spec.js deleted file mode 100644 index 72f0e2e0a..000000000 --- a/test/unit/UserManagerEvents.spec.js +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -import { UserManagerEvents } from '../../src/UserManagerEvents'; - -import chai from 'chai'; -chai.should(); -let assert = chai.assert; - -describe("UserManagerEvents", function () { - - let subject; - - beforeEach(function () { - subject = new UserManagerEvents(); - }); - - describe("silent renew error", function () { - - it("should allow callback", function () { - var cb = function () { - cb.wasCalled = true; - }; - subject.addSilentRenewError(cb); - - subject._raiseSilentRenewError(new Error("boom")); - - cb.wasCalled.should.be.true; - }); - - it("should allow unregistering callback", function () { - var cb = function () { - cb.wasCalled = true; - }; - cb.wasCalled = false; - - subject.addSilentRenewError(cb); - subject.removeSilentRenewError(cb); - - subject._raiseSilentRenewError(new Error("boom")); - - cb.wasCalled.should.be.false; - }); - - it("should pass error to callback", function () { - var cb = function (e) { - e.message.should.equal("boom"); - }; - subject.addSilentRenewError(cb); - - subject._raiseSilentRenewError(new Error("boom")); - }); - - }); - -}); diff --git a/test/unit/UserManagerEvents.test.ts b/test/unit/UserManagerEvents.test.ts new file mode 100644 index 000000000..9ca0a58a0 --- /dev/null +++ b/test/unit/UserManagerEvents.test.ts @@ -0,0 +1,59 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +import { UserManagerEvents } from '../../src/UserManagerEvents'; +import { UserManagerSettings } from '../../src/UserManagerSettings'; + +describe("UserManagerEvents", () => { + + let subject: UserManagerEvents; + + beforeEach(() => { + subject = new UserManagerEvents({} as UserManagerSettings); + }); + + describe("silent renew error", () => { + + it("should allow callback", () => { + // arrange + var cb = jest.fn(); + + // act + subject.addSilentRenewError(cb); + subject._raiseSilentRenewError(new Error("boom")); + + // assert + expect(cb).toBeCalled(); + }); + + it("should allow unregistering callback", () => { + // arrange + var cb = jest.fn(); + + // act + subject.addSilentRenewError(cb); + subject.removeSilentRenewError(cb); + subject._raiseSilentRenewError(new Error("boom")); + + // assert + expect(cb).toBeCalledTimes(0); + }); + + it("should pass error to callback", () => { + // arrange + let e: Error | null = null; + var cb = function (arg_e: Error) { + console.log(arg_e); + e = arg_e; + }; + const expected = new Error("boom"); + + // act + subject.addSilentRenewError(cb); + subject._raiseSilentRenewError(expected); + + // assert + expect(e).toEqual(expected); + }); + }); +}); diff --git a/test/unit/UserManagerSettings.spec.js b/test/unit/UserManagerSettings.test.ts similarity index 50% rename from test/unit/UserManagerSettings.spec.js rename to test/unit/UserManagerSettings.test.ts index 83941a418..a9065565d 100644 --- a/test/unit/UserManagerSettings.spec.js +++ b/test/unit/UserManagerSettings.test.ts @@ -3,10 +3,19 @@ import { Log } from '../../src/Log'; import { UserManagerSettings } from '../../src/UserManagerSettings'; - -import chai from 'chai'; -chai.should(); -let assert = chai.assert; +import { WebStorageStateStore } from '../../src/WebStorageStateStore'; + +// workaround jest parse error +jest.mock('../../jsrsasign/dist/jsrsasign.js', () => { + return { + jws: jest.fn(), + KEYUTIL: jest.fn(), + X509: jest.fn(), + crypto: jest.fn(), + hextob64u: jest.fn(), + b64tohex: jest.fn() + }; +}); describe("UserManagerSettings", function () { @@ -18,12 +27,21 @@ describe("UserManagerSettings", function () { describe("constructor", function () { it("should allow no settings", function () { + // act let subject = new UserManagerSettings(); + + // assert + expect(subject).not.toBeNull(); }); it("should pass settings to base class", function () { - let subject = new UserManagerSettings({ client_id: 'client' }); - subject.client_id.should.equal('client'); + // act + let subject = new UserManagerSettings({ + client_id: 'client' + }); + + // assert + expect(subject.client_id).toEqual('client'); }); }); @@ -31,8 +49,13 @@ describe("UserManagerSettings", function () { describe("popup_redirect_uri", function () { it("should return value from initial settings", function () { - let subject = new UserManagerSettings({ popup_redirect_uri: 'test' }); - subject.popup_redirect_uri.should.equal('test'); + // act + let subject = new UserManagerSettings({ + popup_redirect_uri: 'test' + }); + + // assert + expect(subject.popup_redirect_uri).toEqual('test'); }); }); @@ -40,8 +63,13 @@ describe("UserManagerSettings", function () { describe("popupWindowFeatures", function () { it("should return value from initial settings", function () { - let subject = new UserManagerSettings({ popupWindowFeatures: 'foo' }); - subject.popupWindowFeatures.should.equal('foo'); + // act + let subject = new UserManagerSettings({ + popupWindowFeatures: 'foo' + }); + + // assert + expect(subject.popupWindowFeatures).toEqual('foo'); }); }); @@ -49,8 +77,13 @@ describe("UserManagerSettings", function () { describe("popupWindowTarget", function () { it("should return value from initial settings", function () { - let subject = new UserManagerSettings({ popupWindowTarget: 'foo' }); - subject.popupWindowTarget.should.equal('foo'); + // act + let subject = new UserManagerSettings({ + popupWindowTarget: 'foo' + }); + + // assert + expect(subject.popupWindowTarget).toEqual('foo'); }); }); @@ -58,8 +91,13 @@ describe("UserManagerSettings", function () { describe("silent_redirect_uri", function () { it("should return value from initial settings", function () { - let subject = new UserManagerSettings({ silent_redirect_uri: 'test' }); - subject.silent_redirect_uri.should.equal('test'); + // act + let subject = new UserManagerSettings({ + silent_redirect_uri: 'test' + }); + + // assert + expect(subject.silent_redirect_uri).toEqual('test'); }); }); @@ -67,8 +105,13 @@ describe("UserManagerSettings", function () { describe("silentRequestTimeout", function () { it("should return value from initial settings", function () { - let subject = new UserManagerSettings({ silentRequestTimeout: 123 }); - subject.silentRequestTimeout.should.equal(123); + // act + let subject = new UserManagerSettings({ + silentRequestTimeout: 123 + }); + + // assert + expect(subject.silentRequestTimeout).toEqual(123); }); }); @@ -76,16 +119,22 @@ describe("UserManagerSettings", function () { describe("automaticSilentRenew", function () { it("should return value from initial settings", function () { + // act let subject = new UserManagerSettings({ automaticSilentRenew: true }); - subject.automaticSilentRenew.should.be.true; + + // assert + expect(subject.automaticSilentRenew).toEqual(true); }); it("should use default value", function () { + // act let subject = new UserManagerSettings({ }); - subject.automaticSilentRenew.should.be.false; + + // assert + expect(subject.automaticSilentRenew).toEqual(false); }); }); @@ -93,157 +142,219 @@ describe("UserManagerSettings", function () { describe("validateSubOnSilentRenew", function () { it("should return value from initial settings", function () { + // act let subject = new UserManagerSettings({ validateSubOnSilentRenew: true }); - subject.validateSubOnSilentRenew.should.be.true; + + // assert + expect(subject.validateSubOnSilentRenew).toEqual(true); }); it("should use default value", function () { + // act let subject = new UserManagerSettings({ }); - subject.validateSubOnSilentRenew.should.be.false; + + // assert + expect(subject.validateSubOnSilentRenew).toEqual(false); }); }); describe("includeIdTokenInSilentRenew", function () { it("should return true value from initial settings", function () { + // act let subject = new UserManagerSettings({ includeIdTokenInSilentRenew: true, }); - subject.includeIdTokenInSilentRenew.should.be.true; + + // assert + expect(subject.includeIdTokenInSilentRenew).toEqual(true); }); it("should return false value from initial settings", function () { + // act let subject = new UserManagerSettings({ includeIdTokenInSilentRenew: false, }); - subject.includeIdTokenInSilentRenew.should.be.false; + + // assert + expect(subject.includeIdTokenInSilentRenew).toEqual(false); }); it("should use default value", function () { + // act let subject = new UserManagerSettings({ }); - subject.includeIdTokenInSilentRenew.should.be.true; + + // assert + expect(subject.includeIdTokenInSilentRenew).toEqual(true); }); }); describe("accessTokenExpiringNotificationTime", function () { it("should return value from initial settings", function () { + // act let subject = new UserManagerSettings({ accessTokenExpiringNotificationTime: 10 }); - subject.accessTokenExpiringNotificationTime.should.equal(10); + + // assert + expect(subject.accessTokenExpiringNotificationTime).toEqual(10); }); it("should use default value", function () { + // act let subject = new UserManagerSettings({ }); - subject.accessTokenExpiringNotificationTime.should.equal(60); + + // assert + expect(subject.accessTokenExpiringNotificationTime).toEqual(60); }); }); - describe("redirectNavigator", function() { - it("should return value from initial settings", function() { + describe("redirectNavigator", () => { + it("should return value from initial settings", () => { let temp = {}; + + // act let subject = new UserManagerSettings({ redirectNavigator : temp }); - subject.redirectNavigator.should.equal(temp); + + // assert + expect(subject.redirectNavigator).toEqual(temp); }); }); - describe("popupNavigator", function() { - it("should return value from initial settings", function() { + describe("popupNavigator", () => { + it("should return value from initial settings", () => { let temp = {}; + + // act let subject = new UserManagerSettings({ popupNavigator : temp }); - subject.popupNavigator.should.equal(temp); + + // assert + expect(subject.popupNavigator).toEqual(temp); }); }); - describe("iframeNavigator", function() { - it("should return value from initial settings", function() { + describe("iframeNavigator", () => { + it("should return value from initial settings", () => { let temp = {}; + + // act let subject = new UserManagerSettings({ iframeNavigator : temp }); - subject.iframeNavigator.should.equal(temp); + + // assert + expect(subject.iframeNavigator).toEqual(temp); }); }); - describe("redirectNavigator", function() { - it("should return value from initial settings", function() { - let temp = {}; + describe("redirectNavigator", () => { + it("should return value from initial settings", () => { + let temp = {} as WebStorageStateStore; + + // act let subject = new UserManagerSettings({ userStore : temp }); - subject.userStore.should.equal(temp); + + // assert + expect(subject.userStore).toEqual(temp); }); }); - describe("revokeAccessTokenOnSignout", function() { - it("should return value from initial settings", function() { + describe("revokeAccessTokenOnSignout", () => { + it("should return value from initial settings", () => { + // act let subject = new UserManagerSettings({ revokeAccessTokenOnSignout : true }); - subject.revokeAccessTokenOnSignout.should.equal(true); + + // assert + expect(subject.revokeAccessTokenOnSignout).toEqual(true); }); }); - describe("checkSessionInterval", function() { - it("should return value from initial settings", function() { + describe("checkSessionInterval", () => { + it("should return value from initial settings", () => { + // act let subject = new UserManagerSettings({ checkSessionInterval : 6000 }); - subject.checkSessionInterval.should.equal(6000); + + // assert + expect(subject.checkSessionInterval).toEqual(6000); }); it("should use default value", function () { + // act let subject = new UserManagerSettings({ }); - subject.checkSessionInterval.should.equal(2000); + + // assert + expect(subject.checkSessionInterval).toEqual(2000); }); }); - describe("query_status_response_type", function() { - it("should return value from initial settings", function() { + describe("query_status_response_type", () => { + it("should return value from initial settings", () => { let temp = 'type'; + + // act let subject = new UserManagerSettings({ query_status_response_type : temp }); - subject.query_status_response_type.should.equal(temp); + + // assert + expect(subject.query_status_response_type).toEqual(temp); }); it("should infer default value", function () { { + // act let subject = new UserManagerSettings({ response_type: "id_token token" }); - subject.query_status_response_type.should.equal("id_token"); + + // assert + expect(subject.query_status_response_type).toEqual("id_token"); } - { + { + // act let subject = new UserManagerSettings({ response_type: "code" }); - subject.query_status_response_type.should.equal("code"); + + // assert + expect(subject.query_status_response_type).toEqual("code"); } }); }); - describe("stopCheckSessionOnError", function() { - it("should return value from initial settings", function() { + describe("stopCheckSessionOnError", () => { + it("should return value from initial settings", () => { + // act let subject = new UserManagerSettings({ stopCheckSessionOnError : false }); - subject.stopCheckSessionOnError.should.be.false; + + // assert + expect(subject.stopCheckSessionOnError).toEqual(false); }); it("should use default value", function () { + // act let subject = new UserManagerSettings({ }); - subject.stopCheckSessionOnError.should.be.true; + + // assert + expect(subject.stopCheckSessionOnError).toEqual(true); }); }); }); diff --git a/test/unit/WebStorageStateStore.spec.js b/test/unit/WebStorageStateStore.spec.js deleted file mode 100644 index b5b06bd4d..000000000 --- a/test/unit/WebStorageStateStore.spec.js +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -import { Log } from '../../src/Log'; -import { WebStorageStateStore } from '../../src/WebStorageStateStore'; -import { InMemoryWebStorage } from '../../src/InMemoryWebStorage'; - -import chai from 'chai'; -chai.should(); -let assert = chai.assert; -let expect = chai.expect; - -describe("WebStorageStateStore", function() { - let prefix; - let subject; - let store; - - beforeEach(function() { - prefix = ""; - store = new InMemoryWebStorage(); - subject = new WebStorageStateStore({ prefix: prefix, store: store }); - }); - - describe("set", function() { - - it("should return a promise", function() { - var p = subject.set("key", "value"); - p.should.be.instanceof(Promise); - p.catch(e=>{}); - }); - - it("should store item", function(done) { - subject.set("key", "value").then(() => { - store.getItem("key").should.equal("value"); - done(); - }) - }); - - it("should use prefix if specified", function(done) { - prefix = "foo."; - subject = new WebStorageStateStore({ prefix: prefix, store: store }); - - subject.set("key", "value").then(() => { - store.getItem(prefix + "key").should.equal("value"); - done(); - }) - }); - - }); - - describe("remove", function() { - - it("should return a promise", function() { - var p = subject.remove("key"); - p.should.be.instanceof(Promise); - p.catch(e=>{}); - }); - - it("should remove item", function(done) { - store.setItem("key", "value"); - - subject.remove("key").then(item => { - expect(store.getItem("key")).to.be.undefined; - done(); - }); - }); - - it("should return value if exists", function(done) { - store.setItem("key", "test"); - - subject.remove("key").then(value => { - value.should.equal('test'); - done(); - }); - }); - - it("should return undefined if doesn't exist", function(done) { - subject.remove("key").then(value => { - expect(value).to.be.undefined; - done(); - }); - }); - - it("should use prefix if specified", function(done) { - prefix = "foo."; - subject = new WebStorageStateStore({ prefix: prefix, store: store }); - - store.setItem("foo.key", "value"); - - subject.remove("key").then(item => { - expect(store.getItem("foo.key")).to.be.undefined; - done(); - }); - }); - - }); - - describe("get", function() { - - it("should return a promise", function() { - var p = subject.get("key"); - p.should.be.instanceof(Promise); - }); - - it("should return value if exists", function(done) { - store.setItem("key", "test"); - - subject.get("key").then(value => { - value.should.equal('test'); - done(); - }); - }); - - it("should return undefined if doesn't exist", function(done) { - subject.get("key").then(value => { - expect(value).to.be.undefined; - done(); - }); - }); - - it("should use prefix if specified", function(done) { - prefix = "foo."; - subject = new WebStorageStateStore({ prefix: prefix, store: store }); - - store.setItem("foo.key", "value"); - - subject.get("key").then(item => { - item.should.equal("value"); - done(); - }); - }); - - }); - - describe("getAllKeys", function() { - - it("should return a promise", function() { - var p = subject.getAllKeys(); - p.should.be.instanceof(Promise); - p.catch(e=>{}); - }); - - it("should return keys", function(done) { - store.setItem("key1", "test"); - store.setItem("key2", "test"); - - subject.getAllKeys().then(keys => { - keys.should.deep.equal(["key1", "key2"]); - done(); - }); - }); - - it("should return keys without prefix", function(done) { - prefix = "foo."; - subject = new WebStorageStateStore({ prefix: prefix, store: store }); - - store.setItem("foo.key1", "test"); - store.setItem("foo.key2", "test"); - - subject.getAllKeys().then(keys => { - keys.should.deep.equal(["key1", "key2"]); - done(); - }); - }); - - it("should return empty keys when empty", function(done) { - subject.getAllKeys().then(keys => { - keys.should.deep.equal([]); - done(); - }); - }); - - }); - -}); diff --git a/test/unit/WebStorageStateStore.test.ts b/test/unit/WebStorageStateStore.test.ts new file mode 100644 index 000000000..e3ef238f6 --- /dev/null +++ b/test/unit/WebStorageStateStore.test.ts @@ -0,0 +1,195 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +import { WebStorageStateStore } from '../../src/WebStorageStateStore'; +import { InMemoryWebStorage } from '../../src/InMemoryWebStorage'; + +describe("WebStorageStateStore", () => { + let prefix: string; + let store: Storage; + let subject: WebStorageStateStore; + + beforeEach(() => { + prefix = ""; + store = new InMemoryWebStorage(); + subject = new WebStorageStateStore({ prefix: prefix, store: store }); + }); + + describe("set", () => { + + it("should return a promise", () => { + // act + let p = subject.set("key", "value"); + + // assert + expect(p).toBeInstanceOf(Promise); + }); + + it("should store item", async () => { + // act + await subject.set("key", "value"); + let result = await store.getItem("key"); + + // assert + expect(result).toEqual("value"); + }); + + it("should use prefix if specified", async () => { + // arrange + prefix = "foo."; + subject = new WebStorageStateStore({ prefix: prefix, store: store }); + + // act + await subject.set("key", "value"); + + // assert + let result = await store.getItem(prefix + "key"); + expect(result).toEqual("value"); + }); + }); + + describe("remove", () => { + + it("should return a promise", () => { + // act + let p = subject.remove("key"); + + // assert + expect(p).toBeInstanceOf(Promise); + }); + + it("should remove item", async () => { + // arrange + await store.setItem("key", "value"); + + // act + await subject.remove("key"); + let result = await store.getItem("key"); + + // assert + expect(result).toBeUndefined(); + }); + + it("should return value if exists", async () => { + // arrange + await store.setItem("key", "test"); + + // act + let result = await subject.remove("key"); + + // assert + expect(result).toEqual("test"); + }); + + it("should return undefined if doesn't exist", async () => { + // act + let result = await subject.remove("key"); + + // assert + expect(result).toBeUndefined(); + }); + + it("should use prefix if specified", async () => { + // arrange + prefix = "foo."; + subject = new WebStorageStateStore({ prefix: prefix, store: store }); + await subject.set("key", "value"); + + // act + let result = await subject.remove("key"); + + // assert + expect(result).toEqual("value"); + }); + + }); + + describe("get", () => { + + it("should return a promise", () => { + // act + var p = subject.get("key"); + + // assert + expect(p).toBeInstanceOf(Promise); + }); + + it("should return value if exists", async () => { + // arrange + await store.setItem("key", "test"); + + // act + let result = await subject.get("key"); + + // assert + expect(result).toEqual("test"); + }); + + it("should return undefined if doesn't exist", async () => { + // act + let result = await subject.get("key"); + + // assert + expect(result).toBeUndefined(); + }); + + it("should use prefix if specified", async () => { + // arrange + prefix = "foo."; + subject = new WebStorageStateStore({ prefix: prefix, store: store }); + store.setItem("foo.key", "value"); + + // act + let result = await subject.get("key"); + + // assert + expect(result).toEqual("value"); + }); + + }); + + describe("getAllKeys", () => { + + it("should return a promise", () => { + // act + var p = subject.getAllKeys(); + + // assert + expect(p).toBeInstanceOf(Promise); + }); + + it("should return keys", async () => { + // arrange + await store.setItem("key1", "test"); + await store.setItem("key2", "test"); + + // act + let result = await subject.getAllKeys(); + + // assert + expect(result).toStrictEqual(["key1", "key2"]); + }); + + it("should return keys without prefix", async () => { + // arrange + prefix = "foo."; + subject = new WebStorageStateStore({ prefix: prefix, store: store }); + await store.setItem("foo.key1", "test"); + await store.setItem("foo.key2", "test"); + + // act + let result = await subject.getAllKeys(); + + // assert + expect(result).toStrictEqual(["key1", "key2"]); + }); + + it("should return empty keys when empty", async () => { + // act + let result = await subject.getAllKeys(); + + // assert + expect(result).toStrictEqual([]); + }); + }); +}); diff --git a/test/unit/random.spec.js b/test/unit/random.test.ts similarity index 57% rename from test/unit/random.spec.js rename to test/unit/random.test.ts index 050cb3010..27e168619 100644 --- a/test/unit/random.spec.js +++ b/test/unit/random.test.ts @@ -5,10 +5,11 @@ const pattern = /^[0-9a-f]{8}[0-9a-f]{4}4[0-9a-f]{3}[89ab][0-9a-f]{3}[0-9a-f]{12 describe('random', function() { - it('should return a valid RFC4122 v4 guid (sans dashes)', function(){ - const rnd = random() - console.log(rnd); - rnd.should.match(pattern) - }) -}) - + it('should return a valid RFC4122 v4 guid (sans dashes)', () => { + // act + const rnd = random() + + // assert + expect(rnd).toMatch(pattern); + }); +});