api_sim 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +63 -0
- data/Rakefile +6 -0
- data/api_sim.gemspec +29 -0
- data/bin/console +31 -0
- data/bin/setup +8 -0
- data/config.example.ru +18 -0
- data/lib/api_sim/app_builder.rb +60 -0
- data/lib/api_sim/built_app.rb +145 -0
- data/lib/api_sim/matchers/base_matcher.rb +53 -0
- data/lib/api_sim/matchers/dynamic_request_matcher.rb +36 -0
- data/lib/api_sim/matchers/request_body_matcher.rb +38 -0
- data/lib/api_sim/matchers/static_request_matcher.rb +56 -0
- data/lib/api_sim/matchers.rb +35 -0
- data/lib/api_sim/public/app.css +7 -0
- data/lib/api_sim/public/app.js +42 -0
- data/lib/api_sim/public/favicon.ico +0 -0
- data/lib/api_sim/recorded_request.rb +20 -0
- data/lib/api_sim/version.rb +3 -0
- data/lib/api_sim/view_helpers.rb +41 -0
- data/lib/api_sim/views/index.html.erb +36 -0
- data/lib/api_sim/views/layout.html.erb +31 -0
- data/lib/api_sim/views/requests/index.html.erb +28 -0
- data/lib/api_sim/views/responses/form.html.erb +50 -0
- data/lib/api_sim.rb +10 -0
- metadata +171 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: 9a955753fef75d629e045f738959d4c4a523e466
         | 
| 4 | 
            +
              data.tar.gz: fa3627d00573c6e0299fcc985738977bc40889bc
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: 71d6f5e74b37fc3f74e9ddfb45a7cea2804521d1cc52f579746478c3dc0d7c184b49c6625aed3e2880c7fe2b9b97b8d5c2bde529de0b898084469cd81e675961
         | 
| 7 | 
            +
              data.tar.gz: 802e0105bae925cae7a1ca2a4af8ce272238ab3239cd47e3b991dc66482f8bbc85f14f8e474d2a9c8de2aabe3a57a904014b13b172612654f8df5e5d7aa14177
         | 
    
        data/.gitignore
    ADDED
    
    
    
        data/.rspec
    ADDED
    
    
    
        data/.travis.yml
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE.txt
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            Copyright (c) 2016 TJ Taylor
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            MIT License
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining
         | 
| 6 | 
            +
            a copy of this software and associated documentation files (the
         | 
| 7 | 
            +
            "Software"), to deal in the Software without restriction, including
         | 
| 8 | 
            +
            without limitation the rights to use, copy, modify, merge, publish,
         | 
| 9 | 
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         | 
| 10 | 
            +
            permit persons to whom the Software is furnished to do so, subject to
         | 
| 11 | 
            +
            the following conditions:
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            The above copyright notice and this permission notice shall be
         | 
| 14 | 
            +
            included in all copies or substantial portions of the Software.
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         | 
| 17 | 
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         | 
| 18 | 
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         | 
| 19 | 
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         | 
| 20 | 
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         | 
| 21 | 
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         | 
| 22 | 
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,63 @@ | |
| 1 | 
            +
            # ApiSim
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            An HTTP API DSL on top of Sinatra.
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ## Installation
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            Add this line to your application's Gemfile:
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            ```ruby
         | 
| 12 | 
            +
            gem 'api_sim'
         | 
| 13 | 
            +
            ```
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            And then execute:
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                $ bundle
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            Or install it yourself as:
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                $ gem install api_sim
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            ## Example usage
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            ```ruby
         | 
| 26 | 
            +
            require 'api_sim'
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            ENDPOINT_JSON_SCHEMA = {"type": "object", "properties": {"a": {"type": "integer"}}}.to_json
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            app = ApiSim.build_app do
         | 
| 31 | 
            +
              configure_endpoint 'GET', '/endpoint', 'Hi!', 200, {'X-CUSTOM-HEADER' => 'easy as abc'}, ENDPOINT_JSON_SCHEMA
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              configure_dynamic_endpoint 'GET', '/dynamic', ->(req) {
         | 
| 34 | 
            +
                [201, {'X-CUSTOM-HEADER' => '123'}, 'Howdy!']
         | 
| 35 | 
            +
              }
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              configure_matcher_endpoint 'POST', '/soap', {
         | 
| 38 | 
            +
                /Operation1/ => [200, {'Content-Type' => 'text/xml+soap'}, '<xml>Response1</xml>'],
         | 
| 39 | 
            +
                /Operation2/ => [500, {'Content-Type' => 'text/xml+soap'}, '<xml>Response2</xml>'],
         | 
| 40 | 
            +
              }
         | 
| 41 | 
            +
            end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            run app
         | 
| 44 | 
            +
            ```
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            The above is an exact copy of the `config.example.ru` from the root of the repo. You can boot this without too much
         | 
| 47 | 
            +
            effort by running:
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            ```bash
         | 
| 50 | 
            +
            bundle check || bundle install && bundle exec rackup -Ilib config.example.ru
         | 
| 51 | 
            +
            ```
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            After which the simulators should be running on port 9292.
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            ## Development
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            ## Contributing
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            Bug reports and pull requests are welcome on GitHub at https://github.com/dugancathal/api_sim.
         | 
    
        data/Rakefile
    ADDED
    
    
    
        data/api_sim.gemspec
    ADDED
    
    | @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            # coding: utf-8
         | 
| 2 | 
            +
            lib = File.expand_path('../lib', __FILE__)
         | 
| 3 | 
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 4 | 
            +
            require 'api_sim/version'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Gem::Specification.new do |spec|
         | 
| 7 | 
            +
              spec.name          = "api_sim"
         | 
| 8 | 
            +
              spec.version       = ApiSim::VERSION
         | 
| 9 | 
            +
              spec.authors       = ["TJ Taylor"]
         | 
| 10 | 
            +
              spec.email         = ["dugancathal@gmail.com"]
         | 
| 11 | 
            +
              spec.licenses      = ['MIT']
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              spec.summary       = %q{A DSL on top of sinatra for building application simulators}
         | 
| 14 | 
            +
              spec.description   = %q{A DSL on top of sinatra for building application simulators}
         | 
| 15 | 
            +
              spec.homepage      = "https://github.com/dugancathal/api_sim"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              spec.files         = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
         | 
| 18 | 
            +
              spec.bindir        = "exe"
         | 
| 19 | 
            +
              spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
         | 
| 20 | 
            +
              spec.require_paths = ["lib"]
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              spec.add_dependency "sinatra", '~> 1.0'
         | 
| 23 | 
            +
              spec.add_dependency "nokogiri", '~> 1.6.7'
         | 
| 24 | 
            +
              spec.add_dependency "json-schema", '~> 2.5.2'
         | 
| 25 | 
            +
              spec.add_development_dependency "bundler", "~> 1.11"
         | 
| 26 | 
            +
              spec.add_development_dependency "rake", "~> 10.0"
         | 
| 27 | 
            +
              spec.add_development_dependency "rspec", "~> 3.0"
         | 
| 28 | 
            +
              spec.add_development_dependency "capybara", "~> 2.7.0"
         | 
| 29 | 
            +
            end
         | 
    
        data/bin/console
    ADDED
    
    | @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "bundler/setup"
         | 
| 4 | 
            +
            require "api_sim"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            puts <<-RUBY
         | 
| 7 | 
            +
            #
         | 
| 8 | 
            +
            # Welcome to ApiSim!
         | 
| 9 | 
            +
            #
         | 
| 10 | 
            +
            # You can boot a sample app by running the following
         | 
| 11 | 
            +
            sample_app = ApiSim.build_app do
         | 
| 12 | 
            +
              configure_endpoint 'GET' , '/my-endpoint', 'Returns Hello!', 202
         | 
| 13 | 
            +
            end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            # You can inspect the endpoints that this app has:
         | 
| 16 | 
            +
            puts "=" * 50, sample_app.endpoints, "=" * 50
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            # You can run that app one of two ways.
         | 
| 19 | 
            +
            # Synchronously (you'll need to open a new window to play with the server):
         | 
| 20 | 
            +
            sample_app.run!
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            # Asynchronously (to continue playing in this window):
         | 
| 23 | 
            +
            Thread.new { sample_app.run! }
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            # You can stop the synchronous app by pressing CTRL+c.
         | 
| 26 | 
            +
            # If running asynchronously, you can stop by simply exiting the console.
         | 
| 27 | 
            +
            #
         | 
| 28 | 
            +
            RUBY
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            require "irb"
         | 
| 31 | 
            +
            IRB.start
         | 
    
        data/bin/setup
    ADDED
    
    
    
        data/config.example.ru
    ADDED
    
    | @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            require 'api_sim'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            ENDPOINT_JSON_SCHEMA = {"type": "object", "properties": {"a": {"type": "integer"}}}.to_json
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            app = ApiSim.build_app do
         | 
| 6 | 
            +
              configure_endpoint 'GET', '/endpoint', 'Hi!', 200, {'X-CUSTOM-HEADER' => 'easy as abc'}, ENDPOINT_JSON_SCHEMA
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              configure_dynamic_endpoint 'GET', '/dynamic', ->(req) {
         | 
| 9 | 
            +
                [201, {'X-CUSTOM-HEADER' => '123'}, 'Howdy!']
         | 
| 10 | 
            +
              }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              configure_matcher_endpoint 'POST', '/soap', {
         | 
| 13 | 
            +
                /Operation1/ => [200, {'Content-Type' => 'text/xml+soap'}, '<xml>Response1</xml>'],
         | 
| 14 | 
            +
                /Operation2/ => [500, {'Content-Type' => 'text/xml+soap'}, '<xml>Response2</xml>'],
         | 
| 15 | 
            +
              }
         | 
| 16 | 
            +
            end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            run app
         | 
| @@ -0,0 +1,60 @@ | |
| 1 | 
            +
            require 'api_sim/built_app'
         | 
| 2 | 
            +
            require 'api_sim/matchers'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module ApiSim
         | 
| 5 | 
            +
              class AppBuilder
         | 
| 6 | 
            +
                NOT_FOUND = [nil, [404, {}, 'NOT FOUND']]
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def rackapp
         | 
| 9 | 
            +
                  config = self
         | 
| 10 | 
            +
                  Class.new(BuiltApp) do
         | 
| 11 | 
            +
                    endpoints config.endpoint_configurations
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def configure_endpoint(http_method, route, response_body, response_code=200, headers={}, schema_string='')
         | 
| 16 | 
            +
                  endpoint_configurations.push(
         | 
| 17 | 
            +
                    Matchers::StaticRequestMatcher.new(
         | 
| 18 | 
            +
                      http_method: http_method,
         | 
| 19 | 
            +
                      route: route,
         | 
| 20 | 
            +
                      response_code: response_code,
         | 
| 21 | 
            +
                      headers: headers,
         | 
| 22 | 
            +
                      default: true,
         | 
| 23 | 
            +
                      response_body: response_body,
         | 
| 24 | 
            +
                      schema: schema_string
         | 
| 25 | 
            +
                    )
         | 
| 26 | 
            +
                  )
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def configure_dynamic_endpoint(http_method, route, response_logic)
         | 
| 30 | 
            +
                  endpoint_configurations.push(
         | 
| 31 | 
            +
                    Matchers::DynamicRequestMatcher.new(
         | 
| 32 | 
            +
                      http_method: http_method,
         | 
| 33 | 
            +
                      route: route,
         | 
| 34 | 
            +
                      default: true,
         | 
| 35 | 
            +
                      response_generator: response_logic
         | 
| 36 | 
            +
                    )
         | 
| 37 | 
            +
                  )
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def configure_matcher_endpoint(http_method, route, matchers_to_responses)
         | 
| 41 | 
            +
                  matchers_to_responses.each do |matcher, response|
         | 
