Skip to content

Commit

Permalink
Merge pull request #165 from Nexmo/search-sms
Browse files Browse the repository at this point in the history
Add ability to search SMS
  • Loading branch information
AlexLakatos authored Dec 2, 2017
2 parents 194f711 + 31ad18b commit 4d92d22
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 17 deletions.
17 changes: 15 additions & 2 deletions src/HttpClient.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
var https = require("https");
var http = require("http");
var querystring = require("querystring");

class HttpClient {
constructor(options) {
constructor(options, credentials) {
this.credentials = credentials;
this.host = options.host || "rest.nexmo.com";
this.port = options.port || 443;
this.https = options.https || https;
this.http = options.http || http;
Expand Down Expand Up @@ -32,7 +35,7 @@ class HttpClient {
// headers['Content-Length'] = 0;
}
var options = {
host: endpoint.host ? endpoint.host : "rest.nexmo.com",
host: endpoint.host ? endpoint.host : this.host,
port: this.port,
path: endpoint.path,
method: endpoint.method,
Expand Down Expand Up @@ -163,6 +166,16 @@ class HttpClient {
callback(error, response);
}
}

get(path, params, callback) {
params = params || {};
params["api_key"] = this.credentials.apiKey;
params["api_secret"] = this.credentials.apiSecret;

path = path + "?" + querystring.stringify(params);

this.request({ path: path }, "GET", callback);
}
}

export default HttpClient;
9 changes: 9 additions & 0 deletions src/Message.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ class Message {
shortcodeMarketing() {
this._nexmo.shortcodeMarketing.apply(this._nexmo, arguments);
}

search(id, callback) {
if (typeof id == "string") {
return this.options.rest.get("/search/message", { id: id }, callback);
}

// Otherwise we expect an array
return this.options.rest.get("/search/messages", { ids: id }, callback);
}
}

export default Message;
17 changes: 16 additions & 1 deletion src/Nexmo.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,22 @@ class Nexmo {
if (this.options.appendToUserAgent) {
this.options.userAgent += ` ${this.options.appendToUserAgent}`;
}
this.options.httpClient = new HttpClient(this.options);

// This is legacy, everything should use rest or api going forward
this.options.httpClient = new HttpClient(
Object.assign({ host: "rest.nexmo.com" }, this.options),
this.credentials
);

// We have two different hosts, so we use two different HttpClients
this.options.api = new HttpClient(
Object.assign({ host: "api.nexmo.com" }, this.options),
this.credentials
);
this.options.rest = new HttpClient(
Object.assign({ host: "rest.nexmo.com" }, this.options),
this.credentials
);

this.message = new Message(this.credentials, this.options);
this.voice = new Voice(this.credentials, this.options);
Expand Down
152 changes: 152 additions & 0 deletions test/Message-test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import Message from "../lib/Message";
import Credentials from "../lib/Credentials";
import HttpClient from "../lib/HttpClient";
import NullLogger from "../lib/ConsoleLogger.js";

import NexmoStub from "./NexmoStub";
import ResourceTestHelper from "./ResourceTestHelper";

import sinon from "sinon";
import chai, { expect } from "chai";

var smsAPIs = {
sendBinaryMessage: "sendBinaryMessage",
Expand All @@ -20,3 +27,148 @@ describe("Message Object", function() {
NexmoStub.checkAllFunctionsAreCalled(smsAPIs, Message);
});
});

describe("Message", function() {
beforeEach(function() {
var creds = Credentials.parse({
apiKey: "myKey",
apiSecret: "mySecret"
});

this.httpClientStub = new HttpClient(
{
logger: new NullLogger()
},
creds
);

sinon.stub(this.httpClientStub, "request");

var options = {
rest: this.httpClientStub
};

this.message = new Message(creds, options);
});

describe("#search", function() {
it("should call the correct endpoint (single)", function(done) {
this.httpClientStub.request.yields(null, {});

var expectedRequestArgs = ResourceTestHelper.requestArgsMatch({
path: "/search/message?id=0D00000068264896"
});

this.message.search(
"0D00000068264896",
function(err, data) {
expect(this.httpClientStub.request).to.have.been.calledWith(
sinon.match(expectedRequestArgs)
);

done();
}.bind(this)
);
});

it("should call the correct endpoint (multiple)", function(done) {
this.httpClientStub.request.yields(null, {});

var expectedRequestArgs = ResourceTestHelper.requestArgsMatch({
path: "/search/messages?ids=1&ids=2"
});

this.message.search(
[1, 2],
function(err, data) {
expect(this.httpClientStub.request).to.have.been.calledWith(
sinon.match(expectedRequestArgs)
);

done();
}.bind(this)
);
});

it("returns data on a successful request (single)", function(done) {
const mockData = {
"message-id": "0D00000068264896",
"account-id": "abc123",
network: "23430",
from: "TestTest",
to: "442079460000",
body: "Hello",
price: "0.03330000",
"date-received": "2017-11-24 15:09:30",
"final-status": "DELIVRD",
"date-closed": "2017-11-24 15:09:45",
latency: 14806,
type: "MT"
};

this.httpClientStub.request.yields(null, mockData);
this.message.search("0D00000068264896", function(err, data) {
expect(err).to.eql(null);
expect(data).to.eql(mockData);
done();
});
});

it("returns data on a successful request (multiple)", function(done) {
const mockData = {
count: 1,
items: [
{
"message-id": "0D00000068264896",
"account-id": "abc123",
network: "23430",
from: "TestTest",
to: "442079460000",
body: "Hello",
price: "0.03330000",
"date-received": "2017-11-24 15:09:30",
"final-status": "DELIVRD",
"date-closed": "2017-11-24 15:09:45",
latency: 14806,
type: "MT"
}
]
};

this.httpClientStub.request.yields(null, mockData);
this.message.search(["0D00000068264896"], function(err, data) {
expect(err).to.eql(null);
expect(data).to.eql(mockData);
done();
});
});

it("returns an error when the connection fails", function(done) {
const mockError = {
body: {
"error-code": "401",
"error-code-label": "authentication failed"
},
headers: {
"content-type": "application/json;charset=UTF-8",
date: "Thu, 30 Nov 2017 14:41:50 GMT",
server: "nginx",
"strict-transport-security": "max-age=31536000; includeSubdomains",
"x-frame-options": "deny",
"x-nexmo-trace-id": "91f401d459aa5050af280aee53288135",
"x-xss-protection": "1; mode=block;",
"content-length": "63",
connection: "close"
},
statusCode: 401
};

this.httpClientStub.request.yields(mockError, null);
this.message.search("0D00000068264896", function(err, data) {
expect(err).to.eql(mockError);
expect(data).to.eql(null);
done();
});
});
});
});
62 changes: 48 additions & 14 deletions test/ResourceTestHelper.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import querystring from "querystring";

class ResourceTestHelper {
static getRequestArgs(params, overrides = {}) {
var callsArgs = {
Expand Down Expand Up @@ -25,20 +27,52 @@ class ResourceTestHelper {

static requestArgsMatch(params, requestOverrides) {
return function(actual) {
var expected = ResourceTestHelper.getRequestArgs(
params,
requestOverrides
);

var match =
expected.host === actual.host &&
expected.path === actual.path &&
expected.method === actual.method &&
expected.body === actual.body &&
expected.headers["Content-Type"] === actual.headers["Content-Type"] &&
actual.headers["Authorization"].indexOf(
expected.headers["Authorization"]
) === 0;
var expected;
if (requestOverrides) {
expected = ResourceTestHelper.getRequestArgs(params, requestOverrides);
} else {
expected = params;
}

// We strip api_key and api_secret out of `path` so that our tests
// only look for specific parameters
var qs = actual.path.split("?");
var qsParts = querystring.parse(qs[1]);
delete qsParts["api_key"];
delete qsParts["api_secret"];
if (Object.keys(qsParts).length) {
actual.path = qs[0] + "?" + querystring.stringify(qsParts);
}

var match = true;

// Check response parameters
["host", "path", "method", "body"].forEach(function(k) {
if (expected[k]) {
match = match && expected[k] == actual[k];
}
});

// Also check for any headers that we're expecting
expected.headers = expected.headers || {};
Object.keys(expected.headers).forEach(function(k) {
// We have a special check for authorization
if (k === "Authorization") {
return true;
}
match = match && expected.headers[k] === actual.headers[k];
});

// For Authorization we only check the beginning as JWTs are
// dynamically created
if (expected.headers["Authorization"]) {
match =
match &&
actual.headers["Authorization"].indexOf(
expected.headers["Authorization"]
) === 0;
}

return match;
};
}
Expand Down

0 comments on commit 4d92d22

Please sign in to comment.