rack.git  about / heads / tags
a modular Ruby webserver interface
blob afc855e21a3bb1ada9a4ab31dbd5ca6b7aee9cb4 5606 bytes (raw)
$ git show rfc7231-sec6.3.6-205:lib/rack/mock.rb	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
 
require 'uri'
require 'stringio'
require 'rack'
require 'rack/lint'
require 'rack/utils'
require 'rack/response'

module Rack
  # Rack::MockRequest helps testing your Rack application without
  # actually using HTTP.
  #
  # After performing a request on a URL with get/post/put/patch/delete, it
  # returns a MockResponse with useful helper methods for effective
  # testing.
  #
  # You can pass a hash with additional configuration to the
  # get/post/put/patch/delete.
  # <tt>:input</tt>:: A String or IO-like to be used as rack.input.
  # <tt>:fatal</tt>:: Raise a FatalWarning if the app writes to rack.errors.
  # <tt>:lint</tt>:: If true, wrap the application in a Rack::Lint.

  class MockRequest
    class FatalWarning < RuntimeError
    end

    class FatalWarner
      def puts(warning)
        raise FatalWarning, warning
      end

      def write(warning)
        raise FatalWarning, warning
      end

      def flush
      end

      def string
        ""
      end
    end

    DEFAULT_ENV = {
      RACK_VERSION      => Rack::VERSION,
      RACK_INPUT        => StringIO.new,
      RACK_ERRORS       => StringIO.new,
      RACK_MULTITHREAD  => true,
      RACK_MULTIPROCESS => true,
      RACK_RUNONCE      => false,
    }.freeze

    def initialize(app)
      @app = app
    end

    def get(uri, opts={})     request(GET, uri, opts)     end
    def post(uri, opts={})    request(POST, uri, opts)    end
    def put(uri, opts={})     request(PUT, uri, opts)     end
    def patch(uri, opts={})   request(PATCH, uri, opts)   end
    def delete(uri, opts={})  request(DELETE, uri, opts)  end
    def head(uri, opts={})    request(HEAD, uri, opts)    end
    def options(uri, opts={}) request(OPTIONS, uri, opts) end

    def request(method=GET, uri="", opts={})
      env = self.class.env_for(uri, opts.merge(:method => method))

      if opts[:lint]
        app = Rack::Lint.new(@app)
      else
        app = @app
      end

      errors = env[RACK_ERRORS]
      status, headers, body  = app.call(env)
      MockResponse.new(status, headers, body, errors)
    ensure
      body.close if body.respond_to?(:close)
    end

    # For historical reasons, we're pinning to RFC 2396.
    # URI::Parser = URI::RFC2396_Parser
    def self.parse_uri_rfc2396(uri)
      @parser ||= URI::Parser.new
      @parser.parse(uri)
    end

    # Return the Rack environment used for a request to +uri+.
    def self.env_for(uri="", opts={})
      uri = parse_uri_rfc2396(uri)
      uri.path = "/#{uri.path}" unless uri.path[0] == ?/

      env = DEFAULT_ENV.dup

      env[REQUEST_METHOD]  = opts[:method] ? opts[:method].to_s.upcase : GET
      env[SERVER_NAME]     = uri.host || "example.org"
      env[SERVER_PORT]     = uri.port ? uri.port.to_s : "80"
      env[QUERY_STRING]    = uri.query.to_s
      env[PATH_INFO]       = (!uri.path || uri.path.empty?) ? "/" : uri.path
      env[RACK_URL_SCHEME] = uri.scheme || "http"
      env[HTTPS]           = env[RACK_URL_SCHEME] == "https" ? "on" : "off"

      env[SCRIPT_NAME] = opts[:script_name] || ""

      if opts[:fatal]
        env[RACK_ERRORS] = FatalWarner.new
      else
        env[RACK_ERRORS] = StringIO.new
      end

      if params = opts[:params]
        if env[REQUEST_METHOD] == GET
          params = Utils.parse_nested_query(params) if params.is_a?(String)
          params.update(Utils.parse_nested_query(env[QUERY_STRING]))
          env[QUERY_STRING] = Utils.build_nested_query(params)
        elsif !opts.has_key?(:input)
          opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
          if params.is_a?(Hash)
            if data = Rack::Multipart.build_multipart(params)
              opts[:input] = data
              opts["CONTENT_LENGTH"] ||= data.length.to_s
              opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}"
            else
              opts[:input] = Utils.build_nested_query(params)
            end
          else
            opts[:input] = params
          end
        end
      end

      empty_str = String.new.force_encoding(Encoding::ASCII_8BIT)
      opts[:input] ||= empty_str
      if String === opts[:input]
        rack_input = StringIO.new(opts[:input])
      else
        rack_input = opts[:input]
      end

      rack_input.set_encoding(Encoding::BINARY)
      env[RACK_INPUT] = rack_input

      env["CONTENT_LENGTH"] ||= env[RACK_INPUT].length.to_s

      opts.each { |field, value|
        env[field] = value  if String === field
      }

      env
    end
  end

  # Rack::MockResponse provides useful helpers for testing your apps.
  # Usually, you don't create the MockResponse on your own, but use
  # MockRequest.

  class MockResponse < Rack::Response
    # Headers
    attr_reader :original_headers

    # Errors
    attr_accessor :errors

    def initialize(status, headers, body, errors=StringIO.new(""))
      @original_headers = headers
      @errors           = errors.string if errors.respond_to?(:string)

      super(body, status, headers)
    end

    def =~(other)
      body =~ other
    end

    def match(other)
      body.match other
    end

    def body
      # FIXME: apparently users of MockResponse expect the return value of
      # MockResponse#body to be a string.  However, the real response object
      # returns the body as a list.
      #
      # See spec_showstatus.rb:
      #
      #   should "not replace existing messages" do
      #     ...
      #     res.body.should == "foo!"
      #   end
      super.join
    end

    def empty?
      [201, 204, 304].include? status
    end
  end
end

git clone https://yhbt.net/rack.git