| 42 | 
            +
                    endpoint_configurations.push(
         | 
| 43 | 
            +
                      Matchers::RequestBodyMatcher.new(
         | 
| 44 | 
            +
                        http_method: http_method,
         | 
| 45 | 
            +
                        route: route,
         | 
| 46 | 
            +
                        response_code: response[0],
         | 
| 47 | 
            +
                        headers: response[1],
         | 
| 48 | 
            +
                        response_body: response[2],
         | 
| 49 | 
            +
                        default: true,
         | 
| 50 | 
            +
                        body_matches: matcher
         | 
| 51 | 
            +
                      )
         | 
| 52 | 
            +
                    )
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def endpoint_configurations
         | 
| 57 | 
            +
                  @endpoint_configurations ||= []
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
            end
         | 
| @@ -0,0 +1,145 @@ | |
| 1 | 
            +
            require 'sinatra/base'
         | 
| 2 | 
            +
            require 'nokogiri'
         | 
| 3 | 
            +
            require 'json'
         | 
| 4 | 
            +
            require 'tilt/erb'
         | 
| 5 | 
            +
            require 'api_sim/view_helpers'
         | 
| 6 | 
            +
            require 'json-schema'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module ApiSim
         | 
| 9 | 
            +
              class BuiltApp < Sinatra::Base
         | 
| 10 | 
            +
                use Rack::MethodOverride
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                helpers do
         | 
| 13 | 
            +
                  include ViewHelpers
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def self.endpoints(endpoints = nil)
         | 
| 17 | 
            +
                  return @endpoints if @endpoints
         | 
| 18 | 
            +
                  @endpoints = endpoints
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                get '/' do
         | 
| 22 | 
            +
                  erb :'index.html', layout: :'layout.html'
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                get '/ui/response/:method/*' do
         | 
| 26 | 
            +
                  @config = matcher(faux_request(http_method, route, faux_body))
         | 
| 27 | 
            +
                  erb :'responses/form.html', layout: :'layout.html'
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                get '/ui/requests/:method/*' do
         | 
| 31 | 
            +
                  @config = matcher(faux_request(http_method, route, faux_body))
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  erb :'requests/index.html', layout: :'layout.html'
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                post '/ui/response/:method/*' do
         | 
| 37 | 
            +
                  @config = matcher(faux_request(http_method, route, faux_body))
         | 
| 38 | 
            +
                  unless params['schema'].empty?
         | 
| 39 | 
            +
                    @errors = JSON::Validator.fully_validate(params['schema'], params['body'])
         | 
| 40 | 
            +
                    if @errors.any?
         | 
| 41 | 
            +
                      return erb :'responses/form.html', layout: :'layout.html'
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  new_config = create_matcher_override(mimicked_request)
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  self.class.endpoints.unshift(new_config)
         | 
| 48 | 
            +
                  redirect to '/'
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                delete '/ui/response/:method/*' do
         | 
| 52 | 
            +
                  all_matching_matchers = matchers(faux_request(http_method, route, request.body))
         | 
| 53 | 
            +
                  all_matching_matchers.each &:reset!
         | 
| 54 | 
            +
                  non_default_matchers = all_matching_matchers.reject(&:default)
         | 
| 55 | 
            +
                  self.class.endpoints.delete_if { |endpoint| non_default_matchers.include?(endpoint) }
         | 
| 56 | 
            +
                  redirect to '/'
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                put '/response/*' do
         | 
| 60 | 
            +
                  self.class.endpoints.unshift(create_matcher_override(mimicked_request))
         | 
| 61 | 
            +
                  ''
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                delete '/response/*' do
         | 
| 65 | 
            +
                  all_matching_matchers = matchers(mimicked_request)
         | 
| 66 | 
            +
                  all_matching_matchers.each &:reset!
         | 
| 67 | 
            +
                  non_default_matchers = all_matching_matchers.reject(&:default)
         | 
| 68 | 
            +
                  self.class.endpoints.delete_if { |endpoint| non_default_matchers.include?(endpoint) }
         | 
| 69 | 
            +
                  ''
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                %i(get post put patch delete).each do |http_method|
         | 
| 73 | 
            +
                  public_send(http_method, '/*') do
         | 
| 74 | 
            +
                    endpoint = matcher(request)
         | 
| 75 | 
            +
                    endpoint.record_request(request)
         | 
| 76 | 
            +
                    endpoint.response(request)
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                private
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                def create_matcher_override(request)
         | 
| 83 | 
            +
                  old_matcher = matcher(request)
         | 
| 84 | 
            +
                  config = matcher_overrides(old_matcher.response(request))
         | 
| 85 | 
            +
                  old_matcher.overridden!
         | 
| 86 | 
            +
                  Matcher.dupe_and_reconfigure(old_matcher, config)
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                def matcher_overrides(old_config)
         | 
| 90 | 
            +
                  parsed_body.merge(
         | 
| 91 | 
            +
                    response_code: parsed_body.fetch('status', old_config[0]).to_i,
         | 
| 92 | 
            +
                    headers: parsed_body.fetch('headers', old_config[1]),
         | 
| 93 | 
            +
                    response_body: parsed_body.fetch('body', old_config[2]),
         | 
| 94 | 
            +
                    matcher: parsed_body.fetch('match', ''),
         | 
| 95 | 
            +
                    schema: parsed_body.fetch('schema', '')
         | 
| 96 | 
            +
                  )
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                def mimicked_request
         | 
| 100 | 
            +
                  faux_request(http_method, route, request.body)
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                def http_method
         | 
| 104 | 
            +
                  parsed_body.fetch('method', params['method']).upcase
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                def matcher(request)
         | 
| 108 | 
            +
                  matchers(request).first || halt(404)
         | 
| 109 | 
            +
                end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                def matchers(request)
         | 
| 112 | 
            +
                  self.class.endpoints.select { |matcher| matcher.matches?(request) }
         | 
| 113 | 
            +
                end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                def faux_request(method='', path='', body=StringIO.new(''))
         | 
| 116 | 
            +
                  body.rewind
         | 
| 117 | 
            +
                  Rack::Request.new({'rack.input' => body, 'REQUEST_METHOD' => method, 'PATH_INFO' => path})
         | 
| 118 | 
            +
                end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                def faux_body
         | 
| 121 | 
            +
                  StringIO.new(params[:match].to_s)
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                def parsed_body
         | 
