How to test a JSON REST API - ruby

I'm brand new to ruby (first day working with ruby) so please forgive any novice questions and lack of understanding.
I'm trying to validate the responses to http callouts.
For example, let's say the endpoint is the following:
https://applicationname-api-sbox02.herokuapp.com
And, I'm trying to authenticate a user by sending a get request like this:
get_response = RestClient.get( "https://applicationname-api-sbox02.herokuapp.com/api/v1/users",
{
"Content-Type" => "application/json",
"Authorization" => "token 4d012314b7e46008f215cdb7d120cdd7",
"Manufacturer-Token" => "8d0693ccfe65104600e2555d5af34213"
}
)
Now, I want to validate the response and do the following:
- parse the response to ensure that it is valid JSON
- do some validation and verify the JSON has the correct data (verify that id == 4 for example)
- if an error is encountered, raise an exception using the 'raise' method.
In my first feeble attempt I tried the following:
puts get_response.body
if get_response.code == 200
puts "********* Get current user successful"
else
puts "Get current user failed!!"
end
Now, this returned that getting the current user was successful, but how do I actually parse the json, verify the correct id, and raise an exception if an error occurred?

Instead of raising an exception, write a test.
A straightforward approach, using the json parser and unit test framework from the std lib:
require 'minitest/autorun'
require 'rest_client'
require 'json'
class APITest < MiniTest::Unit::TestCase
def setup
response = RestClient.get("https://applicationname-api-sbox02.herokuapp.com/api/v1/users",
{
"Content-Type" => "application/json",
"Authorization" => "token 4d012314b7e46008f215cdb7d120cdd7",
"Manufacturer-Token" => "8d0693ccfe65104600e2555d5af34213"
}
)
#data = JSON.parse response.body
end
def test_id_correct
assert_equal 4, #data['id']
end
end
Execute with ruby $filename
JSON.parse parses a JSON string into a ruby hash
Getting started with minitest
If you are using ruby 1.8, you'll need to install the json gem and either install the minitest gem, or switch to the older testunit API. If you choose the latter, then you'll need to change require 'minitest/autorun' -> require 'test/unit' and MiniTest::Unit::TestCase -> Test::Unit::TestCase

I'm a little late to the party, but I recently co-created an rspec driven framework called Airborne for just this purpose. Check it out: https://github.com/brooklynDev/airborne

here is an example from our specs so you can see how we test json api:
it 'returns charge' do
get "/charges/#{charge.id}", '', headers
expect(response.status).to eq(200)
expect(response).to match_response_schema(:charge)
expect(response).to match_json(<<-JSON)
{
"id":"{id}",
"email": "{email}",
"ip": "127.0.0.1",
"amount": 10500,
"state": "captured",
"captured_amount": 10500,
}
JSON
end
Lets look at it closely
match_response_schema(:charge)
This matcher checks that json we get in response is in general valid. We use json-schema (json schema validator) for it. Guys from Thoughtbot have a detailed guide how to use json schema validator and create own matcher in this blog post.
Understanding JSON Schema is where I got a lot of useful information on how to create schemas for JSON documents.
match_json
This is our own matcher and we have released match_json gem recently. Using it you can test structure and values of your json. Here are two great features of this matcher:
if you don't know exact values, you can use patterns like {id}, {uuid} {date_time}, etc. we have predefined patterns but you can add your own too.
you get clear failure message what is wrong with your json e.g. "5" was not found in " > array":[1,2,3]

Parsing json can be done with the json gem: http://flori.github.com/json/
Parsed json is accessed through key/value just like in javascript. You can easily verify the values and conditionally raise errors.
Raising errors is done like so:
raise "the ID was #{id} instead of 4"
And writing unit tests can be done with Test::Unit - http://www.ruby-doc.org/stdlib-1.9.3/libdoc/test/unit/rdoc/Test/Unit.html

Related

Using parsed response in separate GET call

