Skip to content

Commit

Permalink
feat(vehicle): add general purpose request method to Vehicle prototype (
Browse files Browse the repository at this point in the history
#143)

Add a new general purpose/BSE related method Vehicle.prototype.request = async function( method, path, body = {}, headers = {} ) to Vehicle that allows for making requests to the Smartcar API. Also, add E2E tests for this new request method.

Test Plan: Use getting-started-app to make requests with this method once it's in staging/prod.

* feat(vehicle): add general purpose request method to Vehicle prototype

* chore: switched post request test to batch instead of lock

* chore: Add Response type and improve attribute checks

* Update readme

* test: override non-Smartcar headers

* fix: resolve linting problems

Co-authored-by: Naomi Perez <[email protected]>
  • Loading branch information
naomiperez and Naomi Perez authored Jan 28, 2022
1 parent 75d31c3 commit d3a8a67
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 2 deletions.
45 changes: 45 additions & 0 deletions doc/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ the following fields :</p>
<dd></dd>
<dt><a href="#Batch">Batch</a> : <code>Object</code></dt>
<dd></dd>
<dt><a href="#Response">Response</a> : <code>Object</code></dt>
<dd></dd>
<dt><a href="#Meta">Meta</a> : <code>Object</code></dt>
<dd></dd>
<dt><a href="#Vin">Vin</a> : <code>Object</code></dt>
Expand Down Expand Up @@ -536,6 +538,7 @@ Initializes a new Service object to make requests to the Smartcar API.
* [.subscribe(webhookId)](#Vehicle+subscribe) ⇒ <code>Object</code>
* [.unsubscribe(amt, webhookId)](#Vehicle+unsubscribe)[<code>Meta</code>](#Meta)
* [.batch(paths)](#Vehicle+batch)[<code>Batch</code>](#Batch)
* [.request(method, path, body, headers)](#Vehicle+request)[<code>Response</code>](#Response)
* [.vin()](#Vehicle+vin)[<code>Vin</code>](#Vin)
* [.charge()](#Vehicle+charge)[<code>Charge</code>](#Charge)
* [.battery()](#Vehicle+battery)[<code>Battery</code>](#Battery)
Expand Down Expand Up @@ -638,6 +641,26 @@ Make batch requests for supported items
| --- | --- | --- |
| paths | <code>Array.&lt;String&gt;</code> | A list of paths of endpoints to send requests to. |

<a name="Vehicle+request"></a>

### vehicle.request(method, path, body, headers) ⇒ [<code>Response</code>](#Response)
General purpose method to make a request to a Smartcar endpoint.

**Kind**: instance method of [<code>Vehicle</code>](#Vehicle)
**Throws**:

- [<code>SmartcarError</code>](#SmartcarError) - an instance of SmartcarError.
See the [errors section](https://github.com/smartcar/node-sdk/tree/master/doc#errors)
for all possible errors.


| Param | Type | Description |
| --- | --- | --- |
| method | <code>String</code> | The HTTP request method to use. |
| path | <code>String</code> | The path to make the request to. |
| body | <code>Object</code> | The request body. |
| headers | <code>Object</code> | The headers to inlcude in the request. |

<a name="Vehicle+vin"></a>

### vehicle.vin() ⇒ [<code>Vin</code>](#Vin)
Expand Down Expand Up @@ -936,6 +959,28 @@ the following fields :
"location" : function() => returns odometer location or throws SmartcarError,
}
```
<a name="Response"></a>
## Response : <code>Object</code>
**Kind**: global typedef
**Properties**
| Name | Type | Description |
| --- | --- | --- |
| body | <code>String</code> | The response body |
| meta | [<code>Meta</code>](#Meta) | |
**Example**
```js
{
body: { distance: 59801.6373441601 },
meta: {
dataAge: 2022-01-20T02:55:25.041Z,
unitSystem: 'imperial',
requestId: 'f787849d-d228-482d-345f-459a5154sg73'
}
}
```
<a name="Meta"></a>
## Meta : <code>Object</code>
Expand Down
59 changes: 59 additions & 0 deletions lib/vehicle.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,65 @@ Vehicle.prototype.batch = async function(paths) {
return response;
};

/**
* @type {Object}
* @typedef Response
* @property {String} body - The response body
* @property {Meta} meta
*
* @example
* {
* body: { distance: 59801.6373441601 },
* meta: {
* dataAge: 2022-01-20T02:55:25.041Z,
* unitSystem: 'imperial',
* requestId: 'f787849d-d228-482d-345f-459a5154sg73'
* }
* }
*/
/**
* General purpose method to make a request to a Smartcar endpoint.
*
* @method
* @param {String} method - The HTTP request method to use.
* @param {String} path - The path to make the request to.
* @param {Object} body - The request body.
* @param {Object} headers - The headers to inlcude in the request.
* @return {Response}
* @throws {SmartcarError} - an instance of SmartcarError.
* See the [errors section](https://github.com/smartcar/node-sdk/tree/master/doc#errors)
* for all possible errors.
*/
Vehicle.prototype.request = async function(
method,
path,
body = {},
headers = {}
) {
const options = {
baseUrl: util.getUrl(this.id, '', this.version),
headers: _.merge({'sc-unit-system': this.unitSystem}, headers),
};

if (!headers.Authorization) {
options.auth = {bearer: this.token};
}

const rawResponse = await new SmartcarService(options).request(
method,
path,
{body}
);

const response = {
body: _.omit(rawResponse, 'meta'),
meta: rawResponse.meta,
};

return response;
};


/* JSDOC for dynamically generated methods */
/**
* @type {Object}
Expand Down
87 changes: 85 additions & 2 deletions test/end-to-end/vehicle.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ test('vehicle odometer', async(t) => {
'distance',
'meta',
]),
[],
[]
);

t.truthy(typeof response.distance === 'number');
Expand All @@ -179,7 +179,6 @@ test('vehicle location', async(t) => {
t.is(response.meta.requestId.length, 36);
});


test('vehicle attributes', async(t) => {
const response = await t.context.volt.attributes();
t.deepEqual(
Expand Down Expand Up @@ -374,6 +373,90 @@ test('vehicle batch', async(t) => {
t.is(location.meta.requestId.length, 36);
});

test('vehicle request - odometer', async(t) => {
const response = await t.context.volt.request(
'get',
'odometer',
{},
{
'sc-unit-system': 'imperial',
}
);

t.deepEqual(
_.xor(_.keys(response), [
'body',
'meta',
]),
[]
);

t.truthy(typeof response.body.distance === 'number');
t.truthy(response.meta.dataAge instanceof Date);
t.is(response.meta.requestId.length, 36);
t.is(response.meta.unitSystem, 'imperial');
});

test('vehicle request - batch', async(t) => {
const response = await t.context.volt.request('post', 'batch', {
requests: [
{path: '/odometer'},
{path: '/tires/pressure'},
],
});

t.deepEqual(
_.xor(_.keys(response), [
'body',
'meta',
]),
[]
);

t.truthy(response.meta.requestId.length, 36);

t.truthy(response.body.responses[0].path === '/odometer');
t.truthy(response.body.responses[0].code === 200);
t.truthy(response.body.responses[0].headers['sc-unit-system'] === 'metric');
t.truthy(new Date(
response.body.responses[0].headers['sc-data-age']
) instanceof Date);
t.truthy(typeof response.body.responses[0].body.distance === 'number');

t.truthy(response.body.responses[1].path === '/tires/pressure');
t.truthy(response.body.responses[1].code === 200);
t.truthy(response.body.responses[1].headers['sc-unit-system'] === 'metric');
t.truthy(new Date(
response.body.responses[1].headers['sc-data-age']
) instanceof Date);
t.truthy(typeof response.body.responses[1].body.frontLeft === 'number');
t.truthy(typeof response.body.responses[1].body.frontRight === 'number');
t.truthy(typeof response.body.responses[1].body.backLeft === 'number');
t.truthy(typeof response.body.responses[1].body.backRight === 'number');

});

test('vehicle request - override auth header', async(t) => {
const errorMessage = 'The authorization header is missing or malformed, '
+ 'or it contains invalid or expired authentication credentials. Please '
+ 'check for missing parameters, spelling and casing mistakes, and '
+ 'other syntax issues.';

await t.context.volt.request('get',
'odometer',
{},
{
'sc-unit-system': 'imperial',
Authorization: 'Bearer abc',
}
).catch((err) => {
t.is(err.statusCode, 401);
t.is(err.type, 'AUTHENTICATION');
t.is(err.description, errorMessage);
t.is(err.docURL, 'https://smartcar.com/docs/errors/v2.0/other-errors/#authentication');
});
});

test.after.always('vehicle disconnect', async(t) => {
const response = await t.context.volt.disconnect();
t.deepEqual(
Expand Down
19 changes: 19 additions & 0 deletions test/unit/lib/vehicle.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,22 @@ test('batch - error', async function(t) {
t.is(error.message, 'monkeys_on_mars:undefined - yes, really');
t.is(error.type, 'monkeys_on_mars');
});

test('request - override non-sc headers', async function(t) {
t.context.n = nock(
`https://api.smartcar.com/v${vehicle.version}/vehicles/${VID}`
)
.matchHeader('User-Agent', 'monkeys_on_mars')
.matchHeader('Authorization', `Bearer ${TOKEN}`)
.matchHeader('Origin', 'monkeys_on_pluto')
.get('/odometer')
.reply(200, {distance: 10}, {'sc-request-id': 'requestId'});

const response = await vehicle.request('get', 'odometer', undefined, {
'User-Agent': 'monkeys_on_mars',
Origin: 'monkeys_on_pluto',
});

t.is(response.body.distance, 10);
t.is(response.meta.requestId, 'requestId');
});

0 comments on commit d3a8a67

Please sign in to comment.