| 125 | 
            +
                  return @response_body if @response_body
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                  @response_body = case request.env['CONTENT_TYPE']
         | 
| 128 | 
            +
                  when 'application/json' then
         | 
| 129 | 
            +
                    JSON.parse(request.body.read)
         | 
| 130 | 
            +
                  when 'text/xml' then
         | 
| 131 | 
            +
                    Nokogiri::XML(request.body.read)
         | 
| 132 | 
            +
                  when 'application/x-www-form-urlencoded' then
         | 
| 133 | 
            +
                    params
         | 
| 134 | 
            +
                  else
         | 
| 135 | 
            +
                    if request.path =~ /ui/
         | 
| 136 | 
            +
                      params
         | 
| 137 | 
            +
                    else
         | 
| 138 | 
            +
                      request.body.read
         | 
| 139 | 
            +
                    end
         | 
| 140 | 
            +
                  end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                  @response_body.empty? ? {} : @response_body
         | 
| 143 | 
            +
                end
         | 
| 144 | 
            +
              end
         | 
| 145 | 
            +
            end
         | 
| @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            require 'api_sim/recorded_request'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ApiSim
         | 
| 4 | 
            +
              module Matchers
         | 
| 5 | 
            +
                class BaseMatcher
         | 
| 6 | 
            +
                  DEFAULT_RACK_RESPONSE=[200, {}, '']
         | 
| 7 | 
            +
                  ALWAYS_TRUE_MATCHER = ->(request) { true }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def custom_matcher?
         | 
| 10 | 
            +
                    matcher != ALWAYS_TRUE_MATCHER
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def overridden!
         | 
| 14 | 
            +
                    @overridden = true
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def overridden?
         | 
| 18 | 
            +
                    !!@overridden
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def reset!
         | 
| 22 | 
            +
                    @overridden = false
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def requests
         | 
| 26 | 
            +
                    @requests ||= []
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def match_on_body?
         | 
| 30 | 
            +
                    false
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def readonly?
         | 
| 34 | 
            +
                    false
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def record_request(request)
         | 
| 38 | 
            +
                    request.body.rewind
         | 
| 39 | 
            +
                    requests.push(RecordedRequest.new(body: request.body.read, request_env: request.env))
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  def to_s
         | 
| 43 | 
            +
                    <<-DOC.gsub(/^\s+/, '')
         | 
| 44 | 
            +
                      #{http_method} #{route}
         | 
| 45 | 
            +
                    DOC
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def response(_)
         | 
| 49 | 
            +
                    [response_code, headers, response_body]
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
            end
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            require 'api_sim/recorded_request'
         | 
| 2 | 
            +
            require 'api_sim/matchers/base_matcher'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module ApiSim
         | 
| 5 | 
            +
              module Matchers
         | 
| 6 | 
            +
                class DynamicRequestMatcher < BaseMatcher
         | 
| 7 | 
            +
                  attr_reader :response_generator, :route, :http_method, :default, :matcher
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def initialize(http_method:, route:, response_generator:, default: false, matcher: ALWAYS_TRUE_MATCHER)
         | 
| 10 | 
            +
                    @matcher = matcher
         | 
| 11 | 
            +
                    @route = route
         | 
| 12 | 
            +
                    @http_method = http_method
         | 
| 13 | 
            +
                    @default = default
         | 
| 14 | 
            +
                    @response_generator = response_generator
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def matches?(request)
         | 
| 18 | 
            +
                    request.path == route && request.request_method == http_method && matcher.call(request)
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def response(request)
         | 
| 22 | 
            +
                    response_generator.call(request)
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def readonly?
         | 
| 26 | 
            +
                    true
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def to_s
         | 
| 30 | 
            +
                    <<-DOC.gsub(/^\s+/, '')
         | 
| 31 | 
            +
                      #{http_method} #{route} -> DYNAMIC BASED ON REQUEST
         | 
| 32 | 
            +
                    DOC
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            require 'api_sim/recorded_request'
         | 
| 2 | 
            +
            require 'api_sim/matchers/base_matcher'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module ApiSim
         | 
| 5 | 
            +
              module Matchers
         | 
| 6 | 
            +
                class RequestBodyMatcher < BaseMatcher
         | 
| 7 | 
            +
                  attr_reader :http_method, :route, :headers, :response_code, :matcher, :response_body, :default, :schema
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def initialize(http_method:, route:, response_code: 200, response_body: '', headers: {}, default: false, body_matches:, schema: nil)
         | 
| 10 | 
            +
                    @default = default
         | 
| 11 | 
            +
                    @matcher = Regexp.compile(body_matches)
         | 
| 12 | 
            +
                    @headers = headers
         | 
| 13 | 
            +
                    @response_body = response_body
         | 
| 14 | 
            +
                    @response_code = response_code
         | 
| 15 | 
            +
                    @route = route
         | 
| 16 | 
            +
                    @http_method = http_method
         | 
| 17 | 
            +
                    @schema = schema
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def matches?(request)
         | 
| 21 | 
            +
                    request.body.rewind
         | 
| 22 | 
            +
                    body = request.body.read
         | 
| 23 | 
            +
                    request.body.rewind
         | 
| 24 | 
            +
                    request.path == route && request.request_method == http_method && matcher.match(body)
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def match_on_body?
         | 
| 28 | 
            +
                    true
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def to_s
         | 
| 32 | 
            +
                    <<-DOC.gsub(/^\s+/, '')
         | 
| 33 | 
            +
                      #{http_method} #{route} /#{matcher.source}/ -> (#{response_code}) #{response_body[0..20]}...
         | 
| 34 | 
            +
                    DOC
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
            end
         | 
| @@ -0,0 +1,56 @@ | |
| 1 | 
            +
            require 'api_sim/recorded_request'
         | 
| 2 | 
            +
            require 'api_sim/matchers/base_matcher'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module ApiSim
         | 
| 5 | 
            +
              module Matchers
         | 
| 6 | 
            +
                class StaticRequestMatcher < BaseMatcher
         | 
