GithubHelp home page GithubHelp logo

airborne's Introduction

Airborne

airborne travis airborne coveralls Code Climate airborne gem version airbore gem downloads airborne gem stable downloads

RSpec driven API testing framework

Looking for Project Maintainers

I am looking for project maintainers to help keep airborne up to date and bug-free while avoiding feature creep and maintaining backwards compatibility.

Comment here if you would like to help out.

Installation

Install Airborne:

$ gem install airborne

Or add it to your Gemfile:

gem 'airborne'

Creating Tests

require 'airborne'

describe 'sample spec' do
  it 'should validate types' do
    get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" }
    expect_json_types(name: :string)
  end

  it 'should validate values' do
    get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" }
    expect_json(name: 'John Doe')
  end
end

When calling expect_json_types, these are the valid types that can be tested against:

  • :int or :integer
  • :float
  • :bool or :boolean
  • :string
  • :date
  • :object
  • :null
  • :array
  • :array_of_integers or :array_of_ints
  • :array_of_floats
  • :array_of_strings
  • :array_of_booleans or :array_of_bools
  • :array_of_objects
  • :array_of_arrays

If the properties are optional and may not appear in the response, you can append _or_null to the types above.

describe 'sample spec' do
  it 'should validate types' do
    get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" } or { "name" : "John Doe", "age" : 45 }
    expect_json_types(name: :string, age: :int_or_null)
  end
end

Additionally, if an entire object could be null, but you'd still want to test the types if it does exist, you can wrap the expectations in a call to optional:

it 'should allow optional nested hash' do
  get '/simple_path_get' #may or may not return coordinates
  expect_json_types('address.coordinates', optional(latitude: :float, longitude: :float))
end

Additionally, when calling expect_json, you can provide a regex pattern in a call to regex:

describe 'sample spec' do
  it 'should validate types' do
    get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" }
    expect_json(name: regex("^John"))
  end
end

When calling expect_json or expect_json_types, you can optionally provide a block and run your own rspec expectations:

describe 'sample spec' do
  it 'should validate types' do
    get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" }
    expect_json(name: -> (name){ expect(name.length).to eq(8) })
  end
end

Calling expect_json_sizes actually make use of the above feature and call expect_json under the hood:

describe 'sample spec' do
  it 'should validate types' do
    get 'http://example.com/api/v1/simple_get_collection' #json api that returns { "ids" : [1, 2, 3, 4] }
    expect_json_sizes(ids: 4)
  end
end

Making requests

Airborne uses rest_client to make the HTTP request, and supports all HTTP verbs. When creating a test, you can call any of the following methods: get, post, put, patch, delete, head, options. This will then give you access the following properties:

  • response - The HTTP response returned from the request
  • headers - A symbolized hash of the response headers returned by the request
  • body - The raw HTTP body returned from the request
  • json_body - A symbolized hash representation of the JSON returned by the request

For example:

it 'should validate types' do
  get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" }
  name = json_body[:name] #name will equal "John Doe"
  body_as_string = body
end

When calling any of the methods above, you can pass request headers to be used.

get 'http://example.com/api/v1/my_api', { 'x-auth-token' => 'my_token' }

For requests that require a body (post, put, patch) you can pass the body as well:

post 'http://example.com/api/v1/my_api', { :name => 'John Doe' }, { 'x-auth-token' => 'my_token' }

The body may be any JSON-serializable type, as long as you want to post application/json content type. You may set a different content type and post a string body this way:

post 'http://example.com/api/v1/my_api', "Hello there!", { content_type: 'text/plain' }

For requests that require Query params you can pass a params hash into headers.

post 'http://example.com/api/v1/my_api', { }, { 'params' => {'param_key' => 'param_value' } }

(Not) Verifying SSL Certificates

SSL certificate verification is enabled by default (specifically, OpenSSL::SSL::VERIFY_PEER).

Carefully consider how you use this. It's not a solution for getting around a failed SSL cert verification; rather, it's intended for testing systems that don't have a legitimate SSL cert, such as a development or test environment.

You can override this behavior per request:

verify_ssl = false
post 'http://example.com/api/v1/my_api', "Hello there!", { content_type: 'text/plain' }, verify_ssl

or with a global Airborne configuration:

Airborne.configure do |config|
  config.verify_ssl = false # equivalent to OpenSSL::SSL::VERIFY_NONE
end

Note the per-request option always overrides the Airborne configuration:

before do
  Airborne.configuration.verify_ssl = false
end

it 'will still verify the SSL certificate' do
  verify_ssl = true
  post 'http://example.com/api/v1/my_api', "Hello there!", { content_type: 'text/plain' }, verify_ssl
end

You can use the verify_ssl setting to override your global defaults in test blocks like this:

describe 'test something', verify_ssl: false do
end

OR

describe 'test something' do
  Airborne.configuration.verify_ssl = false
end

This feature currently isn't supported when testing loaded Rack applications (see "Testing Rack Applications" below). If you need to set verify_ssl: false, then we recommend starting your Rack app server and sending the airborne HTTP requests as you would when testing any other service.

Testing Rack Applications

If you have an existing Rack application like sinatra or grape you can run Airborne against your application and test without actually having a server running. To do that, just specify your rack application in your Airborne configuration:

Airborne.configure do |config|
  config.rack_app = MySinatraApp
end

Under the covers, Airborne uses rack-test to make the requests.

Rails Applications

If you're testing an API you've written in Rails, Airborne plays along with rspec-rails:

require 'rails_helper'

RSpec.describe HomeController, :type => :controller do
  describe 'GET index' do
    it 'returns correct types' do
      get :index, :format => 'json' #if your route responds to both html and json
      expect_json_types(foo: :string)
    end
  end
end

