Skip to content

Instantly share code, notes, and snippets.

@andrasio
Forked from JoshCheek/Readme.md
Last active August 29, 2015 14:21
Show Gist options
  • Save andrasio/f7ae8fd80bdc9d845185 to your computer and use it in GitHub Desktop.
Save andrasio/f7ae8fd80bdc9d845185 to your computer and use it in GitHub Desktop.
require_relative 'fake_sinatra'
class MyApp < FakeSinatra::Base
get '/greet' do
%Q'<DOCTYPE html!>
<html>
<head><title>Welcome!</title></head>
<body>
#{"<p>Hello, #{params[:name]}</p>" if params[:name]}
<form action="/greet" method="get">
<label for="name_field">Name:</label>
<input type="text" id="name_field" name="name" />
</form>
</body>
</html>
'
end
end
run MyApp
module FakeSinatra
class Base
def self.call(env)
instance = run controller_for(env),
env,
params_from(env)
instance.rack_response
end
def self.controller_for(env)
request_method = env['REQUEST_METHOD'].downcase.intern
request_path = env['PATH_INFO']
*, controller = routes.find do |method, path, controller|
method == request_method && path == request_path
end
controller || lambda { |*args| status 404 }
end
def self.params_from(env)
params = Hash.new { |h, k| h[k.to_s] if k.kind_of? Symbol }
params.merge! parse_urlencoded_params(env['QUERY_STRING'])
has_body = env['REQUEST_METHOD'] != 'GET'
is_urlencoded = (env['CONTENT_TYPE'] == 'application/x-www-form-urlencoded')
content_length = env['CONTENT_LENGTH']
if has_body && is_urlencoded && content_length
urlencoded = env['rack.input'].read(content_length.to_i)
params.merge! parse_urlencoded_params(urlencoded)
end
params
end
def self.parse_urlencoded_params(urlencoded)
urlencoded.split('&')
.map { |kv| kv.split '=', 2 }
.to_h
end
def self.run(controller, env, params)
instance = new env, params
catch :finished do
body = instance.instance_exec &controller
instance.body body
end
instance
end
def self.routes
@routes ||= []
end
def self.get(path, &controller)
routes << [:get, path, controller]
end
def self.post(path, &controller)
routes << [:post, path, controller]
end
def self.put(path, &controller)
routes << [:put, path, controller]
end
def self.patch(path, &controller)
routes << [:patch, path, controller]
end
def self.delete(path, &controller)
routes << [:delete, path, controller]
end
attr_reader :params, :env
def initialize(env, params)
@env = env
@params = params
status 200
content_type 'text/html'
end
def response_headers
@response_headers ||= {}
end
def response_status
@status
end
def response_body
@response_body ||= []
end
def rack_response
[response_status, response_headers, response_body]
end
def status(code)
@status = code
end
def body(value)
return unless value.kind_of? String
@response_body = [value]
end
def content_type(type)
response_headers['Content-Type'] = type
end
def redirect(location)
status 302
response_headers['Location'] = location
throw :finished
end
end
end
require_relative 'fake_sinatra'
require 'rack/test'
require 'stringio'
RSpec.describe FakeSinatra do
def session_for(app)
Capybara::Session.new(:rack_test, app)
end
def assert_request(app, method, path, assertions, &overrides)
env = Rack::MockRequest.env_for path, method: method
overrides.call env if overrides
status, headers, body = app.call(env)
assertions.each do |name, expectation|
case name
when :body
expect(body.join).to eq expectation
when :status
expect(status).to eq expectation
when :content_type
expect(headers['Content-Type']).to eq expectation
when :location
expect(headers['Location']).to eq expectation
else
raise "Unexpected assertion: #{name.inspect}"
end
end
end
describe 'routing to a block' do
it 'routes based on the method (get/post/put/patch/delete)' do
app = Class.new FakeSinatra::Base do
get('/') { 'get request to /' }
post('/') { 'post request to /' }
put('/') { 'put request to /' }
patch('/') { 'patch request to /' }
delete('/') { 'delete request to /' }
end
assert_request app, :get, '/', body: 'get request to /'
assert_request app, :post, '/', body: 'post request to /'
assert_request app, :put, '/', body: 'put request to /'
assert_request app, :patch, '/', body: 'patch request to /'
assert_request app, :delete, '/', body: 'delete request to /'
end
it 'routes based on the path' do
app = Class.new FakeSinatra::Base do
get('/a') { 'first' }
get('/b') { 'second' }
end
assert_request app, :get, '/a', body: 'first'
assert_request app, :get, '/b', body: 'second'
end
it 'routes based on both of these together' do
app = Class.new FakeSinatra::Base do
get('/a') { 'first' }
get('/b') { 'second' }
post('/a') { 'third' }
post('/b') { 'fourth' }
end
assert_request app, :get, '/a', body: 'first'
assert_request app, :post, '/a', body: 'third'
assert_request app, :get, '/b', body: 'second'
assert_request app, :post, '/b', body: 'fourth'
end
it 'returns a 404 when it can\'t find a match' do
app = Class.new(FakeSinatra::Base) { get('/a') { '' } }
assert_request app, :get, '/a', status: 200
assert_request app, :get, '/b', status: 404
end
end
describe 'routed code' do
it 'returns the result as the body' do
app = Class.new(FakeSinatra::Base) { get('/') { 'the body' } }
assert_request app, :get, '/', body: 'the body'
end
it 'has an empty body if the block evaluates to a non-string' do
app = Class.new(FakeSinatra::Base) { get('/') { } }
assert_request app, :get, '/', body: ''
end
end
describe 'the block of code' do
it 'defaults the content-type to text/html, but allows it to be overridden' do
app = Class.new FakeSinatra::Base do
get('/a') { }
get('/b') { content_type 'text/plain' }
end
assert_request app, :get, '/a', content_type: 'text/html'
assert_request app, :get, '/b', content_type: 'text/plain'
end
it 'allows the status to be set' do
app = Class.new FakeSinatra::Base do
get('/a') { }
get('/b') { status 400 }
end
assert_request app, :get, '/a', status: 200
assert_request app, :get, '/b', status: 400
end
it 'has access to the params' do
app = Class.new FakeSinatra::Base do
get('/a') { "params: #{params.inspect}" }
end
assert_request app, :get, '/a?b=c', body: 'params: {"b"=>"c"}'
end
it 'has a convenience method "redirect", which sets the status, location, and halts execution' do
app = Class.new FakeSinatra::Base do
get('/a') do
redirect 'http://www.example.com'
raise "should not get here"
end
end
assert_request app, :get, '/a', status: 302, location: 'http://www.example.com', body: ''
end
end
it 'gives access to the env' do
app = Class.new FakeSinatra::Base do
get('/a') { "REQUEST_METHOD: #{env['REQUEST_METHOD']}" }
end
assert_request app, :get, '/a?b=c&d=e', body: 'REQUEST_METHOD: GET'
end
describe 'params' do
describe 'parsing params with a Content-Type of application/x-www-form-urlencoded' do
def assert_parses(urlencoded, expected)
actual = FakeSinatra::Base.parse_urlencoded_params(urlencoded)
expect(actual).to eq expected
end
it 'splits them on "&"' do
assert_parses 'a=b&c=d', {'a' => 'b', 'c' => 'd'}
end
it 'splits keys and values on the first "="' do
assert_parses 'a=b=c', {'a' => 'b=c'}
end
end
it 'includes query parms' do
app = Class.new FakeSinatra::Base do
get('/a') { "params: #{params.inspect}" }
end
assert_request app, :get, '/a?b=c&d=e', body: 'params: {"b"=>"c", "d"=>"e"}'
end
context 'from form data' do
let(:app) do
Class.new FakeSinatra::Base do
get('/a') { "params: #{params.inspect}" }
post('/a') { "params: #{params.inspect}" }
end
end
it 'does not read the form data when the request is GET' do
assert_request app, :get, '/a', body: 'params: {}' do |env|
env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
env['CONTENT_LENGTH'] = '7'
env['rack.input'].string = 'a=1'
end
end
it 'does not read the form data when the CONTENT_TYPE is not application/x-www-form-urlencoded' do
assert_request app, :post, '/a', body: 'params: {}' do |env|
env['CONTENT_TYPE'] = 'application/json'
env['CONTENT_LENGTH'] = '7'
env['rack.input'].string = 'a=1'
end
end
it 'does not read the form data when there is no CONTENT_LENGTH' do
assert_request app, :post, '/a', body: 'params: {}' do |env|
env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
env['CONTENT_LENGTH'] = nil
env['rack.input'].string = 'a=1'
end
end
it 'only reads the form data as far as the CONTENT_LENGTH says it should' do
assert_request app, :post, '/a', body: 'params: {"b"=>"c", "d"=>"e"}' do |env|
env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
env['CONTENT_LENGTH'] = '7'
env['rack.input'].string = "b=c&d=eTHIS IS NOT READ"
end
end
end
it 'returns nil if the param doesn\'t exist' do
app = Class.new FakeSinatra::Base do
get('/') { "nonexistent: #{params['nonexistent'].inspect}" }
end
assert_request app, :get, '/', body: 'nonexistent: nil'
end
it 'allows the params to be accessed with a string or a symbol' do
app = Class.new FakeSinatra::Base do
get('/') { "#{params['key']} #{params[:key]}" }
end
assert_request app, :get, '/?key=value', body: 'value value'
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment