From d3a8a67fd49214fd7ebfaccbcd738222efe36b60 Mon Sep 17 00:00:00 2001
From: Naomi
Object
Object
Object
Object
Object
* [.unsubscribe(amt, webhookId)](#Vehicle+unsubscribe) ⇒ [Meta
](#Meta)
* [.batch(paths)](#Vehicle+batch) ⇒ [Batch
](#Batch)
+ * [.request(method, path, body, headers)](#Vehicle+request) ⇒ [Response
](#Response)
* [.vin()](#Vehicle+vin) ⇒ [Vin
](#Vin)
* [.charge()](#Vehicle+charge) ⇒ [Charge
](#Charge)
* [.battery()](#Vehicle+battery) ⇒ [Battery
](#Battery)
@@ -638,6 +641,26 @@ Make batch requests for supported items
| --- | --- | --- |
| paths | Array.<String>
| A list of paths of endpoints to send requests to. |
+
+
+### vehicle.request(method, path, body, headers) ⇒ [Response
](#Response)
+General purpose method to make a request to a Smartcar endpoint.
+
+**Kind**: instance method of [Vehicle
](#Vehicle)
+**Throws**:
+
+- [SmartcarError
](#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 | String
| The HTTP request method to use. |
+| path | String
| The path to make the request to. |
+| body | Object
| The request body. |
+| headers | Object
| The headers to inlcude in the request. |
+
### vehicle.vin() ⇒ [Vin
](#Vin)
@@ -936,6 +959,28 @@ the following fields :
"location" : function() => returns odometer location or throws SmartcarError,
}
```
+
+
+## Response : Object
+**Kind**: global typedef
+**Properties**
+
+| Name | Type | Description |
+| --- | --- | --- |
+| body | String
| The response body |
+| meta | [Meta
](#Meta) | |
+
+**Example**
+```js
+{
+ body: { distance: 59801.6373441601 },
+ meta: {
+ dataAge: 2022-01-20T02:55:25.041Z,
+ unitSystem: 'imperial',
+ requestId: 'f787849d-d228-482d-345f-459a5154sg73'
+ }
+}
+```
## Meta : Object
diff --git a/lib/vehicle.js b/lib/vehicle.js
index 353a44c..72353f9 100644
--- a/lib/vehicle.js
+++ b/lib/vehicle.js
@@ -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}
diff --git a/test/end-to-end/vehicle.js b/test/end-to-end/vehicle.js
index ad091dd..6a745eb 100644
--- a/test/end-to-end/vehicle.js
+++ b/test/end-to-end/vehicle.js
@@ -153,7 +153,7 @@ test('vehicle odometer', async(t) => {
'distance',
'meta',
]),
- [],
+ []
);
t.truthy(typeof response.distance === 'number');
@@ -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(
@@ -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(
diff --git a/test/unit/lib/vehicle.js b/test/unit/lib/vehicle.js
index 077ac45..3e6e197 100644
--- a/test/unit/lib/vehicle.js
+++ b/test/unit/lib/vehicle.js
@@ -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');
+});