| 7 | 
            +
                  attr_reader :http_method, :route, :headers, :response_code, :matcher, :response_body, :default, :schema
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def initialize(http_method:, route:, response_code: 200, response_body: '', headers: {}, default: false, matcher: ALWAYS_TRUE_MATCHER, schema: nil)
         | 
| 10 | 
            +
                    @default = default
         | 
| 11 | 
            +
                    @matcher = matcher
         | 
| 12 | 
            +
                    @headers = headers
         | 
| 13 | 
            +
                    @response_body = response_body
         | 
| 14 | 
            +
                    @response_code = response_code
         | 
| 15 | 
            +
                    @route = route
         | 
| 16 | 
            +
                    @http_method = http_method
         | 
| 17 | 
            +
                    @schema = schema
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def matches?(request)
         | 
| 21 | 
            +
                    request.path == route && request.request_method == http_method && matcher.call(request)
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def custom_matcher?
         | 
| 25 | 
            +
                    matcher != ALWAYS_TRUE_MATCHER
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def overridden!
         | 
| 29 | 
            +
                    @overridden = true
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def overridden?
         | 
| 33 | 
            +
                    !!@overridden
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  def reset!
         | 
| 37 | 
            +
                    @overridden = false
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  def requests
         | 
| 41 | 
            +
                    @requests ||= []
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  def to_s
         | 
| 45 | 
            +
                    <<-DOC.gsub(/^\s+/, '')
         | 
| 46 | 
            +
                      #{http_method} #{route} -> (#{response_code}) #{response_body[0..20]}...
         | 
| 47 | 
            +
                    DOC
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  def record_request(request)
         | 
| 51 | 
            +
                    request.body.rewind
         | 
| 52 | 
            +
                    requests.push(RecordedRequest.new(body: request.body.read, request_env: request.env))
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            require 'api_sim/matchers/dynamic_request_matcher'
         | 
| 2 | 
            +
            require 'api_sim/matchers/static_request_matcher'
         | 
| 3 | 
            +
            require 'api_sim/matchers/request_body_matcher'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module ApiSim
         | 
| 6 | 
            +
              class Matcher
         | 
| 7 | 
            +
                OVERRIDE_CLASS_MAP = {
         | 
| 8 | 
            +
                  Matchers::DynamicRequestMatcher => Matchers::StaticRequestMatcher,
         | 
| 9 | 
            +
                  Matchers::StaticRequestMatcher => Matchers::StaticRequestMatcher,
         | 
| 10 | 
            +
                  Matchers::RequestBodyMatcher => Matchers::RequestBodyMatcher,
         | 
| 11 | 
            +
                }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def self.dupe_and_reconfigure(old_matcher, overrides)
         | 
| 14 | 
            +
                  if old_matcher.match_on_body?
         | 
| 15 | 
            +
                    Matchers::RequestBodyMatcher.new(
         | 
| 16 | 
            +
                      route: old_matcher.route,
         | 
| 17 | 
            +
                      http_method: old_matcher.http_method,
         | 
| 18 | 
            +
                      response_code: overrides.fetch(:response_code),
         | 
| 19 | 
            +
                      headers: overrides.fetch(:headers),
         | 
| 20 | 
            +
                      response_body: overrides.fetch(:response_body),
         | 
| 21 | 
            +
                      body_matches: overrides.fetch('matcher', old_matcher.matcher)
         | 
| 22 | 
            +
                    )
         | 
| 23 | 
            +
                  else
         | 
| 24 | 
            +
                    Matchers::StaticRequestMatcher.new(
         | 
| 25 | 
            +
                      route: old_matcher.route,
         | 
| 26 | 
            +
                      http_method: old_matcher.http_method,
         | 
| 27 | 
            +
                      response_code: overrides.fetch(:response_code),
         | 
| 28 | 
            +
                      headers: overrides.fetch(:headers),
         | 
| 29 | 
            +
                      response_body: overrides.fetch(:response_body),
         | 
| 30 | 
            +
                      schema: overrides.fetch(:schema),
         | 
| 31 | 
            +
                    )
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            (function () {
         | 
| 2 | 
            +
                var prettifyJson = function prettifyJson(obj) {
         | 
| 3 | 
            +
                    return JSON.stringify(obj, null, 2);
         | 
| 4 | 
            +
                };
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                var prettyPrintAllThings = function prettyPrintAllThings() {
         | 
| 7 | 
            +
                    var elements = document.querySelectorAll('[data-prettify]');
         | 
| 8 | 
            +
                    for (var i = 0; i < elements.length; i++) {
         | 
| 9 | 
            +
                        var el = elements[i];
         | 
| 10 | 
            +
                        try {
         | 
| 11 | 
            +
                            obj = JSON.parse(el.value);
         | 
| 12 | 
            +
                            el.value = prettifyJson(obj)
         | 
| 13 | 
            +
                        } catch (e) {
         | 
| 14 | 
            +
                        }
         | 
| 15 | 
            +
                    }
         | 
| 16 | 
            +
                };
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                var allowCollapseOfTableRows = function allowCollapseOfTableRows() {
         | 
| 19 | 
            +
                    var elements = document.querySelectorAll('tr');
         | 
| 20 | 
            +
                    for (var i = 0; i < elements.length; i++) {
         | 
| 21 | 
            +
                        var el = elements[i];
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                        el.onclick = function collapseTableRow(event) {
         | 
| 24 | 
            +
                            var tr = event.target.parentNode;
         | 
| 25 | 
            +
                            var lastTds = Array.prototype.slice.apply(tr.children).slice(1);
         | 
| 26 | 
            +
                            lastTds.forEach(function (td) {
         | 
| 27 | 
            +
                                var tdContent = Array.prototype.slice.apply(td.children);
         | 
| 28 | 
            +
                                tdContent.forEach(function (innerElement) {
         | 
| 29 | 
            +
                                    if (innerElement.style.display) {
         | 
| 30 | 
            +
                                        innerElement.style.display = null;
         | 
| 31 | 
            +
                                    } else {
         | 
| 32 | 
            +
                                        innerElement.style.display = 'none';
         | 
| 33 | 
            +
                                    }
         | 
| 34 | 
            +
                                });
         | 
| 35 | 
            +
                            });
         | 
| 36 | 
            +
                        }
         | 
| 37 | 
            +
                    }
         | 
| 38 | 
            +
                };
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                prettyPrintAllThings();
         | 
| 41 | 
            +
                allowCollapseOfTableRows();
         | 
| 42 | 
            +
            })();
         | 