I'm new to Ruby and API, so my apologies if this is super simple...
I need to have script that will first POST to initiate the creation of an export file, and then have a GET call to retrieve the file. The GET call needs to use part of the POST json response.
I'm using the httparty gem.
I think I need to create a variable that equals the parsed json, and then make that variable part of the GET call, but I'm not clear on how to do that.
Help is appreciated.
require 'httparty'
url = 'https://api.somewhere.org'
response = HTTParty.post(url)
puts response.parse_response
json response:
export_files"=>
{"id"=> #####,
"export_id"=> #####,
"status"=>"Queued"}}
In my GET call I need to use the export_id number in the url.
HTTParty.get('https://api.somewhere.org/export_id/####')
As described in the comments but a bit more verbose and skeleton for error:
require 'httparty'
require 'json'
url = 'https://api.somewhere.org'
response = HTTParty.post(url)
if hash = JSON.parse(response.body)
if export_id = hash[:export_files][:export_id]
post = HTTParty.post("https://api.somewhere.org/export_id/#{export_id}")
end
else
# handle error
end

Faraday::ParsingError - 757: unexpected token

I'm writing an API Wrapper and running into an issue that I'm not exactly sure how to solve.
Here's the Faraday setup:
#connection = Faraday.new(url: #api_url, params: params,
headers: default_headers,
ssl: { verify: true } ) do |faraday|
faraday.use FaradayMiddleware::Mashify
faraday.use FaradayMiddleware::ParseJson, content_type: /\bjson$/
faraday.use FaradayMiddleware::FollowRedirects
faraday.adapter Faraday.default_adapter
end
This works for 95% of the API calls I'm making - it parses JSON just like I need it to and Mashify's it. Great.
The issue is when the API returns a JSON value NOT wrapped in a JSON object. The API call is this:
https://hacker-news.firebaseio.com/v0/maxitem.json
I can see this isn't a valid JSON object in jsonlint (screenshot below).
Do I need to reconfigure a new Faraday instance for this call alone? That seems somewhat redundant but it's clearly blowing up on the FaradayMiddleware::ParseJson class.
My RSpec test returns this:
Failure/Error: latest = client.max_item
Faraday::ParsingError:
757: unexpected token at '8438316'
Any help is appreciated.
According to the JSON grammar a bare number isn't JSON, it's just a number. So yeah it looks like their API is a little inconsistent and you need a different configuration for that method.

How to test XML file using RSpec?

I have an RSS feed that I am writing an RSpec test for. I want to test that the XML document has the correct nodes and structure. Unfortunately, I can't find any good examples of how to do this in a clean way. I have only found some half-implemented solutions and outdated blog posts. How can I test the structure of an XML document using RSpec?
Hi I can recommend you to use custom matcher for this.
require 'nokogiri'
RSpec::Matchers.define :have_xml do |xpath, text|
match do |body|
doc = Nokogiri::XML::Document.parse(body)
nodes = doc.xpath(xpath)
nodes.empty?.should be_false
if text
nodes.each do |node|
node.content.should == text
end
end
true
end
failure_message_for_should do |body|
"expected to find xml tag #{xpath} in:\n#{body}"
end
failure_message_for_should_not do |response|
"expected not to find xml tag #{xpath} in:\n#{body}"
end
description do
"have xml tag #{xpath}"
end
end
Full example can be found here
https://gist.github.com/Fivell/8025849
No longer necessary to roll your own. We deal this problem daily, using the equivalent-xml matcher at https://github.com/mbklein/equivalent-xml .
require 'rspec/matchers'
require 'equivalent-xml'
...
expect(node_1).to be_equivalent_to(node_2)
Has options for edge cases like whitespace-preservation.
Your other option is to use a formal XSD template for strict validation.
context 'POST #join' do
it 'does successfully hit join xml route' do
post :join,
format: :xml
response.content_type.should == "application/xml"
response.should be_ok
end
end
This worked for me. I didn't realize I had to pass format: :xml. My join route responds to /join.xml and I was testing that this was successful.
Give Approvals a try, it works with rspec, I have used for testing Json payload, and it is used with Minitest in exercism.io
EDIT
it "returns available traffic information around me" do
post '/search_traffic_around', {location: [-87.688219, 41.941149]}.to_json
output = last_response.body
options = {format: :json, name: 'traffic_around_location'}
Approvals.verify(output,options)
end
the JSON I am verifying against is located in spec/fixtures folder named traffic_around_location.approved.json
Implementation where the above snippet is pulled from is available here
How it works is you supply it an expected Payload, JSON, XML, TXT and HTML this I am sure it supports in spec/fixtures and when you run the test it checks to confirm that the payload received matches the expected(approved) payload the test would pass if it matches else the test fails

