BDD for secure REST APIs using cucumber, pickle, and rack-test

The moving parts of tddium communicate with each other using a JSON REST protocol.  The protocol is implemented using an API server written in Rails 3.  The entire rails app is covered with cucumber and rspec tests, and we don’t make changes without accompanying test coverage.

Having reliable testing has been a pleasure.  It has allowed us to iterate quickly and make changes with confidence.  It’s especially useful to encode our access control policies and ensure that our API security model behaves as it should.

More of a pleasure has been the set of testing tools we’ve used:

Our test suite covers, among other things:

  • Every single API route
  • Success and failure paths for authentication and access control
  • Success and failure paths for API parameters

This blog post was a great starting point for using Rack::Test to exercise a JSON API.  (Another great reference for Rack::Test methods is the Sinatra testing guide.)

We soon found that we needed more detailed expressions of how API calls interacted with database records and other system state.  We also implement finer-grained security roles, and we use an API-key for authentication instead of HTTP Basic auth.  Enter pickle, a toolkit for generating readable cucumber steps from ActiveRecord models and factories.  Pickle lets us access the factories we’ve created; it’s then easy to generate API requests with valid and invalid credentials.

Here’s an example scenario to ensure that one user’s test process can’t access a session that belongs to a different user:

[sourcecode language=”ruby”]
Scenario: Accessing a different user’s session should fail
Given I am a test process
And a session exists with user: the user
And another user exists
And another session exists with user: the second user
When I send a POST request to the second session’s update API with:
|param | value |
|stop_reason| tests finished |
Then the response should be "403"
And the JSON response should be:
"""
{"status": 1, "explanation": "Access denied"}
"""
And the first session’s stop_reason should not be "tests finished"
[/sourcecode]

Here’s a snip of the step definition for the example above:

[sourcecode language=”ruby”]
Given /^I am a test process$/ do
@user = Factory(:test_user)
@api_key = @user.test_api_key
header "X-tddium-api-key", @api_key
find_model!("user")
end
[/sourcecode]

Different roles are identified by different API keys.  The “test process” role in the scenario translates easily into the API key we’ll include in the test request.  We have different step definitions so scenarios can switch into other roles, like “end user” or “internal process”.  find_model! is a pickle method that binds the recently created user (@user) into pickle’s remembered objects.  Then, a step can refer to it as “the user”.  Pickle has a ton of built-in steps, but its strength is how easy it makes creating custom steps that manipulate database records.

We’ll describe more of our security model in an upcoming post.

Post a Comment