| Binary file | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            module ApiSim
         | 
| 2 | 
            +
              class RecordedRequest
         | 
| 3 | 
            +
                attr_reader :time, :headers, :body
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(time: Time.now, body:, request_env:)
         | 
| 6 | 
            +
                  @time = time
         | 
| 7 | 
            +
                  @body = body
         | 
| 8 | 
            +
                  @headers = parse_headers_from(request_env)
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                private
         | 
| 12 | 
            +
                def parse_headers_from(request_env)
         | 
| 13 | 
            +
                  request_env.select do |k, v|
         | 
| 14 | 
            +
                    k =~ /^HTTP_/
         | 
| 15 | 
            +
                  end.each_with_object({}) do |(k, v), h|
         | 
| 16 | 
            +
                    h[k.split('_')[1..-1].join('-')] = v
         | 
| 17 | 
            +
                  end.sort
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            module ApiSim
         | 
| 2 | 
            +
              module ViewHelpers
         | 
| 3 | 
            +
                def endpoints
         | 
| 4 | 
            +
                  self.class.endpoints.reject(&:overridden?).sort_by { |endpoint| [endpoint.http_method, endpoint.route].join(' ') }
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def custom_matcher?(endpoint)
         | 
| 8 | 
            +
                  endpoint.custom_matcher? ? '(Custom matcher)' : ''
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def config
         | 
| 12 | 
            +
                  @config
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def route
         | 
| 16 | 
            +
                  "/#{params[:splat].first}"
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def link_to_response_edit(endpoint)
         | 
| 20 | 
            +
                  match = endpoint.match_on_body? ? endpoint.matcher.source : ''
         | 
| 21 | 
            +
                  <<-HTML
         | 
| 22 | 
            +
                  <a href="/ui/response/#{endpoint.http_method}#{endpoint.route}?match=#{match}">
         | 
| 23 | 
            +
                    #{endpoint.route}
         | 
| 24 | 
            +
                  </a>
         | 
| 25 | 
            +
                  HTML
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def h(text)
         | 
| 29 | 
            +
                  Rack::Utils.escape_html(text)
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def link_to_read_requests(endpoint)
         | 
| 33 | 
            +
                  match = endpoint.match_on_body? ? endpoint.matcher.source : ''
         | 
| 34 | 
            +
                  <<-HTML
         | 
| 35 | 
            +
                    <a href="/ui/requests/#{endpoint.http_method}#{endpoint.route}?match=#{match}">
         | 
| 36 | 
            +
                      #{endpoint.requests.count}
         | 
| 37 | 
            +
                    </a>
         | 
| 38 | 
            +
                  HTML
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            <h2>Simulators</h2>
         | 
| 2 | 
            +
            <table>
         | 
| 3 | 
            +
              <thead>
         | 
| 4 | 
            +
              <tr>
         | 
| 5 | 
            +
                <th>Simulated endpoint</th>
         | 
| 6 | 
            +
                <th>Body Match?</th>
         | 
| 7 | 
            +
                <th>Response</th>
         | 
| 8 | 
            +
                <th>Requests</th>
         | 
| 9 | 
            +
                <th></th>
         | 
| 10 | 
            +
              </tr>
         | 
| 11 | 
            +
              </thead>
         | 
| 12 | 
            +
              <tbody>
         | 
| 13 | 
            +
              <% endpoints.each do |endpoint| %>
         | 
| 14 | 
            +
                  <tr>
         | 
| 15 | 
            +
                    <td><%= endpoint.http_method %> <%= endpoint.route %> <%= custom_matcher?(endpoint) %></td>
         | 
| 16 | 
            +
                    <td><%= endpoint.match_on_body? ? "/#{endpoint.matcher.source}/" : '' %></td>
         | 
| 17 | 
            +
                    <td>
         | 
| 18 | 
            +
                      <% if endpoint.readonly? %>
         | 
| 19 | 
            +
                        Cannot edit dynamic endpoints
         | 
| 20 | 
            +
                      <% else %>
         | 
| 21 | 
            +
                        <%= link_to_response_edit endpoint %>
         | 
| 22 | 
            +
                      <% end %>
         | 
| 23 | 
            +
                    </td>
         | 
| 24 | 
            +
                    <td>
         | 
| 25 | 
            +
                      <%= link_to_read_requests endpoint %>
         | 
| 26 | 
            +
                    </td>
         | 
| 27 | 
            +
                    <td>
         | 
| 28 | 
            +
                      <form action="/ui/response/<%= endpoint.http_method %><%= endpoint.route %>" method="post">
         | 
| 29 | 
            +
                        <input type="hidden" value="delete" name="_method">
         | 
| 30 | 
            +
                        <button class='btn-link' type="submit">Reset</button>
         | 
| 31 | 
            +
                      </form>
         | 
| 32 | 
            +
                    </td>
         | 
| 33 | 
            +
                  </tr>
         | 
| 34 | 
            +
              <% end %>
         | 
| 35 | 
            +
              </tbody>
         | 
| 36 | 
            +
            </table>
         | 
| @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            <html>
         | 
| 2 | 
            +
            <head>
         | 
| 3 | 
            +
              <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              <!-- Optional theme -->
         | 
| 6 | 
            +
              <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
         | 
| 7 | 
            +
              <link rel="stylesheet" href="/app.css">
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              <style>
         | 
| 10 | 
            +
                body {
         | 
| 11 | 
            +
                  padding: 20px;
         | 
| 12 | 
            +
                }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                table {
         | 
| 15 | 
            +
                  border-collapse: collapse;
         | 
| 16 | 
            +
                }
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                td, th {
         | 
| 19 | 
            +
                  padding: 10px;
         | 
| 20 | 
            +
                  text-align: left;
         | 
| 21 | 
            +
                  border: 1px solid gray;
         | 
| 22 | 
            +
                }
         | 
