Skip to content

Commit 94b7192

Browse files
author
Joe Goggins
authored
Merge pull request #145 from particle-iot/sc-76665/setDefaultAuth
Support setting access token per-instance in addition to accepting it as an arg to each method
2 parents e74c61b + 10cb93b commit 94b7192

File tree

5 files changed

+157
-13
lines changed

5 files changed

+157
-13
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"test:unit:silent": "npm run test:unit > tmp/test-unit-log.txt 2>&1",
1414
"test:browser": "karma start --single-run",
1515
"test:watch": "npm run test:unit -- --watch",
16-
"coverage": "nyc --reporter=text --include='src/**/*.js' --temp-dir=./tmp/ --check-coverage --lines 50 npm run test:unit:silent",
16+
"coverage": "nyc --reporter=text --include='src/**/*.js' --temp-dir=./tmp/ --check-coverage --lines 91 npm run test:unit:silent",
1717
"lint": "eslint . --ext .js --format unix --ignore-path .gitignore --ignore-pattern \"dist/*\"",
1818
"lint:fix": "npm run lint -- --fix",
1919
"docs": "documentation build src/Particle.js --shallow -g -f md -o docs/api.md",

src/Defaults.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ export default {
22
baseUrl: 'https://api.particle.io',
33
clientSecret: 'particle-api',
44
clientId: 'particle-api',
5-
tokenDuration: 7776000 // 90 days
5+
tokenDuration: 7776000, // 90 days
6+
auth: undefined
67
};

src/Particle.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ class Particle {
2323
* @param {Object} options Options for this API call Options to be used for all requests (see [Defaults](../src/Defaults.js))
2424
*/
2525
constructor(options = {}){
26+
if (options.auth) {
27+
this.setDefaultAuth(options.auth);
28+
}
29+
2630
// todo - this seems a bit dangerous - would be better to put all options/context in a contained object
2731
Object.assign(this, Defaults, options);
2832
this.context = {};
@@ -38,7 +42,7 @@ class Particle {
3842
if (this._isValidContext(name, context)){
3943
this.context[name] = context;
4044
} else {
41-
throw Error('uknown context name or undefined context: '+name);
45+
throw Error('unknown context name or undefined context: '+name);
4246
}
4347
}
4448
}
@@ -887,6 +891,7 @@ class Particle {
887891
uri += `/${encodeURIComponent(name)}`;
888892
}
889893

894+
auth = this._getActiveAuthToken(auth);
890895
return new EventStream(`${this.baseUrl}${uri}`, auth).connect();
891896
}
892897

@@ -2099,6 +2104,27 @@ class Particle {
20992104
});
21002105
}
21012106