API

  • expect_json_types - Tests the types of the JSON property values returned
  • expect_json - Tests the values of the JSON property values returned
  • expect_json_keys - Tests the existence of the specified keys in the JSON object
  • expect_json_sizes - Tests the sizes of the JSON property values returned, also test if the values are arrays
  • expect_status - Tests the HTTP status code returned
  • expect_header - Tests for a specified header in the response
  • expect_header_contains - Partial match test on a specified header

Path Matching

When calling expect_json_types, expect_json, expect_json_keys or expect_json_sizes you can optionally specify a path as a first parameter.

For example, if our API returns the following JSON:

{
  "name": "Alex",
  "address": {
    "street": "Area 51",
    "city": "Roswell",
    "state": "NM",
    "coordinates": {
      "latitude": 33.3872,
      "longitude": 104.5281
    }
  }
}

This test would only test the address object:

describe 'path spec' do
  it 'should allow simple path and verify only that path' do
    get 'http://example.com/api/v1/simple_path_get'
    expect_json_types('address', street: :string, city: :string, state: :string, coordinates: :object)
    #or this
    expect_json_types('address', street: :string, city: :string, state: :string, coordinates: { latitude: :float, longitude: :float })
  end
end

Or, to test the existence of specific keys:

it 'should allow nested paths' do
  get 'http://example.com/api/v1/simple_path_get'
  expect_json_keys('address', [:street, :city, :state, :coordinates])
end

Alternatively, if we only want to test coordinates we can dot into just the coordinates:

it 'should allow nested paths' do
  get 'http://example.com/api/v1/simple_path_get'
  expect_json('address.coordinates', latitude: 33.3872, longitude: 104.5281)
end

When dealing with arrays, we can optionally test all (*) or a single (? - any, 0 - index) element of the array:

Given the following JSON:

{
  "cars": [
    {
      "make": "Tesla",
      "model": "Model S"
    },
    {
      "make": "Lamborghini",
      "model": "Aventador"
    }
  ]
}

We can test against just the first car like this:

it 'should index into array and test against specific element' do
  get '/array_api'
  expect_json('cars.0', make: 'Tesla', model: 'Model S')
end

To test the types of all elements in the array:

it 'should test all elements of the array' do
  get 'http://example.com/api/v1/array_api'
  expect_json('cars.?', make: 'Tesla', model: 'Model S') # tests that one car in array matches the tesla
  expect_json_types('cars.*', make: :string, model: :string) # tests all cars in array for make and model of type string
end

* and ? work for nested arrays as well. Given the following JSON:

{
  "cars": [
    {
      "make": "Tesla",
      "model": "Model S",
      "owners": [
        {
          "name": "Bart Simpson"
        }
      ]
    },
    {
      "make": "Lamborghini",
      "model": "Aventador",
      "owners": [
        {
          "name": "Peter Griffin"
        }
      ]
    }
  ]
}

===

it 'should check all nested arrays for specified elements' do
  get 'http://example.com/api/v1/array_with_nested'
  expect_json_types('cars.*.owners.*', name: :string)
end

Dates

JSON has no support for dates, however airborne gives you the ability to check for dates using the following. For expect_json_types you would use it as you would for any of the other types:

it 'should verify date type' do
  get '/get_date' #api that returns {createdAt: "Mon Oct 20 2014 16:10:42 GMT-0400 (EDT)"}
  expect_json_types(createdAt: :date)
end

However if you want to check the actual date data with expect_json, you need to call the date function:

it 'should verify correct date value' do
  get '/get_date' #api that returns {createdAt: "Mon Oct 20 2014 16:10:42 GMT-0400 (EDT)"}
  prev_day = DateTime.new(2014,10,19)
  next_day = DateTime.new(2014,10,21)
  #within the date callback, you can use regular RSpec expectations that work with dates
  expect_json(createdAt: date { |value| expect(value).to be_between(prev_day, next_day) })
end

Configuration

When setting up Airborne, you can call configure just like you would with rspec:

#config is the RSpec configuration and can be used just like it
Airborne.configure do |config|
  config.include MyModule
end

Additionally, you can specify a base_url and default headers to be used on every request (unless overridden in the actual request):

Airborne.configure do |config|
  config.base_url = 'http://example.com/api/v1'
  config.headers = { 'x-auth-token' => 'my_token' }
end

describe 'spec' do
  it 'now we no longer need the full url' do
    get '/simple_get'
    expect_json_types(name: :string)
  end
end

You can also control the strictness of expect_json and expect_json_types with the global settings match_expected_default and match_actual_default like this.

Airborne.configure do |config|
  config.match_expected_default = true
  config.match_actual_default = false
end

match_expected_default requires all the keys in the expected JSON are present in the response. match_actual_default requires that the keys in the response are tested in the expected Hash.

So you can do the following combinations:

match_expected_default=false, match_actual_default=false - check only intersection match_expected_default=false, match_actual_default=true - raise on extra key in response match_expected_default=true, match_actual_default=false - raise on missing key in response match_expected_default=true, match_actual_default=true - expect exact match

Airborne sets match_expected_default to true and match_actual_default to false by default.

You can use the match_expected and match_actual settings to override your global defaults in test blocks like this.

describe 'test something', match_expected: true, match_actual: false do
end

OR

describe 'test something' do
  Airborne.configuration.match_expected = true
  Airborne.configuration.match_actual = false
end

Run it from the CLI

$ cd your/project
$ rspec spec

Authors

Contributors

https://github.com/brooklynDev/airborne/graphs/contributors

Inspired by frisby.js

License

The MIT License

Copyright (c) 2014 brooklyndev, sethpollack

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

airborne's People

Contributors

balvig avatar brooklyndev avatar carhartl avatar cmckni3 avatar croeck avatar cvortmann avatar dijonkitchen avatar grzesiek avatar iyedb avatar jgwmaxwell avatar johanneswuerbach avatar josephgrossberg avatar jsvisa avatar kenchan avatar leoarnold avatar mcasper avatar mcordell avatar mparramont avatar mycargus avatar pikachuexe avatar rdalverny avatar rosskevin avatar sethpollack avatar shekibobo avatar stefan-kolb avatar tabermike avatar thaivz avatar tikotzky avatar tit avatar wykhuh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