| 23 | 
            +
              </style>
         | 
| 24 | 
            +
            </head>
         | 
| 25 | 
            +
            <body>
         | 
| 26 | 
            +
            <div class="container">
         | 
| 27 | 
            +
              <%= yield %>
         | 
| 28 | 
            +
              <script src="/app.js"></script>
         | 
| 29 | 
            +
            </div>
         | 
| 30 | 
            +
            </body>
         | 
| 31 | 
            +
            </html>
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            <div class="page-header">
         | 
| 2 | 
            +
                <h1>Simulators</h1>
         | 
| 3 | 
            +
                <h2>Requests to <%= config.http_method %> <%= config.route %></h2>
         | 
| 4 | 
            +
            </div>
         | 
| 5 | 
            +
            <table class="table">
         | 
| 6 | 
            +
                <thead>
         | 
| 7 | 
            +
                <tr>
         | 
| 8 | 
            +
                    <th>Time</th>
         | 
| 9 | 
            +
                    <th>Body</th>
         | 
| 10 | 
            +
                    <th>Headers</th>
         | 
| 11 | 
            +
                </tr>
         | 
| 12 | 
            +
                </thead>
         | 
| 13 | 
            +
                <tbody>
         | 
| 14 | 
            +
                <% config.requests.each do |request| %>
         | 
| 15 | 
            +
                    <tr>
         | 
| 16 | 
            +
                        <th class="header-column"><p><%= request.time %></p></th>
         | 
| 17 | 
            +
                        <td><p><%= h request.body %></p></td>
         | 
| 18 | 
            +
                        <td>
         | 
| 19 | 
            +
                          <ul>
         | 
| 20 | 
            +
                          <% request.headers.each do |header, value| %>
         | 
| 21 | 
            +
                            <li><%= header %>: <%= value %></li>
         | 
| 22 | 
            +
                          <% end %>
         | 
| 23 | 
            +
                          </ul>
         | 
| 24 | 
            +
                        </td>
         | 
| 25 | 
            +
                    </tr>
         | 
| 26 | 
            +
                <% end %>
         | 
| 27 | 
            +
                </tbody>
         | 
| 28 | 
            +
            </table>
         | 
| @@ -0,0 +1,50 @@ | |
| 1 | 
            +
            <div class="page-header">
         | 
| 2 | 
            +
              <h1>Simulators</h1>
         | 
| 3 | 
            +
              <h2>Response for <%= config.http_method %> <%= config.route %></h2>
         | 
| 4 | 
            +
            </div>
         | 
| 5 | 
            +
            <form action="/ui/response/<%= config.http_method %><%= config.route %>" method="post">
         | 
| 6 | 
            +
              <div class="row">
         | 
| 7 | 
            +
                <% if @errors %>
         | 
| 8 | 
            +
                  <div class="alert alert-danger">
         | 
| 9 | 
            +
                    <p>Body does not match expected schema</p>
         | 
| 10 | 
            +
                    <ul>
         | 
| 11 | 
            +
                      <% @errors.each do |error| %>
         | 
| 12 | 
            +
                        <li><%= error %></li>
         | 
| 13 | 
            +
                      <% end %>
         | 
| 14 | 
            +
                    </ul>
         | 
| 15 | 
            +
                  </div>
         | 
| 16 | 
            +
                <% end %>
         | 
| 17 | 
            +
              </div>
         | 
| 18 | 
            +
              <div class="row">
         | 
| 19 | 
            +
                <div class="col-md-6">
         | 
| 20 | 
            +
                  <div class="form-group">
         | 
| 21 | 
            +
                    <label for="status">Status code</label>
         | 
| 22 | 
            +
                    <input type="text" class="form-control" id="status" name="status" value="<%= params[:status] || config.response_code %>">
         | 
| 23 | 
            +
                  </div>
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  <% if config.match_on_body? %>
         | 
| 26 | 
            +
                      <div class="form-group">
         | 
| 27 | 
            +
                        <label for="match">Match body on</label>
         | 
| 28 | 
            +
                        <input type="text" class="form-control" id="match" name="match" disabled="disabled" value="<%= params[:match] || config.matcher.source %>"/>
         | 
| 29 | 
            +
                        <input type="hidden" id="match" name="match" value="<%= params[:match] || config.matcher.source %>"/>
         | 
| 30 | 
            +
                      </div>
         | 
| 31 | 
            +
                  <% end %>
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  <div class="form-group">
         | 
| 34 | 
            +
                    <label for="schema">Response schema</label>
         | 
| 35 | 
            +
                    <textarea type="text" class="form-control" id="schema" name="schema" data-prettify rows="25"><%= params['schema'] || config.schema %></textarea>
         | 
| 36 | 
            +
                  </div>
         | 
| 37 | 
            +
                </div>
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                <div class="col-md-6">
         | 
| 40 | 
            +
                  <div class="form-group">
         | 
| 41 | 
            +
                    <label for="body">Response body</label>
         | 
| 42 | 
            +
                    <textarea class="form-control" id="body" name="body" data-prettify rows="25"><%= params['body'] || config.response_body %></textarea>
         | 
| 43 | 
            +
                  </div>
         | 
| 44 | 
            +
                </div>
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                <div class="form-actions pull-right">
         | 
| 47 | 
            +
                  <button class='btn btn-primary' type="submit">Save</button>
         | 
| 48 | 
            +
                </div>
         | 
| 49 | 
            +
              </div>
         | 
| 50 | 
            +
            </form>
         | 
    
        data/lib/api_sim.rb
    ADDED
    
    
    
        metadata
    ADDED
    
    | @@ -0,0 +1,171 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: api_sim
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.1.0
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - TJ Taylor
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: exe
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2016-04-28 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: sinatra
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - "~>"
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '1.0'
         | 
| 20 | 
            +
              type: :runtime
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - "~>"
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '1.0'
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: nokogiri
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - "~>"
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: 1.6.7
         | 