Why can't I post variables using HTTParty?

I'm trying to use HTTParty to manage requests to an API. These are the instructions from the documentation:
url: https://www.havelockinvestments.com/r/orderbook
Required Post Variables symbol: "VTX"
Return Data
status: Contains 'ok' or 'error'
message: Contains error message if applicable
bids: Contains Bids array, as price=>amount pairs
asks: Contains Asks array, as price=>amount pairs
This is what I'm putting in my Ruby script:
require 'httparty'
response = HTTParty.post(
'https://www.havelockinvestments.com/r/orderbook',
:query => { :symbol => "VTX" }
)
But I'm getting an error response:
{"status":"error","message":"post:symbol is required"}
What am I doing wrong here when posting the symbol variable?
The original documentation is at: https://www.havelockinvestments.com/apidoc.php
Documentation seems a bit sparse on the HTTParty Github page, but from the examples it looks like you specify the parameters in a hash as a value to the :body key in the options for HTTParty#post
Like so:
response = HTTParty.post('https://www.havelockinvestments.com/r/orderbook', {body: {symbol: "VTX"}})

Working with Ruby and APIs

I am pretty new to working with Ruby, especially with APIs but I've been trying to get the Darksky API to work, but I'm afraid I'm missing something obvious with how I'm using it.
Here is what I have
require 'darksky'
darksky = Darksky::API.new('my api key')
forecast = darksky.forecast('34.0500', '118.2500')
forecast
When I run this from the command line nothing happens. What am I doing wrong here?
Simply using forecast isn't going to do anything. You need to use puts at a minimum:
puts forecast
Or, see if Ruby's object pretty-printer can return something more interesting:
require 'pp'
pp forecast
Digging in further, I think their API doesn't work. Based on their examples, using a valid key and their location samples, plus the locations from their source site Forecast.io, also returns nil.
Using the REST interface directly from Forecast.io's site does return JSON. JSON is very easy to work with in Ruby, so it's a good way to go.
Here's some code to test the API, and Forecast.io's REST interface:
API_KEY = 'xxxxxxxxxxxxxxxxxxx'
LOCATION = %w[37.8267 -122.423]
require 'darksky'
darksky = Darksky::API.new(API_KEY)
forecast = darksky.forecast(*LOCATION)
forecast # => nil
brief_forecast = darksky.brief_forecast(*LOCATION)
brief_forecast # => nil
require 'json'
require 'httparty'
URL = "https://api.forecast.io/forecast/#{ API_KEY }/37.8267,-122.423"
puts URL
# >> https://api.forecast.io/forecast/xxxxxxxxxxxxxxxxxxx/37.8267,-122.423
puts HTTParty.get(URL).body[0, 80]
# >> {"latitude":37.8267,"longitude":-122.423,"timezone":"America/Los_Angeles","offse
Notice that LOCATION is 37.8267,-122.423 in both cases, which is Alcatraz according to the Forecast.io site. Also notice that the body output displayed is a JSON string.
Pass the returned JSON to the Ruby's JSON class like:
JSON[returned_json]
to get it parsed back into a Ruby Hash. Using OpenURI (because it comes with Ruby) instead of HTTParty, and passing it to JSON for parsing looks like:
body = open(URL).read
puts JSON[body]

Resources