Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"test:unit:silent": "npm run test:unit > tmp/test-unit-log.txt 2>&1",
"test:browser": "karma start --single-run",
"test:watch": "npm run test:unit -- --watch",
"coverage": "nyc --reporter=text --include='src/**/*.js' --temp-dir=./tmp/ --check-coverage --lines 50 npm run test:unit:silent",
"coverage": "nyc --reporter=text --include='src/**/*.js' --temp-dir=./tmp/ --check-coverage --lines 91 npm run test:unit:silent",
"lint": "eslint . --ext .js --format unix --ignore-path .gitignore --ignore-pattern \"dist/*\"",
"lint:fix": "npm run lint -- --fix",
"docs": "documentation build src/Particle.js --shallow -g -f md -o docs/api.md",
Expand Down
3 changes: 2 additions & 1 deletion src/Defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export default {
baseUrl: 'https://api.particle.io',
clientSecret: 'particle-api',
clientId: 'particle-api',
tokenDuration: 7776000 // 90 days
tokenDuration: 7776000, // 90 days
auth: undefined
};
35 changes: 34 additions & 1 deletion src/Particle.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ class Particle {
* @param {Object} options Options for this API call Options to be used for all requests (see [Defaults](../src/Defaults.js))
*/
constructor(options = {}){
if (options.auth) {
this.setDefaultAuth(options.auth);
}

// todo - this seems a bit dangerous - would be better to put all options/context in a contained object
Object.assign(this, Defaults, options);
this.context = {};
Expand All @@ -38,7 +42,7 @@ class Particle {
if (this._isValidContext(name, context)){
this.context[name] = context;
} else {
throw Error('uknown context name or undefined context: '+name);
throw Error('unknown context name or undefined context: '+name);
}
}
}
Expand Down Expand Up @@ -887,6 +891,7 @@ class Particle {
uri += `/${encodeURIComponent(name)}`;
}

auth = this._getActiveAuthToken(auth);
return new EventStream(`${this.baseUrl}${uri}`, auth).connect();
}

Expand Down Expand Up @@ -2099,6 +2104,27 @@ class Particle {
});
}