| 34 | 
            +
              type: :runtime
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - "~>"
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: 1.6.7
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: json-schema
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - "~>"
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: 2.5.2
         | 
| 48 | 
            +
              type: :runtime
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - "~>"
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: 2.5.2
         | 
| 55 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            +
              name: bundler
         | 
| 57 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - "~>"
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: '1.11'
         | 
| 62 | 
            +
              type: :development
         | 
| 63 | 
            +
              prerelease: false
         | 
| 64 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                requirements:
         | 
| 66 | 
            +
                - - "~>"
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            +
                    version: '1.11'
         | 
| 69 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 70 | 
            +
              name: rake
         | 
| 71 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 | 
            +
                requirements:
         | 
| 73 | 
            +
                - - "~>"
         | 
| 74 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 75 | 
            +
                    version: '10.0'
         | 
| 76 | 
            +
              type: :development
         | 
| 77 | 
            +
              prerelease: false
         | 
| 78 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 79 | 
            +
                requirements:
         | 
| 80 | 
            +
                - - "~>"
         | 
| 81 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 82 | 
            +
                    version: '10.0'
         | 
| 83 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 84 | 
            +
              name: rspec
         | 
| 85 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 86 | 
            +
                requirements:
         | 
| 87 | 
            +
                - - "~>"
         | 
| 88 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 89 | 
            +
                    version: '3.0'
         | 
| 90 | 
            +
              type: :development
         | 
| 91 | 
            +
              prerelease: false
         | 
| 92 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 93 | 
            +
                requirements:
         | 
| 94 | 
            +
                - - "~>"
         | 
| 95 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 96 | 
            +
                    version: '3.0'
         | 
| 97 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 98 | 
            +
              name: capybara
         | 
| 99 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 100 | 
            +
                requirements:
         | 
| 101 | 
            +
                - - "~>"
         | 
| 102 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 103 | 
            +
                    version: 2.7.0
         | 
| 104 | 
            +
              type: :development
         | 
| 105 | 
            +
              prerelease: false
         | 
| 106 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 107 | 
            +
                requirements:
         | 
| 108 | 
            +
                - - "~>"
         | 
| 109 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 110 | 
            +
                    version: 2.7.0
         | 
| 111 | 
            +
            description: A DSL on top of sinatra for building application simulators
         | 
| 112 | 
            +
            email:
         | 
| 113 | 
            +
            - dugancathal@gmail.com
         | 
| 114 | 
            +
            executables: []
         | 
| 115 | 
            +
            extensions: []
         | 
| 116 | 
            +
            extra_rdoc_files: []
         | 
| 117 | 
            +
            files:
         | 
| 118 | 
            +
            - ".gitignore"
         | 
| 119 | 
            +
            - ".rspec"
         | 
| 120 | 
            +
            - ".travis.yml"
         | 
| 121 | 
            +
            - Gemfile
         | 
| 122 | 
            +
            - LICENSE.txt
         | 
| 123 | 
            +
            - README.md
         | 
| 124 | 
            +
            - Rakefile
         | 
| 125 | 
            +
            - api_sim.gemspec
         | 
| 126 | 
            +
            - bin/console
         | 
| 127 | 
            +
            - bin/setup
         | 
| 128 | 
            +
            - config.example.ru
         | 
| 129 | 
            +
            - lib/api_sim.rb
         | 
| 130 | 
            +
            - lib/api_sim/app_builder.rb
         | 
| 131 | 
            +
            - lib/api_sim/built_app.rb
         | 
| 132 | 
            +
            - lib/api_sim/matchers.rb
         | 
| 133 | 
            +
            - lib/api_sim/matchers/base_matcher.rb
         | 
| 134 | 
            +
            - lib/api_sim/matchers/dynamic_request_matcher.rb
         | 
| 135 | 
            +
            - lib/api_sim/matchers/request_body_matcher.rb
         | 
| 136 | 
            +
            - lib/api_sim/matchers/static_request_matcher.rb
         | 
| 137 | 
            +
            - lib/api_sim/public/app.css
         | 
| 138 | 
            +
            - lib/api_sim/public/app.js
         | 
| 139 | 
            +
            - lib/api_sim/public/favicon.ico
         | 
| 140 | 
            +
            - lib/api_sim/recorded_request.rb
         | 
| 141 | 
            +
            - lib/api_sim/version.rb
         | 
| 142 | 
            +
            - lib/api_sim/view_helpers.rb
         | 
| 143 | 
            +
            - lib/api_sim/views/index.html.erb
         | 
| 144 | 
            +
            - lib/api_sim/views/layout.html.erb
         | 
| 145 | 
            +
            - lib/api_sim/views/requests/index.html.erb
         | 
| 146 | 
            +
            - lib/api_sim/views/responses/form.html.erb
         | 
| 147 | 
            +
            homepage: https://github.com/dugancathal/api_sim
         | 
| 148 | 
            +
            licenses:
         | 
| 149 | 
            +
            - MIT
         | 
| 150 | 
            +
            metadata: {}
         | 
| 151 | 
            +
            post_install_message: 
         | 
| 152 | 
            +
            rdoc_options: []
         | 
| 153 | 
            +
            require_paths:
         | 
| 154 | 
            +
            - lib
         | 
| 155 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 156 | 
            +
              requirements:
         | 
| 157 | 
            +
              - - ">="
         | 
| 158 | 
            +
                - !ruby/object:Gem::Version
         | 
| 159 | 
            +
                  version: '0'
         | 
| 160 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 161 | 
            +
              requirements:
         | 
| 162 | 
            +
              - - ">="
         | 
| 163 | 
            +
                - !ruby/object:Gem::Version
         | 
| 164 | 
            +
                  version: '0'
         | 
| 165 | 
            +
            requirements: []
         | 
| 166 | 
            +
            rubyforge_project: 
         | 
| 167 | 
            +
            rubygems_version: 2.5.1
         | 
| 168 | 
            +
            signing_key: 
         | 
| 169 | 
            +
            specification_version: 4
         | 
| 170 | 
            +
            summary: A DSL on top of sinatra for building application simulators
         | 
| 171 | 
            +
            test_files: []
         |