feat(testing): add Prism and Testing
This commit is contained in:
1 parent
bf0042120f
commit
0b6cf1b91e
1 file changed
+144
@@ -0,0 +1,144 @@
|
||||
require 'uri'
|
||||
require 'stringio'
|
||||
|
||||
module Prism
|
||||
module Testing
|
||||
# A single simulated "browser" talking to a Rack-style app in-process:
|
||||
# no socket, no server, just direct #call. Keeps a cookie jar across
|
||||
# requests within the session (the way Prism::Response#set_cookie /
|
||||
# Request#parse_cookies expect: raw CGI-escaped values passed through
|
||||
# unchanged) so multi-request flows (login, then an authenticated
|
||||
# request; CSRF token issued, then submitted) can be exercised for real
|
||||
# instead of stitched together by hand in every test file.
|
||||
class IntegrationSession
|
||||
attr_reader :response
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
@cookies = {}
|
||||
@response = nil
|
||||
end
|
||||
|
||||
def get(path, params: nil, headers: {}, remote_addr: '127.0.0.1')
|
||||
process('GET', path, params: params, headers: headers, remote_addr: remote_addr)
|
||||
end
|
||||
|
||||
def head(path, params: nil, headers: {}, remote_addr: '127.0.0.1')
|
||||
process('HEAD', path, params: params, headers: headers, remote_addr: remote_addr)
|
||||
end
|
||||
|
||||
def delete(path, params: nil, headers: {}, remote_addr: '127.0.0.1')
|
||||
process('DELETE', path, params: params, headers: headers, remote_addr: remote_addr)
|
||||
end
|
||||
|
||||
def post(path, params: nil, headers: {}, remote_addr: '127.0.0.1')
|
||||
process('POST', path, params: params, headers: headers, remote_addr: remote_addr)
|
||||
end
|
||||
|
||||
def patch(path, params: nil, headers: {}, remote_addr: '127.0.0.1')
|
||||
process('PATCH', path, params: params, headers: headers, remote_addr: remote_addr)
|
||||
end
|
||||
|
||||
def put(path, params: nil, headers: {}, remote_addr: '127.0.0.1')
|
||||
process('PUT', path, params: params, headers: headers, remote_addr: remote_addr)
|
||||
end
|
||||
|
||||
def follow_redirect!
|
||||
location = response && response.location
|
||||
raise 'no redirect to follow (last response was not a 3xx with a Location header)' unless location
|
||||
|
||||
uri = URI.parse(location)
|
||||
target = uri.path.to_s
|
||||
target = "#{target}?#{uri.query}" if uri.query
|
||||
get(target)
|
||||
end
|
||||
|
||||
def cookies
|
||||
@cookies.dup
|
||||
end
|
||||
|
||||
def clear!
|
||||
@cookies = {}
|
||||
@response = nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
QUERY_METHODS = %w[GET HEAD DELETE].freeze
|
||||
|
||||
def process(method, path, params:, headers:, remote_addr:)
|
||||
path_part, existing_query = path.to_s.split('?', 2)
|
||||
content_type = nil
|
||||
body = ''
|
||||
query = existing_query
|
||||
|
||||
if params
|
||||
if QUERY_METHODS.include?(method)
|
||||
encoded = encode_params(params)
|
||||
query = [existing_query, encoded].compact.reject(&:empty?).join('&')
|
||||
elsif params.is_a?(String)
|
||||
body = params
|
||||
else
|
||||
body = encode_params(params)
|
||||
content_type = 'application/x-www-form-urlencoded'
|
||||
end
|
||||
end
|
||||
|
||||
env = build_env(method, path_part, query, body, headers, content_type, remote_addr)
|
||||
status, response_headers, body_parts = @app.call(env)
|
||||
@response = TestResponse.new(status, response_headers, body_parts)
|
||||
absorb_cookies(@response)
|
||||
@response
|
||||
end
|
||||
|
||||
def build_env(method, path_part, query, body, headers, content_type, remote_addr)
|
||||
env = {
|
||||
'REQUEST_METHOD' => method,
|
||||
'PATH_INFO' => path_part,
|
||||
'QUERY_STRING' => query.to_s,
|
||||
'rack.input' => StringIO.new(body || ''),
|
||||
'CONTENT_LENGTH' => (body ? body.bytesize.to_s : '0'),
|
||||
'REMOTE_ADDR' => remote_addr,
|
||||
'SERVER_NAME' => 'test.host',
|
||||
'rack.url_scheme' => 'http'
|
||||
}
|
||||
env['CONTENT_TYPE'] = content_type if content_type
|
||||
|
||||
headers.each do |name, value|
|
||||
key = name.to_s
|
||||
case key.downcase
|
||||
when 'content-type' then env['CONTENT_TYPE'] = value
|
||||
when 'content-length' then env['CONTENT_LENGTH'] = value.to_s
|
||||
else env["HTTP_#{key.upcase.tr('-', '_')}"] = value
|
||||
end
|
||||
end
|
||||
|
||||
cookie_header = cookie_header_value
|
||||
env['HTTP_COOKIE'] = cookie_header if cookie_header
|
||||
env
|
||||
end
|
||||
|
||||
def cookie_header_value
|
||||
return nil if @cookies.empty?
|
||||
|
||||
@cookies.map { |name, value| "#{name}=#{value}" }.join('; ')
|
||||
end
|
||||
|
||||
def absorb_cookies(response)
|
||||
response.set_cookies.each { |name, value| @cookies[name] = value }
|
||||
end
|
||||
|
||||
def encode_params(params, prefix = nil)
|
||||
case params
|
||||
when Hash
|
||||
params.map { |key, value| encode_params(value, prefix ? "#{prefix}[#{key}]" : key.to_s) }
|
||||
.join('&')
|
||||
when Array
|
||||
params.map { |value| encode_params(value, "#{prefix}[]") }.join('&')
|
||||
else
|
||||
"#{URI.encode_www_form_component(prefix.to_s)}=#{URI.encode_www_form_component(params.to_s)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user