airborne's Issues

How send POST request with form data?

Hello. I want send POST request with form data.
All my requests don't send request with :form.
Why?

# encoding: utf-8
require 'airborne'
describe '' do
  it '' do
    post 'http://httpbin.org/post', {}, {params: {foo: 'bar'}}    
    puts json_body # {:args=>{:foo=>"bar"}, :data=>"{}", :files=>{}, :form=>{}, :headers=>{:Accept=>"*/*; q=0.5, application/xml", :"Accept-Encoding"=>"gzip, deflate", :"Content-Length"=>"2", :"Content-Type"=>"application/json", :Host=>"httpbin.org", :"User-Agent"=>"Ruby"}, :json=>{}, :origin=>"195.151.220.177", :url=>"http://httpbin.org/post?foo=bar"}
    post 'http://httpbin.org/post', {foo: 'bar'}
    puts json_body # {:args=>{}, :data=>"{\"foo\":\"bar\"}", :files=>{}, :form=>{}, :headers=>{:Accept=>"*/*; q=0.5, application/xml", :"Accept-Encoding"=>"gzip, deflate", :"Content-Length"=>"13", :"Content-Type"=>"application/json", :Host=>"httpbin.org", :"User-Agent"=>"Ruby"}, :json=>{:foo=>"bar"}, :origin=>"195.151.220.177", :url=>"http://httpbin.org/post"}
  end
end

Soooooory for my bad english. :(

Why no exact match matcher, what am I overlooking?

I'm a little baffled why there's no exact matcher.

In other words, if my api returns

{"public":"here","secret":"here"}

This happily matches (only matches part, I like it)

expect_json(public:'here')                                                                                                                  

But I'd expect something like this to exist

expect_json_exact(public:'here')                                                                                                                  

Maybe its not a secret, maybe its unnecessary data and you are trying to reduce bandwidth used.

Handling of empty arrays

I am using expect_json_types and expect_json and getting some unexpected results when testing with an empty array.

To illustrate, take the "test_responses/array_with_index.json" file and modify to be an empty array:

{
    "cars":[]
}

The following two expectations pass but I think they should be failing.

expect_json('cars.?', {make: 'Toyota'})
expect_json_types('cars.*', {some_key: :integer, name: :string})

ActiveSupport dependency

Hi,

Do you really need ActiveSupport >= 4.0.1 as dependency ?
I have a Rails 3.2 project and I want to use airbone but I can't because of this.

Jules

expect_json('@id' => '1234') throws errors

The following line throws errors because all the keys in json_body are symbols and not both symbols and strings: expect_json('@id' => '1234')

When the string is converted to a symbol the function works: expect_json(:@id => '1234')

In my case, it gets weirder looking when variables contain colons: expect_json(:'dc:title' => 'Unicorns')

Is there a reason why the keys in json_body shouldn't be both symbols and strings?

I think I know how to fix this, and can provide a pull request.

helper methods for dates

I'd like to check for certain dates and times in JSON output, but I'm running into problems comparing JavaScript Dates and Ruby DateTimes.

This is my current hack:

expect_json({
  ...
  createdAt: -> (created_at) { DateTime.parse(created_at) - user.created_at.to_datetime < 1000 * 60 },
  ...
})

Have any suggestions for improvement? Or would you mind adding in some helper methods for date operations?

Thanks for the useful library!

Can `subject` be called when one of the API methods is called?

I have many context group for my controller spec.
But I always needs to add the get /path at the very end BUT in before hook

I just tried put the get in subject to avoid the repeated call, it works with json_spec since they implement with custom matcher.
But it doesn't work with this gem, with error complaining about missing json_body, meaning subject is not called.

Is there a way to make it call subject when one of the API methods (e.g. expect_json_types) is called?

array of json objects causes NoMethodError: in base.rb

this code:

describe 'zKillboard parsing' do
        it 'What is inside of a killmail?' do
            get 'https://zkillboard.com/api/solo/kills/characterID/268946627/'
            #puts "JSON: #{puts json_body[0]}"  # See JSON in code below for referance
        end
end

causes the following error:

 1) zKillboard parsing What is inside of a killmail?
     Failure/Error: get 'https://zkillboard.com/api/solo/kills/characterID/268946627/'
     NoMethodError:
       undefined method `body' for nil:NilClass
     # /Users/frances/.rvm/gems/ruby-2.0.0-p451/gems/airborne-0.0.20/lib/airborne/base.rb:72:in `set_response'
     # /Users/frances/.rvm/gems/ruby-2.0.0-p451/gems/airborne-0.0.20/lib/airborne/base.rb:28:in `get'
     # ./zKillboardRspec_spec.rb:7:in `block (2 levels) in <top (required)>'

Finished in 1.23 seconds (files took 1.69 seconds to load)
1 example, 1 failure

Am I doing something stupid, or is this broken?

Improper type identification by expect_json_types

I am trying to test an API that returns all information as strings, e.g. :moonID=>"0". When I try to assert that moonID is a string, I get the following error:

  Expected moonID to be of type string, got Fixnum instead

But, if I try to assert that it is an Int, I get an error saying it is a string.

Expected moonID to be of type int, got String instead

This is the code:

        it "Tickle for a single item " do
            get 'https://zkillboard.com/api/solo/kills/characterID/268946627/'
            puts "zKillboard is: #{json_body[1]}"
            expect_json_types('*', {moonID: :string}) # This should pass
            expect_json_types('*', {moonID: :int})    # This should fail because you get a string
        end

This is the first error message:

Failures:

  1) Tickling APIs for fun -  Tickle the zKillboard API
     Failure/Error: expect_json_types('*', {moonID: :string})
       Expected moonID to be of type string, got Fixnum instead
     # /Users/frances/.rvm/gems/ruby-2.0.0-p451@restfulAPI/gems/airborne-0.0.20/lib/airborne/request_expectations.rb:110:in `block in expect_json_types_impl'
     # /Users/frances/.rvm/gems/ruby-2.0.0-p451@restfulAPI/gems/airborne-0.0.20/lib/airborne/request_expectations.rb:98:in `each'
     # /Users/frances/.rvm/gems/ruby-2.0.0-p451@restfulAPI/gems/airborne-0.0.20/lib/airborne/request_expectations.rb:98:in `expect_json_types_impl'
     # /Users/frances/.rvm/gems/ruby-2.0.0-p451@restfulAPI/gems/airborne-0.0.20/lib/airborne/request_expectations.rb:10:in `block in expect_json_types'
     # /Users/frances/.rvm/gems/ruby-2.0.0-p451@restfulAPI/gems/airborne-0.0.20/lib/airborne/request_expectations.rb:61:in `block in call_with_path'
     # /Users/frances/.rvm/gems/ruby-2.0.0-p451@restfulAPI/gems/airborne-0.0.20/lib/airborne/path_matcher.rb:28:in `block in get_by_path'
     # /Users/frances/.rvm/gems/ruby-2.0.0-p451@restfulAPI/gems/airborne-0.0.20/lib/airborne/path_matcher.rb:28:in `each'
     # /Users/frances/.rvm/gems/ruby-2.0.0-p451@restfulAPI/gems/airborne-0.0.20/lib/airborne/path_matcher.rb:28:in `get_by_path'
     # /Users/frances/.rvm/gems/ruby-2.0.0-p451@restfulAPI/gems/airborne-0.0.20/lib/airborne/request_expectations.rb:60:in `call_with_path'
     # /Users/frances/.rvm/gems/ruby-2.0.0-p451@restfulAPI/gems/airborne-0.0.20/lib/airborne/request_expectations.rb:9:in `expect_json_types'
     # ./helloWorldRspec_spec.rb:28:in `block (2 levels) in <top (required)>'

Here is the JSON in question:

{:killID=>"22356123", :solarSystemID=>"30001382", :killTime=>"2012-02-17 21:39:00", :moonID=>"0", :victim=>{:shipTypeID=>"594", :damageTaken=>"1999", :factionName=>"", :factionID=>"0", :allianceName=>"RvB - BLUE Republic", :allianceID=>"99000652", :corporationName=>"Blue Republic", :corporationID=>"1741770561", :characterName=>"Commander Reed", :characterID=>"1828062917", :victim=>""}, :attackers=>[{:characterID=>"268946627", :characterName=>"Karbowiak", :corporationID=>"1699307293", :corporationName=>"Red Federation", :allianceID=>"99000645", :allianceName=>"RvB - RED Federation", :factionID=>"0", :factionName=>"", :securityStatus=>"-1.72987128117126", :damageDone=>"1999", :finalBlow=>"1", :weaponTypeID=>"2185", :shipTypeID=>"12023"}], :items=>[{:typeID=>"438", :flag=>"0", :qtyDropped=>"0", :qtyDestroyed=>"1", :singleton=>"0"}, {:typeID=>"3568", :flag=>"0", :qtyDropped=>"0", :qtyDestroyed=>"1", :singleton=>"0"}, {:typeID=>"3162", :flag=>"0", :qtyDropped=>"0", :qtyDestroyed=>"3", :singleton=>"0"}, {:typeID=>"11285", :flag=>"5", :qtyDropped=>"0", :qtyDestroyed=>"5", :singleton=>"0"}, {:typeID=>"11349", :flag=>"0", :qtyDropped=>"1", :qtyDestroyed=>"0", :singleton=>"0"}, {:typeID=>"12614", :flag=>"0", :qtyDropped=>"168", :qtyDestroyed=>"336", :singleton=>"0"}, {:typeID=>"448", :flag=>"0", :qtyDropped=>"0", :qtyDestroyed=>"1", :singleton=>"0"}, {:typeID=>"12614", :flag=>"5", :qtyDropped=>"0", :qtyDestroyed=>"1000", :singleton=>"0"}, {:typeID=>"11285", :flag=>"0", :qtyDropped=>"1", :qtyDestroyed=>"0", :singleton=>"0"}, {:typeID=>"2048", :flag=>"0", :qtyDropped=>"1", :qtyDestroyed=>"0", :singleton=>"0"}], :zkb=>{:totalValue=>"9664154.53", :points=>"8"}}

Apply Procs to doted paths

My API return {"result":"error","content":{"code":"LOGIN_DATA_INCORRECT","parameters":{"loginAttemptsLeft":4}}} and I want to check that loginAttemptsLeft was decremented after some actions.

For now when I want to use complex matcher (via Proc) I need to use symbols as key i.e.:

expect_json(content: -> (n) { expect(n[:parameters][:loginAttemptsLeft]) < 5 })

But it would be awesome to have same complex matchers for dot paths like so:

expect_json('content.parameters.loginAttemptsLeft' -> (attemts_left) { expect(attemts_left) < 5 })

Posibility to sign requests with oAuth token

Is there airborne'y way so sign requests with oAuth token?

I found only way to do it directly with RestClient before exec proc:

access_token = MyModule.get_oauth[:token]
RestClient.reset_before_execution_procs
RestClient.add_before_execution_proc do |req, params|
    access_token.sign! req 
end
get "#{MyModule.base_url}/rest/agent/user"

This chunk could be executed across many examples. Any advices how to make it more DRY?

Expecting array responses

In 0.1.15 I had no issue with expect_json([]) but now that I upgraded to 0.2.1 it throws an error:

Failure/Error: expect_json([])

