Skip to content

Commit

Permalink
fix: arrow & static class methods as adapter factories (#1197)
Browse files Browse the repository at this point in the history
Co-authored-by: Filip Skokan <panva.ip@gmail.com>
  • Loading branch information
talentumtuum and panva authored Jul 4, 2022
1 parent 994697d commit cee552f
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 8 deletions.
13 changes: 9 additions & 4 deletions lib/helpers/initialize_adapter.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
const util = require('util');

const MemoryAdapter = require('../adapters/memory_adapter');

const instance = require('./weak_cache');
const attention = require('./attention');
const { isConstructable } = require('./type_validators');

module.exports = function initializeAdapter(adapter = MemoryAdapter) {
if (adapter === MemoryAdapter) {
Expand All @@ -10,10 +13,12 @@ module.exports = function initializeAdapter(adapter = MemoryAdapter) {
+ ' between processes');
}

if (!adapter.prototype || !adapter.prototype.constructor.name) {
throw new Error(
'Expected "adapter" to be a constructor, provide a valid adapter in Provider config.',
);
const constructable = isConstructable(adapter);
const executable = typeof adapter === 'function' && !util.types.isAsyncFunction(adapter);
const valid = constructable || executable;

if (!valid) {
throw new Error('Expected "adapter" to be a constructor or a factory function, provide a valid adapter in Provider config.');
}

instance(this).Adapter = adapter;
Expand Down
12 changes: 12 additions & 0 deletions lib/helpers/type_validators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const hasPrototype = (target) => target.prototype !== null && typeof target.prototype === 'object';

const isContructor = (fn) => fn.constructor instanceof Function
&& fn.constructor.name !== undefined;

const isConstructable = (constructable) => constructable instanceof Function
&& hasPrototype(constructable)
&& isContructor(constructable.constructor);

module.exports = {
isConstructable,
};
7 changes: 6 additions & 1 deletion lib/models/base_model.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const snakeCase = require('../helpers/_/snake_case');
const epochTime = require('../helpers/epoch_time');
const pickBy = require('../helpers/_/pick_by');
const instance = require('../helpers/weak_cache');
const { isConstructable } = require('../helpers/type_validators');

const hasFormat = require('./mixins/has_format');

Expand All @@ -23,7 +24,11 @@ module.exports = function getBaseModel(provider) {
const obj = typeof ctx === 'function' ? ctx : ctx.constructor;

if (!adapterCache.has(obj)) {
adapterCache.set(obj, new (instance(provider).Adapter)(obj.name));
if (isConstructable(instance(provider).Adapter)) {
adapterCache.set(obj, new (instance(provider).Adapter)(obj.name));
} else {
adapterCache.set(obj, instance(provider).Adapter(obj.name));
}
}

return adapterCache.get(obj);
Expand Down
9 changes: 8 additions & 1 deletion lib/models/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const base64url = require('../helpers/base64url');
const request = require('../helpers/request');
const nanoid = require('../helpers/nanoid');
const epochTime = require('../helpers/epoch_time');
const { isConstructable } = require('../helpers/type_validators');
const instance = require('../helpers/weak_cache');
const constantEquals = require('../helpers/constant_equals');
const { InvalidClient, InvalidClientMetadata } = require('../helpers/errors');
Expand Down Expand Up @@ -134,7 +135,13 @@ module.exports = function getClient(provider) {
let adapter;

function getAdapter() {
if (!adapter) adapter = new (instance(provider).Adapter)('Client');
if (!adapter) {
if (isConstructable(instance(provider).Adapter)) {
adapter = new (instance(provider).Adapter)('Client');
} else {
adapter = instance(provider).Adapter('Client');
}
}
return adapter;
}

Expand Down
17 changes: 15 additions & 2 deletions test/helpers/initialize_adapter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,20 @@ const { expect } = require('chai');
const initializeAdapter = require('../../lib/helpers/initialize_adapter');

describe('initializeAdapter helper', () => {
it('throws when adapter is not a constructor', () => {
expect(initializeAdapter.bind(undefined, {})).to.throw('Expected "adapter" to be a constructor, provide a valid adapter in Provider config.');
it('throws when adapter is not a constructor or not a function', () => {
expect(initializeAdapter.bind(undefined, {})).to.throw('Expected "adapter" to be a constructor or a factory function, provide a valid adapter in Provider config.');
expect(initializeAdapter.bind(undefined, async () => {})).to.throw('Expected "adapter" to be a constructor or a factory function, provide a valid adapter in Provider config.');
});

it('should be success if argument is static method of class', () => {
expect(
initializeAdapter.bind(undefined, (class { static method() {} }).method),
).to.be.not.throw;
});

it('should be success if argument is arrow function', () => {
expect(
initializeAdapter.bind(undefined, () => {}).method,
).to.be.not.throw;
});
});
19 changes: 19 additions & 0 deletions test/helpers/type_validators.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* eslint-disable prefer-arrow-callback */
const { expect } = require('chai');

const { isConstructable } = require('../../lib/helpers/type_validators');

describe('type validators helper', () => {
it('should be falsy when argument is null', () => {
expect(isConstructable(null)).to.be.false;
});

it('should be falsy when argument is arrow function', () => {
expect(isConstructable(() => {})).to.be.false;
});

it('should be successfull executed if argument is constructable', () => {
expect(isConstructable(class {})).to.be.true;
expect(isConstructable(function () {})).to.be.true;
});
});
63 changes: 63 additions & 0 deletions test/provider/provider_instance.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/* eslint-disable max-classes-per-file */

const { strict: assert } = require('assert');

const { expect } = require('chai');
const sinon = require('sinon');

Expand Down Expand Up @@ -86,4 +90,63 @@ describe('provider instance', () => {
expect(provider.urlFor('resume', { uid: 'foo' })).to.equal('http://localhost/auth/foo');
});
});

describe('adapters', () => {
const error = new Error('used this adapter');

it('can be a class', async () => {
const provider = new Provider('https://op.example.com', {
adapter: class {
// eslint-disable-next-line
async find() {
throw error;
}
},
});
await assert.rejects(provider.AccessToken.find('tokenValue'), {
message: 'used this adapter',
});
await assert.rejects(provider.Client.find('clientId'), {
message: 'used this adapter',
});
});

it('can be a class static function', async () => {
const provider = new Provider('https://op.example.com', {
adapter: (class {
// eslint-disable-next-line
static factory() {
// eslint-disable-next-line
return {
async find() {
throw error;
},
};
}
}).factory,
});
await assert.rejects(provider.AccessToken.find('tokenValue'), {
message: 'used this adapter',
});
await assert.rejects(provider.Client.find('clientId'), {
message: 'used this adapter',
});
});

it('can be an arrow function', async () => {
const provider = new Provider('https://op.example.com', {
adapter: () => ({
async find() {
throw error;
},
}),
});
await assert.rejects(provider.AccessToken.find('tokenValue'), {
message: 'used this adapter',
});
await assert.rejects(provider.Client.find('clientId'), {
message: 'used this adapter',
});
});
});
});

0 comments on commit cee552f

Please sign in to comment.