Skip to content

Commit

Permalink
Wrapped InMemoryCache implementation in a closure (#337)
Browse files Browse the repository at this point in the history
  • Loading branch information
Steve Hobbs committed Jan 26, 2020
1 parent ab6def6 commit cdbdb4e
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 59 deletions.
85 changes: 58 additions & 27 deletions __tests__/cache.test.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
import { InMemoryCache, LocalStorageCache } from '../src/cache';
import { InMemoryCache, LocalStorageCache, ICache } from '../src/cache';

const nowSeconds = () => Math.floor(Date.now() / 1000);
const dayInSeconds = 86400;

describe('InMemoryCache', () => {
let cache: InMemoryCache;
let cache: ICache;

beforeEach(() => {
cache = new InMemoryCache();
cache = new InMemoryCache().enclosedCache;
jest.useFakeTimers();
});

afterEach(jest.useRealTimers);

it('returns undefined when there is no data', () => {
expect(
cache.get({ client_id: 'test-client', audience: 'a', scope: 's' })
cache.get({
client_id: 'test-client',
audience: 'a',
scope: 's'
})
).toBeUndefined();
});

it('builds key correctly', () => {
cache.save({
it('retrieves values from the cache', () => {
const data = {
client_id: 'test-client',
audience: 'the_audience',
scope: 'the_scope',
Expand All @@ -31,17 +35,23 @@ describe('InMemoryCache', () => {
claims: { __raw: 'idtoken', exp: 1, name: 'Test' },
user: { name: 'Test' }
}
});
};

expect(Object.keys(cache.cache)[0]).toBe(
'@@auth0spajs@@::test-client::the_audience::the_scope'
);
cache.save(data);

expect(
cache.get({
client_id: 'test-client',
audience: 'the_audience',
scope: 'the_scope'
})
).toStrictEqual(data);
});

it('expires after `expires_in` when `expires_in` < `user.exp`', () => {
cache.save({
const data = {
client_id: 'test-client',
audience: 'the_audiene',
audience: 'the_audience',
scope: 'the_scope',
id_token: 'idtoken',
access_token: 'accesstoken',
Expand All @@ -54,21 +64,29 @@ describe('InMemoryCache', () => {
},
user: { name: 'Test' }
}
});
};

const cacheEntry = {
client_id: 'test-client',
audience: 'the_audience',
scope: 'the_scope'
};

cache.save(data);

// Test that the cache state is normal up until just before the expiry time..
jest.advanceTimersByTime(799);
expect(Object.keys(cache.cache).length).toBe(1);
expect(cache.get(cacheEntry)).toStrictEqual(data);

// Advance the time to match the expiry time..
jest.advanceTimersByTime(1);

// and test that the cache has been emptied.
expect(Object.keys(cache.cache).length).toBe(0);
expect(cache.get(cacheEntry)).toBeUndefined();
});

it('strips everything except the refresh token when expiry has been reached', () => {
cache.save({
const data = {
client_id: 'test-client',
audience: 'the_audience',
scope: 'the_scope',
Expand All @@ -84,25 +102,30 @@ describe('InMemoryCache', () => {
},
user: { name: 'Test' }
}
});
};

cache.save(data);

const cacheEntry = {
client_id: 'test-client',
audience: 'the_audience',
scope: 'the_scope'
};

// Test that the cache state is normal up until just before the expiry time..
jest.advanceTimersByTime(799);
expect(Object.keys(cache.cache).length).toBe(1);
expect(cache.get(cacheEntry)).toStrictEqual(data);

// Advance the time to just past the expiry..
jest.advanceTimersByTime(1);

// And test that the cache has been emptied, except for the refresh token
expect(cache.cache).toStrictEqual({
'@@auth0spajs@@::test-client::the_audience::the_scope': {
refresh_token: 'refreshtoken'
}
expect(cache.get(cacheEntry)).toStrictEqual({
refresh_token: 'refreshtoken'
});
});

it('expires after `user.exp` when `user.exp` < `expires_in`', () => {
cache.save({
const data = {
client_id: 'test-client',
audience: 'the_audience',
scope: 'the_scope',
Expand All @@ -117,17 +140,25 @@ describe('InMemoryCache', () => {
},
user: { name: 'Test' }
}
});
};

cache.save(data);

const cacheEntry = {
client_id: 'test-client',
audience: 'the_audience',
scope: 'the_scope'
};

// Test that the cache state is normal up until just before the expiry time..
jest.advanceTimersByTime(799);
expect(Object.keys(cache.cache).length).toBe(1);
expect(cache.get(cacheEntry)).toStrictEqual(data);

// Advance the time to just past the expiry..
jest.advanceTimersByTime(1);

// And test that the cache has been emptied
expect(Object.keys(cache.cache).length).toBe(0);
expect(cache.get(cacheEntry)).toBeUndefined();
});
});

Expand Down
22 changes: 16 additions & 6 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
jest.mock('browser-tabs-lock');
jest.mock('../src/jwt');
jest.mock('../src/storage');
jest.mock('../src/cache');
jest.mock('../src/transaction-manager');
jest.mock('../src/utils');

Expand Down Expand Up @@ -40,17 +39,25 @@ const DEFAULT_POPUP_CONFIG_OPTIONS: PopupConfigOptions = {
timeoutInSeconds: DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS
};

const mockEnclosedCache = {
get: jest.fn(),
save: jest.fn(),
clear: jest.fn()
};

jest.mock('../src/cache', () => ({
InMemoryCache: () => ({
enclosedCache: mockEnclosedCache
})
}));

const setup = async (options = {}) => {
const auth0 = await createAuth0Client({
domain: TEST_DOMAIN,
client_id: TEST_CLIENT_ID,
...options
});

// Return the specific instance you want from the module by supplying fn
// e.g. getInstance('../module', m => m.SomeInstance)
const getInstance = (m, fn) => fn(require(m)).mock.instances[0];

const getDefaultInstance = m => require(m).default.mock.instances[0];

const storage = {
Expand All @@ -60,7 +67,8 @@ const setup = async (options = {}) => {
};

const lock = require('browser-tabs-lock');
const cache = getInstance('../src/cache', m => m.InMemoryCache);
const cache = mockEnclosedCache;

const tokenVerifier = require('../src/jwt').verify;
const transactionManager = getDefaultInstance('../src/transaction-manager');
const utils = require('../src/utils');
Expand Down Expand Up @@ -1639,6 +1647,7 @@ describe('Auth0', () => {
result.cache.get.mockReturnValue({ access_token: TEST_ACCESS_TOKEN });
return result;
};

it('calls `loginWithPopup` with the correct default options', async () => {
const { auth0, utils } = await localSetup();

Expand All @@ -1650,6 +1659,7 @@ describe('Auth0', () => {
},
DEFAULT_POPUP_CONFIG_OPTIONS
);

expect(utils.getUniqueScopes).toHaveBeenCalledWith(
'openid profile email',
undefined,
Expand Down
2 changes: 1 addition & 1 deletion src/Auth0Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const GET_TOKEN_SILENTLY_LOCK_KEY = 'auth0.lock.getTokenSilently';

const cacheFactory = location => {
const builders = {
memory: () => new InMemoryCache(),
memory: () => new InMemoryCache().enclosedCache,
localstorage: () => new LocalStorageCache()
};

Expand Down
54 changes: 29 additions & 25 deletions src/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,37 +152,41 @@ export class LocalStorageCache implements ICache {
}
}

export class InMemoryCache implements ICache {
cache: CachedTokens = {};
export class InMemoryCache {
public enclosedCache: ICache = (function() {
let cache: CachedTokens = {};

public save(entry: CacheEntry) {
const key = createKey(entry);
this.cache[key] = entry;
return {
save(entry: CacheEntry) {
const key = createKey(entry);
cache[key] = entry;

const timeout = getExpirationTimeoutInMilliseconds(
entry.expires_in,
entry.decodedToken.claims.exp
);
const timeout = getExpirationTimeoutInMilliseconds(
entry.expires_in,
entry.decodedToken.claims.exp
);

setTimeout(() => {
const payload = this.cache[key];
setTimeout(() => {
const payload = cache[key];

if (!payload) return;
if (!payload) return;

if (payload.refresh_token) {
this.cache[key] = { refresh_token: payload.refresh_token };
return;
}
if (payload.refresh_token) {
cache[key] = { refresh_token: payload.refresh_token };
return;
}

delete this.cache[key];
}, timeout);
}
delete cache[key];
}, timeout);
},

public get(key: CacheKeyData) {
return this.cache[createKey(key)];
}
get(key: CacheKeyData) {
return cache[createKey(key)];
},

public clear() {
this.cache = {};
}
clear() {
cache = {};
}
};
})();
}

0 comments on commit cdbdb4e

Please sign in to comment.