NoMethodError:
 undefined method `keys' for []:Array
# /ruby-2.2.3/gems/airborne-0.2.1/lib/airborne/request_expectations.rb:83:in `expect_json_impl'
# /ruby-2.2.3/gems/airborne-0.2.1/lib/airborne/request_expectations.rb:19:in `block in expect_json'
# /ruby-2.2.3/gems/airborne-0.2.1/lib/airborne/request_expectations.rb:139:in `call_with_path'
# /ruby-2.2.3/gems/airborne-0.2.1/lib/airborne/request_expectations.rb:18:in `expect_json'

Is this expected?

head :no_content issue

This is my test:

  it "returns expected json" do
    delete :destroy, id: @location_1.id
    expect_json(nil)
  end

This is my code destroy action:

    def destroy
      location = Location.find(params[:id])
      authorize! :crud, Location
      location.destroy
      head :no_content
    end

This is the rspec output:

    Failure/Error: expect_json(nil)
    Airborne::InvalidJsonError:
    Api request returned invalid json

When I inspect the response body:

    response.body #=> ""

Please tell me how I should test this scenario give my code ---> head :no_content

Thank you!

How to call "expect_json_keys" for multiple keys

I can seem to use this method to check for multiple keys, it it possible? Sorry, I'm not super ruby fluent just yet!

tried:
expect_json_keys (:_keys, :embedded)

but get:
syntax error, unexpected ','

Thanks,
Bob

Can't login with post '/user/login/', { :email => 'username', :password => 'password' }

I am trying to login
I tried
post '/user/login', { :email => 'username', :password => 'password' }
Tried
post '/user/login', { :email => 'username', :password => 'password' } , { 'Content-Type' => 'application/x-www-form-urlencoded' }
and get an error message

I can login with postman using the same credentials
I can login with frisby.js
frisby.create('login')
.post(BASE_URL + 'user/login',
{
email: email,
password: password
}
)
.expectStatus(200)
.expectJSON({
status: 0
})

json body rails

For instances where airborne http requests are used json_body is set from within the request. However, rails is not using the airborne requests and therefore not setting @json_body, unless one of the airborne custom matchers are called first.

it 'should set json_body' do
  get '/foo'
  expect(json_body).to eq('foo')
end

This will throw an invalid JSON exception which is completely wrong.

Testing Arrays

If the API returns an array of objects in JSON, how can we test it using airborne?

An example would be awesome.

Thank you

undefined method `keys' for nil:NilClass

When I hit this uri
http://voteapi-cloud-test.telescope.tv/moderation/getMessages?topic_id=1000336

and use this expectation
expect_json_keys('0', [:position])

I get this error:

  1. Test Endpoint - has the correct keys
    Failure/Error: expect_json_keys('0', [:position])
    NoMethodError:
    undefined method `keys' for nil:NilClass

    /Users/francesmorales/.rvm/gems/ruby-2.1.2/gems/airborne-0.1.8/lib/airborne/request_expectations.rb:23:in`block in expect_json_keys'

    /Users/francesmorales/.rvm/gems/ruby-2.1.2/gems/airborne-0.1.8/lib/airborne/request_expectations.rb:87:in `block in call_with_path'

    /Users/francesmorales/.rvm/gems/ruby-2.1.2/gems/airborne-0.1.8/lib/airborne/path_matcher.rb:24:in`get_by_path'

    /Users/francesmorales/.rvm/gems/ruby-2.1.2/gems/airborne-0.1.8/lib/airborne/request_expectations.rb:86:in `call_with_path'

    /Users/francesmorales/.rvm/gems/ruby-2.1.2/gems/airborne-0.1.8/lib/airborne/request_expectations.rb:22:in`expect_json_keys'

    /Users/francesmorales/.rvm/gems/ruby-2.1.2/gems/airborne-0.1.8/lib/airborne/request_expectations.rb:61:in `call'

    /Users/francesmorales/.rvm/gems/ruby-2.1.2/gems/airborne-0.1.8/lib/airborne/request_expectations.rb:61:in`block (2 levels) in module:RequestExpectations'

    ./spec/sandbox/toys_rspec.rb:23:in `block (2 levels) in <top (required)>'

As far as I can tell I am calling expect_json_keys exactly like the documentation specifies. What is going wrong?

Use of stabby lambda's is breaking jRuby support

There is a syntax error when using jRuby resulting from the use of stabby lamdba's. Tested with jruby 1.7.16 in 1.9 mode, so likely to exist on earlier versions.

One example can be found in request_expectations.rb where
-> (data) { expect(data.size).to eq(expected_size) }
could be replaced with
->(data) { expect(data.size).to eq(expected_size) }
to avoid syntax errors (note the removal of the space after the dash rocket).

In case of multiple requests, expect_json keeps checking with response of first request.

I am making three requests in a rspec example. The responses are correct but the matchers after second request are failing because they keep comparing using the response of first request.

describe "Sign Out" do
  it "should expire the auth token" do
    # Sign In
    post "/api/v1/sign_in", {"email": @user.email, "password": @user.password}, :format => :json
    expect_status(200) #OK

    auth_token = response.header['X-Auth-Token']
    email = response.header['X-User-Email']

    # Sign out
    post "/api/v1/sign_out", {}, {'X-User-Email' => email, 'X-Auth-Token' => auth_token, 'Content-Type' =>'application/json'}
    body = response.body
    expect_status(200) #OK
    expect_json("message": "Signed out successfully.")

    # Trying with expired token
    get "/api/v1/cycle_days", {'X-User-Email' => email, 'X-Auth-Token' => auth_token}, :format => :json
    expect_status(401)
  end
end

Here is the error.

   Failure/Error: expect_json("message": "Signed out successfully.")

   expected: "Signed out successfully."
        got: "Signed in successfully."

Changelog and semantic versioning

I understand that this gem is unstable until a v1.x is release, but the latest v0.1.16 introduced some breaking changes (expect_json is more strict in how it diffs actual and expected responses).

