├── .coveralls.yml ├── .dependabot └── config.yml ├── .github ├── dependabot.yml └── workflows │ └── tests.yml ├── .gitignore ├── .rspec ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── airborne.gemspec ├── lib ├── airborne.rb └── airborne │ ├── base.rb │ ├── optional_hash_type_expectations.rb │ ├── path_matcher.rb │ ├── rack_test_requester.rb │ ├── request_expectations.rb │ └── rest_client_requester.rb ├── rakelib ├── changelog.rake └── rspec.rake └── spec ├── airborne ├── base_spec.rb ├── client_requester_spec.rb ├── delete_spec.rb ├── expectations │ ├── expect_header_contains_spec.rb │ ├── expect_header_spec.rb │ ├── expect_json_keys_path_spec.rb │ ├── expect_json_keys_spec.rb │ ├── expect_json_lambda_spec.rb │ ├── expect_json_options_spec.rb │ ├── expect_json_path_spec.rb │ ├── expect_json_regex_spec.rb │ ├── expect_json_sizes_spec.rb │ ├── expect_json_spec.rb │ ├── expect_json_types_date_spec.rb │ ├── expect_json_types_lambda_spec.rb │ ├── expect_json_types_optional_spec.rb │ ├── expect_json_types_options_spec.rb │ ├── expect_json_types_path_spec.rb │ ├── expect_json_types_spec.rb │ └── expect_status_spec.rb ├── head_spec.rb ├── options_spec.rb ├── patch_spec.rb ├── path_spec.rb ├── post_spec.rb ├── put_spec.rb └── rack │ └── rack_sinatra_spec.rb ├── spec_helper.rb ├── stub_helper.rb └── test_responses ├── array_of_types.json ├── array_of_values.json ├── array_response.json ├── array_with_index.json ├── array_with_nested.json ├── array_with_nested_bad_data.json ├── array_with_partial_nested.json ├── date_is_null_response.json ├── date_response.json ├── hash_property.json ├── invalid_get.json ├── invalid_json.json ├── numeric_property.json ├── simple_delete.json ├── simple_get.json ├── simple_json.json ├── simple_nested_path.json ├── simple_patch.json ├── simple_path_get.json ├── simple_post.json └── simple_put.json /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci -------------------------------------------------------------------------------- /.dependabot/config.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | 3 | update_configs: 4 | - package_manager: "ruby:bundler" 5 | directory: "./" 6 | update_schedule: "daily" 7 | # https://dependabot.com/docs/config-file/#version_requirement_updates 8 | version_requirement_updates: auto 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | ruby: 13 | - 2.7.7 14 | - 3.0.5 15 | - 3.1.3 16 | - 3.2.0 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: ${{ matrix.ruby }} 23 | bundler-cache: true 24 | - name: Update RubyGems / Bundler 25 | run: | 26 | gem update --system 27 | gem --version 28 | gem install bundler 29 | - run: bundle exec rake 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .ruby-* 3 | *.gem 4 | *.rbc 5 | /.config 6 | /coverage/ 7 | /InstalledFiles 8 | /pkg/ 9 | /spec/reports/ 10 | /test/tmp/ 11 | /test/version_tmp/ 12 | /tmp/ 13 | 14 | ## Specific to RubyMotion: 15 | .dat* 16 | .repl_history 17 | build/ 18 | 19 | ## Documentation cache and generated files: 20 | /.yardoc/ 21 | /_yardoc/ 22 | /doc/ 23 | /rdoc/ 24 | 25 | ## Environment normalisation: 26 | /.bundle/ 27 | /lib/bundler/man/ 28 | 29 | # for a library or gem, you might want to ignore these files since the code is 30 | # intended to run in multiple environments; otherwise, check them in: 31 | Gemfile.lock 32 | # .ruby-version 33 | # .ruby-gemset 34 | 35 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 36 | .rvmrc 37 | .idea 38 | .DS_store 39 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | -fd -c -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v0.3.4](https://github.com/brooklynDev/airborne/tree/v0.3.4) (2019-04-30) 4 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.3.3...v0.3.4) 5 | 6 | **Closed issues:** 7 | 8 | - Add a changelog [\#104](https://github.com/brooklynDev/airborne/issues/104) 9 | - Changelog and semantic versioning [\#63](https://github.com/brooklynDev/airborne/issues/63) 10 | 11 | **Merged pull requests:** 12 | 13 | - send payload with delete requests [\#154](https://github.com/brooklynDev/airborne/pull/154) ([mycargus](https://github.com/mycargus)) 14 | - automate changelog with rake task [\#153](https://github.com/brooklynDev/airborne/pull/153) ([mycargus](https://github.com/mycargus)) 15 | 16 | ## [v0.3.3](https://github.com/brooklynDev/airborne/tree/v0.3.3) (2019-04-03) 17 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.3.2...v0.3.3) 18 | 19 | **Closed issues:** 20 | 21 | - How to set verify\_ssl =\> false [\#118](https://github.com/brooklynDev/airborne/issues/118) 22 | 23 | **Merged pull requests:** 24 | 25 | - create verify\_ssl config [\#152](https://github.com/brooklynDev/airborne/pull/152) ([mycargus](https://github.com/mycargus)) 26 | - Update webmock requirement from ~\> 0 to ~\> 3 [\#151](https://github.com/brooklynDev/airborne/pull/151) ([dependabot[bot]](https://github.com/apps/dependabot)) 27 | 28 | ## [v0.3.2](https://github.com/brooklynDev/airborne/tree/v0.3.2) (2019-03-25) 29 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.3.1...v0.3.2) 30 | 31 | **Merged pull requests:** 32 | 33 | - pass block to RestClient method for better response handling [\#136](https://github.com/brooklynDev/airborne/pull/136) ([tabermike](https://github.com/tabermike)) 34 | 35 | ## [v0.3.1](https://github.com/brooklynDev/airborne/tree/v0.3.1) (2019-03-21) 36 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.3.0...v0.3.1) 37 | 38 | **Merged pull requests:** 39 | 40 | - configure dependabot [\#150](https://github.com/brooklynDev/airborne/pull/150) ([mycargus](https://github.com/mycargus)) 41 | - Serialize to JSON when content type is set to JSON [\#142](https://github.com/brooklynDev/airborne/pull/142) ([rdalverny](https://github.com/rdalverny)) 42 | 43 | ## [v0.3.0](https://github.com/brooklynDev/airborne/tree/v0.3.0) (2019-03-21) 44 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.2.13...v0.3.0) 45 | 46 | **Merged pull requests:** 47 | 48 | - upgrade runtime dependencies [\#149](https://github.com/brooklynDev/airborne/pull/149) ([mycargus](https://github.com/mycargus)) 49 | 50 | ## [v0.2.13](https://github.com/brooklynDev/airborne/tree/v0.2.13) (2017-05-24) 51 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.2.12...v0.2.13) 52 | 53 | **Closed issues:** 54 | 55 | - : \( [\#135](https://github.com/brooklynDev/airborne/issues/135) 56 | - Potential incompatibility with RSpec 3.5.4 [\#121](https://github.com/brooklynDev/airborne/issues/121) 57 | 58 | **Merged pull requests:** 59 | 60 | - Rspec 3.5 compatibility [\#137](https://github.com/brooklynDev/airborne/pull/137) ([axelson](https://github.com/axelson)) 61 | 62 | ## [v0.2.12](https://github.com/brooklynDev/airborne/tree/v0.2.12) (2017-04-10) 63 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.2.11...v0.2.12) 64 | 65 | **Merged pull requests:** 66 | 67 | - date\_or\_null fix [\#123](https://github.com/brooklynDev/airborne/pull/123) ([tabermike](https://github.com/tabermike)) 68 | 69 | ## [v0.2.11](https://github.com/brooklynDev/airborne/tree/v0.2.11) (2017-04-10) 70 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.2.10...v0.2.11) 71 | 72 | ## [v0.2.10](https://github.com/brooklynDev/airborne/tree/v0.2.10) (2017-04-10) 73 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.2.9...v0.2.10) 74 | 75 | **Merged pull requests:** 76 | 77 | - Replace Fixnum and Bignum with Integer [\#132](https://github.com/brooklynDev/airborne/pull/132) ([mcasper](https://github.com/mcasper)) 78 | - Fix Markdown syntax in Readme [\#130](https://github.com/brooklynDev/airborne/pull/130) ([dijonkitchen](https://github.com/dijonkitchen)) 79 | 80 | ## [v0.2.9](https://github.com/brooklynDev/airborne/tree/v0.2.9) (2017-03-27) 81 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.2.8...v0.2.9) 82 | 83 | **Closed issues:** 84 | 85 | - Ruby 2.4 warnings [\#125](https://github.com/brooklynDev/airborne/issues/125) 86 | 87 | **Merged pull requests:** 88 | 89 | - Fix ruby 2.4 Fixnum deprecation warnings [\#127](https://github.com/brooklynDev/airborne/pull/127) ([iyedb](https://github.com/iyedb)) 90 | 91 | ## [v0.2.8](https://github.com/brooklynDev/airborne/tree/v0.2.8) (2017-03-03) 92 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.2.7...v0.2.8) 93 | 94 | **Closed issues:** 95 | 96 | - Faraday Response support [\#120](https://github.com/brooklynDev/airborne/issues/120) 97 | - GET from test environment [\#117](https://github.com/brooklynDev/airborne/issues/117) 98 | - Airborne overrides too much of RSpec by default \(especially in the Rails context\) [\#115](https://github.com/brooklynDev/airborne/issues/115) 99 | - \(Rails 5\) 0.2.6 causes bundle conflict due to activesupport \< 5.0 dependency [\#112](https://github.com/brooklynDev/airborne/issues/112) 100 | 101 | **Merged pull requests:** 102 | 103 | - Loosen gemspec, allow usage with rails 5 [\#122](https://github.com/brooklynDev/airborne/pull/122) ([rosskevin](https://github.com/rosskevin)) 104 | 105 | ## [v0.2.7](https://github.com/brooklynDev/airborne/tree/v0.2.7) (2016-08-19) 106 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.2.6...v0.2.7) 107 | 108 | **Closed issues:** 109 | 110 | - NoMethodError: undefined method `\[\]' for nil:NilClass for POST on resource [\#110](https://github.com/brooklynDev/airborne/issues/110) 111 | 112 | ## [v0.2.6](https://github.com/brooklynDev/airborne/tree/v0.2.6) (2016-08-01) 113 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.2.5...v0.2.6) 114 | 115 | **Closed issues:** 116 | 117 | - How to test array as json root? [\#109](https://github.com/brooklynDev/airborne/issues/109) 118 | - Permit comparison of complete objects [\#106](https://github.com/brooklynDev/airborne/issues/106) 119 | - array\_of\_strings\_or\_null not working [\#92](https://github.com/brooklynDev/airborne/issues/92) 120 | 121 | **Merged pull requests:** 122 | 123 | - Upgrade version range for rest-client and test Ruby 2.3.1 [\#111](https://github.com/brooklynDev/airborne/pull/111) ([stefan-kolb](https://github.com/stefan-kolb)) 124 | - Update README.md [\#102](https://github.com/brooklynDev/airborne/pull/102) ([tit](https://github.com/tit)) 125 | 126 | ## [v0.2.5](https://github.com/brooklynDev/airborne/tree/v0.2.5) (2016-04-12) 127 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.2.4...v0.2.5) 128 | 129 | ## [v0.2.4](https://github.com/brooklynDev/airborne/tree/v0.2.4) (2016-04-12) 130 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.2.3...v0.2.4) 131 | 132 | ## [v0.2.3](https://github.com/brooklynDev/airborne/tree/v0.2.3) (2016-02-09) 133 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.2.2...v0.2.3) 134 | 135 | **Closed issues:** 136 | 137 | - base\_url configuration option not working [\#87](https://github.com/brooklynDev/airborne/issues/87) 138 | 139 | **Merged pull requests:** 140 | 141 | - Fix Ariborne default content\_type override to :json [\#89](https://github.com/brooklynDev/airborne/pull/89) ([kevcha](https://github.com/kevcha)) 142 | 143 | ## [v0.2.2](https://github.com/brooklynDev/airborne/tree/v0.2.2) (2015-12-16) 144 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.2.1...v0.2.2) 145 | 146 | **Closed issues:** 147 | 148 | - Expecting array responses [\#85](https://github.com/brooklynDev/airborne/issues/85) 149 | - Upload file using post [\#84](https://github.com/brooklynDev/airborne/issues/84) 150 | - Clear database before specific test [\#75](https://github.com/brooklynDev/airborne/issues/75) 151 | - Why no exact match matcher, what am I overlooking? [\#65](https://github.com/brooklynDev/airborne/issues/65) 152 | 153 | ## [v0.2.1](https://github.com/brooklynDev/airborne/tree/v0.2.1) (2015-12-13) 154 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.2.0...v0.2.1) 155 | 156 | **Closed issues:** 157 | 158 | - breaking change in expect\_json [\#67](https://github.com/brooklynDev/airborne/issues/67) 159 | 160 | ## [v0.2.0](https://github.com/brooklynDev/airborne/tree/v0.2.0) (2015-12-12) 161 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.20...v0.2.0) 162 | 163 | **Closed issues:** 164 | 165 | - No params = {} parameter in GET request? [\#45](https://github.com/brooklynDev/airborne/issues/45) 166 | 167 | **Merged pull requests:** 168 | 169 | - add configurable strictness for `expect\_json` and `expect\_json\_types` [\#83](https://github.com/brooklynDev/airborne/pull/83) ([sethpollack](https://github.com/sethpollack)) 170 | - updated patch and added 2.2 [\#82](https://github.com/brooklynDev/airborne/pull/82) ([ferdinandrosario](https://github.com/ferdinandrosario)) 171 | 172 | ## [v0.1.20](https://github.com/brooklynDev/airborne/tree/v0.1.20) (2015-09-24) 173 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.19...v0.1.20) 174 | 175 | **Closed issues:** 176 | 177 | - In case of multiple requests, expect\_json keeps checking with response of first request. [\#71](https://github.com/brooklynDev/airborne/issues/71) 178 | 179 | **Merged pull requests:** 180 | 181 | - Add DELETE functionality when options are passed in [\#77](https://github.com/brooklynDev/airborne/pull/77) ([dmhalejr](https://github.com/dmhalejr)) 182 | 183 | ## [v0.1.19](https://github.com/brooklynDev/airborne/tree/v0.1.19) (2015-08-07) 184 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.18...v0.1.19) 185 | 186 | ## [v0.1.18](https://github.com/brooklynDev/airborne/tree/v0.1.18) (2015-08-07) 187 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.17...v0.1.18) 188 | 189 | ## [v0.1.17](https://github.com/brooklynDev/airborne/tree/v0.1.17) (2015-08-07) 190 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.16...v0.1.17) 191 | 192 | **Closed issues:** 193 | 194 | - Compatibility with rspec-rails [\#70](https://github.com/brooklynDev/airborne/issues/70) 195 | - Can `subject` be called when one of the API methods is called? [\#47](https://github.com/brooklynDev/airborne/issues/47) 196 | 197 | **Merged pull requests:** 198 | 199 | - Allow expectations on consecutive requests to work [\#69](https://github.com/brooklynDev/airborne/pull/69) ([balvig](https://github.com/balvig)) 200 | 201 | ## [v0.1.16](https://github.com/brooklynDev/airborne/tree/v0.1.16) (2015-07-06) 202 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.15...v0.1.16) 203 | 204 | **Implemented enhancements:** 205 | 206 | - Add :null to the json\_types mapper [\#61](https://github.com/brooklynDev/airborne/issues/61) 207 | - better error messages when passing bad options to expect\_json\_types [\#59](https://github.com/brooklynDev/airborne/issues/59) 208 | 209 | **Fixed bugs:** 210 | 211 | - json body rails [\#58](https://github.com/brooklynDev/airborne/issues/58) 212 | 213 | **Closed issues:** 214 | 215 | - head :no\_content issue [\#57](https://github.com/brooklynDev/airborne/issues/57) 216 | - How send POST request with form data? [\#56](https://github.com/brooklynDev/airborne/issues/56) 217 | - Error on http://brooklyndev.github.io/airborne/ [\#54](https://github.com/brooklynDev/airborne/issues/54) 218 | - How to provide forms parameters for POST? [\#46](https://github.com/brooklynDev/airborne/issues/46) 219 | - Using Airborne with rails-cucumber [\#44](https://github.com/brooklynDev/airborne/issues/44) 220 | - JSON::ParserError [\#43](https://github.com/brooklynDev/airborne/issues/43) 221 | - ENHANCEMENT: Throw error if object called is not present within json\_body [\#42](https://github.com/brooklynDev/airborne/issues/42) 222 | 223 | **Merged pull requests:** 224 | 225 | - Remove duplicate word and newline in error message [\#60](https://github.com/brooklynDev/airborne/pull/60) ([alcesleo](https://github.com/alcesleo)) 226 | - Update README.md [\#55](https://github.com/brooklynDev/airborne/pull/55) ([tit](https://github.com/tit)) 227 | - Fix cherry-picking Active Support core extension [\#53](https://github.com/brooklynDev/airborne/pull/53) ([carhartl](https://github.com/carhartl)) 228 | - Fix expect\_json in case a property is a hash and keys differ [\#52](https://github.com/brooklynDev/airborne/pull/52) ([carhartl](https://github.com/carhartl)) 229 | - unneeded local var assignment [\#51](https://github.com/brooklynDev/airborne/pull/51) ([josephgrossberg](https://github.com/josephgrossberg)) 230 | - Update README.md [\#49](https://github.com/brooklynDev/airborne/pull/49) ([RodneyU215](https://github.com/RodneyU215)) 231 | - Fix headers not passing during rack-test request [\#48](https://github.com/brooklynDev/airborne/pull/48) ([mcordell](https://github.com/mcordell)) 232 | 233 | ## [v0.1.15](https://github.com/brooklynDev/airborne/tree/v0.1.15) (2015-03-03) 234 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.14...v0.1.15) 235 | 236 | **Closed issues:** 237 | 238 | - upgrade rest-client dependency [\#41](https://github.com/brooklynDev/airborne/issues/41) 239 | 240 | **Merged pull requests:** 241 | 242 | - Feature: Allowing symbolized statuses to be passed to \#expect\_status matcher [\#38](https://github.com/brooklynDev/airborne/pull/38) ([jgwmaxwell](https://github.com/jgwmaxwell)) 243 | 244 | ## [v0.1.14](https://github.com/brooklynDev/airborne/tree/v0.1.14) (2015-03-02) 245 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.13...v0.1.14) 246 | 247 | ## [v0.1.13](https://github.com/brooklynDev/airborne/tree/v0.1.13) (2015-03-01) 248 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.12...v0.1.13) 249 | 250 | **Closed issues:** 251 | 252 | - JSON::ParserError 757 on Post Success [\#39](https://github.com/brooklynDev/airborne/issues/39) 253 | 254 | **Merged pull requests:** 255 | 256 | - throw InvalidJsonError when accessing json\_body on invalid json request [\#40](https://github.com/brooklynDev/airborne/pull/40) ([sethpollack](https://github.com/sethpollack)) 257 | 258 | ## [v0.1.12](https://github.com/brooklynDev/airborne/tree/v0.1.12) (2015-03-01) 259 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.11...v0.1.12) 260 | 261 | **Merged pull requests:** 262 | 263 | - Tests fail under RSpec 3.2 due to Airborne\#initialize needing to accept arguments [\#37](https://github.com/brooklynDev/airborne/pull/37) ([jgwmaxwell](https://github.com/jgwmaxwell)) 264 | 265 | ## [v0.1.11](https://github.com/brooklynDev/airborne/tree/v0.1.11) (2015-01-30) 266 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.10...v0.1.11) 267 | 268 | **Closed issues:** 269 | 270 | - Loading multiple grape apps [\#35](https://github.com/brooklynDev/airborne/issues/35) 271 | - Where is the code for `0.1.10`? [\#34](https://github.com/brooklynDev/airborne/issues/34) 272 | - Expected ? to be array got Hash from JSON response [\#33](https://github.com/brooklynDev/airborne/issues/33) 273 | 274 | **Merged pull requests:** 275 | 276 | - Enable base header support when testing Rack applications [\#36](https://github.com/brooklynDev/airborne/pull/36) ([croeck](https://github.com/croeck)) 277 | 278 | ## [v0.1.10](https://github.com/brooklynDev/airborne/tree/v0.1.10) (2015-01-08) 279 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.9...v0.1.10) 280 | 281 | **Closed issues:** 282 | 283 | - undefined method `keys' for nil:NilClass [\#31](https://github.com/brooklynDev/airborne/issues/31) 284 | 285 | **Merged pull requests:** 286 | 287 | - Fix indention in README.md [\#32](https://github.com/brooklynDev/airborne/pull/32) ([kenchan](https://github.com/kenchan)) 288 | 289 | ## [v0.1.9](https://github.com/brooklynDev/airborne/tree/v0.1.9) (2015-01-05) 290 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.8...v0.1.9) 291 | 292 | **Closed issues:** 293 | 294 | - How do you get into an array inside an object? [\#30](https://github.com/brooklynDev/airborne/issues/30) 295 | - ActiveSupport dependency [\#27](https://github.com/brooklynDev/airborne/issues/27) 296 | 297 | ## [v0.1.8](https://github.com/brooklynDev/airborne/tree/v0.1.8) (2014-12-10) 298 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.7...v0.1.8) 299 | 300 | **Closed issues:** 301 | 302 | - nil json\_body with data in body [\#28](https://github.com/brooklynDev/airborne/issues/28) 303 | 304 | ## [v0.1.7](https://github.com/brooklynDev/airborne/tree/v0.1.7) (2014-12-10) 305 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.6...v0.1.7) 306 | 307 | ## [v0.1.6](https://github.com/brooklynDev/airborne/tree/v0.1.6) (2014-12-10) 308 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.5...v0.1.6) 309 | 310 | ## [v0.1.5](https://github.com/brooklynDev/airborne/tree/v0.1.5) (2014-11-26) 311 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.4...v0.1.5) 312 | 313 | **Merged pull requests:** 314 | 315 | - Add Head method [\#26](https://github.com/brooklynDev/airborne/pull/26) ([jsvisa](https://github.com/jsvisa)) 316 | 317 | ## [v0.1.4](https://github.com/brooklynDev/airborne/tree/v0.1.4) (2014-11-24) 318 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.3...v0.1.4) 319 | 320 | **Closed issues:** 321 | 322 | - Update CHANGELOG [\#23](https://github.com/brooklynDev/airborne/issues/23) 323 | - Use of stabby lambda's is breaking jRuby support [\#21](https://github.com/brooklynDev/airborne/issues/21) 324 | 325 | **Merged pull requests:** 326 | 327 | - Rubygems shouldn't check in Gemfile.lock [\#24](https://github.com/brooklynDev/airborne/pull/24) ([svanhess](https://github.com/svanhess)) 328 | 329 | ## [v0.1.3](https://github.com/brooklynDev/airborne/tree/v0.1.3) (2014-11-17) 330 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.2...v0.1.3) 331 | 332 | ## [v0.1.2](https://github.com/brooklynDev/airborne/tree/v0.1.2) (2014-11-16) 333 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.1...v0.1.2) 334 | 335 | **Closed issues:** 336 | 337 | - Handling of empty arrays [\#19](https://github.com/brooklynDev/airborne/issues/19) 338 | - Feature Request: Add API for testing Array size [\#17](https://github.com/brooklynDev/airborne/issues/17) 339 | - Problem with Dates? [\#16](https://github.com/brooklynDev/airborne/issues/16) 340 | - Testing Arrays [\#15](https://github.com/brooklynDev/airborne/issues/15) 341 | - helper methods for dates [\#14](https://github.com/brooklynDev/airborne/issues/14) 342 | 343 | **Merged pull requests:** 344 | 345 | - Fix problem with undefined status code method for Rack::MockResponse [\#20](https://github.com/brooklynDev/airborne/pull/20) ([grzesiek](https://github.com/grzesiek)) 346 | - Feature/matchers/expect json sizes [\#18](https://github.com/brooklynDev/airborne/pull/18) ([PikachuEXE](https://github.com/PikachuEXE)) 347 | 348 | ## [v0.1.1](https://github.com/brooklynDev/airborne/tree/v0.1.1) (2014-10-20) 349 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.1.0...v0.1.1) 350 | 351 | ## [v0.1.0](https://github.com/brooklynDev/airborne/tree/v0.1.0) (2014-10-20) 352 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.23...v0.1.0) 353 | 354 | **Closed issues:** 355 | 356 | - Improper type identification by expect\_json\_types [\#12](https://github.com/brooklynDev/airborne/issues/12) 357 | 358 | **Merged pull requests:** 359 | 360 | - Fixes problems with configure blocks in README [\#13](https://github.com/brooklynDev/airborne/pull/13) ([cmckni3](https://github.com/cmckni3)) 361 | 362 | ## [v0.0.23](https://github.com/brooklynDev/airborne/tree/v0.0.23) (2014-10-05) 363 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.22...v0.0.23) 364 | 365 | **Merged pull requests:** 366 | 367 | - Allow matching against a path [\#11](https://github.com/brooklynDev/airborne/pull/11) ([tikotzky](https://github.com/tikotzky)) 368 | 369 | ## [v0.0.22](https://github.com/brooklynDev/airborne/tree/v0.0.22) (2014-09-29) 370 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.21...v0.0.22) 371 | 372 | **Closed issues:** 373 | 374 | - How to call "expect\_json\_keys" for multiple keys [\#10](https://github.com/brooklynDev/airborne/issues/10) 375 | - array of json objects causes NoMethodError: in base.rb [\#8](https://github.com/brooklynDev/airborne/issues/8) 376 | 377 | ## [v0.0.21](https://github.com/brooklynDev/airborne/tree/v0.0.21) (2014-09-29) 378 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.20...v0.0.21) 379 | 380 | ## [v0.0.20](https://github.com/brooklynDev/airborne/tree/v0.0.20) (2014-09-22) 381 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.19...v0.0.20) 382 | 383 | **Merged pull requests:** 384 | 385 | - Requires only the necessary core\_ext from active\_support. [\#7](https://github.com/brooklynDev/airborne/pull/7) ([cvortmann](https://github.com/cvortmann)) 386 | 387 | ## [v0.0.19](https://github.com/brooklynDev/airborne/tree/v0.0.19) (2014-09-19) 388 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.18...v0.0.19) 389 | 390 | **Closed issues:** 391 | 392 | - Enable Sourcegraph [\#4](https://github.com/brooklynDev/airborne/issues/4) 393 | 394 | **Merged pull requests:** 395 | 396 | - 100% Coveralls Code Coverage ;\) [\#6](https://github.com/brooklynDev/airborne/pull/6) ([tikotzky](https://github.com/tikotzky)) 397 | - Update README.md [\#5](https://github.com/brooklynDev/airborne/pull/5) ([wykhuh](https://github.com/wykhuh)) 398 | - Fix typo on README.md :\) [\#3](https://github.com/brooklynDev/airborne/pull/3) ([mparramont](https://github.com/mparramont)) 399 | 400 | ## [v0.0.18](https://github.com/brooklynDev/airborne/tree/v0.0.18) (2014-09-18) 401 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.17...v0.0.18) 402 | 403 | **Merged pull requests:** 404 | 405 | - Add syntax highlighting to README [\#1](https://github.com/brooklynDev/airborne/pull/1) ([shekibobo](https://github.com/shekibobo)) 406 | 407 | ## [v0.0.17](https://github.com/brooklynDev/airborne/tree/v0.0.17) (2014-09-17) 408 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.16...v0.0.17) 409 | 410 | ## [v0.0.16](https://github.com/brooklynDev/airborne/tree/v0.0.16) (2014-09-17) 411 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.15...v0.0.16) 412 | 413 | ## [v0.0.15](https://github.com/brooklynDev/airborne/tree/v0.0.15) (2014-09-17) 414 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.14...v0.0.15) 415 | 416 | ## [v0.0.14](https://github.com/brooklynDev/airborne/tree/v0.0.14) (2014-09-17) 417 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.13...v0.0.14) 418 | 419 | ## [v0.0.13](https://github.com/brooklynDev/airborne/tree/v0.0.13) (2014-09-17) 420 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.12...v0.0.13) 421 | 422 | ## [v0.0.12](https://github.com/brooklynDev/airborne/tree/v0.0.12) (2014-09-17) 423 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.11...v0.0.12) 424 | 425 | ## [v0.0.11](https://github.com/brooklynDev/airborne/tree/v0.0.11) (2014-09-17) 426 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.10...v0.0.11) 427 | 428 | ## [v0.0.10](https://github.com/brooklynDev/airborne/tree/v0.0.10) (2014-09-17) 429 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.9...v0.0.10) 430 | 431 | ## [v0.0.9](https://github.com/brooklynDev/airborne/tree/v0.0.9) (2014-09-17) 432 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.8...v0.0.9) 433 | 434 | ## [v0.0.8](https://github.com/brooklynDev/airborne/tree/v0.0.8) (2014-09-17) 435 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.7...v0.0.8) 436 | 437 | ## [v0.0.7](https://github.com/brooklynDev/airborne/tree/v0.0.7) (2014-09-17) 438 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.6...v0.0.7) 439 | 440 | ## [v0.0.6](https://github.com/brooklynDev/airborne/tree/v0.0.6) (2014-09-16) 441 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.5...v0.0.6) 442 | 443 | ## [v0.0.5](https://github.com/brooklynDev/airborne/tree/v0.0.5) (2014-09-16) 444 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.4...v0.0.5) 445 | 446 | ## [v0.0.4](https://github.com/brooklynDev/airborne/tree/v0.0.4) (2014-09-16) 447 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.3...v0.0.4) 448 | 449 | ## [v0.0.3](https://github.com/brooklynDev/airborne/tree/v0.0.3) (2014-09-16) 450 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.2...v0.0.3) 451 | 452 | ## [v0.0.2](https://github.com/brooklynDev/airborne/tree/v0.0.2) (2014-09-16) 453 | [Full Changelog](https://github.com/brooklynDev/airborne/compare/v0.0.1...v0.0.2) 454 | 455 | ## [v0.0.1](https://github.com/brooklynDev/airborne/tree/v0.0.1) (2014-09-16) 456 | 457 | 458 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | gem 'coveralls', require: false 6 | gem 'faraday-retry', require: false 7 | 8 | group :test do 9 | gem 'webmock' 10 | gem 'sinatra' 11 | end 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014 brooklyndev, sethpollack 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Airborne 2 | 3 | [![airborne travis](http://img.shields.io/travis/brooklynDev/airborne.svg?branch=master&style=flat-square)](https://travis-ci.org/brooklynDev/airborne) 4 | [![airborne coveralls](http://img.shields.io/coveralls/brooklynDev/airborne/master.svg?style=flat-square)](https://coveralls.io/r/brooklynDev/airborne?branch=master) 5 | [![Code Climate](https://api.codeclimate.com/v1/badges/00644ffcf94d5813aa80/maintainability)](https://codeclimate.com/github/brooklynDev/airborne/maintainability) 6 | [![airborne gem version](http://img.shields.io/gem/v/airborne.svg?style=flat-square)](http://rubygems.org/gems/airborne) 7 | [![airbore gem downloads](http://img.shields.io/gem/dt/airborne.svg?style=flat-square)](http://rubygems.org/gems/airborne) 8 | [![airborne gem stable downloads](http://img.shields.io/gem/dv/airborne/stable.svg?style=flat-square)](http://rubygems.org/gems/airborne) 9 | 10 | RSpec driven API testing framework 11 | 12 | ## Looking for Project Maintainers 13 | I am looking for project maintainers to help keep airborne up to date and bug-free while avoiding feature creep and maintaining backwards compatibility. 14 | 15 | Comment [here](https://github.com/brooklynDev/airborne/issues/134) if you would like to help out. 16 | 17 | ## Installation 18 | 19 | Install Airborne: 20 | ```shell 21 | $ gem install airborne 22 | ``` 23 | Or add it to your Gemfile: 24 | ```ruby 25 | gem 'airborne' 26 | ``` 27 | ## Creating Tests 28 | 29 | ```ruby 30 | require 'airborne' 31 | 32 | describe 'sample spec' do 33 | it 'should validate types' do 34 | get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" } 35 | expect_json_types(name: :string) 36 | end 37 | 38 | it 'should validate values' do 39 | get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" } 40 | expect_json(name: 'John Doe') 41 | end 42 | end 43 | ``` 44 | 45 | When calling expect_json_types, these are the valid types that can be tested against: 46 | 47 | * `:int` or `:integer` 48 | * `:float` 49 | * `:bool` or `:boolean` 50 | * `:string` 51 | * `:date` 52 | * `:object` 53 | * `:null` 54 | * `:array` 55 | * `:array_of_integers` or `:array_of_ints` 56 | * `:array_of_floats` 57 | * `:array_of_strings` 58 | * `:array_of_booleans` or `:array_of_bools` 59 | * `:array_of_objects` 60 | * `:array_of_arrays` 61 | 62 | If the properties are optional and may not appear in the response, you can append `_or_null` to the types above. 63 | 64 | ```ruby 65 | describe 'sample spec' do 66 | it 'should validate types' do 67 | get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" } or { "name" : "John Doe", "age" : 45 } 68 | expect_json_types(name: :string, age: :int_or_null) 69 | end 70 | end 71 | ``` 72 | 73 | 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`: 74 | 75 | ```ruby 76 | it 'should allow optional nested hash' do 77 | get '/simple_path_get' #may or may not return coordinates 78 | expect_json_types('address.coordinates', optional(latitude: :float, longitude: :float)) 79 | end 80 | ``` 81 | 82 | Additionally, when calling `expect_json`, you can provide a regex pattern in a call to `regex`: 83 | 84 | ```ruby 85 | describe 'sample spec' do 86 | it 'should validate types' do 87 | get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" } 88 | expect_json(name: regex("^John")) 89 | end 90 | end 91 | ``` 92 | 93 | When calling `expect_json` or `expect_json_types`, you can optionally provide a block and run your own `rspec` expectations: 94 | 95 | ```ruby 96 | describe 'sample spec' do 97 | it 'should validate types' do 98 | get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" } 99 | expect_json(name: -> (name){ expect(name.length).to eq(8) }) 100 | end 101 | end 102 | ``` 103 | 104 | Calling `expect_json_sizes` actually make use of the above feature and call `expect_json` under the hood: 105 | 106 | ```ruby 107 | describe 'sample spec' do 108 | it 'should validate types' do 109 | get 'http://example.com/api/v1/simple_get_collection' #json api that returns { "ids" : [1, 2, 3, 4] } 110 | expect_json_sizes(ids: 4) 111 | end 112 | end 113 | ``` 114 | 115 | ## Making requests 116 | 117 | 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: 118 | 119 | * `response` - The HTTP response returned from the request 120 | * `headers` - A symbolized hash of the response headers returned by the request 121 | * `body` - The raw HTTP body returned from the request 122 | * `json_body` - A symbolized hash representation of the JSON returned by the request 123 | 124 | For example: 125 | 126 | ```ruby 127 | it 'should validate types' do 128 | get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" } 129 | name = json_body[:name] #name will equal "John Doe" 130 | body_as_string = body 131 | end 132 | ``` 133 | 134 | When calling any of the methods above, you can pass request headers to be used. 135 | 136 | ```ruby 137 | get 'http://example.com/api/v1/my_api', { 'x-auth-token' => 'my_token' } 138 | ``` 139 | 140 | For requests that require a body (`post`, `put`, `patch`) you can pass the body as well: 141 | 142 | ```ruby 143 | post 'http://example.com/api/v1/my_api', { :name => 'John Doe' }, { 'x-auth-token' => 'my_token' } 144 | ``` 145 | 146 | The body may be any JSON-serializable type, as long as you want to post `application/json` content type. 147 | You may set a different content type and post a string body this way: 148 | 149 | ```ruby 150 | post 'http://example.com/api/v1/my_api', "Hello there!", { content_type: 'text/plain' } 151 | ``` 152 | 153 | For requests that require Query params you can pass a params hash into headers. 154 | 155 | ```ruby 156 | post 'http://example.com/api/v1/my_api', { }, { 'params' => {'param_key' => 'param_value' } } 157 | ``` 158 | 159 | ### (Not) Verifying SSL Certificates 160 | 161 | SSL certificate verification is enabled by default (specifically, `OpenSSL::SSL::VERIFY_PEER`). 162 | 163 | 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. 164 | 165 | You can override this behavior per request: 166 | 167 | ```ruby 168 | verify_ssl = false 169 | post 'http://example.com/api/v1/my_api', "Hello there!", { content_type: 'text/plain' }, verify_ssl 170 | ``` 171 | 172 | or with a global Airborne configuration: 173 | 174 | ```ruby 175 | Airborne.configure do |config| 176 | config.verify_ssl = false # equivalent to OpenSSL::SSL::VERIFY_NONE 177 | end 178 | ``` 179 | 180 | Note the per-request option always overrides the Airborne configuration: 181 | 182 | ```ruby 183 | before do 184 | Airborne.configuration.verify_ssl = false 185 | end 186 | 187 | it 'will still verify the SSL certificate' do 188 | verify_ssl = true 189 | post 'http://example.com/api/v1/my_api', "Hello there!", { content_type: 'text/plain' }, verify_ssl 190 | end 191 | ``` 192 | 193 | You can use the `verify_ssl` setting to override your global defaults in test blocks like this: 194 | 195 | ```ruby 196 | describe 'test something', verify_ssl: false do 197 | end 198 | ``` 199 | 200 | OR 201 | 202 | ```ruby 203 | describe 'test something' do 204 | Airborne.configuration.verify_ssl = false 205 | end 206 | ``` 207 | 208 | 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. 209 | 210 | ## Testing Rack Applications 211 | 212 | 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: 213 | 214 | ```ruby 215 | Airborne.configure do |config| 216 | config.rack_app = MySinatraApp 217 | end 218 | ``` 219 | 220 | Under the covers, Airborne uses [rack-test](https://github.com/brynary/rack-test) to make the requests. 221 | 222 | ## Rails Applications 223 | 224 | If you're testing an API you've written in Rails, Airborne plays along with `rspec-rails`: 225 | 226 | 227 | ```ruby 228 | require 'rails_helper' 229 | 230 | RSpec.describe HomeController, :type => :controller do 231 | describe 'GET index' do 232 | it 'returns correct types' do 233 | get :index, :format => 'json' #if your route responds to both html and json 234 | expect_json_types(foo: :string) 235 | end 236 | end 237 | end 238 | ``` 239 | 240 | ## API 241 | 242 | * `expect_json_types` - Tests the types of the JSON property values returned 243 | * `expect_json` - Tests the values of the JSON property values returned 244 | * `expect_json_keys` - Tests the existence of the specified keys in the JSON object 245 | * `expect_json_sizes` - Tests the sizes of the JSON property values returned, also test if the values are arrays 246 | * `expect_status` - Tests the HTTP status code returned 247 | * `expect_header` - Tests for a specified header in the response 248 | * `expect_header_contains` - Partial match test on a specified header 249 | 250 | ## Path Matching 251 | 252 | When calling `expect_json_types`, `expect_json`, `expect_json_keys` or `expect_json_sizes` you can optionally specify a path as a first parameter. 253 | 254 | For example, if our API returns the following JSON: 255 | 256 | ```json 257 | { 258 | "name": "Alex", 259 | "address": { 260 | "street": "Area 51", 261 | "city": "Roswell", 262 | "state": "NM", 263 | "coordinates": { 264 | "latitude": 33.3872, 265 | "longitude": 104.5281 266 | } 267 | } 268 | } 269 | ``` 270 | 271 | This test would only test the address object: 272 | 273 | ```ruby 274 | describe 'path spec' do 275 | it 'should allow simple path and verify only that path' do 276 | get 'http://example.com/api/v1/simple_path_get' 277 | expect_json_types('address', street: :string, city: :string, state: :string, coordinates: :object) 278 | #or this 279 | expect_json_types('address', street: :string, city: :string, state: :string, coordinates: { latitude: :float, longitude: :float }) 280 | end 281 | end 282 | ``` 283 | Or, to test the existence of specific keys: 284 | 285 | ```ruby 286 | it 'should allow nested paths' do 287 | get 'http://example.com/api/v1/simple_path_get' 288 | expect_json_keys('address', [:street, :city, :state, :coordinates]) 289 | end 290 | ``` 291 | 292 | Alternatively, if we only want to test `coordinates` we can dot into just the `coordinates`: 293 | 294 | ```ruby 295 | it 'should allow nested paths' do 296 | get 'http://example.com/api/v1/simple_path_get' 297 | expect_json('address.coordinates', latitude: 33.3872, longitude: 104.5281) 298 | end 299 | ``` 300 | 301 | When dealing with `arrays`, we can optionally test all (`*`) or a single (`?` - any, `0` - index) element of the array: 302 | 303 | Given the following JSON: 304 | 305 | ```json 306 | { 307 | "cars": [ 308 | { 309 | "make": "Tesla", 310 | "model": "Model S" 311 | }, 312 | { 313 | "make": "Lamborghini", 314 | "model": "Aventador" 315 | } 316 | ] 317 | } 318 | ``` 319 | 320 | We can test against just the first car like this: 321 | 322 | ```ruby 323 | it 'should index into array and test against specific element' do 324 | get '/array_api' 325 | expect_json('cars.0', make: 'Tesla', model: 'Model S') 326 | end 327 | ``` 328 | 329 | To test the types of all elements in the array: 330 | 331 | ```ruby 332 | it 'should test all elements of the array' do 333 | get 'http://example.com/api/v1/array_api' 334 | expect_json('cars.?', make: 'Tesla', model: 'Model S') # tests that one car in array matches the tesla 335 | expect_json_types('cars.*', make: :string, model: :string) # tests all cars in array for make and model of type string 336 | end 337 | ``` 338 | 339 | `*` and `?` work for nested arrays as well. Given the following JSON: 340 | 341 | ```json 342 | { 343 | "cars": [ 344 | { 345 | "make": "Tesla", 346 | "model": "Model S", 347 | "owners": [ 348 | { 349 | "name": "Bart Simpson" 350 | } 351 | ] 352 | }, 353 | { 354 | "make": "Lamborghini", 355 | "model": "Aventador", 356 | "owners": [ 357 | { 358 | "name": "Peter Griffin" 359 | } 360 | ] 361 | } 362 | ] 363 | } 364 | ``` 365 | 366 | === 367 | 368 | ```ruby 369 | it 'should check all nested arrays for specified elements' do 370 | get 'http://example.com/api/v1/array_with_nested' 371 | expect_json_types('cars.*.owners.*', name: :string) 372 | end 373 | ``` 374 | 375 | ## Dates 376 | 377 | 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: 378 | 379 | ```ruby 380 | it 'should verify date type' do 381 | get '/get_date' #api that returns {createdAt: "Mon Oct 20 2014 16:10:42 GMT-0400 (EDT)"} 382 | expect_json_types(createdAt: :date) 383 | end 384 | ``` 385 | However if you want to check the actual date data with `expect_json`, you need to call the `date` function: 386 | 387 | ```ruby 388 | it 'should verify correct date value' do 389 | get '/get_date' #api that returns {createdAt: "Mon Oct 20 2014 16:10:42 GMT-0400 (EDT)"} 390 | prev_day = DateTime.new(2014,10,19) 391 | next_day = DateTime.new(2014,10,21) 392 | #within the date callback, you can use regular RSpec expectations that work with dates 393 | expect_json(createdAt: date { |value| expect(value).to be_between(prev_day, next_day) }) 394 | end 395 | ``` 396 | 397 | ## Configuration 398 | 399 | When setting up Airborne, you can call `configure` just like you would with `rspec`: 400 | 401 | ```ruby 402 | #config is the RSpec configuration and can be used just like it 403 | Airborne.configure do |config| 404 | config.include MyModule 405 | end 406 | ``` 407 | 408 | Additionally, you can specify a `base_url` and default `headers` to be used on every request (unless overridden in the actual request): 409 | 410 | ```ruby 411 | Airborne.configure do |config| 412 | config.base_url = 'http://example.com/api/v1' 413 | config.headers = { 'x-auth-token' => 'my_token' } 414 | end 415 | 416 | describe 'spec' do 417 | it 'now we no longer need the full url' do 418 | get '/simple_get' 419 | expect_json_types(name: :string) 420 | end 421 | end 422 | ``` 423 | 424 | 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. 425 | 426 | ```ruby 427 | Airborne.configure do |config| 428 | config.match_expected_default = true 429 | config.match_actual_default = false 430 | end 431 | ``` 432 | 433 | `match_expected_default` requires all the keys in the expected JSON are present in the response. 434 | `match_actual_default` requires that the keys in the response are tested in the expected Hash. 435 | 436 | So you can do the following combinations: 437 | 438 | `match_expected_default=false`, `match_actual_default=false` - check only intersection 439 | `match_expected_default=false`, `match_actual_default=true` - raise on extra key in response 440 | `match_expected_default=true`, `match_actual_default=false` - raise on missing key in response 441 | `match_expected_default=true`, `match_actual_default=true` - expect exact match 442 | 443 | 444 | Airborne sets `match_expected_default` to `true` and `match_actual_default` to `false` by default. 445 | 446 | You can use the `match_expected` and `match_actual` settings to override your global defaults in test blocks like this. 447 | 448 | ```ruby 449 | describe 'test something', match_expected: true, match_actual: false do 450 | end 451 | ``` 452 | 453 | OR 454 | 455 | ```ruby 456 | describe 'test something' do 457 | Airborne.configuration.match_expected = true 458 | Airborne.configuration.match_actual = false 459 | end 460 | ``` 461 | 462 | ## Run it from the CLI 463 | 464 | ```shell 465 | $ cd your/project 466 | $ rspec spec 467 | ``` 468 | ## Authors 469 | * [Seth Pollack](https://github.com/sethpollack) 470 | * [Alex Friedman](https://github.com/brooklynDev) 471 | 472 | ## Contributors 473 | https://github.com/brooklynDev/airborne/graphs/contributors 474 | 475 | Inspired by [frisby.js](https://github.com/vlucas/frisby) 476 | 477 | ## License 478 | 479 | The MIT License 480 | 481 | Copyright (c) 2014 [brooklyndev](https://github.com/brooklynDev), [sethpollack](https://github.com/sethpollack) 482 | 483 | 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: 484 | 485 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 486 | 487 | 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. 488 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | task default: [:spec] 2 | -------------------------------------------------------------------------------- /airborne.gemspec: -------------------------------------------------------------------------------- 1 | require 'date' 2 | 3 | Gem::Specification.new do |s| 4 | s.name = 'airborne' 5 | s.version = '0.3.7' 6 | s.date = Date.today.to_s 7 | s.summary = 'RSpec driven API testing framework' 8 | s.authors = ['Alex Friedman', 'Seth Pollack'] 9 | s.email = ['a.friedman07@gmail.com', 'seth@sethpollack.net'] 10 | s.require_paths = ['lib'] 11 | s.files = `git ls-files`.split("\n") 12 | s.license = 'MIT' 13 | s.add_runtime_dependency 'rspec', '~> 3.8' 14 | s.add_runtime_dependency 'rest-client', '< 3.0', '>= 2.0.2' 15 | s.add_runtime_dependency 'rack-test', '< 3', '>= 1.1.0' 16 | s.add_runtime_dependency 'rack' 17 | s.add_runtime_dependency 'activesupport' 18 | s.add_development_dependency 'webmock', '~> 3' 19 | s.add_development_dependency 'rake', '~> 12' 20 | s.add_development_dependency 'github_changelog_generator', '~> 1.14' 21 | end 22 | -------------------------------------------------------------------------------- /lib/airborne.rb: -------------------------------------------------------------------------------- 1 | require 'airborne/optional_hash_type_expectations' 2 | require 'airborne/path_matcher' 3 | require 'airborne/request_expectations' 4 | require 'airborne/rest_client_requester' 5 | require 'airborne/rack_test_requester' 6 | require 'airborne/base' 7 | 8 | RSpec.configure do |config| 9 | config.add_setting :base_url 10 | config.add_setting :match_expected 11 | config.add_setting :match_actual 12 | config.add_setting :match_expected_default, default: true 13 | config.add_setting :match_actual_default, default: false 14 | config.add_setting :headers 15 | config.add_setting :rack_app 16 | config.add_setting :requester_type 17 | config.add_setting :requester_module 18 | config.add_setting :verify_ssl, default: true 19 | config.before do |example| 20 | config.match_expected = example.metadata[:match_expected].nil? ? 21 | Airborne.configuration.match_expected_default? : example.metadata[:match_expected] 22 | config.match_actual = example.metadata[:match_actual].nil? ? 23 | Airborne.configuration.match_actual_default? : example.metadata[:match_actual] 24 | config.verify_ssl = example.metadata[:verify_ssl].nil? ? 25 | Airborne.configuration.verify_ssl? : example.metadata[:verify_ssl] 26 | end 27 | 28 | # Include last since it depends on the configuration already being added 29 | config.include Airborne 30 | end 31 | -------------------------------------------------------------------------------- /lib/airborne/base.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'active_support' 3 | require 'active_support/core_ext/hash/indifferent_access' 4 | 5 | module Airborne 6 | class InvalidJsonError < StandardError; end 7 | 8 | include RequestExpectations 9 | 10 | attr_reader :response, :headers, :body 11 | 12 | def self.configure 13 | RSpec.configure do |config| 14 | yield config 15 | end 16 | end 17 | 18 | def self.included(base) 19 | if !Airborne.configuration.requester_module.nil? 20 | base.send(:include, Airborne.configuration.requester_module) 21 | elsif !Airborne.configuration.rack_app.nil? 22 | base.send(:include, RackTestRequester) 23 | else 24 | base.send(:include, RestClientRequester) 25 | end 26 | end 27 | 28 | def self.configuration 29 | RSpec.configuration 30 | end 31 | 32 | def get(url, headers = nil, verify_ssl = base_verify_ssl) 33 | @response = make_request(:get, url, headers: headers, verify_ssl: verify_ssl) 34 | end 35 | 36 | def post(url, post_body = nil, headers = nil, verify_ssl = base_verify_ssl) 37 | @response = make_request(:post, url, body: post_body, headers: headers, verify_ssl: verify_ssl) 38 | end 39 | 40 | def patch(url, patch_body = nil, headers = nil, verify_ssl = base_verify_ssl) 41 | @response = make_request(:patch, url, body: patch_body, headers: headers, verify_ssl: verify_ssl) 42 | end 43 | 44 | def put(url, put_body = nil, headers = nil, verify_ssl = base_verify_ssl) 45 | @response = make_request(:put, url, body: put_body, headers: headers, verify_ssl: verify_ssl) 46 | end 47 | 48 | def delete(url, delete_body = nil, headers = nil, verify_ssl = base_verify_ssl) 49 | @response = make_request(:delete, url, body: delete_body, headers: headers, verify_ssl: verify_ssl) 50 | end 51 | 52 | def head(url, headers = nil) 53 | @response = make_request(:head, url, headers: headers) 54 | end 55 | 56 | def options(url, headers = nil) 57 | @response = make_request(:options, url, headers: headers) 58 | end 59 | 60 | def response 61 | @response 62 | end 63 | 64 | def headers 65 | HashWithIndifferentAccess.new(response.headers) 66 | end 67 | 68 | def body 69 | response.body 70 | end 71 | 72 | def json_body 73 | JSON.parse(response.body, symbolize_names: true) rescue fail InvalidJsonError, 'Api request returned invalid json' 74 | end 75 | 76 | private 77 | 78 | def get_url(url) 79 | base = Airborne.configuration.base_url || '' 80 | base + url 81 | end 82 | 83 | def base_verify_ssl 84 | Airborne.configuration.verify_ssl || false 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/airborne/optional_hash_type_expectations.rb: -------------------------------------------------------------------------------- 1 | module Airborne 2 | class OptionalHashTypeExpectations 3 | include Enumerable 4 | attr_accessor :hash 5 | def initialize(hash) 6 | @hash = hash 7 | end 8 | 9 | def each 10 | @hash.each do|k, v| 11 | yield(k, v) 12 | end 13 | end 14 | 15 | def [](val) 16 | @hash[val] 17 | end 18 | 19 | def keys 20 | @hash.keys 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/airborne/path_matcher.rb: -------------------------------------------------------------------------------- 1 | module Airborne 2 | class PathError < StandardError; end 3 | 4 | module PathMatcher 5 | def get_by_path(path, json, &block) 6 | fail PathError, "Invalid Path, contains '..'" if /\.\./ =~ path 7 | type = false 8 | parts = path.split('.') 9 | parts.each_with_index do |part, index| 10 | if part == '*' || part == '?' 11 | ensure_array(path, json) 12 | type = part 13 | if index < parts.length.pred 14 | walk_with_path(type, index, path, parts, json, &block) && return 15 | end 16 | next 17 | end 18 | begin 19 | json = process_json(part, json) 20 | rescue 21 | raise PathError, "Expected #{json.class}\nto be an object with property #{part}" 22 | end 23 | end 24 | if type == '*' 25 | expect_all(json, &block) 26 | elsif type == '?' 27 | expect_one(path, json, &block) 28 | else 29 | yield json 30 | end 31 | end 32 | 33 | private 34 | 35 | def walk_with_path(type, index, path, parts, json, &block) 36 | last_error = nil 37 | item_count = json.length 38 | error_count = 0 39 | json.each do |element| 40 | begin 41 | sub_path = parts[(index.next)...(parts.length)].join('.') 42 | get_by_path(sub_path, element, &block) 43 | rescue Exception => e 44 | last_error = e 45 | error_count += 1 46 | end 47 | ensure_match_all(last_error) if type == '*' 48 | ensure_match_one(path, item_count, error_count) if type == '?' 49 | end 50 | end 51 | 52 | def process_json(part, json) 53 | if index?(part) && json.is_a?(Array) 54 | part = part.to_i 55 | json = json[part] 56 | else 57 | json = json[part.to_sym] 58 | end 59 | json 60 | end 61 | 62 | def index?(part) 63 | part =~ /^\d+$/ 64 | end 65 | 66 | def expect_one(path, json, &block) 67 | item_count = json.length 68 | error_count = 0 69 | json.each do |part| 70 | begin 71 | yield part 72 | rescue Exception 73 | error_count += 1 74 | ensure_match_one(path, item_count, error_count) 75 | end 76 | end 77 | end 78 | 79 | def expect_all(json, &block) 80 | last_error = nil 81 | begin 82 | json.each { |part| yield part } 83 | rescue Exception => e 84 | last_error = e 85 | end 86 | ensure_match_all(last_error) 87 | end 88 | 89 | def ensure_match_one(path, item_count, error_count) 90 | fail RSpec::Expectations::ExpectationNotMetError, "Expected one object in path #{path} to match provided JSON values" if item_count == error_count 91 | end 92 | 93 | def ensure_match_all(error) 94 | fail error unless error.nil? 95 | end 96 | 97 | def ensure_array(path, json) 98 | fail RSpec::Expectations::ExpectationNotMetError, "Expected #{path} to be array got #{json.class} from JSON response" unless json.class == Array 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/airborne/rack_test_requester.rb: -------------------------------------------------------------------------------- 1 | require 'rack/test' 2 | 3 | module Airborne 4 | module RackTestRequester 5 | def make_request(method, url, options = {}) 6 | headers = options[:headers] || {} 7 | base_headers = Airborne.configuration.headers || {} 8 | headers = base_headers.merge(headers) 9 | browser = Rack::Test::Session.new(Rack::MockSession.new(Airborne.configuration.rack_app)) 10 | headers.each { |name, value| browser.header(name, value) } 11 | browser.send(method, url, options[:body] || {}, headers) 12 | Rack::MockResponse.class_eval do 13 | alias_method :code, :status 14 | end 15 | browser.last_response 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/airborne/request_expectations.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'date' 3 | require 'rack/utils' 4 | 5 | module Airborne 6 | class ExpectationError < StandardError; end 7 | module RequestExpectations 8 | include RSpec 9 | include PathMatcher 10 | 11 | def expect_json_types(*args) 12 | call_with_path(args) do |param, body| 13 | expect_json_types_impl(param, body) 14 | end 15 | end 16 | 17 | def expect_json(*args) 18 | call_with_path(args) do |param, body| 19 | expect_json_impl(param, body) 20 | end 21 | end 22 | 23 | def expect_json_keys(*args) 24 | call_with_path(args) do |param, body| 25 | expect(body.keys).to include(*param) 26 | end 27 | end 28 | 29 | def expect_json_sizes(*args) 30 | args.push(convert_expectations_for_json_sizes(args.pop)) 31 | 32 | expect_json_types(*args) 33 | end 34 | 35 | def expect_status(code) 36 | expect(response.code).to eq(resolve_status(code, response.code)) 37 | end 38 | 39 | def expect_header(key, content) 40 | expect_header_impl(key, content) 41 | end 42 | 43 | def expect_header_contains(key, content) 44 | expect_header_impl(key, content, true) 45 | end 46 | 47 | def optional(hash) 48 | OptionalHashTypeExpectations.new(hash) 49 | end 50 | 51 | def regex(reg) 52 | Regexp.new(reg) 53 | end 54 | 55 | def date 56 | ->(value) { yield DateTime.parse(value) } 57 | end 58 | 59 | private 60 | 61 | def expect_header_impl(key, content, contains = nil) 62 | header = headers[key] 63 | if header 64 | if contains 65 | expect(header.downcase).to include(content.downcase) 66 | else 67 | expect(header.downcase).to eq(content.downcase) 68 | end 69 | else 70 | fail RSpec::Expectations::ExpectationNotMetError, "Header #{key} not present in the HTTP response" 71 | end 72 | end 73 | 74 | def expect_json_impl(expected, actual) 75 | return if nil_optional_hash?(expected, actual) 76 | 77 | actual = actual.to_s if expected.is_a?(Regexp) 78 | 79 | return expect(actual).to match(expected) if property?(expected) 80 | 81 | keys = [] 82 | 83 | keys << expected.keys if match_expected? 84 | keys << actual.keys if match_actual? 85 | keys = expected.keys & actual.keys if match_none? 86 | 87 | keys.flatten.uniq.each do |prop| 88 | expected_value = extract_expected_value(expected, prop) 89 | actual_value = extract_actual(actual, prop) 90 | 91 | next expect_json_impl(expected_value, actual_value) if hash?(expected_value) && hash?(actual_value) 92 | next expected_value.call(actual_value) if expected_value.is_a?(Proc) 93 | next expect(actual_value.to_s).to match(expected_value) if expected_value.is_a?(Regexp) 94 | 95 | expect(actual_value).to eq(expected_value) 96 | end 97 | end 98 | 99 | def expect_json_types_impl(expected, actual) 100 | return if nil_optional_hash?(expected, actual) 101 | 102 | @mapper ||= get_mapper 103 | 104 | actual = convert_to_date(actual) if ((expected == :date) || (expected == :date_or_null)) 105 | 106 | return expect_type(expected, actual) if expected.is_a?(Symbol) 107 | return expected.call(actual) if expected.is_a?(Proc) 108 | 109 | keys = [] 110 | 111 | keys << expected.keys if match_expected? 112 | keys << actual.keys if match_actual? 113 | keys = expected.keys & actual.keys if match_none? 114 | 115 | keys.flatten.uniq.each do |prop| 116 | type = extract_expected_type(expected, prop) 117 | value = extract_actual(actual, prop) 118 | value = convert_to_date(value) if ((type == :date) || (type == :date_or_null)) 119 | 120 | next expect_json_types_impl(type, value) if hash?(type) 121 | next type.call(value) if type.is_a?(Proc) 122 | 123 | type_string = type.to_s 124 | 125 | if type_string.include?('array_of') && !(type_string.include?('or_null') && value.nil?) 126 | check_array_types(value, prop, type) 127 | else 128 | expect_type(type, value, prop) 129 | end 130 | end 131 | end 132 | 133 | def call_with_path(args) 134 | if args.length == 2 135 | get_by_path(args[0], json_body) do |json_chunk| 136 | yield(args[1], json_chunk) 137 | end 138 | else 139 | yield(args[0], json_body) 140 | end 141 | end 142 | 143 | def extract_expected_value(expected, prop) 144 | begin 145 | raise unless expected.keys.include?(prop) 146 | expected[prop] 147 | rescue 148 | raise ExpectationError, "Expectation is expected to contain property: #{prop}" 149 | end 150 | end 151 | 152 | def extract_expected_type(expected, prop) 153 | begin 154 | type = expected[prop] 155 | type.nil? ? raise : type 156 | rescue 157 | raise ExpectationError, "Expectation is expected to contain property: #{prop}" 158 | end 159 | end 160 | 161 | def extract_actual(actual, prop) 162 | begin 163 | value = actual[prop] 164 | rescue 165 | raise ExpectationError, "Expected #{actual.class} #{actual}\nto be an object with property #{prop}" 166 | end 167 | end 168 | 169 | def expect_type(expected_type, value, prop_name = nil) 170 | fail ExpectationError, "Expected type #{expected_type}\nis an invalid type" if @mapper[expected_type].nil? 171 | 172 | insert = prop_name.nil? ? '' : "#{prop_name} to be of type" 173 | message = "Expected #{insert} #{expected_type}\n got #{value.class} instead" 174 | 175 | expect(@mapper[expected_type].any?{|type| value.is_a?(type)}).to eq(true), message 176 | end 177 | 178 | def convert_to_date(value) 179 | begin 180 | DateTime.parse(value) 181 | rescue 182 | end 183 | end 184 | 185 | def check_array_types(value, prop_name, expected_type) 186 | expect_array(value, prop_name, expected_type) 187 | value.each do |val| 188 | expect_type(expected_type, val, prop_name) 189 | end 190 | end 191 | 192 | def nil_optional_hash?(expected, hash) 193 | expected.is_a?(Airborne::OptionalHashTypeExpectations) && hash.nil? 194 | end 195 | 196 | def hash?(hash) 197 | hash.is_a?(Hash) || hash.is_a?(Airborne::OptionalHashTypeExpectations) 198 | end 199 | 200 | def expect_array(value, prop_name, expected_type) 201 | expect(value.class).to eq(Array), "Expected #{prop_name}\n to be of type #{expected_type}\n got #{value.class} instead" 202 | end 203 | 204 | def convert_expectations_for_json_sizes(old_expectations) 205 | unless old_expectations.is_a?(Hash) 206 | return convert_expectation_for_json_sizes(old_expectations) 207 | end 208 | 209 | old_expectations.each_with_object({}) do |(prop_name, expected_size), memo| 210 | new_value = if expected_size.is_a?(Hash) 211 | convert_expectations_for_json_sizes(expected_size) 212 | else 213 | convert_expectation_for_json_sizes(expected_size) 214 | end 215 | memo[prop_name] = new_value 216 | end 217 | end 218 | 219 | def convert_expectation_for_json_sizes(expected_size) 220 | ->(data) { expect(data.size).to eq(expected_size) } 221 | end 222 | 223 | def ensure_hash_contains_prop(prop_name, hash) 224 | begin 225 | yield 226 | rescue 227 | raise ExpectationError, "Expected #{hash.class} #{hash}\nto be an object with property #{prop_name}" 228 | end 229 | end 230 | 231 | def property?(expectation) 232 | [String, Regexp, Float, Integer, TrueClass, FalseClass, NilClass, Array].any?{|type| expectation.is_a?(type)} 233 | end 234 | 235 | def get_mapper 236 | base_mapper = { 237 | integer: [Integer], 238 | array_of_integers: [Integer], 239 | int: [Integer], 240 | array_of_ints: [Integer], 241 | float: [Float, Integer], 242 | array_of_floats: [Float, Integer], 243 | string: [String], 244 | array_of_strings: [String], 245 | boolean: [TrueClass, FalseClass], 246 | array_of_booleans: [TrueClass, FalseClass], 247 | bool: [TrueClass, FalseClass], 248 | array_of_bools: [TrueClass, FalseClass], 249 | object: [Hash], 250 | array_of_objects: [Hash], 251 | array: [Array], 252 | array_of_arrays: [Array], 253 | date: [DateTime], 254 | null: [NilClass] 255 | } 256 | 257 | mapper = base_mapper.clone 258 | base_mapper.each do |key, value| 259 | mapper[(key.to_s + '_or_null').to_sym] = value + [NilClass] 260 | end 261 | mapper 262 | end 263 | 264 | def resolve_status(candidate, authority) 265 | candidate = Rack::Utils::SYMBOL_TO_STATUS_CODE[candidate] if candidate.is_a?(Symbol) 266 | case authority 267 | when String then candidate.to_s 268 | when Integer then candidate.to_i 269 | else candidate 270 | end 271 | end 272 | 273 | def match_none? 274 | !match_actual? && !match_expected? 275 | end 276 | 277 | def match_actual? 278 | Airborne.configuration.match_actual? 279 | end 280 | 281 | def match_expected? 282 | Airborne.configuration.match_expected? 283 | end 284 | end 285 | end 286 | -------------------------------------------------------------------------------- /lib/airborne/rest_client_requester.rb: -------------------------------------------------------------------------------- 1 | require 'rest_client' 2 | 3 | module Airborne 4 | module RestClientRequester 5 | def make_request(method, url, options = {}) 6 | headers = base_headers.merge(options[:headers] || {}) 7 | verify_ssl = options.fetch(:verify_ssl, true) 8 | res = if method == :post || method == :patch || method == :put || method == :delete 9 | begin 10 | request_body = options[:body].nil? || is_empty(options[:body]) ? '' : options[:body] 11 | request_body = request_body.to_json if is_json_request(headers) && !is_empty(request_body) 12 | RestClient::Request.execute( 13 | method: method, 14 | url: get_url(url), 15 | payload: request_body, 16 | headers: headers, 17 | verify_ssl: verify_ssl 18 | ) { |response, request, result| response } 19 | rescue RestClient::Exception => e 20 | e.response ? e.response : e.original_exception 21 | end 22 | else 23 | begin 24 | RestClient::Request.execute( 25 | method: method, 26 | url: get_url(url), 27 | headers: headers, 28 | verify_ssl: verify_ssl 29 | ) { |response, request, result| response } 30 | rescue RestClient::Exception => e 31 | e.response ? e.response : e.original_exception 32 | end 33 | end 34 | res 35 | end 36 | 37 | private 38 | 39 | def is_json_request(headers) 40 | header = headers.fetch(:content_type) 41 | header == :json || /application\/([a-zA-Z0-9\.\_\-]*\+?)json/ =~ header 42 | end 43 | 44 | def is_empty(body) 45 | return body.empty? if body.respond_to?(:empty?) 46 | 47 | false 48 | end 49 | 50 | def base_headers 51 | { content_type: :json }.merge(Airborne.configuration.headers || {}) 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /rakelib/changelog.rake: -------------------------------------------------------------------------------- 1 | require 'github_changelog_generator/task' 2 | 3 | if ENV['CHANGELOG_GITHUB_TOKEN'].nil? 4 | warn '[rake changelog] You need to export CHANGELOG_GITHUB_TOKEN first! You can generate one at: https://github.com/settings/tokens/new' 5 | return 6 | end 7 | 8 | GitHubChangelogGenerator::RakeTask.new :changelog do |config| 9 | config.project = 'airborne' 10 | 11 | # change this to your github username if you plan to submit a PR with a new CHANGELOG.md 12 | config.user = 'brooklynDev' 13 | end 14 | 15 | -------------------------------------------------------------------------------- /rakelib/rspec.rake: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | 3 | RSpec::Core::RakeTask.new 4 | -------------------------------------------------------------------------------- /spec/airborne/base_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'base spec' do 4 | it 'when request is made response should be set' do 5 | mock_get('simple_get') 6 | get '/simple_get' 7 | expect(response).to_not be(nil) 8 | end 9 | 10 | it 'when request is made headers should be set' do 11 | mock_get('simple_get') 12 | get '/simple_get' 13 | expect(headers).to_not be(nil) 14 | end 15 | 16 | it 'should throw an InvalidJsonError when accessing json_body on invalid json' do 17 | mock_get('invalid_json') 18 | get '/invalid_json' 19 | expect(body).to eq('invalid1234') 20 | expect { json_body }.to raise_error(InvalidJsonError) 21 | end 22 | 23 | it 'when request is made headers should be hash with indifferent access' do 24 | mock_get('simple_get', 'Content-Type' => 'application/json') 25 | get '/simple_get' 26 | expect(headers).to be_kind_of(Hash) 27 | expect(headers[:content_type]).to eq('application/json') 28 | expect(headers['content_type']).to eq('application/json') 29 | end 30 | 31 | it 'when request is made body should be set' do 32 | mock_get('simple_get') 33 | get '/simple_get' 34 | expect(body).to_not be(nil) 35 | end 36 | 37 | it 'when request is made json body should be symbolized hash' do 38 | mock_get('simple_get') 39 | get '/simple_get' 40 | expect(json_body).to be_kind_of(Hash) 41 | expect(json_body.first[0]).to be_kind_of(Symbol) 42 | end 43 | 44 | it 'should handle a 500 error on get' do 45 | mock_get('simple_get', {}, [500, 'Internal Server Error']) 46 | get '/simple_get' 47 | expect(json_body).to_not be(nil) 48 | end 49 | 50 | it 'should handle a 500 error on post' do 51 | mock_post('simple_post', {}, [500, 'Internal Server Error']) 52 | post '/simple_post', {} 53 | expect(json_body).to_not be(nil) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/airborne/client_requester_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'client requester' do 4 | before do 5 | allow(RestClient::Request).to receive(:execute) 6 | RSpec::Mocks.space.proxy_for(self).remove_stub_if_present(:get) 7 | end 8 | 9 | after do 10 | allow(RestClient::Request).to receive(:execute).and_call_original 11 | Airborne.configure { |config| config.headers = {} } 12 | Airborne.configure { |config| config.verify_ssl = true } 13 | end 14 | 15 | it 'should set :content_type to :json by default' do 16 | get '/foo' 17 | 18 | expect(RestClient::Request).to have_received(:execute).with( 19 | method: :get, 20 | url: 'http://www.example.com/foo', 21 | headers: { content_type: :json }, 22 | verify_ssl: true 23 | ) 24 | end 25 | 26 | it 'should override headers with option[:headers]' do 27 | get '/foo', { content_type: 'application/x-www-form-urlencoded' } 28 | 29 | expect(RestClient::Request).to have_received(:execute).with( 30 | method: :get, 31 | url: 'http://www.example.com/foo', 32 | headers: { content_type: 'application/x-www-form-urlencoded' }, 33 | verify_ssl: true 34 | ) 35 | end 36 | 37 | it 'should override headers with airborne config headers' do 38 | Airborne.configure { |config| config.headers = { content_type: 'text/plain' } } 39 | 40 | get '/foo' 41 | 42 | expect(RestClient::Request).to have_received(:execute).with( 43 | method: :get, 44 | url: 'http://www.example.com/foo', 45 | headers: { content_type: 'text/plain' }, 46 | verify_ssl: true 47 | ) 48 | end 49 | 50 | it 'should serialize body to json when :content_type is (default) :json' do 51 | post '/foo', { test: 'serialized' } 52 | 53 | expect(RestClient::Request).to have_received(:execute).with( 54 | method: :post, 55 | url: 'http://www.example.com/foo', 56 | payload: { test: 'serialized' }.to_json, 57 | headers: { content_type: :json }, 58 | verify_ssl: true 59 | ) 60 | end 61 | 62 | it 'should serialize body to json when :content_type is any enhanced JSON content type' do 63 | post '/foo', { test: 'serialized' }, { content_type: 'application/vnd.airborne.2+json' } 64 | 65 | expect(RestClient::Request).to have_received(:execute).with( 66 | method: :post, 67 | url: 'http://www.example.com/foo', 68 | payload: { test: 'serialized' }.to_json, 69 | headers: { content_type: 'application/vnd.airborne.2+json' }, 70 | verify_ssl: true 71 | ) 72 | end 73 | 74 | it 'should not serialize body to json when :content_type does not match JSON' do 75 | post '/foo', { test: 'not serialized' }, { content_type: 'text/plain' } 76 | 77 | expect(RestClient::Request).to have_received(:execute).with( 78 | method: :post, 79 | url: 'http://www.example.com/foo', 80 | payload: { test: 'not serialized' }, 81 | headers: { content_type: 'text/plain' }, 82 | verify_ssl: true 83 | ) 84 | end 85 | 86 | it 'should send payload with delete request' do 87 | payload = { example: 'this is the payload' } 88 | delete '/foo', payload 89 | 90 | expect(RestClient::Request).to have_received(:execute).with( 91 | method: :delete, 92 | url: 'http://www.example.com/foo', 93 | payload: payload.to_json, 94 | headers: { content_type: :json }, 95 | verify_ssl: true 96 | ) 97 | end 98 | 99 | context 'verify_ssl' do 100 | it 'should be true by default' do 101 | get '/foo' 102 | 103 | expect(RestClient::Request).to have_received(:execute).with( 104 | method: :get, 105 | url: 'http://www.example.com/foo', 106 | headers: { content_type: :json }, 107 | verify_ssl: true 108 | ) 109 | end 110 | 111 | it 'should be set by airborne config' do 112 | Airborne.configure { |config| config.verify_ssl = false } 113 | 114 | get '/foo' 115 | 116 | expect(RestClient::Request).to have_received(:execute).with( 117 | method: :get, 118 | url: 'http://www.example.com/foo', 119 | headers: { content_type: :json }, 120 | verify_ssl: false 121 | ) 122 | end 123 | 124 | it 'should be overriden with options[:verify_ssl]' do 125 | get '/foo', nil, false 126 | 127 | expect(RestClient::Request).to have_received(:execute).with( 128 | method: :get, 129 | url: 'http://www.example.com/foo', 130 | headers: { content_type: :json }, 131 | verify_ssl: false 132 | ) 133 | end 134 | 135 | it 'should override airborne config with options[:verify_ssl]' do 136 | Airborne.configure { |config| config.verify_ssl = false } 137 | 138 | get '/foo', nil, true 139 | 140 | expect(RestClient::Request).to have_received(:execute).with( 141 | method: :get, 142 | url: 'http://www.example.com/foo', 143 | headers: { content_type: :json }, 144 | verify_ssl: true 145 | ) 146 | end 147 | 148 | it 'should interpret airborne "config.verify_ssl = nil" as false' do 149 | Airborne.configure { |config| config.verify_ssl = nil } 150 | 151 | get '/foo' 152 | 153 | expect(RestClient::Request).to have_received(:execute).with( 154 | method: :get, 155 | url: 'http://www.example.com/foo', 156 | headers: { content_type: :json }, 157 | verify_ssl: false 158 | ) 159 | end 160 | 161 | context 'rspec metadata', verify_ssl: false do 162 | it 'should override the base airborne config with the rspec metadata' do 163 | get '/foo' 164 | 165 | expect(RestClient::Request).to have_received(:execute).with( 166 | method: :get, 167 | url: 'http://www.example.com/foo', 168 | headers: { content_type: :json }, 169 | verify_ssl: false 170 | ) 171 | end 172 | 173 | it 'should be overriden with options[:verify_ssl]' do 174 | get '/foo', nil, true 175 | 176 | expect(RestClient::Request).to have_received(:execute).with( 177 | method: :get, 178 | url: 'http://www.example.com/foo', 179 | headers: { content_type: :json }, 180 | verify_ssl: true 181 | ) 182 | end 183 | 184 | it 'should be overriden by supplied airborne config' do 185 | Airborne.configure { |config| config.verify_ssl = true } 186 | 187 | get '/foo' 188 | 189 | expect(RestClient::Request).to have_received(:execute).with( 190 | method: :get, 191 | url: 'http://www.example.com/foo', 192 | headers: { content_type: :json }, 193 | verify_ssl: true 194 | ) 195 | end 196 | end 197 | end 198 | end 199 | -------------------------------------------------------------------------------- /spec/airborne/delete_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'delete' do 4 | it 'should allow testing on delete requests' do 5 | mock_delete 'simple_delete' 6 | delete '/simple_delete', {} 7 | expect_status 200 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/airborne/expectations/expect_header_contains_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'expect header contains' do 4 | it 'should ensure partial header match exists' do 5 | mock_get('simple_get', 'Content-Type' => 'application/json') 6 | get '/simple_get' 7 | expect_header_contains(:content_type, 'json') 8 | end 9 | 10 | it 'should ensure header is present' do 11 | mock_get('simple_get', 'Content-Type' => 'application/json') 12 | get '/simple_get' 13 | expect { expect_header_contains(:foo, 'bar') }.to raise_error(ExpectationNotMetError) 14 | end 15 | 16 | it 'should ensure partial header is present' do 17 | mock_get('simple_get', 'Content-Type' => 'application/json') 18 | get '/simple_get' 19 | expect { expect_header_contains(:content_type, 'bar') }.to raise_error(ExpectationNotMetError) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/airborne/expectations/expect_header_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'expect header' do 4 | it 'should find exact match for header content' do 5 | mock_get('simple_get', 'Content-Type' => 'application/json') 6 | get '/simple_get' 7 | expect_header(:content_type, 'application/json') 8 | end 9 | 10 | it 'should find exact match for header content' do 11 | mock_get('simple_get', 'Content-Type' => 'json') 12 | get '/simple_get' 13 | expect { expect_header(:content_type, 'application/json') }.to raise_error(ExpectationNotMetError) 14 | end 15 | 16 | it 'should ensure correct headers are present' do 17 | mock_get('simple_get', 'Content-Type' => 'application/json') 18 | get '/simple_get' 19 | expect { expect_header(:foo, 'bar') }.to raise_error(ExpectationNotMetError) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/airborne/expectations/expect_json_keys_path_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'expect_json_keys with path' do 4 | it 'should ensure json keys with path' do 5 | mock_get('simple_nested_path') 6 | get '/simple_nested_path', {} 7 | expect_json_keys('address', [:street, :city]) 8 | end 9 | 10 | it 'should fail when keys are missing with path' do 11 | mock_get('simple_nested_path') 12 | get '/simple_nested_path', {} 13 | expect { expect_json_keys('address', [:bad]) }.to raise_error(ExpectationNotMetError) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/airborne/expectations/expect_json_keys_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'expect_json_keys' do 4 | it 'should fail when json keys are missing' do 5 | mock_get('simple_json') 6 | get '/simple_json', {} 7 | expect { expect_json_keys([:foo, :bar, :baz, :bax]) }.to raise_error(ExpectationNotMetError) 8 | end 9 | 10 | it 'should ensure correct json keys' do 11 | mock_get('simple_json') 12 | get '/simple_json', {} 13 | expect_json_keys([:foo, :bar, :baz]) 14 | end 15 | 16 | it 'should ensure correct partial json keys' do 17 | mock_get('simple_json') 18 | get '/simple_json', {} 19 | expect_json_keys([:foo, :bar]) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/airborne/expectations/expect_json_lambda_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'expect_json lambda' do 4 | it 'should invoke proc passed in' do 5 | mock_get('simple_get') 6 | get '/simple_get' 7 | expect_json(name: ->(name) { expect(name.length).to eq(4) }) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/airborne/expectations/expect_json_options_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'expect_json options' do 4 | describe 'match_expected', match_expected: true, match_actual: false do 5 | it 'should require all expected properties' do 6 | mock_get 'simple_get' 7 | get '/simple_get' 8 | expect{ expect_json(name: 'Alex', other: 'other') }.to raise_error(ExpectationNotMetError) 9 | end 10 | 11 | it 'should not require the actual properties' do 12 | mock_get 'simple_get' 13 | get '/simple_get' 14 | expect_json(name: 'Alex') 15 | end 16 | end 17 | 18 | describe 'match_actual', match_expected: false, match_actual: true do 19 | it 'should require all actual properties' do 20 | mock_get 'simple_get' 21 | get '/simple_get' 22 | expect{ expect_json(name: 'Alex') }.to raise_error(ExpectationError) 23 | end 24 | 25 | it 'should not require the expected properties' do 26 | mock_get 'simple_get' 27 | get '/simple_get' 28 | expect_json(name: 'Alex', age: 32, address: nil, other: 'other') 29 | end 30 | end 31 | 32 | describe 'match_both', match_expected: true, match_actual: true do 33 | it 'should require all actual properties' do 34 | mock_get 'simple_get' 35 | get '/simple_get' 36 | expect{ expect_json(name: 'Alex') }.to raise_error(ExpectationError) 37 | end 38 | 39 | it 'should require all expected properties' do 40 | mock_get 'simple_get' 41 | get '/simple_get' 42 | expect{ expect_json(name: 'Alex', other: 'other') }.to raise_error(ExpectationNotMetError) 43 | end 44 | 45 | it 'should require all expected properties' do 46 | mock_get 'simple_get' 47 | get '/simple_get' 48 | expect{ expect_json(name: 'Alex', nested: {}) }.to raise_error(ExpectationNotMetError) 49 | end 50 | end 51 | 52 | describe 'match_none', match_expected: false, match_actual: false do 53 | it 'should not require the actual properties' do 54 | mock_get 'simple_get' 55 | get '/simple_get' 56 | expect_json(name: 'Alex') 57 | end 58 | 59 | it 'should not require the expected properties' do 60 | mock_get 'simple_get' 61 | get '/simple_get' 62 | expect_json(name: 'Alex', age: 32, address: nil, other: 'other', nested: {}) 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/airborne/expectations/expect_json_path_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'expect_json with path' do 4 | it 'should allow simple path and verify only that path' do 5 | mock_get('simple_path_get') 6 | get '/simple_path_get' 7 | expect_json('address', street: 'Area 51', city: 'Roswell', state: 'NM') 8 | end 9 | 10 | it 'should allow nested paths' do 11 | mock_get('simple_nested_path') 12 | get '/simple_nested_path' 13 | expect_json('address.coordinates', lattitude: 33.3872, longitutde: 104.5281) 14 | end 15 | 16 | it 'should index into array and test against specific element' do 17 | mock_get('array_with_index') 18 | get '/array_with_index' 19 | expect_json('cars.0', make: 'Tesla', model: 'Model S') 20 | end 21 | 22 | it 'should test against all elements in the array' do 23 | mock_get('array_with_index') 24 | get '/array_with_index' 25 | expect_json('cars.?', make: 'Tesla', model: 'Model S') 26 | expect_json('cars.?', make: 'Lamborghini', model: 'Aventador') 27 | end 28 | 29 | it 'should test against properties in the array' do 30 | mock_get('array_with_index') 31 | get '/array_with_index' 32 | expect_json('cars.?.make', 'Tesla') 33 | end 34 | 35 | it 'should ensure at least one match' do 36 | mock_get('array_with_index') 37 | get '/array_with_index' 38 | expect { expect_json('cars.?.make', 'Teslas') }.to raise_error(ExpectationNotMetError) 39 | end 40 | 41 | it 'should check for at least one match' do 42 | mock_get('array_with_nested') 43 | get '/array_with_nested' 44 | expect_json('cars.?.owners.?', name: 'Bart Simpson') 45 | end 46 | 47 | it 'should ensure at least one match' do 48 | mock_get('array_with_nested') 49 | get '/array_with_nested' 50 | expect { expect_json('cars.?.owners.?', name: 'Bart Simpsons') }.to raise_error(ExpectationNotMetError) 51 | end 52 | 53 | it 'should check for one match that matches all ' do 54 | mock_get('array_with_nested') 55 | get '/array_with_nested' 56 | expect_json('cars.?.owners.*', name: 'Bart Simpson') 57 | end 58 | 59 | it 'should check for one match that matches all with lambda' do 60 | mock_get('array_with_nested') 61 | get '/array_with_nested' 62 | expect_json('cars.?.owners.*', name: ->(name) { expect(name).to eq('Bart Simpson') }) 63 | end 64 | 65 | it 'should ensure one match that matches all with lambda' do 66 | mock_get('array_with_nested') 67 | get '/array_with_nested' 68 | expect { expect_json('cars.?.owners.*', name: ->(name) { expect(name).to eq('Bart Simpsons') }) }.to raise_error(ExpectationNotMetError) 69 | end 70 | 71 | it 'should ensure one match that matches all' do 72 | mock_get('array_with_nested') 73 | get '/array_with_nested' 74 | expect { expect_json('cars.?.owners.*', name: 'Bart Simpsons') }.to raise_error(ExpectationNotMetError) 75 | end 76 | 77 | it 'should allow indexing' do 78 | mock_get('array_with_nested') 79 | get '/array_with_nested' 80 | expect_json('cars.0.owners.0', name: 'Bart Simpson') 81 | end 82 | 83 | it 'should allow strings (String) to be tested against a path' do 84 | mock_get('simple_nested_path') 85 | get '/simple_nested_path' 86 | expect_json('address.city', 'Roswell') 87 | end 88 | 89 | it 'should allow floats (Float) to be tested against a path' do 90 | mock_get('simple_nested_path') 91 | get '/simple_nested_path' 92 | expect_json('address.coordinates.lattitude', 33.3872) 93 | end 94 | 95 | it 'should allow integers (Fixnum, Bignum) to be tested against a path' do 96 | mock_get('simple_get') 97 | get '/simple_get' 98 | expect_json('age', 32) 99 | end 100 | 101 | it 'should raise ExpectationError when expectation expects an object instead of value' do 102 | mock_get('array_with_index') 103 | get '/array_with_index' 104 | expect do 105 | expect_json('cars.0.make', make: 'Tesla') 106 | end.to raise_error(ExpectationError, "Expected String Tesla\nto be an object with property make") 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /spec/airborne/expectations/expect_json_regex_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'expect_json regex' do 4 | it 'should test against regex' do 5 | mock_get('simple_get') 6 | get '/simple_get' 7 | expect_json(name: regex('^A')) 8 | end 9 | 10 | it 'should raise an error if regex does not match' do 11 | mock_get('simple_get') 12 | get '/simple_get' 13 | expect { expect_json(name: regex('^B')) }.to raise_error(ExpectationNotMetError) 14 | end 15 | 16 | it 'should allow regex(Regexp) to be tested against a path' do 17 | mock_get('simple_nested_path') 18 | get '/simple_nested_path' 19 | expect_json('address.city', regex('^R')) 20 | end 21 | 22 | it 'should allow testing regex against numbers directly' do 23 | mock_get('simple_nested_path') 24 | get '/simple_nested_path' 25 | expect_json('address.coordinates.lattitude', regex('^3')) 26 | end 27 | 28 | it 'should allow testing regex against numbers in the hash' do 29 | mock_get('simple_nested_path') 30 | get '/simple_nested_path' 31 | expect_json('address.coordinates', lattitude: regex('^3')) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/airborne/expectations/expect_json_sizes_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'expect_json_sizes' do 4 | it 'should detect sizes' do 5 | mock_get('array_of_values') 6 | get '/array_of_values' 7 | expect_json_sizes(grades: 4, bad: 3, emptyArray: 0) 8 | end 9 | 10 | it 'should allow full object graph' do 11 | mock_get('array_with_nested') 12 | get '/array_with_nested' 13 | expect_json_sizes(cars: { 0 => { owners: 1 }, 1 => { owners: 1 } }) 14 | end 15 | 16 | it 'should allow properties to be tested against a path' do 17 | mock_get('array_with_nested') 18 | get '/array_with_nested' 19 | expect_json_sizes('cars.0.owners', 1) 20 | end 21 | 22 | it 'should test against all elements in the array when path contains * AND expectation is an Integer' do 23 | mock_get('array_with_nested') 24 | get '/array_with_nested' 25 | expect_json_sizes('cars.*.owners', 1) 26 | end 27 | 28 | it 'should test against all elements in the array when path contains * AND expectation is a Hash' do 29 | mock_get('array_with_nested') 30 | get '/array_with_nested' 31 | expect_json_sizes('cars.*', owners: 1) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/airborne/expectations/expect_json_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'expect_json' do 4 | it 'should ensure correct json values' do 5 | mock_get('simple_get') 6 | get '/simple_get' 7 | expect_json(name: 'Alex', age: 32) 8 | end 9 | 10 | it 'should allow array response' do 11 | mock_get('array_response') 12 | get '/array_response' 13 | expect_json([{ name: 'Seth' }]) 14 | end 15 | 16 | it 'should fail when incorrect json is tested' do 17 | mock_get('simple_get') 18 | get '/simple_get' 19 | expect { expect_json(bad: 'data') }.to raise_error(ExpectationNotMetError) 20 | end 21 | 22 | it 'should allow full object graph' do 23 | mock_get('simple_path_get') 24 | get '/simple_path_get' 25 | expect_json(name: 'Alex', address: { street: 'Area 51', city: 'Roswell', state: 'NM' }) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/airborne/expectations/expect_json_types_date_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'date' 3 | 4 | describe 'expect_json_types with date' do 5 | it 'should verify correct date types' do 6 | mock_get('date_response') 7 | get '/date_response' 8 | expect_json_types(createdAt: :date) 9 | end 10 | 11 | it 'should verify correct date types with path' do 12 | mock_get('date_response') 13 | get '/date_response' 14 | expect_json_types('createdAt', :date) 15 | end 16 | end 17 | 18 | describe 'expect_json with date' do 19 | it 'should verify correct date value' do 20 | mock_get('date_response') 21 | get '/date_response' 22 | prev_day = DateTime.new(2014, 10, 19) 23 | next_day = DateTime.new(2014, 10, 21) 24 | expect_json(createdAt: date { |value| expect(value).to be_between(prev_day, next_day) }) 25 | end 26 | end 27 | 28 | describe 'expect_json_types with date_or_null' do 29 | it 'should verify date_or_null when date is null' do 30 | mock_get('date_is_null_response') 31 | get '/date_is_null_response' 32 | expect_json_types(dateDeleted: :date_or_null) 33 | end 34 | 35 | it 'should verify date_or_null when date is null with path' do 36 | mock_get('date_is_null_response') 37 | get '/date_is_null_response' 38 | expect_json_types('dateDeleted', :date_or_null) 39 | end 40 | 41 | it 'should verify date_or_null with date' do 42 | mock_get('date_response') 43 | get '/date_response' 44 | expect_json_types(createdAt: :date_or_null) 45 | end 46 | 47 | it 'should verify date_or_null with date with path' do 48 | mock_get('date_response') 49 | get '/date_response' 50 | expect_json_types('createdAt', :date_or_null) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/airborne/expectations/expect_json_types_lambda_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'expect_json_types lambda' do 4 | it 'should invoke proc passed in' do 5 | mock_get('simple_get') 6 | get '/simple_get' 7 | expect_json_types(name: ->(name) { expect(name.length).to eq(4) }) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/airborne/expectations/expect_json_types_optional_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'expect_json_types optional' do 4 | it 'should test optional nested hash when exists' do 5 | mock_get('simple_nested_path') 6 | get '/simple_nested_path' 7 | expect_json_types('address.coordinates', optional(lattitude: :float, longitutde: :float)) 8 | end 9 | 10 | it 'should allow optional nested hash' do 11 | mock_get('simple_path_get') 12 | get '/simple_path_get' 13 | expect_json_types('address.coordinates', optional(lattitude: :float, longitutde: :float)) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/airborne/expectations/expect_json_types_options_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'expect_json_types options' do 4 | describe 'match_expected', match_expected: true, match_actual: false do 5 | it 'should require all expected properties' do 6 | mock_get 'simple_get' 7 | get '/simple_get' 8 | expect{ expect_json_types(name: :string, other: :string) }.to raise_error(ExpectationNotMetError) 9 | end 10 | 11 | it 'should not require the actual properties' do 12 | mock_get 'simple_get' 13 | get '/simple_get' 14 | expect_json_types(name: :string) 15 | end 16 | end 17 | 18 | describe 'match_actual', match_expected: false, match_actual: true do 19 | it 'should require all actual properties' do 20 | mock_get 'simple_get' 21 | get '/simple_get' 22 | expect{ expect_json_types(name: :string) }.to raise_error(ExpectationError) 23 | end 24 | 25 | it 'should not require the expected properties' do 26 | mock_get 'simple_get' 27 | get '/simple_get' 28 | expect_json_types(name: :string, age: :int, address: :null, other: :string) 29 | end 30 | end 31 | 32 | describe 'match_both', match_expected: true, match_actual: true do 33 | it 'should require all actual properties' do 34 | mock_get 'simple_get' 35 | get '/simple_get' 36 | expect{ expect_json_types(name: :string) }.to raise_error(ExpectationError) 37 | end 38 | 39 | it 'should require all expected properties' do 40 | mock_get 'simple_get' 41 | get '/simple_get' 42 | expect{ expect_json_types(name: :string, other: :string) }.to raise_error(ExpectationNotMetError) 43 | end 44 | end 45 | 46 | describe 'match_none', match_expected: false, match_actual: false do 47 | it 'should not require the actual properties' do 48 | mock_get 'simple_get' 49 | get '/simple_get' 50 | expect_json_types(name: :string) 51 | end 52 | 53 | it 'should not require the expected properties' do 54 | mock_get 'simple_get' 55 | get '/simple_get' 56 | expect_json_types(name: :string, age: :int, address: :null, other: :string) 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/airborne/expectations/expect_json_types_path_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'expect_json_types wih path' do 4 | it 'should allow simple path and verify only that path' do 5 | mock_get('simple_path_get') 6 | get '/simple_path_get' 7 | expect_json_types('address', street: :string, city: :string, state: :string) 8 | end 9 | 10 | it 'should allow nested paths' do 11 | mock_get('simple_nested_path') 12 | get '/simple_nested_path' 13 | expect_json_types('address.coordinates', lattitude: :float, longitutde: :float) 14 | end 15 | 16 | it 'should index into array and test against specific element' do 17 | mock_get('array_with_index') 18 | get '/array_with_index' 19 | expect_json_types('cars.0', make: :string, model: :string) 20 | end 21 | 22 | it 'should allow properties to be tested against a path' do 23 | mock_get('array_with_index') 24 | get '/array_with_index' 25 | expect_json_types('cars.0.make', :string) 26 | end 27 | 28 | it 'should test against all elements in the array' do 29 | mock_get('array_with_index') 30 | get '/array_with_index' 31 | expect_json_types('cars.*', make: :string, model: :string) 32 | end 33 | 34 | it 'should ensure all elements of array are valid' do 35 | mock_get('array_with_index') 36 | get '/array_with_index' 37 | expect { expect_json_types('cars.*', make: :string, model: :int) }.to raise_error(ExpectationNotMetError) 38 | end 39 | 40 | it 'should deep symbolize array responses' do 41 | mock_get('array_response') 42 | get '/array_response' 43 | expect_json_types('*', name: :string) 44 | end 45 | 46 | it 'should check all nested arrays for specified elements' do 47 | mock_get('array_with_nested') 48 | get '/array_with_nested' 49 | expect_json_types('cars.*.owners.*', name: :string) 50 | end 51 | 52 | it 'should ensure all nested arrays contain correct data' do 53 | mock_get('array_with_nested_bad_data') 54 | get '/array_with_nested_bad_data' 55 | expect { expect_json_types('cars.*.owners.*', name: :string) }.to raise_error(ExpectationNotMetError) 56 | end 57 | 58 | it 'should raise ExpectationError when expectation expects an object instead of type' do 59 | mock_get('array_with_index') 60 | get '/array_with_index' 61 | expect do 62 | expect_json_types('cars.0.make', make: :string) 63 | end.to raise_error(ExpectationError, "Expected String Tesla\nto be an object with property make") 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/airborne/expectations/expect_json_types_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'expect_json_types' do 4 | it 'should detect current type' do 5 | mock_get('simple_get') 6 | get '/simple_get' 7 | expect_json_types(name: :string, age: :int) 8 | end 9 | 10 | it 'should fail when incorrect json types tested' do 11 | mock_get('simple_get') 12 | get '/simple_get' 13 | expect { expect_json_types(bad: :bool) }.to raise_error(ExpectationNotMetError) 14 | end 15 | 16 | it 'should not fail when optional property is not present' do 17 | mock_get('simple_get') 18 | get '/simple_get' 19 | expect_json_types(name: :string, age: :int, optional: :bool_or_null) 20 | end 21 | 22 | it 'should allow full object graph' do 23 | mock_get('simple_path_get') 24 | get '/simple_path_get' 25 | expect_json_types({name: :string, address: { street: :string, city: :string, state: :string }}) 26 | end 27 | 28 | it 'should check all types in a simple array' do 29 | mock_get('array_of_values') 30 | get '/array_of_values' 31 | expect_json_types(grades: :array_of_ints) 32 | end 33 | 34 | it 'should ensure all valid types in a simple array' do 35 | mock_get('array_of_values') 36 | get '/array_of_values' 37 | expect { expect_json_types(bad: :array_of_ints) }.to raise_error(ExpectationNotMetError) 38 | end 39 | 40 | it "should allow array of types to be null" do 41 | mock_get('array_of_types') 42 | get '/array_of_types' 43 | expect_json_types(nil_array: :array_or_null) 44 | expect_json_types(nil_array: :array_of_integers_or_null) 45 | expect_json_types(nil_array: :array_of_ints_or_null) 46 | expect_json_types(nil_array: :array_of_floats_or_null) 47 | expect_json_types(nil_array: :array_of_strings_or_null) 48 | expect_json_types(nil_array: :array_of_booleans_or_null) 49 | expect_json_types(nil_array: :array_of_bools_or_null) 50 | expect_json_types(nil_array: :array_of_objects_or_null) 51 | expect_json_types(nil_array: :array_of_arrays_or_null) 52 | end 53 | 54 | it "should check array types when not null" do 55 | mock_get('array_of_types') 56 | get '/array_of_types' 57 | expect_json_types(array_of_ints: :array_or_null) 58 | expect_json_types(array_of_ints: :array_of_integers_or_null) 59 | expect_json_types(array_of_ints: :array_of_ints_or_null) 60 | expect_json_types(array_of_floats: :array_of_floats_or_null) 61 | expect_json_types(array_of_strings: :array_of_strings_or_null) 62 | expect_json_types(array_of_bools: :array_of_booleans_or_null) 63 | expect_json_types(array_of_bools: :array_of_bools_or_null) 64 | expect_json_types(array_of_objects: :array_of_objects_or_null) 65 | expect_json_types(array_of_arrays: :array_of_arrays_or_null) 66 | end 67 | 68 | it 'should allow empty array' do 69 | mock_get('array_of_values') 70 | get '/array_of_values' 71 | expect_json_types(emptyArray: :array_of_ints) 72 | end 73 | 74 | it 'should be able to test for a nil type' do 75 | mock_get('simple_get') 76 | get '/simple_get' 77 | expect_json_types(name: :string, age: :int, address: :null) 78 | end 79 | 80 | it 'Should throw bad type error' do 81 | mock_get('simple_get') 82 | get '/simple_get' 83 | expect { expect_json_types(name: :foo) }.to raise_error(ExpectationError, "Expected type foo\nis an invalid type") 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /spec/airborne/expectations/expect_status_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'expect_status' do 4 | it 'should verify correct status code' do 5 | mock_get('simple_get') 6 | get '/simple_get' 7 | expect_status 200 8 | end 9 | 10 | it 'should fail when incorrect status code is returned' do 11 | mock_get('simple_get') 12 | get '/simple_get' 13 | expect { expect_status 123 }.to raise_error(ExpectationNotMetError) 14 | end 15 | 16 | it 'should translate symbol codes to whatever is appropriate for the request' do 17 | mock_get('simple_get') 18 | get '/simple_get' 19 | expect_status :ok 20 | expect_status 200 21 | expect_status '200' 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/airborne/head_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'head' do 4 | it 'should allow testing on head requests' do 5 | mock_head('simple_head', 'foo' => 'foo') 6 | head '/simple_head', {} 7 | expect_status 200 8 | expect_header('foo', 'foo') 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/airborne/options_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'head' do 4 | it 'should allow testing on options requests' do 5 | mock_options('simple_options', 'foo' => 'foo') 6 | options '/simple_options', {} 7 | expect_status 200 8 | expect_header('foo', 'foo') 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/airborne/patch_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'patch' do 4 | it 'should allow testing on patch requests' do 5 | mock_patch('simple_patch') 6 | patch '/simple_patch', {} 7 | expect_json_types(status: :string, someNumber: :int) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/airborne/path_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'expect path' do 4 | describe 'errors' do 5 | before :each do 6 | mock_get('array_with_index') 7 | get '/array_with_index' 8 | end 9 | 10 | it 'should raise PathError when incorrect path containing .. is used' do 11 | expect do 12 | expect_json('cars..make', 'Tesla') 13 | end.to raise_error(PathError, "Invalid Path, contains '..'") 14 | end 15 | 16 | it 'should raise PathError when trying to call property on an array' do 17 | expect do 18 | expect_json('cars.make', 'Tesla') 19 | end.to raise_error(PathError, "Expected Array\nto be an object with property make") 20 | end 21 | end 22 | 23 | it 'should work with numberic properties' do 24 | mock_get('numeric_property') 25 | get '/numeric_property' 26 | expect_json('cars.0.make', 'Tesla') 27 | end 28 | 29 | it 'should work with numberic properties' do 30 | mock_get('numeric_property') 31 | get '/numeric_property' 32 | expect_json_keys('cars.0', [:make, :model]) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/airborne/post_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'webmock/rspec' 3 | 4 | describe 'post' do 5 | it 'should allow testing on post requests' do 6 | mock_post('simple_post') 7 | post '/simple_post', {} 8 | expect_json_types(status: :string, someNumber: :int) 9 | end 10 | 11 | it 'should allow testing on post requests' do 12 | url = 'http://www.example.com/simple_post' 13 | stub_request(:post, url) 14 | post '/simple_post', 'hello', content_type: 'text/plain' 15 | expect(WebMock).to have_requested(:post, url).with(body: 'hello', headers: { 'Content-Type' => 'text/plain' }) 16 | end 17 | 18 | it 'should allow testing on post requests with IO body' do 19 | url = 'http://www.example.com/simple_post' 20 | stub_request(:post, url) 21 | post '/simple_post', StringIO.new('hello'), content_type: 'application/octet-stream' 22 | expect(WebMock).to have_requested(:post, url).with(body: 'hello', headers: { 'Content-Type' => 'application/octet-stream' }) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/airborne/put_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'put' do 4 | it 'should allow testing on put requests w/no body' do 5 | mock_put('simple_put') 6 | put '/simple_put' 7 | expect_json_types(status: :string, someNumber: :int) 8 | end 9 | 10 | it 'should allow testing on put requests w/empty body' do 11 | mock_put('simple_put') 12 | put '/simple_put', {} 13 | expect_json_types(status: :string, someNumber: :int) 14 | end 15 | 16 | it 'should allow testing on put requests w/body' do 17 | mock_put('simple_put') 18 | put '/simple_put', {:key=>:value} 19 | expect_json_types(status: :string, someNumber: :int) 20 | end 21 | 22 | it 'should allow testing on put requests w/body, empty string' do 23 | mock_put('simple_put') 24 | put '/simple_put', '' 25 | expect_json_types(status: :string, someNumber: :int) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/airborne/rack/rack_sinatra_spec.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'sinatra' 3 | 4 | class SampleApp < Sinatra::Application 5 | before do 6 | content_type 'application/json' 7 | end 8 | 9 | get '/' do 10 | { foo: 'bar' }.to_json 11 | end 12 | end 13 | 14 | Airborne.configure do |config| 15 | config.rack_app = SampleApp 16 | end 17 | 18 | describe 'rack app' do 19 | it 'should allow requests against a sinatra app' do 20 | get '/' 21 | expect_json_types(foo: :string) 22 | end 23 | 24 | it 'should ensure correct values from sinatra app' do 25 | get '/' 26 | expect { expect_json_types(foo: :int) }.to raise_error(ExpectationNotMetError) 27 | end 28 | 29 | it 'Should set json_body even when not using the airborne http requests' do 30 | Response = Struct.new(:body, :headers) 31 | @response = Response.new({ foo: 'bar' }.to_json) 32 | expect(json_body).to eq(foo: 'bar') 33 | end 34 | 35 | it 'Should work with consecutive requests' do 36 | Response = Struct.new(:body, :headers) 37 | @response = Response.new({ foo: 'bar' }.to_json) 38 | expect(json_body).to eq(foo: 'bar') 39 | 40 | @response = Response.new({ foo: 'boo' }.to_json) 41 | expect(json_body).to eq(foo: 'boo') 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'coveralls' 2 | Coveralls.wear! 3 | require 'airborne' 4 | require 'stub_helper' 5 | 6 | Airborne.configure do |config| 7 | config.base_url = 'http://www.example.com' 8 | config.include StubHelper 9 | end 10 | 11 | ExpectationNotMetError = RSpec::Expectations::ExpectationNotMetError 12 | ExpectationError = Airborne::ExpectationError 13 | InvalidJsonError = Airborne::InvalidJsonError 14 | PathError = Airborne::PathError 15 | -------------------------------------------------------------------------------- /spec/stub_helper.rb: -------------------------------------------------------------------------------- 1 | require 'webmock/rspec' 2 | 3 | module StubHelper 4 | def initialize(*args) 5 | @base_url = 'http://www.example.com/' 6 | end 7 | 8 | def mock_get(url, response_headers = {}, status = 200) 9 | stub_request(:get, @base_url + url).to_return(headers: response_headers, body: get_json_response_file(url), status: status) 10 | end 11 | 12 | def mock_post(url, options = {}, status = 200) 13 | stub_request(:post, @base_url + url).with(body: options[:request_body] || {}) 14 | .to_return(headers: options[:response_headers] || {}, body: get_json_response_file(url), status: status) 15 | end 16 | 17 | def mock_put(url, options = {}, status = 200) 18 | stub_request(:put, @base_url + url).with(body: options[:request_body] || {}) 19 | .to_return(headers: options[:response_headers] || {}, body: get_json_response_file(url), status: status) 20 | end 21 | 22 | def mock_patch(url, options = {}, status = 200) 23 | stub_request(:patch, @base_url + url).with(body: options[:request_body] || {}) 24 | .to_return(headers: options[:response_headers] || {}, body: get_json_response_file(url), status: status) 25 | end 26 | 27 | def mock_delete(url, options = {}, status = 200) 28 | stub_request(:delete, @base_url + url).with(body: options[:request_body] || {}) 29 | .to_return(headers: options[:response_headers] || {}, body: get_json_response_file(url), status: status) 30 | end 31 | 32 | def mock_head(url, response_headers = {}, status = 200) 33 | stub_request(:head, @base_url + url).to_return(headers: response_headers, body: nil, status: status) 34 | end 35 | 36 | def mock_options(url, response_headers = {}, status = 200) 37 | stub_request(:options, @base_url + url).to_return(headers: response_headers, body: nil, status: status) 38 | end 39 | 40 | private 41 | 42 | def get_json_response_file(name) 43 | IO.read("spec/test_responses/#{name}.json") 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/test_responses/array_of_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "array_of_ints": [1,2,3], 3 | "array_of_floats": [1.2, 2.2, 3.3], 4 | "array_of_strings": ["one", "two", "three"], 5 | "array_of_bools": [true, false, true], 6 | "array_of_objects": [{}, {}, {}], 7 | "array_of_arrays": [[], [], []], 8 | "nil_array": null 9 | } 10 | -------------------------------------------------------------------------------- /spec/test_responses/array_of_values.json: -------------------------------------------------------------------------------- 1 | { 2 | "grades":[89,65,100,92], 3 | "bad": [89,67,"foo"], 4 | "emptyArray": [] 5 | } -------------------------------------------------------------------------------- /spec/test_responses/array_response.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name":"Seth" 4 | } 5 | ] -------------------------------------------------------------------------------- /spec/test_responses/array_with_index.json: -------------------------------------------------------------------------------- 1 | { 2 | "cars":[ 3 | {"make": "Tesla", "model": "Model S"}, 4 | {"make": "Lamborghini", "model": "Aventador"} 5 | ] 6 | } -------------------------------------------------------------------------------- /spec/test_responses/array_with_nested.json: -------------------------------------------------------------------------------- 1 | { 2 | "cars": [{ 3 | "make": "Tesla", 4 | "model": "Model S", 5 | "owners": [ 6 | {"name": "Bart Simpson"} 7 | ] 8 | }, { 9 | "make": "Lamborghini", 10 | "model": "Aventador", 11 | "owners": [ 12 | {"name": "Peter Griffin"} 13 | 14 | ] 15 | }] 16 | } -------------------------------------------------------------------------------- /spec/test_responses/array_with_nested_bad_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "cars": [{ 3 | "make": "Tesla", 4 | "model": "Model S", 5 | "owners": [{ 6 | "name": "Bart Simpson" 7 | }] 8 | }, { 9 | "make": "Lamborghini", 10 | "model": "Aventador", 11 | "owners": [{ 12 | "name": 123 13 | }] 14 | }] 15 | } -------------------------------------------------------------------------------- /spec/test_responses/array_with_partial_nested.json: -------------------------------------------------------------------------------- 1 | { 2 | "cars": [{ 3 | "make": "Tesla", 4 | "model": "Model S", 5 | "owners": [ 6 | {"name": "Bart Simpson"} 7 | ] 8 | }, { 9 | "make": "Lamborghini", 10 | "model": "Aventador" 11 | }] 12 | } -------------------------------------------------------------------------------- /spec/test_responses/date_is_null_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "dateDeleted": null 3 | } -------------------------------------------------------------------------------- /spec/test_responses/date_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "createdAt": "Mon Oct 20 2014 16:10:42 GMT-0400 (EDT)" 3 | } -------------------------------------------------------------------------------- /spec/test_responses/hash_property.json: -------------------------------------------------------------------------------- 1 | { 2 | "person": { "name": "Alex" } 3 | } -------------------------------------------------------------------------------- /spec/test_responses/invalid_get.json: -------------------------------------------------------------------------------- 1 | THIS IS INTENTIONALLY NOT JSON -------------------------------------------------------------------------------- /spec/test_responses/invalid_json.json: -------------------------------------------------------------------------------- 1 | invalid1234 -------------------------------------------------------------------------------- /spec/test_responses/numeric_property.json: -------------------------------------------------------------------------------- 1 | { 2 | "cars": { 3 | "0": {"make": "Tesla", "model": "Model S"}, 4 | "1": {"make": "Lamborghini", "model": "Aventador"} 5 | } 6 | } -------------------------------------------------------------------------------- /spec/test_responses/simple_delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "ok", 3 | "someNumber": 100 4 | } -------------------------------------------------------------------------------- /spec/test_responses/simple_get.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Alex", 3 | "age": 32, 4 | "address": null 5 | } -------------------------------------------------------------------------------- /spec/test_responses/simple_json.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": "foo", 3 | "bar": "bar", 4 | "baz": "baz" 5 | } -------------------------------------------------------------------------------- /spec/test_responses/simple_nested_path.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Alex", 3 | "address": { 4 | "street": "Area 51", 5 | "city": "Roswell", 6 | "state": "NM", 7 | "coordinates":{ 8 | "lattitude": 33.3872, 9 | "longitutde": 104.5281 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /spec/test_responses/simple_patch.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "ok", 3 | "someNumber": 100 4 | } -------------------------------------------------------------------------------- /spec/test_responses/simple_path_get.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Alex", 3 | "address": { 4 | "street": "Area 51", 5 | "city": "Roswell", 6 | "state": "NM" 7 | } 8 | } -------------------------------------------------------------------------------- /spec/test_responses/simple_post.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "ok", 3 | "someNumber": 100 4 | } -------------------------------------------------------------------------------- /spec/test_responses/simple_put.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "ok", 3 | "someNumber": 100 4 | } --------------------------------------------------------------------------------