I'm writing an API wrapper for an external API, to be used in our application.
I have adopted a test-driven approach for this project but since I have little to no experience with writing API wrappers, I'm not sure if I'm on the right track.
I understand that I should not be testing the external API, nor should I be hitting the network in my tests. I'm using Nock to mock my requests to the API.
However, I'm not sure I'm doing this correctly.
I made some requests to the API using curl and put the (XML) response in a file, for example: /test/fixtures/authentication/error.js:
module.exports = "<error>Authorization credentials failed.</error>"
Since I don't want to hit the network, but want to make sure my wrapper parses the XML to JSON, I figured I needed sample data.
My test looks like this:
describe("with an invalid application key", function() {
var cl, api;
before(function(done) {
api = nock(baseApi)
.get('/v1/auth/authenticate')
.reply(200, fixtures.authentication.error);
done();
});
after(function(done) {
nock.cleanAll();
done();
});
it("returns an error", function(done) {
cl = new APIClient(auth.auth_user, auth.auth_pass, "abcd1234");
cl.authenticate(function(err, res) {
should.exist(err);
err.should.match(/Authorization credentials failed./);
should.not.exist(res);
api.isDone().should.be.true;
done();
});
});
});
With my tested code looking like this:
APIClient.prototype.authenticate = function(callback) {
var self = this;
request({
uri: this.httpUri + '/auth/authenticate',
method: 'GET',
headers: {
auth_user: this.user,
auth_pass: this.pass,
auth_appkey: this.appkey
}
}, function(err, res, body) {
if (err) {
return callback('Could not connect to the API endpoint.');
}
self.parser.parseXML(body, function(err, result) {
if (err) { return callback(err); }
if (result.error) { return callback(result.error); }
self.token = result.auth.token[0];
return callback(null, result);
});
});
};
Now, this seems to be working fine for the authentication side of things (I also have a 'success' fixture, which returns the 'success' XML and I check if the returned JSON is actually correct.
However, the API I'm using also has endpoints like:
/data/topicdata/realtime/:reportxhours/:topics/:mediatypes/:pageIndex/:pageSize
I'm not sure how to test all (should I?) possible combinations with URLs like those. I feel like I can hardly put 30 XML responses in my fixtures directory. Also, when mocking responses, I'm afraid to miss out on possible errors, edge cases, etc. the external API might return. Are these valid concerns?
If anyone has any pointers, and/or knows of any open-source and well-tested API wrappers I could take a look at, I'd be very grateful.
I think your concern is very valid and I suggest you to also build tests using Zombie or other simular request-based testing frameworks.
Related
I am looking to use supertest to test API requests and responses. Following is what I have tried so far.
route.test.js
const testUtils = require('./setupTestUtils');
let authenticateUser = request.agent(app);
before(function(done){
testUtils.login(authenticateUser, userCredentials).then((res) => {
expect(res.statusCode).to.equal(200);
done();
}, (err) => {
console.log(err);
done(err);
});
});
setupTestUtils.js
function login (rest, testUserLogin) {
let defer = Q.defer();
rest.post('/login')
.send(testUserLogin)
.expect(200)
.end(function () {
rest.get('/loggedin')
.expect((res) => {
if (err) {
console.log('ERROR: ' + JSON.stringify(err));
defer.reject(err);
} else {
defer.resolve(res);
}
})
.end();
});
return defer.promise;
}
In my app.js, I use passport to authenticate. After authentication, I use the session.regenerate function to regenerate the session ID to avoid session fixation.
The initial post request to login passes without any failure. However, the subsequent GET request 'loggedIn' fails. This function internally uses the req.isAuthenticated() function from passport. This always returns false.
On investigation, I found that the session ID between the regenerated session and the request object (for req.isAuthenticated()) is different.
From my search, I understand that the cookies should be maintained automatically by the use of 'agent' from supertest. However that doesnt seem to be the case for me. I have also tried maintaining the cookies from the initial response. That doesnt seem to work for me either. " res.headers['set-cookie'] " comes in as undefined (not sure why that is happening either).
Can someone please help me understand what I am missing here.?
Am using versions - Supertest #v6.0.1 and passport #v0.4.1
I found the solution to my issue in an old github issue raised on supertest's page. Linking it here for reference.
Essentially, the supertest runs express in insecure port and I had configured my session otherwise. Ideally, we would have to check the environment before setting this variable to false - as represented here.
Hope this saves someone the time I spent!
I have a meteor application hosted on modulus.
Modulus has an api here: https://api.modulus.io/
The API code is hosted here: https://github.com/onmodulus/modulus-cli/blob/master/lib/librarian/librarian.js#L361
I want to save a domain to one of my deployments on modulus through the API.
I know this is the code I am looking to connect to: https://github.com/onmodulus/modulus-cli/blob/master/lib/librarian/librarian.js#L361
librarian.project.saveDomains = function(projectId, domains, authToken, callback) {
if(checkInit(callback)) {
librarian._http.request(util.format('/project/%s/domains?authToken=%s', projectId, authToken), 'POST', domains, callback);
}
};
And I am new to making API calls.
I believe I need to make the call from the server in my meteor app, which could be handled using a meteor method, but I don't know what the API call should look like.
I've researched online and on SO. No luck and I am battling a lack of experience with API calls so I am looking from a little direction.
I've added the http package for meteor:
meteor add http
I think the following is in the ball park, but not currently working:
POST
javascript
//save arguments object to options variable
var options = {
projectId: xxx,
domains: ["example.com"],
authToken: settings.Modulus.authToken
}
//call meteor method from client
Meteor.call('saveDomainToModulus', options, function(error, result) {
if (error)
console.log(error)
else
console.log(result)
}
});
//on server
Meteor.methods({
"saveDomainToModulus": function(options) {
var apiEndpoint = "http://api.modulus.io/project/%s/domains?authToken=" + options.authToken;
HTTP.post( apiEndpoint,
options.projectId,
options.domains,
options.authToken,
function( error, response ) {
if ( error ) {
console.log( error );
} else {
console.log( response );
}
});
}
})
Feels like I'm starting to close in on a solution, but if anyone with more experience has any feedback, let me know.
I’ve been writing tests for my loopback backend using loopback-testing project.
The backend has set loopback-component-storage in order to provide apis to store files in the filesystem. I want to test file upload using the remote api that loopback-component-storage provides using something like this:
describe('Containers', function() {
lt.it.shouldBeAllowedWhenCalledByUserWithRole(TEST_USER, someRole,
'POST', '/api/containers/somecontainer/upload', somefile);
});
But with no luck... There's no documentation about this. I don't know if it is even possible to test. Any ideas?
Thanks in advance
Some links:
https://github.com/strongloop/loopback-testing
https://github.com/strongloop/loopback-component-storage
loopback-testing is currently deprecated.
You should consider using supertest instead. It relies on superagent, and allows you to perform http requests on your REST api and assert on response objects.
Then, you can use the attach method of super-agent to build a multipart-form-data request that can contain a file.
Code using mocha for describing test looks then like this:
var request = require('supertest');
var fs = require('fs');
var app = require('./setup-test-server-for-test.js');
function json(verb, url) {
return request(app)[verb](url)
.set('Content-Type', 'multipart/form-data');
};
describe("User",function() {
it("should be able to add an asset to the new project", function(done){
var req = json('post', '/api/containers/someContainer/upload?access_token=' + accessToken)
.attach("testfile","path/to/your/file.jpg")
.expect(200)
.end(function(err, res){
if (err) return done(err);
done();
});
});
it("should have uploaded the new asset to the project folder", function(done){
fs.access('/path/to/your/file.jpg', fs.F_OK, function(err){
if (err) return done(err);
done();
});
});
};
I'm trying to write tests for my npm module, which takes care of communicating with my backend api. this module will sit inside a cordova android app, and will take care of any api calls. the issue that i'm having seems to be an understanding of mocha, but i've had a good look around the internet and can't find a solution so i turn to the masses. As an example, i have a function like
test: function() {
request.get({
url: defaultHost,
headers: {
}
}, function(err, httpResponse, body) {
if(err) {
return err;
} else {
console.log(body);
return body;
}
});
}
this works will. i'm now trying to create the test for it in mocha. the problem that i'm getting is that i have no idea how to get the return function from the .get call into the mocha test. the api returns json, so i know that i'm going to have to be doing an is equal comparison, but at the moment i can't even get it to print the results. i think the problem with is that with my other mocha tests that i can get working, they all have an argument that you pass in where as this doesn't. my current code for the test looks like this.
describe('#test', function() {
it('tests api reachability;', function() {
var test = new test();
});
});
if someone can explain what is required afterwards or even just point me in the right direction on google, that would be awesome. i'm normally pretty good at the google, but failing to find direction on this one.
I think nock will solve this issue. Let's assume you sending get request to some resource (http://domain.com/resource.json) and the tests would be like this:
var nock = require('nock');
// ...
describe('#test', function() {
beforeEach(function () {
nock('http://domain.com')
.get('resource.json')
.reply(200, {
message: 'some message'
});
});
it('tests api reachability;', function() {
var test = new test();
});
});
I am using Node.js and the request module to create a backend, and we've chose Elasticsearch as our data storage. All fine so far, except it seems Node doesn't support request bodies on GET requests? This is necessary for Elasticsearch's _search API, which expects only GET requests as part of their semantic design. Is there a solution to force Node to send the request body even in the cases of a GET request, or a mean to use _search on Elasticsearch with another HTTP verb?
function elasticGet(url, data) {
data = data || {};
return Q.nfcall(request.get, {
uri: url,
body: JSON.stringify(data) //<-- noop
}).then(function(response) {
return JSON.parse(response[1]);
}, function(err) {
console.log(err);
});
}
The _search API also accepts the POST verb.
For simplicity, why not use their api rather than manually making requests?
simple example:
var elasticsearch = require('elasticsearch'),
client = new elasticsearch.Client({
host: '127.0.0.1:9200',
log: 'trace'
});
client.search({
index: '[your index]',
q: 'simple query',
fields: ['field']
}, function (err, results) {
if (err) next(err);
var ids = []
if (results && results.hits && results.hits.hits) {
ids = results.hits.hits.map(function (h) {
return h._id;
})
}
searchHandler(ids, next)
})
You can combine it with fullscale labs elastic.js to build really complex queries, really fast.
https://github.com/fullscale/elastic.js
I had such an issue a few days ago.
tld;dr use POST
According to https://www.elastic.co/guide/en/elasticsearch/guide/current/_empty_search.html#get_vs_post you can also use POST with elastic.
I tried it with axios but it returns all data like with no body.
So I used POST instead. It works for me and I hope it will help to someone else.