It would be great if this gem would adapt Semantic Versioning and start keeping a changelog.

I could submit i PR with a changelog for the current versions, but it works best when there are tags for each of the released versions.

Disable auto follow redirects?

If I get a link that 302s via curb then I'm able to see the Location: header with the correct 302 status code. Airborne auto follows the redirect and the status code for the original get request is unavailable. Is there a way to toggle follow redirects?

Bring gem's interface closer to "idiomatic Ruby"

Hi. Have you guys thought about making the calls more "rspec-style"? At least for typical cases (you know, those 20% that are used 80% of the time).

Like, turn

expect_json '?.category', {name: chosen_category.name}

into

expect(:any).to_have :category, with: {name: chosen_category.name}

I would love to participate in that.

Optional array of hashes

Given the following JSON. How could you validate JSON types for the owner's name. This is an example from your documentation, but the Lamborghini's owner has been removed.

{
    "cars":[
        {
            "make":"Tesla",
            "model":"Model S",
            "owners":[
                {
                    "name":"Bart Simpson"
                }
            ]
        },
        {
            "make":"Lamborghini",
            "model":"Aventador"
        }
    ]
}

The optional() method appears to only support optional hashes. How do we validate optional arrays of hashes?

Feature Request: Add API for testing Array size

I am using json_spec currently and just started using this gem.

I like how I can validate the types easily with expect_json_types.
But I found no API for Array size, which is very common when writing spec for collection endpoints.
(Like expect_json_size)

I am staying with have_json_size from json_spec at the moment.

nil json_body with data in body

I'm having an issue where json_body is returning nil, but body returns the data I expect. Do you have any insight as to what might cause that?

No params = {} parameter in GET request?

After some failures i noticed that you don't use params = {} parameter in the GET request as rack-test does.So you have to interpolate it like:

get "/endpoint?foo=#{test}"

But in ruby < 2.2 it gives a bad URI error so i had to use URI.encode

Why is that?
Thanks!

base_url configuration option not working

Here is my airborne api spec with base_url configuration:

require 'rails_helper'
require 'airborne'

Airborne.configure do |config|
  config.rack_app = API
  config.base_url = 'http://example.com/api/v1'
end

describe V1::Freewheeler::People do
  it "GET /api/v1/people" do
    get "/people"
  end
end

OUTPUT

Failures:

  1) V1::Freewheeler::People GET /api/v1/people
     Failure/Error: get "/people"
     ActionController::RoutingError:
       No route matches [GET] "/people"

If I provide full url then it pass

require 'rails_helper'
require 'airborne'

Airborne.configure do |config|
  config.rack_app = API
  config.base_url = 'http://example.com/api/v1'
end

describe V1::Freewheeler::People do
  it "GET /api/v1/people" do
    get "http://example.com/api/v1/people"
  end
end

_output:_

Randomized with seed 39703
.

Finished in 0.88466 seconds (files took 16 minutes 29 seconds to load)
1 example, 0 failures

Randomized with seed 39703

What am I missing here?

Compatibility with rspec-rails

Hi.

I am relatively new to Rails and probably doing something very stupid, but:
I use Rails with rspec, my tests have type: :request. When I call get, post, etc. in tests, what is actually called is ActionDispatch::Integration::Runner#get, not airbornes's get. Which leads to some unexpected results, like "Accept" being "text/html" by default and format: arg not working. What am I doing wrong?

ENHANCEMENT: Throw error if object called is not present within json_body

User Story:
As a user I'd like airborne to throw an error if object called is not present within the json_body so that I have better clarity on my malformed test

Test - POST:

  it 'should add new members or update existing members in bulk' do
    $faker_members = Faker::Internet.safe_email
    post '/members', {"members" => [{"email" => "#{$faker_members}"}]}
    $post_member_from_members = json_body[:member_id]
    puts  "#{$post_member_from_members} - Members"
    expect_status(200)
  end

Test - PUT:

  it 'should add a single member to one or more groups' do
    put "/members/#{$post_member_from_members}/groups", {}
    expect_status(200)
  end

json_body object called in test:

    $post_member_from_members = json_body[:member_id]

Actual json_body:

{
  "import_id": 1234
}

Result of puts:

 - Members

Failure on PUT:

  1) PUT - Members should add a single member to one or more groups
     Failure/Error: expect_status(200)

       expected: 200
            got: 404

       (compared using ==)
     # /Users/dhale/.rvm/gems/ruby-2.1.0/gems/airborne-0.1.13/lib/airborne/request_expectations.rb:35:in `expect_status'
     # /Users/dhale/.rvm/gems/ruby-2.1.0/gems/airborne-0.1.13/lib/airborne/request_expectations.rb:62:in `call'
     # /Users/dhale/.rvm/gems/ruby-2.1.0/gems/airborne-0.1.13/lib/airborne/request_expectations.rb:62:in `block (2 levels) in <module:RequestExpectations>'
     # ./spec/api_spec.rb:313:in `block (2 levels) in <top (required)>'

Loading multiple grape apps

I'm mounting multiple grape endpoints into rails. I'm able to use config.rack_app to set the app in each spec file without issue. But I was wondering if I could mass load (maybe in rspec) all the endpoints so they are all routable and available at once? Thanks!

Expected ? to be array got Hash from JSON response

When I hit this uri
http://voteapi-cloud-test.telescope.tv/moderation/getMessages?topic_id=1000336

and use this expectation
expect_json_keys('?', [:position])