/**
* Set default auth token that will be used in each method if `auth` is not provided
* @param {String} auth A Particle access token
* @returns {undefined}
*/
setDefaultAuth(auth){
if (typeof auth === 'string' && auth.length !== 0) {
this._defaultAuth = auth;
} else {
throw new Error('Must pass a non-empty string');
}
}
/**
* Return provided token if truthy else use default auth if truthy else undefined
* @param {*} auth Optional auth token or undefined
* @private
* @returns {String|undefined} a Particle auth token or undefined
*/
_getActiveAuthToken(auth) {
return auth || this._defaultAuth;
}
/**
* API URI to access a device
* @param {Object} options Options for this API call
Expand All @@ -2113,31 +2139,37 @@ class Particle {

get({ uri, auth, headers, query, context }){
context = this._buildContext(context);
auth = this._getActiveAuthToken(auth);
return this.agent.get({ uri, auth, headers, query, context });
}

head({ uri, auth, headers, query, context }){
context = this._buildContext(context);
auth = this._getActiveAuthToken(auth);
return this.agent.head({ uri, auth, headers, query, context });
}

post({ uri, auth, headers, data, context }){
context = this._buildContext(context);
auth = this._getActiveAuthToken(auth);
return this.agent.post({ uri, auth, headers, data, context });
}

put({ uri, auth, headers, data, context }){
context = this._buildContext(context);
auth = this._getActiveAuthToken(auth);
return this.agent.put({ uri, auth, headers, data, context });
}

delete({ uri, auth, headers, data, context }){
context = this._buildContext(context);
auth = this._getActiveAuthToken(auth);
return this.agent.delete({ uri, auth, headers, data, context });
}

request(args){
args.context = this._buildContext(args.context);
args.auth = this._getActiveAuthToken(args.auth);
return this.agent.request(args);
}

Expand All @@ -2147,6 +2179,7 @@ class Particle {

// Internal method used to target Particle's APIs other than the default
setBaseUrl(baseUrl){
this.baseUrl = baseUrl;
this.agent.setBaseUrl(baseUrl);
}
}
Expand Down
29 changes: 29 additions & 0 deletions test/Defaults.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { expect } from './test-setup';
import Defaults from '../src/Defaults';

describe('Default Particle constructor options', () => {
it('includes baseUrl', () => {
expect(Defaults).to.have.property('baseUrl');
expect(Defaults.baseUrl).to.eql('https://api.particle.io');
});

it('includes clientSecret', () => {
expect(Defaults).to.have.property('clientSecret');
expect(Defaults.clientSecret).to.eql('particle-api');
});

it('includes clientId', () => {
expect(Defaults).to.have.property('clientId');
expect(Defaults.clientId).to.eql('particle-api');
});

it('includes tokenDuration', () => {
expect(Defaults).to.have.property('tokenDuration');
expect(Defaults.tokenDuration).to.eql(7776000);
});

it('includes defaultAuth', () => {
expect(Defaults).to.have.property('auth');
expect(Defaults.auth).to.eql(undefined);
});
});
101 changes: 91 additions & 10 deletions test/Particle.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,27 @@ describe('ParticleAPI', () => {
});

describe('constructor', () => {
it('sets the defaults', () => {
it('sets maps defaults to instance properties', () => {
Object.keys(Defaults).forEach((setting) => {
api[setting].should.equal(Defaults[setting]);
expect(api[setting]).to.eql(Defaults[setting]);
});
});

describe('without defaultAuth', () => {
it('does NOT call .setDefaultAuth(defaultAuth) unless provided value is truthy', () => {
sinon.stub(api, 'setDefaultAuth');
expect(api.setDefaultAuth).to.have.property('callCount', 0);
});
});

describe('with defaultAuth', () => {
it('calls .setDefaultAuth(defaultAuth) when provided defaultAuth value is truthy', () => {
const fakeAuthToken = 'foo';
sinon.stub(Particle.prototype, 'setDefaultAuth');
api = new Particle({ auth: fakeAuthToken });
expect(api.setDefaultAuth).to.have.property('callCount', 1);
expect(api.setDefaultAuth.firstCall.args).to.have.lengthOf(1);
expect(api.setDefaultAuth.firstCall.args[0]).to.eql(fakeAuthToken);
});
});
});
Expand Down Expand Up @@ -1013,6 +1031,13 @@ describe('ParticleAPI', () => {
uri.should.endWith(`v1/products/test-product/devices/${props.deviceId}/events/foo`);
});
});

it('calls _getActiveAuthToken(auth)', () => {
const fakeToken = 'abc123';
sinon.stub(api, '_getActiveAuthToken').returns(fakeToken);
api.getEventStream({});
expect(api._getActiveAuthToken).to.have.property('callCount', 1);
});
});

describe('.publishEvent', () => {
Expand Down Expand Up @@ -2618,13 +2643,15 @@ describe('ParticleAPI', () => {
contextResult = { def: 456 };
result = 'fake-result';
api._buildContext = sinon.stub().returns(contextResult);
api._getActiveAuthToken = sinon.stub().returns(auth);
});

afterEach(() => {
expect(api._buildContext).to.have.been.calledWith(context);
expect(api._getActiveAuthToken).to.have.been.calledWith(auth);
});

it('calls _buildContext from get', () => {
it('calls _buildContext and _getActiveAuthToken from get', () => {
api.agent.get = sinon.stub().returns(result);
const options = { uri, auth, headers, query, context };
const res = api.get(options);
Expand All @@ -2638,7 +2665,7 @@ describe('ParticleAPI', () => {
});
});

it('calls _buildContext from head', () => {
it('calls _buildContext and _getActiveAuthToken from head', () => {
api.agent.head = sinon.stub().returns(result);
const options = { uri, auth, headers, query, context };
const res = api.head(options);
Expand All @@ -2652,7 +2679,7 @@ describe('ParticleAPI', () => {
});
});

it('calls _buildContext from post', () => {
it('calls _buildContext and _getActiveAuthToken from post', () => {
api.agent.post = sinon.stub().returns(result);
const options = { uri, auth, headers, data, context };
const res = api.post(options);
Expand All @@ -2666,7 +2693,7 @@ describe('ParticleAPI', () => {
});
});

it('calls _buildContext from put', () => {
it('calls _buildContext and _getActiveAuthToken from put', () => {
api.agent.put = sinon.stub().returns(result);
const options = { uri, auth, headers, data, context };
const res = api.put(options);
Expand All @@ -2680,7 +2707,7 @@ describe('ParticleAPI', () => {
});
});

it('calls _buildContext from delete', () => {
it('calls _buildContext and _getActiveAuthToken from delete', () => {
api.agent.delete = sinon.stub().returns(result);
const options = { uri, auth, headers, data, context };
const res = api.delete(options);
Expand All @@ -2694,10 +2721,10 @@ describe('ParticleAPI', () => {
});
});

it('calls _buildContext from request', () => {
it('calls _buildContext and _getActiveAuthToken from request', () => {
api.agent.request = sinon.stub().returns(result);
api.request({ context }).should.eql(result);
expect(api.agent.request).to.have.been.calledWith({ context:contextResult });
api.request({ context, auth }).should.eql(result);
expect(api.agent.request).to.have.been.calledWith({ context:contextResult, auth });
});
});
});
Expand All @@ -2707,6 +2734,12 @@ describe('ParticleAPI', () => {
sinon.restore();
});

it('sets baseUrl instance property', () => {
const baseUrl = 'foo';
api.setBaseUrl(baseUrl);
expect(api.baseUrl).to.eql(baseUrl);
});

it('calls agent.setBaseUrl', () => {
const baseUrl = 'foo';
sinon.stub(api.agent, 'setBaseUrl');
Expand All @@ -2716,4 +2749,52 @@ describe('ParticleAPI', () => {
expect(api.agent.setBaseUrl.firstCall.args[0]).to.eql(baseUrl);
});
});

describe('setDefaultAuth(auth)', () => {
afterEach(() => {
sinon.restore();
});

it('sets ._defaultAuth', () => {
const auth = 'foo';
api.setDefaultAuth(auth);
expect(api._defaultAuth).to.eql(auth);
});

it('throws error unless given a non-empty string', () => {
let error;
try {
api.setDefaultAuth(undefined);
} catch (e) {
error = e;
}
expect(error).to.be.an.instanceOf(Error);
expect(error.message).to.eql('Must pass a non-empty string');
});
});

describe('_getActiveAuthToken(auth)', () => {
afterEach(() => {
sinon.restore();
});

it('returns provided value when provided value is truthy', () => {
const expectedReturnValue = 'pass through';
expect(api._getActiveAuthToken(expectedReturnValue)).to.eql(expectedReturnValue);
});

it('returns value of _defaultAuth when provided value is NOT truthy', () => {
const providedValue = undefined;
const expectedReturnValue = 'default auth value';
api.setDefaultAuth(expectedReturnValue);
expect(api._getActiveAuthToken(providedValue)).to.eql(expectedReturnValue);
});

it('returns undefined when both provided value and _defaultAuth are NOT truthy', () => {
const providedValue = undefined;
const expectedReturnValue = undefined;
api._defaultAuth = undefined;
expect(api._getActiveAuthToken(providedValue)).to.eql(expectedReturnValue);
});
});
});