2107+
/**
2108+
* Set default auth token that will be used in each method if `auth` is not provided
2109+
* @param {String} auth A Particle access token
2110+
* @returns {undefined}
2111+
*/
2112+
setDefaultAuth(auth){
2113+
if (typeof auth === 'string' && auth.length !== 0) {
2114+
this._defaultAuth = auth;
2115+
} else {
2116+
throw new Error('Must pass a non-empty string');
2117+
}
2118+
}
2119+
/**
2120+
* Return provided token if truthy else use default auth if truthy else undefined
2121+
* @param {*} auth Optional auth token or undefined
2122+
* @private
2123+
* @returns {String|undefined} a Particle auth token or undefined
2124+
*/
2125+
_getActiveAuthToken(auth) {
2126+
return auth || this._defaultAuth;
2127+
}
21022128
/**
21032129
* API URI to access a device
21042130
* @param {Object} options Options for this API call
@@ -2113,31 +2139,37 @@ class Particle {
21132139

21142140
get({ uri, auth, headers, query, context }){
21152141
context = this._buildContext(context);
2142+
auth = this._getActiveAuthToken(auth);
21162143
return this.agent.get({ uri, auth, headers, query, context });
21172144
}
21182145

21192146
head({ uri, auth, headers, query, context }){
21202147
context = this._buildContext(context);
2148+
auth = this._getActiveAuthToken(auth);
21212149
return this.agent.head({ uri, auth, headers, query, context });
21222150
}
21232151

21242152
post({ uri, auth, headers, data, context }){
21252153
context = this._buildContext(context);
2154+
auth = this._getActiveAuthToken(auth);
21262155
return this.agent.post({ uri, auth, headers, data, context });
21272156
}
21282157

21292158
put({ uri, auth, headers, data, context }){
21302159
context = this._buildContext(context);
2160+
auth = this._getActiveAuthToken(auth);
21312161
return this.agent.put({ uri, auth, headers, data, context });
21322162
}
21332163

21342164
delete({ uri, auth, headers, data, context }){
21352165
context = this._buildContext(context);
2166+
auth = this._getActiveAuthToken(auth);
21362167
return this.agent.delete({ uri, auth, headers, data, context });
21372168
}
21382169

21392170
request(args){
21402171
args.context = this._buildContext(args.context);
2172+
args.auth = this._getActiveAuthToken(args.auth);
21412173
return this.agent.request(args);
21422174
}
21432175

@@ -2147,6 +2179,7 @@ class Particle {
21472179

21482180
// Internal method used to target Particle's APIs other than the default
21492181
setBaseUrl(baseUrl){
2182+
this.baseUrl = baseUrl;
21502183
this.agent.setBaseUrl(baseUrl);
21512184
}
21522185
}

test/Defaults.spec.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { expect } from './test-setup';
2+
import Defaults from '../src/Defaults';
3+
4+
describe('Default Particle constructor options', () => {
5+
it('includes baseUrl', () => {
6+
expect(Defaults).to.have.property('baseUrl');
7+
expect(Defaults.baseUrl).to.eql('https://api.particle.io');
8+
});
9+
10+
it('includes clientSecret', () => {
11+
expect(Defaults).to.have.property('clientSecret');
12+
expect(Defaults.clientSecret).to.eql('particle-api');
13+
});
14+
15+
it('includes clientId', () => {
16+
expect(Defaults).to.have.property('clientId');
17+
expect(Defaults.clientId).to.eql('particle-api');
18+
});
19+
20+
it('includes tokenDuration', () => {
21+
expect(Defaults).to.have.property('tokenDuration');
22+
expect(Defaults.tokenDuration).to.eql(7776000);
23+
});
24+
25+
it('includes defaultAuth', () => {
26+
expect(Defaults).to.have.property('auth');
27+
expect(Defaults.auth).to.eql(undefined);
28+
});
29+
});

test/Particle.spec.js

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,27 @@ describe('ParticleAPI', () => {
124124
});
125125

126126
describe('constructor', () => {
127-
it('sets the defaults', () => {
127+
it('sets maps defaults to instance properties', () => {
128128
Object.keys(Defaults).forEach((setting) => {
129-
api[setting].should.equal(Defaults[setting]);
129+
expect(api[setting]).to.eql(Defaults[setting]);
130+
});
131+
});
132+
133+
describe('without defaultAuth', () => {
134+
it('does NOT call .setDefaultAuth(defaultAuth) unless provided value is truthy', () => {
135+
sinon.stub(api, 'setDefaultAuth');
136+
expect(api.setDefaultAuth).to.have.property('callCount', 0);
137+
});
138+
});
139+
140+
describe('with defaultAuth', () => {
141+
it('calls .setDefaultAuth(defaultAuth) when provided defaultAuth value is truthy', () => {
142+
const fakeAuthToken = 'foo';
143+
sinon.stub(Particle.prototype, 'setDefaultAuth');
144+
api = new Particle({ auth: fakeAuthToken });
145+
expect(api.setDefaultAuth).to.have.property('callCount', 1);
146+
expect(api.setDefaultAuth.firstCall.args).to.have.lengthOf(1);
147+
expect(api.setDefaultAuth.firstCall.args[0]).to.eql(fakeAuthToken);
130148
});
131149
});
132150
});
@@ -1013,6 +1031,13 @@ describe('ParticleAPI', () => {
10131031
uri.should.endWith(`v1/products/test-product/devices/${props.deviceId}/events/foo`);
10141032
});
10151033
});
1034+
1035+
it('calls _getActiveAuthToken(auth)', () => {
1036+
const fakeToken = 'abc123';
1037+
sinon.stub(api, '_getActiveAuthToken').returns(fakeToken);
1038+
api.getEventStream({});
1039+
expect(api._getActiveAuthToken).to.have.property('callCount', 1);
1040+
});
10161041
});
10171042

10181043
describe('.publishEvent', () => {
@@ -2618,13 +2643,15 @@ describe('ParticleAPI', () => {
26182643
contextResult = { def: 456 };
26192644
result = 'fake-result';
26202645
api._buildContext = sinon.stub().returns(contextResult);
2646+
api._getActiveAuthToken = sinon.stub().returns(auth);
26212647
});
26222648

26232649
afterEach(() => {
26242650
expect(api._buildContext).to.have.been.calledWith(context);
2651+
expect(api._getActiveAuthToken).to.have.been.calledWith(auth);
26252652
});
26262653

2627-
it('calls _buildContext from get', () => {
2654+
it('calls _buildContext and _getActiveAuthToken from get', () => {
26282655
api.agent.get = sinon.stub().returns(result);
26292656
const options = { uri, auth, headers, query, context };
26302657
const res = api.get(options);
@@ -2638,7 +2665,7 @@ describe('ParticleAPI', () => {
26382665
});
26392666
});
26402667

2641-
it('calls _buildContext from head', () => {
2668+
it('calls _buildContext and _getActiveAuthToken from head', () => {
26422669
api.agent.head = sinon.stub().returns(result);
26432670
const options = { uri, auth, headers, query, context };
26442671
const res = api.head(options);
@@ -2652,7 +2679,7 @@ describe('ParticleAPI', () => {
26522679
});
26532680
});
26542681

2655-
it('calls _buildContext from post', () => {
2682+
it('calls _buildContext and _getActiveAuthToken from post', () => {
26562683
api.agent.post = sinon.stub().returns(result);
26572684
const options = { uri, auth, headers, data, context };
26582685
const res = api.post(options);
@@ -2666,7 +2693,7 @@ describe('ParticleAPI', () => {
26662693
});
26672694
});
26682695

2669-
it('calls _buildContext from put', () => {
2696+
it('calls _buildContext and _getActiveAuthToken from put', () => {
26702697
api.agent.put = sinon.stub().returns(result);
26712698
const options = { uri, auth, headers, data, context };
26722699
const res = api.put(options);
@@ -2680,7 +2707,7 @@ describe('ParticleAPI', () => {
26802707
});
26812708
});
26822709

2683-
it('calls _buildContext from delete', () => {
2710+
it('calls _buildContext and _getActiveAuthToken from delete', () => {
26842711
api.agent.delete = sinon.stub().returns(result);
26852712
const options = { uri, auth, headers, data, context };
26862713
const res = api.delete(options);
@@ -2694,10 +2721,10 @@ describe('ParticleAPI', () => {
26942721
});
26952722
});
26962723

2697-
it('calls _buildContext from request', () => {
2724+
it('calls _buildContext and _getActiveAuthToken from request', () => {
26982725
api.agent.request = sinon.stub().returns(result);
2699-
api.request({ context }).should.eql(result);
2700-
expect(api.agent.request).to.have.been.calledWith({ context:contextResult });
2726+
api.request({ context, auth }).should.eql(result);
2727+
expect(api.agent.request).to.have.been.calledWith({ context:contextResult, auth });
27012728
});
27022729
});
27032730
});
@@ -2707,6 +2734,12 @@ describe('ParticleAPI', () => {
27072734
sinon.restore();
27082735
});
27092736

2737+
it('sets baseUrl instance property', () => {
2738+
const baseUrl = 'foo';
2739+
api.setBaseUrl(baseUrl);
2740+
expect(api.baseUrl).to.eql(baseUrl);
2741+
});
2742+
27102743
it('calls agent.setBaseUrl', () => {
27112744
const baseUrl = 'foo';
27122745
sinon.stub(api.agent, 'setBaseUrl');
@@ -2716,4 +2749,52 @@ describe('ParticleAPI', () => {
27162749
expect(api.agent.setBaseUrl.firstCall.args[0]).to.eql(baseUrl);
27172750
});
27182751
});
2752+
2753+
describe('setDefaultAuth(auth)', () => {
2754+
afterEach(() => {
2755+
sinon.restore();
2756+
});
2757+
2758+
it('sets ._defaultAuth', () => {
2759+
const auth = 'foo';
2760+
api.setDefaultAuth(auth);
2761+
expect(api._defaultAuth).to.eql(auth);
2762+
});
2763+
2764+
it('throws error unless given a non-empty string', () => {
2765+
let error;
2766+
try {
2767+
api.setDefaultAuth(undefined);
2768+
} catch (e) {
2769+
error = e;
2770+
}
2771+
expect(error).to.be.an.instanceOf(Error);
2772+
expect(error.message).to.eql('Must pass a non-empty string');
2773+
});
2774+
});
2775+
2776+
describe('_getActiveAuthToken(auth)', () => {
2777+
afterEach(() => {
2778+
sinon.restore();
2779+
});
2780+
2781+
it('returns provided value when provided value is truthy', () => {
2782+
const expectedReturnValue = 'pass through';
2783+
expect(api._getActiveAuthToken(expectedReturnValue)).to.eql(expectedReturnValue);
2784+
});
2785+
2786+
it('returns value of _defaultAuth when provided value is NOT truthy', () => {
2787+
const providedValue = undefined;
2788+
const expectedReturnValue = 'default auth value';
2789+
api.setDefaultAuth(expectedReturnValue);
2790+
expect(api._getActiveAuthToken(providedValue)).to.eql(expectedReturnValue);
2791+
});
2792+
2793+
it('returns undefined when both provided value and _defaultAuth are NOT truthy', () => {
2794+
const providedValue = undefined;
2795+
const expectedReturnValue = undefined;
2796+
api._defaultAuth = undefined;
2797+
expect(api._getActiveAuthToken(providedValue)).to.eql(expectedReturnValue);
2798+
});
2799+
});
27192800
});

0 commit comments

Comments
 (0)