I get this error:

  1. Test Endpoint - has the correct keys
    Failure/Error: expect_json_keys('?', [:position])
    Expected ? to be array got Hash from JSON response

    /Users/francesmorales/.rvm/gems/ruby-2.1.2/gems/airborne-0.1.10/lib/airborne/path_matcher.rb:99:in `ensure_array'

    /Users/francesmorales/.rvm/gems/ruby-2.1.2/gems/airborne-0.1.10/lib/airborne/path_matcher.rb:11:in`block in get_by_path'

    /Users/francesmorales/.rvm/gems/ruby-2.1.2/gems/airborne-0.1.10/lib/airborne/path_matcher.rb:9:in `each'

    /Users/francesmorales/.rvm/gems/ruby-2.1.2/gems/airborne-0.1.10/lib/airborne/path_matcher.rb:9:in`each_with_index'

    /Users/francesmorales/.rvm/gems/ruby-2.1.2/gems/airborne-0.1.10/lib/airborne/path_matcher.rb:9:in `get_by_path'

    /Users/francesmorales/.rvm/gems/ruby-2.1.2/gems/airborne-0.1.10/lib/airborne/request_expectations.rb:87:in`call_with_path'

    /Users/francesmorales/.rvm/gems/ruby-2.1.2/gems/airborne-0.1.10/lib/airborne/request_expectations.rb:23:in `expect_json_keys'

    /Users/francesmorales/.rvm/gems/ruby-2.1.2/gems/airborne-0.1.10/lib/airborne/request_expectations.rb:62:in`call'

    /Users/francesmorales/.rvm/gems/ruby-2.1.2/gems/airborne-0.1.10/lib/airborne/request_expectations.rb:62:in `block (2 levels) in module:RequestExpectations'

    ./spec/sandbox/toys_rspec.rb:22:in`block (2 levels) in <top (required)>'

I get the same error for *

Am I still running into a bug, or am I doing something wrong?

breaking change in expect_json

Perhaps this was expected behavior but when upgrading from 0.1.15 to 0.1.16, I noticed a change in the expect_json function.

Previously this test worked:

  it 'GET comments for a single topic for a datasource = facebook' do
    get "/topics/#{topic_slug}/comments?datasource_type=facebook"
    expect(response.code).to eq(200)
    expect_json('comments.*', {datasource: {type: 'FACEBOOK'}})
  end 

After the update it required me to define the whole json block object for datasource. i.e.

  it 'GET comments for a single topic for a datasource = facebook' do
    get "/topics/#{topic_slug}/comments?datasource_type=facebook"
    expect(response.code).to eq(200)
    expect_json('comments.*', {datasource: {key: 'FB', type: 'FACEBOOK', human: 'Facebook'}})
  end 

While that certainly makes sense, given the amount of tests we have, is there a function that works equivalently to the the old expect_json? I feel that having the ability to be as strict/loose as you like in the test is pretty useful. Particularly in some more complex examples. Thanks!

JSON::ParserError 757 on Post Success

Hello --

The post is successful, but the only return value I am receiving is 481973.

Ran the call manually through the API and received only one integer value for response and I am not sure how to quantify that within the testing framework since there is no structure.

Manual Response: (NOTE: the integer is different since they are all unique values)

477877

Test:

  it 'should create a new field' do
    post "/fields", {shortcut_name: "#{Faker::Lorem.characters(10)}", display_name: "#{Faker::Lorem.characters(5)}", field_type: "text", widget_type: "text", column_order: 0}
  end

Gemfile:

faker (1.4.3)
airborne (0.1.11)
json (1.7.6)

Error:

  1) GET - Fields should create a new field
     Failure/Error: post "/fields", {shortcut_name: "#{Faker::Lorem.characters(10)}", display_name: "#{Faker::Lorem.characters(5)}", field_type: "text", widget_type: "text", column_order: 0}
     JSON::ParserError:
       757: unexpected token at '481973'
     # /Users/dhale/.rvm/gems/ruby-2.1.0/gems/json-1.7.6/lib/json/common.rb:155:in `parse'
     # /Users/dhale/.rvm/gems/ruby-2.1.0/gems/json-1.7.6/lib/json/common.rb:155:in `parse'
     # /Users/dhale/.rvm/gems/ruby-2.1.0/gems/airborne-0.1.11/lib/airborne/base.rb:82:in `set_response'
     # /Users/dhale/.rvm/gems/ruby-2.1.0/gems/airborne-0.1.11/lib/airborne/base.rb:32:in `post'
     # ./spec/api_spec.rb:52:in `block (2 levels) in <top (required)>'

Thanks for your assistance!

JSON::ParserError

I'm testing grape api. Early, tests completed without errors. After I move api files from app/api to app/lib/api and to config this line:

config.include RSpec::Rails::RequestExampleGroup, type: :request, file_path: /spec\/lib\/api/

some tests (expect_status, expect_json) fails with error:

Failure/Error: expect_status 201.to_s
     JSON::ParserError:
       757: unexpected token at 'null'

but expect(response.status).to eq(201) no fail.

Content-Type header is forced to application/json

This line forces the Content-Type to be application/json:
headers = { content_type: :json }.merge(options[:headers] || {})

However there are other kinds of content types, such as application/vnd.api+json (http://jsonapi.org/), designed specifically to build APIs, that should be allowed.

I think that just checking if the header exists in the options is enough to solve this issue.

Thanks!

Problem with Dates?

It seems at though there may be a problem with dates.

The following fails:

expect_json_types("*", {id: :integer, name: :string, description: :string, created_at: :date})

If I remove the , created_at: :date it works perfectly.

Here is the error:

Failure/Error: expect_json_types("*", {id: :integer, name: :string, description: :string, created_at: :date})
 NoMethodError:
   undefined method `include?' for nil:NilClass

Given the following JSON:

[  
   {  
      "id":1310926,
      "name":"MERCY TIES, DIVIDER, LIL DOWAGER, RAGANA",
      "description":"MERCY TIES(seattle) and DIVIDER(ny) are on tour together. Come to the Hemlock oct     20th to support this glory kid couple and local crushers RAGANA and LIL DOWAGER. \n\nRAGANA     Oakland two piece heavy, dark atmosphere.\nhttp://ragana.bandcamp.com/\n\nMERCY TIES  chaotic     hardcore punk rock from Seattle.\nhttp://mercyties.bandcamp.com/\n\nDIVIDER Dirge. Crush. Heavy.     Riffage. From New York\nhttp://www.altpress.com/features/entry/divider_all_barren_album_premiere    \n\nLIL DOWAGER Oakland harsh and noise post-something.\nhttp://lildowager.bandcamp.com/",
      "start":"2014-10-19T17:00:00.000-07:00",
      "end":"2014-10-20T17:00:00.000-07:00",
      "created_at":"2014-10-05T23:09:27.487-07:00",
      "updated_at":"2014-10-16T22:38:52.096-07:00",
      "latitude":37.7874165773392,
      "longitude":-122.420076951385,
      "address":"1151 Polk St, San Francisco, CA 94109",
      "source_url":"https://world.timeout.com/events/mercy-ties-divider-lil-dowager-tba",
      "upstream_pic_thumb_url":"https://fbcdn-sphotos-b-a.akamaihd.net/hphotos-ak-xpf1/v/t1.0-9/c7.0.50.    50/p50x50/1798194_732430210170553_8908067978434556047_n.jpg?oh=cf33bdbd5803b08babdb88bbc69ae9b8    \u0026oe=54B9C961\u0026__gda__=1422114503_084a38ae5ae947dc1cfdaab5b3d4c0e6",
      "upstream_pic_url":"https://fbcdn-sphotos-b-a.akamaihd.net/hphotos-ak-xpf1/v/t1.0-9/c15.0.100.    100/p100x100/1798194_732430210170553_8908067978434556047_n.jpg?oh=ddb646d41178d74c8f28f02f36620183    \u0026oe=54EF78C1\u0026__gda__=1424833127_023b884e1845b96fd5e8bfa7f586efc5",
      "upstream_pic_big_url":"https://fbcdn-sphotos-b-a.akamaihd.net/hphotos-ak-xpf1/v/t1.0-9/c30.0.200.    200/p200x200/1798194_732430210170553_8908067978434556047_n.jpg?oh=0fc3068ef6a1004608ec2d77429cdf21    \u0026oe=54BC9249\u0026__gda__=1421813487_460f538e7b15a558a2a2e263440a4599",
      "upstream_pic_small_url":null,
      "embedd_video_url":null,
      "updates_count":0,
      "published":true,
      "moderating":false,
      "status_id":1,
      "price":null,
      "repeating":null,
      "public":null,
      "twitter_url":null,
      "facebook_url":null,
      "hashtags":null,
      "name_billing":null,
      "ticket_link":null,
      "show_only_as_deal":false,
      "rsvp_count":0,
      "rsvps_count":0,
      "import_event_id":2582,
      "parent_id":null,
      "search_vector":"'/features/entry/divider_all_barren_album_premiere':63B '1151':74C '20th':22B     '94109':80C 'atmospher':42B 'ca':79C 'chaotic':46B 'come':17B 'coupl':28B 'crush':55B     'crusher':31B 'dark':41B 'dirg':54B 'divid':3A,11B,53B 'dowag':5A,35B,65B 'famili':81B     'francisco':78C 'glori':26B 'hardcor':47B 'harsh':67B 'heavi':40B,56B 'hemlock':20B,82B 'kid':27B     'lil':4A,34B,64B 'lildowager.bandcamp.com':73B 'local':30B 'merci':1A,7B,44B 'mercyties.bandcamp.    com':52B 'new':59B 'nois':69B 'ny':12B 'oakland':37B,66B 'oct':21B 'piec':39B 'polk':75C     'post':71B 'post-someth':70B 'punk':48B 'ragana':6A,32B,36B 'ragana.bandcamp.com':43B     'riffag':57B 'rock':49B 'san':77C 'seattl':9B,51B 'someth':72B 'st':76C 'support':24B     'tavern':83B 'tie':2A,8B,45B 'togeth':16B 'tour':15B 'two':38B 'www.altpress.com':62B 'www.    altpress.com/features/entry/divider_all_barren_album_premiere':61B 'york':60B",
      "venue_name":"Hemlock Tavern",
      "venue":{  
         "id":3302147,
         "name":"Hemlock Tavern",
         "latitude":37.7874165773392,
         "longitude":-122.420076951385,
         "address":"1131 Polk St, San Francisco, CA, United States, 94109",
         "created_at":"2014-10-16T22:38:52.081-07:00",
         "updated_at":"2014-10-16T22:38:52.081-07:00",
         "user_id":null,
         "moderator_id":null,
         "fb_graph_id":null,
         "city":null,
         "state":null,
         "country":null,
         "street":null,
         "zip":null,
         "status_id":null,
         "venue_type_id":null,
         "public":null,
         "capacity":null,
         "stages":null,
         "floors":null,
         "rooms":null,
         "north_east_latitutde":null,
         "north_west_latitutde":null,
         "south_east_latitutde":null,
         "south_west_latitutde":null,
         "north_east_longitude":null,
         "north_west_longitude":null,
         "south_east_longitude":null,
         "south_west_longitude":null,
         "facebook_url":null,
         "photo":{  
            "url":null,
            "thumb":{  
               "url":null
            },
            "ios_profile":{  
               "url":null
            },
            "medium":{  
               "url":null
            }
         },
         "description":null,
         "logo":{  
            "url":null,
            "thumb":{  
               "url":null
            },
            "ios_profile":{  
               "url":null
            },
            "medium":{  
               "url":null
            }
         },
         "search_vector":"'1131':3C '94109':11C 'ca':8C 'francisco':7C 'hemlock':1A 'polk':4C 'san':6C     'st':5C 'state':10C 'tavern':2A 'unit':9C"
      },
      "status":"active",
      "total_checkins=":0,
      "total_attending=":0
   }
]

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.